My SMB hack needs a chr swapping routine

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
nesoldskool
Posts: 5
Joined: Sat Feb 19, 2022 7:24 am

My SMB hack needs a chr swapping routine

Post by nesoldskool »

Hello all. I'm glad I found this forum. I am fairly new to NES 6502 and creating games. I began making a Super Mario Bros. game and the folder I have for it (not sure where I got it, its been years. just now decided to learn more about ASM) but it was expanded to mmc3 not by me. With that, I learned to add SRAM and it works fine so i decided to create a SMB game. I have also just been messing with the rest of the disassembly and having fun seeing what is what. My 6502 is a bit limited but as I stated I just really got serious about all of this. I have been reading many documents on mmc3 and bank swapping and its pretty daunting to say the least.
Long story short, I was hoping someone could teach me how to expand the chr so as well as teach me how to write a routine so I can have animation in my game. animated coins, blocks and such. Again, this may be a big step given my 6502 is limited but a necessary one. I understand loading and storing and as well as branching. Sorry if I am being vague. I can supply all the asm files and to build my game as reference.
Any help is much appreciated.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: My SMB hack needs a chr swapping routine

Post by tokumaru »

The MMC3 has 6 CHR slots you can map banks to, but you can't switch banks whenever you want, since that could cause graphics to change halfway down the screen. To avoid this, you need to switch banks during vblank (or very shortly after it).

The first thing you should do in find a place in RAM where to remember the banks that should be loaded. You need 6 bytes of RAM. While the game logic is running, you can change them freely, then, at the end of vblank, you write those values to the MMC3 as fast as you can.

MMC3 bank switching is pretty straightforward: you first tell the mapper WHERE you want to put the new bank, then you tell it WHICH bank you want to put there. To quickly map all 6 CHR banks you can do something like this:

Code: Select all

;maps the 1st bank
ldx #$00 ;this may be different depending on the banking modes you're using
stx $8000
lda CharacterBanks+0
sta $8001

;maps the 2nd bank
inx
stx $8000
lda CharacterBanks+1
sta $8001

;maps the 3rd bank
inx
stx $8000
lda CharacterBanks+2
sta $8001

;maps the 4th bank
inx
stx $8000
lda CharacterBanks+3
sta $8001

;maps the 5th bank
inx
stx $8000
lda CharacterBanks+4
sta $8001

;maps the 6th bank
inx
stx $8000
lda CharacterBanks+5
sta $8001
CharacterBanks is a 6-byte array in RAM, preferably in ZP for speed, but anywhere will do. Anyway, this must happen during vblank, right after the game is done with its VRAM updates (if you did this before the VRAM updates, you could end up pushing them past the vblank period, corrupting VRAM and glitching the graphics). A pretty safe way to identify the end of VRAM manipulation is to find the place where the game sets the scroll, since this is often the last "sensitive" piece of code that must run during vblank. Place your CHR switch operations after the setting of the scroll, but as close as you can to it, since you don't want the image to start rendering with the wrong banks mapped in (although SMB gives you a good safety buffer up there with a bunch of blank scanlines at the top of the status bar).

Now you have to figure out how to populate the array that selects which banks are going to be loaded. For Mario and the other sprites I assume it's always gonna be the same banks, at least for now (you could at some point give Mario other "skins" or new moves, I don't know), so just store their banks in the appropriate spaces in the array.

For animated backgrounds, the simplest (and most compact) thing you can do is have a variable keep track of the current animation frame and the timer until the next frame, since each of those things only need a few bits. For now we could just use the top 4 bits for the timer and the lower 4 for the animation frame (TTTTFFFF). When the level starts, you have to reset this variable to 0 so the animation starts from the beginning. Then, once per frame, you need to update this animation and define which bank will be used for the background during that frame. It can go something like this:

Code: Select all

  ;increments the timer
  clc
  lda CHRAnimationState
  adc #%00010000 ;this is 1 << 4
  sta CHRAnimationState

  ;branches if the timer didn't overflow yet
  and #%11110000
  cmp #%01000000 ;this is 4 << 4
  bne AnimationStateReady

  ;increments the frame number and resets the timer
  clc
  lda CHRAnimationState
  adc #%00000001
  and #%00001111
  sta CHRAnimationState

  ;branches if the frame number didn't overflow yet
  and #%00001111
  cmp #%00000100 ;last frame is 3, so 1 past that is 4
  bne AnimationStateReady

  ;resets the frame number
  lda CHRAnimationState
  and #%11110000
  sta CHRAnimationState

AnimationStateReady:

  ;adds the frame number to the first frame and use that as the final bank number
  lda CHRAnimationState
  and #%00001111
  clc
  adc #ANIMATION_FIRST_BANK
  sta CharacterBanks+1
If you want to make this more versatile (i.e. something you can use for more than 1 specific animation), instead of comparing the timer and the frame number to hardcoded values you could define a second variable to hold what the limits are for both the timer and the frame number:

