This is a series of 3 posts explaining the experience of reverse engineering a DOS game. The method used is far from sophisticated. Basically disassembling and inspecting the instructions. While live debuggers or live memory inspection through some emulator would bring you faster to this same conclusions, in this exercise we are only using ndiasm, grep and a hex editor to make modifications.
So if you read the previous entry, This x86 DOS instruction:
FE0EF003 dec byte [0x3f0]
Closed one of my childhood traumas, years later, from a more critic point of view we can make some considerations:
- How great when everything just fits in 64KBs
- So LIFE was stored in a single byte at 0x3f0... What other operations are done with that address location:
$ cat Indy.asm | grep -i \\[0x3f0
0000598F C606F00305 mov byte [0x3f0],0x5
00005C3B FE0EF003 dec byte [0x3f0]
00005C42 803EF00300 cmp byte [0x3f0],0x0
00005CB4 C606F00305 mov byte [0x3f0],0x5
00005D1C FE0EF003 dec byte [0x3f0]
00008649 FE06F003 inc byte [0x3f0]
0000A669 A0F003 mov al,[0x3f0]
0000A679 A0F003 mov al,[0x3f0]
I just always saw a LIFE bar(never actually cared about the count of that bar), but in here I actually see that there were 5 lives. As we can see how this address location is initialized with 0x5 in two locations.
C606F00305 mov byte [0x3f0],0x5
You would be tempted to replace that 0x5 with a higher number, but notice that black rectangle in the UI. This is most likely too intrusive. I would suspect the code is trying to dump as many sprites in the screen depending on the integer represented by 0x3f0. So most likely we are reading invalid memory.
Here we see a comparison with 0, so this is likely the only place in the game where LIFE is checked to decide if Game is Over or not.
803EF00300 cmp byte [0x3f0],0x0
Here, as far as I remember, each time you completed a chapter, you get a new life. So this instruction must be it:
FE06F003 inc byte [0x3f0]
- What other memory locations are decreasing or getting subtracted around 0x3f0?
$ cat Indy.asm | grep -i dec.*\\[0x3
00005C3B FE0EF003 dec byte [0x3f0]
00005D1C FE0EF003 dec byte [0x3f0]
000081D8 FE0EED03 dec byte [0x3ed]
00008A86 FE0EAA03 dec byte [0x3aa]
00008AAB FE0EAA03 dec byte [0x3aa]
00008B14 FE0EAA03 dec byte [0x3aa]
00008B30 FE0EAA03 dec byte [0x3aa]
$ cat Indy.asm | grep -i sub.*\\[0x3
00007A71 2A06AA03 sub al,[0x3aa]
00007A9F 2A06AA03 sub al,[0x3aa]
00007AD1 2A06AA03 sub al,[0x3aa]
00007F47 2B069403 sub ax,[0x394]
00007F80 2B069403 sub ax,[0x394]
0000813C 802EEF030A sub byte [0x3ef],0xa
00008659 2806EF03 sub [0x3ef],al
00008854 832E9D0308 sub word [0x39d],byte +0x8
00008859 832E9F0328 sub word [0x39f],byte +0x28
00008979 2B06FF03 sub ax,[0x3ff]
00008983 2B1EFF03 sub bx,[0x3ff]
00008C98 812E9403D000 sub word [0x394],0xd0
00009D83 2A06E203 sub al,[0x3e2]
So applying the same method to these instructions, We can see that:
- 0x3AA is storing the time. Each chapter has a limited amount of time which can be increased by collecting torches ... or you can replace FE0EAA03 by 90909090 and don't rush anymore...
- 0x3ED is the number of lashes. Whenever Indy catches the whiplash artifact, he can use it 5 times. If we check operations on this address:
$ cat Indy.asm | grep -i \\[0x3ed
0000597A A2ED03 mov [0x3ed],al
00005A2E C606ED0300 mov byte [0x3ed],0x0
000081D1 803EED0300 cmp byte [0x3ed],0x0
000081D8 FE0EED03 dec byte [0x3ed]
0000877C C606ED0305 mov byte [0x3ed],0x5
0000B80E 8A16ED03 mov dl,[0x3ed]
0000B857 8A16ED03 mov dl,[0x3ed]
0000B887 8A16ED03 mov dl,[0x3ed]
We can make the same assumptions as with LIFE. We can see how it gets initialized to 0, or to 5 (presumably when the artifact is caught). Likewise, we see in the same way comparison to 0 (to detect that Indy cannot use the whiplash anymore). And finally, the decreasing instruction.
So replacing FE0EED03 by 90909090 will give you infinite whiplash hits... But I am not sure how this can affect the gameplay in other chapters.
You could still change the 0x5 of:
C606ED0305 mov byte [0x3ed],0x5
To a number bigger than 0x5...like 0xFF(255) But the game might not be able as well to render that number on the dash, so this could be conflicting (not tested).
The actions described here might be seeing as hack & cheat a video game. But the message pretended is that by checking the operations performed over the memory locations, we are identifying what are doing whole portions of ASM code. So we can see this as a reverse engineering process.
- 0x3EF is maybe the most interesting one: The ENERGY bar. Although Indy dies immediately in contact with other opponents or falling in uncontrolled areas of the level. Handling this memory portion properly will make Indy immortal by not receiving any damage during the gameplay(not only not decreasing lives as my initial intention...). Someone can beat the game even faster with this one... But this is not as easy as replacing by NOPs...
... TO BE CONTINUED...