Disassembly Notes (NES)

From Dr. Mario
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.

The input handling deserves a brief comment as well. L5C_INPUT_OLD stores which buttons the user is currently pressing (despite its name suggesting otherwise), one button per bit, in standard controller order, with the bottom bit being the right arrow. L5B_INPUT_NEW has the same shape as L5C_INPUT_OLD, but each bit is set if the corresponding button started being pressed this frame; that is, if it is pressed this frame but was not pressed last frame.

           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 15.
                   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