ESI Format
Format type | Font |
---|---|
Max glyph count | 95 |
Minimum glyph size (pixels) | 0×0 |
Maximum glyph size (pixels) | 255×127 |
Access mode | Indexed |
Metadata? | None |
Bitmap glyphs? | Yes |
Vector glyphs? | No |
Compressed glyphs? | No |
Hidden data? | Yes |
Games |
ESI Format stores tiled font data. The format was created in 1984 by William K. Mason, and was used in games by ShareData, First Row Software Publishing, Micromosaics, Northwest Software, and Wesson International.
Information about its creator and year were retrieved from the readable version of its encryption key. It shows that the format was created by the same person who made the document editing, formatting and printing software Dot Writer. The "Letterset Design" manual of Dot Writer v4.0 states that the included "Tiny GEAP" component was a tool to edit and create fonts for Dot Writer.[1] However, while Dot Writer v4.0 is from the same creator and year, its fonts have a different format, and are not encrypted. It is possible that ESI was specifically developed for distributing fonts with commercial applications in an 'uneditable' format.
Format
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.
When a bitwise XOR with value 0x80 is performed on each of these bytes (effectively trimming off the highest bit), the result is the ASCII string "(C)1984WKMASON".
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. The encryption key's text contents were discovered by Nyerguds. 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!)
References
- ↑ TRS-80 Manual: Dot Writer v4.0 Letterset Design System (1984) by W.K. Mason, page 4 (page 10 in the pdf document) "GEAP" stands for "Graphics Editor and Programmer." It was the original on-screen drawing and printing system from which today's advanced DOTWRITER and TGEAP evolved. TGEAP allows you to modify any of our lettersets, or create lettersets of your own.
- All file formats
- All font formats
- Bitmap fonts
- Uncompressed fonts
- Sparse fonts
- Indexed fonts
- Empire: Wargame of the Century
- Family Feud
- Fun House
- The Honeymooners
- Jeopardy!
- Jeopardy!: Junior Edition
- Jeopardy!: First Edition
- Jeopardy!: Second Edition
- Jeopardy!: Third Edition
- Jeopardy!: Sports Edition
- Jim Henson's Muppet Adventure No. 1: Chaos at the Carnival
- Rapcon
- Sesame Street: First Writer
- Tracon: Air Traffic Control Simulator
- Tracon 2