Creating new text conditions for smbdis

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.
dasefx
Posts: 95
Joined: Mon Sep 02, 2024 2:48 pm

Creating new text conditions for smbdis

Post by dasefx »

Hi everyone! I'm creating custom levels in smb and trying to figure out how to display text to the player. I'm open to any strategy.

Originally I was thinking of using the game's text engine (so-to-speak) but it's not clear to me how to add my own condition/trigger for the text. There's a section in the code where labels and corresponding data for text is located but again, not sure how i can use this.

An example would be when Mario saves toad/princess. The corresponding code is below. I don't want to modify this code, instead, I'd like to create a new condition/trigger and setup my own text. Again, I'm open to any strategy with or without the text engine. Any ideas??

Code: Select all

MarioThanksMessage:
;"THANK YOU MARIO!"
  .byte $25, $48, $10
  .byte $1d, $11, $0a, $17, $14, $24
  .byte $22, $18, $1e, $24
  .byte $16, $0a, $1b, $12, $18, $2b
  .byte $00

  .byte $25, $48, $10
  .byte $1d, $11, $0a, $17, $14, $24
  .byte $22, $18, $1e, $24
  .byte $16, $0a, $1b, $12, $18, $2b
  .byte $00
 ; The code continues ....
User avatar
segaloco
Posts: 939
Joined: Fri Aug 25, 2023 11:56 am

Re: Creating new text conditions for smbdis

Post by segaloco »

Ultimately you just have to toss up a display list but the trick is if you expect the screen to be fixed or not. If the message has to persist during scrolling that's a lot of refreshing the background mapping. If its for a static screen, you should be fine just triggering a display list draw at the right page and X position. The scrolling engine only updates tiles offscreen so it shouldn't be redrawing the middle where you put your message.

Also remember your stop point should ideally be on a page boundary so you are only having to draw in one quadrant of the background. You can't draw background tiles outside of the mapping grid so persistent text while scrolling would require some tricky scanline stuff like how the HUD is drawn. In fact, the HUD drawing in general is a good study in stationary BG text while a playfield scrolls.
dasefx
Posts: 95
Joined: Mon Sep 02, 2024 2:48 pm

Re: Creating new text conditions for smbdis

Post by dasefx »

Thank you @segaloco!

Friendos, I finally did something really cool and wanted to provide a very quick update for everyone. Work has been kicking my a$$ so progress has been slow but I'm happy to report there has been progress 8-)

Key points for newbs:
  • Note: I'm using doppelganger's dis (important when I refer to specific variable names, etc.)
  • Data triggers: one trouble I've been having is trying to find the right data and/or verify what certain variables are for. I found it hard to use the debugger for all this stuff because it's sometimes difficult to trigger a specific section of code and catch it in the debugger. Thus i setup a really simple and janky method. I use WRAM to save data (e.g. to location $7fff as an example) and then trigger a loop to catch it. In this example I tie it to PUpDrawLoop such that everytime mario gets a mushroom, the code triggers and I can verify the data i'm interested in. You can of course use different tirggers or make your own
I wanted to verify where the level header data was being handled so in the dis, i added this code to the GetAreaDataAddrs section:

Code: Select all

GetAreaDataAddrs:
	; ... code continues as is until...
	lda (AreaData),y

		; *** my code
		sta $7fff
And it gets viewed here in the power up section of the code so when i hit a mushroom, the game effectively freezes, and i can check the debugger for this specific data and verify precisely what it holds.

Code: Select all

PUpDrawLoop:
      ;*** checker
      looop:
      lda $7fff ; Anywhere in code, store to $7fff and view here
      jmp looop
      ; ... continue on with smbdis code
