Romhacking.net

Romhacking => Programming => Topic started by: yugisokubodai on May 02, 2022, 07:58:33 AM

Title: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on May 02, 2022, 07:58:33 AM
I'm not used to the Nes. Recently I'm looking at MMC1 game, which stores text in CHR-Rom. The game is 256Kb, the first half is PRG ($0000 - $20000 PC address) and the second half is CHR ($20000 - $3FFFF PC address).

The game has 2 text blocks, the first one locates within bank 15 and the second one is at bank 17 of the CHR.
(First one at $35000 PC address: ($35000 - $20000)/$1000 = 15, second one at $37000 PC address: ($37000 - $20000)/$1000 = 17)
Take a look at the text drawing routine.

*************************************
read_text:
LDA #$15
JSR switchbank1
LDA #$17
JSR switchbank2
LDA {letter_count}
CLC
ADC {text_pointer_value}
TAY
LDA {text_pointer_value}+1
ADC #$00
STA $2006
STY $2006
LDA $2007  //get character value
LDA $2007  //get character value
RTS

switchbank1:
STA $BFFF
LSR
STA $BFFF
LSR
STA $BFFF
LSR
STA $BFFF
LSR
STA $BFFF
RTS

switchbank2:
STA $DFFF
LSR
STA $DFFF
LSR
STA $DFFF
LSR
STA $DFFF
LSR
STA $DFFF
RTS
*************************************
I'm confused at these points.

1/It seems that the game read the same pointer to get 2 different text value from 2 CHR banks at the same time. After that, it writes the value to $2007 during NMI. I don't understand how does it judges the "right" bank to write? When the text from bank $15 is needed and when the other one is needed?

2/How bank switching works? Actually I tried writting #$35 instead of #$15 and it gives the same result.
Then I tried to expand the ROM, then relocate the text to another CHR free bank, but it doesn't work.
For example, after expanding, PC address $60000 (bank $20) has plenty of free space. I tried this but it didn't work.
LDA #$20 //new bank
JSR switchbank1

According to NESdev document, CHR bank 0 (internal, $A000-$BFFF) looks like this.

4bit0
-----
CCCCC
|||||
+++++- Select 4 KB or 8 KB CHR bank at PPU $0000 (low bit ignored in 8 KB mode)
#$15 is %10101, can anyone explain how these bits affect the destine bank?

3/Can we force the game to use PRG instead of CHR?
I put the text in some free PRG bank then tried this but no result given.

read_text:
LDX {letter_count}
LDA {free_PRG_space},X
RTS

That's my problems. Thank you for reading.
Btw, The NES mapping is frustrating than the SNES.
Title: Re: [NES]Understanding MMC1 mapping
Post by: Cyneprepou4uk on May 02, 2022, 09:11:36 AM
1. I don't see any writes to 2007 in your example. If you refer to some other code, then it won't be writing neither to bank 15 nor 17, nor any other bank. It will write somewhere to 2000-3FFF in PPU. Bankswitching is only for 0000-1FFF.

2. Only bits 0-4 matter, that is bytes 00-1F. Higher bits will be ignored.

3. Yes, we can. I can't tell you why your code didn't work exactly, not enough information. I guess reading a single byte is not enough, the game will probably keep reading 2007 later to get more bytes from PPU.
Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on May 02, 2022, 09:36:43 AM
The above code is just a routine to get text value.
Here it is.
JSR read_text //the one I posted before
LDX {character_position}+1
STX $2006
LDX {character_position}
STX $2006
STA $2007

