News: 11 March 2016 - Forum Rules
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia, Danke

Author Topic: NES Mapper Conversion  (Read 1415 times)

patrickmollohan

  • Jr. Member
  • **
  • Posts: 5
    • View Profile
NES Mapper Conversion
« on: February 27, 2020, 10:44:48 pm »
Hello,
I have acquired an MMC3-based multicart NES ROM that uses a rather obscure mapper (534) and would like to convert it to a more common mapper for use with other emulators. As I am new to ROM hacking in general, a mapper conversion may not be the best place to start learning, but I would love to figure it out anyway.
With this particular ROM, I edited the iNES header to test other MMC3 multicart mappers. I found that mapper 52, which is supported by most emulators, at least somewhat works (although the menu graphics are slightly glitchy, and the individual games are very corrupted). However, in comparison to the other mappers I've tried, this is a much better start, as the others either don't boot at all, or will boot into one of the individual games (with corrupted graphics) as opposed to the menu like it should. Am I at least on the right track to figuring out what mapper to convert to? I assume I wouldn't be able to patch it to straight MMC3 for better compatibility, correct?
After the appropriate mapper is selected, what is the next step in the process? I have opened the original and modified ROM in FCEUX in order to view the debugger, but mapper 534 is not supported in the emulator (and thus produces nothing useful in the debugger), and I'm not sure if it is okay to view the debugger with just the iNES header changed. If it is, what would I be looking for to fully patch the game? What are the steps involved with this process?
Finally, although I have searched high and low, I could not find any tutorials on mapper conversions, but I would greatly appreciate any general tutorials, advice, tips, etc. on the subject. Anything at all would be really useful!
Thank you for your time!

Disch

  • Hero Member
  • *****
  • Posts: 2785
  • NES Junkie
    • View Profile
Re: NES Mapper Conversion
« Reply #1 on: February 28, 2020, 09:47:03 am »
Unfortunately, I'm not really aware of any comprehensive tutorial on changing mappers (although honestly I haven't really looked for one).  The concept is simple, but it does require a bit of understanding of some NES architecture as well as at least the basics of 6502 assembly, and arguably is at least an intermediate level task.  That's not to say you can't do it as a beginner, but it'll be confusing and difficult, and it'll take you a bit of time to learn everything you need to know in order to pull it off.  But I think even attempting it is a valuable learning experience, so I don't want to discourage you.

Conceptually, you need to do the following:

- Figure out how the old mapper (534) works
- Figure out how the new mapper (52) works
- Find all the code in the original ROM that interacts with the mapper -- and replace it with new code that does the same thing, but using the new mapper instead
- If necessary, cut into the ROM's bootup code and do any necessary prep to configure the new mapper



For a simple example... let's look at some more typical mappers.  Say you want to convert a mapper 1 (MMC1) game to a mapper 4 (MMC3) game.  One of the most common things a mapper does is PRG swapping.  Mapper 1 games will swap out the $8000-BFFF memory region by performing 5 writes to the $E000-FFFF range.  So a typical swap will look like so:

Code: [Select]
; Typical MMC1 swap routine
;  Assuming the desired page is already in A, MMC1 requires regs be written 1 bit at a time (5 writes total)
STA $FFF9   ; write low bit to mapper register
LSR A       ; shift
STA $FFF9   ; write next bit
LSR A       ; repeat until all 5 bits written
STA $FFF9
LSR A
STA $FFF9
LSR A
STA $FFF9

To contrast, to swap on MMC3, you need to write a sort of 'command' to $8000, then write the desired page to $8001.  Additionally, MMC3 swaps the $8000-9FFF and $A000-BFFF ranges independently, so in order to swap the full $8000-BFFF range the same way MMC1 does, you will have to actually do 2 swaps on the MMC3.  ALSO, MMC1 operates on 16K page sizes and MMC3 operates on 8K page sizes, so you'll have to double the page number.

So the MMC3 code that does the same thing might look something like so:

Code: [Select]
ASL A       ; double the page number to transform it form a 16K page to an 8K page

LDX #$06    ; write '6' to $8000, telling MMC3 we want to swap the $8000-9FFF range
STX $8000
STA $8001   ; write page number to $8001, performing the swap

INX         ; +1 to X
STX $8000   ; write '7' to $8000, telling MMC3 we want to swap the $A000-BFFF range
CLC
ADC #$01    ; +1 to A, increasing the desired page number (2nd 8K page in the desired 16K block)
STA $8001   ; perform the swap


A full mapper conversion involves finding EVERY instance of mapper interaction in the ROM and doing this kind of translation.  And while that might sound like a lot, most mapper stuff is usually confined to a small handful of routines.  IE, whenever the game wants to swap, it will jump to a common subroutine --- so you really only need to change that one subroutine in order to change swapping for the entire game.