Code: Select all

  ;increments the timer
  clc
  lda CHRAnimationState
  adc #%00010000
  sta CHRAnimationState

  ;branches if the timer didn't reach the limit
  eor CHRAnimationLimits
  and #%11110000
  ;(EOR causes bits that are equal in 2 numbers to become 0, so if all timer bits are equal, the result will be 0)
  bne AnimationStateReady

  ;increments the frame number and resets the timer
  clc
  lda CHRAnimationState
  adc #%00000001
  and #%00001111
  sta CHRAnimationState

  ;branches if the frame number didn't reach the limit
  eor CHRAnimationLimits
  and #%00001111
  bne AnimationStateReady

  ;resets the frame number
  lda CHRAnimationState
  and #%11110000
  sta CHRAnimationState

AnimationStateReady:
If you also use variables to control the target slot and the base frame number, you have a complete system for simple background animations.
User avatar
N·K
Posts: 15
Joined: Mon May 11, 2020 2:45 am
Location: Japan

Re: My SMB hack needs a chr swapping routine

Post by N·K »

tokumaru wrote: Sat Feb 19, 2022 8:10 pm (if you did this before the VRAM updates, you could end up pushing them past the vblank period, corrupting VRAM and glitching the graphics)
I created an SMB hack about six months ago, and it seems that SMB1 manually turns off the PPU when writing VRAM, and even if it goes over vblank a little, there doesn't seem to be any particular problem beyond a wobbly scroll position.


By the way, I, too, encountered a strange phenomenon while creating the MMC3 hack for SMB1.
In SMB1, the pattern table assignment is BG:$1000, sprite:$0000, but when I use MMC3IRQ in this state, I noticed that the IRQ position sometimes shifts one pixel up.
At first, I thought it was caused by writing to $2006 and $2007, but when I switched the pattern table assignment, the symptom stopped occurring for some reason.
Also, the problem occurs only with the actual MMC3, and cannot be reproduced even with a high-precision emulator such as mesen.
In the first place, does this happen with other than my Famicom?
Fiskbit
Posts: 891
Joined: Sat Nov 18, 2017 9:15 pm

Re: My SMB hack needs a chr swapping routine

Post by Fiskbit »

The MMC3 scanline counter does not behave well outside of the background-on-$0000 and sprites-on-$1000 configuration. After a rising edge is detected on PPU A12, another rising edge cannot be detected until PPU A12 has been low for at least 3 M2 clocks. Normally, dot 0 on each scanline (the 'idle' cycle) performs the first half of a low background tile read, though this read is aborted and a different read is begun on dot 1. The address of that read matches the address of the upcoming read on dot 5. Because of the multiplexed address/data pins, PPU memory accesses take 2 dots, with the address being placed on the bus on the first dot and the low 8 bits being latched so those pins can be used for data on the next dot. Though the read never completes, the address is on the bus, so the cartridge is able to see the effect on A12, and so it impacts the scanline counter. Were it not for this idle cycle behavior, 3 M2s could occur in the dot range of 337 to 4 and cause an extra clock on dot 5.

However, when rendering is enabled, the final dot (340) on the prerender scanline is skipped every other frame, and dot 0 of the next scanline instead completes the memory access that would have otherwise been completed on 340. Thus, the pulse on A12 that would normally happen on dot 0 to keep the scanline counter well behaved in this configuration is skipped and an extra clock can occur here, causing the IRQ to trigger a scanline early. This can be seen sometimes in Wario's Woods, where the rendering disable can happen a scanline early every other frame. The dot 0 behavior is not supported by many emulators and so the delay on A12 is often longer than 3 M2's, which prevents the early IRQ from happening.
User avatar
N·K
Posts: 15
Joined: Mon May 11, 2020 2:45 am
Location: Japan

Re: My SMB hack needs a chr swapping routine

Post by N·K »

Amazing. So it can occur in existing games!
I finally understand why the MMC3 page on the nesdev wiki says it's not common.
nesoldskool
Posts: 5
Joined: Sat Feb 19, 2022 7:24 am

Re: My SMB hack needs a chr swapping routine

Post by nesoldskool »

thank you guys very much for the info. i truly appreciate it. i will mess with it in a bit and if i have any issues i will let you know. i bought a krzyzio cart to test my game on real hardware. sadly my 72 pin connector decided to die. i was only able to test it once and either do to the pin connector or the flash cart itself all games i tried had some type of glitched graphics. also my game worked but started on a water level. the graphical glitches were nothing major. so i figure it is probaly the pin connector, that no longer works. the day i get the flash cart to test my game my nes craps out. :( what luck. anyway, i hope it was the nes and not my game. it works fine on MESEN. i really want it to work on real hardware when i am finished with it. but i guess thats a problem for another day
Last edited by nesoldskool on Sun Feb 20, 2022 5:08 am, edited 1 time in total.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: My SMB hack needs a chr swapping routine

Post by tokumaru »

It has always been suggested that the alternate scanline counting behavior was reliable, though. If it isn't, that should be made clear in the wiki.
nesoldskool
Posts: 5
Joined: Sat Feb 19, 2022 7:24 am

Re: My SMB hack needs a chr swapping routine

Post by nesoldskool »

this is my nes header. 4e 45 53 1a 08 01 42. i suppose i need to change the 01 to a different number
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: My SMB hack needs a chr swapping routine

Post by lidnariq »

I've added some comments to the MMC3 page describing how the "reversed" pattern tables are unstable. Please tell me (or add anything) if you can think of any improvements.

nesdevwiki:MMC3; change 6563->19171
Post Reply