Not one capture here. Every picture is drawn from the bytes of the ROM, by running in Python the same interpreters the Z80 runs, and checked against the emulator's VRAM: zero differences across the colour and pattern tables, 12,288 bytes. The listing and the numbers are reproducible with make.
There are two different Yie Ar Kung-Fu cartridges in circulation, and this is not hearsay: both are here, and tools/coteja_builds.py collates them instruction by instruction. They are the same program -they share 97.0 % of the instructions- but they do not play the same.
| EASY | HARD | |
|---|---|---|
| sha256 | 644c78c7... | 61c86ad4... |
| how often the opponent reacts | always 1 frame in 32 | 1 in 32, 16 or 8 depending on the level |
| CHEN’s long weapon | lasts 18 frames, grows to 8 | lasts 22, grows to 10 |
| Konami’s hidden mark | does NOT carry it | YES: RC-725 and the title in katakana |
| the listing in this repository | this one | collated only |
So: on the hard cartridge, at the highest level the opponent reacts four times as often as on the easy one, and on the easy one that never happens. The rest is five constants of CHEN’s weapon and a change of shape in three height thresholds.
Two dumps of this cartridge circulate, which the archives call easy and hard. Byte for byte they look nothing alike: 15,618 of 16,384 bytes differ, 95.3%. That number is worth nothing, because both are relocated: one byte more in one place shifts everything behind it, and with it every absolute address.
Collated by INSTRUCTION -replacing with a wildcard every 16-bit operand that falls inside the cartridge- they share 3,798 of the 3,917 instructions, 97.0%. They are the same program.
The easy build lets one frame in 64 and one in 32 through with masks written into the code (and 03fh, and 03eh). The hard build puts eight instructions in front, at those two exact spots, that appear nowhere in the easy one:
ld b,03eh / ld a,(0e056h) / and a / jr z,+7 ld b,01eh / dec a / jr z,+2 / ld b,00eh
and swaps the and 03fh for an and b. The level picks the mask: 0x3E, 0x1E or 0x0E. At the highest level the opponent reacts four times as often, and in the easy build that never happens.
The rest is five constants, all of the third opponent's long weapon: it lasts 22 frames instead of 18 and stretches to 10 instead of 8.
Konami hid its catalogue number and the title in katakana at the end of many cartridges; Manuel Pazos (@ManuelPazosMSX) found it. The hard build has it: RC-725 and the title in katakana, closing on the very last byte.
The easy build does not. In that same gap it carries the title in Latin ASCII, YIE AR KUNG FU, which nothing reads: the cartridge cannot read ASCII, because its alphabet starts A at tile 0x21. What that says about which came first the binary does not tell, and it is not assumed here.
The cartridge has two drawing interpreters. The SCRIPT one (0x442c) writes through the VDP data port and is used for patterns and colours; the LIST one (0x44a6) writes tiles with WRTVRM. Of the cartridge's fourteen long scripts, only one writes at 0x3800: the one at 0x5da4, and what it puts there is the whole temple -the columns, the lanterns, the two-kanji sign, the gong, the windows and the brick floor-, 327 bytes.
The tables at 0x4b9c and 0x4ba6 give a pattern script and a colour script per value of 0xE054, and they are drawn while the screen is being built, so they look like five backdrops. They are not: building the screen with all five and comparing them, some 4,000 bytes of VRAM change, but none of the tiles that change is in use in the backdrop's name table. The temple looks identical with all five.
What changes is the opponent's figure: WANG, TAO, CHEN, LANG and WU, in the same order as their names at 0x6c35.
The MSX cannot mirror a sprite or a tile, so a fighter who faces both ways needs both versions stored. 0x4c3e copies the opponent's tiles from VRAM 0x2880 to 0x2440 flipping every byte: from tile 0x10 to 0x88, that is 0x78 further on.
And the pose decompressor, when it has to mirror a block (0x5454), swaps each tile for its twin by adding 0x78. The trick's two halves, 10 KB apart.
The list at 0x47cc reads PERFECT 3000 and is drawn on row 16 through the normal door, 0x449e, which reads its first two bytes as a VRAM address.
But 0x48ad comes in through 0x449a, which reads no address, with DE two bytes further on -skipping exactly that address- and HL on row 4. And right after it writes the tile for digit 5 over the 3. The same text now reads PERFECT 5000, and what is added is 0x5000 in BCD.
The thirteen scripts from 0x596f run back to back, but the table at 0x4cf0 does not point at the start of each one: it points two bytes further on. Those two bytes are a VRAM address.
It is not a misalignment: the loop at 0x4ca4 comes in through the door that wants the destination in HL and so starts past the two bytes, and the one at 0x5098 comes in through the body of command 0x80, which reads the destination out of the script itself. A count proves it: twenty-four words of the recipe block equal exactly one of those thirteen addresses minus two, and none equals one from the table.
The rule for how many entries a pointer table has -read words while they land inside the cartridge- gives six for the one at 0x6b4b. There are five: the sixth, 0x583A, falls inside a drawing script, and following it as code filled the listing with rst 18h and sbc a,a.
Five is what the index says, which is the opponent, and there are five of those. And the proof that the table ends at 0x6b55 is that 0x4182 does call 0x6b55: code that is called by name starts there.
The jr z at 0x4625 has displacement 8, so it jumps to 0x462F. And 0x462F is not the start of anything: it is the third byte of the jp 0x46cf that begins at 0x462d.
Landing there, the Z80 finds byte 0x46 on its own and runs it for what it is, ld b,(hl), and then falls into the call at 0x4630. The high byte of an address doing duty as a filler instruction, and two bytes saved. That this is so and not a tracing slip is settled by the displacement: it is 0x08 and not 0x09.
0x6d2c runs a ten-byte shift register, 0xE135 to 0xE13E: it takes the bit off the top, drags it through the nine below and feeds it back in at the top, twice per call. It returns the two low bits, a number from 0 to 3.
The seed is ten bytes of ROM, and there are four different sets picked by the level. With that the opponent does not repeat but does not depend on anything outside either: the same play gives the same match, which is exactly what you need to be able to check it.
Twenty-six bytes at 0x73b3, three at 0x73df and eight at 0x74ae decode as coherent Z80 code. Nothing reaches them, and that is not an impression: the 16-bit word was searched across all 16,384 bytes of the cartridge, not just the listing, and no address falls inside. In front of all three there is a jump that steps over them.
Under each picture is where it comes from and what is on it.
00 10 E3 FF 11: a gap, the head, three gaps and fifteen consecutive tiles. Twenty, which is 5 x 4