Engine: memory manager and resource manager
The memory manager
The engine has a Mac-style memory manager (0x48e5ec-0x48f660, decomp/newhandle.cpp onwards) on top of Win32's Global* functions. Its state is a struct at 0x4b9cdc (heap: error, ready flag, purge and grow-zone callbacks, handle table). Handles are 1-based indexes into a table of 8-byte entries, whose first word is bitfields: lock count (7 bits), age (4 bits: 15 when used, counted down by purges, least recently used purged first), state (2), in use, never purge, purgeable. Every block starts with an 8-byte header: 'BM' (0x4d42), its handle, then a 31-bit size and a moveable bit. The header declares the size as a 31-bit unsigned long field and the moveable bit as an unsigned short field, which is the only declaration that reproduces both allocBlock and blockOf. Blocks are reached through a master pointer. For a handle's block that's the Windows handle of a GMEM_MOVEABLE block, whose first word Win32 makes point at the memory, and the engine relies on that. For a fixed block (newPtr) it's a GMEM_FIXED block holding a pointer to just past itself. The game installs a grow-zone procedure (noteOutOfMemory, 0x455013). Its "not enough physical memory" check compares total physical memory (MemoryInfo +0xc, from GlobalMemoryStatus) with 6 MB.
The resource manager
The engine reads Mohawk archives (ScummVM's name for the MHWK format) through a Mac-style resource manager (0x48f660-0x492dc4, decomp/resourcefile.cpp onwards). Its state is a 0x24-byte struct at 0x4b9d8c (resources), cleared by initResources (0x4922c6). That function reads [Resource] fShareReadOnly from MOHAWK.INI, installs a purge procedure, and opens SYSTEM.W32 (else SYSTEM.MHK) from the program's directory.
- Files. Everything is big-endian. A 0x1c-byte header (
'MHWK', size,'RSRC', version 0x100, a "compacted" flag, file size, directory offset, then the directory's size and the file table's size, both 16-bit) is followed by the data, then the directory and the file table. The directory is a type table (u16offset of the names,u16count, then{u32 type, u16 resource table, u16 name table}), and each type's resource table and name table (u16count, then{u16 id or name offset, u16 index}), sorted and searched by bisection. The file table is au32count and 10-byte entries (u32offset, a 24-bit size, a flags byte, and au16that is 0 on disk and holds the loaded data's handle in memory).0x49210band0x4923f3byte-swap the directory and file table in place; they are swapped back to write them out (writeMapHeader,0x492483, which Ghidra split into a 6-byte prologue and0x492489). - Maps. Each open file has a map in a handle, tagged
'RMap': a list of maps, a ring of those with preloads pending, a use count, the file, whether it can be read in the background (from its volume) or only read, the directory's handle, a handle of per-resource counts (loads and preloads), then the file table. A resource ID is a long: its file-table index (from 1) in the high word and its map's handle in the low word. - Loading. A loaded resource's data is in a handle given a handle state of its own (
countHeapUp). The purge procedure (purgeResource,0x490570) lets the memory manager purge such handles (writing back modified data first), marking the entry purged so the data is read again when needed. Other handles go to the previous procedure. - Preloads. Requests (56 bytes from
malloc, tagged'RQRq') read resources ahead of use, a chunk at a time within a time budget (servicePreloads,0x4906b4), or on a thread (preloadThread,0x49138e) for files that can be read in the background. They are kept in rings per map, sorted by position in the file. A request's provider supplies the buffer and hears the outcome; the default one (0x490ff3) reads into a new handle that becomes the resource's data. - Writing. Resources can be added to and removed from the directory (
0x491c64,0x49194e) and rewritten in place or at the end of the file (0x492a3c). Closing a file (0x48f660) can compact it, squeezing out deleted resources and gaps.