But now for some GOOD NEWS.  Since you just want to do a multicart conversion, you probably don't need to worry about individual page swapping, or even very much mapper interaction at all. The individual games are all using MMC3 stuff in both the new and old mappers, so you won't have to find or translate any of that.  All you'll need to worry about is the multicart game selection stuff, which there is going to be MUCH LESS of.  AND it's all very likely going to be clumped together in one area of the ROM and accessed once or twice very shortly after bootup and game selection, and then never touched again.  (Once a multicart picks a game, it generally stays in that game permanently)

So a multicart conversion is simpler, but the concept is the same:

- Figure out how the old mapper selects its games
- Figure out how the new mapper selects its games
- Do the translation

Cyneprepou4uk

  • Sr. Member
  • ****
  • Posts: 334
  • I am the baldest romhacker
    • View Profile
Re: NES Mapper Conversion
« Reply #2 on: February 28, 2020, 01:41:12 pm »
iromhacker.ru - NES ROM hacking tutorials for beginners. Please use Google Translate browser extension

patrickmollohan

  • Jr. Member
  • **
  • Posts: 5
    • View Profile
Re: NES Mapper Conversion
« Reply #3 on: February 29, 2020, 11:49:22 am »
Disch, thank you very much for your reply; this is valuable information and I appreciate the time you took to write it all. This does sound like it'll take a bit of effort, but I think I now have a better understanding of what is involved and it doesn't seem like it'll be as difficult as I initially imagined. I think I might attempt a mapper conversion on a game with a simple mapper first, maybe one that already has a mapper conversion so I can double-check my work.
Quote
IE, whenever the game wants to swap, it will jump to a common subroutine --- so you really only need to change that one subroutine in order to change swapping for the entire game.
So would looking for JSR operations be a good place to start in terms of finding these mapper interactions?
Also, I am glad to hear the multicart conversion should be much simpler. Would it matter if the individual games are a mix of MMC3 and NROM?
Cyneprepou4uk, thank you for the link. It is an excellent resource to have on hand!

Disch

  • Hero Member
  • *****
  • Posts: 2785
  • NES Junkie
    • View Profile
Re: NES Mapper Conversion
« Reply #4 on: February 29, 2020, 03:27:14 pm »
So would looking for JSR operations be a good place to start in terms of finding these mapper interactions?

No, JSRs are going to be everywhere in the game.  Just looking for JSRs will get you nowhere.

Start by looking for writes to mapper registers (typically in the $8000-FFFF range, but it depends on the mapper).  A "write" being a STA/STX/STY instruction.  Debugging emus can put write breakpoints on address ranges which will help you find where and when the game is doing that.

For example, load up any MMC3 game in FCEUX and put a write breakpoint on the 8000-FFFF range.  The debugger will likely snap immediately and reveal a bankswap routine.

With a debugger, games will usually (but not always) reveal most/all of their mapper interaction code in the first few seconds after startup.  There will be a bunch of code to configure/set up the mapper right at the start, and then it'll start swapping banks around shortly after that (or even in the midst of it)


Quote
Would it matter if the individual games are a mix of MMC3 and NROM?

No, that won't matter.  In fact NROM is even simpler because there are no mapper regs so there's literally nothing to do.

patrickmollohan

  • Jr. Member
  • **
  • Posts: 5
    • View Profile
Re: NES Mapper Conversion
« Reply #5 on: March 02, 2020, 02:48:01 pm »
Quote
No, JSRs are going to be everywhere in the game.  Just looking for JSRs will get you nowhere.
Oh, okay, yeah I get what you mean. I figured that if it happened towards the beginning of the game, it might be helpful to take a look at the first few JSRs or so, but that makes sense now looking at the debugger.
I do have some progress! According to the Nesdev page for mapper 534, in regards to the $8000-$FFFF range,
Quote
These registers function identically to a normal MMC3, except that the scanline counter latch register ($C000) takes the inverted value (XOR $FF) compared to a regular MMC3.
So I set a breakpoint for any writes to $C000 and got the following section of code:
Code: [Select]
LDY #$00
LDA $0040
STA ($43),Y @ $C000 = #$9B
STA ($49),Y @ $C001 = #$00
STA ($45),Y @ $E001 = #$D8
RTS
I changed the $0040 to $00BF (which is 40 XOR FF), and now there is less corruption on the menu screen. However, it now doesn't display the top part of the screen correctly.
This is what it used to look like by simply changing the mapper in the iNES header:

And after modifying the value:

I feel like this could be progress, although the "Atari Flashback" logo is now corrupted whereas it wasn't before. Based on my understanding of the $C001 and $E001 registers, it shouldn't matter that I changed the value of A since writing any value to $C001 reloads the IRQ counter and writing any value to $E001 enables interrupts, so nothing should have changed functionally. There doesn't seem to be any further writes to $C000 (though I am still new to the debugger, so maybe there is and I'm not using it properly), but there are reads from $C000 such as
Code: [Select]
...
LDA ($08),Y @ $C000 = #$9B
...
Will I need to XOR those as well to fix the logo?
Also, when opening the unmodified ROM in NintendulatorNRS and looking at the PPU viewer, it shows the exact some corruption:
« Last Edit: March 02, 2020, 03:37:38 pm by patrickmollohan »

Disch

  • Hero Member
  • *****
  • Posts: 2785
  • NES Junkie
    • View Profile
Re: NES Mapper Conversion
« Reply #6 on: March 03, 2020, 12:22:16 am »
I changed the $0040 to $00BF (which is 40 XOR FF), and now there is less corruption on the menu screen. However, it now doesn't display the top part of the screen correctly.

That won't work.  You are confusing immediate mode with zero page mode.

Example:
Code: [Select]
LDA #$40   ; <- this will put the actual value $40 into A
LDA $40    ; <- this will read from address $40 in RAM, and put whatever is at that location into A

If you're familiar with C style languages, it's kind of like this:
Code: [Select]
A = 0x40;           // LDA #$40
A = memory[0x40];   // LDA $40

By changing that to $BF, you've just made it read from a completely different area of memory, which is basically nonsense.

You'll could do the XOR/EOR in the code which requires adding a few bytes:

Code: [Select]
LDA $40
EOR #$FF   ; <- add this
STA ($43),Y
... ; etc

Adding bytes can be tough because you'll have to figure out a way to get some free space.  And that.... well.... that might not be easy.  It's not something I can really guide you with.  =/

ALTERNATIVELY, you could track down where that value is being WRITTEN to $40 and change it there.  Hopefully the game is just pulling the value straight from ROM and not actually computing it.  Something like this:

Code: [Select]
LDA $8000,X     ; <- $8000-FFFF range is ROM, so whatever value being read from here can be easily modified
STA $40         ; <- so when it's written to $40, it's already properly adjusted

The downside to this approach is that you'll have to track down ALL the possible values that can be written to $40, and that might be really hard too.  Adding in the EOR next to the register write is probably the more full-proof solution, here.



Keep it up!  Don't be discouraged!  And don't be afraid to ask more questions!  =)

patrickmollohan

  • Jr. Member
  • **
  • Posts: 5
    • View Profile
Re: NES Mapper Conversion
« Reply #7 on: March 03, 2020, 01:05:38 am »
Right! I was debating on whether that was the case or not earlier, but I saw the graphics were slightly fixed and figured I was simply second-guessing myself. Alright, back to the drawingboard I go.
Quote
Adding bytes can be tough because you'll have to figure out a way to get some free space.  And that.... well.... that might not be easy.
So as I was typing the reply to this, I doublechecked the code. There are several lines of FF/Undefined in the code immediately after the RTS in the code section I posted above. Shifted the STA and RTS operations down, inserted the EOR, and what do you know? IT WORKED! The menu is perfect! I still might try the other way you mentioned (for practice and to save an EOR operation), but that is still a result.
Now on with the game selection. I will probably continue this tomorrow/throughout the week, but I did give it a try as I was waiting for the mods to approve my post. Wasn't successful yet in getting the individual games to boot. I noticed the ROM writes to two of the mapper's registers at $6800 and $6803, no writes to $6801 or $6802. However, I think in addition to mixing up the immediate and zero page modes again, I was also a touch confused on the information on those registers. The wiki mentions
Quote
Mask: $E803

D~7654 3210
Does this denote that the value that gets written to $6800/$6803 is inverted? And where does the mask come in play? I set a breakpoint for any reads/writes/executes to that address, and yet nothing happened. Also the line is seemingly missing from the debugger, as in ROM and RAM, it is the second byte of an operation, and not an operation itself.
The chunk of code when selecting "Adventure" looks like this
Code: [Select]
...
LDA #$00
STA $0000 = #$00
LDA $0418 = #$E1
STA $6800 = #$E1
LDA $041B = #$97
STA $6803 = #$97
JMP $E9DA
...
« Last Edit: March 03, 2020, 01:25:18 am by patrickmollohan »

Disch

  • Hero Member
  • *****
  • Posts: 2785
  • NES Junkie
    • View Profile
Re: NES Mapper Conversion
« Reply #8 on: March 03, 2020, 11:35:54 am »
Quote
Mask: $E803

D~7654 3210
---
Does this denote that the value that gets written to $6800/$6803 is inverted? And where does the mask come in play?

The mask means the register can be accessed through other addresses.  IE, if the game writes to $7C9F it would have the same effect as writing to $6803 (because 7C9F AND E803 = 6803)

