Metroid ported to use the MMC3.
Moderator: Moderators
Metroid ported to use the MMC3.
As a fun experiment, I ported the disassembly of Metroid from the MMC1 to the MMC3. Hope this comes in handy for someone, someday. The source code will recompile using Ophis: https://hkn.eecs.berkeley.edu/~mcmartin/ophis/
The source code is separated by bank, so I set up a rudimentary make system that will put the rom together after all the various banks are assembled by the Ophis Assembler.
The source code is currently ~85% documented. I'd like to see the remainder of the source code documented. Would anyone be interested in taking it on as a group project? You could take on a single undocumented routine, figure out what it does, and then name it and document it. This wouldn't be a difficult endeavor: the undocumented portions of the disassembly have been separated out by routine, and thus each remaining routine would be a nice bite-sized chunk of code to take on - not an unmanageable project for a person who has a rudimentary understanding of the 6502 and a couple of hours to burn. As an example, it took me 2 hours of work to switch the game from MMC1 to MMC3.
I've uploaded the current disassembly to GitHub. If you would like to take on a routine, you should fork the source, make your fixes, and then submit a pull request. I should be able to respond to all pull requests within 24 hours.
The source code is separated by bank, so I set up a rudimentary make system that will put the rom together after all the various banks are assembled by the Ophis Assembler.
The source code is currently ~85% documented. I'd like to see the remainder of the source code documented. Would anyone be interested in taking it on as a group project? You could take on a single undocumented routine, figure out what it does, and then name it and document it. This wouldn't be a difficult endeavor: the undocumented portions of the disassembly have been separated out by routine, and thus each remaining routine would be a nice bite-sized chunk of code to take on - not an unmanageable project for a person who has a rudimentary understanding of the 6502 and a couple of hours to burn. As an example, it took me 2 hours of work to switch the game from MMC1 to MMC3.
I've uploaded the current disassembly to GitHub. If you would like to take on a routine, you should fork the source, make your fixes, and then submit a pull request. I should be able to respond to all pull requests within 24 hours.
Last edited by pops on Thu Aug 14, 2014 8:26 am, edited 2 times in total.
Re: Metroid ported to use the MMC3.
That's funny. I was pondering the idea of converting Metroid to MMC3 just yesterday. It would make it easier to expand ROMs by providing a larger capacity and doing away with the awkward serial interface that causes problems if you want to bank swap on both the NMI and main thread.
Re: Metroid ported to use the MMC3.
Even MMC3's banking writes aren't atomic though (need to write the bank select reg first, then the actual data), so you'd need some safeguards.snarfblam wrote:doing away with the awkward serial interface that causes problems if you want to bank swap on both the NMI and main thread.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Re: Metroid ported to use the MMC3.
Can you even read the current bank? (disclaimer: no idea how MMC3 works) Because if you can't the problem stays anyway, remember that if you bank switch within NMI you'll need to undo the switch when returning.
Re: Metroid ported to use the MMC3.
That's a really neat disassembly.
You can't read the register, but the solution I'd think is to put a marker byte with the bank number in the same position of every bank.Sik wrote:Can you even read the current bank? (disclaimer: no idea how MMC3 works) Because if you can't the problem stays anyway, remember that if you bank switch within NMI you'll need to undo the switch when returning.
Re: Metroid ported to use the MMC3.
Or write the bank info to a known RAM location together with the bank switch.Memblers wrote:You can't read the register, but the solution I'd think is to put a marker byte with the bank number in the same position of every bank.
(In STREEMERZ, I put the marker byte into ROM. However one thing I don't like about that is that if you put the marker as the very first byte of a bank, the beginning of the bank is no longer aligned to a page (can make it slightly more difficult to align code/data that needs to be aligned for whatever reason). And if you put it at the end, you can't put the marker in the last, usually fixed bank because the vectors are there. Now of course the fixed bank doesn't ever (?) need to be banked into a switchable location, but I'd like to provide this option so that the bankswitching routine is consistent.) (EDIT: I realized just now that if the game doesn't use IRQ, the IRQ vector in the last bank can be repurposed for the marker byte.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Re: Metroid ported to use the MMC3.
I agree. I want to point out that 99.99% of this disassembly is not my work: the program was first disassembled by Kent Hansen (aka SnowBro) and was later organized and commented by Nick Mikstas (aka Dirty McDingus). I've only changed the code to use the MMC3, written a build system so it can be easily reassembled, and added a little documentation for the 'area' banks. Additional history on the base disassembly is available online at the metroid database website.Memblers wrote:That's a really neat disassembly.
Re: Metroid ported to use the MMC3.
The source code is currently ~85% documented, which is an amazing accomplishment on the part of Hansen/Mikstas. I'd like to see the remainder of the source code documented. Would anyone be interested in taking it on as a group project?
Anyone who is interested could take one undocumented routine, figure out what it does, and then name it and document it. This wouldn't be a difficult endeavor: the undocumented portions of the disassembly have been separated out by routine, and thus each remaining routine would be a nice bite-sized chunk of code to take on - not an unmanageable project to take on for a person who has a rudimentary understanding of the 6502 and a couple of hours to burn.
I've uploaded the current disassembly to GitHub. If you would like to take on a routine, you should fork the source, make your fixes, and then submit a pull request. I have the next two weeks available and will be back in school after that, so I should be able to respond to all pull requests within 24 hours.
I think the easiest place to start would be naming and documenting the shared routines that are common to each of the five 'Area' ROM banks. There are 33 of these undocumented common routines at the following addresses:
Anyone who is interested could take one undocumented routine, figure out what it does, and then name it and document it. This wouldn't be a difficult endeavor: the undocumented portions of the disassembly have been separated out by routine, and thus each remaining routine would be a nice bite-sized chunk of code to take on - not an unmanageable project to take on for a person who has a rudimentary understanding of the 6502 and a couple of hours to burn.
I've uploaded the current disassembly to GitHub. If you would like to take on a routine, you should fork the source, make your fixes, and then submit a pull request. I have the next two weeks available and will be back in school after that, so I should be able to respond to all pull requests within 24 hours.
I think the easiest place to start would be naming and documenting the shared routines that are common to each of the five 'Area' ROM banks. There are 33 of these undocumented common routines at the following addresses:
Code: Select all
There are a number of routines in this section. I do not know what the purpose
of any of them are.
Unknown routine 8058
... 80B0
80B8
80FB
8134
816E
81B1
81B8
81C0
81C7
81DA
81F6
81FC
820F
822B
8244
82A5
82B3
833F
8395
83F5
8441
8441
844B
84A7
84FE
855A
8563
856B
Unknown routine 8B79
... 8B8C
8B9D
Unknown data 8BD1 (four byts of data)
Unknown routine 8BD5
Unknown data 8D3A (38 bytes of data)Re: Metroid ported to use the MMC3.
That one would not work because you could get NMI after the bank switch but before the RAM write (or vice versa if you do the operations the other way, but point stands).thefox wrote:Or write the bank info to a known RAM location together with the bank switch.Memblers wrote:You can't read the register, but the solution I'd think is to put a marker byte with the bank number in the same position of every bank.
Re: Metroid ported to use the MMC3.
You could save to RAM the last writes in the main program to both $8000 and $8001.
So first you write $8000's RAM shadow, then $8000 itself. Then write $8001's RAM shadow and then the register itself. When your interrupt is done with any bankswitching it has done it can just write the shadowed values back to the real registers and that should avoid any interruptions causing a crash.
Atleast that's one idea that came to my mind. MMC1 seems more problematic since you really don't want anything interrupting the serial write process.
So first you write $8000's RAM shadow, then $8000 itself. Then write $8001's RAM shadow and then the register itself. When your interrupt is done with any bankswitching it has done it can just write the shadowed values back to the real registers and that should avoid any interruptions causing a crash.
Atleast that's one idea that came to my mind. MMC1 seems more problematic since you really don't want anything interrupting the serial write process.
Re: Metroid ported to use the MMC3.
Yeah, I remember seeing SnowBro's disassemblies, I didn't know it was worked on further. Reminds me too of another interesting game disassembly I'd only seen recently, SMB3 http://www.sonicepoch.com/sm3mix/
That's what I thought at first, but if you're running the bankswitch routine itself within a fixed bank (or RAM), and you write the RAM shadow first, then it should be interrupt-safe. Safe for MMC3 at least, if it's just one write to swap banks.Sik wrote:That one would not work because you could get NMI after the bank switch but before the RAM write (or vice versa if you do the operations the other way, but point stands).thefox wrote:Or write the bank info to a known RAM location together with the bank switch.Memblers wrote:You can't read the register, but the solution I'd think is to put a marker byte with the bank number in the same position of every bank.
Re: Metroid ported to use the MMC3.
Actually, coming to think on it, if you always do write RAM write bank like that (no operations in between) it probably doesn't matter because NMI will bank switch and then the main code will bank switch to that bank, effectively making it a no-op (and getting the same result in the end). Requires you to ensure you always do it that way though.
There's also the Master System method where the bank switching registers are mapped in the same addresses as RAM, so whenever you write to a bank register it also writes to RAM simultaneously (and then when you read you just get the value from RAM). This would mean implementing that in the mapper itself though, so unless you can configure MMC3 to do that (is that what you mean with shadow RAM?) it's not an option here =P
There's also the Master System method where the bank switching registers are mapped in the same addresses as RAM, so whenever you write to a bank register it also writes to RAM simultaneously (and then when you read you just get the value from RAM). This would mean implementing that in the mapper itself though, so unless you can configure MMC3 to do that (is that what you mean with shadow RAM?) it's not an option here =P
Re: Metroid ported to use the MMC3.
The NINA board that shares mapper #34 with BNROM has RAM at $6000-$7FFF "under" the bank registers at $7FFD-$7FFF in this manner.
Re: Metroid ported to use the MMC3.
A single write won't swap banks in MMC3. You first need to write the bank select register ($8000-$9FFE, even), then the bank data ($8001-$9FFF, odd).Memblers wrote:Safe for MMC3 at least, if it's just one write to swap banks.
When I said the current bank could be stored in RAM, I assumed some method of synchronization would be used to avoid to usual concurrency issues. This could mean, for example, that the NMI could signal to the bankswitching routine in the main thread that it has interrupted the bank switch, and that the bankswitch needs to be reapplied. Note that in this case it doesn't matter whether NMI would restore the correct bank, because it'd get rewritten anyways right after the NMI leaves and main thread resumes. This method can work with MMC1 as well.
Back when STREEMERZ used MMC1, I did it like this: 1) the bankswitching routine clears a variable "nmiInterrupted" 2) NMI routine saves current bank, resets MMC1, does whatever bankswitches it needs, then restores the saved bank and sets the flag "nmiInterrupted" 3) when the main thread's bankswitching routine notices that "nmiInterrupted" flag has been set, it resets MMC1 and starts the write over.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Re: Metroid ported to use the MMC3.
That actually sounds simpler than the technique I ended up using. I followed someone else's suggestion to set a variable to indicate a bank swap is in progress. If the NMI interrupts the swap, it sets a flag to indicate so and immediately returns. When the bank swap routine finishes, it checks the flag set by the NMI and if set, then runs the NMI routine.thefox wrote: Back when STREEMERZ used MMC1, I did it like this: 1) the bankswitching routine clears a variable "nmiInterrupted" 2) NMI routine saves current bank, resets MMC1, does whatever bankswitches it needs, then restores the saved bank and sets the flag "nmiInterrupted" 3) when the main thread's bankswitching routine notices that "nmiInterrupted" flag has been set, it resets MMC1 and starts the write over.