In other news, in preparation for making a level editor... The ROM needs to be expanded to allow room for custom levels without messing up any other data in there that the game uses for other purposes.
Wario Land is 512 KB and uses the MBC1 mapper. MBC1 switches PRG-ROM banks by writing the desired bank number to any address in the $2000-$3FFF range, which will then map the corresponding 16 KB region of ROM data to the $4000-$7FFF range.
MBC1 also supports multiple RAM banks, and can support an additional 2 bits for ROM bank addressing for up to 2 MB of PRG-ROM data, but there's a bit of a complication. The ROM bank register is only 5 bits, meaning banks 1-0x1F can be mapped. Times 16 KB, this is 512 KB exactly. For the purpose of switching ROM banks in MBC1, Wario Land uses every available bank without adding programming overhead to access the upper bank index bits.
Fortunately, we lucked out in that the ROM bank register is the only MBC1 register used by Wario Land. It never sets the ROM/RAM switching mode, and never switches RAM banks. The cartridge hardware likely had 512 KB of ROM, 8 KB of RAM, and a friendly smile. Why on earth is this good news? Because it means we can change the setting in the ROM header to use MBC3, which also uses $2000-$3FFF for switching ROM banks (which map into $4000-$7FFF), except it uses a full 7 bits giving access to the entire 2 MB through the same register.
The byte at 0x000147 is a field in the ROM header specifying the mapper type. It's currently 0x03 (MBC1 + SRAM), and we want to change it to 0x13 (MBC3 + SRAM). If you do this yourself, you'll notice the game plays just fine without any trouble. But secretly, we know it's an upgrade.
__________
There are three kinds of variable-length data we'll want to be able to stuff into arbitrary banks and addresses:
- World data
- Door destinations
- Items, objects and enemies
Accessing world data won't require any changes: simply specify a higher bank number in the data and you're good to go.
__________
The table of door definitions is at 0x030F30, aka 0C:4F30. The game has this bank and address hard-coded in bank 0, which is always present in the $0000-$3FFF range:
- When you attempt to enter a door, it pushes the current bank to the stack, swaps to bank C, reads the door destination, saves the door definition address to $A98E, then pulls the original bank off the stack and swaps back to it.
- If the door's destination is not a special-case value (like the exits), the game will swap back to bank C, load the door definition address from $A98E, and process the graphics and teleportation accordingly.
In short, there are three instructions we're interested in here: two that select bank C, and one that selects the table address $4F30. These are located at $073D, $0F44 and $0763, respectively. I'd have to test to be sure, but it looks as though changing these numbers to a new bank and table address will make it work as merrily as can be.
The door table contains 32 pointers for 86 levels, each of which points to a 24-byte structure. 2 * 32 * 86 = 5504 bytes just for the pointer table, and in a 16384-byte bank, that leaves us with 10880 bytes for actual door destination definitions. At 24 bytes each, this means we can have no more than 453 doors in the entire game once all custom levels have been patched.
Storage space notwithstanding, the engine supports up to a theoretical 2752 doors, so that's quite a hit on our total due to a technical limitation. But don't think that 453 is a small number. Think about it: there are 43 levels (the duplicates tend to use the same door definitions), and they can have up to 32 doors each. How many doors does a typical level have, 3 to 6? 6 * 43 = 258; barely over half of what we can fit in. And the exit doors only require 1 byte since they don't technically go anywhere.
__________
Now the level object data is a different story. The ones used in the game pretty neatly fit into 4 KB in bank 7, and the game won't load them from any other bank (in fact, the code that loads them is also in bank 7). The other 12 KB in bank 7 is used for other stuff. Since we need to be able to move this data to accommodate custom levels, the game will need to be reprogrammed a bit.
The object data table is at 0x01C199 (07:4199), and object data itself occupies 0x01EF00 to almost 0x01FFFF. I say almost, because I'm not sure if the data at 0x01FF68 is actually object data. The table contains 43 addresses to the data.
There is a function at 0x01C056-0x01C0A6 (07:4056-07:40A6) that loads the level index from $A804 (which is set by the overworld map), then the address from $4199 accordingly, ultimately loading the object data into $B000-$BFFF. The data is transferred in its packed 4-bit values, meaning 2 blocks per byte. 256 * 32 block levels = 128 * 32 bytes = 4096, aka 0x1000.
The function never pops anything from the stack before it returns at $40A6. Additionally, it isn't called directly by address; it uses the HL register to jump, so the code at $4056 should be modified directly rather than attempt to cause the program to call from a different address.
Those two bytes used for the object data address can be repurposed to work the same way as the world data references: the first byte is the bank number, and the second byte is an index such that $4000 + ThatByte * 2 points to the address of the data. After the game program loads the bytes, the program can be shot down to $7F60 or thereabouts, where some custom code will exist to switch to the target bank.
Since the code switching banks will itself be in the region being switched, there should be copies of the code in every bank that contains object data. This will allow the code to continue to function after a bank switch, where it can load object data into $B000, then switch back to bank 7 to return once it's finished.
Originally posted by Xkeeper
Yeah, that's what we thought about (insert game here) and then we found (insert surprise here).
Fortunately, I am not a betting man.
|