diff --git a/linker_scripts/apple2_5a00.inc b/linker_scripts/apple2_5a00.inc new file mode 100644 index 00000000..09bc28f2 --- /dev/null +++ b/linker_scripts/apple2_5a00.inc @@ -0,0 +1,12 @@ +MEMORY { + ZP: start = $00, size = $1A, type = rw; + RAM: start = $5a00, size = $5000, file = %O; +} + +SEGMENTS { +CODE: load = RAM, type = ro, align = $100; +RODATA: load = RAM, type = ro; +DATA: load = RAM, type = rw; +BSS: load = RAM, type = bss, define = yes; +ZEROPAGE: load = ZP, type = zp; +} diff --git a/linker_scripts/apple2_800.inc b/linker_scripts/apple2_800.inc new file mode 100644 index 00000000..85e85a06 --- /dev/null +++ b/linker_scripts/apple2_800.inc @@ -0,0 +1,12 @@ +MEMORY { + ZP: start = $00, size = $1A, type = rw; + RAM: start = $800, size = $8E00, file = %O; +} + +SEGMENTS { +CODE: load = RAM, type = ro, align = $100; +RODATA: load = RAM, type = ro; +DATA: load = RAM, type = rw; +BSS: load = RAM, type = bss, define = yes; +ZEROPAGE: load = ZP, type = zp; +} diff --git a/still_alive/Makefile b/still_alive/Makefile index 65264cc5..6f470a94 100644 --- a/still_alive/Makefile +++ b/still_alive/Makefile @@ -5,25 +5,50 @@ TOKENIZE = ../asoft_basic-utils/tokenize_asoft all: still_alive.dsk -still_alive.dsk: STILL_ALIVE TITLE.BAS ENDING - $(DOS33) -y still_alive.dsk BSAVE -a 0x0C00 STILL_ALIVE +still_alive.dsk: SA_COMPRESSED STILL_ALIVE TITLE.BAS ENDING + $(DOS33) -y still_alive.dsk BSAVE -a 0x5a00 SA_COMPRESSED + $(DOS33) -y still_alive.dsk BSAVE -a 0x0800 STILL_ALIVE $(DOS33) -y still_alive.dsk BSAVE -a 0x1800 ENDING $(DOS33) -y still_alive.dsk SAVE A TITLE.BAS # $(DOS33) -y still_alive.dsk SAVE B GLADOS.HGR +SA_COMPRESSED: sa_compressed.o + ld65 -o SA_COMPRESSED sa_compressed.o -C ../linker_scripts/apple2_5a00.inc + +sa_compressed.o: sa_compressed.s \ + STILL_ALIVE.lz4 ENDING.lz4 SA.ED.lz4 SA.KR4.lz4 + ca65 -o sa_compressed.o sa_compressed.s -l sa_compressed.lst + +SA.ED.lz4: SA.ED + lz4 -f -16 SA.ED + truncate --size=-8 SA.ED.lz4 + +SA.KR4.lz4: SA.KR4 + lz4 -f -16 SA.KR4 + truncate --size=-8 SA.KR4.lz4 + +STILL_ALIVE.lz4: STILL_ALIVE + lz4 -f -16 STILL_ALIVE + truncate --size=-8 STILL_ALIVE.lz4 + +ENDING.lz4: ENDING + lz4 -f -16 ENDING + truncate --size=-8 ENDING.lz4 + + STILL_ALIVE: still_alive.o - ld65 -o STILL_ALIVE still_alive.o -C ../linker_scripts/apple2_c00.inc + ld65 -o STILL_ALIVE still_alive.o -C ../linker_scripts/apple2_800.inc still_alive.o: still_alive.s \ - ../asm_routines/mockingboard.s \ - ../asm_routines/lz4_decode.s \ + mockingboard_a.s lz4_decode.s \ display_art.s display_lyrics.s \ - sa_ed.s duet.s SA.ED \ + sa_ed.s duet.s \ sa_mb.s ascii_art.inc \ interrupt_handler.s \ ascii_art.inc ascii_art_lz4.inc lyrics.inc zp.inc ca65 -o still_alive.o still_alive.s -l still_alive.lst + ENDING: ending.o ld65 -o ENDING ending.o -C ../linker_scripts/apple2_1800.inc @@ -35,4 +60,4 @@ TITLE.BAS: title.bas $(TOKENIZE) < title.bas> TITLE.BAS clean: - rm -f *~ *.o *.lst STILL_ALIVE SA_ED ENDING + rm -f *~ *.o *.lz4 *.lst STILL_ALIVE SA_ED ENDING SA_COMPRESSED diff --git a/still_alive/README.still_alive b/still_alive/README.still_alive index cc2f93bf..db039f28 100644 --- a/still_alive/README.still_alive +++ b/still_alive/README.still_alive @@ -48,14 +48,15 @@ have art loading properly 40col: 18910 lz4 encode the ascii art: 15529 revert because lz4 code not re-entrant: 18910 add 4-channel music (KR4): 20128 -add cursor (a bit distracting) 20162 +add cursor (a bit distracting): 20162 feature complete 40 col: 20180 intial 80 col support: 20191 80 col cursor: 20344 strip out some unneeded text printing: 19962 -merge ED and MB code 24993 +merge ED and MB code: 24993 properly select between versions: 25001 merge the ED and MB lyrics: 22828 +80 column ED, wait for keypress+reboot at end: 22852 Memory Map: @@ -73,12 +74,12 @@ D000: ROM FFFF: END -Code: c00 - 8c00 32k - (8k=audio, 5k=lyrics, 3k=art) -Sound buffers: 5e00-9600 = 14k - Plan: - Load compressed 18k image at $5000 - $9600 + Load compressed 16k image ($4000) at $5600 - $9600 Decompress ENDING sequence at $1800 - $4000 - When done, decompress STILL_ALIVE (22k) at $C00 - $5000 - + (writes background to HGR graphics automatically) + When done: + + STILL_ALIVE ($5a00, 22.5k) at $0c00 - $6600 + + Sound buffers ($3800, 14k) at $5e00 - $9600 + (the overlap is OK, the ED file is over-written by + the mockingboard buffers, but that is not used by MB) diff --git a/still_alive/gr_offsets.s b/still_alive/gr_offsets.s new file mode 100644 index 00000000..f9f575be --- /dev/null +++ b/still_alive/gr_offsets.s @@ -0,0 +1,5 @@ + ; move these to zero page for slight speed increase? +gr_offsets: + .word $400,$480,$500,$580,$600,$680,$700,$780 + .word $428,$4a8,$528,$5a8,$628,$6a8,$728,$7a8 + .word $450,$4d0,$550,$5d0,$650,$6d0,$750,$7d0 diff --git a/still_alive/lz4_decode.s b/still_alive/lz4_decode.s new file mode 100644 index 00000000..66a7c878 --- /dev/null +++ b/still_alive/lz4_decode.s @@ -0,0 +1,207 @@ +; LZ4 data decompressor for Apple II + +; Code by Peter Ferrie (qkumba) (peter.ferrie@gmail.com) +; "LZ4 unpacker in 143 bytes (6502 version) (2013)" +; http://pferrie.host22.com/misc/appleii.htm +; This is that code, but with comments and labels added for clarity. +; I also found a bug when decoding with runs of multiples of 256 +; which has since been fixed upstream. + +; For LZ4 reference see +; https://github.com/lz4/lz4/wiki/lz4_Frame_format.md + +; LZ4 summary: +; +; HEADER: +; Should: check for magic number 04 22 4d 18 +; FLG: 64 in our case (01=version, block.index=1, block.checksum=0 +; size=0, checksum=1, reserved +; MAX Blocksize: 40 (64kB) +; HEADER CHECKSUM: a7 +; BLOCK HEADER: 4 bytes (le) If highest bit set, uncompressed! +; BLOCKS: +; Token byte. High 4-bits literal length, low 4-bits copy length +; + If literal length==15, then following byte gets added to length +; If that byte was 255, then keep adding bytes until not 255 +; + The literal bytes follow. There may be zero of them +; + Next is block copy info. little-endian 2-byte offset to +; be subtracted from current read position indicating source +; + The low 4-bits of the token are the copy length, which needs +; 4 added to it. As with the literal length, if it is 15 then +; you read a byte and add (and if that byte is 255, keep adding) + +;LZ4_SRC EQU $00 +;LZ4_DST EQU $02 +;LZ4_END EQU $04 +;COUNT EQU $06 +;DELTA EQU $08 + +;UNPACK_BUFFER EQU $5E00 ; offset of first unpacked byte + + + ;====================== + ; LZ4 decode + ;====================== + ; input buffer in LZ4_SRC + ; output buffer hardcoded still + ; size in ENDH:ENDL + +lz4_decode: + lda LZ4_SRC ; packed data offset + clc + adc LZ4_END + sta LZ4_END + lda LZ4_SRC+1 + adc LZ4_END+1 + sta LZ4_END+1 + + lda #>UNPACK_BUFFER ; original unpacked data offset + sta LZ4_DST+1 + lda # Latch Address -> Inactive -> Write Data -> Inactive + + ;========================================= + ; Write Right/Left to save value AY-3-8910 + ;========================================= + ; register in X + ; value in MB_VALUE + +write_ay_both: + ; address + stx MOCK_6522_ORA1 ; put address on PA1 ; 3 + stx MOCK_6522_ORA2 ; put address on PA2 ; 3 + lda #MOCK_AY_LATCH_ADDR ; latch_address on PB1 ; 2 + sta MOCK_6522_ORB1 ; latch_address on PB1 ; 3 + sta MOCK_6522_ORB2 ; latch_address on PB2 ; 3 + lda #MOCK_AY_INACTIVE ; go inactive ; 2 + sta MOCK_6522_ORB1 ; 3 + sta MOCK_6522_ORB2 ; 3 + + ; value + lda MB_VALUE ; 3 + sta MOCK_6522_ORA1 ; put value on PA1 ; 3 + sta MOCK_6522_ORA2 ; put value on PA2 ; 3 + lda #MOCK_AY_WRITE ; ; 2 + sta MOCK_6522_ORB1 ; write on PB1 ; 3 + sta MOCK_6522_ORB2 ; write on PB2 ; 3 + lda #MOCK_AY_INACTIVE ; go inactive ; 2 + sta MOCK_6522_ORB1 ; 3 + sta MOCK_6522_ORB2 ; 3 + + rts ; 6 + ;=========== + ; 53 + ;======================================= + ; clear ay -- clear all 14 AY registers + ; should silence the card + ;======================================= +clear_ay_both: + ldx #14 + lda #0 + sta MB_VALUE +clear_ay_left_loop: + jsr write_ay_both + dex + bpl clear_ay_left_loop + rts + + ;======================================= + ; Detect a Mockingboard card + ;======================================= + ; Based on code from the French Touch "Pure Noise" Demo + ; Attempts to time an instruction sequence with a 6522 + ; + ; If found, puts in bMB + ; MB_ADDRL:MB_ADDRH has address of Mockingboard + ; returns X=0 if not found, X=1 if found + +mockingboard_detect: + lda #0 + sta MB_ADDRL + +mb_detect_loop: ; self-modifying + lda #$07 ; we start in slot 7 ($C7) and go down to 0 ($C0) + ora #$C0 ; make it start with C + sta MB_ADDRH + ldy #04 ; $CX04 + ldx #02 ; 2 tries? +mb_check_cycle_loop: + lda (MB_ADDRL),Y ; timer 6522 (Low Order Counter) + ; count down + sta TEMP ; 3 cycles + lda (MB_ADDRL),Y ; + 5 cycles = 8 cycles + ; between the two accesses to the timer + sec + sbc TEMP ; subtract to see if we had 8 cycles + cmp #$f8 ; -8 + bne mb_not_in_this_slot + dex ; decrement, try one more time + bne mb_check_cycle_loop ; loop detection + inx ; Mockingboard found (X=1) +done_mb_detect: + ;stx bMB ; store result to bMB + rts ; return + +mb_not_in_this_slot: + dec mb_detect_loop+1 ; decrement the "slot" (self_modify) + bne mb_detect_loop ; loop down to one + ldx #00 + beq done_mb_detect + + + ;======================================= + ; Detect a Mockingboard card in Slot4 + ;======================================= + ; Based on code from the French Touch "Pure Noise" Demo + ; Attempts to time an instruction sequence with a 6522 + ; + ; MB_ADDRL:MB_ADDRH has address of Mockingboard + ; returns X=0 if not found, X=1 if found + +mockingboard_detect_slot4: + lda #0 + sta MB_ADDRL + +mb4_detect_loop: ; self-modifying + lda #$04 ; we're only looking in Slot 4 + ora #$C0 ; make it start with C + sta MB_ADDRH + ldy #04 ; $CX04 + ldx #02 ; 2 tries? +mb4_check_cycle_loop: + lda (MB_ADDRL),Y ; timer 6522 (Low Order Counter) + ; count down + sta TEMP ; 3 cycles + lda (MB_ADDRL),Y ; + 5 cycles = 8 cycles + ; between the two accesses to the timer + sec + sbc TEMP ; subtract to see if we had 8 cycles + cmp #$f8 ; -8 + bne mb4_not_in_this_slot + dex ; decrement, try one more time + bne mb4_check_cycle_loop ; loop detection + inx ; Mockingboard found (X=1) +done_mb4_detect: + rts ; return + +mb4_not_in_this_slot: + ldx #00 + beq done_mb4_detect + diff --git a/still_alive/sa_compressed.s b/still_alive/sa_compressed.s new file mode 100644 index 00000000..874a2b36 --- /dev/null +++ b/still_alive/sa_compressed.s @@ -0,0 +1,313 @@ +; Still Alive Decompression Util + +.include "zp.inc" + +UNPACK_ENDING EQU $1800 +UNPACK_ALIVE EQU $0800 + +start: + ;============================== + ; Unpack the Ending routine from $1800-$4000 + ;============================== + +unpack_ending: + lda #ending + sta LZ4_SRC+1 + + lda #ending_end + sta LZ4_END+1 + + lda #UNPACK_ENDING ; original unpacked data offset + sta LZ4_DST+1 + + + + jsr lz4_decode + + ;============================== + ; Run the ending routine + ;============================== + + jsr $1800 + + ;===================================== + ; Unpack the main code from $800-$367d + ;===================================== + + lda #still_alive + sta LZ4_SRC+1 + + lda #still_alive_end + sta LZ4_END+1 + + lda #UNPACK_ALIVE ; original unpacked data offset + sta LZ4_DST+1 + + jsr lz4_decode + + ;===================================== + ; Unpack the music right after previous + ;===================================== + + ;===================================== + ; MB SA.KR4 from 367D to 58ff + ;===================================== + + lda #music_mb + sta LZ4_SRC+1 + + lda #music_mb_end + sta LZ4_END+1 + +; lda #UNPACK_ALIVE ; original unpacked data offset +; sta LZ4_DST+1 + + + jsr lz4_decode + + + + ;============================== + ; Run still alive + ;============================== + + jmp $800 + + + + +; LZ4 data decompressor for Apple II + +; Code by Peter Ferrie (qkumba) (peter.ferrie@gmail.com) +; "LZ4 unpacker in 143 bytes (6502 version) (2013)" +; http://pferrie.host22.com/misc/appleii.htm +; This is that code, but with comments and labels added for clarity. +; I also found a bug when decoding with runs of multiples of 256 +; which has since been fixed upstream. + +; For LZ4 reference see +; https://github.com/lz4/lz4/wiki/lz4_Frame_format.md + +; LZ4 summary: +; +; HEADER: +; Should: check for magic number 04 22 4d 18 +; FLG: 64 in our case (01=version, block.index=1, block.checksum=0 +; size=0, checksum=1, reserved +; MAX Blocksize: 40 (64kB) +; HEADER CHECKSUM: a7 +; BLOCK HEADER: 4 bytes (le) If highest bit set, uncompressed! +; BLOCKS: +; Token byte. High 4-bits literal length, low 4-bits copy length +; + If literal length==15, then following byte gets added to length +; If that byte was 255, then keep adding bytes until not 255 +; + The literal bytes follow. There may be zero of them +; + Next is block copy info. little-endian 2-byte offset to +; be subtracted from current read position indicating source +; + The low 4-bits of the token are the copy length, which needs +; 4 added to it. As with the literal length, if it is 15 then +; you read a byte and add (and if that byte is 255, keep adding) + + + ;====================== + ; LZ4 decode + ;====================== + ; input buffer in LZ4_SRC + ; output buffer in LZ4_DST + ; end in LZ4_END + +lz4_decode: + +unpmain: + ldy #0 ; used to index, always zero + +parsetoken: + jsr getsrc ; get next token + pha ; save for later (need bottom 4 bits) + + lsr ; number of literals in top 4 bits + lsr ; so shift into place + lsr + lsr + beq copymatches ; if zero, then no literals + ; jump ahead and copy + + jsr buildcount ; add up all the literal sizes + ; result is in ram[count+1]-1:A + tax ; now in ram[count+1]-1:X + jsr docopy ; copy the literals + + lda LZ4_SRC ; 16-bit compare + cmp LZ4_END ; to see if we have reached the end + lda LZ4_SRC+1 + sbc LZ4_END+1 + bcs done + +copymatches: + jsr getsrc ; get 16-bit delta value + sta DELTA + jsr getsrc + sta DELTA+1 + + pla ; restore token + and #$0f ; get bottom 4 bits + ; match count. 0 means 4 + ; 15 means 19+, must be calculated + + jsr buildcount ; add up count bits, in ram[count+1]-:A + + clc + adc #4 ; adjust count by 4 (minmatch) + + tax ; now in ramp[count+1]-1:X + + beq copy_no_adjust ; BUGFIX, don't increment if + ; exactly a multiple of 0x100 + bcc copy_no_adjust + + inc COUNT+1 ; increment if we overflowed +copy_no_adjust: + + lda LZ4_SRC+1 ; save src on stack + pha + lda LZ4_SRC + pha + + sec ; subtract delta + lda LZ4_DST ; from destination, make new src + sbc DELTA + sta LZ4_SRC + lda LZ4_DST+1 + sbc DELTA+1 + sta LZ4_SRC+1 + + jsr docopy ; do the copy + + pla ; restore the src + sta LZ4_SRC + pla + sta LZ4_SRC+1 + + jmp parsetoken ; back to parsing tokens + +done: + pla + + rts + + + + ;========= + ; getsrc + ;========= + ; gets byte from src into A, increments pointer +getsrc: + lda (LZ4_SRC), Y ; get a byte from src + inc LZ4_SRC ; increment pointer + bne done_getsrc ; update 16-bit pointer + inc LZ4_SRC+1 ; on 8-bit overflow +done_getsrc: + rts + + ;============ + ; buildcount + ;============ +buildcount: + ldx #1 ; high count starts at 1 + stx COUNT+1 ; (loops at zero?) + cmp #$0f ; if LITERAL_COUNT < 15, we are done + bne done_buildcount +buildcount_loop: + sta COUNT ; save LITERAL_COUNT (15) + jsr getsrc ; get the next byte + tax ; put in X + clc + adc COUNT ; add new byte to old value + bcc bc_8bit_oflow ; if overflow, increment high byte + inc COUNT+1 +bc_8bit_oflow: + inx ; check if read value was 255 + beq buildcount_loop ; if it was, keep looping and adding +done_buildcount: + rts + + ;============ + ; getput + ;============ + ; gets a byte, then puts the byte +getput: + jsr getsrc + ; fallthrough to putdst + + ;============= + ; putdst + ;============= + ; store A into destination +putdst: + sta (LZ4_DST), Y ; store A into destination + inc LZ4_DST ; increment 16-bit pointer + bne putdst_end ; if overflow, increment top byte + inc LZ4_DST+1 +putdst_end: + rts + + ;============================= + ; docopy + ;============================= + ; copies ram[count+1]-1:X bytes + ; from src to dst +docopy: + +docopy_loop: + jsr getput ; get/put byte + dex ; decrement count + bne docopy_loop ; if not zero, loop + dec COUNT+1 ; if zero, decrement high byte + bne docopy_loop ; if not zero, loop + + rts + + +;=============================================== +; External modules +;=============================================== + +; Note, the endings have all been truncated by 8 bytes with truncate +; to skip the checksums at the end + +; Load, skipping 11 bytes of header +ending: +.incbin "ENDING.lz4",11 +ending_end: + +still_alive: +.incbin "STILL_ALIVE.lz4",11 +still_alive_end: + +music_mb: +.incbin "SA.KR4.lz4",11 +music_mb_end: + +music_ed: +.incbin "SA.ED.lz4",11 +music_ed_end: + + diff --git a/still_alive/still_alive.s b/still_alive/still_alive.s index 65a5faf6..87aa0a20 100644 --- a/still_alive/still_alive.s +++ b/still_alive/still_alive.s @@ -91,9 +91,9 @@ reset: ;========= ;routines ;========= -.include "../asm_routines/gr_offsets.s" -.include "../asm_routines/mockingboard_a.s" -.include "../asm_routines/lz4_decode.s" +.include "gr_offsets.s" +.include "mockingboard_a.s" +.include "lz4_decode.s" .include "display_art.s" .include "display_lyrics.s" @@ -114,8 +114,7 @@ art: .include "ascii_art.inc" LZ4_BUFFER: -.incbin "SA.KR4" - music_address: -.incbin "SA.ED" +;.incbin "SA.KR4" +;.incbin "SA.ED" diff --git a/still_alive/title.bas b/still_alive/title.bas index 8c9e923f..2a70563d 100644 --- a/still_alive/title.bas +++ b/still_alive/title.bas @@ -1,4 +1,5 @@ 5 HOME +7 PRINT CHR$(4)"MAXFILES 1" 10 PRINT " __ ___ ___ _ ___ __" 15 PRINT "/ | | | | / \ | | | | |" 20 PRINT "\_ | | | | |_| | | | | |_" @@ -27,4 +28,4 @@ 130 POKE 768,1:POKE 769,1:GOTO 160 140 POKE 768,0:POKE 769,0:GOTO 160 150 POKE 768,1:POKE 769,0 -160 PRINT CHR$(4);"BRUN STILL_ALIVE" +160 PRINT CHR$(4);"BRUN SA_COMPRESSED"