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

Author Topic: Help with bankswitching (NES)  (Read 7755 times)

PolishedTurd

  • Full Member
  • ***
  • Posts: 193
    • View Profile
Help with bankswitching (NES)
« on: December 13, 2010, 02:00:22 am »
Thanks to the users and documents here, I've been successfully adding my own code and modifications to Ninja Gaiden.

All the code I've been adding is related to player movement and conditions, and response to controller input. It all has been in the fixed bank (Ninja Gaiden uses mapper MMC1). But I'm out of space there and need more. I've expanded the ROM using KingMike's Nflate program (thanks KingMike!), so I now have oodles of space.

Disch's documents about mappers are thorough and helpful (thanks Disch!), but I don't understand how to move code execution from the fixed bank to a particular page in the swappable bank, which is what I think I want to do. Is that even possible? Can I read from the MMC what page it's currently on, push that on the stack, then jump to one of my newly added pages, execute my code, pull the previous page number off the stack and switch back to it? That would be all too easy, and I've seen nothing in the documents discussing "reading" values from the MMC.

I presume the logical method is to find some code in the swappable bank that executes "often" (since I am adding code that will need to execute in all levels of the game, not just particular places like level data or story text), figure what page that's on, insert code to swap to the page where I've added more code, and then at the end of its execution, swap back to the page the game ordinarily expects. Do I have that right? One problem is that all of the code I've seen and poked around with so far (sprite movement, powerups, player status, score, etc.) lives in the fixed bank. So I would have to jump from the fixed bank to a subroutine in the swappable bank, then swap in the new page, right? And be certain before the switch that that exact page would always be the one swapped in when jumping to it from the fixed bank.

...And that's my question, assuming I have everything correct so far. How can I find a suitable page to perform the swap from, when I need the code to be ready to execute at any moment during gameplay throughout the entire game, and all the code I see doing that lives in the fixed bank?

Trax

  • RHDN Patreon Supporter!
  • Hero Member
  • *****
  • Posts: 578
    • View Profile
    • Trax ROM Hacking
Re: Help with bankswitching (NES)
« Reply #1 on: December 13, 2010, 03:47:53 am »
That is a legitimate question. First, no, you can't read from the MMC1 ports. They are read-only. Usually, the bank you want to return to after you do your stuff is stored into a variable. There is one trick I've seen a few times to determine the current bank number swapped into 8000-BFFF is that each bank has its very first byte numbered accordingly. Bank 0 starts with 00. Bank 1 starts with 01. When you want to know the number of the bank currently swapped, you just load the value at $8000...

For bank switching, usually, the pattern goes like this, in the fixed bank (likely Bank 7):

- Swap Bank X
- JSR to your routine
- Swap back to original bank (usually Bank 0 or a number stored in a variable beforehand)

Now, I presume that you have a lot of stuff stored in the extra space, so you also have a lot of possible offsets that would go in step 2 above. If that's the case and you really have no more space left in the fixed bank, then one solution would be to use some kind of universal index in the newly created empty banks. That would go like this:

- Load A/X/Y with your index
- Swap Bank X (stored in A/X/Y or some other memory)
- JSR to your index selection routine
- Swap back

The index selection routine takes your index value, ASL it, then use the offset to either jump to a new routine (JSR indirect), or to point to a data table. Then you come back with RTS...

Another solution would be to use JMP instead of JSR when you want to jump to your own routine in the new bank. This way, you can save a few bytes in the fixed bank. Now the question is, where you will return to when you RTS from your routine. Well, you just have to push the bytes of the address to return to before doing your bank switching. If you pushed an address in the 8000-BFFF range, you can return to this address with RTS...

Lenophis

  • Discord Staff
  • Hero Member
  • *****
  • Posts: 971
  • The return of the sombrero!
    • View Profile
    • Slick Productions
Re: Help with bankswitching (NES)
« Reply #2 on: December 13, 2010, 08:53:55 am »
First, no, you can't read from the MMC1 ports. They are read-only.
:huh:


https://ff6randomizer.codeplex.com/ - Randomize your FF6 experience!

KingMike

  • Forum Moderator
  • Hero Member
  • *****
  • Posts: 7067
  • *sigh* A changed avatar. Big deal.
    • View Profile
Re: Help with bankswitching (NES)
« Reply #3 on: December 13, 2010, 09:41:57 am »
Look for the game's original bankswap routine and see if it's writing the bank number to RAM before it writes it to the bankswap registers. (use FCEUX, go to the debugger, add a breakpoint for writes to CPU address $A000-BFFF. It'll probably break as soon as you click Run.)
Also note that the MMC1 registers need to be written to 1 BIT at a time, so games will always write to the register 5 times with a bit-shift instruction between each write.
It appears that Ninja Gaiden uses 128KB PRG-ROM and 128 CHR-ROM.
Good, because while you can expand MMC1 to 256KB PRG-ROM easily, expanding to 512KB is not.
"My watch says 30 chickens" Google, 2018

Trax

  • RHDN Patreon Supporter!
  • Hero Member
  • *****
  • Posts: 578
    • View Profile
    • Trax ROM Hacking
Re: Help with bankswitching (NES)
« Reply #4 on: December 13, 2010, 12:55:52 pm »
Ah crap...
Thanks Lenophis for pointing out my stupid mistake...
I meant, MMC ports are write-only...

PolishedTurd

  • Full Member
  • ***
  • Posts: 193
    • View Profile
Re: Help with bankswitching (NES)
« Reply #5 on: January 07, 2011, 03:36:03 pm »
I can't seem to find a place where the current page number is being stored in RAM. The next best solution (please correct me if I'm wrong) seems to be a simple lookup table to match the first few bytes in the bank, and determine the page number that way.