You'll need to be careful not to overwrite registers when that data is needed elsewhere in the program.
  • In terms of setting up triggers to start certain events (e.g. displaying text), I again use WRAM to store states/data and when conditions are met, i then commence the event. In this example, i store 1 into $7fff when the level header high matches my secret area, and then add 1 if the lower level header also matches; otherwise $7fff gets cleared. When the variable holds #$02, i trigger my event. Again you need to be careful where you inject your event in the code because you don't want to overwrite register data, etc. For me, i found it helpful to insert my code into the WriteBufferToScreenSequence, specifically right after RepeatByte. In this example, when Mario enters my new secret area (since I matched the level header), I change a random background tile as a simple test (and it works!)

Code: Select all

RepeatByte:    lda ($00),y               ;load more data from buffer and write to vram

               ; ... code continues as is till end of RepeatByte

;*** my code
SecretArea1TextTrigger:
      lda $7fff
      cmp #$02
      bne :+
            ldx #$20 ; high byte of name table (screen 1)
            lda PPU_STATUS ; PPU trigger/latch
            txa
            sta PPU_ADDRESS
            lda #$a4
            sta PPU_ADDRESS
            ldy #$33 ; CHR tile (offset from $2000)
            sty PPU_DATA
:
; .... smbdis continues as is
dasefx
Posts: 95
Joined: Mon Sep 02, 2024 2:48 pm

Re: Creating new text conditions for smbdis

Post by dasefx »

A quick note: it turns out variable AreaPointer is a much better trigger to use when trying to get text to appear in certain rooms/areas. Used in conjunction with screen#, it becomes a very easy and reliable trigger.

I created a subsequent post documentating useful smbdis learnings. In that post, Level ID corresponds to AreaPointer. I also show how to calculate Level ID (AreaPointer) in that post: link
dasefx
Posts: 95
Joined: Mon Sep 02, 2024 2:48 pm

Re: Creating new text conditions for smbdis

Post by dasefx »

Until now, I've only been testing by changing one background tile and everything has been working. Today I tried to change multiple tiles to write some text but ended up getting an error. The error is "Range error (-133 not in [-128..127])" which refers to the following code:

Code: Select all

UpdateScreen:  ldx PPU_STATUS
               ldy #$00
               lda ($00),y
               bne WriteBufferToScreen   ; <-------------- This line here.
Any ideas on how better to change the background tiles? I tried injecting my code in several places I thought made sense but keep getting the same error. I believe the root cause is related to the data list referred to in the code i.e. ($00),y but don't have a good handle on how this works. My approach was clearly too brutish ... I was hoping I could simply overwrite using direct address writes but clearly not finessed enough and does not consider how the larger code works.

I create conditions to get to this next bit of but you can see that I was just trying to direct over-write

Code: Select all

	    ldx #$20 ; high byte of name table (screen 1)
            lda PPU_STATUS ; PPU trigger/latch
            txa
            sta PPU_ADDRESS
            lda #$e9
            sta PPU_ADDRESS
            ldy #$11 ; CHR tile ("H")
            sty PPU_DATA

            lda PPU_STATUS ; PPU trigger/latch
            txa
            sta PPU_ADDRESS
            lda #$ea
            sta PPU_ADDRESS
            ldy #$0e ; CHR tile ("E")
            sty PPU_DATA
User avatar
segaloco
Posts: 939
Joined: Fri Aug 25, 2023 11:56 am

Re: Creating new text conditions for smbdis

Post by segaloco »

Consider studying the subroutine that handles Toad's message at the end of a castle level. It isn't ideal for all cases, as the end of level scroll handler ensures the pan transition happens on a page boundary, so the drawing expects to draw inside a single nametable. This would have to change if you're expecting a nametable seam to be anywhere in the drawing area.
dasefx
Posts: 95
Joined: Mon Sep 02, 2024 2:48 pm

Re: Creating new text conditions for smbdis

Post by dasefx »

*** EDIT: pls skip to next post ... keeping this here for now in case needed but will likely delete later.

----------------------

Thanks segaloco, I tried a few different attempts with toad's message but didn't really get too far. I've been mostly playing with the code that handles top status bar, warp text, etc. I've had very meager results that are very buggy so I thought i'd reach out to see if anyone has some insight.

Here's a summary of what I've done...

