mirror of
https://codeberg.org/hkzlab/TK2048.git
synced 2025-12-25 14:42:16 +11:00
Merged dii_refactoring, modularize code for 2.0
This commit is contained in:
parent
5d739383aa
commit
cfc455f1ca
37 changed files with 2101 additions and 249 deletions
23
CHANGELOG.md
Normal file
23
CHANGELOG.md
Normal file
|
|
@ -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._
|
||||
78
Makefile
78
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
|
||||
54
README.md
54
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.
|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||

|
||||
|
||||
### 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.
|
||||
I plan to add (optional) support for several expansion boards I'm working on, starting with a VDP board sporting a TMS9918A graphic processor.
|
||||
BIN
data/LOADS.bin
Normal file
BIN
data/LOADS.bin
Normal file
Binary file not shown.
BIN
data/STATE.bin
Normal file
BIN
data/STATE.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
|
@ -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))
|
||||
))
|
||||
30
linker-files/master.scm
Normal file
30
linker-files/master.scm
Normal file
|
|
@ -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
|
||||
|
||||
19
linker-files/module.scm
Normal file
19
linker-files/module.scm
Normal file
|
|
@ -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))
|
||||
))
|
||||
BIN
pics/title.png
Normal file
BIN
pics/title.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
46
src/disk2.h
Normal file
46
src/disk2.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#ifndef _DISK2_HEADER_
|
||||
#define _DISK2_HEADER_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
||||
726
src/disk2.s
Normal file
726
src/disk2.s
Normal file
|
|
@ -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
|
||||
16
src/dlog_data.h
Normal file
16
src/dlog_data.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef _DLOG_DATA_HEADER_
|
||||
#define _DLOG_DATA_HEADER_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
||||
64
src/dlog_main.c
Normal file
64
src/dlog_main.c
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include <stubs.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <calypsi/intrinsics6502.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
185
src/dos_floppy.c
Normal file
185
src/dos_floppy.c
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#include "dos_floppy.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
81
src/dos_floppy.h
Normal file
81
src/dos_floppy.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#ifndef _DOS_FLOPPY_HEADER_
|
||||
#define _DOS_FLOPPY_HEADER_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
||||
14
src/game_data.h
Normal file
14
src/game_data.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef _GAME_DATA_HEADER_
|
||||
#define _GAME_DATA_HEADER_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define GAME_MODE_NORMAL 0
|
||||
#define GAME_MODE_LOAD 1
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t mode;
|
||||
} game_data;
|
||||
|
||||
#endif /* _GAME_DATA_HEADER_ */
|
||||
|
|
@ -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++) {
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
165
src/game_main.c
Normal file
165
src/game_main.c
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
#include <stubs.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <calypsi/intrinsics6502.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 "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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
39
src/intro_main.c
Normal file
39
src/intro_main.c
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#include <stubs.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <calypsi/intrinsics6502.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include <stdint.h>
|
||||
|
||||
__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,
|
||||
|
|
|
|||
152
src/main.c
152
src/main.c
|
|
@ -1,152 +0,0 @@
|
|||
#include <stubs.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
23
src/master_func.s
Normal file
23
src/master_func.s
Normal file
|
|
@ -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
|
||||
185
src/master_main.c
Normal file
185
src/master_main.c
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#include <stubs.h>
|
||||
|
||||
#include <calypsi/intrinsics6502.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -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_ */
|
||||
|
|
|
|||
21
src/shared_page.h
Normal file
21
src/shared_page.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef _SHARED_PAGE_HEADER_
|
||||
#define _SHARED_PAGE_HEADER_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
||||
16
src/sound.h
Normal file
16
src/sound.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef _SOUND_HEADER_
|
||||
#define _SOUND_HEADER_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
||||
161
src/sound.s
Normal file
161
src/sound.s
Normal file
|
|
@ -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
|
||||
18
src/state_page.h
Normal file
18
src/state_page.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef _STATE_PAGE_HEADER_
|
||||
#define _STATE_PAGE_HEADER_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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_ */
|
||||
|
|
@ -86,6 +86,7 @@ __call_heap_initialize:
|
|||
sta zp:_Zp
|
||||
sta zp:_Zp+1
|
||||
jsr main
|
||||
|
||||
jmp exit
|
||||
|
||||
;;; ***************************************************************************
|
||||
123
src/tk2k_startup_module.s
Normal file
123
src/tk2k_startup_module.s
Normal file
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue