ESI Format

From ModdingWiki
Jump to: navigation, search
ESI Format
ESI Format.png
Format typeTileset
HardwareCGA
Max tile count95
PaletteN/A
Tile names?No
Minimum tile size (pixels)0×0
Maximum tile size (pixels)2048×2048
Plane count1
Plane arrangementUnknown
Transparent pixels?Palette-based
Hitmap pixels?No
Metadata?None
Supports sub-tilesets?No
Compressed tiles?No
Hidden data?Yes
Games

ESI Format stores tiled font data. The format has existed since at least 1984 and was used in games by ShareData, First Row Software Publishing, Micromosaics, Northwest Software, and Wesson International.

Files have a 109 byte header, followed by a data array containing the 1-bit bitmap data of all glyphs. This data is encrypted with a rolling XOR cipher. The font can house glyphs for the ASCII characters 0x21-0x7F (95 glyphs), which is the entire printable region of the ASCII table. The space glyph is not included in the bitmap section, and, as far as I can tell, its width is not in the header, so it must be inferred from the font. Likewise, the games that use it seem to apply a padding between characters which also doesn't seem to be present in the header.

Each glyph can have a variable width set by a width map in the header. The height is variable, but is set for the entire font. The height of the actual stored images seems to always be rounded to the next multiple of 8, meaning the files are generally larger than they need to be.

Because the format does not allow for the addition of new characters, if you want to add letters with diacritical marks or unique glyphs, you'll have to overwrite existing glyphs. Many games of the time only used the uppercase letters, so this often leaves the entire 26 lowercase letters available for customization.

Offset Data type Name Description
0x00 INT8 Y-Offset Y-Offset (baseline?) up from bottom of lowest block.
0x01 INT8 Number of glyphs The number of glyphs this file contains.
0x02 INT8 Character shift left 1-based. At 1, characters are normal. At 2 all glyphs are shifted one character to the left (so B would be A). At 0, everything is shifted one to the right (so B would be C).
0x03 BYTE Unknown 0x01 in every file I could find.
0x04 BYTE Unknown Unknown. In all tested files, the value seems too large to be the space character's width.
0x05 INT8 Bitmap height in pixels The number of pixels high to draw each glyph.
0x06 INT16LE Bitmap width in bytes The number of bytes wide each glyph will be.
0x08 UINT16LE Bytes per glyph The number of bytes each glyph requires. Bitmap height in bytes is derived from this and bitmap width in bytes.
0x0A UINT8 Glyph height to baseline The height of all glyphs to the baseline (glyphs like y and p may extend below it).
0x0B BYTE Unknown Might be the amount of pixels to leave between two symbols.
0x0C BYTE Unknown 0x00 in every file I could find.
0x0D UINT8[95] Glyph widths in pixels The width of each glyph in the font (this is a fixed width regardless of the value in number of glyphs).
0x6C BYTE Unknown 0x00 in every file I could find, but one.
0x6D BYTE[] Encrypted bitmaps The encrypted bitmaps for all of the glyphs. The total size is the Number of glyphs x Bytes per glyph.

Encryption

The bitmap data portion of the font file is encrypted with a rolling XOR cipher. The key is 0xA8 0xC3 0xA9 0xB1 0xB9 0xB8 0xB4 0xD7 0xCB 0xCD 0xC1 0xD3 0xCF 0xCE.

Source Code

Font Viewer

The following FreeBASIC code will open, decrypt, and display ESI fonts with their width maps. It works on every ESI font I've been able to find, but, since I still haven't figured out some of the header values, it's still not perfect.

' Opens and displays ShareData's ESI font files.
Dim As String FileName = "ROMAN3.ESI"
Dim As Integer X, Y, BlockX, BlockY
Dim As Integer ByteOffset, BitmapOffset
Dim As Integer White = RGB(255, 255, 255), Gray = RGB(64, 64, 64), DarkBlue = RGB(0, 32, 96)

' Open the file.
Open FileName For Binary As #1

''' Start of header '''

' 0x00 - Unknown.
Dim As UByte Unknown1
Get #1, , Unknown1

' 0x01 - Total number of glyphs.
Dim As UByte TotalGlyphs
Get #1, , TotalGlyphs

' 0x02-0x03 - Unknown, always 1 in every file I could find.
Dim As UByte Always1First
Get #1, , Always1First
Dim As UByte Always1Second
Get #1, , Always1Second

' 0x04-0x05 - Unknown.
Dim As UByte Unknown2
Get #1, , Unknown2
Dim As UByte Unknown3
Get #1, , Unknown3

' 0x06 - Bitmap Width - Number of bytes wide each bitmap is.
Dim As UByte BlockWidth
Get #1, , BlockWidth

' 0x07 - Unknown.
Dim As UByte Unknown5
Get #1, , Unknown5