1. I created a trigger that once the player enters room #$43 (single tilemap), a flag is triggered.
2. I created a new message

Code: Select all

   Secretzz:
  .byte $15, $1e, $12, $10, $12    ; "LUIGI", no address or length
  .byte $ff
3. I added it to the following:

Code: Select all

GameTextOffsets:
  .byte TopStatusBarLine-GameText, TopStatusBarLine-GameText
  .byte WorldLivesDisplay-GameText, WorldLivesDisplay-GameText
  .byte TwoPlayerTimeUp-GameText, OnePlayerTimeUp-GameText
  .byte TwoPlayerGameOver-GameText, OnePlayerGameOver-GameText
  .byte WarpZoneWelcome-GameText, WarpZoneWelcome-GameText
  .byte Secretzz-GameText ; <------------------ HERE (position $0a in this list)
4. I created the following routine to write the text. You'll notice i copied a lot of the code used to write the top bar/other code (eg GameTextLoop2 ... i just appended the "2" to the label).

Code: Select all

Checkzz:
            pha ; save the accumulator (return it later)
            lda $7fff ; Check trigger 
            cmp #$01
            bne skipzz ; if not triggered, bypass routine.
            ldy #$0a ; if triggered, apply $0a offset for GameTextOffsets
	LdGameText2:
	      ldx GameTextOffsets,y ;get offset to message we want to print
	      ldy #$00
	GameTextLoop2:
	       lda GameText,x           ;load message data
	       cmp #$ff                 ;check for terminator
               beq EndGameText2          ;branch to end text if found
               sta VRAM_Buffer1+27,y       ;otherwise write data to buffer
               inx                      ;and increment increment
               iny
               bne GameTextLoop2         ;do this for 256 bytes if no terminator found
	EndGameText2:
               ;lda #$00                 ;put null terminator at end
      skipzz:
            pla
            rts
5. Finally, I call this immediately in the WriteTopStatusLine section. I've tried moving this to numerous different places with no change. I thought perhaps "VRAM_Buffer1_Offset" may be playing a role but not seeing it.

Code: Select all

WriteTopStatusLine:
      lda #$00
      jsr WriteGameText
      jsr Checkzz ; <----------------- HERE
      jmp IncSubtask    ;onto the next task
I'm results like this which vary depending on how I set "sta VRAM_Buffer1+15,y ". If i add 15, I get this:
Screenshot 2025-11-22 223448.png
If i change it to +10, i get:
Screenshot 2025-11-22 223618.png
------------------------------

small note: in the images, you'll notice Mario is written on the right. Please ignore - i changed this while testing other things earlier and forgot to change back.
You do not have the required permissions to view the files attached to this post.
Last edited by dasefx on Sun Nov 23, 2025 3:50 pm, edited 1 time in total.
dasefx
Posts: 95
Joined: Mon Sep 02, 2024 2:48 pm

Re: Creating new text conditions for smbdis

Post by dasefx »

** EDIT: pls skip to next post ... keeping this here for now in case needed but will likely delete later.

----------------------

Okay, I've gone back to trying Toad's message as per segaloco's suggestion. I've been reviewing it and I do believe it may be the simpler option. I'm still not seeing text and any feedback would be extremely helpful. I believe the code i put together is tight but clearly not enough!

1. I created a new message

Code: Select all

SecretMessage1:
    .byte $21, $4a	; VRAM address = $214a (row 10 col 10)
    .byte $01		; write 1 tile
    .byte $1f		; "V"
    .byte $ff		; end-of-buffer marker
SecretMessage1End:
2. I added it to this list (although i don't think this is necessary but shouldn't hurt for now):

Code: Select all

VRAM_AddrTable_Low:
      .byte <VRAM_Buffer1, <WaterPaletteData, <GroundPaletteData
      .byte <UndergroundPaletteData, <CastlePaletteData, <VRAM_Buffer1_Offset
      .byte <VRAM_Buffer2, <VRAM_Buffer2, <BowserPaletteData
      .byte <DaySnowPaletteData, <NightSnowPaletteData, <MushroomPaletteData
      .byte <MarioThanksMessage, <LuigiThanksMessage, <MushroomRetainerSaved
      .byte <PrincessSaved1, <PrincessSaved2, <WorldSelectMessage1
      .byte <WorldSelectMessage2, <SecretMessage1 ; <-------------- HERE

