;license:MIT ;(c) 2020-2 by 4am ; ; disk functions to load puzzles, and load & save puzzle completion status ; ; Public functions: ; - MaybeLoadWorldFromDisk ; - PreParseWorldData ; - LoadProgressFromDisk ; - LoadProgressFromMemory ; - LoadPuzzleFromMemory ; - MarkPuzzleCompleted ; - FindPackedProgress ; ; Public variables: ; - gWorldID ; - gPuzzleID ; ; Public constants: ; - kPuzzleWidths ; gWorldID ; a world is a group of 100 similar puzzles (12 worlds in total, 0-based) !byte $FD ; 0x00..0x0B gPuzzleID ; ID of current puzzle within the current world (100 puzzles per world, 0-based) !byte $FD ; 0x00..0x63 kPuzzleWidths ; width in letters of the puzzles in each world (index=world) !byte 4,5,6,7 ; note: every puzzle in a world is the same width !byte 4,5,6,7 !byte 4,5,6,7 checksum = $FB packedvalue = $FC bitcount = $FD ;------------------------------------------------------------------------------ ; MaybeLoadWorldFromDisk ; load a world (100 puzzles) from a file on disk ; caches the last world loaded to reduce unnecessary disk access ; ; in: A = world ID ; out: C clear if file loaded and parsed successfully (or it was already in memory) ; C set if file could not be loaded and/or parsed ; other registers/flags clobbered ;------------------------------------------------------------------------------ MaybeLoadWorldFromDisk cmp WorldFileLoaded bne + @success clc rts + sta WorldFileLoaded jsr $ff58 jsr OpenFile !word @filename !word WORLDFILEBUFFER bcs @failure sta @refnum lda WorldFileLoaded asl asl tax lda data_index, x ldy data_index+1, x tax lda @refnum jsr SeekFile lda WorldFileLoaded asl asl tax lda data_index+2, x sta @readlen lda data_index+3, x sta @readlen+1 jsr ReadFile @refnum !byte $FD ; SMC !word compressed_data ; data address @readlen !word $FDFD ; data length php lda @refnum jsr CloseFile plp bcs @failure lda #compressed_data sta get_crunched_byte+2 jsr decrunch jsr PreParseWorldData cpx #100 beq @success @failure lda WorldFileLoaded jsr CloseFile ; ignore error, if any lda #$FF sta WorldFileLoaded sec rts @filename !raw 8,"MPL.DATA" WorldFileLoaded !byte $FF ; no file ;------------------------------------------------------------------------------ ; PreParseWorldData ; build internal list of pointers to the start of each puzzle within a world, ; after the raw world data has been loaded into memory ; ; in: WORLDDATA contains raw world data from disk ; out: X = number of puzzles found ; other registers & flags clobbered ; $FE/$FF clobbered ;------------------------------------------------------------------------------ PreParseWorldData lda #<(WORLDDATA-1) sta $FE lda #>(WORLDDATA-1) sta $FF ; ($FE) -> one byte before start of file data ldy #$00 @newLine ldx #$00 ; X = puzzle ID beq @emptyline ; always branches @skipLine ; skip to CR jsr @IncAndGetChar cmp #$0A ; CR bne @skipLine @emptyline jsr @IncAndGetChar cmp #$0A ; CR in first position (blank line) -> skip beq @emptyline cmp #$23 ; '#' starts a comment -> skip to CR beq @skipLine cmp #$5B ; '[' ends the parsing beq @exit lda $FE sta PUZZLELO, x lda $FF sta PUZZLEHI, x inx bne @skipLine ; always branches @IncAndGetChar ; in: Y = 0 +INCADDR $FE lda ($FE),y cmp #$0D ; CR - hide it beq @IncAndGetChar @exit rts ;------------------------------------------------------------------------------ ; LoadProgressFromDisk ; load PROGRESS file into memory ; ; see comments in MarkPuzzleCompleted for file format ; ; in: none ; out: C clear if file was read successfully ; C set if error occurred ;------------------------------------------------------------------------------ LoadProgressFromDisk jsr LoadFile1Shot !word progressFile !word PACKEDPROGRESS ; data address !word $00C0 ; data length !word PROGRESSFILEBUFFER rts progressFile !raw 12,"MPL.PROGRESS" ;------------------------------------------------------------------------------ ; LoadProgressFromMemory ; unpacks one world's progress into a 1-byte-per-puzzle array for easier access ; ; in: progress file must be in memory (call MaybeLoadProgressFromDisk and ; check C) ; out: A = next puzzle ID ;------------------------------------------------------------------------------ LoadProgressFromMemory lda gWorldID jsr FindPackedProgress ; ($FE) -> start of the 16-byte structure for this world ; zero destination array ldx #103 lda #0 - sta PROGRESS, x dex bpl - ; unpack values ldy #0 ; index into ($FE) (source data) ldx #0 ; index into PROGRESS (destination data) -- lda #8 sta bitcount lda ($FE), y - lsr rol PROGRESS, x ; note: PROGRESS structure is 104 bytes (a multiple of 8), inx ; even though only the first 100 bytes are used dec bitcount bne - iny cpy #$0D bne -- lda ($FE), y ; A = next puzzle ID rts ;------------------------------------------------------------------------------ ; LoadPuzzleFromMemory ; loads one puzzle from the in-memory cache of the world file, into the ; 'now playing this puzzle' data structures ; ; in: X = puzzle ID ; world file must be in memory (call MaybeLoadWorldFromDisk and check C) ; out: all registers and flags clobbered ;------------------------------------------------------------------------------ LoadPuzzleFromMemory lda PUZZLELO, x sta $FE lda PUZZLEHI, x sta $FF bne @addLine ; always branches @lineLoop +INCADDR $FE @addLine ; ($FE) -> characters for 1 line of this puzzle (no prefix) jsr AddLineToPuzzle ldx gWorldID lda kPuzzleWidths, x clc adc $FE sta $FE bcc + inc $FF + ldy #0 lda ($FE), y cmp #$2C ; ',' beq @lineLoop ; if next byte is ',' then there is another line so loop back to parse it ; otherwise the next byte is a delimiter between lines and target words ; (probably '|' but we don't actually check it) @targetWordLoop +INCADDR $FE ; ($FE) -> characters for 1 target word of this puzzle (no prefix) jsr AddTargetWordToPuzzle ldx gWorldID lda kPuzzleWidths, x clc adc $FE sta $FE bcc + inc $FF + ldy #0 lda ($FE), y cmp #$2C ; ',' beq @targetWordLoop ; if next byte is ',' then there is another target word so loop back to parse it cmp #$7C ; if next byte is '|' then there is a message, otherwise we're done bne @exit +INCADDR $FE @messageLoop lda ($FE), y cmp #$0A beq @setMessage cmp #$0D beq @setMessage iny sta @messageBuffer, y bne @messageLoop @setMessage sty @messageBuffer +LDADDR @messageBuffer jsr SetNextMessage @exit rts @messageBuffer !byte 40 !raw " " ;------------------------------------------------------------------------------ ; MarkPuzzleCompleted/MarkPuzzleSkipped ; ; in: X = puzzle ID ; out: A = next puzzle ID ; X = number of puzzles completed in this world (0x01..0x64) ;------------------------------------------------------------------------------ MarkPuzzleCompleted ; Completion status for the current world (100 puzzles) is stored unpacked in ; memory for easy access ; Mark this puzzle as completed in the unpacked table lda #1 !byte $2C MarkPuzzleSkipped lda #0 sta PROGRESS, x ; Find the next uncompleted puzzle clc beq @next +HIDE_NEXT_BYTE -- sec - lda PROGRESS, x beq @done @next inx cpx #100 bne - ldx #0 ; start over at the puzzle 0 bcc -- ; If we fall through here, it means all puzzles in this world ; have been completed (yay!), and we'll leave X at 0. ; If we branch here, we found the next uncompleted puzzle ; and X contains that puzzle ID. @done txa pha ; push next puzzle ID ; ; Completion status for all puzzles across all worlds is stored in packed ; bitfields, 1 bit per puzzle, so that the file on disk fits in a single ; block (actually a single sector). ; ; This is the file format (0xC0 bytes): ; +0x00 bit 0 - completion status for world 0x00, puzzle 0x00 ; bit 1 - completion status for world 0x00, puzzle 0x01 ; bit 2 - completion status for world 0x00, puzzle 0x02 ; bit 3 - completion status for world 0x00, puzzle 0x03 ; bit 4 - completion status for world 0x00, puzzle 0x04 ; bit 5 - completion status for world 0x00, puzzle 0x05 ; bit 6 - completion status for world 0x00, puzzle 0x06 ; bit 7 - completion status for world 0x00, puzzle 0x07 ; ... ; +0x0C bit 0 - completion status for world 0x00, puzzle 0x60 ; bit 1 - completion status for world 0x00, puzzle 0x61 ; bit 2 - completion status for world 0x00, puzzle 0x62 ; bit 3 - completion status for world 0x00, puzzle 0x63 ; bit 4-7 always 0 ; +0x0D next puzzle to play in world 0x00 (0x00..0x63) ; +0x0E percent complete (0x00..0x64) ; +0x0F XOR of bytes +0x00..+0x0E ; +0x10..+0x1F same format but for world 0x01 ; +0x20..+0x2F same format but for world 0x02 ; +0x30..+0x3F same format but for world 0x03 ; +0x40..+0x4F same format but for world 0x04 ; +0x50..+0x5F same format but for world 0x05 ; +0x60..+0x6F same format but for world 0x06 ; +0x70..+0x7F same format but for world 0x07 ; +0x80..+0x8F same format but for world 0x08 ; +0x90..+0x9F same format but for world 0x09 ; +0xA0..+0xAF same format but for world 0x0A ; +0xB0..+0xBF same format but for world 0x0B ; ; Among other things, this means that a file filled with zeroes is valid ; and means that no puzzles have been completed in any world and you should ; start at the beginning. So that's nice. ; lda gWorldID jsr FindPackedProgress ; ($FE) -> start of the 16-byte structure for this world ; zero out all 16 bytes ldy #$0F lda #$00 - sta ($FE), y dey bpl - sta checksum ; build packed values ldy #0 ldx #0 -- lda #8 sta bitcount lda #0 sta packedvalue - lda PROGRESS, x ror ror packedvalue inx dec bitcount bne - lda packedvalue sta ($FE), y eor checksum sta checksum iny cpy #$0D bne -- pla ; A = next puzzle ID (pushed earlier) pha sta ($FE), y eor checksum sta checksum ; count number of completed puzzles ldy #0 ldx #99 - lda PROGRESS, x beq + iny + dex bpl - tya ; A = number of puzzles completed (out of 100, so also the percent complete) pha ldy #$0E sta ($FE), y eor checksum iny sta ($FE), y jsr SaveFile1Shot !word progressFile !byte 4 !word 0 !word PACKEDPROGRESS !word $00C0 !word PROGRESSFILEBUFFER pla tax pla rts ;------------------------------------------------------------------------------ ; FindPackedProgress ; find address of a world's packed progress data ; ; in: A = world ID ; out: ($FE) points to first byte of this world's packed progress (16-byte structure) ; A/Y clobbered ; X preserved ; all flags clobbered ;------------------------------------------------------------------------------ FindPackedProgress pha +LDADDR PACKEDPROGRESS +ST16 $FE pla asl asl asl asl clc adc $FE sta $FE bcc + inc $FF + rts get_crunched_byte lda compressed_data inc get_crunched_byte+1 bne + inc get_crunched_byte+2 + rts