This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Metroid ported to use the MMC3.

Metroid ported to use the MMC3.
by on (#132521)
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.
Re: Metroid ported to use the MMC3.
by on (#132535)
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.
by on (#132540)
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.

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.
Re: Metroid ported to use the MMC3.
by on (#132550)
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.
by on (#132551)
That's a really neat disassembly.

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.


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.
by on (#132556)
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.

Or write the bank info to a known RAM location together with the bank switch.

(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. :))
Re: Metroid ported to use the MMC3.
by on (#132557)
Memblers wrote:
That's a really neat disassembly.
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.
Re: Metroid ported to use the MMC3.
by on (#132558)
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:
Code:
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.
by on (#132564)
thefox wrote:
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.

Or write the bank info to a known RAM location together with the bank switch.

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).
Re: Metroid ported to use the MMC3.
by on (#132567)
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.
Re: Metroid ported to use the MMC3.
by on (#132569)
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/

Sik wrote:
thefox wrote:
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.

Or write the bank info to a known RAM location together with the bank switch.

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).


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.
Re: Metroid ported to use the MMC3.
by on (#132570)
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
Re: Metroid ported to use the MMC3.
by on (#132575)
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.
by on (#132577)
Memblers wrote:
Safe for MMC3 at least, if it's just one write to swap banks.

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).

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.
Re: Metroid ported to use the MMC3.
by on (#132585)
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.

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.
Re: Metroid ported to use the MMC3.
by on (#132586)
snarfblam wrote:
... it checks the flag set by the NMI and if set, then runs the NMI routine.


That is how I was doing it too. I always felt there had to be a better way.
Re: Metroid ported to use the MMC3.
by on (#132758)
An update:

The Metroid disassembly is incredibly disorganized. It's pretty clear that multiple people were working on this project, and that the left hand didn't know what the right was doing. Over the past few days of tidying the source code up, I've seen many dead ends - code that looks like it would do something but (a) is never called, or (b) because of the input data it receives, it always exits immediately. Redundant loading of variables, as in the snippet below, is common:

Code:
LC457:
        lda MirrorCntrl                 ;Load MirrorCntrl into A.
        jmp PrepPPUMirror

    ...

PrepPPUMirror:
        lda MirrorCntrl                 ;Load MirrorCntrl into A.
        jmp SetPPUMirror                ;Set mirroring through MMC3 chip.



The AI routines are labyrinth, inefficient, and have inconsistent ways of calling subroutines. Because the code is disorganized and bizarre, I'm uncertain that fully documenting the remaining code would be helpful to anyone. While Metroid was certainly an amazing accomplishment for its time, it wouldn't be a good template for future projects.

That said, I think I've done something pretty neat.

The original disassembly referenced a ton of hardcoded addresses. I've managed to replace all of these in the $C000-$FFFF range (the hardwired last bank that contains the main game engine) with labels - although not very descriptive ones as of yet. If I decide to continue working on this project, I'm going to start cleaning up the GameEngine.asm source code file - perhaps separating it out, placing all related code together. Thus this project would become less of a straight disassembly of Metroid - instead, a better organized and lightly optimized version of that game.

We'll see.
Re: Metroid ported to use the MMC3.
by on (#132761)
pops wrote:
That said, I think I've done something pretty neat.

A quick note: UxROM is not MMC3, TxROM is.
Re: Metroid ported to use the MMC3.
by on (#132769)
Ha. I started using that designation for a different project, months ago, and never double checked it. Thanks. :)
Re: Metroid ported to use the MMC3.
by on (#132772)
About the disorganized code. Exactly 10 years ago, I tried out SnowBro's metedit, and I remembered there was a patch that reduced the size of level data by about 1/4 or something due to metroid storing attribute bits for every object placed when the whole screen was set to a single attribute anyway.

Also the PHP as the first thing in NMI.
Re: Metroid ported to use the MMC3.
by on (#132896)
43110 wrote:
I remembered there was a patch that reduced the size of level data by about 1/4 or something due to metroid storing attribute bits for every object placed when the whole screen was set to a single attribute anyway.
I'm certain this isn't the case - the very first room in Brinstar contains objects using all four BG palettes.

However, on the subject of making level data much smaller:

Each area in Metroid (Brinstar, Tourian, etc.) occupies a single 16kb bank. Almost 50% of these Area banks is occupied by the sound engine and AI routines common to every Area bank. These shared routines are further duplicated in other banks. I estimate that eliminating this shared data would save almost 36kb of rom space! I'm certain it would be possible to move these shared routines into a single shared bank of ~8kb (leaving ~8kb of space left over for new AI/graphics routines), while simultaneously doubling the space available for an Area's rooms/structures/graphics/music data. There's also ~3kb of free space in the Graphics bank.

I've started reorganizing the code in the fixed code bank: instead of a single 9600 line file, there's now separate files for graphics routines, sound effects, PPU writing, room display, and so on. There's still a ton of work left to go, of course. I've also eliminated ~240 bytes of data - and the game still runs fine, as far as I can tell (I haven't done a complete run through yet).
Re: Metroid ported to use the MMC3.
by on (#132897)
pops wrote:
Each area in Metroid (Brinstar, Tourian, etc.) occupies a single 16kb bank. Almost 50% of these Area banks is occupied by the sound engine and AI routines common to every Area bank. These shared routines are further duplicated in other banks.

I read this and I was quite surprised they would waste so much space duplicating routines like this that could easily be reached by a banked-jump, but then I realized that this would have been unavoidable on the FDS, so it kinda makes sense as a port from that version.
Re: Metroid ported to use the MMC3.
by on (#132900)
rainwarrior wrote:
pops wrote:
Each area in Metroid (Brinstar, Tourian, etc.) occupies a single 16kb bank. Almost 50% of these Area banks is occupied by the sound engine and AI routines common to every Area bank. These shared routines are further duplicated in other banks.

I read this and I was quite surprised they would waste so much space duplicating routines like this that could easily be reached by a banked-jump, but then I realized that this would have been unavoidable on the FDS, so it kinda makes sense as a port from that version.

Same as if you first looked at Kid Icarus, you probably wouldn't guess that it needs 8 KB of WRAM.
Re: Metroid ported to use the MMC3.
by on (#132903)
If your game is 80K, there's no hurt in duplication anyway.
Re: Metroid ported to use the MMC3.
by on (#132904)
You know, I've already written a hack to expand Metroid that places each level's object and screen definitions into its own 16k bank.
Re: Metroid ported to use the MMC3.
by on (#132906)
snarfblam wrote:
You know, I've already written a hack to expand Metroid that places each level's object and screen definitions into its own 16k bank.
I didn't know that.
What modifications did you make? Did you have to make any trade-offs?
Re: Metroid ported to use the MMC3.
by on (#132934)
pops wrote:
43110 wrote:
I remembered there was a patch that reduced the size of level data by about 1/4 or something due to metroid storing attribute bits for every object placed when the whole screen was set to a single attribute anyway.
I'm certain this isn't the case - the very first room in Brinstar contains objects using all four BG palettes.
It was basically removing the third byte of each of the object data in room definitions, and letting the attribute byte of the whole room take charge. Not that much of a saver for sure.
Re: Metroid ported to use the MMC3.
by on (#132948)
pops wrote:
What modifications did you make? Did you have to make any trade-offs?


I expanded the ROM from 8 to 16 banks (256 KB), and converted it from CHR RAM to CHR ROM. Each level's object and screen definitions were moved to their own bank, giving you $4000 for them. Removing the CHR and object/screen definitions from level data banks also frees up more room than you'll ever need for palettes, item data, etc. Originally, the only time bank swapping occurred was when switching levels. Since the sound engine (called from NMI) is stored in the original level data bank, when I added bank-swapping code to access the moved object/screen data, I had to add a safeguard to the NMI routine to make sure the proper bank is loaded.

Aside from that, I did some data shuffling to keep the organization of banks more consistent.

The game doesn't do anything tricky with CHR RAM. There are 18 or so predefined (4 KB) CHR arrangements so that made it easy to convert to CHR ROM. Since there were extra CHR ROM banks, I added the ability to cycle banks for animated background tiles (as seen in 'Roids). Alternatively, you could use the extra banks to do CHR swaps and provide different tilesets for different areas of a level for more variety, which is what I'm doing with a hack I'm working on.

The expansion can be applied via my level editor.
Re: Metroid ported to use the MMC3.
by on (#133008)
snarfblam wrote:
... converted it from CHR RAM to CHR ROM.
This is brilliant. I know you can use the MMC3 to split up CHR ROM into 2x2kb and 4x1kb chunks. Do you use the 2kb chunks for BG or sprites?
Quote:
Since the sound engine (called from NMI) is stored in the original level data bank, when I added bank-swapping code to access the moved object/screen data, I had to add a safeguard to the NMI routine to make sure the proper bank is loaded.
Did you consider putting the songs in their own 16kb banks? The sound engine takes up 3414 bytes at present and it might be possible to slim it slightly further. The average song data is about 300 bytes; the largest is the title song at about 1000 bytes. With that in mind, you could put all the music and sound data into its own bank and have tons of room left over for new music.

Do you have any updates on the hack you're working on?
Re: Metroid ported to use the MMC3.
by on (#133022)
Here's a thought: how do you think a music engine like Famitone 2 would compare, feature-wise, with the default Metroid music engine? I think that with a little awesome engineering, it would be possible to replace one with the other...
Re: Metroid ported to use the MMC3.
by on (#133036)
Replacing sound engines is generally not too hard, they tend to be fairly self-contained.
Re: Metroid ported to use the MMC3.
by on (#133049)
An update: I'm continuing to reformat the disassembly so that all the code and data could be moved anywhere and still referenced by any other code. Basically, this goal could be divided into two separate sub-problems:

1. Code in the switchable banks ($8000-$BFFF) reference data and routines in the fixed game engine bank ($C000-$FFFF).
2. Code in the fixed bank ($C000-$FFFF) references data and routines in the title bank (0), area banks (1-5) and the graphics bank (6), which are switched in at runtime ($8000-$BFFF).

The eight banks are assembled separately, and the assembler used by the original disassembly (Ophis) does not have any easy ability to export address/label mapping*. So I wrote my own quick and dirty C# console app that can output a list of addresses, mapped to labels. This solved the first problem from above: at this point, code from banks 0-6 can call any labeled routine in the fixed game engine bank - and these routines can be relocated anywhere (I've seriously reorganized bank 7 - the assembled binary no longer resembles the original METROID ROM). This reorganization has left 303 bytes of contiguous free space in the fixed bank - enough for a few small extra routines.

Now I'm starting to move code around in the other banks. This presents a new problem: the fixed bank references addresses in the switchable banks, and the switchable banks reference addresses in the fixed bank. Mutual dependancies! I decided to solve this problem by adding 'address tables' at the beginning of the switchable banks: instead of the fixed bank jumping to direct addresses in the switchable banks, it will instead jump to indirect addresses from an address table at $8000. Here's an example of what the address tables look like.

I've found that there's a ton of addresses in the switchable banks that haven't been labeled, so at this point I'm going through the code, labeling things, and building the address tables. Once this task is done, I think I will have solved the second problem I listed above - and my "main goal" from the first sentence in this post will be complete.



* A quick note - changing to an assembler that has label exporting capabilities (CA65, perhaps) is beyond the scope of this project. Further, this would not solve the problem of mutual dependancies between the banks. So: more work, problems not solved, sticking with Ophis. As an aside, I've included the assembler as a windows executable within the repository on github, to make assembling the project easier. If anyone using Linux or OSX wants to experiment with it, you can build Ophis from source on those platforms. I'm sure you're used to experimenting. :)
Re: Metroid ported to use the MMC3.
by on (#136562)
A github user named Adam D. has ported the various MetroidMMC3 make scripts to *nix. You'll need WINE and the python environment to run them. https://github.com/ZaneDubya/MetroidMMC3