Games typically don't use mirrored addresses for regs, but to be safe, when looking for reg writes, it is probably best to set write breakpoints on the entire 6000-7FFF range.


The weird "D~" thing does not mean it's inverted.  It just means it's referring to the value (or the "Data") being written to the register.  And the numbers after it are just numbering/labeling the bits for the below diagram.  IE:  the 'N' bit of $6800 is controlled by bit 6 of the written value.



SIDE NOTE / BAD NEWS:
You said before the multicart had NROM games in it, but according to this wiki they're actually GNROM games.  That is a big difference!  GNROM is an entirely different mapper ( https://wiki.nesdev.com/w/index.php/GxROM ).  What's more, it may not be possible to convert GNROM to vanilla MMC3 because GNROM swaps the E000-FFFF region and MMC3 does not.  Meaning you can't fully mimic GNROM behavior with MMC3!    :(

It would depend on the game, and if you're very lucky you might be able to slap together a simple conversion to MMC3, but chances of that are slim.

What you MIGHT be able to do is to split the GNROM games into several "games" on the multicart, and when the GNROM game PRG swaps, use the multicart regs to swap to a different "game" on the fly.  So it's more complicated than I originally suspected.  :(

patrickmollohan

  • Jr. Member
  • **
  • Posts: 5
    • View Profile
Re: NES Mapper Conversion
« Reply #9 on: March 03, 2020, 02:50:14 pm »
Gotcha, that makes more sense now. Thanks for the clarification of that (and of course for being very patient with me throughout this process)! Should the bad news be the end of this project, I feel like I learned quite a bit already thanks to you.
I do have the games as individual ROMs and confirmed they are MMC3 (mapper 4) and NROM (mapper 0), at least based on their iNES headers. I have also reached out to the source of where I got them from for any potential verification on the matter. However, the games seem to have no issues in emulators. Using Super Mario Bros. as an example of NROM and Dragon Power as an example of GNROM, I changed the mappers of each game to 0, 3, 4, 5, 6, 9, and 66 (numbers selected for no reason in particular) in the iNES header and ran the modified games to see the results. Dragon Power only would display a blank screen on the mappers aside from 66, whereas Super Mario Bros. would still be playable on all of the above, with some minor corruption in a couple of the mappers and fairly major corruption on mapper 9. The games listed as NROM behave exactly the same as Super Mario Bros, and not as Dragon Power. Not saying you or the wiki is wrong by any means, just my observations in the spare time I've had today.
Edit:
Yeah this project sounds dead, unfortunately. I'm assuming this is less about the games individually and more about how they were stored/swapped. From my source:
Quote
That is probably the worst ROM for starting to learn mapper conversion, because mapper 534 has quite a few obfuscatory aspects to it. The "GNROM" term used on the mapper 534 wiki page should be more understood to be "GNROM-like" rather than actual GNROM. Mapper 534 has GNROM-like functionality, meaning that the fixed bank at $E000-$FFFF is overcome by making MMC regs 6 and 7 apply both to $8000-$BFFF and $C000-$FFFF, as described before the "Note" on that wiki page. (In addition, there is also the outer bank register that can switch 128 KiB or 256 KiB PRG, which *also* affects the fixed bank.) The best way to understand this is to make some kind of truth table that tells you which address bit will come from which register (MMC3, mapper outer bank, CPU) depending on the CPU and mapper outer bank mode bits.

The problem with converting this thing to mapper 52 is that mapper 52 has no such GNROM-like functionality, so it's an unsuitable choice for a conversion's target mapper number.
Being realistic, this is sounding beyond my capabilities at this point, even with my hand held. Bit bummed about that, but at least I did learn quite a bit, and perhaps others can as well. At some point, I shall revisit this when I have more knowledge and experience; maybe it would be possible to recreate the menu with a more appropriate mapper. In any case, thank you very much, Disch, for all the knowledge you've shared with myself and everyone here! I hope to still make use of it, just perhaps with a different project.
« Last Edit: March 03, 2020, 04:49:37 pm by patrickmollohan »

Disch

  • Hero Member
  • *****
  • Posts: 2785
  • NES Junkie
    • View Profile
Re: NES Mapper Conversion
« Reply #10 on: March 04, 2020, 03:37:16 pm »
C'est la vie.

But it sounds like you came out of this a little wiser and in a better position to tackle your next project.  So it was still time well spent, even if the final product never materialized.   :beer:

PolishedTurd

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: NES Mapper Conversion
« Reply #11 on: March 04, 2020, 09:48:45 pm »
My understanding grows a little more every time you explain mapper conversion to someone. I'm hoping to do a conversion to MMC5 at some point so I can use its awesome power, so I appreciate it.

C'est la vie.

But it sounds like you came out of this a little wiser and in a better position to tackle your next project.  So it was still time well spent, even if the final product never materialized.   :beer: