Merged dii_refactoring, modularize code for 2.0

This commit is contained in:
hkz 2025-08-30 10:01:49 +02:00
commit cfc455f1ca
37 changed files with 2101 additions and 249 deletions

23
CHANGELOG.md Normal file
View 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._

View file

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

View file

@ -7,15 +7,14 @@ By using this project You will agree that I cannot be held responsible if it wil
## Introduction
I recently built myself a Microdigital TK2000, a clone of an almost-clone (the MPF-II) of an Apple II.
![TK2048 V2.0 title screen](pics/title.png)
Building it from the files found in [this github repository](https://github.com/clemarfolly/Microdigital-TK2000) proved to be a [bit of an adventure](https://github.com/hkzlab/TK2000_Build_Notes),
but I took a liking to the computer, so I started developing [boards](https://codeberg.org/hkzlab) for it.
This game is an excuse and a testbed for the development on the Microdigital TK2000 clone [I recently built for myself](https://github.com/hkzlab/TK2000_Build_Notes).
Hardware fiddling is fine, but given that there isn't much software available for the machine and that I wanted to experiment with the [Calypsi toolchain](https://www.calypsi.cc/) a bit, I decided to
pick a simple game concept and develop it for the machine.
My primary objective with this game is building something where I can add support for all my expansion boards (well, at least where it makes a sliver of sense) so I can test and PoC them, beside,
I want to experiment with the [Calypsi toolchain](https://www.calypsi.cc/).
I decided for 2048, the now-classic tile sliding game, as it provided several advantages as a first time/playground project:
I decided for a port of 2048, the now-classic tile sliding game, as it provided several advantages as a first time/playground project:
- Doesn't need complex graphics hardware or advanced artist skills
- It can be drawn in B/W with static graphics, but animations and colors can be added in the future
@ -25,7 +24,11 @@ I decided for 2048, the now-classic tile sliding game, as it provided several ad
- Can be expanded with support for additional hardware (sound cards, video cards, timers) if so desired, but it's not necessary
- Floppy drive can be potentially used not only to load the game, but also for saving the high score
![TK2048 main game shown on a green phosphor CRT](pics/screenshot.jpg)
![TK2048 V1.0 main game shown on a green phosphor CRT](pics/screenshot.jpg)
### Support
If you wish to support me in building new hardware and software for old machines, [throw a few euros in my direction via Ko-Fi](https://ko-fi.com/hkzlab) ☕!
### Current state
@ -33,13 +36,18 @@ The game is playable, and the following features have been implemented:
- B/W mode
- Single graphical tileset
- Crude sound (only beeps)
- Crude sound
- Control via keyboard
- 5x5 grid, with randomized start and new tiles
- Game save/load
- Highscore saving
## How to play
You can move the tiles using the cursor keys. Pushing CTRL-R will make you lose the game and restart it.
- You can move the tiles using the cursor keys
- `CTRL-R` during the game will clear your score and restart
- `CTRL-S` during the game will save the game on floppy and let you continue
- `CTRL-L` during the game will load the previous save from floppy and let you continue from there
The game ends once you reach a tile with a value of 2048.
@ -47,28 +55,6 @@ The game ends once you reach a tile with a value of 2048.
Just put your floppy in the first drive and power on the TK2000. It will autoboot.
### Tape version
Power on the TK2000 with the sound player connected to the EAR input. Once at the `>` prompt, type the following
```
CALL -159
```
This will drop you into the monitor, `@` prompt. Type
```
801.4EBBRA
```
And press play on tape. If the load completes successfully, the computer will beep and give you back the prompt. Now type:
```
801G
```
This will start the game.
## How to build
You need the following:
@ -77,11 +63,7 @@ You need the following:
- [Calypsi 6502 toolchain](https://www.calypsi.cc/) (tested with 5.10)
- [AppleCommander](https://github.com/AppleCommander/AppleCommander), "ac" command line version (tested with 1.9.0)
- [dsk2woz](https://github.com/TomHarte/dsk2woz)
- vitasam's fork of [c2t](https://github.com/vitasam/c2t)
## The future
I plan to refactor the whole game, modularize it, and leverage loading of single modules from a floppy disk. This
should give more flexibility, expandability, free some memory and allow me to exercise the floppy drive routines I wrote.
As such, V1.0 will probably be the last one supporting loading from tape.
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

Binary file not shown.

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

Before After
Before After

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

46
src/disk2.h Normal file
View 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
View 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 023 are destroyed by a function call.
;;; Pseudo registers 2447 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
View 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
View 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
View 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
View 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
View 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_ */

View file

@ -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++) {

View file

@ -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
View 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;
}

View file

@ -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);

View file

@ -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
View 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;
}

View file

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

View file

@ -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
View 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
View 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;
}

View file

@ -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
View 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
View 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
View 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
View 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_ */

View file

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

View file

@ -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;
}
/*

View file

@ -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);