Add empty floppicator project

This commit is contained in:
hkz 2025-09-01 09:01:39 +02:00
commit be70ff936e
29 changed files with 2153 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
obj/*.o
obj/*.lst
out/*.bin
out/*.hex
out/*.wav

48
Makefile Normal file
View file

@ -0,0 +1,48 @@
VPATH = src
# Apple Commander tool JAR
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
SW_NAME=floppicator
PRG=master
# Libraries
LIBS=clib-6502.a
ASM_SRCS = tk2k_startup.s preserve_zero_pages.s disk2.s
C_SRCS = main.c
# Object files
OBJS = $(ASM_SRCS:%.s=%.o) $(C_SRCS:%.c=%.o)
all: $(SW_NAME).woz
%.o: %.s
as6502 --core=6502 --list-file=$(@:%.o=obj/%.lst) -o obj/$@ $<
%.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)
$(SW_NAME).dsk: $(PRG).bin
(cd out ; cp ../dsk/TK2048_AUTO_BRUN.dsk ./$(SW_NAME).dsk; \
cat $(PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk HELLO B 0x800;)
$(SW_NAME).woz: $(SW_NAME).dsk
(cd out ; dsk2woz ./$(SW_NAME).dsk ./$(SW_NAME).woz)
clean:
-rm obj/*.o
-rm obj/*.lst
-rm out/*.hex
-rm out/*.bin
-rm out/*.dsk
-rm out/*.woz

BIN
dsk/TK2048_AUTO_BRUN.dsk Normal file

Binary file not shown.

BIN
dsk/TK2048_AUTO_RUN.dsk Normal file

Binary file not shown.

21
linker-files/linker.scm Normal file
View file

@ -0,0 +1,21 @@
(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 dataMem (address (#x1600 . #x1fff)) (type ram) (section cstack zdata data heap zpsave))
(memory displayPage1 (address (#x2000 . #x3fff)) (type ram))
(memory upperMem (address (#x4000 . #x9bff)) (type ram))
(memory diskBuffer (address (#x9c00 . #x9eff)) (type ram)) ;;; This memory will be used by the disk II routines as buffer
(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 #x600))
(block heap (size #x000))
(block stack (size #x100))
))

0
obj/empty Normal file
View file

0
out/empty Normal file
View file

24
src/charset.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef _CHARSET_HEADER_
#define _CHARSET_HEADER_
#include <stdint.h>
// @ A B C D E F G H I
// J K L M N O P Q R S
// T U V W X Y Z [ \ ]
// ^ _ ! " # $ % & ' (
// ) * + , - . / 0 1 2
// 3 4 5 6 7 8 9 : ; <
// = > ?
// Then graphic characters follow
#define CHAR_HEIGHT 8
#define ALPHA_OFFSET (1 * CHAR_HEIGHT
#define SYMBOL_OFFSET (28 * CHAR_HEIGHT)
#define NUM_OFFSET (48 * CHAR_HEIGHT)
#define GRAPH_OFFSET (58 * CHAR_HEIGHT)
const uint8_t* const CHARSET = (uint8_t*)0xF200;
#endif /* _CHARSET_HEADER_ */

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

418
src/game_graphics.c Normal file
View file

