Mail has been fixed; you should now be able to confirm your e-mail address, watch pages, and the like.
Please report any issues on Discord.

Notes:Mega Man (DOS)

From The Cutting Room Floor
Jump to navigation Jump to search
This page contains changes which are not marked for translation.

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 Image in 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 register cs=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: and mm.exe. Use the nd cheat code and play through to the Wily level.
  • As each breakpoint is hit, you can delete it by number and continue.

Source code fragment

There's a photograph of a source code listing, with handwritten annotations, at 13:00 in The Gaming Historian's video.

 	FRSG 	DW 	0 	;UnInitialized SEG. pointer
???TR	ENDS	;18 BYTES

???TR	STRUC
	SPCI			;Sprite Code Index for animating	Select 1st
	SPXC	DWI	0	;x center pos
	SPYC	DWI	0	;y center pos
	SPFR	DWI	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)
???TR	ENDS	;38 bytes

???TR	STRUC

The data structure defined 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