' 0x08-0x09 - Bytes per Glyph.
Dim As UShort BytesPerGlyph
Get #1, , BytesPerGlyph

' Block Height is derived from block width and BytesPerGlyph
Dim As UByte BlockHeight = (BytesPerGlyph / BlockWidth) / 8

' 0x0A - Height to baseline for all glyphs.
Dim As UByte GlyphsHeight
Get #1, , GlyphsHeight

' 0x0B-0x0C - Unknown.
Dim As UByte Unknown6
Get #1, , Unknown6
Dim As UByte Unknown7
Get #1, , Unknown7

' 0x0D-0x6B - Width of all glyphs.
Dim As UByte GlyphWidths(0 To 94)
For ByteOffset = 0 To 94
	Get #1, , GlyphWidths(ByteOffset)
Next ByteOffset

' 0x6C - Unknown.
Dim As UByte Unknown8
Get #1, , Unknown8

''' End of header '''

' Read the glyph bitmaps.
Dim As Integer BitmapDataSize = (TotalGlyphs * BytesPerGlyph)
Dim As UByte GlyphBytes (0 To BitmapDataSize - 1)
For ByteOffset = 0 To (BitmapDataSize) - 1
	Get #1, , GlyphBytes(ByteOffset)
Next ByteOffset

Close #1

' Setup the XOR encryption key (Thanks, CTPAX-X Team).
Dim As UByte ESIKey(0 To 14)
ESIKey(0) = &hA8
ESIKey(1) = &hC3
ESIKey(2) = &hA9
ESIKey(3) = &hB1
ESIKey(4) = &hB9
ESIKey(5) = &hB8
ESIKey(6) = &hB4
ESIKey(7) = &hD7
ESIKey(8) = &hCB
ESIKey(9) = &hCD
ESIKey(10) = &hC1
ESIKey(11) = &hD3
ESIKey(12) = &hCF
ESIKey(13) = &hCE

' Decrypt the bitmap data (the header is not encrypted).
Dim As Integer Position = 0
For ByteOffset = 0 To BitmapDataSize - 1
	GlyphBytes(ByteOffset) = GlyphBytes(ByteOffset) XOR ESIKey(Position)

	Position = Position + 1
	If Position = 14 Then Position = 0
Next ByteOffset

' Draw the grid.
Dim As Integer ScreenPadding = 8
Dim As Integer Cols = 8, Rows = (96 / Cols)
Dim As Integer BoxPadding = 2
Dim As Integer GridWidth = 1
Dim As Integer BoxWidth = (BlockWidth * 8) + (BoxPadding * 2) + GridWidth
Dim As Integer BoxHeight = (BlockHeight * 8) + (BoxPadding * 2) + GridWidth

ScreenRes BoxWidth * Cols + (ScreenPadding * 2), BoxHeight * Rows + (ScreenPadding * 2), 32

For X = 0 To Cols		' Vertical Lines
	Line (ScreenPadding + (X * BoxWidth), ScreenPadding)-(ScreenPadding + (X * BoxWidth), ScreenPadding + (BoxHeight * Rows)), Gray
Next X
For Y = 0 To Rows		' Horizontal Lines
	Line (ScreenPadding, ScreenPadding + (Y * BoxHeight))-(ScreenPadding + (BoxWidth * Cols), ScreenPadding + (Y * BoxHeight)), Gray
Next Y

' Draw the glyphs.
Dim As Integer Row, Col, XOffset, YOffset, Char
Row = 0
Col = 1
For Char = 0 To TotalGlyphs - 1
	XOffset = ScreenPadding + (BoxWidth * Col) + BoxPadding
	YOffset = ScreenPadding + (BoxHeight * Row) + BoxPadding

	' Draw the width box.
	If GlyphWidths(Char) > 0 Then
		Line (XOffset, YOffset)-(XOffset + GlyphWidths(Char), YOffset + GlyphsHeight), DarkBlue, BF
	End If
	
	BitmapOffset = Char * BytesPerGlyph
	For BlockY = 0 To BlockHeight - 1
		For Y = 0 To 7
			For BlockX = 0 To BlockWidth - 1
				For X = 0 To 7
					If Bit(GlyphBytes(BitmapOffset), 7 - X) = -1 Then
						PSet (XOffset + X + (BlockX * 8), YOffset + Y + (BlockY * 8)), White
					End If
				Next X
				BitmapOffset = BitmapOffset + 1
			Next BlockX
		Next Y
	Next BlockY

	' Determine the next box in which to draw.
	Col = Col + 1
	If Col > (Cols - 1) Then
		Col = 0
		Row = Row + 1
	End If
Next Char

Sleep

Credits

This encryption was cracked by CTPAX-X Team, TheAlmightyGuru worked out most of the format, wrote the viewer, and did the game usage research. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)