Skip to content

Latest commit

 

History

History
162 lines (131 loc) · 8.47 KB

File metadata and controls

162 lines (131 loc) · 8.47 KB

The tools, what each one measures, and what came back empty

Plain Python 3, no dependencies. Nothing here needs a disassembler, an image library or a hex editor. Every tool writes to stdout and the saved output of every run is in notes/, so every figure in the documents can be checked without having the game.

Run order, which is also the order the session went in:

python tools/ziplist.py HeroQuest_DOS_EN.zip            > notes/ziplist.txt
python tools/census.py  _work/hero-quest                > notes/census.txt
python tools/mzinfo.py  _work/hero-quest/QUEST.EXE      > notes/mz.txt
python tools/exepack.py _work/hero-quest/QUEST.EXE -o _work/QUEST.unp
python tools/strdump.py _work/QUEST.unp --min 5         > notes/str-exe.txt
python tools/font.py    _work/QUEST.unp                 > notes/font.txt
python tools/lang.py    _work/hero-quest --strict --run 6 > notes/lang.txt
python tools/quest.py   _work/hero-quest                > notes/quest.txt
python tools/gfx.py     _work/hero-quest --codec        > notes/gfx.txt
python tools/audio.py   _work/hero-quest                > notes/audio.txt
python tools/sav.py     _work/hero-quest/QUEST.SAV      > notes/sav.txt
python tools/licence.py _work/hero-quest _work          > notes/licence.txt

What each one measures

ziplist.py — the ZIP central directory, with each entry's MS-DOS date and time re-encoded back into the raw 16-bit words so the timestamp can be read as the filesystem stored it rather than as a library interpreted it. Also compares the local file header against the central directory for every entry, which is the check that eliminates the container from 03-the-1980-stamp.md in one pass. Produces the date histogram, the year histogram and the extension census.

census.py — per-file size, SHA-1, whole-file Shannon entropy, zero fraction, printable fraction, last non-zero offset, and the first sixteen bytes as hex and text. Also checks for byte-identical files. The first-sixteen-bytes column is what first showed that ADLIB.DRV and MT32.DRV share a prologue, that PIC1.VGA and TEXT1.VGA share a header, and that QUEST*.BIN begins with a run of small ascending integers.

mzinfo.py — MZ header arithmetic (declared image size against file size, which is how an overlay is found), the relocation table, entry-point bytes, a 17-signature packer scan, a 4 KiB-window entropy profile and the longest zero run. The packer scan is reported as "N signatures, M hits" because a scan that finds nothing proves nothing; the header arithmetic and the entry-point bytes are the real evidence and they are printed next to it.

exepack.py — identifies and undoes Microsoft EXEPACK. Reads the 18-byte header at cs:0000, checks the RB signature and the 8C C0 05 10 00 stub head, strips the 0xFF padding, and runs the backwards decompression. Reports src_dst_meet, which is the check that catches the one mistake this format invites — see below.

strdump.py — a strings replacement, because this shell has none. Runs of printable bytes with file offsets; --high accepts 0x80–0xFF and reports a census of which high bytes occurred; --dollar filters to $-terminated runs, the DOS INT 21h/09h convention.

font.py — finds the bitmap font by searching for the glyph of 0 (a slashed zero, a very recognisable bit pattern), derives the table base from the character code, finds the end by the first 40-byte zero run, and renders every glyph as ASCII art. Also checks each glyph in 0x60–0x76 for byte-identity against 0x20–0x5F, which is what revealed that seven of them are copies of GM.

lang.py — the five language files: table geometry at both byte alignments, a six-way byte-class census, and the accent census. --strict counts a byte as a literal only when it sits inside a run of at least N drawable bytes, which is necessary because the second byte of a two-byte reference is otherwise indistinguishable from a character. Also prints the longest uncompressed runs per language, which is where GESCHaFTIG and L'AVVERSARIO e MORTO came from.

quest.py — diffs the twenty-four quest files against each other before parsing any one of them: distinct grids, the per-offset distinct-value profile, the constant regions, the tails, and the printable-run census. It prints the 26 × 19 grid because the grid is the finding.

gfx.py — the per-pair EGA/VGA size and entropy table, the naive ratio and the paired ratio side by side, the EGA-only and VGA-only groups, and with --codec a partial decode of every picture file reporting where it stops, plus a raw opcode-range census.

audio.py — the four drivers' entry bytes, zero fractions and pairwise common prefixes (a test for a shared runtime); the banks' module name, order list and 16-byte directory records; and the instrument names after the directory.

sav.pyQUEST.SAV's 32 × 17-byte name slots and 32 × 26-byte records.

licence.py — the same scan run against the CD32 sequel, over every file in this installation, for the names that would have to appear if the licence were written rather than painted. Reports single-byte copyright-sign candidates separately and labels them as noise, because they occur by chance thousands of times in compressed data.

What came back empty, and why that is a result

Zero attributable strings in all four audio drivers. No author, no company, no version, no date, in 22,092 bytes. The same scan over INTRO.EXE returns HASBRO, GAMES WORKSHOP, BRADLEY, GREMLIN, IMAGITEC, WOMACK, GLOSSOP, HARLING in one pass, so the tool works and the drivers are silent.

Zero occurrences of any licence term in any of the forty-two picture files. HASBRO, GAMES WORKSHOP, BRADLEY, MILTON, GREMLIN, IMAGITEC — none of them, anywhere, in 585,807 bytes of graphics. That is the measurement that says the notice is not painted, and it is the direct counterpart of the CD32 sequel's result where the notice is only painted.

Zero byte-identical files. All 84 SHA-1s are distinct.

Zero overlays. All three executables declare an image size exactly equal to their file size.

Zero relocations declared in all three MZ headers — which is not "nothing to see", it is the tell that identified EXEPACK.

Zero paths, drive letters or development directories in any executable string. Every filename in the archive is a bare lower-case 8.3 name. Some executables of this era are full of C:\WORK\...; this one is not, and the only thing that points at a physical machine is a keyboard table (12-open-questions.md, question 9).

Zero files stamped 1980 other than QUEST.EXE, and zero files stamped after 1992 other than QUEST.SAV. Both were checked before either was theorised about.

588 candidate bit layouts for the language-file back-reference model, none of which decoded past eleven bytes, and seven candidate absolute-offset arithmetics, none of which lands on the required expansion. Recorded in 12-open-questions.md so that nobody repeats it.

The one thing that is easy to get wrong

exepack.py prints src_dst_meet for a reason. EXEPACK's stub does not relocate the whole load image before decompressing; the low part of the image is already in place. When the command stream hits its terminate flag, the source and destination pointers are equal, and everything below that point must be copied across verbatim from the packed file. A decoder that stops at the terminate flag and leaves the rest zero produces an output of exactly the right declared length, containing plausible code in its upper region, and missing 15–25 % of the program:

                   QUEST.EXE   INTRO.EXE   HERO.EXE
verbatim head        12,273       2,722        151
of unpacked size     83,504      33,104        304
missing if ignored    14.7 %       8.2 %      49.7 %

The first run of this session produced exactly that and it looked fine. src_dst_meet being True in all three files is what proves the copy is correct rather than a patch over a bug.

Not committed

The extracted game (_work/) and everything derived from it that is still the game — _work/QUEST.unp, _work/INTRO.unp, _work/HERO.unp — are in .gitignore along with *.EXE *.VGA *.EGA *.BIN *.DRV *.SAV *.zip *.png. No asset, no picture and no executable is in this repository, packed or unpacked. The text extracts quoted in the documents are quotations, and the saved tool output in notes/ is measurement.