Font analysis and translation of the 1996 quest - I Have no Mouth, and I Must Scream
Good to all!
Based on the eponymous novel by Harlan Ellison, the game I Have No Mouth, and I Must Scream is one of the darkest quests of all time. The oppressive atmosphere does not let go until the denouement.
The near future. Three superpowers, the USA, Russia and China, each striving to surpass rivals, created supercomputers for waging wars. But they miscalculated. Having united in a single whole, calling itself AM, three supercomputers, using the power given to them by people, wiped humanity from the face of the earth. The computer leaves only five alive, who will serve as toys for endless torture.
Last time I described an 8-bit font , but this time I managed to parse a 1-bit font .
Both types of fonts are not encrypted and not compressed, which greatly simplified the task.
Tools: IDA, dosbox + debugger , winhex, GBS .
KDPV

Problem - find the font among the files.
Obviously, like FONTS, there are no files and folders; there is a scream.res resource file with a size of 75.5 MB.
Using Process Monitor (ex FileMon) with a filter on dosbox, I compiled a table of file access order, offsets and sizes.
First I started looking at the .res file using GBS and spoiling the blocks in scream.res in the order they were called, I found a place to store the font images: I


went over it with my own fonts, it seems from Arial, I didn’t adjust the width and alignment, but I became sure that the game can be played translate, letter codes, matched cp1251.
For drawing characters, Excel will be quite suitable:


Later they prompted an excellent link, the comrades from SCUMMVM picked up the game, there is a manual .
The font is 1-bit, that is, one pixel can specify 8 pixels. A few bytes form a row. Several rows form a height, we get a block in memory (height * row length) bytes or a rectangle with resolution (height X (row * 8)).
Visually in memory and in the game they look like this (green and red stripes show bytes):


The game uses a small R3 font and an interactive R1 font.
ITE_FONTHEADER, 6 bytes are
INT16, c_height is the maximum character height,
INT16 c_width is the maximum character width, the parameter is completely unnecessary,
INT16 row_length is the length of the font image series (in bytes),
then 256 bytes, the index array itself I equated indexes to cp1251 (AYaa - these are indexes 192-255).
INT16 index [256] - the number of bytes from which the character image begins.
Then 256 bytes, an array of character widths:
BYTE width [256] - the width of the character in pixels, it can be any within reasonable limits, from 0 to infinity. Well, up to 255 :)
Then 256 bytes, a flag. As far as I understood from a series of experiments, it sets the left margin, in pixels.
BYTE flag [256] Unknown character flag (either 0 or 1)
Then 256 bytes - tracking how many pixels of the character to draw. Countdown from left to right from 0 to infinity. To the byte.
BYTE tracking [256] - tracking
Following the header, there is a data block (row_length * c_height) bytes long.
The formula for accessing any beginning of a row is
font_data + (row_length * y),
where font_data is the pointer to the start of the font data, y is the number of the row.
Each character takes ((width [index] - 1) / 8) + 1 byte.
After this data, the forum artist produced a beautiful, dialog-like font. In the original, enough bytes were reserved by all sorts of icons and umlauts to fit the original and Russian font. In the end, in the end, even 1 byte remained, which I nullified. I wrote a program for copying blocks from BMP, where the Russian font was drawn in a row, right in the game block.
At the output, I received cp1251 a set of characters, icons, numbers, English and Russian characters: I

started the second font:

Here a jamb happened. In the original block there was very, very little space, that is, 14 stupid characters could be excluded from the proposed set, but this was clearly not enough for 66 Russian letters.
First I tried to play with the number of bytes of the row, thinking that the game initializes the buffers in height and bytes of the row. I looked at winhex that the next font is the next block, but it is not used anywhere in the game. Zeroed the block, increased the length of the row, but received the same set of characters, but with a shift. It turned out that the size of the data buffer is written somewhere else.
I tried to leave the length of the row unchanged, but play with the indices.
I didn’t succeed in any of the undertakings, the garbage climbed. But it turned out, I forgot that at the end of the resource file there is a table of offsets and block sizes, I had to play with it and everything would work without a patch. Exe file)
Then I got confused how to throw out the dos header and remove the LE file, which you can put in the IDA, it joyfully recognizes it as a 32-bit LE file. I opened the file in winhex, found the line "*** NULL assignment detected", went up to MZ and cut the beginning to MZ.
Thanks cracklab.
Then another problem got out, how to match the address in dosbox (looks like 180: 200c61) and the address in IDA (looks like cseg01: 0001AC1F) to see where the code is running and analyze it. The SCUMMVM website offers a rather stupid option, working, but extremely slow. I’m all waiting for IDADOS, when it will be possible to look at the code in the IDA and immediately trace it with the power of dosbox. But for now ... in the IDA, we search for a sequence of about 10 bytes of what we see in dosbox debuger.
For this game, the formula is:
the address in dosbox is 1DFFFE h = the address in the IDA.
At this point, I knew the exact offsets of the font blocks and their sizes. I began to search for these numbers in the IDA with a simple search, but found nothing.
Then, in dosbox, the debuger set an interrupt to position the read pointer (int 21h, ah = 42h, LSEEK). Before calling CX: DX, there must be a value on how much to move the pointer: (CX * 65536) + DX.
After several jumps, I found 2 consecutive calls to read 2 fonts from the game, just those that are used.
DX had the number I needed. I rewound a bit and found a font initialization block that looked something like this:
mov eax, 6 (eventually patched to mov eax, 3)
call InitFont
mov eax, 8
call InitFont
just in the block - this is the 5th and 7th font. After playing with different numbers, I looked at how the unused fonts look in the game: I




looked at the sizes of the font blocks, selected the largest one, moved the small font block there, patched the executable file, made BMP import Russian letters (in bmp 256 colors, just one byte is equal to one pixel, casting a byte into bits is the second thing), corrected the indices, tracking, widths, row lengths, increased the unit height so that the letters above and below do not merge and finally got the final font.
Preserved original characters, numbers, letters, a number of Russian letters were added, including Yeo.