@ -0,0 +1,418 @@
#include "game_graphics.h"
#include <string.h>
#include "utility.h"
#include "mem_map.h"
#include "mem_registers.h"
#include "line_data.h"
#include "game_logic.h"
#include "tiles.h"
#include "charset.h"
#include "monitor_subroutines.h"
#include "graph_misc_data.h"
#include "arrows_pic.h"
#define SCREEN_WIDTH 280
#define SCREEN_HEIGHT 192
#define SCREEN_WIDTH_B 40
#define GRID_CELL_SIDE 35
#define TOP_OFFSET 7
#define LEFT_OFFSET_B 1 // Left is offset by 1 bytes (7 pixels)
static uint8_t *front_buf;
static uint8_t *back_buf;
#define BOX_WIDTH 32
#define BOX_HEIGHT 17
#define BOX_CONTENT_SIZE (BOX_WIDTH * BOX_HEIGHT)
static const uint8_t box_content_win[BOX_CONTENT_SIZE] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 77, 0, 77, 0, 83, 77, 84, 0, 77, 0, 77, 0, 0, 77, 0, 0, 0, 77, 0, 77, 0, 77, 0, 77, 0, 0, 0, 0,
0, 0, 0, 0, 77, 0, 77, 0, 77, 0, 77, 0, 77, 0, 77, 0, 0, 77, 0, 77, 0, 77, 0, 77, 0, 77, 84, 77, 0, 0, 0, 0,
0, 0, 0, 0,212, 77,211, 0, 77, 0, 77, 0, 77, 0, 77, 0, 0, 77, 0, 77, 0, 77, 0, 77, 0, 77, 0, 77, 0, 0, 0, 0,
0, 0, 0, 0, 0, 77, 0, 0,212, 77,211, 0,212, 77,211, 0, 0,212,211, 0,212,211, 0, 77, 0, 77, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 25, 15, 21, 18, 0, 19, 3, 15, 18, 5, 0, 9, 19, 58, 0, 48, 48, 48, 48, 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, 16, 21, 19, 8, 0, 1, 14, 25, 0, 11, 5, 25, 0, 20, 15, 0, 3, 15, 14, 20, 9, 14, 21, 5, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static const uint8_t box_content_lose[BOX_CONTENT_SIZE] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 1, 13, 5, 0, 0, 15, 22, 5, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 77, 0, 77, 0, 83, 77, 84, 0, 77, 0, 77, 0, 0, 77, 0, 0, 0, 83, 77, 84, 0, 83, 77,192, 0, 77, 77, 77, 0, 0,
0, 0, 77, 0, 77, 0, 77, 0, 77, 0, 77, 0, 77, 0, 0, 77, 0, 0, 0, 77, 0, 77, 0,212, 77, 84, 0, 77, 0, 0, 0, 0,
0, 0,212, 77,211, 0, 77, 0, 77, 0, 77, 0, 77, 0, 0, 77, 0, 0, 0, 77, 0, 77, 0, 0,203, 77, 0, 77,193, 0, 0, 0,
0, 0, 0, 77, 0, 0,212, 77,211, 0,212, 77,211, 0, 0,212, 77, 77, 0,212, 77,211, 0, 77, 77,211, 0, 77, 77, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 25, 15, 21, 18, 0, 19, 3, 15, 18, 5, 0, 9, 19, 58, 0, 48, 48, 48, 48, 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, 16, 21, 19, 8, 0, 1, 14, 25, 0, 11, 5, 25, 0, 20, 15, 0, 3, 15, 14, 20, 9, 14, 21, 5, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
static const uint8_t box_content_start[BOX_CONTENT_SIZE] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 16, 21, 19, 8, 0, 1, 14, 25, 0, 11, 5, 25, 0, 20, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 83, 77,192, 0, 77, 77, 77, 0, 83, 77, 84, 0, 77, 77, 84, 0, 77, 77, 77, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,212, 77, 84, 0, 0, 77, 0, 0, 77, 0, 77, 0, 77, 0, 77, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0,
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, 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,
};
static const uint8_t box_new_hi_score[BOX_WIDTH] = {
0, 0,102, 0, 23, 5, 0, 7, 15, 20, 0, 1, 0, 14, 5, 23, 0, 8, 9, 7, 8, 45, 19, 3, 15, 18, 5, 33, 0,102, 0, 0
};
#define INSTR_BOX_WIDTH 12
#define INSTR_BOX_HEIGHT 8
#define INSTR_BOX_SIZE (INSTR_BOX_HEIGHT * INSTR_BOX_WIDTH)
#define INSTR_BOX_X 28
#define INSTR_BOX_Y 127
static const uint8_t instruction_box[INSTR_BOX_SIZE] = {
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,
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, 50, 46, 48, 0, 79
};
// The grid is 5x5 squares,
// It is offset on the left side by 7 pixels and on the top by 14
// Every square is 35x35 pixels
void draw_field_borders_on_buffer(uint8_t brd, uint8_t* buf);
void draw_picture(uint8_t w, uint8_t h, uint8_t x, uint8_t y, const uint8_t *data, uint8_t *dest);
void direct_draw_number(uint16_t n, uint8_t len, uint8_t x, uint8_t y, uint8_t *disp_buf);
void draw_graph_char_box(uint8_t x_offset, uint8_t y_offset, uint8_t width, uint8_t height, uint8_t const *data, uint8_t* buf);
void ddraw_field_borders_on_buffer(uint8_t brd) {
draw_field_borders_on_buffer(brd, front_buf);
}
#define HIGH_TEXT_X 32
#define HIGH_TEXT_Y 107
#define HIGH_TEXT_WIDTH 5
void draw_game_background(uint16_t hi_score) {
// Draw the background on display page 1
uint8_t* buf = (uint8_t*)DISPLAY_PAGE_1;
// Draw the borders
draw_field_borders_on_buffer(0x0F, buf);
// Draw required pics
draw_picture(SCORE_PIC_WIDTH_BYTES, SCORE_PIC_HEIGHT, 31, 14, score_pic_data, buf);
draw_picture(MOVES_PIC_WIDTH_BYTES, MOVES_PIC_HEIGHT, 31, 45, moves_pic_data, buf);
draw_picture(HIGH_PIC_WIDTH_BYTES, HIGH_PIC_HEIGHT, 31, 76, high_pic_data, buf);
draw_picture(SCORE_PIC_WIDTH_BYTES, SCORE_PIC_HEIGHT, 31, 90, score_pic_data, buf);
// Draw the high-score. This won't change at every turn, so makes sense to just draw once
direct_draw_number(hi_score, HIGH_TEXT_WIDTH, HIGH_TEXT_X, HIGH_TEXT_Y, front_buf);
// Draw instruction box
draw_graph_char_box(INSTR_BOX_X, INSTR_BOX_Y, INSTR_BOX_WIDTH, INSTR_BOX_HEIGHT, instruction_box, front_buf);
// Copy the data from display page 1 to 2
memcpy((void*)DISPLAY_PAGE_2, (void*)DISPLAY_PAGE_1, DISPLAY_PAGE_SIZE);
}
// This will draw directly to the front buffer
void ddraw_single_tile(uint8_t offset) {
uint8_t* grid = get_front_grid();
if(!grid[offset]) return; // The tile is not there, nothing to do
const uint8_t *tile_data = tiles + (TILE_WIDTH_BYTES * TILE_HEIGHT * (grid[offset] - 1));
uint8_t col = offset % GRID_SIDE;
uint8_t row = offset / GRID_SIDE;
uint8_t delay = 0xFF;
for(uint8_t h = 0; h < TILE_HEIGHT; h++) {
memcpy(front_buf + line_offset_map[TOP_OFFSET + 7 + (row * GRID_CELL_SIDE) + h] + LEFT_OFFSET_B + 1 + (col * GRID_CELL_SIDE/7),
tile_data + (TILE_WIDTH_BYTES * h), TILE_WIDTH_BYTES);
WAIT(48);
}
}
void direct_draw_number(uint16_t n, uint8_t len, uint8_t x, uint8_t y, uint8_t *disp_buf) {
uint8_t buf[len];
// Decode the number into the buffer
num_to_decbuf(n, len, buf);
for(uint8_t row = 0; row < CHAR_HEIGHT; row++) {
uint16_t offset = line_offset_map[y + row];
for(uint8_t col = 0; col < len; col++) {
disp_buf[(offset + (len - 1) - col) + x] = CHARSET[NUM_OFFSET + (buf[col] * CHAR_HEIGHT) + row];
}
}
}
void draw_number(uint16_t n, uint8_t len, uint8_t x, uint8_t y) {
direct_draw_number(n, len, x, y, back_buf);
}
void draw_tiles(void) {
uint8_t* grid = get_front_grid();
// Clear the grid so we'll be able to draw the boxes on
clear_box(GRID_SIDE * (GRID_CELL_SIDE/7) + 1, (GRID_SIDE * GRID_CELL_SIDE) + 4, LEFT_OFFSET_B, TOP_OFFSET + 1, back_buf);
for (uint8_t tile = 0; tile < GRID_SIDE * GRID_SIDE; tile++) {
if(grid[tile]) {
const uint8_t *tile_data = tiles + (TILE_WIDTH_BYTES * TILE_HEIGHT * (grid[tile] - 1));
uint8_t col = tile % GRID_SIDE;
uint8_t row = tile / GRID_SIDE;
draw_picture(TILE_WIDTH_BYTES, TILE_HEIGHT, LEFT_OFFSET_B + 1 + (col * GRID_CELL_SIDE/7), TOP_OFFSET + 7 + (row * GRID_CELL_SIDE), tile_data, back_buf);
}
}
// Re-draw the borders, to restore the correct width
draw_field_borders_on_buffer(0x0F, back_buf);
}
#define ENDGAME_BOX_X_OFFSET 2
#define ENDGAME_BOX_Y_OFFSET 16
#define ENDGAME_BOX_SCORE_LEN 5
void ddraw_endgame_box(int8_t done, uint16_t score, uint16_t hi_score) {
// Clear the part of the screen where we'll draw
clear_box((SCREEN_WIDTH_B - (ENDGAME_BOX_X_OFFSET * 2)) + 1, (SCREEN_HEIGHT - (ENDGAME_BOX_Y_OFFSET * 2)) + CHAR_HEIGHT, ENDGAME_BOX_X_OFFSET, ENDGAME_BOX_Y_OFFSET, front_buf);
// Horizontal lines
for(uint8_t row = 0; row < CHAR_HEIGHT; row++) {
uint16_t offset_top = line_offset_map[ENDGAME_BOX_Y_OFFSET + CHAR_HEIGHT + row] + ENDGAME_BOX_X_OFFSET + 1;
uint16_t offset_bottom = line_offset_map[SCREEN_HEIGHT - ENDGAME_BOX_Y_OFFSET - CHAR_HEIGHT + row] + ENDGAME_BOX_X_OFFSET + 1;
for(uint8_t col = 0; col < SCREEN_WIDTH_B - ((ENDGAME_BOX_X_OFFSET * 2) + 2); col++) {
front_buf[offset_top + col] = CHARSET[GRAPH_OFFSET + (12 * CHAR_HEIGHT) + row];
front_buf[offset_bottom + col] = CHARSET[GRAPH_OFFSET + (12 * CHAR_HEIGHT) + row];
}
}
// Corners
for(uint8_t row = 0; row < CHAR_HEIGHT; row++) {
uint16_t offset_top = line_offset_map[ENDGAME_BOX_Y_OFFSET + CHAR_HEIGHT + row] + ENDGAME_BOX_X_OFFSET + 1;
uint16_t offset_bottom = line_offset_map[SCREEN_HEIGHT - ENDGAME_BOX_Y_OFFSET - CHAR_HEIGHT + row] + ENDGAME_BOX_X_OFFSET + 1;
front_buf[offset_top] = CHARSET[GRAPH_OFFSET + (25 * CHAR_HEIGHT) + row];
front_buf[offset_bottom] = CHARSET[GRAPH_OFFSET + (27 * CHAR_HEIGHT) + row];
front_buf[offset_top + (SCREEN_WIDTH_B - ((ENDGAME_BOX_X_OFFSET * 2) + 2))] = CHARSET[GRAPH_OFFSET + (26 * CHAR_HEIGHT) + row];
front_buf[offset_bottom + (SCREEN_WIDTH_B - ((ENDGAME_BOX_X_OFFSET * 2) + 2))] = CHARSET[GRAPH_OFFSET + (28 * CHAR_HEIGHT) + row];
}
// Vertical lines
for(uint8_t row = 0; row < ((SCREEN_HEIGHT - (ENDGAME_BOX_Y_OFFSET * 3) - CHAR_HEIGHT)) + 1; row++) {
uint16_t offset = line_offset_map[ENDGAME_BOX_Y_OFFSET + (CHAR_HEIGHT * 2) + row] + ENDGAME_BOX_X_OFFSET + 1;
front_buf[offset] = CHARSET[GRAPH_OFFSET + (19 * CHAR_HEIGHT) + (row % CHAR_HEIGHT)];
front_buf[offset + (SCREEN_WIDTH_B - ((ENDGAME_BOX_X_OFFSET * 2) + 2))] = CHARSET[GRAPH_OFFSET + (19 * CHAR_HEIGHT) + (row % CHAR_HEIGHT)];
}
uint8_t const *content;
// Decide which type of content to show
if(done == 0) content = box_content_start;
else if (done > 0) content = box_content_win;
else content = box_content_lose;
// And now, the content!!!
draw_graph_char_box(ENDGAME_BOX_X_OFFSET + 2, (ENDGAME_BOX_Y_OFFSET + (2 * CHAR_HEIGHT)), BOX_WIDTH, BOX_HEIGHT, content, front_buf);
if(done != 0) { // Print the score
direct_draw_number(score, ENDGAME_BOX_SCORE_LEN, ENDGAME_BOX_X_OFFSET + 23, ENDGAME_BOX_Y_OFFSET + (11 * CHAR_HEIGHT), front_buf);
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);
}
}
void draw_graph_char_box(uint8_t x_offset, uint8_t y_offset, uint8_t width, uint8_t height, uint8_t const *data, uint8_t* buf) {
// Draw box
for(uint16_t tile = 0; tile < width * height; tile++) {
if(!data[tile]) continue;
uint8_t x = tile % width;
uint8_t y = tile / width;
uint8_t invert = data[tile] & 0x80;
uint8_t ch_num = data[tile] & 0x7F;
for(uint8_t row = 0; row < CHAR_HEIGHT; row++) {
uint16_t offset = line_offset_map[y_offset + (y * CHAR_HEIGHT) + row] + x_offset + x;
buf[offset] = invert ? ((~CHARSET[(ch_num * CHAR_HEIGHT) + row]) & 0x7F) : CHARSET[(ch_num * CHAR_HEIGHT) + row];
}
}
}
// Note that the horizontal values here are in group of 7 pixels
void clear_box(uint8_t w, uint8_t h, uint8_t off_x, uint8_t off_y, uint8_t *disp_buf) {
for(uint8_t y = off_y; y < off_y + h; y++) {
uint16_t line_counter = line_offset_map[y];
for(uint8_t x = off_x; x < off_x + w; x++) {
disp_buf[line_counter + x] = 0;
}
}
}
void swap_display_buffers(void) {
uint8_t *temp = front_buf;
front_buf = back_buf;
back_buf = temp;
// Show the current buffer
PEEK(((uint16_t)front_buf == DISPLAY_PAGE_1) ? IO_DISPLAY_PAGE1 : IO_DISPLAY_PAGE2);
}
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
front_buf = (uint8_t*)DISPLAY_PAGE_1;
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++) {
buf[line_offset_map[TOP_OFFSET - 1] + col + LEFT_OFFSET_B] = BRD_SKIP_UP(brd) ? 0x00: 0x7F;
buf[line_offset_map[TOP_OFFSET - 0] + col + LEFT_OFFSET_B] = BRD_DOUBLING_UP(brd) && !BRD_SKIP_UP(brd) ? 0x7F : 0x00;
buf[line_offset_map[TOP_OFFSET + (GRID_CELL_SIDE * GRID_SIDE) + 7] + col + LEFT_OFFSET_B] = BRD_SKIP_DOWN(brd) ? 0x00: 0x7F;
buf[line_offset_map[TOP_OFFSET + (GRID_CELL_SIDE * GRID_SIDE) + 6] + col + LEFT_OFFSET_B] = BRD_DOUBLING_DOWN(brd) && !BRD_SKIP_DOWN(brd) ? 0x7F : 0x00;
}
// Vertical borders
for(uint8_t row = 0; row < (GRID_CELL_SIDE * GRID_SIDE) + 7; row++) {
buf[line_offset_map[row + TOP_OFFSET] + LEFT_OFFSET_B - 1] = BRD_SKIP_LEFT(brd) ? 0x00 : (BRD_DOUBLING_LEFT(brd) ? 0x60 : 0x40);
buf[line_offset_map[row + TOP_OFFSET] + LEFT_OFFSET_B + (GRID_SIDE * (GRID_CELL_SIDE/7)) + 1] = BRD_SKIP_RIGHT(brd) ? 0x00 : (BRD_DOUBLING_RIGHT(brd) ? 0x03 : 0x01);
}
}
void draw_picture(uint8_t w, uint8_t h, uint8_t x, uint8_t y, const uint8_t *data, uint8_t *dest) {
for(uint8_t row = 0; row < h; row++) {
memcpy(dest + line_offset_map[row + y] + x, data + (w * row), w);
}
}
void ddraw_direction_arrows(uint8_t dir) {
uint8_t pic_buffer[ARROWS_HEIGHT];
int8_t start, step, end, flip;
uint8_t ext, x, y;
switch(dir) {
case GRAPH_ARROW_UP:
x = 2;
y = TOP_OFFSET + 1;
ext = 1;
start = 1;
step = 1;
end = ARROWS_HEIGHT;
flip = 0;
break;
case GRAPH_ARROW_DOWN:
x = 2;
y = TOP_OFFSET + (GRID_SIDE * GRID_CELL_SIDE);
ext = 1;
start = ARROWS_HEIGHT - 1;
step = -1;
end = 0;
flip = 0;
break;
case GRAPH_ARROW_LEFT:
x = 1;
y = TOP_OFFSET + 7;
ext = 0;
start = ARROWS_HEIGHT;
step = 1;
end = (ARROWS_HEIGHT * 2);
flip = 0;
break;
case GRAPH_ARROW_RIGHT:
x = 1 + (GRID_SIDE * (GRID_CELL_SIDE/7));
y = TOP_OFFSET + 7;
ext = 0;
start = ARROWS_HEIGHT;
step = 1;
end = (ARROWS_HEIGHT * 2);
flip = 1;
break;
default:
return;
}
uint8_t tot_arrows = (GRID_SIDE * (GRID_CELL_SIDE/7)) - 1;
if(ext) { // Horizontal lines
uint8_t s_start = x;
for(uint8_t cur_arrow = 0; cur_arrow < tot_arrows; cur_arrow++) {
for(int8_t s = start, row = 0; s != end; s += step, row++) {
front_buf[line_offset_map[y + row] + s_start] = arrows_pic[s];
}
s_start++;
}
} else {
uint8_t s_start = y;
for(uint8_t cur_arrow = 0; cur_arrow < tot_arrows; cur_arrow++) {
for(int8_t s = start, row = 0; s != end; s += step, row++) {
front_buf[line_offset_map[s_start + row] + x] = flip ? (bit_reverse(arrows_pic[s]) >> 1) : arrows_pic[s];
}
s_start += ARROWS_HEIGHT;
}
}
}