Ofcourse, a single byte is not enough. But this game doesn't read all bytes in a row. It reads 1 character 1 frame.
Title: Re: [NES]Understanding MMC1 mapping
Post by: Cyneprepou4uk on May 02, 2022, 10:46:07 AM
Use debugger, and make sure that your LDA {free_PRG_space},X is loading the same values as the 2nd LDA $2007 from original game.
Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on May 02, 2022, 11:04:19 AM
Ofcourse I'm using a debugger. Obiviously it doesn't the value I want, that's why this topic presents.
Title: Re: [NES]Understanding MMC1 mapping
Post by: Cyneprepou4uk on May 02, 2022, 11:10:58 AM
Your LDA {free_PRG_space},X can only address 256 bytes range. However original game does some calculations with text_pointer_value, I think it uses up to 8192 bytes range. You need post-indexed indirect addressing mode.
Title: Re: [NES]Understanding MMC1 mapping
Post by: KingMike on May 02, 2022, 02:45:59 PM
That or else you need to detect X rollover and increment the high byte.
Actually, you'd still need to do that to have a string longer than 256 bytes.
Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on May 03, 2022, 09:33:00 AM
I know, but this game uses very short text. Each text string is about 30 bytes in Japanese. It doesn't have a page break code. I added one and I don't need a 16 bits index.
What confuses me is why does this text routine read text value from CHR twice. Only the last read gives the right data. Is the first read dummy or something?
And since there're just 4 bits for the bank switch, so how can I use bank $20?
Title: Re: [NES]Understanding MMC1 mapping
Post by: Cyneprepou4uk on May 03, 2022, 10:02:40 AM
The first read is dummy.

Use another mapper for more banks.
Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on May 03, 2022, 10:35:30 AM
Use another mapper? Do you mean to change it by FCEU?
I have a question. Do we have to switch bank during v-blank or something?
Is the new bank lasts forever until we change it?
For example, for the Snes
SEP #$20
LDA #$7E
PHA
PLB

This will change the DB bank in to $7E, so any read like
LDA $2116

will access to $7E:2116, not the Vram access port $002116.
For the NES, I don't know if we change the PRG bank to $05, then any read like
LDA $2007 will read the Vram read the PPUDATA port or just a byte at $2007?
Title: Re: [NES]Understanding MMC1 mapping
Post by: Cyneprepou4uk on May 03, 2022, 10:46:38 AM
Yes, use = change. You can change mapper by any means. But usually it's not as simple as just changing a couple of bytes in header.

Bank lasts forever until changed. You can change it anytime you want.

LDA 2007 reads from whatever PPU address was specified by 2006. Changing PRG banks won't affect it. 2007 is a PPU register.
Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on May 03, 2022, 12:19:44 PM
So it's no point in expanding a MMC1 Rom?
The original one's CHR  ends at bank $1F. After expanding it, now I have ton of free space to bank $3F but how to access these banks?
Title: Re: [NES]Understanding MMC1 mapping
Post by: Cyneprepou4uk on May 03, 2022, 12:23:06 PM
https://www.nesdev.org/wiki/MMC1

QuoteCHR capacity 128K
Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on May 05, 2022, 06:34:16 PM
I have a question about bank swapping. Below is the original routine of the game.

