From cfc455f1cae2656e81e42d81f4dd5d35b72a39d6 Mon Sep 17 00:00:00 2001 From: hkz Date: Sat, 30 Aug 2025 10:01:49 +0200 Subject: [PATCH] Merged dii_refactoring, modularize code for 2.0 --- CHANGELOG.md | 23 + Makefile | 78 +- README.md | 54 +- data/LOADS.bin | Bin 0 -> 8192 bytes data/STATE.bin | Bin 0 -> 256 bytes graphics/loading_screen.aseprite | Bin 4377 -> 4337 bytes graphics/loading_screen.png | Bin 3430 -> 3392 bytes linker-files/linker.scm | 19 - linker-files/master.scm | 30 + linker-files/module.scm | 19 + pics/title.png | Bin 0 -> 4706 bytes src/disk2.h | 46 ++ src/disk2.s | 726 ++++++++++++++++++ src/dlog_data.h | 16 + src/dlog_main.c | 64 ++ src/dos_floppy.c | 185 +++++ src/dos_floppy.h | 81 ++ src/game_data.h | 14 + src/game_graphics.c | 33 +- src/game_graphics.h | 2 + src/game_main.c | 165 ++++ src/input.h | 2 + src/input_asm.s | 26 +- src/intro_main.c | 39 + src/loading_screen.c | 2 +- src/main.c | 152 ---- src/master_func.s | 23 + src/master_main.c | 185 +++++ src/mem_map.h | 5 +- src/shared_page.h | 21 + src/sound.h | 16 + src/sound.s | 161 ++++ src/state_page.h | 18 + src/{tk2k_startup.s => tk2k_startup_master.s} | 1 + src/tk2k_startup_module.s | 123 +++ src/utility.c | 14 +- src/utility.h | 7 + 37 files changed, 2101 insertions(+), 249 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 data/LOADS.bin create mode 100644 data/STATE.bin delete mode 100644 linker-files/linker.scm create mode 100644 linker-files/master.scm create mode 100644 linker-files/module.scm create mode 100644 pics/title.png create mode 100644 src/disk2.h create mode 100644 src/disk2.s create mode 100644 src/dlog_data.h create mode 100644 src/dlog_main.c create mode 100644 src/dos_floppy.c create mode 100644 src/dos_floppy.h create mode 100644 src/game_data.h create mode 100644 src/game_main.c create mode 100644 src/intro_main.c delete mode 100644 src/main.c create mode 100644 src/master_func.s create mode 100644 src/master_main.c create mode 100644 src/shared_page.h create mode 100644 src/sound.h create mode 100644 src/sound.s create mode 100644 src/state_page.h rename src/{tk2k_startup.s => tk2k_startup_master.s} (99%) create mode 100644 src/tk2k_startup_module.s diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..05bab1d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +## 2.0 - 2025-08-30 + +_This release removes the tape version of the game._ + +### Changed + +- Game code is now modularized so that only the current module plus a resident master is loaded into memory + +### Added + +- Simple sound effects (replacing the standard *BELL* from the monitor routines) +- Game save and load +- High score save + +### Removed + +- Tape variant of the game + +## 1.0 - 2025-08-20 + +_First release._ \ No newline at end of file diff --git a/Makefile b/Makefile index 798da8b..ec1e49e 100644 --- a/Makefile +++ b/Makefile @@ -5,23 +5,35 @@ ACMD=/d/Users/hkzla/software/applecommander/AppleCommander-ac-1.11.0.jar JAVA=/c/Program\ Files/Microsoft/jdk-21.0.8.9-hotspot/bin/java.exe # Program output -PRG=output +SW_NAME=tk2048 + +MASTER_PRG=master +INTRO_PRG=intro +DLOG_PRG=dlog +GAME_PRG=game # Libraries LIBS=clib-6502.a +MASTER_ASM_SRCS = tk2k_startup_master.s disk2.s master_func.s +MASTER_C_SRCS = master_main.c dos_floppy.c -# Common source files -ASM_SRCS = tk2k_startup.s preserve_zero_pages.s input_asm.s -C_SRCS = main.c monitor_subroutines.c utility.c \ - game_graphics.c game_logic.c input.c \ - line_data.c tiles.c graph_misc_data.c \ - arrows_pic.c loading_screen.c +INTRO_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s +INTRO_C_SRCS = intro_main.c utility.c + +DLOG_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s sound.s +DLOG_C_SRCS = dlog_main.c input.c utility.c game_graphics.c line_data.c + +GAME_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s input_asm.s sound.s +GAME_C_SRCS = game_main.c input.c utility.c game_graphics.c line_data.c game_logic.c arrows_pic.c tiles.c graph_misc_data.c # Object files -OBJS = $(ASM_SRCS:%.s=%.o) $(C_SRCS:%.c=%.o) +MASTER_OBJS = $(MASTER_ASM_SRCS:%.s=%.o) $(MASTER_C_SRCS:%.c=%.o) +INTRO_OBJS = $(INTRO_ASM_SRCS:%.s=%.o) $(INTRO_C_SRCS:%.c=%.o) +DLOG_OBJS = $(DLOG_ASM_SRCS:%.s=%.o) $(DLOG_C_SRCS:%.c=%.o) +GAME_OBJS = $(GAME_ASM_SRCS:%.s=%.o) $(GAME_C_SRCS:%.c=%.o) -all: $(PRG).wav $(PRG).woz +all: $(SW_NAME).woz %.o: %.s as6502 --core=6502 --list-file=$(@:%.o=obj/%.lst) -o obj/$@ $< @@ -29,26 +41,46 @@ all: $(PRG).wav $(PRG).woz %.o: %.c cc6502 --core=6502 -O2 --list-file=$(@:%.o=obj/%.lst) --char-is-unsigned --pedantic-errors -o obj/$@ $< -$(PRG).hex: $(OBJS) - (cd obj ; ln6502 -g ../linker-files/linker.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) - -$(PRG).bin: $(PRG).hex - (cd out ; objcopy -I ihex -O binary $(PRG).hex $(PRG).bin) - -$(PRG).wav: $(PRG).bin - (cd out ; c2t -3 $(PRG).bin,801 $(PRG).wav) +$(MASTER_PRG).hex: $(MASTER_OBJS) + (cd obj ; ln6502 -g ../linker-files/master.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) -$(PRG).dsk: $(PRG).bin - (cd out ; cp ../dsk/TK2048_AUTO_BRUN.dsk ./$(PRG).dsk; cat $(PRG).bin | $(JAVA) -jar $(ACMD) -p $(PRG).dsk HELLO B 0x801) +$(INTRO_PRG).hex: $(INTRO_OBJS) + (cd obj ; ln6502 -g ../linker-files/module.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) + +$(DLOG_PRG).hex: $(DLOG_OBJS) + (cd obj ; ln6502 -g ../linker-files/module.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) -$(PRG).woz: $(PRG).dsk - (cd out ; dsk2woz ./$(PRG).dsk ./$(PRG).woz) +$(GAME_PRG).hex: $(GAME_OBJS) + (cd obj ; ln6502 -g ../linker-files/module.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) + +$(MASTER_PRG).bin: $(MASTER_PRG).hex + (cd out ; objcopy -I ihex -O binary $(MASTER_PRG).hex $(MASTER_PRG).bin) + +$(INTRO_PRG).bin: $(INTRO_PRG).hex + (cd out ; objcopy -I ihex -O binary $(INTRO_PRG).hex $(INTRO_PRG).bin) + +$(DLOG_PRG).bin: $(DLOG_PRG).hex + (cd out ; objcopy -I ihex -O binary $(DLOG_PRG).hex $(DLOG_PRG).bin) + +$(GAME_PRG).bin: $(GAME_PRG).hex + (cd out ; objcopy -I ihex -O binary $(GAME_PRG).hex $(GAME_PRG).bin) + +$(SW_NAME).dsk: $(MASTER_PRG).bin $(INTRO_PRG).bin $(DLOG_PRG).bin $(GAME_PRG).bin + (cd out ; cp ../dsk/TK2048_AUTO_BRUN.dsk ./$(SW_NAME).dsk; \ + cat $(MASTER_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk HELLO B 0x800; \ + cat $(INTRO_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk INTRO b; \ + cat $(DLOG_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk DLOG b; \ + cat $(GAME_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk GAME b; \ + cat ../data/LOADS.bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk LOADS b; \ + cat ../data/STATE.bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk STATE b;) + +$(SW_NAME).woz: $(SW_NAME).dsk + (cd out ; dsk2woz ./$(SW_NAME).dsk ./$(SW_NAME).woz) clean: - -(cd obj ; rm $(OBJS) $(OBJS:%.o=%.lst)) + -rm obj/*.o -rm obj/*.lst -rm out/*.hex -rm out/*.bin - -rm out/*.wav -rm out/*.dsk -rm out/*.woz \ No newline at end of file diff --git a/README.md b/README.md index 3b1350c..34a756e 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,14 @@ By using this project You will agree that I cannot be held responsible if it wil ## Introduction -I recently built myself a Microdigital TK2000, a clone of an almost-clone (the MPF-II) of an Apple II. +![TK2048 V2.0 title screen](pics/title.png) -Building it from the files found in [this github repository](https://github.com/clemarfolly/Microdigital-TK2000) proved to be a [bit of an adventure](https://github.com/hkzlab/TK2000_Build_Notes), -but I took a liking to the computer, so I started developing [boards](https://codeberg.org/hkzlab) for it. +This game is an excuse and a testbed for the development on the Microdigital TK2000 clone [I recently built for myself](https://github.com/hkzlab/TK2000_Build_Notes). -Hardware fiddling is fine, but given that there isn't much software available for the machine and that I wanted to experiment with the [Calypsi toolchain](https://www.calypsi.cc/) a bit, I decided to -pick a simple game concept and develop it for the machine. +My primary objective with this game is building something where I can add support for all my expansion boards (well, at least where it makes a sliver of sense) so I can test and PoC them, beside, +I want to experiment with the [Calypsi toolchain](https://www.calypsi.cc/). -I decided for 2048, the now-classic tile sliding game, as it provided several advantages as a first time/playground project: +I decided for a port of 2048, the now-classic tile sliding game, as it provided several advantages as a first time/playground project: - Doesn't need complex graphics hardware or advanced artist skills - It can be drawn in B/W with static graphics, but animations and colors can be added in the future @@ -25,7 +24,11 @@ I decided for 2048, the now-classic tile sliding game, as it provided several ad - Can be expanded with support for additional hardware (sound cards, video cards, timers) if so desired, but it's not necessary - Floppy drive can be potentially used not only to load the game, but also for saving the high score -![TK2048 main game shown on a green phosphor CRT](pics/screenshot.jpg) +![TK2048 V1.0 main game shown on a green phosphor CRT](pics/screenshot.jpg) + +### Support + +If you wish to support me in building new hardware and software for old machines, [throw a few euros in my direction via Ko-Fi](https://ko-fi.com/hkzlab) ☕! ### Current state @@ -33,13 +36,18 @@ The game is playable, and the following features have been implemented: - B/W mode - Single graphical tileset -- Crude sound (only beeps) +- Crude sound - Control via keyboard - 5x5 grid, with randomized start and new tiles +- Game save/load +- Highscore saving ## How to play -You can move the tiles using the cursor keys. Pushing CTRL-R will make you lose the game and restart it. +- You can move the tiles using the cursor keys +- `CTRL-R` during the game will clear your score and restart +- `CTRL-S` during the game will save the game on floppy and let you continue +- `CTRL-L` during the game will load the previous save from floppy and let you continue from there The game ends once you reach a tile with a value of 2048. @@ -47,28 +55,6 @@ The game ends once you reach a tile with a value of 2048. Just put your floppy in the first drive and power on the TK2000. It will autoboot. -### Tape version - -Power on the TK2000 with the sound player connected to the EAR input. Once at the `>` prompt, type the following - -``` -CALL -159 -``` - -This will drop you into the monitor, `@` prompt. Type - -``` -801.4EBBRA -``` - -And press play on tape. If the load completes successfully, the computer will beep and give you back the prompt. Now type: - -``` -801G -``` - -This will start the game. - ## How to build You need the following: @@ -77,11 +63,7 @@ You need the following: - [Calypsi 6502 toolchain](https://www.calypsi.cc/) (tested with 5.10) - [AppleCommander](https://github.com/AppleCommander/AppleCommander), "ac" command line version (tested with 1.9.0) - [dsk2woz](https://github.com/TomHarte/dsk2woz) -- vitasam's fork of [c2t](https://github.com/vitasam/c2t) ## The future -I plan to refactor the whole game, modularize it, and leverage loading of single modules from a floppy disk. This -should give more flexibility, expandability, free some memory and allow me to exercise the floppy drive routines I wrote. - -As such, V1.0 will probably be the last one supporting loading from tape. \ No newline at end of file +I plan to add (optional) support for several expansion boards I'm working on, starting with a VDP board sporting a TMS9918A graphic processor. \ No newline at end of file diff --git a/data/LOADS.bin b/data/LOADS.bin new file mode 100644 index 0000000000000000000000000000000000000000..be529d4610b8a71a8be0478b80676012a7384ce9 GIT binary patch literal 8192 zcmds6|5Fo36n}enNgzcN5lYgb<^lnev!+p62b4COgf9k}0colILG%!n(Y6>=>{-*? zzux!uZZCJa1dj~Nw0$$V-P_%J-}Zg>?QQ_hK&DyYH}7OJn`|teX=Hz|G%m$Wh$mnn zgLUR`V)=QlFqYpK@5fnRt1XTNI=GF*=pmofqJnLwaGcW~_~4vhcn+Y=s-~U5C-QDH zI|r7HEhOJ*=_B&j{kVEiY5d_@-DiZ;qyV%~!eHWd%8`aow3f7RbTP>bPJQs#vWG!h z1CDPE5Cx}Twr#k+6ffSUUTWm@IHt#i6R+s|4J9tb zk}&kYWpAMhUryu`U?mlpjx|e#d{Mhz(&h?Ry@511oi9*vX0pvn=jMkT;s0f>@$pSX zZ6|mxuC^DfH(mT+f6Wz@Z5>bduAuqpqaMiKEJvcK8%arN@Qj+oC-$EcOSC%%HtU<&Z3v-eNlD=@?0tGn1N zDTIH@NF>eKg;I8+p+Wn(QjBdUbyjk@c=k)K=UxWw=VSBP&D$9vmP9c?t=G}tzocV` z{|$h`TroM9FCqRXmkRsdKpLFR4w?UXwR7`jsbot1{!|MTp@)BU!(mg{Vy4n1_O=r)LLDD_9l z|DVM_CNc`R9%1s;!#^%QV>BoY*0_CwDmxT@Yww9)&nS#m6aVzg&Oh08f7N za5(|@cM?!|_29Qmxx7?rKFepBqoq4qj}3?au0kH{#8OLaE8GBzE|Jp3!bb7CfJ%gMhT z8}q+F1iHfL{SMzF=Ks*V3j9fa3`RyA{NKqW$X<^50QE2d?;q+jd-|>I^75ZYwUzHi z8DD&KS$;?IyLnbPF>n@iT4=OufA>qV{^a%eH-r)fT3X0A0W2C$!iic{%KsNSyMRGy%@b7W)@K2ue9gMsI-AeG|3&wv(gnn0esCGX17tzLv`E=R=Sz8rB z#xMX!rXf85eBS!=+5GSBKbyA}UWaU;Xm{`b6fp0hEL_&uV=54Ti2swLy-$BH;uSz} z?t++}WN{WbovR_HCxOaUd?jMVDsls2#$vb~=y(JmD zB`yH4d$gxKD#s?C6ci}$-54hS2kqz9Y_0Yz#Y*nZ8u1TH)K1=v1Jpc7w!{OiDseXw6XuX` zl@>`({QtHxbCP6z!FLAy=MB1lKfHQ8BUSg69{x4!%`~A)=f~?X{{Osk;xT0LV{69L8XX5;=y}G)R zy*$6ju0PLYUIf3M_KwY>y^8m0?SoY7Fx5WTdU|}A>sTt!RX?>q;=P(Y50L%zzxrGM O-o<091E*O(GyMa-0RR92 literal 0 HcmV?d00001 diff --git a/graphics/loading_screen.aseprite b/graphics/loading_screen.aseprite index a6c2a590691e63e3c513936a85e2d06d71e31398..279674dc8bb9c15929fe891a9e83b6c43167c5cb 100644 GIT binary patch literal 4337 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 literal 4377 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 W!wb@1R-gX$!(q)#qmxhFEB_~sgwlBc diff --git a/graphics/loading_screen.png b/graphics/loading_screen.png index 2bb2d8ed3ec90eb2973254bbfd12c2a8776f5b30..1800be76cd27fa54094b655f230446771db016a7 100644 GIT binary patch literal 3392 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 literal 3430 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 XaZldQNj*Nx|Mbet$kLFa?-ub7PiA{$ diff --git a/linker-files/linker.scm b/linker-files/linker.scm deleted file mode 100644 index 1a74116..0000000 --- a/linker-files/linker.scm +++ /dev/null @@ -1,19 +0,0 @@ -(define memories - '((memory zeroPage (address (#x56 . #xff)) (type ram) ;;; Should be starting at 0x56, ending at 0xff, but experiments in the monitor have shown doc is not to be trusted if monitor routines are used - (section registers zpage zzpage)) - (memory firstPage (address (#x100 . #x1ff)) (section stack)) - (memory reserved (address (#x200 . #x7ff)) (type ram)) - (memory program (address (#x801 . #x1fff)) (type ram) - (section (programStart #x801) (dii_critical_wr_code #x804) dii_critical_rd_code startup code)) - (memory displayPage1 (address (#x2000 . #x3fff)) (type ram) (section loadscreen)) - (memory upperData (address (#x4000 . #x85ff)) (type ram) (section switch idata cdata data_init_table)) ;;; usermem goes from 0x4000 to 0x9FFFF (included), we are splitting it - (memory upperMem (address (#x8600 . #x95ff)) (type ram) (section cstack zdata data heap zpsave)) - (memory dosMem (address (#x9600 . #x9fff)) (type ram)) - (memory displayPage2 (address (#xa000 . #xbfff)) (type ram)) - (memory io (address (#xc000 . #xc0ff)) (type ram)) - (memory rombank (address (#xc100 . #xffff)) (type rom)) - - (block cstack (size #x800)) - (block stack (size #x100)) - (block heap (size #x400)) - )) \ No newline at end of file diff --git a/linker-files/master.scm b/linker-files/master.scm new file mode 100644 index 0000000..2097742 --- /dev/null +++ b/linker-files/master.scm @@ -0,0 +1,30 @@ +(define memories + '((memory zeroPage (address (#x56 . #xff)) (type ram) + (section registers zpage zzpage)) + (memory firstPage (address (#x100 . #x1ff)) (section stack)) + (memory reserved (address (#x200 . #x7ff)) (type ram)) + (memory program (address (#x800 . #x15ff)) (type ram) + (section (programStart #x800) (dii_critical_wr_code #x803) (dii_critical_rd_code #x90b) startup code switch idata cdata data_init_table)) +;;; (memory exportedTables (address (#x1c00 . #x1c1f)) (type ram) (section functionTable)) ;;; Exported function table can be found here + (memory dataMem (address (#x1600 . #x1fff)) (type ram) (section cstack zdata data heap zpsave)) + (memory displayPage1 (address (#x2000 . #x3fff)) (type ram)) + (memory upperMem (address (#x4000 . #x99ff)) (type ram)) + (memory sharedMem (address (#x9a00 . #x9bff)) (type ram)) ;;; This memory page will be used to pass parameters and data between the master and the modules, and to save the game state + (memory diskBuffer (address (#x9c00 . #x9fff)) (type ram)) ;;; This memory will be used by the disk II routines as buffer + (memory displayPage2 (address (#xa000 . #xbfff)) (type ram)) + (memory io (address (#xc000 . #xc0ff)) (type ram)) + (memory rombank (address (#xc100 . #xffff)) (type rom)) + + (block cstack (size #x600)) + (block heap (size #x000)) + (block stack (size #x100)) + )) + + + ;;; Floppy buffer structure + ;;; The decoding table for the reads should be generated ad 0x0356 + ;;; The "twos" buffer should be located at 0x9C00 + ;;; The encoding table should be located at 0x9C56 + ;;; The "sixes" buffer should be located at 0x9D00, and it should overlap with the buffer used for DOS catalog reads + ;;; We leave pages 9E and 9F alone, as resetting back with those modified leads to breakage + \ No newline at end of file diff --git a/linker-files/module.scm b/linker-files/module.scm new file mode 100644 index 0000000..81c846b --- /dev/null +++ b/linker-files/module.scm @@ -0,0 +1,19 @@ +(define memories + '((memory zeroPage (address (#x56 . #xff)) (type ram) + (section registers zpage zzpage)) + (memory firstPage (address (#x100 . #x1ff)) (section stack)) + (memory reserved (address (#x200 . #x1fff)) (type ram)) + (memory displayPage1 (address (#x2000 . #x3fff)) (type ram)) + (memory upperProg (address (#x4000 . #x93ff)) (type ram) (section (programStart #x4000) startup code switch idata cdata data_init_table)) + (memory upperData (address (#x9400 . #x99ff)) (type ram) (section cstack zdata data heap)) + (memory sharedMem (address (#x9a00 . #x9bff)) (type ram)) ;;; This memory page will be used to pass parameters and data between the master and the modules, and to save the game state + (memory diskBuffer (address (#x9c00 . #x9eff)) (type ram)) + (memory zeroPageBackup (address (#x9f00 . #x9fff)) (type ram) (section (zpsave #x9f00))) + (memory displayPage2 (address (#xa000 . #xbfff)) (type ram)) + (memory io (address (#xc000 . #xc0ff)) (type ram)) + (memory rombank (address (#xc100 . #xffff)) (type rom)) + + (block cstack (size #x400)) + (block heap (size #x020)) + (block stack (size #x100)) + )) \ No newline at end of file diff --git a/pics/title.png b/pics/title.png new file mode 100644 index 0000000000000000000000000000000000000000..1156bd5f766ab67217fc67a5eed6d22033a7e2a5 GIT binary patch literal 4706 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 F{vYr{?85*6 literal 0 HcmV?d00001 diff --git a/src/disk2.h b/src/disk2.h new file mode 100644 index 0000000..4238f06 --- /dev/null +++ b/src/disk2.h @@ -0,0 +1,46 @@ +#ifndef _DISK2_HEADER_ +#define _DISK2_HEADER_ + +#include + +#define DII_MAX_TRACK 96 +#define DECODING_MAPPING_TABLE_DEFAULT_ADDRESS 0x0356 +#define ENCODING_MAPPING_TABLE_DEFAULT_ADDRESS 0x9D56 +#define WRITE_SIXES_BUFFER_DEFAULT_ADDRESS 0x9C00 +#define WRITE_TWOS_BUFFER_DEFAULT_ADDRESS 0x9D00 + +void dii_generate_6bit_decoding_mapping_table(uint8_t* dest); +void dii_generate_6bit_encoding_mapping_table(uint8_t* dest); +void dii_encode_gcr62_data(uint8_t *src, uint8_t* dest6, uint8_t* dest2); +void dii_power_on(uint8_t io_offset, uint8_t drive_no); +void dii_power_off(uint8_t io_offset); +void dii_head_reposition(uint8_t io_offset, uint8_t cur_track, uint8_t dest_track); + +/** + * Patch the dii_write_sector routine to change the memory addresses used. + * buf2 and buf6 must start at the beginning of a memory page, nibtab must fit completely inside a page. + */ +void dii_patch_write(uint8_t* buf6, uint8_t* buf2, uint8_t *nibtab); + +/** + * This routine requires the decoding mapping table to be initialized beforehand at + * DECODING_MAPPING_TABLE_DEFAULT_ADDRESS, and uses space in page 3 as a buffer + * + * If the return value is 0, the read failed + */ +uint8_t dii_read_sector(uint8_t io_offset, uint8_t track, uint8_t sector, uint8_t *buffer, uint8_t aonly); + +/** + * Writes gcr 6+2 encoded data to the chosen sector. The track number is used to verify that we're on the correct + * track before writing. This routine does not move the head. + * The data must be pre-encoded. + * + * By default, it writes 86 bytes from the "twos" buffer located at 0x9C00, 256 bytes of "sixes" from the buffer + * at 0x9D00 and using a 63 entries translation table preloaded at 0x9C56. + * If these addresses need to be changes, use the dii_patch_write routine. + * + * If the return value is > 0, the write failed + */ +uint8_t dii_write_sector(uint8_t io_offset, uint8_t track, uint8_t sector); + +#endif /* _DISK2_HEADER_ */ diff --git a/src/disk2.s b/src/disk2.s new file mode 100644 index 0000000..f29f44a --- /dev/null +++ b/src/disk2.s @@ -0,0 +1,726 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern _Vfp + .extern _Vsp + .extern _Zp + +;;; I/O offsets +DII_BASE: .equ 0xC080 +PH0_OFF: .equ DII_BASE + 0x00 +PH0_ON: .equ DII_BASE + 0x01 +PH1_OFF: .equ DII_BASE + 0x02 +PH1_ON: .equ DII_BASE + 0x03 +PH2_OFF: .equ DII_BASE + 0x04 +PH2_ON: .equ DII_BASE + 0x05 +PH3_OFF: .equ DII_BASE + 0x06 +PH3_ON: .equ DII_BASE + 0x07 +PWR_DWN: .equ DII_BASE + 0x08 +PWR_UP: .equ DII_BASE + 0x09 +SEL_D1: .equ DII_BASE + 0x0A +SEL_D2: .equ DII_BASE + 0x0B +READ_SW: .equ DII_BASE + 0x0C +WRITE_SW: .equ DII_BASE + 0x0D +CLEAR_SW: .equ DII_BASE + 0x0E +SHIFT_SW: .equ DII_BASE + 0x0F + +;;; Monitor routines +MON_WAIT .equ 0xFCA8 + +;;; Memory addresses +CONV_TABLE .equ 0x0356 ; 6+2 conversion table generated by BOOT0 +PAGE3_BUF .equ 0x0300 + +;;; Summing up the calling convention as... +;;; Parameters are passed in register A and pseudo registers _Zp[0-7]. +;;; Registers X, Y and pseudo registers 0–23 are destroyed by a function call. +;;; Pseudo registers 24–47 must be preserved. + + .section code,text + +;;; dii_generate_6bit_decoding_mapping_table: +;;; Generates a 6+2 decoding table at the chosen address +;;; Parameters: +;;; - address [_Zp[0], _Zp[1]]: Address for the generation of the table +;;; +;;; Returns: Nothing +dii_generate_6bit_decoding_mapping_table: +T_REGX$: .equ _Zp+4 +T_REGY$: .equ _Zp+3 +T_BITS$: .equ _Zp+2 +P_DESTH$: .equ _Zp+1 +P_DESTL$: .equ _Zp+0 + + ldy #0x00 + ldx #0x03 + +CreateTableLoop$: + stx zp:T_BITS$ + txa + asl a ; Arithmetic Shift Left, will put the high bit in carry + bit zp:T_BITS$ ; Check of the overlap for the shifted value. If two one overlaps it means that there are two adjacent 1s + beq reject$ ; Nope, refuse this number + ora zp:T_BITS$ ; Merge shifted and non-shifted number + eor #0xff ; Invert the value, we'll look for adjacent zeros (which have now become 1s) + and #0x7e ; Discard msb and lsb, as one is mandatory and the other falsely generated with the shift +check_dub0$:bcs reject$ ; Branch on C = 1. Initial bhi bit set or adjacent 0 bits set + lsr a ; Shift right. lower bit into carry + bne check_dub0$ ; Check all the bits until we're left with a 0 + tya ; We have a good one. Store it in the table + sty zp:T_REGY$ ; Save y + stx zp:T_REGX$ ; Save x + ldy zp:T_REGX$ ; Y <- X + sta (zp:P_DESTL$),y ; Bytes that reference this will have highest bit set, so this'll be accessed using CONV_TABLE-128 + ldy zp:T_REGY$ ; Recover y + iny +reject$: inx ; Next candidate + bpl CreateTableLoop$ + + rts + +;;; dii_generate_6bit_encoding_mapping_table: +;;; Generates a 6bit mapping table to be used with GCR 6+2 +;;; Parameters: +;;; - address [_Zp[0], _Zp[1]]: Address for the generation of the table +;;; +;;; Returns: Nothing +dii_generate_6bit_encoding_mapping_table: +T_BITS$: .equ _Zp+2 +P_DESTH$: .equ _Zp+1 +P_DESTL$: .equ _Zp+0 + + ldy #0x00 + ldx #0x03 + +CreateTableLoop$: + stx zp:T_BITS$ + txa + asl a + bit zp:T_BITS$ + beq reject$ + ora zp:T_BITS$ + eor #0xff + and #0x7e +check_dub0$:bcs reject$ + lsr a + bne check_dub0$ + txa + ora #0x80 + sta (zp:P_DESTL$),y + iny +reject$: inx ; Next candidate + bpl CreateTableLoop$ + + rts + +;;; dii_encode_gcr62_data: +;;; Split 256 bytes of data into 256 + 86 6-bit nibbles +;;; Parameters: +;;; - source [_Zp[0], _Zp[1]]: Address for the 256 bytes data source +;;; - buf6 [_Zp[2], _Zp[3]]: Address for the destination buffer that will contain 256 "sixes" nibble entries +;;; - buf2 [_Zp[4], _Zp[5]]: Address for the destination buffer that will contain 86 "twos" nibble entries +;;; +;;; Returns: Nothing +;;; +;;; We will go through the 86 bytes three times (which means a total of 258 runs). Note that we have a source +;;; of 256 bytes, plus, the result will have to be 256+86, so, in the "twos" buffer we'll have one entry containing +;;; (86*3)-256 = 2 spurious (duplicated entries); +;;; +dii_encode_gcr62_data: +T_REGY$: .equ _Zp+7 +T_BITS$: .equ _Zp+6 +P_DEST2H$: .equ _Zp+5 +P_DEST2L$: .equ _Zp+4 +P_DEST6H$: .equ _Zp+3 +P_DEST6L$: .equ _Zp+2 +P_SRCH$: .equ _Zp+1 +P_SRCL$: .equ _Zp+0 + + ldx #0x00 ; This will address the current byte of the "twos" + ldy #0x02 ; We'll use this as an offset into the source (and sixes) buffer. Note that this means the first call + ; will have BASE+1, the second BASE+0, while the third BASE+255, and will decrease from there + ; onward. Here are the two spurious entries. +prenib1$: dey + sty zp:T_REGY$ ; Save y + txa ; Copy x to a + tay ; then a to y ---- x -> y + lda (P_DEST2L$),y ; Recover the current 'two' + sta zp:T_BITS$ ; Save it on a temp register + ldy zp:T_REGY$ ; Recover y + + lda (zp:P_SRCL$),y ; Get current byte from the source buffer + lsr a ; Put the two least significant bits into the corrisponding byte of the "twos" + rol zp:T_BITS$ + lsr a + rol zp:T_BITS$ ; Move two bits in + sta (zp:P_DEST6L$),y; The remaining six bites will go in the 'sixes' buffer + sty zp:T_REGY$ ; Save y + txa ; Move X to Y + tay + lda zp:T_BITS$ ; Recover the updated 'two' + sta (P_DEST2L$),y ; Save the updated 'two' + ldy zp:T_REGY$ ; Recover y + inx + cpx #86 + bcc prenib1$ ; We'll repeat this 86 times + ldx #0 + tya + bne prenib1$ ; After decreasing y 258 times, we'll end up with 0 here (2 - 86 - 86 - 86 = -256) and go on + + ldy #85 +prenib2$: lda (zp:P_DEST2L$),y + and #0x3f + sta (zp:P_DEST2L$),y + dey + bpl prenib2$ + + rts + +;;; dii_power_on: +;;; Selects and powers on a disk drive +;;; Parameters: +;;; - offset [A]: offset from the base address (DII_BASE) to access the Disk II interface +;;; - drive [_Zp[0]]: Drive number, 0 for first drive, 1 for the next. Only the first bit will be considered +;;; +;;; Returns: Nothing +dii_power_on: +P_OFFSET$: .equ _Zp+1 +P_DRVNO$: .equ _Zp+0 + + and #0x70 ; Mask the offset with 0x70, so we keep the bits relevant for "slot" selection + sta zp:P_OFFSET$ ; Save the cleaned offset, we'll use it later to calculate the offset to turn on the drive + + tax + lda CLEAR_SW,x ; Enter read mode + lda READ_SW,x + + lda #0x01 ; Keep only the lsb of the drive number parameter + and zp:P_DRVNO$ + ora zp:P_OFFSET$ ; Combine the result with the "slot" offset + tax + lda SEL_D1,x ; Select the proper drive + + lda zp:P_OFFSET$ + tax + lda PWR_UP,x ; Drive Power up + + jsr __short_wait ; Wait for the driver to power on and the motor to spin up + rts + +;;; dii_power_off: +;;; Selects and powers on a disk drive +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; +;;; Returns: Nothing +dii_power_off: + and #0x70 ; Mask the offset with 0x70, so we keep the bits relevant for "slot" selection + tax + lda PWR_DWN,x ; Drive Power down + + rts + +;;; dii_head_reposition: +;;; Repositions the head according to the current track and destination track. +;;; Keep in mind that the parameters to this function are taking "phase movements" into consideration, not the +;;; DOS track number. +;;; There is a correspondance of 1:2 between DOS tracks and phase movements, which means that TWO phase movements +;;; will shift ONE track. +;;; With standard DOS, all EVEN numbered tracks (0 included) are positioned under "phase 0" of the head, all ODD +;;; numbered tracks will be positioned under "phase 2". Moving to phase 1 or 3 will get us to half-tracks. +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; - curtrack [_Zp[0]]: Current track number +;;; - desttrack [_Zp[1]]: Desired destination track number +;;; +;;; Returns: Nothing +dii_head_reposition: +P_OFFSET$: .equ _Zp+2 +P_DTRACK$: .equ _Zp+1 +P_CTRACK$: .equ _Zp+0 + + and #0x70 + sta zp:P_OFFSET$ ; Save the slot offset into a zp register +H_MNLOOP$: lda zp:P_CTRACK$ ; Check and branch depending on relationship between current and destination track + cmp zp:P_DTRACK$ + beq H_ALLDONE$ + bcc H_MOVUP$ + bcs H_MOVDOWN$ + +H_MOVDOWN$: dec zp:P_CTRACK$ ; Prepare to move down a track + jmp H_DOWORK$ + +H_MOVUP$: inc zp:P_CTRACK$ ; Prepare to move up a track + +H_DOWORK$: lda zp:P_CTRACK$ + and #0x03 ; We're going to keep only the 3 least significant bits of the current track + asl a ; Shifting them left, now we're using the 4 msb of the byte + ; This multiplication is necessary because the phase on (or off) registers + ; are spaced by two between each other, and this calculates the offset. See later. + ora zp:P_OFFSET$ ; And now combine the slot number with the track value we calculated in the previous step... + tay ; a -> y + lda PH0_ON,y ; We are offsetting the base to reach for both the slot number and the proper phase + lda #0x56 + jsr MON_WAIT ; Call the monitor wait routine + lda PH0_OFF,y ; Now turn the phase off + jmp H_MNLOOP$ ; And go back checking the status of the relationship between current and destination tracks +H_ALLDONE$: rts ; We're done + + +__short_wait: + lda #0xEF + sta zp:_Zp ; We'll use the first two zp registers to hold a counter + lda #0xD8 + sta zp:_Zp+1 +MWAITA$: ldy #0x12 +MWAITB$: dey + bne MWAITB$ + inc zp:_Zp + bne MWAITA$ + inc zp:_Zp+1 + bne MWAITA$ + rts + + +;;; dii_patch_write: +;;; Modifies the addresses in the dii_write_sector routine to change where data is read +;;; Parameters: +;;; - buf6 [_Zp[0] and _Zp[1]]: Address for the "sixes" buffer. 256 bytes in length. Must start at the beginning of a page+ +;;; - buf2 [_Zp[2] and _Zp[3]]: Address for the "twos" buffer. 86 bytes in length. Must start at the beginning of a page +;;; - nibtab [_Zp[4] and _Zp[5]]: Address for the translation table for nibbles. 63 bytes in length. +;;; +;;; Returns: nothing +;;; +dii_patch_write: +P_NIBTH$: .equ _Zp+5 +P_NIBTL$: .equ _Zp+4 +P_BUF2H$: .equ _Zp+3 +P_BUF2L$: .equ _Zp+2 +P_BUF6H$: .equ _Zp+1 +P_BUF6L$: .equ _Zp+0 + + ;;; Common changes + lda #0x00 ; Make sure we start at the beginning of the page + sta _b2add01+1 + sta _b2add02+1 + sta _b6add01+1 + sta _b6add02+1 + + ;;; Change the address for buf2 + lda zp:P_BUF2H$ ; Overwrite the most significant byte + sta _b2add01+2 + sta _b2add02+2 + + dec zp:P_BUF2H$ ; Make sure we save this address for buf2 - 1, needed for page crossing + lda zp:P_BUF2H$ + sta _b2madd01+2 + + lda #0xff + sta _b2madd01+1 + + ;;; Change the address for buf6 + lda zp:P_BUF6H$ ; Overwrite the most significant byte + sta _b6add01+2 + sta _b6add02+2 + + ;;; Nibble table address + lda zp:P_NIBTL$ + sta _nibadd01+1 + sta _nibadd02+1 + sta _nibadd03+1 + + lda zp:P_NIBTH$ + sta _nibadd01+2 + sta _nibadd02+2 + sta _nibadd03+2 + + rts + +;;; Timing critical code here, must not cross page boundary + + .section dii_critical_rd_code,text,noreorder + +;;; dii_read_sector: +;;; Reads and decode a 256 byte sector from the Disk II interface. +;;; It is shamelessly copied and then slightly adaptet from the BOOT0 code in the Disk II interface PROM. +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; - track [_Zp[0]]: Search for this track +;;; - sector [_Zp[1]]: Search for the sector specified in the low nibble of this byte. If high nibble is != 0, do not read the data, and return after address header +;;; - destination [_Zp[2] and _Zp[3]]: Destination address for a 256b buffer for the data +;;; - destination [_Zp[4]]: If != 0, only the address data will be read +;;; +;;; Returns: Nothing +insta_return: + rts +dii_read_sector: +RETRIES$: .equ 0xff +T_HCHKS$: .equ _Zp+8 +T_BITS$: .equ _Zp+7 +T_RETRYC$: .equ _Zp+6 +P_OFFSET$: .equ _Zp+5 +P_ADDRONLY$:.equ _Zp+4 +P_DESTH$: .equ _Zp+3 +P_DESTL$: .equ _Zp+2 +P_SECTOR$: .equ _Zp+1 +P_TRACK$: .equ _Zp+0 + + ; Save the slot offset, properly cleaned, into a zp register + and #0x70 + sta zp:P_OFFSET$ + tax + + ; Load the retry counter + lda #RETRIES$ ; Set up the retry counter + sta zp:T_RETRYC$ + + ;;; Address header is the sequence (D5 AA 96). Data header is (D5 AA AD). +RdSect$: + dec zp:T_RETRYC$ + beq insta_return ; Out of retries + + clc ; Clear carry. C=0 will have the code look for an address, C=1 for data +RdSect_C$: php ; Push state on stack + +RdByte1$: lda READ_SW,x + bpl RdByte1$ ; Read the first byte +ChkD5$: eor #0xD5 ; An xor of 0xD5 with 0xD5 will get ourselves a 0, something else otherwise + bne RdByte1$ ; Not the first byte, keep searching + +RdByte2$: lda READ_SW,x + bpl RdByte2$ ; Read the second byte + cmp #0xAA + bne ChkD5$ ; Not the second byte, check if it's the first... + nop + +RdByte3$: lda READ_SW,x + bpl RdByte3$ + cmp #0x96 ; Check if third byte is 0x96 + beq FoundAddr$ ; We found an address! + + plp ; Pull the status byte + bcc RdSect$ ; We did not want to read data (C=0), so we start searching again + eor #0xAD ; We did want to read data! So check if it's the last byte of a data sequence + beq FoundData$ ; It is, read the data! (A = 0 at this point) + + ;;; ADDRESS: Data in the address header is stored in 4+4 GCR encoding +FoundAddr$: lda #0x00 ; Clear A + sta zp:T_HCHKS$ ; Save 0 to the temporary checksum in ZP + ldy #0x05 ; Dummy write (+5), Volume # (+4), Track # (+3), Sector # (+2), checksum (+1), in decreasing order. We'll ignore the epilogue bytes +hdr_loop$: sta (zp:P_DESTL$),y ; We'll use the destination buffer, temporarily... + eor zp:T_HCHKS$ ; Running and saving the checksum + sta zp:T_HCHKS$ + +RdByteA1$: lda READ_SW,x + bpl RdByteA1$ + rol a ; Rotate the bits left (the first byte is odd encoded 1 b7 1 b5 1 b3 1 b1). + sta zp:T_BITS$ +RdByteA2$: lda READ_SW,x + bpl RdByteA2$ ; Read the second byte (the second byte is even encoded 1 b6 1 b4 1 b2 1 b0) + and zp:T_BITS$ ; Merge with the first byte + dey + bne hdr_loop$ ; Are we at the last element? + +;RdByteA3$: lda READ_SW,x ; Ignore the epilogue bytes... +; bpl RdByteA3$ +;ChkDE$ eor #0xDE +; bne RdByteA3$ +; +;RdByteA4$: lda READ_SW,x +; bpl RdByteA4$ +; eor #0xAA +; bne ChkDE$ + + plp ; Pull the status byte + + lda zp:T_HCHKS$ ; Verify if checksum is bad. + bne RdSect$ ; If so, go back searching for an address... + + ldy #2 ; Point to the saved sector # in memory + lda (zp:P_DESTL$),y + cmp zp:P_SECTOR$ ; Check if this is the sector we want + bne RdSect$ ; If not, go back searching for an address... + iny ; Point to the saved track # in memory + lda (zp:P_DESTL$),y + cmp zp:P_TRACK$ ; Check if it is the track we want + bne RdSect$ ; If not, go back searching for an address... + + lda zp:P_ADDRONLY$ + bne insta_return + bcs RdSect_C$ ; Yes, go back reading the sector without clearing the Carry, which will be 1, so we'll read data at next run + + ;;; DATA. 6+2 encoded sector data + ;;; The values we'll read are encoded in a way that they'll have the range 0x96 - 0xff + ;;; A will be 0 on entry here + ;;; Read the 2s! +FoundData$: ldy #86 ; We'll read 86 bytes of data in 0x300-0x355 +Read2Loop$: sty zp:T_BITS$ ; Each byte of these will contain 3 sets of "2 bits" encoded inside +RdByteD1$: ldy READ_SW,x + bpl RdByteD1$ + eor CONV_TABLE-128,y; Note that we have 0x02d6 + 0x96 = 0x36c, our first table entry for the conversion. Also, note we're doing a rolling xor + ldy zp:T_BITS$ + dey + sta PAGE3_BUF,y ; Store the result of the 2s decoding in page 3 + bne Read2Loop$ ; Loop until we read them all + + ;;; Read the 6s! +Read6Loop$: sty zp:T_BITS$ ; y should be 255 at this point, which means we'll read 256 bytes +RdByteD2$: ldy READ_SW,x + bpl RdByteD2$ + eor CONV_TABLE-128,y; Going on with the rolling xor + ldy zp:T_BITS$ + sta (zp:P_DESTL$),y ; Store the result on the destination buffer + iny + bne Read6Loop$ ; Keep reading and converting 256 6s + + ;;; Verify the checksum! +RdByteD3$: ldy READ_SW,x + bpl RdByteD3$ + eor CONV_TABLE-128,y; Finally check if the checksum matches... + bne EndRdSect$ ; No, probably something got corrupted. Retry the read... + + ;;; Decode 6+2 data. The high 6 bits of every byte are already in place. We need to get the remaining 2 in + ldy #0x00 +init_x$: ldx #86 ; We'll go through the 2-bit pieces three times +decod_loop$:dex ; Decrement x + bmi init_x$ ; Branch on N = 1 (negative set), make sure we roll x. 0x02ff -> 0x0355 as it is an offset + lda (zp:P_DESTL$),y ; For each byte in the data buffer + lsr PAGE3_BUF,x ; grab the low two bits from the 2s at 0x0300 - 0x0355 (we'll shift them in the carry) + rol a ; and roll them in the least significant bits of the byte (and roll the carry in!) + lsr PAGE3_BUF,x + rol a + sta (zp:P_DESTL$),y ; Store the decoded byte back + iny + bne decod_loop$ + + ;;; Done! + lda zp:T_RETRYC$ ; Recover the number of retries + + rts + ;;; We're too far off the beginning, we cannot do a quick branch with an offset +EndRdSect$: + jmp RdSect$ + +;;; Timing critical code here, must not cross page boundary + + .section dii_critical_wr_code,text,noreorder + + +;;; dii_write_sector: +;;; Writes a 342 byte from the pointed GCR 6+2 data buffers. +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; - track [_Zp[0]]: Check that we're writing on this track +;;; - sector [_Zp[1]]: Write on this sector +;;; +;;; Returns: 0 for a completed write, 0x10 for a write protected disk +;;; +;;; MAKE SURE THE FOLLOWING ALL FITS INSIDE A SINGLE PAGE!!! +;;; Note that the addresses used in this function will modified by calling dii_patch_write +;;; +wr_err_return: + lda READ_SW,x ; Back into read mode + lda #0x10 ; This will be the error code + rts +dii_write_sector: +RETRIES$: .equ 0xff + ; Note that the following buffers MUST be at the start of a page for write timing to be respected +__BUF2S: .equ 0x9D00 ; Default to this, it'll be overwritten when modifying this code. 86 entries. +__BUF6S: .equ 0x9C00 ; 256 entries. +__NIBTAB: .equ 0x9D56 ; This will contain the translation table for writing nibbles. 63 entries. +__OFFSETP6 .equ 0x0678 + +__T_VOL: .equ _Zp+9 +__T_TRK: .equ _Zp+8 +__T_SEC: .equ _Zp+7 +__T_CHK: .equ _Zp+6 ; 7, 8, 9 also taken +__T_HCHKS: .equ _Zp+5 +__T_HOLDNIB:.equ _Zp+4 +__T_RETRYC: .equ _Zp+3 +__P_OFFSET: .equ _Zp+2 +__P_SECTOR: .equ _Zp+1 +__P_TRACK: .equ _Zp+0 + ; Save the slot offset, properly cleaned, into a zp register and on page 6 + and #0x70 + sta zp:__P_OFFSET + sta __OFFSETP6 + tax + + lda #RETRIES$ ; Set up the retry counter + sta zp:__T_RETRYC + + ;;; Search for the address prologue (D5 AA 96) +RdSect$: + dec zp:__T_RETRYC + beq wr_err_return ; Out of retries + + ;;; Read the address prologue +RdByte1$: lda READ_SW,x + bpl RdByte1$ ; Read the first byte +ChkD5$: cmp #0xD5 + bne RdByte1$ ; Not the first byte, keep searching + nop + +RdByte2$: lda READ_SW,x + bpl RdByte2$ + cmp #0xaa + bne ChkD5$ + ldy #0x03 + +RdByte3$: lda READ_SW,x + bpl RdByte3$ + cmp #0x96 + bne ChkD5$ + + ;;; Read the address fields and store them in ZP + lda #0x00 +RdAddrFld$: sta zp:__T_HCHKS +RdByte4$: lda READ_SW,x + bpl RdByte4$ + rol a + sta zp:__T_HOLDNIB +RdByte5$: lda READ_SW,x + bpl RdByte5$ + and zp:__T_HOLDNIB + sta __T_CHK,y + eor zp:__T_HCHKS + dey + bpl RdAddrFld$ + + tay + bne RdSect$ ; Checksum error, retry + + ;;; Read the bit-slip nibbles +RdByte6$: lda READ_SW,x + bpl RdByte6$ + cmp #0xde + bne RdSect$ + nop + +RdByte7$: lda READ_SW,x + bpl RdByte7$ + cmp #0xaa + bne RdSect$ + nop + + ;;; Check we're on the correct track + lda __T_TRK + cmp zp:__P_TRACK + bne wr_err_return + + ;;; Check we're on the correct sector, or retry the read + lda __T_SEC + cmp zp:__P_SECTOR + bne RdSect$ + + lda WRITE_SW,x ; Combination of these two in sequence checks for write protect + lda CLEAR_SW,x ; and enables the write sequence. + bmi wr_err_return ; Write protected disk. Branch on N = 1 +_b2add01: + lda __BUF2S ; Get the first 2-bit encode byte + sta zp:__T_HOLDNIB ; and save it for future use. + + ;;; Write a 5-sync gap between the address epilogue and the data prologue + ;;; Write of the sync bytes will take 36 cycles + lda #0xff ; Sync byte + sta SHIFT_SW,x ; Write 1 sync byte + ora READ_SW,x + pha ; (3 cycles) + pla ; (4 cycles) + nop ; (2 cycles) + ldy #4 ; (2 cycles), plus load a counter to write 4 more syncs +__wr4ff: pha ; (3 cycles) + pla ; (4 cycles) + jsr __write2 ; (12 cycles before, 6 after) + dey ; (2 cycles) + bne __wr4ff ; (2 or 3 cycles) + + ;;; Write the data prologue (D5 AA AD) + lda #0xd5 ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xaa ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xad ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + + ;;; Convert & write contents of the buffers to the disk! + ;;; Start with the 2-bits + ;;; + ;;; At this point, we're 10 cycles after the previous write + ;;; Writes will take 32 cycles + tya + ldy #0x56 ; We'll use this to count back from the beginning of the "twos" buffer + bne __doeor ; This will ALWAYS be taken +_b2add02: +__getnibl: lda __BUF2S,y +_b2madd01: +__doeor: eor __BUF2S - 1,y ; Note that this will always cross a page boundary, and thus be 5 cycles!! + tax ; Get index for translation table +_nibadd01: + lda __NIBTAB,x ; Get byte from translation table + ldx zp:__P_OFFSET + sta WRITE_SW,x + lda READ_SW,x + dey + bne __getnibl + + ;;; Proceed with the 6-bits + ;;; y will be 0 at this point + lda zp:__T_HOLDNIB + nop +_b6add01: +__scndeor: eor __BUF6S,y + tax ; Get index for translation table +_nibadd02: + lda __NIBTAB,x ; Get byte from translation table + ldx __OFFSETP6 ; Retrieve the slot offset from page 6 + sta WRITE_SW,x ; Write "sixes", byte 87 and onward + lda READ_SW,x +_b6add02: + lda __BUF6S,y ; Read the next byte + iny ; 00 to ff + bne __scndeor + + ;;; Convert and write the checksum byte + tax ; Get the index on the table +_nibadd03: + lda __NIBTAB,x + ldx zp:__P_OFFSET + jsr __write3 ; Write the checksum + + ;;; Write the epilogue (DE AA EB) + lda #0xde ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xaa ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xeb ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + + ;;; Write a sync byte + lda #0xff + jsr __write1 ; (14 cycles before, 6 after) + lda CLEAR_SW,x + lda READ_SW,x ; Back into read mode + lda #0x00 + rts + +__write1: + clc ; (2 cycles) +__write2: + pha ; (3 cycles) + pla ; (4 cycles) +__write3: + sta WRITE_SW,x ; (5 cycles) Load latch from data bus + ora READ_SW,x ; (4 cycles) and write byte. Must have previously selected SHIFT_SW + rts ; (6 cycles) + +;;; Declaration of public symbols + .public dii_generate_6bit_decoding_mapping_table + .public dii_generate_6bit_encoding_mapping_table + .public dii_encode_gcr62_data + .public dii_power_on + .public dii_power_off + .public dii_head_reposition + .public dii_patch_write + .public dii_read_sector + .public dii_write_sector diff --git a/src/dlog_data.h b/src/dlog_data.h new file mode 100644 index 0000000..7eaebb9 --- /dev/null +++ b/src/dlog_data.h @@ -0,0 +1,16 @@ +#ifndef _DLOG_DATA_HEADER_ +#define _DLOG_DATA_HEADER_ + +#include + +#define DLOG_MODE_START 0 +#define DLOG_MODE_WIN 1 +#define DLOG_MODE_LOSE 2 + + +typedef struct { + uint8_t mode; + uint16_t score; +} dlog_data; + +#endif /* _DLOG_DATA_HEADER_ */ diff --git a/src/dlog_main.c b/src/dlog_main.c new file mode 100644 index 0000000..1cbeaf0 --- /dev/null +++ b/src/dlog_main.c @@ -0,0 +1,64 @@ +#include +#include + +#include + +#include "game_graphics.h" +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "shared_page.h" +#include "state_page.h" +#include "dlog_data.h" +#include "game_data.h" +#include "input.h" +#include "sound.h" + +// External initialization requirements +#pragma require __preserve_zp +#pragma require __data_initialization_needed + +static state_page_data* state_page = (state_page_data*)STATE_PAGE; +static shared_page_data *shared_page = (shared_page_data*)SHARED_PAGE; + +void main(void) { + dlog_data *dld = (dlog_data *)(shared_page->module_data); + game_data *gad = (game_data *)(shared_page->module_data); + + // Make sure the buffers are pointing to the correct memory + initialize_display_buffers(); + + switch(dld->mode) { + case DLOG_MODE_WIN: + snd_festive(); + ddraw_endgame_box(1, dld->score, state_page->hi_score); + break; + case DLOG_MODE_LOSE: + snd_sad_scale(); + ddraw_endgame_box(-1, dld->score, state_page->hi_score); + break; + default: + case DLOG_MODE_START: + ddraw_endgame_box(0, dld->score, state_page->hi_score); + snd_start(); + break; + }; + + if(dld->score > state_page->hi_score) { // New high-score. We need to save it. + state_page->hi_score = dld->score; + shared_page->master_command = MASTER_COMMAND_SAVE; + } else { + shared_page->master_command = MASTER_COMMAND_NONE; + } + + shared_page->next_module_idx = 4; // Go to the GAME module + gad->mode = GAME_MODE_NORMAL; // Set the proper start mode for the game + + while(!read_any_key()) { + lfsr_update(); + } + + snd_mod_button(); + + return; +} diff --git a/src/dos_floppy.c b/src/dos_floppy.c new file mode 100644 index 0000000..3865551 --- /dev/null +++ b/src/dos_floppy.c @@ -0,0 +1,185 @@ +#include "dos_floppy.h" + +#include + +#include "disk2.h" + +#define DEFAULT_READ_RETRIES 2 + +static uint8_t * const bufferMem = (uint8_t*)0x9E00; + +static uint8_t dos2phys_sector_map[] = { 0, 13, 11, 9, 7, 5, 3, 1, 14, 12, 10, 8, 6, 4, 2, 15 }; +// Translation table from https://github.com/fadden/CiderPress2/blob/main/DiskArc/GeneralChunkAccess.cs + +static void __recalibrate_head(uint8_t io_offset, uint8_t dest_track); +static uint8_t __read_sector(uint8_t io_offset, uint8_t track, uint8_t sector, uint8_t *dest, uint8_t retries); + +static void __recalibrate_head(uint8_t io_offset, uint8_t dest_track) { + dii_head_reposition(io_offset, DII_MAX_TRACK, 0); + dii_head_reposition(io_offset, 0, TRK_DOS2RAW(dest_track)); +} + +static uint8_t __read_sector(uint8_t io_offset, uint8_t track, uint8_t sector, uint8_t *dest, uint8_t retries) { + // We assume to be at the correct track already + + do { + if(dii_read_sector(io_offset, track, dos2phys_sector_map[sector], dest, 0)) // Make sure we translate the dos sector to a physical sector + return 1; // Ok if we can, return with a good read! + __recalibrate_head(io_offset, track); // Nope, recalibrate and try again + } while(retries--); + + // Fail + return 0; +} + +FDE* df_search_file(uint8_t io_offset, const uint8_t *f_name, uint8_t *cur_trk) { + // Move the head to track 17 + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(DOS_VTOC_TRACK)); + *cur_trk = TRK_DOS2RAW(DOS_VTOC_TRACK); + + // Read the VTOC sector at the beginning of the DOS buffer + if(!__read_sector(io_offset, DOS_VTOC_TRACK, DOS_VTOC_SECTOR, bufferMem, DEFAULT_READ_RETRIES)) + return NULL; + + // At this point we have the VTOC + VTOC *vtoc = (VTOC*)bufferMem; + + // Move to the catalog track + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(vtoc->catalog_trk)); + *cur_trk = TRK_DOS2RAW(vtoc->catalog_trk); + + // Read the first catalog sector + if(!__read_sector(io_offset, vtoc->catalog_trk, vtoc->catalog_sec, bufferMem, DEFAULT_READ_RETRIES)) + return NULL; + + CAT_Sector *cats = (CAT_Sector*)bufferMem; + + do { + for (uint8_t fde_idx = 0; fde_idx < FDE_PER_CAT; fde_idx++) { + uint8_t found = 0; + if( cats->f_descriptor[fde_idx].tsl_trk == 0 || + cats->f_descriptor[fde_idx].tsl_trk == 0xFF) continue; // File deleted or entry unused + + uint8_t *fde_f_name = cats->f_descriptor[fde_idx].f_name; + + found = 1; + for (uint8_t fname_idx = 0; fname_idx < FNAME_SIZE; fname_idx++) { + if(!f_name[fname_idx]) break; // We found a 0 byte: it indicates that the filename string ends here, + // if we did not get a mismatch, it means we found the correct file. + + if(f_name[fname_idx] != fde_f_name[fname_idx]) { + found = 0; // Nah, filename differs + break; + } + } + + if (found) // Ok, we found the entry! + return &(cats->f_descriptor[fde_idx]); + } + + if (!cats->catalog_trk) break; // This was the last sector, we failed. + + // Move to the track for the next catalog sector + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(cats->catalog_trk)); + *cur_trk = TRK_DOS2RAW(cats->catalog_trk); + + // Read the next catalog sector + if(!__read_sector(io_offset, cats->catalog_trk, cats->catalog_sec, bufferMem, DEFAULT_READ_RETRIES)) + return NULL; + } while(1); + + return NULL; +} + +TSList_Sector* df_get_tslist(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *cur_trk) { + // Move the head to track indicated for the first entry in the TSL + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(tsl_trk)); + *cur_trk = TRK_DOS2RAW(tsl_trk); + + // Read the tsl sector + if(!__read_sector(io_offset, tsl_trk, tsl_sec, bufferMem, DEFAULT_READ_RETRIES)) + return NULL; + + return (TSList_Sector*)bufferMem; +} + +uint8_t df_read_file(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *dest, uint8_t *cur_trk) { + uint8_t read_sectors = 0; + + TSList_Sector* tsl = df_get_tslist(io_offset, tsl_trk, tsl_sec, cur_trk); + if(!tsl) return 0; + + do { + for(uint8_t ts = 0; ts < TSLIST_SIZE; ts++) { + if(!(tsl->ts_list[ts].trk)) break; // Found an empty pair, break out of this loop (an the next check will probably find this is going to be the last sector of the T/S list too) + + // Move to the track + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(tsl->ts_list[ts].trk)); + *cur_trk = TRK_DOS2RAW(tsl->ts_list[ts].trk); + + // Read the sector into destination + if(!__read_sector(io_offset, tsl->ts_list[ts].trk, tsl->ts_list[ts].sec, dest, DEFAULT_READ_RETRIES)) + return 0; // Failed read + + dest += 0x100; // Next page + read_sectors++; + } + + if(!(tsl->tsl_trk)) break; // Last sector + // Otherwise, read the next + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(tsl->tsl_trk)); + *cur_trk = TRK_DOS2RAW(tsl->tsl_trk); + + // Read the tsl sector + if(!__read_sector(io_offset, tsl->tsl_trk, tsl->tsl_sec, bufferMem, DEFAULT_READ_RETRIES)) // The read has failed + return 0; + } while(1); + + return read_sectors; +} + +#include "mem_map.h" + +uint8_t df_overwrite_file(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *src, uint8_t *cur_trk) { + uint8_t written_sectors = 0; + + TSList_Sector* tsl = df_get_tslist(io_offset, tsl_trk, tsl_sec, cur_trk); + if(!tsl) return 0; + + do { + for(uint8_t ts = 0; ts < TSLIST_SIZE; ts++) { + if(!(tsl->ts_list[ts].trk)) break; // Found an empty pair, break out of this loop (an the next check will probably find this is going to be the last sector of the T/S list too) + + // Encode the data + dii_encode_gcr62_data(src, (uint8_t*)WRITE_SIXES_BUFFER_DEFAULT_ADDRESS, (uint8_t*)WRITE_TWOS_BUFFER_DEFAULT_ADDRESS); + + // Move to the track + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(tsl->ts_list[ts].trk)); + *cur_trk = TRK_DOS2RAW(tsl->ts_list[ts].trk); + + // Write the sector into destination + if(dii_write_sector(io_offset, tsl->ts_list[ts].trk, dos2phys_sector_map[tsl->ts_list[ts].sec])) { + return 0; // Failed write + } + + src += 0x100; // Next page + written_sectors++; + } + + if(!(tsl->tsl_trk)) break; // Last sector + + // Otherwise, write the next + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(tsl->tsl_trk)); + *cur_trk = TRK_DOS2RAW(tsl->tsl_trk); + + // Read the tsl sector + if(!__read_sector(io_offset, tsl->tsl_trk, tsl->tsl_sec, bufferMem, DEFAULT_READ_RETRIES)) // The read has failed + return 0; + } while(1); + + return written_sectors; +} + +uint8_t df_read_file_desc(uint8_t io_offset, const FDE *f_descriptor, uint8_t *dest, uint8_t *cur_trk) { + return df_read_file(io_offset, f_descriptor->tsl_trk, f_descriptor->tsl_sec, dest, cur_trk); +} diff --git a/src/dos_floppy.h b/src/dos_floppy.h new file mode 100644 index 0000000..5c6fcbc --- /dev/null +++ b/src/dos_floppy.h @@ -0,0 +1,81 @@ +#ifndef _DOS_FLOPPY_HEADER_ +#define _DOS_FLOPPY_HEADER_ + +#include + +#define DOS_VTOC_TRACK 17 +#define DOS_VTOC_SECTOR 0 + +#define FNAME_SIZE 30 +#define FDE_PER_CAT 7 +#define TSLIST_SIZE 122 + +#define TRK_RAW2DOS(x) ((x) >> 1) +#define TRK_DOS2RAW(x) ((x) << 1) + +/* + * This header includes structures used to parse data from DOS 3.3 - style floppies + */ + +// The following struct maps what is found on Track 17, Sector 00 of a DOS 3.3 style floppy +typedef struct { + uint8_t _unused_00; // 00 + uint8_t catalog_trk; // 01, the following track/sector points to the first of the CAT_Sector(s), containing a list of the file entries on the disk + uint8_t catalog_sec; // 02 + uint8_t dos_version; // 03 + uint8_t _unused_01[2]; // 04-05 + uint8_t volume; // 06 + uint8_t _unused_02[32]; // 07-26 + uint8_t max_trksec; // 27 + uint8_t _unused_03[8]; // 28-2F + uint8_t last_trk; // 30 + uint8_t alloc_dir; // 31 + uint8_t _unused_04[2]; // 32-33 + uint8_t tot_trks; // 34 + uint8_t sec_per_trk; // 35 + uint16_t sec_size; // 36 - 37 + uint32_t sec_bitmap[50];// 38 - FF, first byte contains sectors F - 8, second 7 - 0, third and fourth are unused +} VTOC; + +typedef struct { // 35 bytes each + uint8_t tsl_trk; // 00, pointer to the first "Track/Sector List" sector (TSList_Sector). If this is FF, + // it means the file was deleted (and the original track is moved to the last byte of + // the f_name field), if 00, it means the entry is unused (which means that track 00 + // can never be used for files). + uint8_t tsl_sec; // 01 + uint8_t f_type; // 02, File type and flags. If the most significant bit (0x80) is set, the file is locked + uint8_t f_name[FNAME_SIZE]; // 03-20, // A0 (nbsp) will be used as padding + uint16_t f_len; // 21-22, length of file in sectors. Usually, only the first byte is used +} FDE; + +// The following struct contains a list of file descriptors, FDEs +typedef struct { + uint8_t _unused_00; // 00 + uint8_t catalog_trk; // 01, the following track/sector points to the next catalog sector. These will be 00/00 if this is the last in the list + uint8_t catalog_sec; // 02 + uint8_t _unused_01[8]; // 03-0A + FDE f_descriptor[FDE_PER_CAT]; // 0B-FF +} CAT_Sector; + +typedef struct { + uint8_t trk; + uint8_t sec; +} TS_Entry; + +typedef struct { + uint8_t _unused_00; // 00 + uint8_t tsl_trk; // 01, points to the next TSList_Sector if needed. 0 otherwise + uint8_t tsl_sec; // 02 + uint8_t _unused_01[2]; // 03-04 + uint16_t sec_offset; // 05-06, offset (in sectors) in the file of the first sector pointed by this entry + uint8_t _unused_02[5]; // 07-0B + TS_Entry ts_list[TSLIST_SIZE]; // 0C-FF, Sectors representing the file +} TSList_Sector; + +FDE* df_search_file(uint8_t io_offset, const uint8_t * f_name, uint8_t *cur_trk); +TSList_Sector* df_get_tslist(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *cur_trk); +uint8_t df_read_file(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *dest, uint8_t *cur_trk); +uint8_t df_read_file_desc(uint8_t io_offset, const FDE *f_descriptor, uint8_t *dest, uint8_t *cur_trk); +uint8_t df_overwrite_file(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *src, uint8_t *cur_trk); + +#endif /* _DOS_FLOPPY_HEADER_ */ diff --git a/src/game_data.h b/src/game_data.h new file mode 100644 index 0000000..63f2e48 --- /dev/null +++ b/src/game_data.h @@ -0,0 +1,14 @@ +#ifndef _GAME_DATA_HEADER_ +#define _GAME_DATA_HEADER_ + +#include + +#define GAME_MODE_NORMAL 0 +#define GAME_MODE_LOAD 1 + + +typedef struct { + uint8_t mode; +} game_data; + +#endif /* _GAME_DATA_HEADER_ */ diff --git a/src/game_graphics.c b/src/game_graphics.c index d06e014..9aa1818 100644 --- a/src/game_graphics.c +++ b/src/game_graphics.c @@ -78,13 +78,13 @@ static const uint8_t box_content_start[BOX_CONTENT_SIZE] = { 0, 0, 0, 0, 0, 0, 0,203, 77, 0, 0, 77, 0, 0, 77,192, 77, 0, 77, 77,211, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 77,211, 0, 0, 77, 0, 0, 77, 0, 77, 0, 77,198, 84, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 20, 11, 50, 48, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 50, 46, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 8, 9, 7, 8, 45, 19, 3, 15, 18, 5, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; @@ -99,14 +99,14 @@ static const uint8_t box_new_hi_score[BOX_WIDTH] = { #define INSTR_BOX_X 28 #define INSTR_BOX_Y 127 static const uint8_t instruction_box[INSTR_BOX_SIZE] = { - 3, 21, 18, 19, 15, 18, 0, 0, 11, 5, 25, 19, - 19, 12, 9, 4, 5, 0, 0, 20, 9, 12, 5, 19, + 3, 20, 18, 12, 45, 18, 0, 18, 5, 19, 5, 20, + 3, 20, 18, 12, 45, 19, 0, 19, 1, 22, 5, 0, + 3, 20, 18, 12, 45, 12, 0, 12, 15, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 5, 17, 21, 1, 12, 19, 0, 13, 5, 18, 7, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 5, 1, 3, 8, 0, 0, 50, 48, 52, 56, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 79, 0, 50, 48, 50, 53, 0, 49, 46, 48, 0, 79 + 79, 0, 50, 48, 50, 53, 0, 50, 46, 48, 0, 79 }; @@ -261,6 +261,8 @@ void ddraw_endgame_box(int8_t done, uint16_t score, uint16_t hi_score) { if(score > hi_score) { draw_graph_char_box(ENDGAME_BOX_X_OFFSET + 2, (ENDGAME_BOX_Y_OFFSET + (2 * CHAR_HEIGHT) + (13 * CHAR_HEIGHT)), BOX_WIDTH, 1, box_new_hi_score, front_buf); } + } else { // Print the loaded high score + direct_draw_number(hi_score, ENDGAME_BOX_SCORE_LEN, ENDGAME_BOX_X_OFFSET + 22, ENDGAME_BOX_Y_OFFSET + (16 * CHAR_HEIGHT), front_buf); } } @@ -301,11 +303,14 @@ void swap_display_buffers(void) { PEEK(((uint16_t)front_buf == DISPLAY_PAGE_1) ? IO_DISPLAY_PAGE1 : IO_DISPLAY_PAGE2); } -void clear_display_buffers(void) { - // Clear the buffers - memset((void*)DISPLAY_PAGE_1, 0, DISPLAY_PAGE_SIZE); - memset((void*)DISPLAY_PAGE_2, 0, DISPLAY_PAGE_SIZE); +void sync_display1_buffer(void) { + if(((uint16_t)front_buf == DISPLAY_PAGE_1)) return; // We're already displaying buffer 1 + // We need to copy from the secondary display to the primary + memcpy(back_buf, front_buf, DISPLAY_PAGE_SIZE); +} + +void initialize_display_buffers(void) { PEEK(IO_DISPLAY_PAGE1); // Select the first display page // Restore the buffer ordering @@ -313,6 +318,14 @@ void clear_display_buffers(void) { back_buf = (uint8_t*)DISPLAY_PAGE_2; } +void clear_display_buffers(void) { + // Clear the buffers + memset((void*)DISPLAY_PAGE_1, 0, DISPLAY_PAGE_SIZE); + memset((void*)DISPLAY_PAGE_2, 0, DISPLAY_PAGE_SIZE); + + initialize_display_buffers(); +} + void draw_field_borders_on_buffer(uint8_t brd, uint8_t* buf) { // Horizontal borders for(uint8_t col = 0; col < (GRID_SIDE * (GRID_CELL_SIDE/7)) + 1; col++) { diff --git a/src/game_graphics.h b/src/game_graphics.h index 126ac18..72fcf99 100644 --- a/src/game_graphics.h +++ b/src/game_graphics.h @@ -18,6 +18,7 @@ #define GRAPH_ARROW_LEFT 2 #define GRAPH_ARROW_RIGHT 3 +void initialize_display_buffers(void); void ddraw_field_borders_on_buffer(uint8_t brd); void draw_game_background(uint16_t hi_score); void draw_number(uint16_t n, uint8_t len, uint8_t x, uint8_t y); @@ -28,5 +29,6 @@ void clear_display_buffers(void); void clear_box(uint8_t w, uint8_t h, uint8_t off_x, uint8_t off_y, uint8_t *disp_buf); void ddraw_direction_arrows(uint8_t dir); void ddraw_endgame_box(int8_t done, uint16_t score, uint16_t hi_score); +void sync_display1_buffer(void); #endif /* _GAME_GRAPHICS_HEADER_ */ diff --git a/src/game_main.c b/src/game_main.c new file mode 100644 index 0000000..1043aff --- /dev/null +++ b/src/game_main.c @@ -0,0 +1,165 @@ +#include +#include + +#include + +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "shared_page.h" +#include "state_page.h" +#include "dlog_data.h" +#include "game_data.h" +#include "input.h" +#include "game_logic.h" +#include "game_graphics.h" +#include "monitor_subroutines.h" +#include "sound.h" + +// External initialization requirements +#pragma require __preserve_zp +#pragma require __data_initialization_needed + +#define MOVES_TEXT_X 32 +#define MOVES_TEXT_Y 61 +#define MOVES_TEXT_WIDTH 5 + +#define SCORE_TEXT_X 32 +#define SCORE_TEXT_Y 29 +#define SCORE_TEXT_WIDTH 5 + +#define HIGH_TEXT_X 32 +#define HIGH_TEXT_Y 107 + +#define WIN_SCORE_BONUS 10000 + +static state_page_data* state_page = (state_page_data*)STATE_PAGE; +static shared_page_data *shared_page = (shared_page_data*)SHARED_PAGE; + +void main(void) { + uint16_t moves_count = 0; + uint16_t score = 0; + int8_t done = 0; + + // By default, once we return from this, return to the DLOG module and give the master no command to execute + shared_page->master_command = MASTER_COMMAND_NONE; + shared_page->next_module_idx = 3; // Go to the DLOG module + + dlog_data *dld = (dlog_data *)(shared_page->module_data); + dlog_data *gad = (dlog_data *)(shared_page->module_data); + + // Make sure the buffers are pointing to the correct memory and are clear + clear_display_buffers(); + + // Reset the game, calculate the initial score depending on which tiles we randomly get + score = reset_game(); + + // Load the game + if(gad->mode == GAME_MODE_LOAD) { + gad->mode = GAME_MODE_NORMAL; + + memcpy(get_front_grid(), (void*)(state_page->save_grid), GRID_SIDE * GRID_SIDE); + moves_count = state_page->saved_moves_count; + score = calculate_score(); + + // We loaded an empty save, just restart the game + if (!score) score = reset_game(); + } + + // Draw the initial state of the game + draw_game_background(state_page->hi_score); + draw_number(moves_count, MOVES_TEXT_WIDTH, MOVES_TEXT_X, MOVES_TEXT_Y); + draw_number(score, SCORE_TEXT_WIDTH, SCORE_TEXT_X, SCORE_TEXT_Y); + draw_tiles(); + + // Swap graphical buffers + swap_display_buffers(); + + while(1) { // Game loop + lfsr_update(); + + switch(read_kb()) { + case K_UP: + SND_TAP(); + done = step_game(GAME_STEP_UP); + ddraw_direction_arrows(GRAPH_ARROW_UP); + break; + case K_DOWN: + SND_TAP(); + done = step_game(GAME_STEP_DOWN); + ddraw_direction_arrows(GRAPH_ARROW_DOWN); + break; + case K_LEFT: + SND_TAP(); + done = step_game(GAME_STEP_LEFT); + ddraw_direction_arrows(GRAPH_ARROW_LEFT); + break; + case K_RIGHT: + SND_TAP(); + done = step_game(GAME_STEP_RIGHT); + ddraw_direction_arrows(GRAPH_ARROW_RIGHT); + break; + case K_CTRL_R: + snd_mod_button(); + score = 0; // We'll reset the score + done = -1; + break; + case K_CTRL_S: // The following two will return early + snd_mod_button(); + memcpy((void*)(state_page->save_grid), get_front_grid(), GRID_SIDE * GRID_SIDE); + state_page->saved_moves_count = moves_count; + shared_page->master_command = MASTER_COMMAND_SAVE; + case K_CTRL_L: + snd_mod_button(); + sync_display1_buffer(); + shared_page->next_module_idx = 4; + gad->mode = GAME_MODE_LOAD; + return; + default: + continue; // Do nothing, loop again + } + + // Increase the count of moves we made (unless we lost or reset the game) + if(done >= 0) moves_count++; + + // Draw the number of moves + draw_number(moves_count, MOVES_TEXT_WIDTH, MOVES_TEXT_X, MOVES_TEXT_Y); + + // Draw the moved tiles + draw_tiles(); + + // If we have won, or we got a reset request, break out of this loop + if(done) { + score += (done > 0) ? WIN_SCORE_BONUS : 0; + draw_number(score, SCORE_TEXT_WIDTH, SCORE_TEXT_X, SCORE_TEXT_Y); + swap_display_buffers(); // Make sure we show the latest changes + break; + } + + // Unable to add a tile: we ran out of space and lost!!! + uint8_t random_tile_off = add_random_tile(); + if(!random_tile_off) { + done = -1; // Lost the game + break; + } + + score = calculate_score(); + + // Draw the score + draw_number(score, SCORE_TEXT_WIDTH, SCORE_TEXT_X, SCORE_TEXT_Y); + + swap_display_buffers(); + + // Draw the new tile directly on the front buffer, this way we make it appear with an "animation" + ddraw_single_tile(random_tile_off - 1); + } + + // Sync the display buffers + sync_display1_buffer(); + + dld->mode = (done > 0) ? DLOG_MODE_WIN : DLOG_MODE_LOSE; + dld->score = score; + + + return; +} diff --git a/src/input.h b/src/input.h index 0c40334..614bcbf 100644 --- a/src/input.h +++ b/src/input.h @@ -9,6 +9,8 @@ #define K_LEFT 3 #define K_RIGHT 4 #define K_CTRL_R 5 +#define K_CTRL_S 6 +#define K_CTRL_L 7 uint8_t read_kb(void); diff --git a/src/input_asm.s b/src/input_asm.s index d143a8b..0d64445 100644 --- a/src/input_asm.s +++ b/src/input_asm.s @@ -10,7 +10,10 @@ K_UP .equ 1 K_DOWN .equ 2 K_LEFT .equ 3 K_RIGHT .equ 4 -K_CTRL_R .equ 5 +K_CTRL_R .equ 5 +K_CTRL_S .equ 6 +K_CTRL_L .equ 7 + __internal_read_kb: IO_DATAOUT$:.equ 0xC000 @@ -30,7 +33,26 @@ IO_KB_CTRL_HI$: .equ 0xC05F ror a lda #K_CTRL_R bcs Return$ - + lda #0x02 + sta IO_DATAOUT$ + lda IO_DATAIN$ + ror a + ror a + ror a + ror a + ror a + lda #K_CTRL_S + bcs Return$ + lda #0x40 + sta IO_DATAOUT$ + lda IO_DATAIN$ + ror a + ror a + ror a + ror a + ror a + lda #K_CTRL_L + bcs Return$ NoCtrl$: lda IO_KB_CTRL_LOW$ ldy #3 diff --git a/src/intro_main.c b/src/intro_main.c new file mode 100644 index 0000000..4595ad0 --- /dev/null +++ b/src/intro_main.c @@ -0,0 +1,39 @@ +#include +#include + +#include + +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "shared_page.h" + +// External initialization requirements +#pragma require __preserve_zp +#pragma require __data_initialization_needed + +static shared_page_data *shared_page = (shared_page_data*)SHARED_PAGE; + +void main(void) { + uint8_t wait_count = 0x40; + + // Initialize the register for LFSR + lfsr_init(0xF00D); + + // Clear the memory used to pass parameters to the next module + memset((void*)(shared_page->module_data), 0, MODULE_DATA_SIZE); + + // TODO: Detect expansion hardware, and choose what to load according to that + + // Delay a bit + while(wait_count--) WAIT(0xFF); + + // Clear the display memory + memset((void*)DISPLAY_PAGE_2, 0, DISPLAY_PAGE_SIZE); + memset((void*)DISPLAY_PAGE_1, 0, DISPLAY_PAGE_SIZE); + + shared_page->master_command = MASTER_COMMAND_NONE; + shared_page->next_module_idx = 3; // DLOG module is next! + + return; +} diff --git a/src/loading_screen.c b/src/loading_screen.c index e3790bd..0aceb00 100644 --- a/src/loading_screen.c +++ b/src/loading_screen.c @@ -1,6 +1,6 @@ #include -__attribute__((section("loadscreen"))) const uint8_t __loading_screen[] = { +/*__attribute__((section("loadscreen")))*/ const uint8_t __loading_screen[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x30, 0x40, 0x2D, 0x36, 0x18, 0x18, 0x4C, 0x01, 0x0C, 0x06, 0x18, 0x43, 0x19, 0x6C, 0x30, 0x43, 0x0D, 0x06, 0x60, 0x00, 0x06, 0x07, 0x00, 0x33, 0x18, 0x6C, 0x30, 0x43, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x19, 0x33, 0x66, 0x7C, 0x33, 0x66, 0x4C, 0x19, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0C, 0x66, 0x30, 0x18, 0x43, 0x01, 0x03, 0x00, 0x00, 0x10, 0x00, 0x00, 0x7F, 0x7F, 0x1F, 0x00, 0x00, diff --git a/src/main.c b/src/main.c deleted file mode 100644 index cd2b163..0000000 --- a/src/main.c +++ /dev/null @@ -1,152 +0,0 @@ -#include - -#include -#include - -#include "utility.h" -#include "mem_registers.h" -#include "mem_map.h" -#include "input.h" -#include "game_logic.h" -#include "game_graphics.h" -#include "monitor_subroutines.h" - -// External initialization requirements -#pragma require __preserve_zp -//#pragma require __call_heap_initialize -#pragma require __data_initialization_needed - -// Make sure the loading screen is included -#pragma require __loading_screen - -#define MOVES_TEXT_X 32 -#define MOVES_TEXT_Y 61 -#define MOVES_TEXT_WIDTH 5 - -#define SCORE_TEXT_X 32 -#define SCORE_TEXT_Y 29 -#define SCORE_TEXT_WIDTH 5 - -#define HIGH_TEXT_X 32 -#define HIGH_TEXT_Y 107 - -#define WIN_SCORE_BONUS 10000 - -void init(void); - -// Low level initialization -void init(void) { - POKE(P3_PWRDUP, 0); // Dirty the value checked by the reset vector - PEEK(IO_ROMSEL); // Make sure the ROM is selected - - PEEK(IO_DISPLAY_BW); // Disable colors - - // Clear display memory - clear_display_buffers(); -} - -__task int main(void) { - uint16_t moves_count; - uint16_t score = 0; - uint16_t hi_score = 0; - int8_t done = 0; - - init(); - - while(1){ // Outer loop - moves_count = 0; - - // Draw a screen and wait for a key press here here - BELL1(); - ddraw_endgame_box(done, score, hi_score); - while(!read_any_key()) { - lfsr_update(); - } - BELL1(); - clear_display_buffers(); // Clear display again - - // Check if we have a new high-score from a previous game - if (score > hi_score) hi_score = score; - - // Reset the game, calculate the initial score depending on which tiles we randomly get - score = reset_game(); - - // Draw the initial state of the game - draw_game_background(hi_score); - draw_number(moves_count, MOVES_TEXT_WIDTH, MOVES_TEXT_X, MOVES_TEXT_Y); - draw_number(score, SCORE_TEXT_WIDTH, SCORE_TEXT_X, SCORE_TEXT_Y); - draw_tiles(); - - // Swap graphical buffers - swap_display_buffers(); - - while(1) { // Game loop - lfsr_update(); - - switch(read_kb()) { - case K_UP: - BELL1(); - done = step_game(GAME_STEP_UP); - ddraw_direction_arrows(GRAPH_ARROW_UP); - break; - case K_DOWN: - BELL1(); - done = step_game(GAME_STEP_DOWN); - ddraw_direction_arrows(GRAPH_ARROW_DOWN); - break; - case K_LEFT: - BELL1(); - done = step_game(GAME_STEP_LEFT); - ddraw_direction_arrows(GRAPH_ARROW_LEFT); - break; - case K_RIGHT: - BELL1(); - done = step_game(GAME_STEP_RIGHT); - ddraw_direction_arrows(GRAPH_ARROW_RIGHT); - break; - case K_CTRL_R: - BELL1(); - score = 0; // We'll reset the score - done = -1; - break; - default: - continue; // Do nothing, loop again - } - - // Increase the count of moves we made - moves_count++; - - // If we have won, or we got a reset request, break out of this loop - if(done) { - score += (done > 0) ? WIN_SCORE_BONUS : 0; - break; - } - - // Draw the number of moves - draw_number(moves_count, MOVES_TEXT_WIDTH, MOVES_TEXT_X, MOVES_TEXT_Y); - - // Draw the moved tiles - draw_tiles(); - - // Unable to add a tile: we ran out of space and lost!!! - uint8_t random_tile_off = add_random_tile(); - if(!random_tile_off) { - done = -1; // Lost the game - break; - } - - score = calculate_score(); - - // Draw the score - draw_number(score, SCORE_TEXT_WIDTH, SCORE_TEXT_X, SCORE_TEXT_Y); - - swap_display_buffers(); - - // Draw the new tile directly on the front buffer, this way we make it appear with an "animation" - ddraw_single_tile(random_tile_off - 1); - } - - }; - - return 0; -} diff --git a/src/master_func.s b/src/master_func.s new file mode 100644 index 0000000..cbc77b7 --- /dev/null +++ b/src/master_func.s @@ -0,0 +1,23 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern dii_encode_gcr62_data + .extern dii_power_on + .extern dii_power_off + .extern dii_head_reposition + .extern dii_read_sector + .extern dii_write_sector + + .section functionTable,text + + ;;; Function table + ;;; Export some functions here to be used by modules +__exported_func_table: + .word dii_encode_gcr62_data + .word dii_power_on + .word dii_power_off + .word dii_head_reposition + .word dii_read_sector + .word dii_write_sector + + .public __exported_func_table \ No newline at end of file diff --git a/src/master_main.c b/src/master_main.c new file mode 100644 index 0000000..9d85844 --- /dev/null +++ b/src/master_main.c @@ -0,0 +1,185 @@ +#include + +#include + +#include + +#include "shared_page.h" +#include "state_page.h" +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "mem_registers.h" +#include "disk2.h" +#include "dos_floppy.h" + +// External initialization requirements +#pragma require __data_initialization_needed + +// Require the exported function table to be present +//#pragma require __exported_func_table + +#define DEFAULT_DRIVE_CONTROLLER_OFFSET 0x10 + +static uint8_t *module_page = (uint8_t*)MODULE_PAGE; +static shared_page_data * shared_page = (shared_page_data*)SHARED_PAGE; + +#define FILE_LIST_LEN 5 +#define FNAME_LEN 6 +#define STATE_FILE_IDX 1 + +#define AUTOLOAD_FILES 3 // Autoload the first 3 files + +// We'll limit ourselves to files with a name of max 5 chars, to save some memory +static const uint8_t file_table[FILE_LIST_LEN][FNAME_LEN] = { + { 0x80 | 'L', 0x80 | 'O', 0x80 | 'A', 0x80 | 'D', 0x80 | 'S', 0x00}, // LOADS (this is not an executable, but will be used to show the loading screen). + { 0x80 | 'S', 0x80 | 'T', 0x80 | 'A', 0x80 | 'T', 0x80 | 'E', 0x00}, // STATE (this is not an executable, but will be used to save/load the game and scores). + { 0x80 | 'I', 0x80 | 'N', 0x80 | 'T', 0x80 | 'R', 0x80 | 'O', 0x00}, // INTRO (this executable will show the initial presentation picture) + { 0x80 | 'D', 0x80 | 'L', 0x80 | 'O', 0x80 | 'G', 0xA0, 0x00}, // DLOG (startup, win, lose dialogs) + { 0x80 | 'G', 0x80 | 'A', 0x80 | 'M', 0x80 | 'E', 0xA0, 0x00}, // GAME (the actual game) +}; + +static uint8_t file_trksec[FILE_LIST_LEN][2]; // This will hold track/sector for initial ts list sector for every one of the listed files. Populated at startup. +static uint16_t file_load_address[FILE_LIST_LEN] = { // This will hold the load address for the files + DISPLAY_PAGE_1, + STATE_PAGE, + MODULE_PAGE, + MODULE_PAGE, + MODULE_PAGE, +}; + +static void init(void); +static void init_floppy_data(uint8_t *cur_trk, uint8_t *cur_file); +static uint8_t fill_trksec_list(uint8_t* cur_trk); +static uint8_t calculate_crc8(uint8_t* data, uint8_t len); + +// Low level initialization +static void init(void) { + POKE(P3_PWRDUP, 0); // Dirty the value checked by the reset vector + PEEK(IO_ROMSEL); // Make sure the ROM is selected + PEEK(DISPLAY_PAGE_1); // Select display page 1 + PEEK(IO_DISPLAY_BW); // Disable colors + + // Generate the decoding table + dii_generate_6bit_decoding_mapping_table((uint8_t*)DECODING_MAPPING_TABLE_DEFAULT_ADDRESS); + // Generate the encoding table + dii_generate_6bit_encoding_mapping_table((uint8_t*)ENCODING_MAPPING_TABLE_DEFAULT_ADDRESS); +} + +static uint8_t fill_trksec_list(uint8_t* cur_trk) { + uint8_t file_counter = 0; + + for(uint8_t file_idx = 0; file_idx < FILE_LIST_LEN; file_idx++) { + FDE* f_desc = df_search_file(DEFAULT_DRIVE_CONTROLLER_OFFSET, file_table[file_idx], cur_trk); + if(f_desc) { + file_trksec[file_idx][0] = f_desc->tsl_trk; + file_trksec[file_idx][1] = f_desc->tsl_sec; + + file_counter++; + } else { + file_trksec[file_idx][0] = 0; + file_trksec[file_idx][1] = 0; + } + } + + return file_counter; +} + +// Load the info we need from floppy +static void init_floppy_data(uint8_t *cur_trk, uint8_t *cur_file) { + uint8_t initialized_correctly = 0; + + // Power on the drive and reset the head + dii_power_on(DEFAULT_DRIVE_CONTROLLER_OFFSET, 0); + dii_head_reposition(DEFAULT_DRIVE_CONTROLLER_OFFSET, 96, 0); // Head bang back to track 0! + + + // Build a table that maps every file we're interested in to the track/sector of the first T/S list entry. + initialized_correctly = fill_trksec_list(cur_trk) == FILE_LIST_LEN; + + // Load the first files to autoload + for(uint8_t file_num = 0; file_num < AUTOLOAD_FILES && initialized_correctly; file_num++) { + initialized_correctly = df_read_file(DEFAULT_DRIVE_CONTROLLER_OFFSET, file_trksec[file_num][0], file_trksec[file_num][1], (uint8_t*)file_load_address[file_num], cur_trk) && initialized_correctly; + *cur_file = file_num; + shared_page->next_module_idx = file_num; + } + + // Power off + dii_power_off(DEFAULT_DRIVE_CONTROLLER_OFFSET); + + // Check the CRC for the state page + uint8_t crc = calculate_crc8((uint8_t*)file_load_address[STATE_FILE_IDX], sizeof(state_page_data) - 1); + if(((state_page_data*)file_load_address[STATE_FILE_IDX])->crc != crc) { + memset(((void*)file_load_address[STATE_FILE_IDX]), 0, sizeof(state_page_data)); + } + + // If something went wrong, trigger a break + if (!initialized_correctly) __asm volatile(" brk\n":::); +} + +#define CRC8RDALLAS_POLY 0x31 +static uint8_t calculate_crc8(uint8_t* data, uint8_t len) { + uint8_t crc = 0; + + for(uint8_t data_idx = 0; data_idx < len; data_idx++) { + uint8_t carry; + uint8_t d = data[data_idx]; + + for (uint8_t i = 8; i > 0; i--) { + carry = (crc & 0x80); + crc <<= 1; + if (d & 1) crc |= 1; + d >>= 1; + if (carry) crc ^= CRC8RDALLAS_POLY; + } + } + + return crc; +} + +__task int main(void) { + uint8_t cur_trk = 0; + uint8_t cur_file = 0; + uint8_t keep_going = 1; + + __disable_interrupts(); + + init(); + init_floppy_data(&cur_trk, &cur_file); + + __enable_interrupts(); + + + do { + if((cur_file != shared_page->next_module_idx) || (shared_page->master_command == MASTER_COMMAND_SAVE)) { + __disable_interrupts(); + dii_power_on(DEFAULT_DRIVE_CONTROLLER_OFFSET, 0); + + // Check if we need to load another module + if(cur_file != shared_page->next_module_idx) { + cur_file = shared_page->next_module_idx; + // Read the next module + keep_going = df_read_file(DEFAULT_DRIVE_CONTROLLER_OFFSET, file_trksec[cur_file][0], file_trksec[cur_file][1], (uint8_t*)file_load_address[cur_file], &cur_trk); + } + + // Check if we need to save the state page + if (shared_page->master_command == MASTER_COMMAND_SAVE) { + uint8_t crc = calculate_crc8((uint8_t*)file_load_address[STATE_FILE_IDX], sizeof(state_page_data) - 1); + ((state_page_data*)file_load_address[STATE_FILE_IDX])->crc = crc; + keep_going = keep_going && df_overwrite_file(DEFAULT_DRIVE_CONTROLLER_OFFSET, file_trksec[STATE_FILE_IDX][0], file_trksec[STATE_FILE_IDX][1], (uint8_t*)file_load_address[STATE_FILE_IDX], &cur_trk); + } + + dii_power_off(DEFAULT_DRIVE_CONTROLLER_OFFSET); + __enable_interrupts(); + } + + shared_page->master_command = MASTER_COMMAND_NONE; + + // Execute the module + (((void (*)(void))(MODULE_PAGE))()); + } while(keep_going); + + __asm volatile(" brk\n":::); + + return 0; +} diff --git a/src/mem_map.h b/src/mem_map.h index 1e0fa00..1f3aaac 100644 --- a/src/mem_map.h +++ b/src/mem_map.h @@ -5,7 +5,10 @@ #define DISPLAY_PAGE_1 0x2000 #define DISPLAY_PAGE_2 0xA000 - #define ROMRAM_BANK 0xC100 +#define SHARED_PAGE 0x9B00 +#define STATE_PAGE 0x9A00 +#define MODULE_PAGE 0x4000 + #endif /* _MEMORY_MAP_HEADER_ */ diff --git a/src/shared_page.h b/src/shared_page.h new file mode 100644 index 0000000..1a3c54c --- /dev/null +++ b/src/shared_page.h @@ -0,0 +1,21 @@ +#ifndef _SHARED_PAGE_HEADER_ +#define _SHARED_PAGE_HEADER_ + +#include + +#define MASTER_COMMAND_NONE 0 +#define MASTER_COMMAND_SAVE 1 + +#define MODULE_DATA_SIZE 128 +#define MASTER_DATA_SIZE 126 + +typedef struct { + // master -> module + volatile uint8_t module_data[MODULE_DATA_SIZE]; + // module -> master + volatile uint8_t master_command; + volatile uint8_t next_module_idx; // Index in the file table for the next module to execute + volatile uint8_t master_data[MASTER_DATA_SIZE]; +} shared_page_data; + +#endif /* _SHARED_PAGE_HEADER_ */ diff --git a/src/sound.h b/src/sound.h new file mode 100644 index 0000000..922cf0f --- /dev/null +++ b/src/sound.h @@ -0,0 +1,16 @@ +#ifndef _SOUND_HEADER_ +#define _SOUND_HEADER_ + +#include + +#define SND_TAP() (snd_beep_lower(0x08, 0xFF)) + +void snd_beep(uint8_t duration, uint8_t pitch); +void snd_beep_lower(uint8_t duration, uint8_t pitch); + +void snd_start(void); +void snd_sad_scale(void); +void snd_festive(void); +void snd_mod_button(void); + +#endif /* _SOUND_HEADER_ */ diff --git a/src/sound.s b/src/sound.s new file mode 100644 index 0000000..5adb1a8 --- /dev/null +++ b/src/sound.s @@ -0,0 +1,161 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern _Vfp + .extern _Vsp + .extern _Zp + +;;; I/O ports +IO_SPK: .equ 0xC030 + +;;; Monitor routines +MON_WAIT .equ 0xFCA8 + +;;; snd_beep +;;; Generates a sound of configurable duration and pitch +;;; See https://github.com/tilleul/apple2/blob/master/2liners/the%20art%20of%202-liners/SCRN_PLOT_your_sound_routine.md +;;; Parameters: +;;; - duration [A]: duration of the sound +;;; - pitch [_Zp[0]]: sound pitch +;;; +;;; Returns: Nothing +snd_beep: +P_PITCH$: .equ _Zp+0 + tax ; Move accumulator with duration into register x +-- ldy zp:P_PITCH$ ; Load the pitch from zp + lda IO_SPK ; Toggle the speaker +- dey + bne - + dex ; Decrement the time left + bne -- ; Back at loading the pitch if we need to go on + rts + +snd_beep_lower: +P_PITCH$: .equ _Zp+0 + tax ; Move accumulator with duration into register x +-- ldy zp:P_PITCH$ ; Load the pitch from zp + lda IO_SPK ; Toggle the speaker +- dey + nop + nop + nop + bne - + dex ; Decrement the time left + bne -- ; Back at loading the pitch if we need to go on + rts + +snd_mod_button: +P_PITCH$: .equ _Zp+0 + lda #0xAA + sta zp:P_PITCH$ + lda #0x25 + jsr snd_beep_lower + lda #0xCC + sta zp:P_PITCH$ + lda #0x0A + jsr snd_beep_lower + rts + +snd_start: +P_PITCH$: .equ _Zp+0 + lda #0xCC + sta zp:P_PITCH$ + lda #0x99 + jsr snd_beep_lower + lda #0xAA + sta zp:P_PITCH$ + lda #0x80 + jsr snd_beep_lower + lda #0x88 + sta zp:P_PITCH$ + lda #0xff + jsr snd_beep_lower + rts + +snd_sad_scale: +T_IDX$: .equ _Zp+1 +P_PITCH$: .equ _Zp+0 + ldy #6 +- lda __sad_scale_pitches,y + sta zp:P_PITCH$ + lda #0x90 + sty zp:T_IDX$ + jsr snd_beep_lower + ldy zp:T_IDX$ + dey + bpl - + rts + +__sad_scale_pitches: + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xCC + .byte 0xAA + .byte 0x88 + .byte 0x88 + +snd_festive: +T_IDX$: .equ _Zp+1 +P_PITCH$: .equ _Zp+0 + ldy #10 +- lda __festive_pitches,y + sta zp:P_PITCH$ + lda __festive_duration,y + sty zp:T_IDX$ + jsr snd_beep + ldy zp:T_IDX$ + + lda __festive_wait,y + jsr MON_WAIT ; Call the monitor wait routine + + dey + bpl - + rts + +__festive_pitches: + .byte 0xEE + .byte 0xCC + .byte 0xAA + .byte 0x99 + .byte 0xAA + .byte 0xCC + .byte 0xCC + .byte 0x88 + .byte 0xAA + .byte 0xCC + .byte 0xCC + +__festive_duration: + .byte 0xFF + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + +__festive_wait: + .byte 0x70 + .byte 0x70 + .byte 0x70 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + +;;; Declaration of public symbols + .public snd_beep + .public snd_beep_lower + .public snd_sad_scale + .public snd_festive + .public snd_mod_button + .public snd_start diff --git a/src/state_page.h b/src/state_page.h new file mode 100644 index 0000000..d2598ce --- /dev/null +++ b/src/state_page.h @@ -0,0 +1,18 @@ +#ifndef _STATE_PAGE_HEADER_ +#define _STATE_PAGE_HEADER_ + +#include + +#include "game_logic.h" + +#define STATE_PAGE_DATA (251 - (GRID_SIDE * GRID_SIDE)) + +typedef struct { + volatile uint16_t hi_score; + volatile uint16_t saved_moves_count; + volatile uint8_t save_grid[GRID_SIDE * GRID_SIDE]; + volatile uint8_t data[STATE_PAGE_DATA]; + volatile uint8_t crc; +} state_page_data; + +#endif /* _STATE_PAGE_HEADER_ */ diff --git a/src/tk2k_startup.s b/src/tk2k_startup_master.s similarity index 99% rename from src/tk2k_startup.s rename to src/tk2k_startup_master.s index aa48beb..3736666 100644 --- a/src/tk2k_startup.s +++ b/src/tk2k_startup_master.s @@ -86,6 +86,7 @@ __call_heap_initialize: sta zp:_Zp sta zp:_Zp+1 jsr main + jmp exit ;;; *************************************************************************** diff --git a/src/tk2k_startup_module.s b/src/tk2k_startup_module.s new file mode 100644 index 0000000..e0c254d --- /dev/null +++ b/src/tk2k_startup_module.s @@ -0,0 +1,123 @@ + .rtmodel cstartup,"tk2k" + .rtmodel version, "1" + .rtmodel core, "*" + + ;; External declarations + .section cstack + .section heap + .section data_init_table + .section registers ; pseudo registers in zero page + .section zpsave ; this is where pseudo registers are saved + + .extern main, exit, __low_level_init + .extern _Zp, _Vsp, _Vfp + + .pubweak __program_root_section, __program_start + +call: .macro dest + jsr \dest + .endm + + .section programStart, root +__program_root_section: + jmp __program_start + .section startup, root, noreorder +__program_start: + .section startup, noreorder + .pubweak __preserve_zp_needed +__preserve_zp_needed: + ldx #0x00 ; Save the whole zero-page, a bit wasteful, will have to look into it in the future +- lda zp:0x00,x + sta .sectionStart zpsave,x + inx + bne - + + .section startup, root, noreorder + lda #.byte0(.sectionEnd cstack) + sta zp:_Vsp + lda #.byte1(.sectionEnd cstack) + sta zp:_Vsp+1 + jsr __low_level_init + +;;; Initialize data sections if needed. + .section startup, noroot, noreorder + .pubweak __data_initialization_needed + .extern __initialize_sections +__data_initialization_needed: + lda #.byte0 (.sectionStart data_init_table) + sta zp:_Zp + lda #.byte1 (.sectionStart data_init_table) + sta zp:_Zp+1 + lda #.byte0 (.sectionEnd data_init_table) + sta zp:_Zp+2 + lda #.byte1 (.sectionEnd data_init_table) + sta zp:_Zp+3 + call __initialize_sections + + .section startup, noroot, noreorder + .pubweak __call_initialize_global_streams + .extern __initialize_global_streams +__call_initialize_global_streams: + call __initialize_global_streams + +;;; **** Initialize heap if needed. + .section startup, noroot, noreorder + .pubweak __call_heap_initialize + .extern __heap_initialize, __default_heap +__call_heap_initialize: + lda #.byte0 __default_heap + sta zp:_Zp+0 + lda #.byte1 __default_heap + sta zp:_Zp+1 + lda #.byte0 (.sectionStart heap) + sta zp:_Zp+2 + lda #.byte1 (.sectionStart heap) + sta zp:_Zp+3 + lda #.byte0 (.sectionSize heap) + sta zp:_Zp+4 + lda #.byte1 (.sectionSize heap) + sta zp:_Zp+5 + call __heap_initialize + + .section startup, root, noreorder + tsx + stx _InitialStack ; for exit() + lda #0 ; argc = 0 + sta zp:_Zp + sta zp:_Zp+1 + jsr main + + ;;; Restore the zeropage + ldx #0x00 ; Restore the whole zero-page +- lda .sectionStart zpsave,x + sta zp:0x00,x + inx + bne - + + rts + +;;; *************************************************************************** +;;; +;;; __low_level_init - custom low level initialization +;;; +;;; This default routine just returns doing nothing. You can provide your own +;;; routine, either in C or assembly for doing custom low leve initialization. +;;; +;;; *************************************************************************** + + .section code + .pubweak __low_level_init +__low_level_init: + rts + +;;; *************************************************************************** +;;; +;;; Keep track of the initial stack pointer so that it can be restores to make +;;; a return back on exit(). +;;; +;;; *************************************************************************** + + .section zdata, bss + .pubweak _InitialStack +_InitialStack: + .space 1 \ No newline at end of file diff --git a/src/utility.c b/src/utility.c index 06fc881..5be83ab 100644 --- a/src/utility.c +++ b/src/utility.c @@ -30,14 +30,18 @@ uint8_t bit_count(uint8_t b) { return b; } +void lfsr_init(uint16_t reg) { + *((uint16_t*)LFSR_REGISTER_ADDRESS) = 0xF00D; +} + uint16_t lfsr_update(void) { - static uint16_t lfsr = 0xF00D; + uint16_t *lfsr = ((uint16_t*)LFSR_REGISTER_ADDRESS); - lfsr ^= lfsr >> 7; - lfsr ^= lfsr << 9; - lfsr ^= lfsr >> 13; + *lfsr ^= (*lfsr) >> 7; + *lfsr ^= (*lfsr) << 9; + *lfsr ^= (*lfsr) >> 13; - return lfsr; + return *lfsr; } /* diff --git a/src/utility.h b/src/utility.h index 6723446..365fa3d 100644 --- a/src/utility.h +++ b/src/utility.h @@ -18,9 +18,16 @@ #define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) +// 0x356 - 0x357 actually fall inside the +// decoding table for DISK II sector reading, +// but they're unused bytes! So we can use them +// to store the lfsr_update +#define LFSR_REGISTER_ADDRESS 0x0356 + void num_to_decbuf(uint16_t n, uint8_t len, uint8_t *buf); uint8_t bit_reverse(uint8_t b); uint8_t bit_count(uint8_t b); +void lfsr_init(uint16_t reg); uint16_t lfsr_update(void); //void print_line(const char* line, uint8_t off_x, uint8_t off_y);