34
src/game_graphics.h Normal file
View file

@ -0,0 +1,34 @@
#ifndef _GAME_GRAPHICS_HEADER_
#define _GAME_GRAPHICS_HEADER_
#include <stdint.h>
#define BRD_DOUBLING_UP(a) (a & 0x01)
#define BRD_DOUBLING_DOWN(a) (a & 0x02)
#define BRD_DOUBLING_LEFT(a) (a & 0x04)
#define BRD_DOUBLING_RIGHT(a) (a & 0x08)
#define BRD_SKIP_UP(a) (a & 0x10)
#define BRD_SKIP_DOWN(a) (a & 0x20)
#define BRD_SKIP_LEFT(a) (a & 0x40)
#define BRD_SKIP_RIGHT(a) (a & 0x80)
#define GRAPH_ARROW_UP 0
#define GRAPH_ARROW_DOWN 1
#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);
void draw_tiles(void);
void ddraw_single_tile(uint8_t offset);
void swap_display_buffers(void);
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_ */

25
src/input.c Normal file
View file

@ -0,0 +1,25 @@
#include "input.h"
#include "utility.h"
#include "mem_registers.h"
uint8_t __internal_read_kb(void);
uint8_t read_kb(void) {
static uint8_t last_press = K_NONE;
uint8_t cur_press = __internal_read_kb();
if (cur_press != last_press) {
last_press = cur_press;
return cur_press;
} else {
return K_NONE;
}
}
uint8_t read_any_key(void) {
PEEK(IO_KB_CTRL_LOW);
POKE(IO_DATAOUT, 0xFF);
return PEEK(IO_DATAIN) & DATAIN_KB_MASK;
}

19
src/input.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef _INPUT_HEADER_
#define _INPUT_HEADER_
#include <stdint.h>
#define K_NONE 0
#define K_UP 1
#define K_DOWN 2
#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);
uint8_t read_any_key(void);
#endif /* _INPUT_HEADER_ */

85
src/input_asm.s Normal file
View file

@ -0,0 +1,85 @@
.rtmodel version,"1"
.rtmodel core,"6502"
.extern _Vfp
.extern _Vsp
.extern _Zp
K_NONE: .equ 0
K_UP .equ 1
K_DOWN .equ 2
K_LEFT .equ 3
K_RIGHT .equ 4
K_CTRL_R .equ 5
K_CTRL_S .equ 6
K_CTRL_L .equ 7
__internal_read_kb:
IO_DATAOUT$:.equ 0xC000
IO_DATAIN$: .equ 0xC010
IO_KB_CTRL_LOW$: .equ 0xC05E
IO_KB_CTRL_HI$: .equ 0xC05F
lda IO_KB_CTRL_HI$
lda IO_DATAIN$
ror a
bcc NoCtrl$
lda #0x04
sta IO_DATAOUT$
lda IO_DATAIN$
ror a
ror a
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
NextKey$: lda _rkb_key_inp,y
sta IO_DATAOUT$
lda IO_DATAIN$
ror a
lda _rkb_key_ret,y
bcs Return$
dey
bpl NextKey$
lda #K_NONE
Return$:
rts
_rkb_key_ret:
.byte K_LEFT
.byte K_RIGHT
.byte K_DOWN
.byte K_UP
_rkb_key_inp:
.byte 0x08
.byte 0x10
.byte 0x20
.byte 0x40
;;; Declaration of public symbols
.public __internal_read_kb

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

22
src/line_data.c Normal file
View file

@ -0,0 +1,22 @@
#include <stdint.h>
#include "utility.h"
const uint16_t line_offset_map[SCREEN_HEIGHT] = {
0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x0080, 0x0480, 0x0880, 0x0C80,
0x1080, 0x1480, 0x1880, 0x1C80, 0x0100, 0x0500, 0x0900, 0x0D00, 0x1100, 0x1500, 0x1900, 0x1D00,
0x0180, 0x0580, 0x0980, 0x0D80, 0x1180, 0x1580, 0x1980, 0x1D80, 0x0200, 0x0600, 0x0A00, 0x0E00,
0x1200, 0x1600, 0x1A00, 0x1E00, 0x0280, 0x0680, 0x0A80, 0x0E80, 0x1280, 0x1680, 0x1A80, 0x1E80,
0x0300, 0x0700, 0x0B00, 0x0F00, 0x1300, 0x1700, 0x1B00, 0x1F00, 0x0380, 0x0780, 0x0B80, 0x0F80,
0x1380, 0x1780, 0x1B80, 0x1F80, 0x0028, 0x0428, 0x0828, 0x0C28, 0x1028, 0x1428, 0x1828, 0x1C28,
0x00A8, 0x04A8, 0x08A8, 0x0CA8, 0x10A8, 0x14A8, 0x18A8, 0x1CA8, 0x0128, 0x0528, 0x0928, 0x0D28,
0x1128, 0x1528, 0x1928, 0x1D28, 0x01A8, 0x05A8, 0x09A8, 0x0DA8, 0x11A8, 0x15A8, 0x19A8, 0x1DA8,
0x0228, 0x0628, 0x0A28, 0x0E28, 0x1228, 0x1628, 0x1A28, 0x1E28, 0x02A8, 0x06A8, 0x0AA8, 0x0EA8,
0x12A8, 0x16A8, 0x1AA8, 0x1EA8, 0x0328, 0x0728, 0x0B28, 0x0F28, 0x1328, 0x1728, 0x1B28, 0x1F28,
0x03A8, 0x07A8, 0x0BA8, 0x0FA8, 0x13A8, 0x17A8, 0x1BA8, 0x1FA8, 0x0050, 0x0450, 0x0850, 0x0C50,
0x1050, 0x1450, 0x1850, 0x1C50, 0x00D0, 0x04D0, 0x08D0, 0x0CD0, 0x10D0, 0x14D0, 0x18D0, 0x1CD0,
0x0150, 0x0550, 0x0950, 0x0D50, 0x1150, 0x1550, 0x1950, 0x1D50, 0x01D0, 0x05D0, 0x09D0, 0x0DD0,
0x11D0, 0x15D0, 0x19D0, 0x1DD0, 0x0250, 0x0650, 0x0A50, 0x0E50, 0x1250, 0x1650, 0x1A50, 0x1E50,
0x02D0, 0x06D0, 0x0AD0, 0x0ED0, 0x12D0, 0x16D0, 0x1AD0, 0x1ED0, 0x0350, 0x0750, 0x0B50, 0x0F50,
0x1350, 0x1750, 0x1B50, 0x1F50, 0x03D0, 0x07D0, 0x0BD0, 0x0FD0, 0x13D0, 0x17D0, 0x1BD0, 0x1FD0
};