A:00 X:80 Y:00 S:ED P:nvUbdIzc                   $0F:E04C: A9 06     LDA #$06
A:06 X:80 Y:00 S:ED P:nvUbdIzc                   $0F:E04E: 20 B7 E5  JSR $E5B7
A:06 X:80 Y:00 S:EB P:nvUbdIzc                     $0F:E5B7: 8D 3A 06  STA $063A = #$04
A:06 X:80 Y:00 S:EB P:nvUbdIzc                     $0F:E5BA: A9 04     LDA #$04
A:04 X:80 Y:00 S:EB P:nvUbdIzc                     $0F:E5BC: 8D 3F 06  STA $063F = #$00
A:04 X:80 Y:00 S:EB P:nvUbdIzc                     $0F:E5BF: AD 3A 06  LDA $063A = #$06
A:06 X:80 Y:00 S:EB P:nvUbdIzc                     $0F:E5C2: 8D FF FE  STA $FEFF = #$B6
A:06 X:80 Y:00 S:EB P:nvUbdIzc                     $0F:E5C5: 4A        LSR
A:03 X:80 Y:00 S:EB P:nvUbdIzc                     $0F:E5C6: 8D FF FE  STA $FEFF = #$B6
A:03 X:80 Y:00 S:EB P:nvUbdIzc                     $0F:E5C9: 4A        LSR
A:01 X:80 Y:00 S:EB P:nvUbdIzC                     $0F:E5CA: 8D FF FE  STA $FEFF = #$B6
A:01 X:80 Y:00 S:EB P:nvUbdIzC                     $0F:E5CD: 4A        LSR
A:00 X:80 Y:00 S:EB P:nvUbdIZC                     $0F:E5CE: 8D FF FE  STA $FEFF = #$B6
A:00 X:80 Y:00 S:EB P:nvUbdIZC                     $0F:E5D1: 4A        LSR
A:00 X:80 Y:00 S:EB P:nvUbdIZc                     $0F:E5D2: 8D FF FE  STA $FEFF = #$B6
A:00 X:80 Y:00 S:EB P:nvUbdIZc                     $0F:E5D5: A9 00     LDA #$00
A:00 X:80 Y:00 S:EB P:nvUbdIZc                     $0F:E5D7: 8D 3F 06  STA $063F = #$04
A:00 X:80 Y:00 S:EB P:nvUbdIZc                     $0F:E5DA: 60        RTS (from $E5B7) ----------------------------

A:00 X:80 Y:00 S:ED P:nvUbdIZc                   $0F:E051: 20 42 8F  JSR $8F42
A:00 X:80 Y:00 S:EB P:nvUbdIZc                    $06:8F42: AD 41 06  LDA $0641 = #$05


You can see, afer the 5th write to $FEFF (I wonder why $FEFF because writing to $E000 or $FFFF has the same effect) the PRG bank does not change immediately. It still wait for RTS to the original bank ($0F) then swap to new bank ($06).

And here's my new code.

A:90 X:00 Y:00 S:EF P:NvUbdIzc                 $06:B800: A9 03     LDA #$03
A:03 X:00 Y:00 S:EF P:nvUbdIzc                 $06:B802: 8D 42 06  STA $0642 = #$04
A:03 X:00 Y:00 S:EF P:nvUbdIzc                 $06:B805: 20 30 90  JSR $9030
A:03 X:00 Y:00 S:ED P:nvUbdIzc                   $06:9030: 4C 61 B9  JMP $B961
A:03 X:00 Y:00 S:ED P:nvUbdIzc                   $06:B961: A9 06     LDA #$06
A:06 X:00 Y:00 S:ED P:nvUbdIzc                   $06:B963: 20 66 B9  JSR $B966
A:06 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B966: 8D 3A 06  STA $063A = #$06
A:06 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B969: A9 08     LDA #$08
A:08 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B96B: 8D 3F 06  STA $063F = #$00
A:08 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B96E: 8D FF FE  STA $FEFF = #$B6
A:08 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B971: 4A        LSR
A:04 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B972: 8D FF FE  STA $FEFF = #$B6
A:04 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B975: 4A        LSR
A:02 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B976: 8D FF FE  STA $FEFF = #$B6
A:02 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B979: 4A        LSR
A:01 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B97A: 8D FF FE  STA $FEFF = #$B6
A:01 X:00 Y:00 S:EB P:nvUbdIzc                     $06:B97D: 4A        LSR
A:00 X:00 Y:00 S:EB P:nvUbdIZC                     $06:B97E: 8D FF FE  STA $FEFF = #$B6
A:00 X:00 Y:00 S:EB P:nvUbdIZC                     $08:B981: 06 20     ASL $20 = #$E0