VRAM_AddrTable_High:
      .byte >VRAM_Buffer1, >WaterPaletteData, >GroundPaletteData
      .byte >UndergroundPaletteData, >CastlePaletteData, >VRAM_Buffer1_Offset
      .byte >VRAM_Buffer2, >VRAM_Buffer2, >BowserPaletteData
      .byte >DaySnowPaletteData, >NightSnowPaletteData, >MushroomPaletteData
      .byte >MarioThanksMessage, >LuigiThanksMessage, >MushroomRetainerSaved
      .byte >PrincessSaved1, >PrincessSaved2, >WorldSelectMessage1
      .byte >WorldSelectMessage2, >SecretMessage1 ; <-------------- HERE
3. I created a subroutine to save the message

Code: Select all

WriteSecretMessage1:
    lda #$00
    sta VRAM_Buffer1_Offset

    ldx #0
S1Loop:
    lda SecretMessage1, x
    sta VRAM_Buffer1, x
    inx
    cpx #(SecretMessage1End - SecretMessage1)
    bne S1Loop

    lda #$00
    sta VRAM_Buffer_AddrCtrl
    rts
4. Finally, when the player enters room $43, it calls the subroutine and keeps active while in this room.

Code: Select all

GetAreaDataAddrs:
            lda AreaPointer	; use 2 MSB for Y

	   ; My code STARTS
            cmp #$43 ; Level ID ($43) for 1-1 secret area
            bne :+
            jsr WriteSecretMessage1
      :
            lda AreaPointer	; reload AreaPointer
            ; My code ENDS
            ; smbdis continues as is ....
Last edited by dasefx on Mon Nov 24, 2025 7:06 pm, edited 1 time in total.
dasefx
Posts: 95
Joined: Mon Sep 02, 2024 2:48 pm

Re: Creating new text conditions for smbdis

Post by dasefx »

Okay, I've gone back to trying techniques similar to those used for the top status bar, warp text, etc.
I've come up with a strategy that seems very simple and straight forward but am getting results I don't quite understand.

I have a subroutine (SecretArea1TextTrigger) that writes the letter "E" at three different vram buffer locations. However, instead of getting 3 letters, I get 3 rows of spaced out letters. Does anyone have any clues?

Code: Select all

SecretArea1TextTrigger:
      lda $7fff ; Check trigger
      cmp #$01
      bne dskip

      lda #$0e ; "E"
      sta VRAM_Buffer1+115
      sta VRAM_Buffer1+116
      sta VRAM_Buffer1+117
dskip:
      rts
Result looks like this (i removed mario to avoid any copyright concerns):
Screenshot 2025-11-24 205436.png
You do not have the required permissions to view the files attached to this post.
User avatar
segaloco
Posts: 939
Joined: Fri Aug 25, 2023 11:56 am

Re: Creating new text conditions for smbdis

Post by segaloco »

My guess would be whatever buffer is trying to write is configured to write vertically. If you're writing these E's at 115, 116, and 117, 114 may be the length byte for the particular display list you're poking into, you could try and 0x7F on that byte to turn off the row increment flag. Just a guess, that doesn't solve the whole problem but tells you if you're hooking into a display list causing that vertical arrangement.
dasefx
Posts: 95
Joined: Mon Sep 02, 2024 2:48 pm

Re: Creating new text conditions for smbdis

Post by dasefx »

Thanks segaloco! I really appreciate the response. And yes, I think that makes sense.

I went back to trying to modify the nametable directly (instead of buffer) and ... i have good/bad news. I believe i found the issue and .......... unfortunately i believe it's related to branching with too large a jump. Classic newb :roll:

I'll report back later this week once i confirm but basically 100% certain.