This page contains notes for the game Mega Man (DOS).
Reverse engineering source code etc.#
In case I never properly write up my findings on Mega Man PC, here is some disorganized source code and other resources. It has, for example, unpackers for the .scn (maps), .blk (tiles), .frm (sprites), and .sta (fullscreen graphics) formats. There's a program to rip the sound effects as .wav files. Some additional information is documented as code comments, for example the meaning of tile properties (gravity, friction, etc.) in map/main.go. There's some lightly analyzed disassembly of the game code.
git clone https://www.bamsoftware.com/git/megamanpc.git
EXEPACK#
The mm.exe executable is packed with EXEPACK. You have to unpack it in order for the disassembly to make sense. As of 2018-12-07, the suggested OpenKB unexepack.c tool has a bug: it stops processing the compressed relocation tables when it reaches a section that is empty of relocations, ignoring any following sections that may not be empty. mm.exe has non-empty sections following empty sections and is affected by this bug. Instead, either use https://github.com/w4kfu/unEXEPACK (commit eaf8f6a1 or later), or apply this patch to the OpenKB version:
--- unexepack.c.orig
+++ unexepack.c
@@ -330,8 +330,6 @@
int num_entries = READ_WORD(pbuffer, p);p += 2;
- if (num_entries == 0) break;
-
int k;
for (k = 0; k < num_entries; k++) {
Details about the invincibility code#
The way the invincibility cheat code works is, a magic global variable has to have the value 0x01 when the menu finishes. The value of the variable is initially 0x00. Pressing n ORs the variable with 0x02, and pressing d XORs the variable with 0x03. So nd works, because (0x00 | 0x02) ^ 0x03 == 0x01, but dn does not work, because (0x00 ^ 0x03) | 0x02 == 0x03. But other variations will work, like nddd, ddnd, dnnd.
WILEY trigger tile callbacks#
It initially appears that four of the trigger tile callbacks in WILEY.BIN are unused, because their corresponding tile indices point to out-of-bounds tiles. But running in a debugger with breakpoints set on the callbacks shows that they actually are called, in the boss arenas during refights.
Here is the trigger tile table, which starts at offset a88 in WILEY.BIN. The highlighted rows are the ones that at first seem to be unused.
tile index |
tile coordinates |
callback address |
|---|---|---|
0585 |
(13, 7) |
0bb1 |
04c6 |
(22, 6) |
0c89 |
04e0 |
(48, 6) |
0ce3 |
04fa |
(74, 6) |
0d3d |
0512 |
(98, 6) |
0d97 |
052d |
(125, 6) |
0df1 |
0ddd |
(149, 17) |
0e80 |
0001 |
(1, 0) |
0ffa |
088d |
(189, 10) |
10db |
1676 |
(150, 28) |
11c8 |
1806 |
(150, 30) |
11c8 |
17b0 |
(64, 30) |
122a |
17c0 |
(80, 30) |
126a |
17ce |
(94, 30) |
12aa |
1860 |
(40, 31) |
1327 |
0002 |
(2, 0) |
14c8 |
19d2 |
(10, 33) |
15c1 |
22a6 |
(70, 44) |
163a |
2386 |
(94, 45) |
1695 |
2554 |
(156, 47) |
178f |
0003 |
(3, 0) |
1904 |
0004 |
(4, 0) |
19e5 |
2895 |
(189, 51) |
19f5 |
2dee |
(158, 58) |
1af3 |
2c48 |
(136, 56) |
1b49 |
2dd8 |
(136, 58) |
1b49 |
2e81 |
(105, 59) |
1b9f |
2e67 |
(79, 59) |
1c14 |
2e4b |
(51, 59) |
1cee |
This is where each callback is called:
- 0ffa is called after beating Sonic Man
- 14c8 is called after beating Volt Man
- 1904 is called after beating Dyna Man
- 19e5 is called upon entering Dyna Man's arena
This is what I did:
Drives→Import Imagein JPC-RR on the directory containing the game files. Image Type: Hard Disk; Sides: 16; Sectors: 63; Tracks: 16.- Assemble a machine with the image as HDA, then
Drives→dump→HDA, save as hda.img. Drives→dump→FreeDOS (initial fda image), save as freedos.img.qemu-system-i386 -icount shift=10,align=on -drive file=freedos.img,index=0,if=floppy,format=raw -drive file=hda.img,index=3,media=disk,format=raw -boot a -s
Press F5 during startup so as not to run autoexec.bat; otherwise you'll get the error "Packed file is corrupt".gdb -ex 'target remote 127.0.0.1:1234' -ex 'set architecture i8086' -ex 'break *(0x5f870+0x0bb1)' -ex 'break *(0x5f870+0x0ffa)' -ex 'break *(0x5f870+0x14c8)' -ex 'break *(0x5f870+0x1904)' -ex 'break *(0x5f870+0x19e5)' -ex 'continue'
5f870 is the address at which .BIN files are loaded into memory; i.e., the registercs=5f87. 0bb1 is the trigger tile callback for the initial shutter, included as a check to see that the breakpoints were working.- Inside QEMU,
c:andmm.exe. Use thendcheat code and play through to the Wily level. - As each breakpoint is hit, you can
deleteit by number andcontinue.
Source code fragment#
Original design documents contain a printout of a source code listing with handwritten annotations. The same printed page appears at 13:00 in The Gaming Historian's video.
XBSTR STRUCT
XBMP DW 0 ;Explode Block Map Position
XBHP DB 0 ;Hit Points
XBRD DB 0 ;Regular Damage
XBSD DB 0 ;Sonic Damage
XBDD DB 0 ;Dyna Damage (Volt No Damage)
XBSTR ENDS ;6 bytes
FRSTR STRUC
FRXL DW 0 ;x left offset
FRXR DW 0 ;x right offset
FRYT DW 0 ;y top offset
FRYB DW 0 ;y bottom offset
FRXO DW 0 ;x offset
FRYO DW 0 ;y offset
FRWD DW 0 ;UnInitialized width
FROF DW 0 ;UnInitialized :OFS pointer
FRSG DW 0 ;UnInitialized SEG: pointer
FRSTR ENDS ;18 BYTES
SPSTR STRUC
SPCI I ;Sprite Code Index for animating Select 1st
SPXC DW✓ 0 ;x center pos
SPYC DW✓ 0 ;y center pos
SPFR DW✓ 0 ;frame
SPHP DBI 0 ;hit points block # of explode
SPAP DBI 0 ;attack points ✓
SPF0 DBI 0 ;flags 1 04 ????
SPF1 DBI 0 ;flags 2 40 active
SPXL DW 0 ;
SPXR DW 0 ;
SPYT DW 0 ;
SPYB DW 0 ;
XMAX DWI 0 ;init x
XMIN DWI 0 ;Block #screen ptr of explode
YMAX DWI 0 ;init y
YMIN DWI 0 ;
SPHC DB 0 ;UnInitialized hit timer (0) Block # of explode
SPRC DB 0 ;UnInitialized count 1 (0) delay
SPCC DB 0 ;UnInitialized count 2 (0)
SPFC DB 0 ;UnInitialized face (right)
JUMP DW 0 ;UnInitialized jmp flgs (0)
SPXM DW 0 ;UnInitialized x mmntm (0)
SPYM DW 0 ;UnInitialized y mmntm (0)
SPSTR ENDS ;36 bytes
BFSTR STRUC
BFFL DB 0 ;Flags ✓
BFYO DB 0 ;Y Pixel Offset ✓
BFSP DW 0 ;Sprite Offset
BFXB DW 0 ;X Block ✓
BFYB DW 0 ;Y Block ✓
BFXM DW 0 ;X Movement ✓
BFYM DW 0 ;Y Movement ✓
BFBT DB 0 ;Bullet Timer ✓
BFBL DB 0 ;Background Block ✓
BFSTR ENDS ;14 bytes
TBSTR STRUC
TBCN DB 0 ;Count to place block
TBBN DB 0 ;Block to place
The SPSTR data structure mostly corresponds to what is called an "actor" in the HUD script used for a TAS:
function read_actor(base)
local frm_addr = BIN_AREA + jpcrr.read_word(base+4)
return {
x = jpcrr.read_word(base),
y = jpcrr.read_word(base+2),
frm_addr = jpcrr.read_word(base+4),
xmin = jpcrr.read_word_signed(frm_addr),
xmax = jpcrr.read_word_signed(frm_addr+2),
ymin = jpcrr.read_word_signed(frm_addr+4),
ymax = jpcrr.read_word_signed(frm_addr+6),
hp = jpcrr.read_byte_signed(base+6),
damage_amount = jpcrr.read_byte(base+7), -- or could be pickup type: 1=1up 2=etank 3=big health 4=small health 5=big weapon 6=small weapon
flags = jpcrr.read_byte(base+9),
x_bound_max = jpcrr.read_word(base+10),
x_bound_min = jpcrr.read_word(base+12),
y_bound_max = jpcrr.read_word(base+14),
y_bound_min = jpcrr.read_word(base+16),
counter_0 = jpcrr.read_byte(base+18),
counter_1 = jpcrr.read_byte(base+19),
counter_2 = jpcrr.read_byte(base+20),
facing = jpcrr.read_byte(base+21),
extra_0x18 = jpcrr.read_word_signed(base+0x18),
extra_0x1a = jpcrr.read_word_signed(base+0x1a)
}
end