As you can see, after the 5th write to $FEFF, it swaps from original PRG bank ($06) to new bank ($08) immediately.
Can you explain why there's the difference?
Title: Re: [NES]Understanding MMC1 mapping
Post by: Cyneprepou4uk on May 05, 2022, 06:53:34 PM
It swaps immediately in both cases. What you see in the log is a bank number for current CPU address. It doesn't show you all banks for all locations at once.
Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on May 06, 2022, 06:56:38 PM
I've tested and confirm that, if the routine is called from the kernel (hi bank), then the current bank is changed after RTS. If the routine is called from low bank, the bank swaps immediately without waiting for RTS.
Title: Re: [NES]Understanding MMC1 mapping
Post by: Cyneprepou4uk on May 06, 2022, 06:58:39 PM
Ok, suit yourself  >:D
Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on May 08, 2022, 05:01:33 AM
One more question. To my understanding, the NES can't read data in the bank which is different from the Program bank. Is it wrong?
For example, with the SNES, our current data bank is $C0 for a Hirom, direct page is $0000 so that

SEP #$20
LDA $9ABC will load a byte from $C0:9ABC regardless of where this code is excuted.

But for the Snes, if the code is executed in PRG bank $06, so it can load data in that bank only?
Title: Re: [NES]Understanding MMC1 mapping
Post by: Cyneprepou4uk on May 08, 2022, 06:01:08 AM
NES basically can read any data located in CPU memory via any code located in CPU memory.
Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on May 09, 2022, 10:16:17 AM
Thank you. I solved the case.
So the answear is, the NES can't read any data in the bank which is not loaded to the CPU memory currently.
Title: Re: [NES]Understanding MMC1 mapping
Post by: KingMike on May 09, 2022, 02:19:37 PM
In your previous example, you were comparing an RTS in the $C000-FFFF range, to one in the $8000-BFFF range.

The difference is that, generally, $C000-FFFF is locked to the last bank (and thus code executed in that range remains in the same bank), whereas $8000-BFFF is swappable).
I know MMC1 can reverse that so that the $8000 range is locked, but I believe that is uncommon practice and I haven't seen it.

The SNES CPU (65816) is different because the CPU was designed with "bank swapping" integrated (only a few odd games have separate mapper chips), so that it basically has 16MB to work with, whereas the NES CPU (6502) only has 64KB to access (the space shared between ROM, RAM, and I/O access ports), so therefore games had to use mappers separate of the CPU.

Why FEFF and not FFFF? I don't know. Programmer's choice.
Title: Re: [NES]Understanding MMC1 mapping
Post by: DragonAtma on May 23, 2022, 07:48:26 PM
Assuming it's little-endian, FEFF is as high as you get without being the maximum (it'd be FFFE in big-endian). I've done the same in python (using 98 or 9998 for "this is a dummy value").
Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on July 04, 2022, 10:30:54 AM
(https://i.imgur.com/0yNa9lj.png)
Title: Re: [NES]Understanding MMC1 mapping
Post by: pianohombre on July 09, 2022, 03:50:51 PM
Can someone please explain what is PRG and CHR? Or maybe post a link? I've seen some hacks will require PRG-0 or PRG-1 roms. Never understand what that meant. Also, is this format exclusively for NES roms? Thanks.
Title: Re: [NES]Understanding MMC1 mapping
Post by: Bavi_H on July 09, 2022, 06:24:03 PM
Quote from: pianohombre on July 09, 2022, 03:50:51 PMwhat is PRG and CHR? [...] I've seen some hacks will require PRG-0 or PRG-1 roms. Never understand what that meant.

In NES cartridges, a PRG chip contains program code and data, a CHR chip contains graphics data.


If a game gets revised, usually only the PRG chip needs to be updated. In that case, the original version of the game is called PRG0, and updates are sequentially numbered PRG1, PRG2, or so on.

Title: Re: [NES]Understanding MMC1 mapping
Post by: yugisokubodai on July 09, 2022, 09:26:55 PM
Tldr version: PRG is where execution code and data, pointers stored. CHR is where graphic stored.
Title: Re: [NES]Understanding MMC1 mapping
Post by: pianohombre on July 11, 2022, 01:24:11 PM
Oh that's cool. Sounds like the Super FX/Cx4 chip added to some SNES games.