9
src/line_data.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef _LINE_DATA_HEADER_
#define _LINE_DATA_HEADER_
#include <stdint.h>
#include "utility.h"
extern const uint16_t line_offset_map[SCREEN_HEIGHT];
#endif /* _LINE_DATA_HEADER */

53
src/main.c Normal file
View file

@ -0,0 +1,53 @@
#include <stubs.h>
#include <calypsi/intrinsics6502.h>
#include <string.h>
#include "monitor_subroutines.h"
#include "utility.h"
#include "mem_map.h"
#include "mem_registers.h"
#include "disk2.h"
// External initialization requirements
#pragma require __preserve_zp
#pragma require __data_initialization_needed
#define DEFAULT_DRIVE_CONTROLLER_OFFSET 0x10
static void init(void);
// 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);
}
__task int main(void) {
uint8_t cur_trk = 0;
uint8_t cur_file = 0;
uint8_t keep_going = 1;
__disable_interrupts();
init();
__enable_interrupts();
do {
} while(keep_going);
__asm volatile(" brk\n":::);
return 0;
}

14
src/mem_map.h Normal file
View file

@ -0,0 +1,14 @@
#ifndef _MEMORY_MAP_HEADER_
#define _MEMORY_MAP_HEADER_
#define DISPLAY_PAGE_SIZE 0x2000
#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_ */

