So to begin, I'm going to start at the beginning: reset:
01 .proc reset
02
03 sei
04 cld
05
06 mb PPU_CTRL := #%00010000
07 ldx #$ff
08 txs
09
10 repeat
11 until lda PPU_STATUS == bit7 set
12
13 repeat
14 until lda PPU_STATUS == bit7 set
15
16 ldy #ColdBootOffset ; load default cold boot pointer
17 ldx #$05 ; this is where we check for a warm boot
18
19 repeat ; check each score digit in the top score
20 if TopScoreDisplay[ x ] >= #10 goto coldboot ; to see if we have a valid digit
21 until dex == negative
22
23 if WarmBootValidation = #$a5 ; second checkpoint, check to see if another location has a specific value
24 ldy #WarmBootOffset ; if passed both, load warm boot pointer
25 endif
26
27 coldboot:
28
29 jsr InitializeMemory ; clear memory using pointer in Y, depending on cold/warmboot
30
31 sta SND_DELTA_REG+1 ; reset delta counter load register , a := #0 after InitializeMemory
32 sta OperMode ; reset primary mode of operation
33
34 mb WarmBootValidation := #$a5 ; set warm boot flag with reg a
35 mb PseudoRandomBitReg := a ; set seed for pseudorandom register
36 mb SND_MASTERCTRL_REG := #%00001111 ; enable all sound channels except dmc
37 mb PPU_MASK := #%00000110 ; turn off clipping for OAM and background
38
39 jsr MoveAllSpritesOffscreen
40 jsr InitializeNameTables ; initialize both name tables
41
42 inc DisableScreenFlag ; set flag to disable screen output
43
44 mb a := Mirror_PPU_CTRL | #%10000000 ; enable NMIs
45
46 jsr WritePPU_CTRL ; write to CTRL port and to mirror
47
48 : jmp :-
49 .endproc
Not much to explain here, pretty straightforward. Small loop checks that the score could be valid and checks for an additional good value to keep highscores. SMB does everything in NMI, so reset just loops forever waiting for the next NMI. It also calls four subroutines, first is InitializeMemory:
01 .proc InitializeMemory
02 ; Clear ram, but skip the top of the stack and start with the value in reg y for page 7
03 ; If called by reset:
04 ; If warm boot, start at $07D7, which will leave the following memory alone:
05 ;
06 ; TopScoreDisplay,DisplayDigits,PlayerScoreDisplay,
07 ; ScoreAndCoinDisplay,GameTimerDisplay,WorldSelectEnableFlag
08 ; ContinueWorld,WarmBootValidation
09 ;
10 ; Otherwise y will start at $07fe (clear everything but WarmBootValidation )
11 ;
12 ; also called by InitializeArea, and InitializeGame
13 ;
14 pointer = $06
15
16 ldx #$07 ; set initial high byte to $0700-$07ff
17 mb a, pointer := #0 ; set initial low byte to start of page
18
19 repeat
20
21 mb pointer[ 1 ] := x
22 repeat
23 if x <> #$01 || y < #$60 ; $0160-$01ff = do not clear (leave stack alone)
24 mb (pointer)[ y ] := a ; #0
25 endif
26 until y := y - 1 = #$FF ; do this all bytes in page have been erased (could be 'until negative')
27
28 until dex == negative ; do this until all pages of memory have been erased
29 rts
30
31 .endproc
It clears the RAM but will skip the top of the stack. As well it starts on page 7 with the value in reg y and counts down form there. Values to be saved are at the end of RAM. Next is MoveAllSpritesOffscreen. This is interesting because it uses the BIT opcode trick to skip a ldy immediate instruction if it is called to remove all sprites.
01 .proc MoveAllSpritesOffscreen
02
03 .export MoveSpritesOffscreen
04
05 ldy #$00 ; this routine moves all sprites off the screen
06 .byte $2c ; BIT instruction opcode - skip over next two bytes trick
07
08 MoveSpritesOffscreen:
09 ldy #$04 ; this routine moves all but sprite 0
10 lda #$f8 ; off the screen
11
12 repeat
13 mb Sprite[ y ]::Y_Position := a ; write 248 into OAM data's Y coordinate
14 until y := y + 4 == zero
15 rts
16 .endproc
Then, InitializeNameTables:
01 .proc InitializeNameTables
02
03 lda PPU_STATUS ; reset flip-flop
04
05 mb a := Mirror_PPU_CTRL | #%00010000 & #%11110000 ; set sprites for first 4k and
06 jsr WritePPU_CTRL ; background for second 4k, clear low half
07
08 lda #$24 ; set vram address to start of name table 1
09 jsr WriteNTAddr
10 lda #$20 ; and then set it to name table 0
11
12 WriteNTAddr:
13
14 sta PPU_ADDRESS
15 lda #$00
16 sta PPU_ADDRESS
17
18 ldx #$04
19 ldy #$c0
20 lda # ' ' ; clear name table with blank tile
21 repeat
22 repeat ; 960 , $3c0h needs to be cleared
23 sta PPU_DATA ; first loop is only $c0 (192) three more loops are 3 * 256 = 960 bytes exactly
24 until dey == zero
25 until dex == zero
26
27 ldy #64 ; now to clear the attribute table (with zero this time)
28 mb a := x ; x is zero
29 sta VRAM_Buffer1_Offset ; init vram buffer 1 offset
30 sta VRAM_Buffer1 ; init vram buffer 1
31
32 repeat
33 sta PPU_DATA
34 until dey == zero
35
36 sta HorizontalScroll ; reset scroll variables
37 sta VerticalScroll
38 jmp InitScroll ; initialize scroll registers to zero and rts
39 .endproc
Note that the blank tile here is defined as # ' ' which is mapped to $24 with the ca65 ".charmap" command. This routine simply loads that blank tile into both nametables and clears the attribute table. The only subroutine left is WritePPU_CTRL:
01 .proc WritePPU_CTRL
02 sta PPU_CTRL ; write contents of A to PPU register 1
03 sta Mirror_PPU_CTRL ; and its mirror
04 rts
05 .endproc
Pretty boring… Well, that's it for now, more code to come.
No comments:
Post a Comment