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.
|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||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.|
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.
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
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!)