48
src/mem_registers.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef _MEM_REGISTERS_HEADER_
#define _MEM_REGISTERS_HEADER_
#include <stdint.h>
#define ROM_MONITOR 0xFF61
#define ZP_WNDLFT 0x0020 // 0, left column of scroll window
#define ZP_WNDWDTH 0x0021 // 40, width of scroll window
#define ZP_WNDTOP 0x0022 // 0, top line of the scroll window
#define ZP_WNDBTM 0x0023 // 24, bottom line of the scroll window
#define ZP_CH 0x0024 // Displacement from window left for the cursor
#define ZP_CV 0x0025 // Displacement from top of screen (not window!) for the cursor
#define ZP_INVFLAG 0x0032 // Either 0x00 or 0x7F, set text color inversion
#define ZP_PROMPT 0x0033 // Prompt character
#define ZP_RND 0x004E // Note that this is a 16bit register incremented by the RDKEY func
#define P3_PWRDUP_REF 0x03F3
#define P3_PWRDUP 0x03F4 // Already-powered-up indicator. If it is set to the content of 0x03F3 XOR'd with 0xA5, the soft reset vector is considered valid
#define DATAIN_KB_MASK 0x3F
#define DATAIN_PRNT_MASK 0x40
#define DATAIN_TAPEIN_MASK 0x80
#define IO_DATAOUT 0xC000 // (W) To keyboard and printer port
#define IO_DATAIN 0xC010 // (R) Data input from keyboard (0:5), printer (6) and tape (7)
#define IO_TAPEOUT 0xC020 // (R) Data output for tape, read from here to output bit on tape
#define IO_SPEAKER 0xC030 // (R) Speaker toggle
#define IO_DISPLAY_COLOR 0xC050 // (R / W) Access here to enable the colorburst
#define IO_DISPLAY_BW 0xC051 // (R / W) Access here to disable the colorburst
#define IO_MTA_OFF 0xC052 // (?)
#define IO_MTA_ON 0xC053 // (?)
#define IO_DISPLAY_PAGE1 0xC054 // (R / W) Access here to select the primary display page
#define IO_DISPLAY_PAGE2 0xC055 // (R / W) Access here to select the secondary display page
#define IO_MTB_OFF 0xC056 // (?)
#define IO_MTB_ON 0xC057 // (?)
#define IO_PRNT_STRB_LO 0xC058 // (R / W) Access LO/HI/LO or HI/LO/HI consecutively depending on the type of strobe pulse to create
#define IO_PRNT_STRB_HI 0xC059 // (R / W)
#define IO_ROMSEL 0xC05A // (R / W) Access here will make region C100-FFFF a ROM area
#define IO_RAMSEL 0xC05B // (R / W) Access here will make region C100-FFFF a RAM area
#define IO_KB_CTRL_LOW 0xC05E // (R / W) Set the CTRL line to 0, access is through DATAIN
#define IO_KB_CTRL_HI 0xC05F // (R / W) Set the CTRL line to 1
#endif /* _MEM_REGISTERS_HEADER_ */

19
src/monitor_subroutines.c Normal file
View file

@ -0,0 +1,19 @@
#include "monitor_subroutines.h"
void sbrt_prntax(uint8_t msb, uint8_t lsb) {
__asm(
" jsr 0xF941\n"
:
: "Ka" (msb), "Kx" (lsb)
:
);
}
void sbrt_prbl2(uint8_t count) {
__asm(
" jsr 0xF94A\n"
:
: "Kx" (count)
:
);
}

85
src/monitor_subroutines.h Normal file
View file