It occurs to me that I ought to know which pages are PRG and which are CHR ROM, so I can narrow my search. What is the best way to do this?  :-[

snarfblam

  • Submission Reviewer
  • Hero Member
  • *****
  • Posts: 593
  • CANT HACK METROID
    • View Profile
    • snarfblam
Re: Help with bankswitching (NES)
« Reply #6 on: January 07, 2011, 05:22:02 pm »
I would look for the code that performs the actual bank swap (set a breakpoint for writes to MMC1 registers). You can check and see whether that code writes the bank being swapped to, and if not, you can modify it to do so.

If the game uses CHR ROMs, you can't swap those into the PRG address space, so you can safely forget they exist. But with MMC1 it's common to have the CHR data mixed into the PRG ROM. In this case there would be no CHR ROMs.

Trax

  • RHDN Patreon Supporter!
  • Hero Member
  • *****
  • Posts: 578
    • View Profile
    • Trax ROM Hacking
Re: Help with bankswitching (NES)
« Reply #7 on: January 10, 2011, 12:44:02 am »
If you are working on Ninja Gaiden, there may be something interesting related to bank switching at 0x1D8D5...

PolishedTurd

  • Full Member
  • ***
  • Posts: 193
    • View Profile
Re: Help with bankswitching (NES)
« Reply #8 on: March 19, 2011, 12:48:03 pm »
OK, I've been working on this and am stumped again.

The Ninja Gaiden ROM has 8 pages of PRG that are 16K each. That's 0x4000.  I've determined that the pages are mapped as follows:

PAGE   ADDRESS
0   0x00010
1   0x04010
2   0x08010
3   0x0C010
4   0x10010
5   0x14010
6   0x18010
7   0x1C010

Since it's MMC1, I thought the last bank was "fixed" in RAM at $C000-$FFFF, and that pages get swapped into the address space of $8000-$BFFF. But I have written code to do some test bank switching, and the results tell me I am way off in my concept of what's happening.

At $FA80 ( =0x1FA90), I execute this code:
Code: [Select]
LDA #00
STA 8000
LSR
STA 8000
LSR
STA 8000
LSR
STA 8000
LSR
STA 8000
RTS

At the last STA instruction, the program counter points to $FA93, but after the instruction, this is now mapped to 0x0FAA3 instead of 0x01FAA3 (3 bytes after where it was before the last write to $8000). So the RTS never happens. The PC lands in garbage and the game crashes. Furthermore, even though I have written #00 to the MMC1 register, the data at $8000 indicates that page 2 is still swapped there (during most of gameplay, at least in level 1, page 2 remains in $8000 and does not change).

Obviously, I am completely wrong about something, or a number of things. I thought code executing in $C000-$FFFF was "fixed" and would not be swapped. What is happening?

Lenophis

  • Discord Staff
  • Hero Member
  • *****
  • Posts: 971
  • The return of the sombrero!
    • View Profile
    • Slick Productions
Re: Help with bankswitching (NES)
« Reply #9 on: March 19, 2011, 12:58:37 pm »
Are you sure that's the register you use when you swap banks?


https://ff6randomizer.codeplex.com/ - Randomize your FF6 experience!

snarfblam

  • Submission Reviewer
  • Hero Member
  • *****
  • Posts: 593
  • CANT HACK METROID
    • View Profile
    • snarfblam
Re: Help with bankswitching (NES)
« Reply #10 on: March 19, 2011, 02:23:08 pm »
I think your issue is the address you are writing to. Check out MMC1 on Nesdev, namely, the addresses for the registers: CHR0 = $A000-$BFFF, CHR1 = $C000-$DFFF, PRG = $E000-$FFFF. I'm not positive, but it looks like if you write a #$00 to $8000, you'll throw the mapper into a different mode where $8000-$FFFF will be mapped as a single 32KB bank. If you're in level 1, bank 2 will be mapped to 0x8010. After you write the register, you'll have $8000-$FFFF mapped to 0x8010-0x1000F, which would explain what you are seeing.

PolishedTurd

  • Full Member
  • ***
  • Posts: 193
    • View Profile
Re: Help with bankswitching (NES)
« Reply #11 on: March 19, 2011, 05:46:09 pm »
Thank you! I thought writing to any address $8000 or above would accomplish the bank switch the way I intended, but I should have read the MMC1 documentation more carefully.

I changed the write address to $FFFF, and now my bank switching code works perfectly. Writing the routines for this had been a serious hindrance to my motivation for the rest of the hack I'm working on, for some reason. I guess I was a little intimidated. Thanks for helping me get back on track.  :beer: