My favourite game of all time is
Wonder Boy in Monster Land for the Sega Master System. This is primarily because it was one of the first games I played as a kid, but also because it is a flawless masterpiece. Except:
For this. Five screens of horror, because while it doesn't look so bad when it goes well, more often this climb looks like this:
One tiny slip in this chimney and you'll fall down. This happens in stage 12, the final level of the game, whose layout is a maze. Take the wrong path and you'll be sent back to earlier in the stage, often to the start of the stage. This climb is about the half-way mark, and failing here sends you back to the start. There's a timer ticking down which costs you some health whenever the hourglass at the upper right of the screen empties, and my number one cause of game overs in this game is screwing up this climb repeatedly until my health runs out. It's awful (though it must be said that the feeling of relief on reaching the top is pretty good, countered by the despair of subsequently taking a wrong turn and winding up back at the start).
I've recently tried my hand at romhacking, creating
a translation patch for the MSX shooter Pleasure Hearts. That was an interesting process, and I got to thinking: could I hack WBiML to remove or shorten the climb? In my mind, it should be a pretty simple hack: somewhere in the ROM would be the game's map. Whenever you exit a screen, the game must check the map data to see where that exit takes you. I'd just need to find the point where it said "go to the first/second/third screen of the climb" (depending on whether I wanted to skip or shorten the climb) and change it to say "go to the top of the climb" (alternatively, I could potentially change the data for the screen exit to make it think it was exiting a later screen, but let's not go too far into the weeds). The required change was simple. The difficulty would be figuring out which data needed to change, and what it needed to change to.
To help me figure that out, I fired up my preferred SMS emulator, MEKA, to try and figure out its cheat finder. This handy tool will take a snapshot of the simulated master system's memory state which can be compared to its state at a later time to narrow down which memory addresses have changed or not changed. For example, if you wanted to figure out where the current GOLD tally was scored, you could take the reference snapshot just before collecting a coin, collect it, and see which memory values have increased by pressing the > button under REDUCE. This would probably get several values, so you could narrow it further by moving around and doing stuff (but not collecting any money) and then hit == to get only the values which hadn't changed.
Here's one of my attempts at narrowing it down. I've gone up the ladder in the first screenshot to reach the platform in the second screen. Even before the first shot I'd narrowed things to 46 memory addresses, changing the screen narrows it further. Unfortunately none of the memory addresses here are the relevant ones. I dunno what they signify, in fact. Possibly I hit the != button when I meant to hit == at some point, or the reverse. I dunno. I didn't get screenshots of when I actually figured out where the addresses are.
Maybe I should talk about what these memory addresses mean. I am going to be speaking largely from a point of ignorance here, so you should mentally add the phrase "as I understand it" to pretty much any sentence where I say how this stuff works. The master system uses a Z80A CPU, which has 64KB of memory space, identified using the hex addresses 0000h (=0 decimal) through FFFFh (=65,535 decimal). The first 48KB are used to hold data from the ROM - program code, text, pictures. The next 8KB are the system RAM, and the final 8KB are a mirror of the system RAM. The last few bytes of the RAM mirror are used to hold some flags mainly to tell the system which parts of the ROM to put into the first 48KB. It's more complicated than that, but the other stuff doesn't matter for this post.
The values I'm looking for are in the system RAM area - that's where the in-game variables (like "which room are we currently in") are stored. So I'm looking at address space C000 to DFFF, which is where all the values the cheat finder is giving me fit. Like I said, I didn't get screenshots of when I actually found the addresses I was looking for, so I'll just tell you: by going back and forwards between rooms in stage 3, I was able to identify C10E, C10F, and C110 as three addresses that changed with room transitions.
Here we can see this in action using MEKA's memory editor. The highlighed address is C10F, the middle of the three we're looking at. In the first shot the three values are 60 40 60, once I go up a screen they become 1D 00 20. When I go back down they return to:
5D 40 60? Not quite the same. I think maybe the first number is telling the game where I entered the screen - 5D is coming in from the top like I've done here, the 60 I got earlier was for entering from below. Anyway, now that I have an idea where the room data goes, let's try putting it in manually and see where it takes us:
Uh... not where I thought it would. There's a bit more going on in this shot, which I'll get to, but basically I reset the game and overwrote the data for the first screen of the game with the data for that room in level 3. The result: a messed up version of the end of the first stage. It appears that the data I've identified is involved in what gets drawn on screen, but there's more to it.
The other thing happening here is that I've turned on the debugger. This displays the game code and provides ways to manipulate its execution. Mostly I just used it to set breakpoints so that the emulation would pause at certain events and I'd be able to review the code around them. In this case, I've added a breakpoint using the command "b w c10e" - break when address C10E is written to. This would cause the game to stop when the room data was written to memory. I could then look at the data in the memory editor. If C10E-C110 were written as 00 00 5A (what they're set to on the opening screen of the game), I'd know it had just written which screen to display, and at that point I could overwrite it with 5D 40 60 and restart the emulation to jump straight to the room in stage 3 I'd been looking at earlier. Didn't work, obviously.
So at this point I decided to work smart, not hard, and went looking to see if anyone had already figured out how to change the RAM to move around in the game. On
GameHacking.org I found codes for a round select:
RAM Write - Constantly writes 1 to the RAM address 0xC094.
Replace XX and YY with the following for an appropriate round. Sometimes it does not work from the start of the game, but it works when complete the round.
XX YY Round
01 01 Round 1
02 02 Round 2
03 05 Round 3
04 08 Round 4
05 0C Round 5
06 11 Round 6
07 14 Round 7
08 19 Round 8
09 1D Round 9
0A 20 Round 10
0B 22 Round 11
0C 27 Round 12
Although it specifies address C094, for these codes you write to C094 and C095 - the first one is the round number, the second the specific room within that round. It's quite simple, in fact. So I went back into the game, reset, and set a breakpoint for writing to C094. After hitting start on the title screen I twice had 00 written to C094 - not what I was looking for - before it wrote 01. The highlighted line at the middle right of the screen is the instruction after writing to C094, and is about to write 01 to C095 as well. I guess this is as good a time as any to start talking about assembly:
Here's the relevant part of the previous screenshot. The numbers in the left column are addresses in the Z80's memory space. If you remember, these numbers are for where parts of the game's ROM are held for execution. The middle column shows the data held at the address on the left, and as many subsequent locations as required to complete a machine code instruction. On the right are those instructions translated into (somewhat) human readable assembly language. I haven't tried to use assembly since my first year of uni, before I changed courses and got into health care, and my main memory of it from those days is that I wrote what seemed like quite an elegant program to control a model elevator and it didn't work. So it took me a while to figure out what's going on here. I'll try and break it down, starting with the highlighted line:
0E9D: 32 95 C0 >ld (C095h), a
OK. 0E9D is the address in the ROM where this instruction is stored. 32 95 C0 is the machine code - a bunch of ones and zeros that tell the Z80 what to do. "ld (C095h), a" is the version we're supposed to be able to read. 32 in the machine code is "ld xx, a" meaning "load the value of register a into memory address xx", where xx is the next two bytes in the machine code, 95 C0. The Z80 uses little endian encoding, meaning the least significant byte is written first, which is why C095 appears as 95C0. Endianness is confusing and annoying and something we just have to deal with. And what is register a? It's the accumulator - a single byte of memory that the CPU can put data into to work with it, basically. It's one of several registers in the Z80, mostly grouped into two-byte pairs. The MEKA debugger shows their current state at the bottom of this screenshot. A (currently 01) is the first part of AF (currently 0140). There are also BC, DE, HL, IX, and IY. PC is the program counter - the address in memory being executed next (note that its value is 0E9D - the address of the highlighted line). SP is the stack pointer, which identifies the location of values stored temporarily in memory. The flags are the other part of AF - 40, the current value of F, is 0100 0000 in binary, which corresponds with the single active flag. The value of F changes in response to events in the CPU - the Z flag that's currently set indicates that the last arithmetic operation to happen resulted in a zero. This can be used, for example, to check the value of something - if the program wants to know if A is set to 05, it can subtract 05 from A. If the zero flag is set after this, then A must equal 05.
I feel like we're getting into the weeds here. Let's get back to fiddling with the game. At this point in the code, C094 has been set to 01, indicating the first stage. If I hit step to run the next instruction then C095 will also be set to 01, indicating the first screen of the first stage. Then if I go into the memory editor and change C094 and C095 from 01 01 to 02 02 before it does anything else, it should load in the start of stage two instead of stage one. Let's see what happens:
Success! We're in level 2. With our starting equipment, which doesn't include a weapon. Oh well. Actually, looking at the memory editor in the screenshot, I've put in 02 03 instead of 02 02 like in the codes listed above. I just reopened MEKA and tried that again and 02 03 takes me to the second room of stage 2, not the first one shown here. Maybe after reaching stage 2 I tried changing it to 3 to see if it would skip me ahead? I don't remember.
Anyways, now that we know how to choose which room we'll appear in, let's skip to stage 12. The level select codes I got off the internet allowed me to jump to the start of the level by putting in 0C 27. I've also used codes to give myself all the best equipment so that I have some chance of making it through the level, and then made my way to the bottom of the climb. The stage/room values here are 0C 30. Let's start climbing:
Turns out it's real hard to screenshot during the climb without screwing it up, so my shots are of the pause screen and you'll have to take my word that they are from the second and third screens of the climb. The room data in the first shot is 0C 32, which makes sense - two screens on from the previous shot at 0C 30. In the second shot here, it's gone up to... 0C 32? OK, so it turns out things are slightly more complicated than I thought. The game doesn't have vertical scrolling, but it seems that some of the rooms occupy more than one screen of vertical space. The whole climb takes place in room 32. As it turns out, this is why I had identified the wrong area of data when I looked for it myself - C10E through C110 are where the game stores which part of the room I'm in, but not which room. I'd been moving between parts of a single room when I found them. Anyway, in these two shots, the values go from 20 20 A0 to 40 40 C0. The full set of values for the climb are:
00 00 80 - first screen
20 20 A0 - second screen
40 40 C0 - third screen
60 60 E0 - fourth screen
80 80 00 - fifth screen
So each value is going up by 20 with each screen transition. At the top, we reach:
Room 34 (not the highlighted value in the memory editor - address C095 is one row below and three columns to the left from there). So now I know what I need to change in RAM to either skip the climb entirely or to skip some of the screens. The question is, what do I have to change in the ROM to get that into the RAM? Time to look at the code again.
We're skipping ahead quite a bit in time here. I gave up on this project, had an idea, it failed, I gave up again, had another idea, and made some progress. The key realisation was that the debugger has a TRACE function, which gives you a list of recently executed code, as seen here. I've inserted a breakpoint when C095 is written to, then traced the previous instructions. What's shown here is me exiting room 30 (bottom of the climb) and entering room 32 (the climb). We'll start from the bottom and work backwards:
47D2: ld (C095h), a - this is putting the value of register A (currently 32) into memory address C095h. In other words, setting which room will be loaded next. Next I need to figure out why register A is holding the value 32. Let's look at the previous instruction:
47CE: jr +02h - "jr" is jump relative - it moves the program counter forward, in this case by two. The effect of this is to skip the instruction at 47D0 (which would have given a different value to A) and go straight to 47D2. This is not the instruction we are looking for.
47CC: add 26h - this adds 26 (in hex, so 38 decimal) to the value of register A, taking it from 0C to 32. This is part of what we're looking for, but we still need to know why it was set to 0C. Let's go back further:
47CD: ld a, c - this copies the value of register C into register A. So now we know how A came to be 0C and subsequently 32, but why was C carrying 0C? Let's skip back a few lines to when C was last changed:
4680: ld c,0Ch - this loads the hex value 0C into register C. This seems to be the key point. This instruction is stored in the ROM at address 4680. In machine code, it's 0E 0C. 0E is the instruction ld c, and the next value, 0C, is the value it's to be loaded with. If I increase that by 2 to 0E, then C will be loaded with 0E instead, which will be passed to A and then have 26 added to it to make 34 instead of 32, which should result in room 34 loading instead of 32. Let's give it a shot:
Success! In this shot, I've just jumped from room 30 to room 34, skipping the climb entirely, by changing the value stored in the ROM at 4681 from 0C to 0E. Stupidly, I have 4682 highlighted (because the selection automatically moves to the next address after you change something), making 4681 look more like 0B. Anyway, at this point I've achieved half of what I set out to do: I've found the point in the ROM where the game is told to load the climb and changed it to load the room after the climb instead. I'm feeling good. And it's a tiny change: C in binary is 1100. E in binary is 1110. So I've only actually changed a single bit in the ROM. An alternative approach would have been to change 47CD from 26 to 28, but I suspect that the 26 is an offset that would impact every room of stage 12 - the first room of the level is 27, so my guess is that the room numbers for each level are stored starting from 1 and going up, and then the stage offset (26 in this case) is added to them.
OK, now that I've done that, how about making a shortened climb? That's going to require a different method, because the different screens of the climb are all in the same room. I'll need to look at the instructions around changes to the "where in the room" data (C10E through C110) instead of the "which room" data.
Here's me going from the first screen of the climb to the second. I've added a breakpoint when C10E is written to which has stopped me here, I've done a trace on the recently executed instructions, and crucially I've expanded the size of the debugger window so that each instruction gets its own line, greatly improving readability. Let's try to figure out what's going on. Firstly, at this point C10E has gone from 00 to 20, but C10F and C110 are still at 00 and 80 respectively. If we look at the code in the lower window of the debugger, a few instructions back at 37D5 it loaded the value 20 into register C. Then register A was loaded with the value of address C106 (why that address? Dunno, but its current value is zero) and had the value of register C added to it, setting A to 20. Then the value of A was loaded into memory address C10E. The next few instructions are going to load the values of C10F and C110 into A, add C to them, and put the new values back into C10F and C110. So this is how the "where in the room" values are modified.
One method for shortening the climb, then, is to change the value added to C at the start of the above process. I don't have shots showing it, but by going into the ROM and changing 37D6 from 20 to 80, I was able to shorten the climb to two screens - it would start at the bottom, and then by adding 80 instead of 20 it would skip straight to the fifth and final screen of the climb, at the top of which you'd be in the next room. That's not bad, but there are two problems: first, I want to know how to cut the climb to a single screen, and second, the top and bottom screens of the climb are the same type of screen - just a chimney. In normal play the climb alternates between a pure vertical column and a T junction with a path leading off to the right. If I'm cutting to two screens, I'd rather have one of each. So let's keep looking.
In this shot I'm transiting from room 30 (below the climb) into room 32 (the climb itself). I've set it to break when C110 is written with the value 80. What I'm hoping to find here is where the game pulls the "where in the room" data from when I initially enter the room 32. If I can change that so that it starts in either the last or second last room of the climb, then I can shorten the climb as I choose. If we look at the recently executed code here:
4011: ld a,(hl) ; AF:9100 BC:0009 DE:0064 HL:9B0D
4012: ld (C10Eh),a ; AF:0000 BC:0009 DE:0064 HL:9B0D
4015: inc hl ; AF:0000 BC:0009 DE:0064 HL:9B0D
4016: ld a,(hl) ; AF:0000 BC:0009 DE:0064 HL:9B0E
4017: ld (C10Fh),a ; AF:0000 BC:0009 DE:0064 HL:9B0E
401A: inc hl ; AF:0000 BC:0009 DE:0064 HL:9B0E
401B: ld a,(hl) ; AF:0000 BC:0009 DE:0064 HL:9B0F
401C: ld (C110h),a ; AF:8000 BC:0009 DE:0064 HL:9B0F
This is a three times repeated sequence: it loads the value at the memory address stored in register HL (980D at the start) into A, then deposits the value from A into one of the "where in the room" addresses, then increments HL and repeats. So it copies the three values from 9B0D through 9B0F (in the ROM area of the Z80's memory space) into C10E through C110 (in the RAM area of the Z80's memory space). So if I want to change the data that gets loaded at this point, I just need to modify the ROM at address 9B0D. Let's take a look at it:
OK, on the left we have the memory editor showing us address 9B0D in the Z80 (red box). It's 00 00 80, as expected. On the right, I have the ROM opened in a hex editor. Again, location 9B0D is in the red box. But it doesn't match! What gives? This is pretty simple, actually: the Z80's total memory space is 64KB, of which 48KB can be used to hold ROM data. Wonder Boy in Monster Land, as
the box proudly proclaims, is a two-mega cartridge. Which is to say, two megabits, i.e. 256 kilobytes. Too big to fit into the Z80 all at once. Consequently, a mapper is used to load sections of the ROM into the Z80 as needed. 9B0D is within the third mapper slot (slot 2, because it counts from 0). Let's look at the very end of the Z80's memory space:
Here, at address FFFF, the last byte of memory, is the value 09. This byte
tells the Z80 which part of the ROM to load into mapper slot 2. The ROM is divided into 16KB chunks for this, so we're looking for the tenth such chunk (00 being the first), which will be from 24000 through 27FFF in the ROM. We subtract 8000 from 9B0D to get the address within the mapper slot (1B0D), and add that to 24000 to get 25B0D. Let's look in in the ROM:
Bingo! That's 00 00 80 at 25B0D-F. Let's just try changing that in the memory editor:
Et voilà! It's a little hard to convey through a screenshot, but I've set 25B0D through 25B0F to 60 60 E0, and now when I move from room 30 into room 32 it starts me at the fourth room of the climb (I've moved to the platform on the right before taking the screenshot because of the difficulty of screenshotting while on the climb). I also tried changing it to 80 80 00 and it took me to the top room of the climb.
And with that, we're done! I've found and changed the bits I wanted to. I still need to do some testing, make sure this doesn't break anything else. I'm pretty sure the things I'm changing relate specifically to the climb and nothing else in the game, but I can't say I've established any more than the bare minimum understanding of how the game works, so it's very possible I have that wrong. Once I'm happy with it, maybe I'll submit it to ROMHacking.net - a triple pack of ips files: one to skip the climb completely, one to shorten it to one screen, and one to shorten to two screens. Will they accept such a minor hack? Will anyone ever download and use it? Dunno.