From be70ff936e159cbe22ef3a96472cdc121902fe92 Mon Sep 17 00:00:00 2001 From: hkz Date: Mon, 1 Sep 2025 09:01:39 +0200 Subject: [PATCH] Add empty floppicator project --- .gitignore | 5 + Makefile | 48 +++ dsk/TK2048_AUTO_BRUN.dsk | Bin 0 -> 143360 bytes dsk/TK2048_AUTO_RUN.dsk | Bin 0 -> 143360 bytes linker-files/linker.scm | 21 ++ obj/empty | 0 out/empty | 0 src/charset.h | 24 ++ src/disk2.h | 46 +++ src/disk2.s | 726 ++++++++++++++++++++++++++++++++++++++ src/game_graphics.c | 418 ++++++++++++++++++++++ src/game_graphics.h | 34 ++ src/input.c | 25 ++ src/input.h | 19 + src/input_asm.s | 85 +++++ src/intro_main.c | 39 ++ src/line_data.c | 22 ++ src/line_data.h | 9 + src/main.c | 53 +++ src/mem_map.h | 14 + src/mem_registers.h | 48 +++ src/monitor_subroutines.c | 19 + src/monitor_subroutines.h | 85 +++++ src/preserve_zero_pages.s | 12 + src/sound.h | 16 + src/sound.s | 161 +++++++++ src/tk2k_startup.s | 125 +++++++ src/utility.c | 65 ++++ src/utility.h | 34 ++ 29 files changed, 2153 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 dsk/TK2048_AUTO_BRUN.dsk create mode 100644 dsk/TK2048_AUTO_RUN.dsk create mode 100644 linker-files/linker.scm create mode 100644 obj/empty create mode 100644 out/empty create mode 100644 src/charset.h create mode 100644 src/disk2.h create mode 100644 src/disk2.s create mode 100644 src/game_graphics.c create mode 100644 src/game_graphics.h create mode 100644 src/input.c create mode 100644 src/input.h create mode 100644 src/input_asm.s create mode 100644 src/intro_main.c create mode 100644 src/line_data.c create mode 100644 src/line_data.h create mode 100644 src/main.c create mode 100644 src/mem_map.h create mode 100644 src/mem_registers.h create mode 100644 src/monitor_subroutines.c create mode 100644 src/monitor_subroutines.h create mode 100644 src/preserve_zero_pages.s create mode 100644 src/sound.h create mode 100644 src/sound.s create mode 100644 src/tk2k_startup.s create mode 100644 src/utility.c create mode 100644 src/utility.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4aa462 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +obj/*.o +obj/*.lst +out/*.bin +out/*.hex +out/*.wav \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6793950 --- /dev/null +++ b/Makefile @@ -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 \ No newline at end of file diff --git a/dsk/TK2048_AUTO_BRUN.dsk b/dsk/TK2048_AUTO_BRUN.dsk new file mode 100644 index 0000000000000000000000000000000000000000..8562a983ace3c66604b7ea9bad6652ad05a78400 GIT binary patch literal 143360 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

8&#qfqUUWl zvWpuRHeSX(WyFFa>l3yzaxL>>R`88-TUkZeVjA5^zRSFxMNOmu3DvPcXz?yU> zU=BQi9^8Q~=#9E6h^$gqT}kx$3m~#8T{S$bbya}Ys58?#TUTvrY@I!C9!@*&$68=L zDao6k9&9j7KcW}j=j1I+@3WVMId<;c*-IMj4jew-V0Q>%NMoKWeZ5gnD`xBPbL9wE zL9WWHPw#7+9yX9`^BM&B%;e_0ru6k~)1v_YM_#i4us5%Tr)`@)Rz7B^IMhaVNWKPE8dl zQN1V*BHu@k7)A%lBGv0++T8k$1}HnCpqkEFxi`zrl_9XNz)V03MX!Q1sNNM1k`@)4 zwuf}6W&(-I>EUrK$q!YPX+gLhg>G5Qdh%1%ytI2v{;C?vaDq4nRn6xR z#{tzhct%t@qiu#Tc5xjfngc8{K%Qr!q{mFMIZQT>=kn4P;SF+!(d`ZG?X6aXN< zTuzU;&uPLdL>+Y|)j6BQeULpBiWRb_av-%iKJh@cNl%=zz?mFVl*&~igCWNSXU7?aibYw%$BjP^fNl&_Sel6ZZ zw&gr7Fn=g#pLoU6rF>;Xj^vDp`@r;7sK41{GFOD+Pvo~bPl{JAT}s}`Ie^qqbVm6! zS^I0yk<3wr6C$VT98Ga0a#Et~6ZP3p*r!dKh6=$1rnX7EI{`QgpGt#{6N%S3EH`eP7$C)iO`QugvX)i ziXx!+rKk^C36B}9$OXL(n1Rf#iU$REq#(ih=ZYS|c_Ou6;hVrkFEDvj;ZLNFx*kz% zoj@Fo@q}WV;4H)#b^WViM5~y^LWP7jBdWthaf1jdV>9S%Mx;ZKVcgr=Z$!B$k$+0gCBI7fXM+1g z`CTsmLU5iaziZ@Vf=Hs0`k`E$2!7P{7xMH8#8H_9fovd`%QlkrMvKtazgcdotl%2a4A494#2IWQDM)Ut5d@nne2# z9QhB~1B5o2#%l6&*@JX8eVB7|yh7X~3t=WYc@Sp;@M$AK*)iaZE;22V1~jo@*-L~k ztXECPbo{4Luu*e{`Ij-i_0W?yWUmm28*f4zuBRIvkPl^Vkf1YOb!EsSlriRq{|3!| zfpiwR7yT{gh!K5DG_B+k4EyuVwOMcd8PDFj(n&7G3;?*|i+IFRS!MlM8*ySXz>rhd zMt#37{W=*kqHBe3VRI_^k#;EY?`F4|?5V;*l;V_ZzGm_2d`Q>j|xA z^`svw4EP*Y@>#<#pLM>48Npfu-mi-HtFrR0FJtXNf~ye1DEPgPMF$Doq9LZq*5hNG z7V;&Hr*rcqgmf+~M^Cguhpd|Q6=Wt%0(atLM(EgB*YY1P|x986|)RAmP)q1 z)z#v3H!f&loec{bo9i)dZ^snsZ{?*GLzhkU8b-!0zKv`MDU*E5^|53q)BzWaH+ASivLQA{%}f8!DVxD kPR2<&375rXav5AYm&S=X5l2)xDy2%H%1#XN&;R^?0h&cDy8r+H literal 0 HcmV?d00001 diff --git a/dsk/TK2048_AUTO_RUN.dsk b/dsk/TK2048_AUTO_RUN.dsk new file mode 100644 index 0000000000000000000000000000000000000000..ceb5fa9e10059d70f62a168e99fe9ff06098dbb4 GIT binary patch literal 143360 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 literal 0 HcmV?d00001 diff --git a/linker-files/linker.scm b/linker-files/linker.scm new file mode 100644 index 0000000..67482fb --- /dev/null +++ b/linker-files/linker.scm @@ -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)) + )) + diff --git a/obj/empty b/obj/empty new file mode 100644 index 0000000..e69de29 diff --git a/out/empty b/out/empty new file mode 100644 index 0000000..e69de29 diff --git a/src/charset.h b/src/charset.h new file mode 100644 index 0000000..3006afb --- /dev/null +++ b/src/charset.h @@ -0,0 +1,24 @@ +#ifndef _CHARSET_HEADER_ +#define _CHARSET_HEADER_ + +#include + +// @ 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_ */ diff --git a/src/disk2.h b/src/disk2.h new file mode 100644 index 0000000..4238f06 --- /dev/null +++ b/src/disk2.h @@ -0,0 +1,46 @@ +#ifndef _DISK2_HEADER_ +#define _DISK2_HEADER_ + +#include + +#define DII_MAX_TRACK 96 +#define DECODING_MAPPING_TABLE_DEFAULT_ADDRESS 0x0356 +#define ENCODING_MAPPING_TABLE_DEFAULT_ADDRESS 0x9D56 +#define WRITE_SIXES_BUFFER_DEFAULT_ADDRESS 0x9C00 +#define WRITE_TWOS_BUFFER_DEFAULT_ADDRESS 0x9D00 + +void dii_generate_6bit_decoding_mapping_table(uint8_t* dest); +void dii_generate_6bit_encoding_mapping_table(uint8_t* dest); +void dii_encode_gcr62_data(uint8_t *src, uint8_t* dest6, uint8_t* dest2); +void dii_power_on(uint8_t io_offset, uint8_t drive_no); +void dii_power_off(uint8_t io_offset); +void dii_head_reposition(uint8_t io_offset, uint8_t cur_track, uint8_t dest_track); + +/** + * Patch the dii_write_sector routine to change the memory addresses used. + * buf2 and buf6 must start at the beginning of a memory page, nibtab must fit completely inside a page. + */ +void dii_patch_write(uint8_t* buf6, uint8_t* buf2, uint8_t *nibtab); + +/** + * This routine requires the decoding mapping table to be initialized beforehand at + * DECODING_MAPPING_TABLE_DEFAULT_ADDRESS, and uses space in page 3 as a buffer + * + * If the return value is 0, the read failed + */ +uint8_t dii_read_sector(uint8_t io_offset, uint8_t track, uint8_t sector, uint8_t *buffer, uint8_t aonly); + +/** + * Writes gcr 6+2 encoded data to the chosen sector. The track number is used to verify that we're on the correct + * track before writing. This routine does not move the head. + * The data must be pre-encoded. + * + * By default, it writes 86 bytes from the "twos" buffer located at 0x9C00, 256 bytes of "sixes" from the buffer + * at 0x9D00 and using a 63 entries translation table preloaded at 0x9C56. + * If these addresses need to be changes, use the dii_patch_write routine. + * + * If the return value is > 0, the write failed + */ +uint8_t dii_write_sector(uint8_t io_offset, uint8_t track, uint8_t sector); + +#endif /* _DISK2_HEADER_ */ diff --git a/src/disk2.s b/src/disk2.s new file mode 100644 index 0000000..f29f44a --- /dev/null +++ b/src/disk2.s @@ -0,0 +1,726 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern _Vfp + .extern _Vsp + .extern _Zp + +;;; I/O offsets +DII_BASE: .equ 0xC080 +PH0_OFF: .equ DII_BASE + 0x00 +PH0_ON: .equ DII_BASE + 0x01 +PH1_OFF: .equ DII_BASE + 0x02 +PH1_ON: .equ DII_BASE + 0x03 +PH2_OFF: .equ DII_BASE + 0x04 +PH2_ON: .equ DII_BASE + 0x05 +PH3_OFF: .equ DII_BASE + 0x06 +PH3_ON: .equ DII_BASE + 0x07 +PWR_DWN: .equ DII_BASE + 0x08 +PWR_UP: .equ DII_BASE + 0x09 +SEL_D1: .equ DII_BASE + 0x0A +SEL_D2: .equ DII_BASE + 0x0B +READ_SW: .equ DII_BASE + 0x0C +WRITE_SW: .equ DII_BASE + 0x0D +CLEAR_SW: .equ DII_BASE + 0x0E +SHIFT_SW: .equ DII_BASE + 0x0F + +;;; Monitor routines +MON_WAIT .equ 0xFCA8 + +;;; Memory addresses +CONV_TABLE .equ 0x0356 ; 6+2 conversion table generated by BOOT0 +PAGE3_BUF .equ 0x0300 + +;;; Summing up the calling convention as... +;;; Parameters are passed in register A and pseudo registers _Zp[0-7]. +;;; Registers X, Y and pseudo registers 0–23 are destroyed by a function call. +;;; Pseudo registers 24–47 must be preserved. + + .section code,text + +;;; dii_generate_6bit_decoding_mapping_table: +;;; Generates a 6+2 decoding table at the chosen address +;;; Parameters: +;;; - address [_Zp[0], _Zp[1]]: Address for the generation of the table +;;; +;;; Returns: Nothing +dii_generate_6bit_decoding_mapping_table: +T_REGX$: .equ _Zp+4 +T_REGY$: .equ _Zp+3 +T_BITS$: .equ _Zp+2 +P_DESTH$: .equ _Zp+1 +P_DESTL$: .equ _Zp+0 + + ldy #0x00 + ldx #0x03 + +CreateTableLoop$: + stx zp:T_BITS$ + txa + asl a ; Arithmetic Shift Left, will put the high bit in carry + bit zp:T_BITS$ ; Check of the overlap for the shifted value. If two one overlaps it means that there are two adjacent 1s + beq reject$ ; Nope, refuse this number + ora zp:T_BITS$ ; Merge shifted and non-shifted number + eor #0xff ; Invert the value, we'll look for adjacent zeros (which have now become 1s) + and #0x7e ; Discard msb and lsb, as one is mandatory and the other falsely generated with the shift +check_dub0$:bcs reject$ ; Branch on C = 1. Initial bhi bit set or adjacent 0 bits set + lsr a ; Shift right. lower bit into carry + bne check_dub0$ ; Check all the bits until we're left with a 0 + tya ; We have a good one. Store it in the table + sty zp:T_REGY$ ; Save y + stx zp:T_REGX$ ; Save x + ldy zp:T_REGX$ ; Y <- X + sta (zp:P_DESTL$),y ; Bytes that reference this will have highest bit set, so this'll be accessed using CONV_TABLE-128 + ldy zp:T_REGY$ ; Recover y + iny +reject$: inx ; Next candidate + bpl CreateTableLoop$ + + rts + +;;; dii_generate_6bit_encoding_mapping_table: +;;; Generates a 6bit mapping table to be used with GCR 6+2 +;;; Parameters: +;;; - address [_Zp[0], _Zp[1]]: Address for the generation of the table +;;; +;;; Returns: Nothing +dii_generate_6bit_encoding_mapping_table: +T_BITS$: .equ _Zp+2 +P_DESTH$: .equ _Zp+1 +P_DESTL$: .equ _Zp+0 + + ldy #0x00 + ldx #0x03 + +CreateTableLoop$: + stx zp:T_BITS$ + txa + asl a + bit zp:T_BITS$ + beq reject$ + ora zp:T_BITS$ + eor #0xff + and #0x7e +check_dub0$:bcs reject$ + lsr a + bne check_dub0$ + txa + ora #0x80 + sta (zp:P_DESTL$),y + iny +reject$: inx ; Next candidate + bpl CreateTableLoop$ + + rts + +;;; dii_encode_gcr62_data: +;;; Split 256 bytes of data into 256 + 86 6-bit nibbles +;;; Parameters: +;;; - source [_Zp[0], _Zp[1]]: Address for the 256 bytes data source +;;; - buf6 [_Zp[2], _Zp[3]]: Address for the destination buffer that will contain 256 "sixes" nibble entries +;;; - buf2 [_Zp[4], _Zp[5]]: Address for the destination buffer that will contain 86 "twos" nibble entries +;;; +;;; Returns: Nothing +;;; +;;; We will go through the 86 bytes three times (which means a total of 258 runs). Note that we have a source +;;; of 256 bytes, plus, the result will have to be 256+86, so, in the "twos" buffer we'll have one entry containing +;;; (86*3)-256 = 2 spurious (duplicated entries); +;;; +dii_encode_gcr62_data: +T_REGY$: .equ _Zp+7 +T_BITS$: .equ _Zp+6 +P_DEST2H$: .equ _Zp+5 +P_DEST2L$: .equ _Zp+4 +P_DEST6H$: .equ _Zp+3 +P_DEST6L$: .equ _Zp+2 +P_SRCH$: .equ _Zp+1 +P_SRCL$: .equ _Zp+0 + + ldx #0x00 ; This will address the current byte of the "twos" + ldy #0x02 ; We'll use this as an offset into the source (and sixes) buffer. Note that this means the first call + ; will have BASE+1, the second BASE+0, while the third BASE+255, and will decrease from there + ; onward. Here are the two spurious entries. +prenib1$: dey + sty zp:T_REGY$ ; Save y + txa ; Copy x to a + tay ; then a to y ---- x -> y + lda (P_DEST2L$),y ; Recover the current 'two' + sta zp:T_BITS$ ; Save it on a temp register + ldy zp:T_REGY$ ; Recover y + + lda (zp:P_SRCL$),y ; Get current byte from the source buffer + lsr a ; Put the two least significant bits into the corrisponding byte of the "twos" + rol zp:T_BITS$ + lsr a + rol zp:T_BITS$ ; Move two bits in + sta (zp:P_DEST6L$),y; The remaining six bites will go in the 'sixes' buffer + sty zp:T_REGY$ ; Save y + txa ; Move X to Y + tay + lda zp:T_BITS$ ; Recover the updated 'two' + sta (P_DEST2L$),y ; Save the updated 'two' + ldy zp:T_REGY$ ; Recover y + inx + cpx #86 + bcc prenib1$ ; We'll repeat this 86 times + ldx #0 + tya + bne prenib1$ ; After decreasing y 258 times, we'll end up with 0 here (2 - 86 - 86 - 86 = -256) and go on + + ldy #85 +prenib2$: lda (zp:P_DEST2L$),y + and #0x3f + sta (zp:P_DEST2L$),y + dey + bpl prenib2$ + + rts + +;;; dii_power_on: +;;; Selects and powers on a disk drive +;;; Parameters: +;;; - offset [A]: offset from the base address (DII_BASE) to access the Disk II interface +;;; - drive [_Zp[0]]: Drive number, 0 for first drive, 1 for the next. Only the first bit will be considered +;;; +;;; Returns: Nothing +dii_power_on: +P_OFFSET$: .equ _Zp+1 +P_DRVNO$: .equ _Zp+0 + + and #0x70 ; Mask the offset with 0x70, so we keep the bits relevant for "slot" selection + sta zp:P_OFFSET$ ; Save the cleaned offset, we'll use it later to calculate the offset to turn on the drive + + tax + lda CLEAR_SW,x ; Enter read mode + lda READ_SW,x + + lda #0x01 ; Keep only the lsb of the drive number parameter + and zp:P_DRVNO$ + ora zp:P_OFFSET$ ; Combine the result with the "slot" offset + tax + lda SEL_D1,x ; Select the proper drive + + lda zp:P_OFFSET$ + tax + lda PWR_UP,x ; Drive Power up + + jsr __short_wait ; Wait for the driver to power on and the motor to spin up + rts + +;;; dii_power_off: +;;; Selects and powers on a disk drive +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; +;;; Returns: Nothing +dii_power_off: + and #0x70 ; Mask the offset with 0x70, so we keep the bits relevant for "slot" selection + tax + lda PWR_DWN,x ; Drive Power down + + rts + +;;; dii_head_reposition: +;;; Repositions the head according to the current track and destination track. +;;; Keep in mind that the parameters to this function are taking "phase movements" into consideration, not the +;;; DOS track number. +;;; There is a correspondance of 1:2 between DOS tracks and phase movements, which means that TWO phase movements +;;; will shift ONE track. +;;; With standard DOS, all EVEN numbered tracks (0 included) are positioned under "phase 0" of the head, all ODD +;;; numbered tracks will be positioned under "phase 2". Moving to phase 1 or 3 will get us to half-tracks. +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; - curtrack [_Zp[0]]: Current track number +;;; - desttrack [_Zp[1]]: Desired destination track number +;;; +;;; Returns: Nothing +dii_head_reposition: +P_OFFSET$: .equ _Zp+2 +P_DTRACK$: .equ _Zp+1 +P_CTRACK$: .equ _Zp+0 + + and #0x70 + sta zp:P_OFFSET$ ; Save the slot offset into a zp register +H_MNLOOP$: lda zp:P_CTRACK$ ; Check and branch depending on relationship between current and destination track + cmp zp:P_DTRACK$ + beq H_ALLDONE$ + bcc H_MOVUP$ + bcs H_MOVDOWN$ + +H_MOVDOWN$: dec zp:P_CTRACK$ ; Prepare to move down a track + jmp H_DOWORK$ + +H_MOVUP$: inc zp:P_CTRACK$ ; Prepare to move up a track + +H_DOWORK$: lda zp:P_CTRACK$ + and #0x03 ; We're going to keep only the 3 least significant bits of the current track + asl a ; Shifting them left, now we're using the 4 msb of the byte + ; This multiplication is necessary because the phase on (or off) registers + ; are spaced by two between each other, and this calculates the offset. See later. + ora zp:P_OFFSET$ ; And now combine the slot number with the track value we calculated in the previous step... + tay ; a -> y + lda PH0_ON,y ; We are offsetting the base to reach for both the slot number and the proper phase + lda #0x56 + jsr MON_WAIT ; Call the monitor wait routine + lda PH0_OFF,y ; Now turn the phase off + jmp H_MNLOOP$ ; And go back checking the status of the relationship between current and destination tracks +H_ALLDONE$: rts ; We're done + + +__short_wait: + lda #0xEF + sta zp:_Zp ; We'll use the first two zp registers to hold a counter + lda #0xD8 + sta zp:_Zp+1 +MWAITA$: ldy #0x12 +MWAITB$: dey + bne MWAITB$ + inc zp:_Zp + bne MWAITA$ + inc zp:_Zp+1 + bne MWAITA$ + rts + + +;;; dii_patch_write: +;;; Modifies the addresses in the dii_write_sector routine to change where data is read +;;; Parameters: +;;; - buf6 [_Zp[0] and _Zp[1]]: Address for the "sixes" buffer. 256 bytes in length. Must start at the beginning of a page+ +;;; - buf2 [_Zp[2] and _Zp[3]]: Address for the "twos" buffer. 86 bytes in length. Must start at the beginning of a page +;;; - nibtab [_Zp[4] and _Zp[5]]: Address for the translation table for nibbles. 63 bytes in length. +;;; +;;; Returns: nothing +;;; +dii_patch_write: +P_NIBTH$: .equ _Zp+5 +P_NIBTL$: .equ _Zp+4 +P_BUF2H$: .equ _Zp+3 +P_BUF2L$: .equ _Zp+2 +P_BUF6H$: .equ _Zp+1 +P_BUF6L$: .equ _Zp+0 + + ;;; Common changes + lda #0x00 ; Make sure we start at the beginning of the page + sta _b2add01+1 + sta _b2add02+1 + sta _b6add01+1 + sta _b6add02+1 + + ;;; Change the address for buf2 + lda zp:P_BUF2H$ ; Overwrite the most significant byte + sta _b2add01+2 + sta _b2add02+2 + + dec zp:P_BUF2H$ ; Make sure we save this address for buf2 - 1, needed for page crossing + lda zp:P_BUF2H$ + sta _b2madd01+2 + + lda #0xff + sta _b2madd01+1 + + ;;; Change the address for buf6 + lda zp:P_BUF6H$ ; Overwrite the most significant byte + sta _b6add01+2 + sta _b6add02+2 + + ;;; Nibble table address + lda zp:P_NIBTL$ + sta _nibadd01+1 + sta _nibadd02+1 + sta _nibadd03+1 + + lda zp:P_NIBTH$ + sta _nibadd01+2 + sta _nibadd02+2 + sta _nibadd03+2 + + rts + +;;; Timing critical code here, must not cross page boundary + + .section dii_critical_rd_code,text,noreorder + +;;; dii_read_sector: +;;; Reads and decode a 256 byte sector from the Disk II interface. +;;; It is shamelessly copied and then slightly adaptet from the BOOT0 code in the Disk II interface PROM. +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; - track [_Zp[0]]: Search for this track +;;; - sector [_Zp[1]]: Search for the sector specified in the low nibble of this byte. If high nibble is != 0, do not read the data, and return after address header +;;; - destination [_Zp[2] and _Zp[3]]: Destination address for a 256b buffer for the data +;;; - destination [_Zp[4]]: If != 0, only the address data will be read +;;; +;;; Returns: Nothing +insta_return: + rts +dii_read_sector: +RETRIES$: .equ 0xff +T_HCHKS$: .equ _Zp+8 +T_BITS$: .equ _Zp+7 +T_RETRYC$: .equ _Zp+6 +P_OFFSET$: .equ _Zp+5 +P_ADDRONLY$:.equ _Zp+4 +P_DESTH$: .equ _Zp+3 +P_DESTL$: .equ _Zp+2 +P_SECTOR$: .equ _Zp+1 +P_TRACK$: .equ _Zp+0 + + ; Save the slot offset, properly cleaned, into a zp register + and #0x70 + sta zp:P_OFFSET$ + tax + + ; Load the retry counter + lda #RETRIES$ ; Set up the retry counter + sta zp:T_RETRYC$ + + ;;; Address header is the sequence (D5 AA 96). Data header is (D5 AA AD). +RdSect$: + dec zp:T_RETRYC$ + beq insta_return ; Out of retries + + clc ; Clear carry. C=0 will have the code look for an address, C=1 for data +RdSect_C$: php ; Push state on stack + +RdByte1$: lda READ_SW,x + bpl RdByte1$ ; Read the first byte +ChkD5$: eor #0xD5 ; An xor of 0xD5 with 0xD5 will get ourselves a 0, something else otherwise + bne RdByte1$ ; Not the first byte, keep searching + +RdByte2$: lda READ_SW,x + bpl RdByte2$ ; Read the second byte + cmp #0xAA + bne ChkD5$ ; Not the second byte, check if it's the first... + nop + +RdByte3$: lda READ_SW,x + bpl RdByte3$ + cmp #0x96 ; Check if third byte is 0x96 + beq FoundAddr$ ; We found an address! + + plp ; Pull the status byte + bcc RdSect$ ; We did not want to read data (C=0), so we start searching again + eor #0xAD ; We did want to read data! So check if it's the last byte of a data sequence + beq FoundData$ ; It is, read the data! (A = 0 at this point) + + ;;; ADDRESS: Data in the address header is stored in 4+4 GCR encoding +FoundAddr$: lda #0x00 ; Clear A + sta zp:T_HCHKS$ ; Save 0 to the temporary checksum in ZP + ldy #0x05 ; Dummy write (+5), Volume # (+4), Track # (+3), Sector # (+2), checksum (+1), in decreasing order. We'll ignore the epilogue bytes +hdr_loop$: sta (zp:P_DESTL$),y ; We'll use the destination buffer, temporarily... + eor zp:T_HCHKS$ ; Running and saving the checksum + sta zp:T_HCHKS$ + +RdByteA1$: lda READ_SW,x + bpl RdByteA1$ + rol a ; Rotate the bits left (the first byte is odd encoded 1 b7 1 b5 1 b3 1 b1). + sta zp:T_BITS$ +RdByteA2$: lda READ_SW,x + bpl RdByteA2$ ; Read the second byte (the second byte is even encoded 1 b6 1 b4 1 b2 1 b0) + and zp:T_BITS$ ; Merge with the first byte + dey + bne hdr_loop$ ; Are we at the last element? + +;RdByteA3$: lda READ_SW,x ; Ignore the epilogue bytes... +; bpl RdByteA3$ +;ChkDE$ eor #0xDE +; bne RdByteA3$ +; +;RdByteA4$: lda READ_SW,x +; bpl RdByteA4$ +; eor #0xAA +; bne ChkDE$ + + plp ; Pull the status byte + + lda zp:T_HCHKS$ ; Verify if checksum is bad. + bne RdSect$ ; If so, go back searching for an address... + + ldy #2 ; Point to the saved sector # in memory + lda (zp:P_DESTL$),y + cmp zp:P_SECTOR$ ; Check if this is the sector we want + bne RdSect$ ; If not, go back searching for an address... + iny ; Point to the saved track # in memory + lda (zp:P_DESTL$),y + cmp zp:P_TRACK$ ; Check if it is the track we want + bne RdSect$ ; If not, go back searching for an address... + + lda zp:P_ADDRONLY$ + bne insta_return + bcs RdSect_C$ ; Yes, go back reading the sector without clearing the Carry, which will be 1, so we'll read data at next run + + ;;; DATA. 6+2 encoded sector data + ;;; The values we'll read are encoded in a way that they'll have the range 0x96 - 0xff + ;;; A will be 0 on entry here + ;;; Read the 2s! +FoundData$: ldy #86 ; We'll read 86 bytes of data in 0x300-0x355 +Read2Loop$: sty zp:T_BITS$ ; Each byte of these will contain 3 sets of "2 bits" encoded inside +RdByteD1$: ldy READ_SW,x + bpl RdByteD1$ + eor CONV_TABLE-128,y; Note that we have 0x02d6 + 0x96 = 0x36c, our first table entry for the conversion. Also, note we're doing a rolling xor + ldy zp:T_BITS$ + dey + sta PAGE3_BUF,y ; Store the result of the 2s decoding in page 3 + bne Read2Loop$ ; Loop until we read them all + + ;;; Read the 6s! +Read6Loop$: sty zp:T_BITS$ ; y should be 255 at this point, which means we'll read 256 bytes +RdByteD2$: ldy READ_SW,x + bpl RdByteD2$ + eor CONV_TABLE-128,y; Going on with the rolling xor + ldy zp:T_BITS$ + sta (zp:P_DESTL$),y ; Store the result on the destination buffer + iny + bne Read6Loop$ ; Keep reading and converting 256 6s + + ;;; Verify the checksum! +RdByteD3$: ldy READ_SW,x + bpl RdByteD3$ + eor CONV_TABLE-128,y; Finally check if the checksum matches... + bne EndRdSect$ ; No, probably something got corrupted. Retry the read... + + ;;; Decode 6+2 data. The high 6 bits of every byte are already in place. We need to get the remaining 2 in + ldy #0x00 +init_x$: ldx #86 ; We'll go through the 2-bit pieces three times +decod_loop$:dex ; Decrement x + bmi init_x$ ; Branch on N = 1 (negative set), make sure we roll x. 0x02ff -> 0x0355 as it is an offset + lda (zp:P_DESTL$),y ; For each byte in the data buffer + lsr PAGE3_BUF,x ; grab the low two bits from the 2s at 0x0300 - 0x0355 (we'll shift them in the carry) + rol a ; and roll them in the least significant bits of the byte (and roll the carry in!) + lsr PAGE3_BUF,x + rol a + sta (zp:P_DESTL$),y ; Store the decoded byte back + iny + bne decod_loop$ + + ;;; Done! + lda zp:T_RETRYC$ ; Recover the number of retries + + rts + ;;; We're too far off the beginning, we cannot do a quick branch with an offset +EndRdSect$: + jmp RdSect$ + +;;; Timing critical code here, must not cross page boundary + + .section dii_critical_wr_code,text,noreorder + + +;;; dii_write_sector: +;;; Writes a 342 byte from the pointed GCR 6+2 data buffers. +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; - track [_Zp[0]]: Check that we're writing on this track +;;; - sector [_Zp[1]]: Write on this sector +;;; +;;; Returns: 0 for a completed write, 0x10 for a write protected disk +;;; +;;; MAKE SURE THE FOLLOWING ALL FITS INSIDE A SINGLE PAGE!!! +;;; Note that the addresses used in this function will modified by calling dii_patch_write +;;; +wr_err_return: + lda READ_SW,x ; Back into read mode + lda #0x10 ; This will be the error code + rts +dii_write_sector: +RETRIES$: .equ 0xff + ; Note that the following buffers MUST be at the start of a page for write timing to be respected +__BUF2S: .equ 0x9D00 ; Default to this, it'll be overwritten when modifying this code. 86 entries. +__BUF6S: .equ 0x9C00 ; 256 entries. +__NIBTAB: .equ 0x9D56 ; This will contain the translation table for writing nibbles. 63 entries. +__OFFSETP6 .equ 0x0678 + +__T_VOL: .equ _Zp+9 +__T_TRK: .equ _Zp+8 +__T_SEC: .equ _Zp+7 +__T_CHK: .equ _Zp+6 ; 7, 8, 9 also taken +__T_HCHKS: .equ _Zp+5 +__T_HOLDNIB:.equ _Zp+4 +__T_RETRYC: .equ _Zp+3 +__P_OFFSET: .equ _Zp+2 +__P_SECTOR: .equ _Zp+1 +__P_TRACK: .equ _Zp+0 + ; Save the slot offset, properly cleaned, into a zp register and on page 6 + and #0x70 + sta zp:__P_OFFSET + sta __OFFSETP6 + tax + + lda #RETRIES$ ; Set up the retry counter + sta zp:__T_RETRYC + + ;;; Search for the address prologue (D5 AA 96) +RdSect$: + dec zp:__T_RETRYC + beq wr_err_return ; Out of retries + + ;;; Read the address prologue +RdByte1$: lda READ_SW,x + bpl RdByte1$ ; Read the first byte +ChkD5$: cmp #0xD5 + bne RdByte1$ ; Not the first byte, keep searching + nop + +RdByte2$: lda READ_SW,x + bpl RdByte2$ + cmp #0xaa + bne ChkD5$ + ldy #0x03 + +RdByte3$: lda READ_SW,x + bpl RdByte3$ + cmp #0x96 + bne ChkD5$ + + ;;; Read the address fields and store them in ZP + lda #0x00 +RdAddrFld$: sta zp:__T_HCHKS +RdByte4$: lda READ_SW,x + bpl RdByte4$ + rol a + sta zp:__T_HOLDNIB +RdByte5$: lda READ_SW,x + bpl RdByte5$ + and zp:__T_HOLDNIB + sta __T_CHK,y + eor zp:__T_HCHKS + dey + bpl RdAddrFld$ + + tay + bne RdSect$ ; Checksum error, retry + + ;;; Read the bit-slip nibbles +RdByte6$: lda READ_SW,x + bpl RdByte6$ + cmp #0xde + bne RdSect$ + nop + +RdByte7$: lda READ_SW,x + bpl RdByte7$ + cmp #0xaa + bne RdSect$ + nop + + ;;; Check we're on the correct track + lda __T_TRK + cmp zp:__P_TRACK + bne wr_err_return + + ;;; Check we're on the correct sector, or retry the read + lda __T_SEC + cmp zp:__P_SECTOR + bne RdSect$ + + lda WRITE_SW,x ; Combination of these two in sequence checks for write protect + lda CLEAR_SW,x ; and enables the write sequence. + bmi wr_err_return ; Write protected disk. Branch on N = 1 +_b2add01: + lda __BUF2S ; Get the first 2-bit encode byte + sta zp:__T_HOLDNIB ; and save it for future use. + + ;;; Write a 5-sync gap between the address epilogue and the data prologue + ;;; Write of the sync bytes will take 36 cycles + lda #0xff ; Sync byte + sta SHIFT_SW,x ; Write 1 sync byte + ora READ_SW,x + pha ; (3 cycles) + pla ; (4 cycles) + nop ; (2 cycles) + ldy #4 ; (2 cycles), plus load a counter to write 4 more syncs +__wr4ff: pha ; (3 cycles) + pla ; (4 cycles) + jsr __write2 ; (12 cycles before, 6 after) + dey ; (2 cycles) + bne __wr4ff ; (2 or 3 cycles) + + ;;; Write the data prologue (D5 AA AD) + lda #0xd5 ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xaa ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xad ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + + ;;; Convert & write contents of the buffers to the disk! + ;;; Start with the 2-bits + ;;; + ;;; At this point, we're 10 cycles after the previous write + ;;; Writes will take 32 cycles + tya + ldy #0x56 ; We'll use this to count back from the beginning of the "twos" buffer + bne __doeor ; This will ALWAYS be taken +_b2add02: +__getnibl: lda __BUF2S,y +_b2madd01: +__doeor: eor __BUF2S - 1,y ; Note that this will always cross a page boundary, and thus be 5 cycles!! + tax ; Get index for translation table +_nibadd01: + lda __NIBTAB,x ; Get byte from translation table + ldx zp:__P_OFFSET + sta WRITE_SW,x + lda READ_SW,x + dey + bne __getnibl + + ;;; Proceed with the 6-bits + ;;; y will be 0 at this point + lda zp:__T_HOLDNIB + nop +_b6add01: +__scndeor: eor __BUF6S,y + tax ; Get index for translation table +_nibadd02: + lda __NIBTAB,x ; Get byte from translation table + ldx __OFFSETP6 ; Retrieve the slot offset from page 6 + sta WRITE_SW,x ; Write "sixes", byte 87 and onward + lda READ_SW,x +_b6add02: + lda __BUF6S,y ; Read the next byte + iny ; 00 to ff + bne __scndeor + + ;;; Convert and write the checksum byte + tax ; Get the index on the table +_nibadd03: + lda __NIBTAB,x + ldx zp:__P_OFFSET + jsr __write3 ; Write the checksum + + ;;; Write the epilogue (DE AA EB) + lda #0xde ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xaa ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xeb ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + + ;;; Write a sync byte + lda #0xff + jsr __write1 ; (14 cycles before, 6 after) + lda CLEAR_SW,x + lda READ_SW,x ; Back into read mode + lda #0x00 + rts + +__write1: + clc ; (2 cycles) +__write2: + pha ; (3 cycles) + pla ; (4 cycles) +__write3: + sta WRITE_SW,x ; (5 cycles) Load latch from data bus + ora READ_SW,x ; (4 cycles) and write byte. Must have previously selected SHIFT_SW + rts ; (6 cycles) + +;;; Declaration of public symbols + .public dii_generate_6bit_decoding_mapping_table + .public dii_generate_6bit_encoding_mapping_table + .public dii_encode_gcr62_data + .public dii_power_on + .public dii_power_off + .public dii_head_reposition + .public dii_patch_write + .public dii_read_sector + .public dii_write_sector diff --git a/src/game_graphics.c b/src/game_graphics.c new file mode 100644 index 0000000..9aa1818 --- /dev/null +++ b/src/game_graphics.c @@ -0,0 +1,418 @@ +#include "game_graphics.h" + +#include + +#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; + } + } +} diff --git a/src/game_graphics.h b/src/game_graphics.h new file mode 100644 index 0000000..72fcf99 --- /dev/null +++ b/src/game_graphics.h @@ -0,0 +1,34 @@ +#ifndef _GAME_GRAPHICS_HEADER_ +#define _GAME_GRAPHICS_HEADER_ + +#include + +#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_ */ diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..3278088 --- /dev/null +++ b/src/input.c @@ -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; +} diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..614bcbf --- /dev/null +++ b/src/input.h @@ -0,0 +1,19 @@ +#ifndef _INPUT_HEADER_ +#define _INPUT_HEADER_ + +#include + +#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_ */ diff --git a/src/input_asm.s b/src/input_asm.s new file mode 100644 index 0000000..0d64445 --- /dev/null +++ b/src/input_asm.s @@ -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 diff --git a/src/intro_main.c b/src/intro_main.c new file mode 100644 index 0000000..4595ad0 --- /dev/null +++ b/src/intro_main.c @@ -0,0 +1,39 @@ +#include +#include + +#include + +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "shared_page.h" + +// External initialization requirements +#pragma require __preserve_zp +#pragma require __data_initialization_needed + +static shared_page_data *shared_page = (shared_page_data*)SHARED_PAGE; + +void main(void) { + uint8_t wait_count = 0x40; + + // Initialize the register for LFSR + lfsr_init(0xF00D); + + // Clear the memory used to pass parameters to the next module + memset((void*)(shared_page->module_data), 0, MODULE_DATA_SIZE); + + // TODO: Detect expansion hardware, and choose what to load according to that + + // Delay a bit + while(wait_count--) WAIT(0xFF); + + // Clear the display memory + memset((void*)DISPLAY_PAGE_2, 0, DISPLAY_PAGE_SIZE); + memset((void*)DISPLAY_PAGE_1, 0, DISPLAY_PAGE_SIZE); + + shared_page->master_command = MASTER_COMMAND_NONE; + shared_page->next_module_idx = 3; // DLOG module is next! + + return; +} diff --git a/src/line_data.c b/src/line_data.c new file mode 100644 index 0000000..992b832 --- /dev/null +++ b/src/line_data.c @@ -0,0 +1,22 @@ +#include + +#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 +}; diff --git a/src/line_data.h b/src/line_data.h new file mode 100644 index 0000000..219744e --- /dev/null +++ b/src/line_data.h @@ -0,0 +1,9 @@ +#ifndef _LINE_DATA_HEADER_ +#define _LINE_DATA_HEADER_ + +#include +#include "utility.h" + +extern const uint16_t line_offset_map[SCREEN_HEIGHT]; + +#endif /* _LINE_DATA_HEADER */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..82c7d66 --- /dev/null +++ b/src/main.c @@ -0,0 +1,53 @@ +#include + +#include + +#include + +#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; +} diff --git a/src/mem_map.h b/src/mem_map.h new file mode 100644 index 0000000..1f3aaac --- /dev/null +++ b/src/mem_map.h @@ -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_ */ diff --git a/src/mem_registers.h b/src/mem_registers.h new file mode 100644 index 0000000..5ca2394 --- /dev/null +++ b/src/mem_registers.h @@ -0,0 +1,48 @@ +#ifndef _MEM_REGISTERS_HEADER_ +#define _MEM_REGISTERS_HEADER_ + +#include + +#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_ */ diff --git a/src/monitor_subroutines.c b/src/monitor_subroutines.c new file mode 100644 index 0000000..f1a54cf --- /dev/null +++ b/src/monitor_subroutines.c @@ -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) + : + ); +} diff --git a/src/monitor_subroutines.h b/src/monitor_subroutines.h new file mode 100644 index 0000000..03cd408 --- /dev/null +++ b/src/monitor_subroutines.h @@ -0,0 +1,85 @@ +#ifndef _MONITOR_SUBROUTINES_HEADER_ +#define _MONITOR_SUBROUTINES_HEADER_ + +#include + +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_ */ diff --git a/src/preserve_zero_pages.s b/src/preserve_zero_pages.s new file mode 100644 index 0000000..c600175 --- /dev/null +++ b/src/preserve_zero_pages.s @@ -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 \ No newline at end of file diff --git a/src/sound.h b/src/sound.h new file mode 100644 index 0000000..922cf0f --- /dev/null +++ b/src/sound.h @@ -0,0 +1,16 @@ +#ifndef _SOUND_HEADER_ +#define _SOUND_HEADER_ + +#include + +#define SND_TAP() (snd_beep_lower(0x08, 0xFF)) + +void snd_beep(uint8_t duration, uint8_t pitch); +void snd_beep_lower(uint8_t duration, uint8_t pitch); + +void snd_start(void); +void snd_sad_scale(void); +void snd_festive(void); +void snd_mod_button(void); + +#endif /* _SOUND_HEADER_ */ diff --git a/src/sound.s b/src/sound.s new file mode 100644 index 0000000..5adb1a8 --- /dev/null +++ b/src/sound.s @@ -0,0 +1,161 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern _Vfp + .extern _Vsp + .extern _Zp + +;;; I/O ports +IO_SPK: .equ 0xC030 + +;;; Monitor routines +MON_WAIT .equ 0xFCA8 + +;;; snd_beep +;;; Generates a sound of configurable duration and pitch +;;; See https://github.com/tilleul/apple2/blob/master/2liners/the%20art%20of%202-liners/SCRN_PLOT_your_sound_routine.md +;;; Parameters: +;;; - duration [A]: duration of the sound +;;; - pitch [_Zp[0]]: sound pitch +;;; +;;; Returns: Nothing +snd_beep: +P_PITCH$: .equ _Zp+0 + tax ; Move accumulator with duration into register x +-- ldy zp:P_PITCH$ ; Load the pitch from zp + lda IO_SPK ; Toggle the speaker +- dey + bne - + dex ; Decrement the time left + bne -- ; Back at loading the pitch if we need to go on + rts + +snd_beep_lower: +P_PITCH$: .equ _Zp+0 + tax ; Move accumulator with duration into register x +-- ldy zp:P_PITCH$ ; Load the pitch from zp + lda IO_SPK ; Toggle the speaker +- dey + nop + nop + nop + bne - + dex ; Decrement the time left + bne -- ; Back at loading the pitch if we need to go on + rts + +snd_mod_button: +P_PITCH$: .equ _Zp+0 + lda #0xAA + sta zp:P_PITCH$ + lda #0x25 + jsr snd_beep_lower + lda #0xCC + sta zp:P_PITCH$ + lda #0x0A + jsr snd_beep_lower + rts + +snd_start: +P_PITCH$: .equ _Zp+0 + lda #0xCC + sta zp:P_PITCH$ + lda #0x99 + jsr snd_beep_lower + lda #0xAA + sta zp:P_PITCH$ + lda #0x80 + jsr snd_beep_lower + lda #0x88 + sta zp:P_PITCH$ + lda #0xff + jsr snd_beep_lower + rts + +snd_sad_scale: +T_IDX$: .equ _Zp+1 +P_PITCH$: .equ _Zp+0 + ldy #6 +- lda __sad_scale_pitches,y + sta zp:P_PITCH$ + lda #0x90 + sty zp:T_IDX$ + jsr snd_beep_lower + ldy zp:T_IDX$ + dey + bpl - + rts + +__sad_scale_pitches: + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xCC + .byte 0xAA + .byte 0x88 + .byte 0x88 + +snd_festive: +T_IDX$: .equ _Zp+1 +P_PITCH$: .equ _Zp+0 + ldy #10 +- lda __festive_pitches,y + sta zp:P_PITCH$ + lda __festive_duration,y + sty zp:T_IDX$ + jsr snd_beep + ldy zp:T_IDX$ + + lda __festive_wait,y + jsr MON_WAIT ; Call the monitor wait routine + + dey + bpl - + rts + +__festive_pitches: + .byte 0xEE + .byte 0xCC + .byte 0xAA + .byte 0x99 + .byte 0xAA + .byte 0xCC + .byte 0xCC + .byte 0x88 + .byte 0xAA + .byte 0xCC + .byte 0xCC + +__festive_duration: + .byte 0xFF + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + +__festive_wait: + .byte 0x70 + .byte 0x70 + .byte 0x70 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + +;;; Declaration of public symbols + .public snd_beep + .public snd_beep_lower + .public snd_sad_scale + .public snd_festive + .public snd_mod_button + .public snd_start diff --git a/src/tk2k_startup.s b/src/tk2k_startup.s new file mode 100644 index 0000000..fd5148c --- /dev/null +++ b/src/tk2k_startup.s @@ -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 \ No newline at end of file diff --git a/src/utility.c b/src/utility.c new file mode 100644 index 0000000..e019be9 --- /dev/null +++ b/src/utility.c @@ -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; +} diff --git a/src/utility.h b/src/utility.h new file mode 100644 index 0000000..d30bb0a --- /dev/null +++ b/src/utility.h @@ -0,0 +1,34 @@ +#ifndef _UTILITY_HEADER_ +#define _UTILITY_HEADER_ + +#include + +#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_ */