1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
|
SAVES_BANK .rs 1 ; bank with saves
LAST_STARTED_SAVE .rs 1 ; last used save ID
CLEAN_AFTER .equ 128 ; clean flash after this number of menu states
; saving last selected game
save_state:
.if USE_FLASH_WRITING=0
rts
.endif
; detect flash memory type
jsr flash_detect
lda <FLASH_TYPE
beq .end ; not writable flash
lda <SAVES_BANK
bne .bank_already_formatted
lda #($100 - 8) ; last bank - 8*16kb
sta <SAVES_BANK
jsr flash_format_sector
.bank_already_formatted:
jsr write_state
.end:
rts
write_state:
lda #0
sta BUFFER ; marker
; last started game
lda <SELECTED_GAME
sta BUFFER+1
lda <SELECTED_GAME+1
sta BUFFER+2
; line
lda <SCROLL_LINES_TARGET
sta BUFFER+3
lda <SCROLL_LINES_TARGET+1
sta BUFFER+4
lda <LAST_STARTED_SAVE
sta BUFFER+5
lda <STATE_CELL_NEXT
sta COPY_DEST_ADDR
clc
adc #8
sta <STATE_CELL_NEXT
lda <STATE_CELL_NEXT+1
sta COPY_DEST_ADDR+1
adc #0
sta <STATE_CELL_NEXT+1
lda <SAVES_BANK
sta <NROM_BANK_L ; storing saves bank number
lda #$FF
sta <NROM_BANK_H ; at the very end of flash memory
ldx #6; 6 bytes
jsr flash_write
rts
load_state:
.if USE_FLASH_WRITING=0
rts
.endif
; detect flash memory type
jsr flash_detect
lda <FLASH_TYPE
beq .end
lda BUTTONS
cmp #$5C ; Up+Left+Select+Start = full erase
bne .no_force_erase
jsr flash_force_erase
rts
.no_force_erase:
jsr find_saves_bank ; checks for saves bank
beq .end ; no saves bank?
sta <NROM_BANK_L ; storing saves bank number
lda #$FF
sta <NROM_BANK_H ; at the very end of flash memory
jsr flash_find_empty_cell ; searching for first empty cell
lda <STATE_CELL_NEXT+1
cmp #$80
bne .state_exists
lda <STATE_CELL_NEXT
cmp #$20
bne .state_exists
jmp .end ; if it's at the very beginning ($8020), there is no saved status
.state_exists:
lda <STATE_CELL_NEXT
sec
sbc #8
sta COPY_SOURCE_ADDR
lda <STATE_CELL_NEXT+1
sbc #0
sta COPY_SOURCE_ADDR+1
ldx #8
jsr flash_read ; loading 8 bytes of data
.if ENABLE_LAST_GAME_SAVING!=0
; loading last started game
lda BUFFER+1
sta <SELECTED_GAME
lda BUFFER+2
sta <SELECTED_GAME+1
lda BUFFER+3
sta <SCROLL_LINES_TARGET
lda BUFFER+4
sta <SCROLL_LINES_TARGET+1
.else
lda #0
sta <SELECTED_GAME
sta <SELECTED_GAME+1
sta <SCROLL_LINES_TARGET
sta <SCROLL_LINES_TARGET+1
.endif
; loading last save ID
lda BUFFER+5
sta <LAST_STARTED_SAVE
;beq .end
; maybe it's time to clean flash?
lda <STATE_CELL_NEXT+1
cmp #$80 + (CLEAN_AFTER / ($100 / 8)) ; 32 saves per 256 bytes
bcc .clean_not_required
jsr saving_warning_show
jsr flash_cleanup
.clean_not_required:
lda <LAST_STARTED_SAVE
beq .save_last_game_not_required
jsr saving_warning_show
jsr save_last_game
.save_last_game_not_required:
jsr saving_warning_hide
.end:
rts
save_last_game:
.save_last_game_again:
lda <SAVES_BANK
sta <NROM_BANK_L ; storing saves bank number
lda #$FF
sta <NROM_BANK_H ; at the very end of flash memory
lda #$10
sta COPY_SOURCE_ADDR ;$8010
lda #$80
sta COPY_SOURCE_ADDR+1
ldx #16
jsr flash_read ; reading 16 bytes to buffer
; searching for free slot
lda #$FF
ldx #1
.loop:
cmp BUFFER, x
beq .found
inx
cpx #16
bne .loop
jsr flash_cleanup ; not found, cleanup
jmp .save_last_game_again ; repeat
.found:
txa
pha ; storing save slot number
clc
adc #$10
sta COPY_DEST_ADDR
lda #$80
sta COPY_DEST_ADDR+1
lda <LAST_STARTED_SAVE
sta BUFFER
ldx #1
jsr flash_write
pla
tax
lsr A
clc
adc <SAVES_BANK
sta <NROM_BANK_L
lda #$00
sta COPY_DEST_ADDR
txa
and #1
bne .subbankA0
lda #$80
bmi .subbank
.subbankA0:
lda #$A0
.subbank:
sta COPY_DEST_ADDR+1
jsr flash_write_prg_ram
lda #0
sta <LAST_STARTED_SAVE
jsr write_state
rts
find_saves_bank:
lda #$00
sta COPY_SOURCE_ADDR
lda #$80
sta COPY_SOURCE_ADDR+1
lda #$FF
sta <NROM_BANK_H
lda #($100 - 8 * 2) ; last - 8 (8 banks*16kb = sector size) * 2
.bloop:
sta <NROM_BANK_L
ldx #8
jsr flash_read
ldx #0
.loop:
lda saves_signature, x
cmp BUFFER, x
bne .failed
inx
cpx #8
bne .loop
lda <NROM_BANK_L
jmp .end
.failed:
lda <NROM_BANK_L
clc
adc #8 ; +128kb, next sector
bne .bloop
.end:
sta <SAVES_BANK
rts
; A - game save number
; Returns Y - slot number (or zero if no save)
find_save_slot:
cmp #0
bne .savable_game
tay
rts
.savable_game:
ldx <SAVES_BANK
stx <NROM_BANK_L ; storing saves bank number
ldx #$FF
stx <NROM_BANK_H ; at the very end of flash memory
ldx #$10
stx COPY_SOURCE_ADDR ;$8010
ldx #$80
stx COPY_SOURCE_ADDR+1
ldx #16
pha
jsr flash_read
pla
; searching for last save slot
ldy #0
ldx #1
.save_search_loop:
cmp BUFFER, x
bne .save_search_next
pha ; copy X to Y, keep A
txa
tay
pla
.save_search_next:
inx
cpx #16
bne .save_search_loop
; Y contains slot number... or zero
rts
flash_write_prg_ram:
lda #$00
sta COPY_SOURCE_ADDR
lda #$60
sta COPY_SOURCE_ADDR+1
.loop:
lda #$80
sta $A001
ldy #0
.read_loop:
lda [COPY_SOURCE_ADDR], y
sta BUFFER, y
iny
bne .read_loop
ldx #0
jsr flash_write
inc COPY_DEST_ADDR+1
inc COPY_SOURCE_ADDR+1
bpl .loop
rts
flash_force_erase:
jsr error_sound
jsr saving_warning_show
lda #$F0
sta NROM_BANK_L
lda #$FF
sta NROM_BANK_H
jsr flash_erase_sector
jsr switch_sector
jsr flash_erase_sector
lda #0
sta SAVES_BANK
jsr saving_warning_hide
rts
flash_format_sector:
ldx <SAVES_BANK
stx <NROM_BANK_L ; storing saves bank number
ldx #$FF
stx <NROM_BANK_H ; at the very end of flash memory
jsr flash_erase_sector
ldy #8
.load:
lda saves_signature, y
sta BUFFER, y
dey
bpl .load
lda #$00
sta COPY_DEST_ADDR
lda #$80
sta COPY_DEST_ADDR+1
ldx #8
jsr flash_write
lda #$20
sta <STATE_CELL_NEXT
lda #$80
sta <STATE_CELL_NEXT+1
rts
flash_cleanup:
lda #SAVES_COUNT
beq flash_format_sector ; no games with saves
; format other sector (erase, write signature)
; go to dest bank
jsr switch_sector ; dest
jsr flash_format_sector ; write signature
jsr switch_sector ; source
lda #0
.save_id_loop:
cmp #SAVES_COUNT
beq .end
clc
adc #1 ; next
cmp <LAST_STARTED_SAVE
beq .save_id_loop ; skip actual save
jsr find_save_slot
cpy #0 ; if save slop is 0...
beq .save_id_loop ; not found, next
pha ; save current save id
sta BUFFER
tya
pha ; save slot id
clc
adc #$10
sta COPY_DEST_ADDR
lda #$80
sta COPY_DEST_ADDR+1
jsr switch_sector ; dest
ldx #1
jsr flash_write
jsr switch_sector ; source
pla ; load slot id
pha ; but still keep it in stack
lsr A
clc
adc <SAVES_BANK
sta <NROM_BANK_L
lda #$FF
sta <NROM_BANK_H
lda #$00
sta COPY_SOURCE_ADDR
sta COPY_DEST_ADDR
pla ; load slot id
and #1
bne .subbankA0
lda #$80
bmi .subbank
.subbankA0:
lda #$A0
.subbank:
sta COPY_SOURCE_ADDR+1
sta COPY_DEST_ADDR+1
.copy_loop:
ldx #0
jsr flash_read ; read 256 bytes to buffer
jsr switch_sector ; dest sector
ldx #0
jsr flash_write ; write 256 bytes from buffer
jsr switch_sector ; source sector
inc COPY_SOURCE_ADDR+1 ; +256 of source address
inc COPY_DEST_ADDR+1 ; +256 of dest address
lda COPY_DEST_ADDR+1 ; load high byte of dest address and check it
cmp #$A0 ; $A0? it's end of data
beq .copy_end ; go to end
cmp #$C0 ; $C0? it's end too...
bne .copy_loop ; continue copying otherwise
.copy_end:
pla ; restore save id from stack
jmp .save_id_loop ; repeat with next game
.end:
ldx <SAVES_BANK
stx <NROM_BANK_L ; storing saves bank number
ldx #$FF
stx <NROM_BANK_H ; at the very end of flash memory
jsr flash_erase_sector ; erasing source sector
jsr switch_sector ; finally switching to new sector
rts
switch_sector:
pha
lda <SAVES_BANK
eor #8 ; 8 banks*16kb = sector size
sta <SAVES_BANK
lda <NROM_BANK_L
eor #8 ; 8 banks*16kb = sector size
sta <NROM_BANK_L
pla
rts
saves_signature:
.db 'C','O','O','L','S','A','V','E'
|