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 0000000..8562a98 Binary files /dev/null and b/dsk/TK2048_AUTO_BRUN.dsk differ diff --git a/dsk/TK2048_AUTO_RUN.dsk b/dsk/TK2048_AUTO_RUN.dsk new file mode 100644 index 0000000..ceb5fa9 Binary files /dev/null and b/dsk/TK2048_AUTO_RUN.dsk differ 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_ */