Disassembly Notes (NES)

From Dr. Mario
Revision as of 01:41, 13 February 2023 by Dmwit (talk | contribs)
Jump to navigation Jump to search

Demo

If you sit idle on the title screen for a while, a demo plays. It's triggered by the frame counter rolling over to 00 8 times in a row. The frame counter cycles through from 00-FF. So this means it takes anywhere from ~29-34 seconds to start ((256 frames * 7 + [1,256] frames) / 60 frames per second). It can be disabled a number of ways, but the simplest way is editing the jump offset (to 0) when it decides to load the demo code (at $9905), accomplishable through the Game Genie code AAAPSP. Next, while the demo board is a generatable one (seed 03BE), it's stored directly in the ROM ($CF00-$CF7F) and loaded into memory. The capsules generated do not match this seed, but they're stored right after the board ($CF80-$CFFF). The controls are stored in pairs of bytes, the first byte being the buttons pressed (bitmask - A, B, Select, Start, Up, Down, Left, Right) and the second being the number of frames to hold down those buttons. These are stored right after the board/capsules at $D000-$D1FF.

Pill movement

This is all the code responsible for moving the pill each frame. The disassembly and function descriptions are from Brian Huffman's disassembly; this version has added detailed English descriptions of the code. I also did a cursory look through the code for what $06f1 does, but I couldn't find anything obvious that read from that address. So either it's dead code, or it's read via indirect addressing.

           jsr R8D70_DROP_PILL ; may go to state 1 ; 8D66 20 70 8D
           jsr R8DBF_PILL_MOVE                     ; 8D69 20 BF 8D
           jsr R8E2B_PILL_ROTATE                   ; 8D6C 20 2B 8E
   
   ; --------------------------------------
   ; Called once, in L8D56.
   R8D70_DROP_PILL:
       begin
           lda L43_CLOCK                           ; 8D70 A5 43
           and #$01                                ; 8D72 29 01
           beq L8D7E                               ; 8D74 F0 08    Every other frame,
           lda L5C_INPUT_OLD                       ; 8D76 A5 5C
           and #$0F                                ; 8D78 29 0F
           cmp #$04 ; "down"                       ; 8D7A C9 04        if the user is pressing down but not left, right, or up,
           beq L8D94                               ; 8D7C F0 16            skip ahead to L8D94.
       L8D7E:
           inc L92_DROP_TIMER                      ; 8D7E E6 92    Increase the drop counter.
           lda L8A_EXTRA_SPEED                     ; 8D80 A5 8A
           sta LA0_TEMP ; redundant copy           ; 8D82 85 A0
           ldx L8B_SPEED                           ; 8D84 A6 8B
           lda LA38D,x ; $0F,$19,$1F               ; 8D86 BD 8D A3 Load the base speed offset for the player-selected speed (low, med, hi),
           add LA0_TEMP                            ; 8D89 18 65 A0 then add in the offset for having played a long time.
           tax                                     ; 8D8C AA
           lda LA795,x ; drop timer threshold      ; 8D8D BD 95 A7 Load the gravity value.
           cmp L92_DROP_TIMER                      ; 8D90 C5 92    If the drop counter is <= the gravity value,
           bcs break                               ; 8D92 B0 2A        return from this function.
       L8D94:
           dec L86_PILL_ROW                        ; 8D94 C6 86    Move down.
           lda #$00                                ; 8D96 A9 00
           sta L92_DROP_TIMER                      ; 8D98 85 92    Reset the drop counter to 0.
           jsr R90D3_COLLISION                     ; 8D9A 20 D3 90
           if_eq                                   ; 8D9D D0 06    If we are over in-bounds empty space,
               lda L86_PILL_ROW                    ; 8D9F A5 86
               cmp #$FF                            ; 8DA1 C9 FF        and not below the board,
               bne break                           ; 8DA3 D0 19            return from this function.
           end_if
           inc L86_PILL_ROW                        ; 8DA5 E6 86    Move up.
           lda #$07                                ; 8DA7 A9 07
           sta L06F1_                              ; 8DA9 8D F1 06 Set $06F1 to 7.
           jsr R8F52_PLACE_PILL                    ; 8DAC 20 52 8F Lock the pill in place.
           ; make the program crash if L0740_CHECKSUM_FAIL is nonzero
           lda L0740_CHECKSUM_FAIL                 ; 8DAF AD 40 07
           if_ne                                   ; 8DB2 F0 05
               lda L53_SPRITE_NUM                  ; 8DB4 A5 53
               pha                                 ; 8DB6 48
               pha                                 ; 8DB7 48
               pha                                 ; 8DB8 48
           end_if
           inc L97_GAME_STATE                      ; 8DB9 E6 97
           jmp break                               ; 8DBB 4C BE 8D
       end
       rts                                         ; 8DBE 60
   
   ; --------------------------------------
   ; Increment or decrement pill column based on controller input.
   ; Repeat every 6 frames after holding for 16.
   R8DBF_PILL_MOVE:
       begin
           lda L5B_INPUT_NEW                       ; 8DBF A5 5B
           and #$03 ; left or right                ; 8DC1 29 03
           if_eq                                   ; 8DC3 D0 15    If the user did not start pressing left or right,
               lda L5C_INPUT_OLD                   ; 8DC5 A5 5C
               and #$03 ; left or right            ; 8DC7 29 03
               beq break                           ; 8DC9 F0 5F        and neither left nor right is currently pressed, return from this function. Otherwise,
               inc L93_REPEAT_TIMER                ; 8DCB E6 93            increase the DAS counter.
               lda L93_REPEAT_TIMER                ; 8DCD A5 93
               cmp #$10                            ; 8DCF C9 10
               bmi break                           ; 8DD1 30 57        If the DAS counter is under 16, return from this function. Otherwise,
               lda #$0A                            ; 8DD3 A9 0A
               sta L93_REPEAT_TIMER                ; 8DD5 85 93            set the DAS counter to 10.
           else                                    ; 8DD7 4C E3 8D If the user started pressing left or right,
               lda #$00                            ; 8DDA A9 00
               sta L93_REPEAT_TIMER                ; 8DDC 85 93        set the DAS counter to 0,
               lda #$03                            ; 8DDE A9 03
               sta L06F1_                          ; 8DE0 8D F1 06     and set $06F1 to 3.
           end_if
           lda L5C_INPUT_OLD                       ; 8DE3 A5 5C
           and #$01 ; "right"                      ; 8DE5 29 01
           if_ne                                   ; 8DE7 F0 20    If the user is pressing right,
               lda LA5_PILL_DIR                    ; 8DE9 A5 A5
               and #$01 ; 1 = U/D, 0 = L/R         ; 8DEB 29 01
               add #$06                            ; 8DED 18 69 06
               cmp L85_PILL_COLUMN                 ; 8DF0 C5 85
               if_ne                               ; 8DF2 F0 15        and the pill is not in the rightmost column for its orientation,
                   inc L85_PILL_COLUMN             ; 8DF4 E6 85            move right.
                   jsr R90D3_COLLISION             ; 8DF6 20 D3 90
                   if_eq                           ; 8DF9 D0 08            If that would put us over in-bounds empty space,
                       lda #$03                    ; 8DFB A9 03
                       sta L06F1_                  ; 8DFD 8D F1 06             set $06F1 to 3.
                   else                            ; 8E00 4C 09 8E         If that would put us out of bounds or over a non-empty space,
                       dec L85_PILL_COLUMN         ; 8E03 C6 85                move left
                       lda #$0F                    ; 8E05 A9 0F
                       sta L93_REPEAT_TIMER        ; 8E07 85 93                and set the DAS counter to 16.
                   end_if
               end_if
           end_if
           lda L5C_INPUT_OLD                       ; 8E09 A5 5C
           and #$02 ; "left"                       ; 8E0B 29 02
           if_ne                                   ; 8E0D F0 1B    If the user is pressing left,
               lda L85_PILL_COLUMN                 ; 8E0F A5 85
               cmp #$00                            ; 8E11 C9 00
               if_ne                               ; 8E13 F0 15        and we're not in the left-most column,
                   dec L85_PILL_COLUMN             ; 8E15 C6 85            move left.
                   jsr R90D3_COLLISION             ; 8E17 20 D3 90
                   if_eq                           ; 8E1A D0 08            If that would put us over in-bounds empty space,
                       lda #$03                    ; 8E1C A9 03
                       sta L06F1_                  ; 8E1E 8D F1 06             set $06F1 to 3.
                   else                            ; 8E21 4C 2A 8E         If that would put us out of bounds or over a non-empty space,
                       inc L85_PILL_COLUMN         ; 8E24 E6 85                move right,
                       lda #$0F                    ; 8E26 A9 0F
                       sta L93_REPEAT_TIMER        ; 8E28 85 93                and set the DAS counter to 16.
                   end_if
               end_if
           end_if
       end
       rts                                         ; 8E2A 60
   
   ; ----------------------------------------------------------------------------
   ; Pill collision detection.
   ; Returns A = 0, Z set for no collision.
   ; Returns A = FF, Z clear for collision.
   R90D3_COLLISION:
   .scope
       ldx LA6_PILL_SIZE                           ; 90D3 A6 A6
       dex                                         ; 90D5 CA
       dex                                         ; 90D6 CA
       lda LA5_PILL_DIR                            ; 90D7 A5 A5
       and #$01 ; 1 = U/D, 0 = L/R                 ; 90D9 29 01
       if_eq                                       ; 90DB D0 13    If the current pill is horizontal,
           lda LA455,x ; $00,$FF,$FF               ; 90DD BD 55 A4
           add L85_PILL_COLUMN                     ; 90E0 18 65 85     and the pill's column is negative,
           bmi collision                           ; 90E3 30 4C            it's a collision.
           lda LA458,x ; $F9,$F9,$FA               ; 90E5 BD 58 A4
           add L85_PILL_COLUMN                     ; 90E8 18 65 85     and the pill's column - 7 is nonnegative,
           bpl collision                           ; 90EB 10 44            it's a collision.
       else                                        ; 90ED 4C F8 90 If the current pill is vertical,
           lda LA45B,x ; $00,$FF,$FF,...           ; 90F0 BD 5B A4
           add L86_PILL_ROW                        ; 90F3 18 65 86     and the pill's row is negative,
           bmi collision                           ; 90F6 30 39            it's a collision.
       end_if
       lda #$00                                    ; 90F8 A9 00
       sta L57_                                    ; 90FA 85 57    $57 = 0
       ldx L86_PILL_ROW                            ; 90FC A6 86
       lda LA474,x ; 120 - x*8                     ; 90FE BD 74 A4
       add L85_PILL_COLUMN                         ; 9101 18 65 85
       sta L47_TEMP                                ; 9104 85 47    $47 = pill's current position
       lda LA6_PILL_SIZE                           ; 9106 A5 A6
       sub #$02                                    ; 9108 38 E9 02
       asl a                                       ; 910B 0A
       sta L49_TEMP                                ; 910C 85 49    $49 = 0
       ; L49_TEMP = LA6_PILL_SIZE - 2
       lda LA5_PILL_DIR                            ; 910E A5 A5
       and #$01 ; 1 = U/D, 0 = L/R                 ; 9110 29 01
       add L49_TEMP                                ; 9112 18 65 49
       asl a                                       ; 9115 0A
       asl a                                       ; 9116 0A
       tax                                         ; 9117 AA       x = 0 for horizontal pill, 4 for vertical pill
       ; x = ((LA5_PILL_DIR & #1) + L49_TEMP) << 2
       lda #$04                                    ; 9118 A9 04
       sta L48_TEMP                                ; 911A 85 48    for(i=0; i<4; i++)
       loop
           lda LA4C4,x ; 0,1,0,0, f8,0,0,0         ; 911C BD C4 A4
           add L47_TEMP                            ; 911F 18 65 47
           tay                                     ; 9122 A8           y = pill's position + { horizontal pill -> 0 or 1; vertical pill -> 0 or -8 }
           lda ($57),y                             ; 9123 B1 57
           cmp #$FF                                ; 9125 C9 FF        If the board at index y is not empty,
           bne collision                           ; 9127 D0 08             it's a collision.
           inx                                     ; 9129 E8
           dec L48_TEMP                            ; 912A C6 48
       while_ne                                    ; 912C D0 EE
       lda #$00                                    ; 912E A9 00    It's not a collision.
       rts                                         ; 9130 60
   
   collision:
       lda #$FF                                    ; 9131 A9 FF
       rts                                         ; 9133 60
   .endscope
   
   
   ; --------------------------------------
   ; Increment or decrement LA5_PILL_DIR (mod 4) depending on controller input.
   R8E2B_PILL_ROTATE:
       lda LA5_PILL_DIR                            ; 8E2B A5 A5
       sta L4A_TEMP                                ; 8E2D 85 4A
       lda L85_PILL_COLUMN                         ; 8E2F A5 85
       sta L4B_TEMP                                ; 8E31 85 4B
       lda L5B_INPUT_NEW                           ; 8E33 A5 5B
       and #$80 ; "A"                              ; 8E35 29 80
       if_ne                                       ; 8E37 F0 10    If the user started pressing A,
           lda #$05                                ; 8E39 A9 05
           sta L06F1_                              ; 8E3B 8D F1 06     set $06f1 to 5,
           dec LA5_PILL_DIR                        ; 8E3E C6 A5
           lda LA5_PILL_DIR                        ; 8E40 A5 A5
           and #$03                                ; 8E42 29 03
           sta LA5_PILL_DIR                        ; 8E44 85 A5        subtract one (mod 4) from the current rotation count,
           jsr R8E60_CHECK_ROTATE                  ; 8E46 20 60 8E     and then roll back the change if there's a problem.
       end_if
       lda L5B_INPUT_NEW                           ; 8E49 A5 5B
       and #$40 ; "B"                              ; 8E4B 29 40    If the user started pressing B,
       if_ne                                       ; 8E4D F0 10
           lda #$05                                ; 8E4F A9 05
           sta L06F1_                              ; 8E51 8D F1 06     set $06f1 to 5,
           inc LA5_PILL_DIR                        ; 8E54 E6 A5
           lda LA5_PILL_DIR                        ; 8E56 A5 A5
           and #$03                                ; 8E58 29 03
           sta LA5_PILL_DIR                        ; 8E5A 85 A5        add one (mod 4) to the current rotation count,
           jsr R8E60_CHECK_ROTATE                  ; 8E5C 20 60 8E     and then roll back the change if there's a problem.
       end_if
       rts                                         ; 8E5F 60
   
   
   ; --------------------------------------
   ; Adjust or undo pill rotation if there are collisions.
   ; Previous DIR and COLUMN values are passed in L4A_TEMP/L4B_TEMP.
   R8E60_CHECK_ROTATE:
       begin
           lda LA5_PILL_DIR                        ; 8E60 A5 A5
           and #$01 ; 1 = U/D, 0 = L/R             ; 8E62 29 01
           if_eq                                   ; 8E64 D0 19    If the current pill is horizontal,
               jsr R90D3_COLLISION                 ; 8E66 20 D3 90
               if_eq                               ; 8E69 D0 12        and over in-bounds empty space,
                   lda L5C_INPUT_OLD               ; 8E6B A5 5C
                   and #$02 ; "left"               ; 8E6D 29 02
                   beq break                       ; 8E6F F0 1B             and the user is not pressing left, return from this function.
                   dec L85_PILL_COLUMN             ; 8E71 C6 85             Move left.
                   jsr R90D3_COLLISION             ; 8E73 20 D3 90
                   beq break                       ; 8E76 F0 14             If the pill is over in-bounds empty space, return from this function.
                   inc L85_PILL_COLUMN             ; 8E78 E6 85             Move right.
                   jmp break                       ; 8E7A 4C 8C 8E          Return from this function.
               end_if
               dec L85_PILL_COLUMN                 ; 8E7D C6 85        Move left.
           end_if
           jsr R90D3_COLLISION                     ; 8E7F 20 D3 90 If the current pill is out of bounds or over non-empty space,
           beq break                               ; 8E82 F0 08
   
           lda L4A_TEMP                            ; 8E84 A5 4A
           sta LA5_PILL_DIR                        ; 8E86 85 A5
           lda L4B_TEMP                            ; 8E88 A5 4B
           sta L85_PILL_COLUMN                     ; 8E8A 85 85        roll back to the position and rotation we were at before we started the rotation.
       end
       rts                                         ; 8E8C 60

External resources

Brian Huffman's disassembly notes

miscellaneous Mesen labels