@ -0,0 +1,85 @@
#ifndef _MONITOR_SUBROUTINES_HEADER_
#define _MONITOR_SUBROUTINES_HEADER_
#include <stdint.h>
typedef struct {
uint8_t key;
uint8_t ch;
} rdkey_res;
inline void sbrt_crout(void);
inline void sbrt_crout1(void);
void sbrt_prntax(uint8_t a, uint8_t x);
inline void sbrt_prblnk(void);
void sbrt_prbl2(uint8_t count);
inline void sbrt_bell(void);
inline rdkey_res sbrt_rdkey(void);
/*** ***/
#define COUT(a) (((void (*)(char))(0xFDED))(a))
#define COUT1(a) (((void (*)(char))(0xFDF0))(a))
#define COUTZ(a) (((void (*)(char))(0xFDF6))(a))
#define PRBYTE(a) (((void (*)(uint8_t))(0xFDDA))(a))
#define PRHEX(a) (((void (*)(uint8_t))(0xFDE3))(a))
#define WAIT(a) (((void (*)(uint8_t))(0xFCA8))(a))
#define BELL1() (((void (*)(uint8_t))(0xFBD9))(0x87))
#define SETINV() (((void (*)(void))(0xFE80))())
#define SETNORM() (((void (*)(void))(0xFE84))())
inline void sbrt_crout(void) {
__asm volatile(
" jsr 0xFD8E\n"
:
:
:
);
}
inline void sbrt_crout1(void) {
__asm volatile(
" jsr 0xFD8B\n"
:
:
:
);
}
inline void sbrt_prblnk(void) {
__asm volatile(
" jsr 0xF948\n"
:
:
: "a", "x"
);
}
inline void sbrt_bell(void) {
__asm volatile(
" jsr 0xFF3A\n"
:
:
: "a"
);
}
inline rdkey_res sbrt_rdkey(void) {
rdkey_res res;
uint8_t key;
uint8_t ch;
__asm volatile(
" jsr 0xFD0C\n"
: "=Ka"(key), "=Ky"(ch)
:
: "a", "y"
);
res.key = key;
res.ch = ch;
return res;
}
#endif /* _MONITOR_SUBROUTINES_HEADER_ */

12
src/preserve_zero_pages.s Normal file
View file

@ -0,0 +1,12 @@
.rtmodel version, "1"
.rtmodel core, "*"
;; External declarations
.section registers ; pseudo registers in zero page
.section zpsave, noinit
.pubweak __preserve_zp
__preserve_zp: ; This symbol meant to be required
.space 256
.require __preserve_zp_needed
.require __restore_zp_needed

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

125
src/tk2k_startup.s Normal file
View file

@ -0,0 +1,125 @@
.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 we'll save the zero page data
.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
;;; jmp exit
;;; ***************************************************************************
;;;
;;; __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

65
src/utility.c Normal file
View file

@ -0,0 +1,65 @@
#include "utility.h"
#include "mem_registers.h"
#include "monitor_subroutines.h"
#include "line_data.h"
void num_to_decbuf(uint16_t n, uint8_t len, uint8_t *buf) {
for(uint8_t idx = 0; idx < len; idx++) {
buf[idx] = n % 10;
n /= 10;
}
}
// https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte
uint8_t bit_reverse(uint8_t b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
// https://stackoverflow.com/questions/14009765/fastest-way-to-count-bits
uint8_t bit_count(uint8_t b) {
b = (b & 0x55) + (b >> 1 & 0x55);
b = (b & 0x33) + (b >> 2 & 0x33);
b = (b & 0x0f) + (b >> 4 & 0x0f);
return b;
}
void lfsr_init(uint16_t reg) {
*((uint16_t*)LFSR_REGISTER_ADDRESS) = 0xF00D;
}
uint16_t lfsr_update(void) {
uint16_t *lfsr = ((uint16_t*)LFSR_REGISTER_ADDRESS);
*lfsr ^= (*lfsr) >> 7;
*lfsr ^= (*lfsr) << 9;
*lfsr ^= (*lfsr) >> 13;
return *lfsr;
}
#define CRC8RDALLAS_POLY 0x31
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;
}

34
src/utility.h Normal file
View file

@ -0,0 +1,34 @@
#ifndef _UTILITY_HEADER_
#define _UTILITY_HEADER_
#include <stdint.h>
#define SCREEN_HEIGHT 192
#define SCREEN_WIDTH_BYTES 128
#define BYTES_PER_LINE 40
#define PEEKZ(a) (*(volatile uint8_t* __attribute__((zpage)))(a))
#define POKEZ(a, b) ((*(volatile uint8_t* __attribute__((zpage)))(a)) = b)
#define PEEK(a) (*(volatile uint8_t*)(a))
#define POKE(a, b) ((*(volatile uint8_t*)(a)) = b)
#define PEEKW(a) (*(volatile uint16_t*)(a))
#define POKEW(a, b) ((*(volatile uint16_t*)(a)) = b)
#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);
uint8_t calculate_crc8(uint8_t* data, uint8_t len);
#endif /* _UTILITY_HEADER_ */