diff --git a/examples/hires/cm.sh b/examples/hires/cm.sh new file mode 100755 index 0000000..984cb79 --- /dev/null +++ b/examples/hires/cm.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Define filename as variable +PROGNAME="hires" +# Only set C65LIBPATH if not already defined +if [ -z "$C65LIBPATH" ]; then + export C65LIBPATH=$(readlink -f "../../lib") +fi +# Compile +c65gm -in ${PROGNAME}.c65 -out ${PROGNAME}.s +if [ $? -ne 0 ]; then + echo "Compilation terminated" + exit 1 +fi +echo assemble. +acme ${PROGNAME}.s +if [ -f ${PROGNAME}.prg ]; then + rm ${PROGNAME}.prg +fi +# main.bin ${PROGNAME}.prg +mv main.bin main.prg \ No newline at end of file diff --git a/examples/hires/hires.c65 b/examples/hires/hires.c65 new file mode 100644 index 0000000..4c664e4 --- /dev/null +++ b/examples/hires/hires.c65 @@ -0,0 +1,57 @@ +#INCLUDE +#INCLUDE + +GOTO start + + +FUNC sethires + BYTE b + b = PEEK $d011 + b = b | 32 //enable bitmap mode + POKE $d011 , b + + b = PEEK $d018 + b = b & %11110000 + b = b | 8 //enable bitmap mode + POKE $d018 , b + + +FEND + + +FUNC fillmem({WORD start_addr @ $fa} {WORD end_addr @ $fc} {BYTE value}) + + WHILE start_addr <= end_addr + POKE start_addr , value + start_addr++ + WEND + +FEND + +FUNC main + + sethires() + + WORD CONST screen = $0400 + + fillmem(screen, screen+1000, $cf) + + WHILE 1 + + fillmem($2000, $3fff, %00000001) + fillmem($2000, $3fff, %00000010) + fillmem($2000, $3fff, %00000100) + fillmem($2000, $3fff, %00001000) + fillmem($2000, $3fff, %00010000) + fillmem($2000, $3fff, %00100000) + fillmem($2000, $3fff, %01000000) + fillmem($2000, $3fff, %10000000) + + WEND + +FEND + + +LABEL start + +main() diff --git a/examples/hires/start_in_vice.sh b/examples/hires/start_in_vice.sh new file mode 100644 index 0000000..865c48e --- /dev/null +++ b/examples/hires/start_in_vice.sh @@ -0,0 +1 @@ +x64 -autostartprgmode 1 main.prg diff --git a/examples/multicolorbm/cm.sh b/examples/multicolorbm/cm.sh new file mode 100755 index 0000000..376b39b --- /dev/null +++ b/examples/multicolorbm/cm.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Define filename as variable +PROGNAME="multicolorbm" +# Only set C65LIBPATH if not already defined +if [ -z "$C65LIBPATH" ]; then + export C65LIBPATH=$(readlink -f "../../lib") +fi +# Compile +c65gm -in ${PROGNAME}.c65 -out ${PROGNAME}.s +if [ $? -ne 0 ]; then + echo "Compilation terminated" + exit 1 +fi +echo assemble. +acme ${PROGNAME}.s +if [ -f ${PROGNAME}.prg ]; then + rm ${PROGNAME}.prg +fi +# main.bin ${PROGNAME}.prg +mv main.bin main.prg \ No newline at end of file diff --git a/examples/multicolorbm/multicolorbm.c65 b/examples/multicolorbm/multicolorbm.c65 new file mode 100644 index 0000000..9ec78be --- /dev/null +++ b/examples/multicolorbm/multicolorbm.c65 @@ -0,0 +1,58 @@ +#INCLUDE +#INCLUDE + +GOTO start + + +FUNC setmulti + BYTE b + b = PEEK $d011 + b = b | 32 + POKE $d011 , b + + b = PEEK $d016 + b = b | 16 + POKE $d016 , b + + b = PEEK $d018 + b = b & %11110000 + b = b | 8 + POKE $d018 , b + +FEND + + +FUNC fillmem({WORD start_addr @ $fa} {WORD end_addr @ $fc} {BYTE value}) + + WHILE start_addr <= end_addr + POKE start_addr , value + start_addr++ + WEND + +FEND + + +FUNC main + + setmulti() + + WORD CONST screen = $0400 + + fillmem(screen, screen+999, $12) + fillmem(colorram, colorram+999, $03) + + POKE $d021 , 0 + + WHILE 1 + fillmem($2000, $3fff, %00011011) + fillmem($2000, $3fff, %01101100) + fillmem($2000, $3fff, %10110001) + fillmem($2000, $3fff, %11000110) + WEND + +FEND + + +LABEL start + +main() diff --git a/examples/multicolorbm/start_in_vice.sh b/examples/multicolorbm/start_in_vice.sh new file mode 100644 index 0000000..865c48e --- /dev/null +++ b/examples/multicolorbm/start_in_vice.sh @@ -0,0 +1 @@ +x64 -autostartprgmode 1 main.prg diff --git a/lib/c64defs.c65 b/lib/c64defs.c65 new file mode 100644 index 0000000..a9335a2 --- /dev/null +++ b/lib/c64defs.c65 @@ -0,0 +1,40 @@ +//----------------------------------------------------------- +// +// Commodore 64 definitions library +// +// +// Author: Mattias Hansson, Mikael Hansson +// Copyright (c) : 2000-2005 Mattias Hansson +// License: GNU LGPL 2 +// Language: 65CM v0.4+ +// Dependencies: +// Target: Commodore 64 +// +// Purpose: Define accessor constants to various resources +// in the C64. +//----------------------------------------------------------- +#IFNDEF __C64DEFS +#DEFINE __C64DEFS = 1 + WORD CONST vic2 = $d000 + WORD CONST cia1 = $dc00 + WORD CONST cia2 = $dd00 + WORD CONST colorram = $d800 + WORD CONST sid = $d400 + + BYTE CONST color_black = 0 + BYTE CONST color_white = 1 + BYTE CONST color_red = 2 + BYTE CONST color_cyan = 3 + BYTE CONST color_purple = 4 + BYTE CONST color_green = 5 + BYTE CONST color_blue = 6 + BYTE CONST color_yellow = 7 + BYTE CONST color_orange = 8 + BYTE CONST color_brown = 9 + BYTE CONST color_pink = 10 + BYTE CONST color_dark_grey = 11 + BYTE CONST color_grey = 12 + BYTE CONST color_light_green = 13 + BYTE CONST color_light_blue = 14 + BYTE CONST color_light_grey = 15 +#IFEND \ No newline at end of file diff --git a/lib/c64kernal.c65 b/lib/c64kernal.c65 new file mode 100644 index 0000000..49b68c1 --- /dev/null +++ b/lib/c64kernal.c65 @@ -0,0 +1,52 @@ +//----------------------------------------------------------- +// +// Commodore 64 kernal definitions +// +// +// Author: Mattias Hansson +// Copyright (c) : 2025 Mattias Hansson +// License: GNU LGPL 2 +// Language: 65CM v0.6+ +// Dependencies: +// Target: Commodore 64 +// +// Purpose: Define accessor constants to various kernal +// routines in the C64. +//----------------------------------------------------------- + +#IFNDEF __C64KERNAL +#DEFINE __C64KERNAL = 1 + + WORD CONST CINT = $FF81 + WORD CONST IOINIT = $FF84 + WORD CONST RAMTAS = $FF87 + WORD CONST RESTOR = $FF8A + WORD CONST VECTOR = $FF8D + WORD CONST SETMSG = $FF90 + WORD CONST SECOND = $FF93 + WORD CONST TKSA = $FF96 + WORD CONST MEMTOP = $FF99 + WORD CONST MEMBOT = $FF9C + WORD CONST SCNKEY = $FF9F + WORD CONST SETTMO = $FFA2 + WORD CONST ACPTR = $FFA5 + WORD CONST CIOUT = $FFA8 + WORD CONST UNTLK = $FFAB + WORD CONST UNLSN = $FFAE + WORD CONST LISTEN = $FFB1 + WORD CONST TALK = $FFB4 + WORD CONST READST = $FFB7 + WORD CONST SETLFS = $FFBA + WORD CONST SETNAM = $FFBD + WORD CONST OPEN = $FFC0 + WORD CONST CLOSE = $FFC3 + WORD CONST LOAD = $FFD5 + WORD CONST SAVE = $FFD8 + WORD CONST SETTIM = $FFDB + WORD CONST RDTIM = $FFDE + WORD CONST CLRSCR = $E544 // Clear the screen + WORD CONST KBDREAD = $E5B4 // Get Character From Keyboard Buffer + WORD CONST NMIEXIT = $FEBC + WORD CONST UPDCRAMPTR = $EA24 // Update color ram pointer + +#IFEND \ No newline at end of file diff --git a/lib/c64scr.c65 b/lib/c64scr.c65 new file mode 100644 index 0000000..c52e99e --- /dev/null +++ b/lib/c64scr.c65 @@ -0,0 +1,25 @@ + +#IFNDEF __C64_SCR +#DEFINE __C64_SCR = 1 +GOTO lib_c64scr_skip + +LABEL lib_c64scr_blank +ASM + lda $d011 + and #($ff-$10) + sta $d011 +ENDASM + +SUBEND + +LABEL lib_c64scr_show +ASM + lda $d011 + ora #$10 + sta $d011 +ENDASM +SUBEND + +LABEL lib_c64scr_skip + +#IFEND diff --git a/lib/c64start.c65 b/lib/c64start.c65 new file mode 100644 index 0000000..9adf102 --- /dev/null +++ b/lib/c64start.c65 @@ -0,0 +1,34 @@ +//------------------------------------------------------- +// +// C64 START LIB +// +// Author: Mattias Hansson +// License: LGPL +// Compiler version: c65cm, v.04+ +// +// Purpose: +// When loaded into CBM memory this source generates +// a valid basic line that starts the program. +// +// Note: Include this first before any own written code, +// and other libraries. +//------------------------------------------------------- + +ORIGIN $0801 + +#IFNDEF __C64_BASIC_START +#DEFINE __C64_BASIC_START = 1 +#DEFINE MACHINE_C64 = 1 + +ASM + !to "main.bin", cbm ; The output file definition (add CBM starting address) + !sl "main.sym" ; save the symbols to separate file + !cpu 6502 ; The processor definition + ; basic line "0 sys 2064" + !8 $0c, $08, $00, $00, $9e, $20, $32, $30, $36, $34, $0, $0, $0 + +ENDASM + +ORIGIN $0810 + +#IFEND diff --git a/lib/cbmiolib.c65 b/lib/cbmiolib.c65 new file mode 100644 index 0000000..3a3c710 --- /dev/null +++ b/lib/cbmiolib.c65 @@ -0,0 +1,136 @@ +//------------------------------------------------------------------------ +// Library CBM I/O +// +// Author: Mattias Hansson +// Copyright (c) : 2005 Mattias Hansson +// License: GNU LGPL 2 +// Language: 65CM v0.4+ +// Dependencies: (all libs might be included, selectively) +// Target: CBM computers. +// +// Purpose: To shorten lib names to normal usage length +//------------------------------------------------------------------------ + +#IFNDEF __LIB_CBMIO +#DEFINE __LIB_CBMIO = 1 + + +GOTO lib_cbmio_skip + +WORD lib_cbmio_to_txtptr +FUNC lib_cbmio_print ( lib_cbmio_to_txtptr ) + //On c64 we "borrow" the undocumented "print zero-terminated string" routine + #IFDEF MACHINE_C64 + ASM + lda lib_cbmio_to_txtptr + ldy lib_cbmio_to_txtptr+1 + jsr $ab1e ; strout kernal routine + ENDASM + #IFEND + #IFNDEF MACHINE_C64 + BYTE lib_cbmio_to_char + PEEK lib_cbmio_to_txtptr -> lib_cbmio_to_char + WHILE lib_cbmio_to_char + GOSUB $ffd2 PASSING lib_cbmio_to_char AS ACC + INC lib_cbmio_to_txtptr + PEEK lib_cbmio_to_txtptr -> lib_cbmio_to_char + WEND + #IFEND +FEND + +WORD lib_cbmio_tl_txtptr +FUNC lib_cbmio_printlf ( lib_cbmio_tl_txtptr ) + + CALL lib_cbmio_print ( lib_cbmio_tl_txtptr ) + CALL lib_cbmio_print ( @lib_cbmio_tl_linefeed ) + +FEND + +FUNC lib_cbmio_lf + + CALL lib_cbmio_print ( @lib_cbmio_tl_linefeed ) + +FEND + +FUNC lib_cbmio_home + + CALL lib_cbmio_print ( @lib_cbmio_tl_home ) + +FEND + + +ASM +lib_cbmio_tl_home + !8 19, 0 +lib_cbmio_tl_linefeed + !8 13, 0 +lib_cbmio_text_nullstr + !8 0 +ENDASM + + +BYTE lib_cbmio_hb_value +FUNC lib_cbmio_hexoutb ( lib_cbmio_hb_value ) + ASM + ;ldy #0 + ;lda (lib_cbmio_ho_varaddress),y + lda lib_cbmio_hb_value + and #$0f + tax + lda lib_cbmio_ho_hexdigits,x + sta lib_cbmio_ho_hextxt+1 + ;lda (lib_cbmio_ho_varaddress),y + lda lib_cbmio_hb_value + lsr + lsr + lsr + lsr + tax + lda lib_cbmio_ho_hexdigits,x + sta lib_cbmio_ho_hextxt + ENDASM + CALL lib_cbmio_print ( @lib_cbmio_ho_hextxt ) + +FEND + +WORD lib_cbmio_lib_cbmio_ho_value +FUNC lib_cbmio_hexoutw ( lib_cbmio_lib_cbmio_ho_value ) + ASM + lda lib_cbmio_lib_cbmio_ho_value+1 + sta lib_cbmio_hb_value + jsr lib_cbmio_hexoutb + lda lib_cbmio_lib_cbmio_ho_value + sta lib_cbmio_hb_value + jsr lib_cbmio_hexoutb + ENDASM +FEND + +WORD lib_cbmio_i_ptr +FUNC lib_cbmio_input ( lib_cbmio_i_ptr ) + BYTE char + + LET char = 0 + WHILE char <> 13 + ASM + jsr $ffcf + sta |char| + ENDASM + POKE lib_cbmio_i_ptr WITH char + INC lib_cbmio_i_ptr + WEND + DEC lib_cbmio_i_ptr //replace ending #13 with #0 + POKE lib_cbmio_i_ptr WITH 0 +FEND + +ASM + +lib_cbmio_ho_hextxt + !8 0,0,0 + +lib_cbmio_ho_hexdigits + !pet "0123456789abcdef" +ENDASM + + +LABEL lib_cbmio_skip +#IFEND diff --git a/lib/fat16/fat16lib.c65 b/lib/fat16/fat16lib.c65 new file mode 100644 index 0000000..25daa47 --- /dev/null +++ b/lib/fat16/fat16lib.c65 @@ -0,0 +1,1566 @@ +//------------------------------------------------------------------------ +// FAT16 Library +// +// Author: Mattias Hansson +// Copyright (c) : 2005 Mattias Hansson +// License: GNU LGPL 2 +// Language: 65CM v0.4+ +// Dependencies: mmc64lib, multdivlib +// ZP usage: $14-15, $3f-$41, $4b-$4c, $49-$4a +// Target: Commodore 64 with MMC64 interface +// (possible future conversions for other platforms possible) +// +// External functions: +// lib_fat16_mount_partition +// lib_fat16_dirstart +// lib_fat16_dirnext +// lib_fat16_chdir +// lib_fat16_fopen +// lib_fat16_fblockread +// lib_fat16_fblockwrite +// lib_fat16_fclose +// lib_fat16_seek //future expansion +// lib_fat16_fgetc //future expansion +// lib_fat16_fputc //future expansion +// +// +// Internal function: +// lib_fat16_calc_total_clusters +// lib_fat16_int_read_cluster_sector +// lib_fat16_int_write_cluster_sector +// lib_fat16_int_get_cluster_link +// lib_fat16_int_set_cluster_link (rw-support) +// lib_fat16_int_search_free_clusters (rw-support) +// lib_fat16_int_get_free_cluster (rw_support) +// lib_fat16_int_add_dir_entry (rw-support) +// TODO: lib_fat16_int_update_dir_entry (rw-support) //internal in fclose? +// +// External define support: +// __LIB_FAT16_MAX_OPEN_FILES - define this to the # of open files you +// need in your project (default=5) +// If you #DEFINE this in your own code +// before #INCLUDE:ing the lib, the lib +// will "obey" your setting. +// +// OBS: use __LIB_MMC64_READONLY instead of mocking with any R/W +// settings in this lib. This lib is configured to obey that +// directive also. +// +//------------------------------------------------------------------------ + +#IFNDEF __LIB_FAT16 +#DEFINE __LIB_FAT16 = 1 + +//If you want to change this -> See above in the header. +#IFNDEF __LIB_FAT16_MAX_OPEN_FILES + #DEFINE __LIB_FAT16_MAX_OPEN_FILES = 5 +#IFEND + +//This is the total size of a file descriptor record +//OBS! Do NOT (re)define this one from outside!!! +//NOTE: If lib is compiled for R/W this is redefined +//below. +#DEFINE __LIB_FAT16_FILE_DESCRIPTOR_SIZE = 14 + +#INCLUDE +#INCLUDE + +//We inherit the "write support" ([Y]/n) from settings in mmc64rwlib.c65 +#IFDEF __LIB_MMC64_WRITESUPPORT + #PRINT Write access in fat16lib.c65 is experimental!!! + #DEFINE __LIB_FAT16_WRITESUPPORT = 1 + #UNDEF __LIB_FAT16_FILE_DESCRIPTOR_SIZE + //note: see description of descriptor it below + #DEFINE __LIB_FAT16_FILE_DESCRIPTOR_SIZE = 22 +#IFEND + +#INCLUDE + +GOTO lib_fat16_skip + +//Precalculated sector locations +WORD lib_fat16_partition_bootsector_lo +BYTE lib_fat16_partition_bootsector_hi + +WORD lib_fat16_fat1_sector_lo +BYTE lib_fat16_fat1_sector_hi + +WORD lib_fat16_rootdir_sector_lo +BYTE lib_fat16_rootdir_sector_hi + +WORD lib_fat16_data_sector_lo +BYTE lib_fat16_data_sector_hi + +//dir access variables +WORD lib_fat16_active_dir_startcluster // Important if a subdir spans over several clusters. special: $0000 = root directory +WORD lib_fat16_active_dir_cluster // special: $0000 = root directory +WORD lib_fat16_active_dir_sector // Special if active_dir_cluster = $0000 = "root dir" this may be more than + // "lib_fat16_sectors_per_cluster-1". Thats the reason that this is of type WORD. +WORD lib_fat16_active_dir_entry_offset // address to the current directory-entry we shall examine in bytes. (inside the fat-buffer) + // Starts @ lib_fat16_buffer_ptr and in incremented with 32 per entry +BYTE lib_fat16_active_dir_bitmask // The bitmask, sent in when calling "lib_fat16_dirstart", used in "lib_fat16_dirnext" + +//------------------------------------------------------------------------ +// File descriptor table +// +// An array of records representing closed or open files. +// +// Purpose: Having a file open and currently in a read or write process +// requires the lib to hold some info about the location in the file +// etc. If we want several files open at once (and heck yes we want it +// since this is a freakin mass-storage device for our beloved C64) we +// must multiply the descriptor records for as many files we want open +// simultaniously. +// +// NOTE: To change how many files we can have open -> change the constant +// "lib_fat16_max_open_files" under the constant declaration section. +// +// Description of a record: +// +// offset size field_description +// -------------------------------------- +// 0 2 Current cluster ($0002-$ffef) special $0000 - file not open +// 2 1 Current sector offset in the cluster (0..127) +// 3 2 Current byte position in current sector (0..511) +// 5 4 File total size in bytes (0..2147483647) +// 9 4 Current position in file, counted in bytes (0..2147483647) +// 13 1 One of the "lib_fat16_filemode_*" constants +// +// The rest of the descriptor below is only avaliable if the lib is compiled +// with R/W access enabled. +// +// 14 2 Direntry cluster (writemode only) //($0000 = rootdir) +// 16 2 Direntry directory sector (writemode only) //used as fclose() must +// 18 2 Direntry offset in sector (writemode only) //update the entry at close +// 20 2 First cluster in file (writemode only) //To be written to fileentry at fclose() +// +// Total sizeof(descriptor) = 22 bytes (14 if READONLY) +//------------------------------------------------------------------------ +// File descriptor access constants +//------------------------------------------------------------------------ +BYTE CONST FAT16_DESC_CURR_CLUSTER = 0 +BYTE CONST FAT16_DESC_CURR_SECTOR = 2 +BYTE CONST FAT16_DESC_CURR_POS = 3 +BYTE CONST FAT16_DESC_FILESIZE = 5 +BYTE CONST FAT16_DESC_FILEPOS = 9 +BYTE CONST FAT16_DESC_FILEMODE = 13 +#IFDEF __LIB_FAT16_WRITESUPPORT +BYTE CONST FAT16_DESC_DIRE_CLUSTER = 14 +BYTE CONST FAT16_DESC_DIRE_SECTOR = 16 +BYTE CONST FAT16_DESC_DIRE_OFFSET = 18 +BYTE CONST FAT16_DESC_FIRST_CLUSTER = 20 +#IFEND + + +BYTE lib_fat16_file_descriptor_size @ __LIB_FAT16_FILE_DESCRIPTOR_SIZE +ASM +lib_fat16_file_descriptors + !fill (lib_fat16_max_open_files*lib_fat16_file_descriptor_size), $ff +ENDASM + + +//------------------------------------------------------------------------ +//lib_fat16_calc_total_clusters (INTERNAL) +// +// purpose: To calculate the number of the last cluster in a filesystem +// +// Method: +// ( Total sectors - ( data-area-start - start of partition) ) / clusters_per_sector = clusters_of_data_area + +#IFDEF __LIB_FAT16_WRITESUPPORT +FUNC lib_fat16_calc_total_clusters + WORD dataarea_offset_lo + BYTE dataarea_offset_hi + //these two represent the dataarea size in sectors. + WORD dataarea_size_lo + WORD dataarea_size_hi + BYTE sectors_per_cluster + + LET sectors_per_cluster = lib_fat16_sectors_per_cluster + //data-area-start - start of partition + ASM + sec + lda lib_fat16_data_sector_lo + sbc lib_fat16_partition_bootsector_lo + sta |dataarea_offset_lo| + lda lib_fat16_data_sector_lo+1 + sbc lib_fat16_partition_bootsector_lo+1 + sta |dataarea_offset_lo|+1 + lda lib_fat16_data_sector_hi + sbc lib_fat16_partition_bootsector_hi + sta |dataarea_offset_hi| + ENDASM + //Total sectors - "offset of start of dataarea in partition" + ASM + sec + lda lib_fat16_total_sectors_lo + sbc |dataarea_offset_lo| + sta |dataarea_size_lo| + lda lib_fat16_total_sectors_lo+1 + sbc |dataarea_offset_lo|+1 + sta |dataarea_size_lo|+1 + lda lib_fat16_total_sectors_hi + sbc |dataarea_offset_hi| + sta |dataarea_size_hi| + lda lib_fat16_total_sectors_hi+1 + sbc #0 + sta |dataarea_size_hi|+1 + ENDASM + //sectors_of_data_area / sectors_per_cluster = total_clusters + //First bit unimportant (1 sector per cluster and we're done here!) + ASM + lsr |sectors_per_cluster| + ENDASM + WHILE sectors_per_cluster + ASM + clc + ror |dataarea_size_hi|+1 + ror |dataarea_size_hi| + ror |dataarea_size_lo|+1 + ror |dataarea_size_lo| + + lsr |sectors_per_cluster| + ENDASM + WEND + + LET lib_fat16_total_clusters = dataarea_size_lo + + //CALL lib_dbg_hexoutw ( lib_fat16_total_clusters ) + //CALL lib_dbg_textoutlf ( " total clusters" ) + +FEND +#IFEND + +//------------------------------------------------------------------------ +// lib_fat16_mount_partition +// +// Purpose: +// 1. To mount one of the 4 possible partitions on a MMC or SD card. +// 2. Setup all the needed FAT pointers for work. +// 3. Active directory = root-dir of the disk. +// +// Parameters: +// lib_fat16_mp_partnr = partition nr to mount 0..3 +// lib_fat16_mp_status = returns 0 if success otherwise !0 +// +// Preparation: lib_mmc64_cardinit (inherited from our usage of +// lib_mmc64_readsector) +// +// Note: if the SD/MMC is superfloppy formatted "partition nr" is ignored +// +// Usage of lib temp variables: +// lib_fat16_tempw +// lib_fat16_tempw2 +// lib_fat16_tempb +//------------------------------------------------------------------------ + +BYTE lib_fat16_mp_partnr +BYTE lib_fat16_mp_status +FUNC lib_fat16_mount_partition ( lib_fat16_mp_partnr out:lib_fat16_mp_status ) + + BYTE callstat //internal status communication with lower level routines + + + LET lib_fat16_mp_status = 1 //assume a problem + + //Note to developers: comment line below if using a constant buffer reference + POINTER lib_fat16_buffer_ptr TO lib_fat16_fatbuffer + //!!!!DEBUG CODE!!!!: To easily look at the fat-buffer while debugging we can place it where we like it here. + //LET lib_fat16_buffer_ptr = $c000 //comment this line when finished debugging + + //Two formats exist: + // 1. Removable Harddrive mode (sector0 = mbr with partition table) + // 2. Superfloppy mode USB-ZIP (sector0 = partition boot record ) + //We must determine what this (likely) is. + CALL lib_mmc64_readsector ( 0 0 lib_fat16_buffer_ptr callstat ) + IF 0 != callstat + SUBEND //error! Read of MBR failed. + ENDIF + + //First we assume that this is a "Removable Harddrive". + + //Calculate the address (in the buffer) to the partition record caller selected + ADD $1be + lib_fat16_buffer_ptr -> lib_fat16_tempw + WHILE lib_fat16_mp_partnr + ADD 16 + lib_fat16_tempw -> lib_fat16_tempw + DEC lib_fat16_mp_partnr + WEND + + //Is the type of the partition = FAT16 = $06 ? + PEEK lib_fat16_tempw[4] -> lib_fat16_tempb + IF lib_fat16_tempb == $06 //Yes + //Now we extract the partition bootsector start and store it for + //future usage. (needed when calculating the rest of the data areas) + GETASWORD@ lib_fat16_tempw[8] -> lib_fat16_partition_bootsector_lo + PEEK lib_fat16_tempw[10] -> lib_fat16_partition_bootsector_hi + + //Read the boot record of the FAT16 partition + CALL lib_mmc64_readsector ( lib_fat16_partition_bootsector_lo lib_fat16_partition_bootsector_hi lib_fat16_buffer_ptr callstat ) + IF 0 != callstat + SUBEND //error! Read of FAT16 partition boot record failed! + ENDIF + ELSE //No. Is this a "SuperFloppy"? (we take a wild guess) + //So we set the partition boot sector to 0 + LET lib_fat16_partition_bootsector_lo = 0 + LET lib_fat16_partition_bootsector_hi = 0 + ENDIF + + //MHTAG 20051205 + //CALL lib_dbg_hexoutb ( lib_fat16_partition_bootsector_hi ) + //CALL lib_dbg_hexoutw ( lib_fat16_partition_bootsector_lo ) + //CALL lib_dbg_textoutlf ( " start of partition" ) + + + //Get "bytes per sector". This is a sanity check for if this might be a FAT16 PBR + //The code highly depends on $200 bytes per sector so it's good having this covered. + + LET lib_fat16_tempw = lib_fat16_buffer_ptr + GETASWORD@ lib_fat16_tempw[11] -> lib_fat16_tempw2 + IF lib_fat16_tempw2 != LIB_FAT16_SECTOR_SIZE + SUBEND //error! "bytes per sector" is not $200 (512) (or this is not a FAT16 pbr!) + ENDIF + + //Get "Sectors per cluster" + PEEK lib_fat16_tempw[13] -> lib_fat16_sectors_per_cluster + + //Get "Reserved sectors" (including FAT boot sector) + GETASWORD@ lib_fat16_tempw[14] -> lib_fat16_reserved_sectors + //Get "nr of FATs" + PEEK lib_fat16_tempw[16] -> lib_fat16_nr_of_fats + //Get "max nr of rootdir entries" + GETASWORD@ lib_fat16_tempw[17] -> lib_fat16_rootdir_entries + //Get "sectors per fat" + GETASWORD@ lib_fat16_tempw[22] -> lib_fat16_sectors_per_fat + + +#IFDEF __LIB_FAT16_WRITESUPPORT + + //MHTAG 20051205 + GETASWORD@ lib_fat16_tempw[34] -> lib_fat16_total_sectors_hi + //CALL lib_dbg_hexoutw ( lib_fat16_total_sectors_hi ) + GETASWORD@ lib_fat16_tempw[32] -> lib_fat16_total_sectors_lo + //CALL lib_dbg_hexoutw ( lib_fat16_total_sectors_lo ) + //CALL lib_dbg_textoutlf ( " total sectors" ) + +#IFEND + + + //Now we'll do some precalculations based on the values we've read + //so that we know the start sectors of all areas of the partition. + +//known +//WORD lib_fat16_partition_bootsector_lo +//BYTE lib_fat16_partition_bootsector_hi + + //First FAT starts after the reserved sectors + // this below means fat1_sector = partition_start + reserved_sectors + // in assembler due to this beeing a 24-bit number + ASM + clc + lda lib_fat16_reserved_sectors + adc lib_fat16_partition_bootsector_lo + sta lib_fat16_fat1_sector_lo + lda lib_fat16_reserved_sectors+1 + adc lib_fat16_partition_bootsector_lo+1 + sta lib_fat16_fat1_sector_lo+1 + lda #0 + adc lib_fat16_partition_bootsector_hi + sta lib_fat16_fat1_sector_hi + ENDASM + + //Partition root-dir starts after the FATs + //So first we must figure out how much space all the FATs take up together. + LET lib_fat16_tempw = lib_fat16_nr_of_fats + CALL lib_multdiv_mult ( lib_fat16_tempw lib_fat16_sectors_per_fat ) + + //Now "total size of FATs" + "offset to the first FAT" = "root dir start" + ASM + clc + lda lib_fat16_tempw + adc lib_fat16_fat1_sector_lo + sta lib_fat16_rootdir_sector_lo + lda lib_fat16_tempw+1 + adc lib_fat16_fat1_sector_lo+1 + sta lib_fat16_rootdir_sector_lo+1 + lda #0 + adc lib_fat16_fat1_sector_hi + sta lib_fat16_rootdir_sector_hi + ENDASM + + //Partitions data-area starts after the root-dir. To calculate the total size of the data-area in sectors + //we use this formula: ( "max root dir entries" * size of directory entry (32) ) / sector size(512) + //To simplify 1 sector has space for 16, 32-byte directory entries. + //So "max root dir entries" / 16 = "size of the total root-dir in sectors" + LET lib_fat16_tempw = lib_fat16_rootdir_entries + CALL lib_multdiv_div ( lib_fat16_tempw 16 ) + + LET lib_fat16_rootdir_sectors = lib_fat16_tempw + //Now "total size of root dir in sectors" + "root dir start sector" = "start of data area" + ASM + clc + lda lib_fat16_tempw + adc lib_fat16_rootdir_sector_lo + sta lib_fat16_data_sector_lo + lda lib_fat16_tempw+1 + adc lib_fat16_rootdir_sector_lo+1 + sta lib_fat16_data_sector_lo+1 + lda #0 + adc lib_fat16_rootdir_sector_hi + sta lib_fat16_data_sector_hi + ENDASM + + //MHTAG 20051205 + //CALL lib_dbg_hexoutb ( lib_fat16_data_sector_hi ) + //CALL lib_dbg_hexoutw ( lib_fat16_data_sector_lo ) + //CALL lib_dbg_textoutlf ( " data area start-sector" ) + +#IFDEF __LIB_FAT16_WRITESUPPORT + CALL lib_fat16_calc_total_clusters +#IFEND + + //Now we've calculated all the pointers that important in this partition + //For the more high level FAT16 access routines + + //Initialize work variables + //LET lib_fat16_active_cluster = $ffff + LET lib_fat16_active_dir_startcluster = $0000 // startdir = root + +#IFDEF __LIB_FAT16_WRITESUPPORT + //MHTAG 20051114 - prepare writing by finding some suitable write space when mounting + LET lib_fat16_first_write_cluster = $ffff + LET lib_fat16_last_write_cluster = 0 + CALL lib_fat16_int_search_free_clusters ( callstat ) + IF callstat != 0 + SUBEND + ENDIF +#IFEND + + LET lib_fat16_mp_status = 0 //if we got this far, we've successfully mounted the partition! + +FEND + +//------------------------------------------------------------------------ +// lib_fat16_dirnext +// lib_fat16_dirnext_le (INTERNAL) +// +// Purpose: +// To scan active directory for the next direntry with the same bitmask +// that was passed to the latest call to lib_fat16_dirfirst +// +// Parameters: +// lib_fat16_ds_entry_ptr - pointer to the fat entry (check FAT docs for struct layout) +// NULL = 0 = not found / whole dir listed +// lib_fat16_ds_status - result code: 0 - success +// 1 - internal error +// lib_fat16_dn_firstnamechar - $00 = last entry, else not last entry +// +// Preparation: lib_fat16_dirfirst +// +// Usage of lib temp variables: +// lib_fat16_tempw (pointer to current dir-entry in sector-buffer-memory) +// +// Note: +// When we enter this routine we're about to read the next dir +// entry. We must however first look at and adjust (if needed) the current +// cluster/sector/dir_entry_offset if we've passed a border in the previous +// call. +// +//------------------------------------------------------------------------ + + + +WORD lib_fat16_dn_entry_ptr +BYTE lib_fat16_dn_status +BYTE lib_fat16_dn_firstnamechar + +#IFDEF __LIB_FAT16_WRITESUPPORT + +FUNC lib_fat16_dirnext_le ( out:lib_fat16_dn_entry_ptr out:lib_fat16_dn_status out:lib_fat16_dn_firstnamechar ) + LET lib_fat16_dn_firstnamechar = $ff //when we write a new file we must be sure that a valid 0-entry terminated the dir. +#IFEND + +FUNC lib_fat16_dirnext ( out:lib_fat16_dn_entry_ptr out:lib_fat16_dn_status ) + + BYTE callstat + BYTE filetype + BYTE validentry //valid directory entry found for the caller (exit condition) + + LET lib_fat16_dn_status = 1 + LET lib_fat16_dn_entry_ptr = 0 + LET validentry = 0 + WHILE validentry = 0 + //have we passed the end of the sector (in buffer) yet? + IF lib_fat16_active_dir_entry_offset >= LIB_FAT16_SECTOR_SIZE + INC lib_fat16_active_dir_sector + + //if this is the root directory + IF lib_fat16_active_dir_startcluster == 0 + IF lib_fat16_active_dir_sector >= lib_fat16_rootdir_sectors + //(For all I know this is an error in the filesystem not to terminate the + // directory with an entry that starts with a NULL-char). I just choose not to take action + BREAK //read to the end of directory successfully. no more data + ENDIF + ELSE //if this is in a normal cluster + IF lib_fat16_active_dir_sector >= lib_fat16_sectors_per_cluster //we've passed the last sector of it + + //Call will update the "lib_fat16_active_dir_cluster" to the next cluster link. + CALL lib_fat16_int_get_cluster_link ( lib_fat16_active_dir_cluster callstat ) + IF callstat != 0 + SUBEND //internal error (or end of cluster-chain == error in filesystem) + ENDIF + + IF lib_fat16_active_dir_cluster == LIB_FAT16_CLUSTER_FREE + SUBEND //internal error (or error in filesystem), how did we end up at an free cluster while folloing a chain? + ENDIF + + // end of cluster-chain. we are ready with this dir-scan! + IF lib_fat16_active_dir_cluster >= LIB_FAT16_CLUSTER_END + //(For all I know this is an error in the filesystem not to terminate the + // directory with an entry that starts with a NULL-char). I just choose not to take action + BREAK //read to the end of directory successfully. no more data + ENDIF + + //since we've gone to a new cluster we restart the internal "cluster sector offset counter" + LET lib_fat16_active_dir_sector = 0 + ENDIF + ENDIF + + LET lib_fat16_active_dir_entry_offset = 0 //restart "offset in sector" counter + CALL lib_fat16_int_read_cluster_sector ( lib_fat16_active_dir_cluster lib_fat16_active_dir_sector lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + + ENDIF + + //calc the pointer address the the direntry in memory + ADD lib_fat16_buffer_ptr + lib_fat16_active_dir_entry_offset -> lib_fat16_tempw + + PEEK lib_fat16_tempw -> lib_fat16_dn_firstnamechar + + //is it the end of the directory? + IF lib_fat16_dn_firstnamechar == LIB_FAT16_END_OF_DIR + //This is the correct end of an FAT16 directory. (see two "BREAK"s above) + BREAK //read to the end of directory successfully. no more data + ENDIF + + //is it a deleted entry? + IF lib_fat16_dn_firstnamechar != LIB_FAT16_DELETED_FILE + //no it exists! + + //Check the filetype of the current direntry + PEEK lib_fat16_tempw[11] -> filetype + + IF lib_fat16_active_dir_bitmask != 0 + //calc the pointer to the filetype-bitfield in the direntry + AND filetype WITH lib_fat16_active_dir_bitmask -> filetype + //is it a fileentry of a type we're looking for? + IF filetype != 0 + //found a matching entry, no more looping. + LET lib_fat16_dn_entry_ptr = lib_fat16_tempw + LET validentry = 1 + ENDIF + ELSE //bitmask = 0, we want ALL file-entries! + AND filetype WITH 8 -> filetype + //(but not volume labels (and long filenames)) + IF filetype == 0 + //no volume label - it seams ok to return + LET lib_fat16_dn_entry_ptr = lib_fat16_tempw + LET validentry = 1 + ENDIF + + ENDIF + ENDIF + + //prepare the offset to the next direntry read + ADD LIB_FAT16_DIRENTRY_SIZE + lib_fat16_active_dir_entry_offset -> lib_fat16_active_dir_entry_offset + + WEND + + LET lib_fat16_dn_status = 0 +FEND + + +//------------------------------------------------------------------------ +// lib_fat16_dirstart +// +// Purpose: +// To scan active directory for the first entry that maches the given bitmask +// +// Parameters: +// lib_fat16_ds_bitmask - a bitmask that will be AND:ed with the entries +// of the active directory. If the result is not 0 +// the entry is considered as a match returned to caller. +// (refer to FAT documentation for the meaning of the bits). +// special: value 0 lists all files and subdirs but not +// volume labels (and the LFN-support in VFAT) +// lib_fat16_ds_entry_ptr - pointer to the fat entry (check FAT docs for struct layout) +// NULL = 0 = not found / whole dir listed +// lib_fat16_ds_status - result code: 0 - success +// 1 - internal error +// +// Preparation: lib_mmc64_cardinit (inherited from our usage of +// lib_mmc64_readsector) +// lib_fat16_mount_partition +// +// Usage of lib temp variables: +// +//------------------------------------------------------------------------ + +BYTE lib_fat16_ds_bitmask +WORD lib_fat16_ds_entry_ptr +BYTE lib_fat16_ds_status +FUNC lib_fat16_dirstart ( lib_fat16_ds_bitmask out:lib_fat16_ds_entry_ptr out:lib_fat16_ds_status ) + + BYTE callstat + + LET lib_fat16_ds_status = 1 + LET lib_fat16_active_dir_cluster = lib_fat16_active_dir_startcluster + LET lib_fat16_active_dir_sector = 0 + LET lib_fat16_active_dir_entry_offset = 0 + LET lib_fat16_active_dir_bitmask = lib_fat16_ds_bitmask + + CALL lib_fat16_int_read_cluster_sector ( lib_fat16_active_dir_cluster lib_fat16_active_dir_sector lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND + ENDIF + + CALL lib_fat16_dirnext ( lib_fat16_ds_entry_ptr callstat ) + IF callstat != 0 + SUBEND + ENDIF + + LET lib_fat16_ds_status = 0 //set success if all went ok +FEND + +//------------------------------------------------------------------------ +// lib_fat16_chdir +// +// Purpose: +// To change to one of the avaliable directories that exist in current +// dir. +// +// Parameters: +// lib_fat16_cd_dirname_ptr - pointer to a 11-char string. If this string +// is found to be an exact match of a directory- +// entry in the current dir, we will change to +// this new directory. +// lib_fat16_cd_status - 0 = success, !0 = unable to change dir or internal error +// +// Preparation: lib_fat16_mount_partition +// +// Usage of lib temp variables: +// +// Note: +// In FAT16 there are always (except in the ROOT-dir) two dirs +// avaliable, one called "." and the other ".." +// "." points to the current dir (which just makes you go back where you are +// i.e. pointless =) (but there was a need for it in DOS long ago) +// ".." points back to the parent dir. +// +//------------------------------------------------------------------------ + +WORD lib_fat16_cd_dirname_ptr +BYTE lib_fat16_cd_status +FUNC lib_fat16_chdir ( lib_fat16_cd_dirname_ptr out:lib_fat16_cd_status ) + + WORD direntry @ $14 + BYTE callstat + BYTE charcount + BYTE char1 + BYTE char2 + WORD dirnamecmp_ptr + + LET lib_fat16_cd_status = 1 + + //search for directories in current dir + CALL lib_fat16_dirstart ( 16 direntry callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + WHILE direntry + //Compare the strings + LET dirnamecmp_ptr = lib_fat16_cd_dirname_ptr + LET charcount = 11 + WHILE charcount + PEEK dirnamecmp_ptr -> char1 + PEEK direntry -> char2 + IF char1 != char2 + GOTO lib_fat16_cd_findnext + ENDIF + INC dirnamecmp_ptr + INC direntry + DEC charcount + WEND + //if we got here we have a match! So we change dir! + //offset from start of direntry is now 11 and we want to 26 + GETASWORD@ direntry[26-11] -> lib_fat16_active_dir_startcluster + GOTO lib_fat16_cd_exitsuccess + + LABEL lib_fat16_cd_findnext + CALL lib_fat16_dirnext ( direntry callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + WEND + + //if we came here through the loop but didn't find anything the cd has failed - exit with errorcode + SUBEND + + LABEL lib_fat16_cd_exitsuccess + LET lib_fat16_cd_status = 0 +FEND + +//------------------------------------------------------------------------ +// lib_fat16_int_get_free_cluster (internal) +// +// purpose: This routine is used when the lib needs a new cluster to write +// to, may it be for filedata or for extending a subdirectory +// with an entry past the end of it's current last cluster +// +// The need of this routine comes because +// lib_fat16_int_find_unused_clusters can only find new cluster +// holes however with any fragmentation these holes are spread +// between chunks data on the disk. So this routine searches +// for next cluster hole as needed. +// +// An error is issued if diskspace is full. +//------------------------------------------------------------------------ + +#IFDEF __LIB_FAT16_WRITESUPPORT + +WORD lib_fat16_int_cw_free_cluster_nr +BYTE lib_fat16_int_cw_status + +FUNC lib_fat16_int_get_free_cluster ( out:lib_fat16_int_cw_free_cluster_nr out:lib_fat16_int_cw_status ) + + BYTE callstat + + LET lib_fat16_int_cw_status = 1 //error + + //Are there any more free clusters in the previously discovered (or not discovered) + //"cluster hole"? + IF lib_fat16_first_write_cluster > lib_fat16_last_write_cluster + //No, so we try to find another "cluster hole" + CALL lib_fat16_int_search_free_clusters ( callstat ) + IF callstat != 0 + SUBEND + ENDIF + ENDIF + + LET lib_fat16_int_cw_free_cluster_nr = lib_fat16_last_write_cluster + DEC lib_fat16_last_write_cluster + + LET lib_fat16_int_cw_status = 0 //success + +FEND + +#IFEND + +//------------------------------------------------------------------------ +// lib_fat16_int_add_dir_entry (INTERNAL) +// +// Purpose: when a new, previously not existing, file is opened for +// writing, a new direntry must be added to the current +// directory. This function does that. +// Note: This function is only responsible for allocating a new +// uninitialized entry, whereas the fopen function is responsible +// to make sure that we don't add a duplicate entry in the +// directory. +// +// Method: This function gets its input from the dirstart/dirnext done +// by fopen() when searching, if the file for write exists from +// before. +// Then it copies the last entry forward one entry +// (if possible). If all went successful it returns the pointers +// to the newly allocated entry to the caller. +// +// Preparation: fopen() internal call +// Run dirstart/dirnext until the active directory ends. +// This is done in fopen() before calling this function. +// +// Parameters: +// +// lib_fat16_ae_new_entry_filename - pointer the a 11 char long filename +// for the new entry. +// lib_fat16_ae_new_entry_cluster - The cluster in which the new direntry +// resides. +// +// +// ZP-vars: +// lib_fat16_tempw, lib_fat16_tempw2 +//------------------------------------------------------------------------ + +#IFDEF __LIB_FAT16_WRITESUPPORT + +WORD lib_fat16_ae_new_entry_filename +WORD lib_fat16_ae_new_entry_cluster +WORD lib_fat16_ae_new_entry_sector +WORD lib_fat16_ae_new_entry_offset +BYTE lib_fat16_ae_status + +FUNC lib_fat16_int_add_dir_entry ( lib_fat16_ae_new_entry_filename out:lib_fat16_ae_new_entry_cluster out:lib_fat16_ae_new_entry_sector out:lib_fat16_ae_new_entry_offset out:lib_fat16_ae_status ) + + LET lib_fat16_ae_status = 1 + + WORD null_entry_cluster + WORD null_entry_sector + WORD null_entry_offset + + BYTE callstat + + + //We assume caller has used looped to the last direntry with "dirnext" + //NOTE!! If this is not the case we will OVERWRITE valid entries. + LET lib_fat16_ae_new_entry_cluster = lib_fat16_active_dir_cluster + LET lib_fat16_ae_new_entry_sector = lib_fat16_active_dir_sector + LET lib_fat16_ae_new_entry_offset = lib_fat16_active_dir_entry_offset + LET null_entry_cluster = lib_fat16_active_dir_cluster + LET null_entry_sector = lib_fat16_active_dir_sector + LET null_entry_offset = lib_fat16_active_dir_entry_offset + + //------------------------------------------------------- + //TASK1: prepare to move the NULL-entry (end-of-dir) + // (leaving the old position for the new entry) + + //Is the last entry the last entry in sector also? + IF null_entry_offset >= LIB_FAT16_SECTOR_SIZE-LIB_FAT16_DIRENTRY_SIZE + INC null_entry_sector + //This is the ROOT dir? + IF null_entry_cluster == 0 + IF null_entry_sector >= lib_fat16_rootdir_sectors + SUBEND //root directory cannot be exceeded + ELSE + //There are rootdir sectors left so we just take the next one. + LET null_entry_offset = 0 + ENDIF + ELSE //this is NOT the ROOT-dir + //must we allocate another cluster to fit the new entry? + IF null_entry_cluster >= lib_fat16_sectors_per_cluster + //Yes, so we ask for another cluster. + CALL lib_fat16_int_get_free_cluster ( null_entry_cluster callstat ) + IF callstat != 0 + SUBEND //Could not get another cluster (disk probably full) + ENDIF + //Now we have the number of a free cluster, yet we have to: + // a. Link the old last cluster to this new cluster + // b. mark the new cluster as the last in the chain + + //a. + CALL lib_fat16_int_set_cluster_link ( lib_fat16_ae_new_entry_cluster null_entry_cluster callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + + //b. + //Note: allthough everything above > $ffef is a "cluster chain ended", some badly written drivers only understand $ffff + CALL lib_fat16_int_set_cluster_link ( null_entry_cluster $ffff callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + + //Now in the new cluster we restart the internal "offsets" + LET null_entry_offset = 0 + LET null_entry_sector = 0 + + ELSE //no, we just use another sector in the current cluster + LET null_entry_offset = 0 + ENDIF + ENDIF + ELSE //last entry is not at the end of a sector. + ADD LIB_FAT16_DIRENTRY_SIZE + null_entry_offset -> null_entry_offset + ENDIF + + //When we've got here we have calculated all the pointers + //to the newly created END-OF-DIR entry. + + //------------------------------------------------------- + //TASK2 - Write a new NULL-entry. + + //Read up the sector where it resides. + CALL lib_fat16_int_read_cluster_sector ( null_entry_cluster null_entry_sector lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + //Adjust offset to where in memory the buffer is. + ADD lib_fat16_buffer_ptr + null_entry_offset -> lib_fat16_tempw2 + //make this a "null-entry" + POKE lib_fat16_tempw2 , 0 + //and write it back to disk + CALL lib_fat16_int_write_cluster_sector ( null_entry_cluster null_entry_sector lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + + + //------------------------------------------------------- + //TASK3 - Create a new direntry (empty file) + //Read up the sector where it resides. + CALL lib_fat16_int_read_cluster_sector ( lib_fat16_ae_new_entry_cluster lib_fat16_ae_new_entry_sector lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + //Adjust offset to where in memory the buffer is. + ADD lib_fat16_buffer_ptr + lib_fat16_ae_new_entry_offset -> lib_fat16_tempw2 + //Copy in the new filename + LET lib_fat16_tempw = lib_fat16_ae_new_entry_filename + ASM + ldy #0 +lib_fat16_ae_l1 + lda (lib_fat16_tempw),y + sta (lib_fat16_tempw2),y + iny + cpy #11 + bne lib_fat16_ae_l1 + + ;clear the rest of the entry (NULLs) + lda #0 +lib_fat16_ae_l2 + sta (lib_fat16_tempw2),y + iny + cpy #32 + bne lib_fat16_ae_l2 + ENDASM + + CALL lib_fat16_int_write_cluster_sector ( lib_fat16_ae_new_entry_cluster lib_fat16_ae_new_entry_sector lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + + LET lib_fat16_ae_status = 0 + +FEND +#IFEND + +//------------------------------------------------------------------------ +// lib_fat16_fopen +// +// Purpose: +// To open a file in the current directory and initialize a descriptor +// table entry. +// A file we're opening for read must exist. +// A file we're opening for write must NOT exist. +// +// Parameters: +// descriptor_ptr - this is the fileptr the user needs to +// remember for all further operations +// on this file. If = 0 = NULL then +// the function was unable to open the file. +// lib_fat16_fo_filename_ptr - 11 char string, that must match the dir- +// entry exacly (or represent the new filename +// when writing). +// lib_fat16_fo_filemode - use one of the filemode constants defined in +// the constant section. "lib_fat16_filemode_*" +// currently only "LIB_FAT16_FILEMODE_READ" is +// implemented. +// lib_fat16_fo_status - 0 = success. !0 = file open error or internal +// error. +// +// Preparation: lib_fat16_mount_partition +// +// Dependencies: This function uses both "lib_fat16_dirstart" and +// "lib_fat16_dirnext" so don't use fopen in a dir-scan +// sequence. (it won't work at all as lib_fat16_dirstart +// will reset the current search your are doing) +// +// +// Usage of lib temp variables: +// (none) ... used lib_fat16_tempw before. +// +//------------------------------------------------------------------------ + +WORD descriptor_ptr +WORD lib_fat16_fo_filename_ptr +BYTE lib_fat16_fo_filemode +BYTE lib_fat16_fo_status + +FUNC lib_fat16_fopen ( out:descriptor_ptr lib_fat16_fo_filename_ptr lib_fat16_fo_filemode out:lib_fat16_fo_status ) + + WORD descr @ $49 + BYTE max_files + WORD clusternr + WORD direntry @ $14 + BYTE callstat + BYTE charcount + BYTE char1 + BYTE char2 + BYTE attributes + WORD filnamecmp_ptr + WORD wtemp +#IFDEF __LIB_FAT16_WRITESUPPORT + BYTE lastdirentrychar + WORD new_entry_cluster + WORD new_entry_sector + WORD new_entry_offset +#IFEND + + LET lib_fat16_fo_status = 1 + LET descriptor_ptr = 0 + +#IFNDEF __LIB_FAT16_WRITESUPPORT + //if library is compiled for readonly support, trying to opening file for write is forbidden + IF lib_fat16_fo_filemode != LIB_FAT16_FILEMODE_READ + SUBEND + ENDIF +#IFEND + + //------------------------------------------ + //TASK1 - search for an avaliable descriptor + + //max files is defined as the address of "lib_fat16_max_open_files". + //This is a little quirk to get it into a local variable + ASM + lda # clusternr + IF clusternr == $ffff + GOTO lib_fat16_fo_task2 + ENDIF + ADD descr + __LIB_FAT16_FILE_DESCRIPTOR_SIZE -> descr + DEC max_files + WEND + + SUBEND //unable to find a free filedescriptor-slot, return error + + //-------------------------------------------------------- + //TASK2 - find the requested file in the current directory + LABEL lib_fat16_fo_task2 + + CALL lib_fat16_dirstart ( 0 direntry callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + WHILE direntry + //Compare the strings + LET filnamecmp_ptr = lib_fat16_fo_filename_ptr + LET charcount = 11 + WHILE charcount + PEEK filnamecmp_ptr -> char1 + PEEK direntry -> char2 + IF char1 != char2 + GOTO lib_fat16_fo_findnext + ENDIF + + INC filnamecmp_ptr + INC direntry + DEC charcount + WEND + + //if we got here we have a match! Now this must be a valid file + //so we've test the attributes of it. + PEEK direntry -> attributes + //if the direntry is systemfile, volume label or directory we cannot open it! + AND attributes WITH 4+8+16 -> attributes + IF attributes != 0 + SUBEND //error, direntry was not valid to open (see reasons above) + ENDIF + #IFDEF __LIB_FAT16_WRITESUPPORT + IF lib_fat16_fo_filemode == LIB_FAT16_FILEMODE_READ + #IFEND + GOTO lib_fat16_fo_task4 + #IFDEF __LIB_FAT16_WRITESUPPORT + ELSE + SUBEND //it's write error to try to overwrite an existing file. + ENDIF + #IFEND + + + + LABEL lib_fat16_fo_findnext +#IFDEF __LIB_FAT16_WRITESUPPORT + CALL lib_fat16_dirnext_le ( direntry callstat lastdirentrychar ) +#IFEND +#IFNDEF __LIB_FAT16_WRITESUPPORT + CALL lib_fat16_dirnext ( direntry callstat ) +#IFEND + IF callstat != 0 + SUBEND //internal error + ENDIF + WEND + + +#IFDEF __LIB_FAT16_WRITESUPPORT + IF lib_fat16_fo_filemode == LIB_FAT16_FILEMODE_READ +#IFEND + SUBEND // if we got here, we didn't find the file the caller requested for read access +#IFDEF __LIB_FAT16_WRITESUPPORT + ENDIF +#IFEND + + +#IFDEF __LIB_FAT16_WRITESUPPORT + //------------------------------------------------------------------- + //TASK3 - If writemode we need to create us a new direntry + + //check verification that dirnext was on the last entry in dir + IF lastdirentrychar != LIB_FAT16_END_OF_DIR + SUBEND //The DIR did not end with a NULL-filename. Eighter a filesystem error or + //it might be the rootdir that is completely filled + //We cannot however proceed to create another file in this condition. + ENDIF + + CALL lib_fat16_int_add_dir_entry ( lib_fat16_fo_filename_ptr new_entry_cluster new_entry_sector new_entry_offset callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + + +#IFEND + + //------------------------------------------------------------------- + //TASK4 - Copy info from the direntry to the accuired(or created)file descriptor + + LABEL lib_fat16_fo_task4 + + //clear descriptor + ASM + ldy #0 + tya +lib_fat16_fo_l2 + sta (|descr|),y + iny + cpy #lib_fat16_file_descriptor_size + bne lib_fat16_fo_l2 + ENDASM + + //Copy the startcluster to the descriptor + // (direntry points to position 11 in the directory-entry + // since we've scanned the filename before. therefore "X-11" + GETASWORD@ direntry[26-11] -> clusternr + PUTASWORD@ descr[FAT16_DESC_CURR_CLUSTER] VALUE clusternr + + //Copy the filesize to the descriptor. (Two words == one double word :-) + GETASWORD@ direntry[26-11+2] -> wtemp + PUTASWORD@ descr[FAT16_DESC_FILESIZE] VALUE wtemp + GETASWORD@ direntry[26-11+4] -> wtemp + PUTASWORD@ descr[FAT16_DESC_FILESIZE+2] VALUE wtemp + + //set filemode in descriptor + POKE descr[FAT16_DESC_FILEMODE] , lib_fat16_fo_filemode + +#IFDEF __LIB_FAT16_WRITESUPPORT + IF lib_fat16_fo_filemode == LIB_FAT16_FILEMODE_WRITE + PUTASWORD@ descr[FAT16_DESC_DIRE_CLUSTER] VALUE new_entry_cluster + PUTASWORD@ descr[FAT16_DESC_DIRE_SECTOR] VALUE new_entry_sector + PUTASWORD@ descr[FAT16_DESC_DIRE_OFFSET] VALUE new_entry_offset + ENDIF +#IFEND + + LET descriptor_ptr = descr + LET lib_fat16_fo_status = 0 +FEND + + +//------------------------------------------------------------------------ +// lib_fat16_fclose +// lib_fat16_fclose (returns status of operation R/W only) +// +// Purpose: +// To close an open file specified by a descriptor pointer, the caller +// accuired from a previous call to "lib_fat16_fopen" +// +// Parameters: +// lib_fat16_fc_descriptor_ptr - the file pointer. +// +// Preparation: lib_fat16_mount_partition, lib_fat16_fopen +// +// Usage of lib temp variables: +// (none) ... used lib_fat16_tempw before +// +//------------------------------------------------------------------------ + +//WORD lib_fat16_fc_descriptor_ptr +WORD lib_fat16_fc_descr @ $49 +#IFDEF __LIB_FAT16_WRITESUPPORT +BYTE lib_fat16_fc_stat +FUNC lib_fat16_fcloses ( lib_fat16_fc_descr out:lib_fat16_fc_stat ) + LET lib_fat16_fc_stat = 1 +#IFEND +FUNC lib_fat16_fclose ( lib_fat16_fc_descr ) + + //LET lib_fat16_fc_descr = lib_fat16_fc_descriptor_ptr + +#IFDEF __LIB_FAT16_WRITESUPPORT + BYTE filemode + WORD direntry @ $14 + WORD entry_cluster + WORD entry_sector + WORD entry_offset + WORD startcluster + WORD clusternr // <- this is the last cluster! + WORD filesize_lo + WORD filesize_hi + BYTE callstat + + PEEK lib_fat16_fc_descr[FAT16_DESC_FILEMODE] -> filemode + IF filemode != LIB_FAT16_FILEMODE_READ + + //read up the direntry + GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_DIRE_CLUSTER] -> entry_cluster + GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_DIRE_SECTOR] -> entry_sector + GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_DIRE_OFFSET] -> entry_offset + CALL lib_fat16_int_read_cluster_sector ( entry_cluster entry_sector lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND + ENDIF + + ADD lib_fat16_buffer_ptr + entry_offset -> direntry + + //update it + GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_FIRST_CLUSTER] -> startcluster + GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_CURR_CLUSTER] -> clusternr + PUTASWORD@ direntry[26] VALUE startcluster + + //Copy the filesize to the descriptor. (Two words == one double word :-) + GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_FILEPOS] -> filesize_lo + PUTASWORD@ direntry[28] VALUE filesize_lo + GETASWORD@ lib_fat16_fc_descr[FAT16_DESC_FILEPOS+2] -> filesize_hi + PUTASWORD@ direntry[30] VALUE filesize_hi + + //write down the direntry + CALL lib_fat16_int_write_cluster_sector ( entry_cluster entry_sector lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND + ENDIF + + //mark last cluster in file as last-in-cluster-chain + // + //Note: allthough everything above > $ffef is a "cluster chain ended", some badly written drivers only understand $ffff + CALL lib_fat16_int_set_cluster_link ( clusternr $ffff callstat ) + IF callstat != 0 + SUBEND + ENDIF + + ENDIF + LET lib_fat16_fc_stat = 0 //if we got here it's a success +#IFEND + + //we set the "current cluster" to $ffff. This + //indicates for "lib_fat16_fopen" that this + //descriptor location is up for grabs + ASM + ldy #0 + ;tya + lda #$ff + sta (lib_fat16_fc_descr),y + iny + sta (lib_fat16_fc_descr),y + ENDASM + +FEND + + +//------------------------------------------------------------------------ +// lib_fat16_fblockread +// +// Purpose: +// Try to read a block of 512 bytes from an file that is open for reading. +// If the file end before we've reached 512 read bytes the reading ends. +// (Or more correct: we don't write anymore to memory-buffer, but the +// sector is fully read) +// +// Parameters: +// lib_fat16_fr_descriptor_ptr - the file pointer. +// lib_fat16_fr_block_ptr - pointer to memory where to write the block +// read from the file +// lib_fat16_fr_read_count - The number of bytes actually read from +// file. If this is not equal to 512 +// it usually means we've reached the end +// of the file. +// +// Preparation: lib_fat16_mount_partition, lib_fat16_fopen +// +// NOTE: this routine calls "lib_fat16_int_get_cluster_link" which +// will ruin any ongoing "lib_fat16_dirstart" - "lib_fat16_dirnext" +// sequencen. Don't do it. ( both use the internal FAT-buffer ) +// +// Usage of lib temp variables: +// (none) ... used lib_fat16_tempw before +// +//------------------------------------------------------------------------ + +//WORD lib_fat16_fr_descriptor_ptr +WORD lib_fat16_fr_descr @ $49 +WORD lib_fat16_fr_block_ptr +WORD lib_fat16_fr_read_count +BYTE lib_fat16_fr_status +FUNC lib_fat16_fblockread ( lib_fat16_fr_descr lib_fat16_fr_block_ptr out:lib_fat16_fr_read_count out:lib_fat16_fr_status ) + + WORD lib_fat16_fr_current_cluster + WORD lib_fat16_fr_sector_in_cluster //note ONLY the lowbyte in this one i used (must be word for call to next_cluster_link). + BYTE lib_fat16_fr_current_filemode + BYTE lib_fat16_fr_callstat + WORD lib_fat16_fr_filesize_lo + WORD lib_fat16_fr_filesize_hi + WORD lib_fat16_fr_filepos_lo + WORD lib_fat16_fr_filepos_hi + WORD lib_fat16_fr_fileleft_lo + WORD lib_fat16_fr_fileleft_hi + + LET lib_fat16_fr_status = 1 + + //-------------------------------------- + //TASK1 - is the file valid for reading - copy data + + //LET lib_fat16_fr_descr = lib_fat16_fr_descriptor_ptr + GETASWORD@ lib_fat16_fr_descr[FAT16_DESC_CURR_CLUSTER] -> lib_fat16_fr_current_cluster + + PEEK lib_fat16_fr_descr[FAT16_DESC_FILEMODE] -> lib_fat16_fr_current_filemode + IF lib_fat16_fr_current_filemode != LIB_FAT16_FILEMODE_READ + SUBEND //error. file not open for reading. + ENDIF + + //copy descriptor data to local variables + PEEK lib_fat16_fr_descr[FAT16_DESC_CURR_SECTOR] -> lib_fat16_fr_sector_in_cluster + //Copy filesize och filepos from descriptor (8 bytes for two 32-bit variables) + ASM + ldy #5 + ldx #0 +lib_fat16_fr_l1 + lda (lib_fat16_fr_descr),y + sta |lib_fat16_fr_filesize_lo|,x + iny + inx + cpx #8 + bne lib_fat16_fr_l1 + ENDASM + + IF lib_fat16_fr_current_cluster == 0 + //special case - empty (0-size) file is valid to open for reading but only 0 bytes can be read. + IF lib_fat16_fr_filesize_lo == 0 + IF lib_fat16_fr_filesize_hi == 0 + LET lib_fat16_fr_read_count = 0 + LET lib_fat16_fr_status = 0 //exit is set to success + ENDIF + ENDIF + SUBEND //error (unless special above) - file is not open + ENDIF + + //------------------------------------------- + //TASK2 - Adjust clusters / sectors if needed + + //Did the previous read put us past the end of the cluster? + IF lib_fat16_fr_sector_in_cluster >= lib_fat16_sectors_per_cluster + + //Call will update the "lib_fat16_active_dir_cluster" to the next cluster link. + CALL lib_fat16_int_get_cluster_link ( lib_fat16_fr_current_cluster lib_fat16_fr_callstat ) + IF lib_fat16_fr_callstat != 0 + SUBEND //internal error + ENDIF + LET lib_fat16_fr_sector_in_cluster = 0 + ENDIF + + //now if ( "File total size in bytes" - "Current position in file" ) < 512 then the sector we just read contains the EOF + //subtracting 32 bit values is so very funny on 6502 :) + ASM + sec + lda |lib_fat16_fr_filesize_lo| + sbc |lib_fat16_fr_filepos_lo| + sta |lib_fat16_fr_fileleft_lo| + lda |lib_fat16_fr_filesize_lo|+1 + sbc |lib_fat16_fr_filepos_lo|+1 + sta |lib_fat16_fr_fileleft_lo|+1 + lda |lib_fat16_fr_filesize_hi| + sbc |lib_fat16_fr_filepos_hi| + sta |lib_fat16_fr_fileleft_hi| + lda |lib_fat16_fr_filesize_hi|+1 + sbc |lib_fat16_fr_filepos_hi|+1 + sta |lib_fat16_fr_fileleft_hi|+1 + ENDASM + + //---------------------------------------------- + //TASK3 - Do the actual read + + + LET lib_fat16_fr_read_count = LIB_FAT16_SECTOR_SIZE + IF lib_fat16_fr_fileleft_hi == 0 + IF lib_fat16_fr_fileleft_lo < LIB_FAT16_SECTOR_SIZE + //We've just read to the end of the file + //of course we could know that from the link-table also. but we want to report + //the number of bytes read to the caller. + LET lib_fat16_fr_read_count = lib_fat16_fr_fileleft_lo + + CALL lib_fat16_int_read_cluster_sector_part ( lib_fat16_fr_current_cluster lib_fat16_fr_sector_in_cluster lib_fat16_fr_block_ptr 0 lib_fat16_fr_read_count lib_fat16_fr_callstat ) + IF lib_fat16_fr_callstat != 0 + SUBEND //internal error + ENDIF + + GOTO lib_fat16_fr_skipelse1 + ENDIF + ENDIF + + CALL lib_fat16_int_read_cluster_sector ( lib_fat16_fr_current_cluster lib_fat16_fr_sector_in_cluster lib_fat16_fr_block_ptr lib_fat16_fr_callstat ) + IF lib_fat16_fr_callstat != 0 + SUBEND //internal error + ENDIF + +LABEL lib_fat16_fr_skipelse1 + INC lib_fat16_fr_sector_in_cluster //we've just read it so we add the offset + + //---------------------------------------------- + //TASK4 - Recalc filepos + // Write back data to the file descriptor + PUTASWORD@ lib_fat16_fr_descr[FAT16_DESC_CURR_CLUSTER] VALUE lib_fat16_fr_current_cluster + ASM + ldy #2 + lda |lib_fat16_fr_sector_in_cluster| + sta (lib_fat16_fr_descr),y + + ; increment the position (32-bit add) + + ldy #9 + clc + lda (lib_fat16_fr_descr),y + adc lib_fat16_fr_read_count ; lowbyte of the sector read + sta (lib_fat16_fr_descr),y + iny + lda (lib_fat16_fr_descr),y + adc lib_fat16_fr_read_count+1 ; hibyte of the sector read + sta (lib_fat16_fr_descr),y + iny + lda (lib_fat16_fr_descr),y + adc #0 ; does not apply (sector is max 512 bytes) + sta (lib_fat16_fr_descr),y + iny + lda (lib_fat16_fr_descr),y + adc #0 ; does not apply (sector is max 512 bytes) + sta (lib_fat16_fr_descr),y + ENDASM + + LET lib_fat16_fr_status = 0 +FEND + +//------------------------------------------------------------------------ +// lib_fat16_fblockwrite +// +// Purpose: +// To write a block (512) of data to a file, opened for writing. +// If the complete size of the file is not dividable by 512, the size of +// the last block is allowed to be less than 512 bytes. +// +// Params: +// +// +// +//------------------------------------------------------------------------ + +#IFDEF __LIB_FAT16_WRITESUPPORT + +//WORD lib_fat16_fw_descriptor_ptr +WORD lib_fat16_fw_descr @ $49 +WORD lib_fat16_fw_block_ptr +WORD lib_fat16_fw_block_size +BYTE lib_fat16_fw_status +FUNC lib_fat16_fblockwrite ( lib_fat16_fw_descr lib_fat16_fw_block_ptr lib_fat16_fw_block_size out:lib_fat16_fw_status ) + + WORD lib_fat16_fw_start_cluster + WORD lib_fat16_fw_current_cluster + WORD lib_fat16_fw_sector_in_cluster + WORD lib_fat16_fw_new_cluster + BYTE lib_fat16_fw_current_filemode + BYTE lib_fat16_fw_callstat + + LET lib_fat16_fw_status = 1 //assume error + + //LET lib_fat16_fw_descr = lib_fat16_fw_descriptor_ptr + //---------------------------------------------- + //TASK1 - Read up vital data from filedescriptor + + PEEK lib_fat16_fw_descr[FAT16_DESC_FILEMODE] -> lib_fat16_fw_current_filemode + IF lib_fat16_fw_current_filemode != LIB_FAT16_FILEMODE_WRITE + SUBEND //error. file not open for writing. + ENDIF + GETASWORD@ lib_fat16_fw_descr[FAT16_DESC_FIRST_CLUSTER] -> lib_fat16_fw_start_cluster + GETASWORD@ lib_fat16_fw_descr[FAT16_DESC_CURR_CLUSTER] -> lib_fat16_fw_current_cluster + PEEK lib_fat16_fw_descr[FAT16_DESC_CURR_SECTOR] -> lib_fat16_fw_sector_in_cluster + + //This assures that only the last sector (block) written can be other than the size of a sector. + IF lib_fat16_fw_block_size != LIB_FAT16_SECTOR_SIZE + IF lib_fat16_fw_block_size > LIB_FAT16_SECTOR_SIZE + SUBEND //error - not supported + ENDIF + //LET lib_fat16_fw_current_filemode = LIB_FAT16_FILEMODE_WRITE_ENDED + POKE lib_fat16_fw_descr[FAT16_DESC_FILEMODE] , LIB_FAT16_FILEMODE_WRITE_ENDED + IF lib_fat16_fw_block_size == 0 //Writing 0 bytes to a valid open write-file is OK (but nothing happens) + LET lib_fat16_fw_status = 0 + SUBEND + ENDIF + ENDIF + + //---------------------------------------------- + //TASK2 - Is this the first block in the file? + IF lib_fat16_fw_start_cluster == 0 + CALL lib_fat16_int_get_free_cluster ( lib_fat16_fw_start_cluster lib_fat16_fw_callstat ) + IF lib_fat16_fw_callstat != 0 + SUBEND //Could not get another cluster (disk probably full) + ENDIF + PUTASWORD@ lib_fat16_fw_descr[FAT16_DESC_FIRST_CLUSTER] VALUE lib_fat16_fw_start_cluster + LET lib_fat16_fw_current_cluster = lib_fat16_fw_start_cluster + //As this file is just opened sector_in_cluster is set to 0 + //LET lib_fat16_fw_sector_in_cluster = 0 + ENDIF + + //---------------------------------------------- + //TASK3 - Time to allocate another cluster? + IF lib_fat16_fw_sector_in_cluster >= lib_fat16_sectors_per_cluster + CALL lib_fat16_int_get_free_cluster ( lib_fat16_fw_new_cluster lib_fat16_fw_callstat ) + IF lib_fat16_fw_callstat != 0 + SUBEND //Could not get another cluster (disk probably full) + ENDIF + //Update the cluster link from last cluster to the new one. + CALL lib_fat16_int_set_cluster_link ( lib_fat16_fw_current_cluster lib_fat16_fw_new_cluster lib_fat16_fw_callstat ) + IF lib_fat16_fw_callstat != 0 + SUBEND //internal error + ENDIF + LET lib_fat16_fw_sector_in_cluster = 0 + LET lib_fat16_fw_current_cluster = lib_fat16_fw_new_cluster + ENDIF + + //---------------------------------------------- + //TASK4 - Write the data! + CALL lib_fat16_int_write_cluster_sector_part ( lib_fat16_fw_current_cluster lib_fat16_fw_sector_in_cluster lib_fat16_fw_block_ptr 0 lib_fat16_fw_block_size lib_fat16_fw_callstat ) + IF lib_fat16_fw_callstat != 0 + SUBEND //internal error + ENDIF + INC lib_fat16_fw_sector_in_cluster //step forward for next write + + //---------------------------------------------- + //TASK5 - Update descriptor (and filepos calc) + + PUTASWORD@ lib_fat16_fw_descr[FAT16_DESC_CURR_CLUSTER] VALUE lib_fat16_fw_current_cluster + POKE lib_fat16_fw_descr[FAT16_DESC_CURR_SECTOR] , lib_fat16_fw_sector_in_cluster + + ASM + ; increment the position (32-bit add) + + ldy #9 + clc + lda (lib_fat16_fw_descr),y + adc lib_fat16_fw_block_size ; lowbyte of the sector read + sta (lib_fat16_fw_descr),y + iny + lda (lib_fat16_fw_descr),y + adc lib_fat16_fw_block_size+1 ; hibyte of the sector read + sta (lib_fat16_fw_descr),y + iny + lda (lib_fat16_fw_descr),y + adc #0 ; does not apply (sector is max 512 bytes) + sta (lib_fat16_fw_descr),y + iny + lda (lib_fat16_fw_descr),y + adc #0 ; does not apply (sector is max 512 bytes) + sta (lib_fat16_fw_descr),y + ENDASM + + LET lib_fat16_fw_status = 0 //if we got here it's a success +FEND + +#IFEND + + +LABEL lib_fat16_skip + +#IFEND diff --git a/lib/fat16/fat16lowlib.c65 b/lib/fat16/fat16lowlib.c65 new file mode 100644 index 0000000..1c9d2bf --- /dev/null +++ b/lib/fat16/fat16lowlib.c65 @@ -0,0 +1,686 @@ +//------------------------------------------------------------------------ +// FAT16 Library Low Level +// +// Author: Mattias Hansson +// Target: Commodore 64 with MMC64 interface +// (possible future conversions for other platforms possible) +// +// Purpose: To provide lowlevel access routines for the FAT16 Library +// +// Routines: +// lib_fat16_int_read_cluster_sector (INTERNAL) +// +// +// +//------------------------------------------------------------------------ + + +#IFNDEF __LIB_FAT16LOW +#DEFINE __LIB_FAT16LOW = 1 + +GOTO lib_fat16low_skip + + +//------------------------------------------------------------------------ +// FAT16 constants +//------------------------------------------------------------------------ +WORD CONST LIB_FAT16_CLUSTER_FREE = $0000 +WORD CONST LIB_FAT16_CLUSTER_END = $fff0 //this value or above = clusterchain ended +BYTE CONST LIB_FAT16_DIRENTRY_SIZE = $20 +BYTE CONST LIB_FAT16_DELETED_FILE = $e5 +BYTE CONST LIB_FAT16_END_OF_DIR = $00 +WORD CONST LIB_FAT16_SECTOR_SIZE = 512 + +//******Do NOT EVER access this var as a regular variable*********** +//special: this is NOT a regular variable for usage!!!!!!!!!!!!!!!!! +//The "Address" defines how many files may be open simultainously!!! +//If you want to change the number of open files, change the!!!!!!!! +//address to the number you of files you want open!!!!!!!!!!!!!!!!!! +BYTE lib_fat16_max_open_files @ __LIB_FAT16_MAX_OPEN_FILES +//******Do NOT EVER access this var as a regular variable*********** + +BYTE CONST LIB_FAT16_FILEMODE_READ = 0 +BYTE CONST LIB_FAT16_FILEMODE_WRITE = 1 +//This filemode is special as it's only valid for fclose() +//This file gets set when blockwriting another size then $512 +//as further writes would not be alligned with the sectors. +BYTE CONST LIB_FAT16_FILEMODE_WRITE_ENDED = 2 +//BYTE CONST LIB_FAT16_FILEMODE_READWRITE = 2 //future expansion + +#IFDEF __LIB_FAT16_WRITESUPPORT +//This constant controls how many consecutive clusters will be controlled +//for beeing free when mounting a partition for read/write access. +//Generally speaking this is good when having an unfragmented disk/card +//with just a little data on it, as i shortens the mount time. + +//However if you'll be writing much data (like several megabytes) you'll +//probably want to set this to $ffff (no limit) since this will not add +//any performance penalty (for searching new free clusters) when you've +//reached the limit. + +#IFDEF __LIB_MMC64_OPTIMIZE_SPEED + WORD CONST LIB_FAT16_WRITE_CLUSTERS_MAX = $400 +#IFEND +#IFNDEF __LIB_MMC64_OPTIMIZE_SPEED + //Explanation: if we don't cache the reads, every single cluster + //we search (in fat) will force the lib to re-read the sector again + //which will take painfully much time + #PRINT __LIB_MMC64_OPTIMIZE_SPEED combined with __LIB_FAT16_WRITESUPPORT is catastrophic performance-wise. + WORD CONST LIB_FAT16_WRITE_CLUSTERS_MAX = $10 +#IFEND + + +#IFEND + +//------------------------------------------------------------------------ +// FAT16 common variables +//------------------------------------------------------------------------ + +//Info for developers: the lib_fat16_buffer_ptr must point out an 512 byte area +//You can choose to do so by a constant or variable. +//Default is a variable that points out an area reserved inside this library +//called "lib_fat16_fatbuffer". If you change this to a constant, remark +//the "lib_fat16_fatbuffer" area to save the unused filler. + +//WORD CONST lib_fat16_buffer_ptr = $c800 //change this for your particular usage +// //(size needed is "one sector" = 512 bytes ) + +WORD lib_fat16_buffer_ptr +BYTE lib_fat16_sectors_per_cluster // needed for cluster reading routine +WORD lib_fat16_reserved_sectors // offset from FAT16 boot record to FATs (file allocation tables) +BYTE lib_fat16_nr_of_fats // self*sectors_per_fat = offset from FATs to the root directory +WORD lib_fat16_sectors_per_fat // (see above) +WORD lib_fat16_rootdir_entries // Max file-entries in the ROOT directory + // ( self * size of directory entry (32) ) / 512 = + // offset from ROOT directory to DATA area (where clusters begin. + // N.B. First cluster here is #2 (!)) +WORD lib_fat16_rootdir_sectors // calculated size of rootdir measured in sectors. + +#IFDEF __LIB_FAT16_WRITESUPPORT + +WORD lib_fat16_total_clusters = 0 // ( sectors_per_fat * bytes_per_sector ($200) ) / 2 == ( sectors_per_fat * $100 ). + +WORD lib_fat16_first_write_cluster = $ffff //these two are used internally by lib_fat16_int_find_unused_clusters and +WORD lib_fat16_last_write_cluster = $0000 //lib_fat16_fwriteblock to find new areas for writing bit by bit (as needed) +WORD lib_fat16_total_sectors_lo +WORD lib_fat16_total_sectors_hi + +#IFEND + +//Temp ZP-variables for usage in the lib. +WORD lib_fat16_tempw @ $3f +WORD lib_fat16_tempw2 @ $4b +BYTE lib_fat16_tempb @ $41 + +//------------------------------------------------------------------------ +// 512 byte internal buffer for FAT16 operations. +//------------------------------------------------------------------------ +LABEL lib_fat16_fatbuffer +ASM + !fill 512 +ENDASM + +//------------------------------------------------------------------------ +// lib_fat16_int_read_cluster_sector (INTERNAL) +// +// Purpose: +// 1. To read out a specific sector in a specific cluster +// or +// 2. To read out a sector in the root-dir +// +// Parameters: +// lib_fat16_int_rc_cluster - cluster to read. special: 0 = root-dir +// lib_fat16_int_rc_sector - sector # inside the cluster (0..lib_fat16_sectors_per_cluster-1) +// special: if in root-dir (0..lib_fat16_rootdir_sectors-1) +// lib_fat16_int_rc_buffer_ptr - pointer, where to put the read sector in memory +// lib_fat16_int_rc_status - 0 = OK. !0 = error. +// +// Preparation: lib_fat16_mount_partition +// +// Usage of lib temp variables: +// lib_fat16_tempw2 +// lib_fat16_tempb +//------------------------------------------------------------------------ + +WORD lib_fat16_int_rc_cluster +WORD lib_fat16_int_rc_sector +WORD lib_fat16_int_rc_buffer_ptr +BYTE lib_fat16_int_rc_status + +FUNC lib_fat16_int_read_cluster_sector ( lib_fat16_int_rc_cluster lib_fat16_int_rc_sector lib_fat16_int_rc_buffer_ptr out:lib_fat16_int_rc_status ) +WORD first_pos // in bytes +WORD read_count + +LET first_pos = 0 +LET read_count = LIB_FAT16_SECTOR_SIZE + +FUNC lib_fat16_int_read_cluster_sector_part ( lib_fat16_int_rc_cluster lib_fat16_int_rc_sector lib_fat16_int_rc_buffer_ptr first_pos read_count out:lib_fat16_int_rc_status ) + + BYTE spc_count + WORD csect_offset_lo + BYTE csect_offset_hi + BYTE callstat //status for internal calls + + + LET lib_fat16_int_rc_status = 1 //indicate an error at first (we SUBEND out if things go wrong) + + IF lib_fat16_int_rc_cluster == 0 //the root directory + IF lib_fat16_int_rc_sector >= lib_fat16_rootdir_sectors + SUBEND // caller tried to make us read past the root-dir region. probably an internal error. + ENDIF + //Now we must add the start sector for the rootdir with the sector requested inside it, to get the right one + ASM + clc + lda lib_fat16_rootdir_sector_lo + adc lib_fat16_int_rc_sector + sta lib_fat16_tempw2 + lda lib_fat16_rootdir_sector_lo+1 + adc lib_fat16_int_rc_sector+1 + sta lib_fat16_tempw2+1 + lda lib_fat16_rootdir_sector_hi + adc #0 + sta lib_fat16_tempb + ENDASM + CALL lib_mmc64_readsector ( lib_fat16_tempw2 lib_fat16_tempb lib_fat16_int_rc_buffer_ptr callstat ) + IF callstat != 0 + SUBEND + ENDIF + GOTO lib_fat16_int_rc_exit_success + ENDIF + + //Else this is a real cluster in the dataarea that's about to be read + IF lib_fat16_int_rc_sector >= lib_fat16_sectors_per_cluster + SUBEND // caller passed a sector offset beyond the end of the cluster. probably an internal error + ENDIF + + //Adjust for data-area starting with cluster #2 + SUBT lib_fat16_int_rc_cluster - 2 -> lib_fat16_int_rc_cluster + + //Now we must multiply the clusternr with "nr of sectors per cluster" to + //get a valid sector-offset into the data-area + LET csect_offset_lo = lib_fat16_int_rc_cluster + LET csect_offset_hi = 0 + LET spc_count = lib_fat16_sectors_per_cluster + //First bit unimportant (1 sector per cluster and we're done here!) + ASM + lsr |spc_count| + ENDASM + WHILE spc_count + ASM + clc + rol |csect_offset_lo| + rol |csect_offset_lo|+1 + rol |csect_offset_hi| + + lsr |spc_count| + ENDASM + WEND + + // "real sector to access" = "start of data area" + "cluster sector offset" + lib_fat16_int_rc_sector (sector # in cluster) + //first we do "start of data area" + "cluster sector offset" + ASM + clc + lda lib_fat16_data_sector_lo + adc |csect_offset_lo| + sta lib_fat16_tempw2 + lda lib_fat16_data_sector_lo+1 + adc |csect_offset_lo|+1 + sta lib_fat16_tempw2+1 + lda lib_fat16_data_sector_hi + adc |csect_offset_hi| + sta lib_fat16_tempb + ENDASM + //now we add the lib_fat16_int_rc_sector + ASM + clc + lda lib_fat16_tempw2 + adc |lib_fat16_int_rc_sector| + sta lib_fat16_tempw2 + lda lib_fat16_tempw2+1 + adc #0 ; we know that "sectors per cluster" cannot exceed 128. + sta lib_fat16_tempw2+1 + lda lib_fat16_tempb + adc #0 + sta lib_fat16_tempb + ENDASM + + //Mostly we read whole sectors. However if the file ends in (( size % 0x200 )!=0) the file ends someplace inside + //a sector, and we must read the last sector partly. + CALL lib_mmc64_readsector_part ( lib_fat16_tempw2 lib_fat16_tempb lib_fat16_int_rc_buffer_ptr first_pos read_count callstat ) + IF callstat != 0 + SUBEND + ENDIF + + +LABEL lib_fat16_int_rc_exit_success + + LET lib_fat16_int_rc_status = 0 +FEND + +//------------------------------------------------------------------------ +// lib_fat16_int_write_cluster_sector (INTERNAL) +// +// Purpose: +// 1. To write a specific sector in a specific cluster +// or +// 2. To write a sector in the root-dir +// +// Parameters: +// lib_fat16_int_wc_cluster - cluster to write. special: 0 = root-dir +// lib_fat16_int_wc_sector - sector # inside the cluster (0..lib_fat16_sectors_per_cluster-1) +// special: if in root-dir (0..lib_fat16_rootdir_sectors-1) +// lib_fat16_int_wc_buffer_ptr - pointer, sector-data in memory to write +// lib_fat16_int_wc_status - 0 = OK. !0 = error. +// +// Preparation: lib_fat16_mount_partition +// +// Usage of lib temp variables: +// lib_fat16_tempw2 +// lib_fat16_tempb +//------------------------------------------------------------------------ + +#IFDEF __LIB_FAT16_WRITESUPPORT + +WORD lib_fat16_int_wc_cluster +WORD lib_fat16_int_wc_sector +WORD lib_fat16_int_wc_buffer_ptr +BYTE lib_fat16_int_wc_status + +FUNC lib_fat16_int_write_cluster_sector ( lib_fat16_int_wc_cluster lib_fat16_int_wc_sector lib_fat16_int_wc_buffer_ptr out:lib_fat16_int_wc_status ) +WORD first_pos // in bytes +WORD write_count + +LET first_pos = 0 +LET write_count = LIB_FAT16_SECTOR_SIZE + +FUNC lib_fat16_int_write_cluster_sector_part ( lib_fat16_int_wc_cluster lib_fat16_int_wc_sector lib_fat16_int_wc_buffer_ptr first_pos write_count out:lib_fat16_int_wc_status ) + + BYTE spc_count + WORD csect_offset_lo + BYTE csect_offset_hi + BYTE callstat //status for internal calls + + + LET lib_fat16_int_wc_status = 1 //indicate an error at first (we SUBEND out if things go wrong) + + IF lib_fat16_int_wc_cluster == 0 //the root directory + IF lib_fat16_int_wc_sector >= lib_fat16_rootdir_sectors + SUBEND // caller tried to make us read past the root-dir region. probably an internal error. + ENDIF + //Now we must add the start sector for the rootdir with the sector requested inside it, to get the right one + ASM + clc + lda lib_fat16_rootdir_sector_lo + adc lib_fat16_int_wc_sector + sta lib_fat16_tempw2 + lda lib_fat16_rootdir_sector_lo+1 + adc lib_fat16_int_wc_sector+1 + sta lib_fat16_tempw2+1 + lda lib_fat16_rootdir_sector_hi + adc #0 + sta lib_fat16_tempb + ENDASM + CALL lib_mmc64_writesector ( lib_fat16_tempw2 lib_fat16_tempb lib_fat16_int_wc_buffer_ptr callstat ) + IF callstat != 0 + SUBEND + ENDIF + GOTO lib_fat16_int_wc_exit_success + ENDIF + + //Else this is a real cluster in the dataarea that's about to be read + IF lib_fat16_int_wc_sector >= lib_fat16_sectors_per_cluster + SUBEND // caller passed a sector offset beyond the end of the cluster. probably an internal error + ENDIF + + //Adjust for data-area starting with cluster #2 + SUBT lib_fat16_int_wc_cluster - 2 -> lib_fat16_int_wc_cluster + + //Now we must multiply the clusternr with "nr of sectors per cluster" to + //get a valid sector-offset into the data-area + LET csect_offset_lo = lib_fat16_int_wc_cluster + LET csect_offset_hi = 0 + LET spc_count = lib_fat16_sectors_per_cluster + //First bit unimportant (1 sector per cluster and we're done here!) + ASM + lsr |spc_count| + ENDASM + WHILE spc_count + ASM + clc + rol |csect_offset_lo| + rol |csect_offset_lo|+1 + rol |csect_offset_hi| + + lsr |spc_count| + ENDASM + WEND + + // "real sector to access" = "start of data area" + "cluster sector offset" + lib_fat16_int_wc_sector (sector # in cluster) + //first we do "start of data area" + "cluster sector offset" + ASM + clc + lda lib_fat16_data_sector_lo + adc |csect_offset_lo| + sta lib_fat16_tempw2 + lda lib_fat16_data_sector_lo+1 + adc |csect_offset_lo|+1 + sta lib_fat16_tempw2+1 + lda lib_fat16_data_sector_hi + adc |csect_offset_hi| + sta lib_fat16_tempb + ENDASM + //now we add the lib_fat16_int_wc_sector + ASM + clc + lda lib_fat16_tempw2 + adc |lib_fat16_int_wc_sector| + sta lib_fat16_tempw2 + lda lib_fat16_tempw2+1 + adc #0 ; we know that "sectors per cluster" cannot exceed 128. + sta lib_fat16_tempw2+1 + lda lib_fat16_tempb + adc #0 + sta lib_fat16_tempb + ENDASM + + //Mostly we read whole sectors. However if the file ends in (( size % 0x200 )!=0) the file ends someplace inside + //a sector, and we must read the last sector partly. + CALL lib_mmc64_writesector_part ( lib_fat16_tempw2 lib_fat16_tempb lib_fat16_int_wc_buffer_ptr first_pos write_count callstat ) + IF callstat != 0 + SUBEND + ENDIF + + +LABEL lib_fat16_int_wc_exit_success + + LET lib_fat16_int_wc_status = 0 +FEND + +#IFEND + +//------------------------------------------------------------------------ +// lib_fat16_int_get_cluster_link (INTERNAL) +// +// Purpose: +// Return the next cluster nr for a given cluster. +// In the FAT the position for the current cluster holds the link +// to the next cluster in the "chain". +// special values are: $0000 unallocated, $ffef-$ffff end-of-cluster-chain +// +// Parameters: +// lib_fat16_int_nc_cluster_nr - we send in our current cluster number +// and we get the next returned in the +// same param +// lib_fat16_int_nc_status - the status operation. 0 = success +// !0 = failure (probably internal) +// +// Preparation: lib_fat16_dirnext (caller) +// (unfinished) lib_fat16_fopen +// +// Usage of lib temp variables: +// lib_fat16_tempw2 +// lib_fat16_tempb +// +// +// lib_fat16_int_set_cluster_link (INTERNAL) +// +// Purpose: to update the two FAT's with a new value for the cluster-link +// +// Additional parameter: +// lib_fat16_int_nc_target_cluster_nr = the new cluster-link to write +// +// Sorry for the clutter, but this way saves a whole lot of code to +// support write-access //Mattias +// +// A note on caching: Since a fat-sector is read, updated and written back +// at once we don't need to invalidate the "caching" of the FAT-buffer. +// (a later cached read will still be valid) +//------------------------------------------------------------------------ + + +WORD lib_fat16_int_nc_cluster_nr +BYTE lib_fat16_int_nc_status + +#IFDEF __LIB_FAT16_WRITESUPPORT + +BYTE lib_fat16_int_nc_writeflag +WORD lib_fat16_int_nc_target_cluster_nr +FUNC lib_fat16_int_set_cluster_link ( lib_fat16_int_nc_cluster_nr lib_fat16_int_nc_target_cluster_nr out:lib_fat16_int_nc_status ) + + LET lib_fat16_int_nc_writeflag = 1 + +#IFEND + +FUNC lib_fat16_int_get_cluster_link ( io:lib_fat16_int_nc_cluster_nr out:lib_fat16_int_nc_status ) + + BYTE sect_offset + BYTE callstat + WORD sector_lo + BYTE sector_hi + + //MHTAG 20050927 - this internal variable was replaced internally for lib_fat16_tempw2 (ZP-vars == better code) + //WORD lib_fat16_int_nc_link_offset_bytes + + LET lib_fat16_int_nc_status = 1 + + //Clusters in the data-area are numbered from 2 (i.e. 2 = offset 0 ) + IF lib_fat16_int_nc_cluster_nr < 2 + SUBEND + ENDIF + + //Values from $fff0 are "bad", reserved or cluster-chain end markers. (endmark>=$fff8) + //We don't care here, since we cannot follow longer anyhow. + IF lib_fat16_int_nc_cluster_nr >= LIB_FAT16_CLUSTER_END + SUBEND + ENDIF + + // exp for the sector offset on disk for requested cluster entry: + // "first FAT sector" + ( (cluster_nr * "sizeof cluster entry (2 bytes in FAT16 )") / 512 ) + // simplified = "first FAT sector" + ( cluster_nr / 256 ) i.e. the high byte of "cluster nr" + // div 256 = shift left 8 bits, so we simply take the highbyte and move it to the lowbyte (see below) + ASM + lda lib_fat16_int_nc_cluster_nr+1 + sta |sect_offset| + ENDASM + //and now we add the "first FAT sector" + ASM + clc + lda lib_fat16_fat1_sector_lo + adc |sect_offset| + sta lib_fat16_tempw2 + lda lib_fat16_fat1_sector_lo+1 + adc #0 + sta lib_fat16_tempw2+1 + lda lib_fat16_fat1_sector_hi + adc #0 + sta lib_fat16_tempb + ENDASM + + LET sector_lo = lib_fat16_tempw2 + LET sector_hi = lib_fat16_tempb + + //read up the sector + CALL lib_mmc64_readsector ( sector_lo sector_hi lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND //pass the read error on to the caller. + ENDIF + + //calculate the position of the link + //Following the logic above the offset the the position of the link in the sector must be in the low-byte + //However since every entry is 2 bytes long, we must multiply the lowbyte with 2 to get the offset in bytes + // + //MHTAG 20050927 - all usage of lib_fat16_tempw2 below in this func is replacement for lib_fat16_int_nc_link_offset_bytes + ASM + clc + lda lib_fat16_int_nc_cluster_nr + rol + sta lib_fat16_tempw2 + lda #0 + rol + sta lib_fat16_tempw2+1 + ENDASM + + ADD lib_fat16_tempw2 + lib_fat16_buffer_ptr -> lib_fat16_tempw2 + +#IFDEF __LIB_FAT16_WRITESUPPORT + //if readaccess - skit this section + IF lib_fat16_int_nc_writeflag == 1 + //this section updates the cluster link value and writes the fat-sector back to disk + PUTASWORD@ lib_fat16_tempw2 VALUE lib_fat16_int_nc_target_cluster_nr + + CALL lib_mmc64_writesector ( sector_lo sector_hi lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND //pass the read error on to the caller. + ENDIF + + //now we must update the second FAT. + //change here to support more than 2 FATs + //reason for decision to support only 2 = VERY standard. Even linux vfat driver relies on two FATs + //24-bit addition of the size of one fat to get right offset + ASM + clc + lda |sector_lo| + adc lib_fat16_sectors_per_fat + sta |sector_lo| + lda |sector_lo|+1 + adc lib_fat16_sectors_per_fat+1 + sta |sector_lo|+1 + lda |sector_hi| + adc #0 + sta |sector_hi| + ENDASM + //and finally the actual write. + CALL lib_mmc64_writesector ( sector_lo sector_hi lib_fat16_buffer_ptr callstat ) + IF callstat != 0 + SUBEND //pass the read error on to the caller. + ENDIF + + GOTO lib_fat16_int_nc_skipread + ENDIF +#IFEND + + //return it + GETASWORD@ lib_fat16_tempw2 -> lib_fat16_int_nc_cluster_nr + +#IFDEF __LIB_FAT16_WRITESUPPORT + LABEL lib_fat16_int_nc_skipread + LET lib_fat16_int_nc_writeflag = 0 //reset for next access of this function +#IFEND + + LET lib_fat16_int_nc_status = 0 +FEND + +//------------------------------------------------------------------------ +// lib_fat16_int_search_free_clusters +// +// Purpose: To find clusters on the mounted drive that are free to allocate +// for new filedata. +// +// Usage: When mounting a partition for R/W access this routine is called +// to find unused clusters, in the event that a write is issued. The +// routine searches for a "cluster hole" of max LIB_FAT16_WRITE_CLUSTERS_MAX +// continous clusters (to reduce search time). It updates the variables +// lib_fat16_first_write_cluster and lib_fat16_last_write_cluster with +// the limits of the found "cluster hole" (range of unallocated clusters) +// +// If writing has completely filled previously discovered "cluster hole" +// the write routines may call this routine again to try to find more +// unclaimed disc space to fill. +// +// Method: +// Space for writing is searched from the end of the FAT (last clusters) +// and backwards until we find free clusters. When we find a free cluster +// we set the "lib_fat16_last_write_cluster" marker and continue backwards +// for a while (until we reach a count or until we hit data again. This is +// where we set the "lib_fat16_first_write_cluster" marker. +// After this operation we know about an area between the two markers that +// consists of free clusters, ready to use for writing file-data +//------------------------------------------------------------------------ + +#IFDEF __LIB_FAT16_WRITESUPPORT + +BYTE lib_fat16_int_fu_status + +FUNC lib_fat16_int_search_free_clusters ( out:lib_fat16_int_fu_status ) + + WORD active_cluster + WORD active_link + WORD cluster_search_max + BYTE callstat + + LET lib_fat16_int_fu_status = 1 //default = error exit + + //----------------------------------------------------- + //TASK1 - find out where to start the search + LET active_cluster = lib_fat16_first_write_cluster + IF active_cluster == $ffff //if free area is uninitialized yet + LET active_cluster = lib_fat16_total_clusters //start the search at the end of the fat + ENDIF + + //----------------------------------------------------- + //TASK2 - rewind until we reach: + // a) free clusters + // b) start of fat + + WHILE 1 + LET active_link = active_cluster + CALL lib_fat16_int_get_cluster_link ( active_link callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + IF active_link == 0 + BREAK + ENDIF + DEC active_cluster + WEND + + IF active_cluster < 2 //disk is full. Unable to find a single free cluster! + SUBEND + ENDIF + + LET lib_fat16_last_write_cluster = active_cluster + + //----------------------------------------------------- + //TASK3 - rewind until we reach the end of the free area. + + //LET active_link = active_cluster + LET cluster_search_max = LIB_FAT16_WRITE_CLUSTERS_MAX + WHILE active_link == LIB_FAT16_CLUSTER_FREE + DEC cluster_search_max + IF cluster_search_max == 0 + BREAK + ENDIF + DEC active_cluster + LET active_link = active_cluster + CALL lib_fat16_int_get_cluster_link ( active_link callstat ) + IF callstat != 0 + SUBEND //internal error + ENDIF + WEND + + IF active_cluster < 2 + LET active_cluster = 1 + ENDIF + + INC active_cluster //this one was used so we need the next one (the first free) + LET lib_fat16_first_write_cluster = active_cluster + + //now we should have found a area free for writing. + + //MHTAG 20051114 - debug writes out the free area to the screen + //CALL lib_dbg_hexoutw ( lib_fat16_first_write_cluster ) + //CALL lib_dbg_outlf + //CALL lib_dbg_hexoutw ( lib_fat16_last_write_cluster ) + //CALL lib_dbg_outlf + + + + LET lib_fat16_int_fu_status = 0 //if we got this far it's a success + +FEND + +#IFEND + +LABEL lib_fat16low_skip + +#IFEND diff --git a/lib/fat16/mmc64lib.c65 b/lib/fat16/mmc64lib.c65 new file mode 100644 index 0000000..2e569d4 --- /dev/null +++ b/lib/fat16/mmc64lib.c65 @@ -0,0 +1,709 @@ +//------------------------------------------------------------------------ +// MMC64 ReadWrite Library +// +// Author: Mattias Hansson +// Copyright (c) : 2005 Mattias Hansson +// License: GNU GPL 2 +// Language: 65CM v0.4+ +// Dependencies: +// ZP usage: $fb-$fc (pointer to buffer) +// Target: Commodore 64 with MMC64 interface +// +// functions: +// ( MMC cartridge level ) +// lib_mmc64_enable +// lib_mmc64_status +// ( MMC-card or SD-card level ) +// lib_mmc64_cardinit +// lib_mmc64_readsector +// +// External compiler directive support: +// __LIB_MMC64_OPTIMIZE_SIZE - define this to build lib smaller +// but slower (default UNDEFINED) +// __LIB_MMC64_READONLY - define this to build the lib to support +// only read operations (decreasing it's size) +// (default UNDEFINED) +// +// Note: Some of the code is copied from Olivers Achtens democode. +// I havent removed any comments or anything and it's very clear to +// see, what comes from his paper caller "mmc64prog.txt". These +// parts of the code are, if I understand it right, in the public domain +// as just code-snippets to demonstrate how the MMC64 can be accessed. +// Just want to clarify everything, and give credit where credit due. +// +// Note2: Communication with MMC/SD in this lib is implemented to always +// Align with sectorsizes i.e. $200. i.e. AccessAddress % $200 is always +// zero(!). Every single read or write access is always $200 long. +// (Reason: This seams to keep all MMC/SD iv'e tried happy and provides +// realiable communications.) +// +// Mattias Hansson +//------------------------------------------------------------------------ + +#IFNDEF __LIB_MMC64 +#DEFINE __LIB_MMC64 = 1 + +//Define __LIB_MMC64_OPTIMIZE_SIZE externally to optimize for size instead of speed. +#IFNDEF __LIB_MMC64_OPTIMIZE_SIZE + #DEFINE __LIB_MMC64_OPTIMIZE_SPEED = 1 +#IFEND + +//This define is needed to include the code needed for writesupport. +#IFNDEF __LIB_MMC64_READONLY + #PRINT Write access in mmc65lib.c65 is experimental!!! + #DEFINE __LIB_MMC64_WRITESUPPORT = 1 +#IFEND + +GOTO lib_mmc64_skip + + +//------------------------------------------------------------------------ +// buffer pointer zeropage variable used for data transfer function +//------------------------------------------------------------------------ +WORD lib_mmc64_buffer_ptr @ $fb + +//------------------------------------------------------------------------ +// MMC64 reader status constants +// +// Everything except LIB_MMC64_R_OK +// is an error. +// Constants in order of importance. +//------------------------------------------------------------------------ + +BYTE CONST LIB_MMC64_R_OK = 0 +BYTE CONST LIB_MMC64_R_BUSY = 1 +BYTE CONST LIB_MMC64_R_NOCARD = 2 +BYTE CONST LIB_MMC64_R_FLASH_ENABLED = 3 + + +//------------------------------------------------------------------------ +// lib_mmc64_enable +// +// Purpose: Enables the MMC64 cartridge +// Params: none. +// Preparation: none. +//------------------------------------------------------------------------ +FUNC lib_mmc64_enable +ASM + ;(re)enable MMC64 + lda #$0A + sta $df13 + lda #$1C + sta $df13 + + ;enable MMC64 with initial settings + lda $df11 ;get contents of MMC64 control register + and #%10111011 ;set 250khz & write trigger mode + ora #%00000010 ;disable card select + sta $df11 ;update control register +ENDASM +FEND + + +//------------------------------------------------------------------------ +// lib_mmc64_status +// +// Purpose: +// To get actual status codes from the reader controller. +// Parameters: +// out:lib_mmc64_st_value - Value of one of the +// "MMC64 reader status constants" defined +// above. +// +// Preparation: lib_mmc64_enable +// +//------------------------------------------------------------------------ + +BYTE lib_mmc64_st_value +FUNC lib_mmc64_status ( out:lib_mmc64_st_value ) +ASM + lda $df12 + and #%00111001 + asl + asl + asl ; flash jumper enabled bit. true if set! + bcc lib_mmc64_rstatus_l1 ; if NOT flash jumper is set + + ;flash jumper is set! + lda #3 + sta lib_mmc64_st_value + rts + +lib_mmc64_rstatus_l1 + asl + asl ; card inserted bit. card NOT inserted if set + bcc lib_mmc64_rstatus_l2 ; if card IS inserted + + ; no card inserted! + lda #2 + sta lib_mmc64_st_value + rts + +lib_mmc64_rstatus_l2 + and #%00100000 ; the current position of the busy-bit after ASL's above + beq lib_mmc64_rstatus_l3 ; after the and-operation we have a 0-value if all is ok, so we store it! + + lda #1 ; card is busy +lib_mmc64_rstatus_l3 + sta lib_mmc64_st_value +ENDASM +FEND + +//------------------------------------------------------------------------ +// lib_mmc64_set512blocks +// +// Send CMD16 to MMC/SD to set the blocksize to $200 (512) which is the +// only transfer size used in this lib. +// This size is the defaut when doing read operations, but must be +// exclusivly set when attempting writes to the MMC/SD +//------------------------------------------------------------------------ + +#IFDEF __LIB_MMC64_WRITESUPPORT + +FUNC lib_mmc64_set512blocks + + ASM + ldy #$09 +lib_mmc64_s5_l1 + lda lib_mmc64_s5_cmd16-1,y + sta $df10 + dey + bne lib_mmc64_s5_l1 + ENDASM +FEND + +ASM +lib_mmc64_s5_cmd16 + !8 $ff,$ff,$ff,$00,$02,$00,$00,$50,$ff ;CMD16 +ENDASM + +#IFEND + +//------------------------------------------------------------------------ +// lib_mmc64_cardinit +// +// Purpose: Reset the SD||MMC card and set it up in SPI mode +// Params: none. +// Preparation: lib_mmc64_enable, +// (lib_mmc64_status +// to know that there is a working card in the slot) +// +//------------------------------------------------------------------------ + +FUNC lib_mmc64_cardinit +ASM + +lib_mmc64_reset + ldx #$0a ;initialize counter (10*8 pulses) + ldy #$ff ;initialize value to be written (bits must be all high) +lib_mmc64_reset_l1 + sty $df10 ;send 8 pulses to the card +lib_mmc64_reset_l2 + lda $df12 ;we catch the status register + and #$01 ;all bits shiftet out? + bne lib_mmc64_reset_l2 ;nope, so wait + dex ;decrement counter + bne lib_mmc64_reset_l1 ;until 80 pulses sent + lda $df11 ;pull card chip select line down for SPI communication + and #%11111101 + sta $df11 + +lib_mmc64_reset_l6 + ldy #$09 ;we send 8 command bytes to the card +lib_mmc64_reset_l3 + lda lib_mmc64_reset_cmdtxt0-1,y ;grab command byte + sta $df10 ;and fire it to the card +lib_mmc64_reset_l4 + lda $df12 ;we check the busy bit + and #$01 ;to ensure that the transfer is safe + bne lib_mmc64_reset_l4 + dey ;decrease command counter + bne lib_mmc64_reset_l3 ;until the entire command has been sent + lda $df10 ;now we check the card response + and #$01 ;did it accept the command ? + bne lib_mmc64_reset_l5 ;ok, everything is fine ! + lda $df12 ;is there a card at all ? + and #$08 + beq lib_mmc64_reset_l6 ;yup, so we just resend the command +lib_mmc64_reset_l5 + +lib_mmc64_reset_l7 + ldy #$09 ;we send 8 command bytes to the card +lib_mmc64_reset_l8 + lda lib_mmc64_reset_cmdtxt1-1,y ;grab command byte + sta $df10 ;and fire it to the card +lib_mmc64_reset_l9 + lda $df12 ;we check the busy bit + and #$01 ;to ensure that the transfer is safe + bne lib_mmc64_reset_l9 + dey ;decrease command counter + bne lib_mmc64_reset_l8 ;until entire command has been sent + lda $df10 ;did the card left its init state? + and #$01 + bne lib_mmc64_reset_l7 ;no, so we resend the command to check it again + + ;Enable 8Mhz mode + lda $df11 + ora #%00000100 + sta $df11 + +ENDASM +#IFDEF __LIB_MMC64_WRITESUPPORT + CALL lib_mmc64_set512blocks +#IFEND + +FEND + +ASM + +lib_mmc64_reset_cmdtxt0 + !8 $ff,$ff,$95,$00,$00,$00,$00,$40,$ff ;CMD0 +lib_mmc64_reset_cmdtxt1 + !8 $ff,$ff,$ff,$00,$00,$00,$00,$41,$ff ;CMD1 + +ENDASM + +//------------------------------------------------------------------------ +// lib_mmc64_readsector +// +// Purpose: To read a sector specified by the 24-bit number sent in. +// (65CM does not support larger types than 16-bit so we have +// to use two.) and put the data where the buffer pointer points. +// +// lib_mmc64_readsector_part +// +// Purpose: Like above but optionally be able to choose what part of the +// sector caller wants to read. This call was implemented to +// allow FAT16 to: +// 1. Read files to their real end (not the end of the last sector of a file) +// 2. fgetc() +// +// Params: +// lib_mmc64_rs_sectornr_lo = the two lowest bytes of the 24-bit sector# +// lib_mmc64_rs_sectornr_hi = the highest byte of the 24-bit sector# +// lib_mmc64_buffer_ptr = the address where to put the read data +// lib_mmc64_rs_status = after a completed operation the caller can +// check if it went OK. 0=OK. !0=fail! +// +// Params (extended for "lib_mmc64_readsector_part"): +// WORD first_pos - first byte in the sector to write to memory +// WORD read_count - how many bytes to write to target memory +// +// Preparation: lib_mmc64_cardinit +//------------------------------------------------------------------------ + +WORD lib_mmc64_rs_sectornr_lo +BYTE lib_mmc64_rs_sectornr_hi +BYTE lib_mmc64_rs_status + +FUNC lib_mmc64_readsector ( lib_mmc64_rs_sectornr_lo lib_mmc64_rs_sectornr_hi lib_mmc64_buffer_ptr out:lib_mmc64_rs_status ) + +WORD first_pos // in bytes +WORD read_count + +LET first_pos = 0 +LET read_count = 512 + +FUNC lib_mmc64_readsector_part ( lib_mmc64_rs_sectornr_lo lib_mmc64_rs_sectornr_hi lib_mmc64_buffer_ptr first_pos read_count out:lib_mmc64_rs_status ) + +WORD totalread +BYTE readbyte + +LET lib_mmc64_rs_status = 1 + +// If what we are reading up is to the internal buffer +// We compare the sector# sent in with the last sector# +// read there. We don't need to read it up if it's equal. +// Note: this code relies on lib_fat16. Not nice, but +// It'll do for now. +// Note2: this is not neccessary for normal operation +// but will certainly speed up continous read-speed when +// used on a non-fragmented file, a whole lot. +// (without optimization it reads up a sector from the +// FAT everytime we change cluster, as it's unaware of +// whats in the fat_buffer for the moment) +// Therefore it's only included if compiled with speed +// optimization. +#IFDEF __LIB_MMC64_OPTIMIZE_SPEED +ASM + lda lib_mmc64_buffer_ptr + cmp #lib_fat16_fatbuffer + bne lib_mmc64_rs_buffnotequal_skip + ; here we know that the pointer sent in is indeed the internal FAT buffer. + + lda lib_mmc64_rs_sectornr_lo + cmp lib_mmc64_rs_oldsector + bne lib_mmc64_rs_storenewbuffsector + lda lib_mmc64_rs_sectornr_lo+1 + cmp lib_mmc64_rs_oldsector+1 + bne lib_mmc64_rs_storenewbuffsector + lda lib_mmc64_rs_sectornr_hi + cmp lib_mmc64_rs_oldsector+2 + bne lib_mmc64_rs_storenewbuffsector + + lda #0 + sta lib_mmc64_rs_status + rts ; if everything matched we just saved the caller the whole readup + ; of the sector in question. +lib_mmc64_rs_storenewbuffsector + lda lib_mmc64_rs_sectornr_lo + sta lib_mmc64_rs_oldsector + lda lib_mmc64_rs_sectornr_lo+1 + sta lib_mmc64_rs_oldsector+1 + lda lib_mmc64_rs_sectornr_hi + sta lib_mmc64_rs_oldsector+2 + + jmp lib_mmc64_rs_buffnotequal_skip +lib_mmc64_rs_oldsector + !24 $ffffff + +lib_mmc64_rs_buffnotequal_skip +ENDASM +#IFEND //__LIB_MMC64_OPTIMIZE_SPEED + +ASM + + ;first we recalc the sector nr to byte nr (that the card requires) + ;since a sector of a MMC or SD card is 512 bytes it's "shift left 9" + ;shift left 8 is done by just shifting the bytes (naturally) which + ;leaves us 1 shift left to do. + + clc + rol lib_mmc64_rs_sectornr_lo + rol lib_mmc64_rs_sectornr_lo+1 + rol lib_mmc64_rs_sectornr_hi + + lda #0 + sta lib_mmc64_rs_bytenr + lda lib_mmc64_rs_sectornr_lo + sta lib_mmc64_rs_bytenr+1 + lda lib_mmc64_rs_sectornr_lo+1 + sta lib_mmc64_rs_bytenr+2 + lda lib_mmc64_rs_sectornr_hi + sta lib_mmc64_rs_bytenr+3 + + ldy #$09 +lib_mmc64_rs_l1 + lda lib_mmc64_rs_cmd17-1,y + sta $df10 + dey + bne lib_mmc64_rs_l1 + +lib_mmc64_rs_l2 + lda #$ff + sta $df10 ;write all high bits + lda $df10 ;to give the possibility to respond + cmp #$fe ;has it started? + beq lib_mmc64_rs_l4 ; start receive + cmp #0 + beq lib_mmc64_rs_l2 ; still waiting for card to answer + cmp #$ff + beq lib_mmc64_rs_l2 ; still waiting for card to answer + + jmp lib_mmc64_rs_error_exit ; Serious error - sector could not be read. + ; (normaly this is because we've called the + ; blockread CMD17 with a wrong param!) + + +lib_mmc64_rs_l4 ; start receive + + lda $df11 ;set MMC64 into read trigger mode + ora #%01000000 ;which means every read triggers a SPI transfer + sta $df11 + + lda $df10 ;we have to start with one dummy read here + +ENDASM + +#IFDEF __LIB_MMC64_OPTIMIZE_SPEED + +//If we're about to read the whole sector we use this routine! +//It's faster than the one below, 'coz it does not check for +//anything except the $200 (512) limit. +IF first_pos == 0 + IF read_count == 512 + ASM + ldx #$02 ;set up counters + ldy #$00 +lib_mmc64_rs_l3 + lda $df10 ;get data byte from card + sta (lib_mmc64_buffer_ptr),y ;store it into memory ( memptr has to be initialized) + iny ;have we copied 256 bytes ? + bne lib_mmc64_rs_l3 ;nope, so go on! + inc lib_mmc64_buffer_ptr+1 ;increase memory pointer for next 256 bytes + dex ;have we copied 512 bytes ? + bne lib_mmc64_rs_l3 + + ENDASM + GOTO lib_mmc64_rs_skipelse1 + ENDIF +ENDIF + +#IFEND //__LIB_MMC64_OPTIMIZE_SPEED + +//ELSE - we want to read just a part of the sector. so this routine is used instead + + LET totalread = 0 + WHILE totalread < 512 + IF first_pos == 0 + IF read_count == 0 + GOTO lib_mmc64_rs_dummyread + ENDIF + PEEK $df10 -> readbyte + POKE lib_mmc64_buffer_ptr , readbyte + INC lib_mmc64_buffer_ptr + DEC read_count + ELSE //prepare for first position + DEC first_pos + LABEL lib_mmc64_rs_dummyread + ASM + lda $df10 ; dummyread a byte (to move forward for the desired offset) + ENDASM + ENDIF + INC totalread + WEND + + +LABEL lib_mmc64_rs_skipelse1 +ASM + lda $df10 ;we have to end the data transfer with one dummy read + + lda $df11 ;now we put the hardware back into write trigger mode again + and #%10111111 + sta $df11 + +ENDASM + + LET lib_mmc64_rs_status = 0 // if we got this far it's a successful read! + + LABEL lib_mmc64_rs_error_exit + + +FEND + + + +ASM + ;!pet "debugtxt>" + +lib_mmc64_rs_cmd17 + ;Outline of the command (read sector 0) + ;!8 $ff,$ff,$ff,$00,$00,$00,$00,$51,$ff ;CMD17 + ; ^^^^^^^^^^^^^^^ <- 32 bit byte offset where we want to read + ; In this lib we always send in byte-offsets corresponding to a sector start.i (i.e. $200*sectornr) + ; + ;Start of command + !8 $ff,$ff,$ff +lib_mmc64_rs_bytenr + !32 0 + !8 $51,$ff ;CMD17 + +ENDASM + +//------------------------------------------------------------------------ +// lib_mmc64_writesector +// +// Purpose: To write a sector specified by the 24-bit number sent in. +// (65CM does not support larger types than 16-bit so we have +// to use two.) data for the write is taken from where +// lib_mmc64_buffer_ptr points and onwards $200(512) bytes. +// +// lib_mmc64_writesector_part +// +// Purpose: Like above but optionally be able to choose what part of the +// sector caller wants to write. This call was implemented to +// allow FAT16 to write just the part of the sector that represents +// the actual EOF in the file. (rest will be zero-filled) +// +// Note: Mem-pointer points to the actual area to be written. +// I.e. first_pos equals byte @ lib_mmc64_buffer_ptr +// +// Params: +// lib_mmc64_ws_sectornr_lo = the two lowest bytes of the 24-bit sector# +// lib_mmc64_ws_sectornr_hi = the highest byte of the 24-bit sector# +// lib_mmc64_buffer_ptr = the address where to read data for writing +// lib_mmc64_ws_status = after a completed operation the caller can +// check if it went OK. 0=OK. !0=fail! +// +// Params (extended for "lib_mmc64_readsector_part"): +// WORD first_pos - first byte in the sector to write to memory +// WORD read_count - how many bytes to write to target memory +// +// Preparation: lib_mmc64_cardinit +//------------------------------------------------------------------------ + +#IFDEF __LIB_MMC64_WRITESUPPORT + +//Block write command - set up a 512 byte transfer to the SD Card from $00000100 to $000002ff + +WORD lib_mmc64_ws_sectornr_lo +BYTE lib_mmc64_ws_sectornr_hi +BYTE lib_mmc64_ws_status + +FUNC lib_mmc64_writesector ( lib_mmc64_ws_sectornr_lo lib_mmc64_ws_sectornr_hi lib_mmc64_buffer_ptr out:lib_mmc64_ws_status ) + +WORD first_pos // in bytes +WORD read_count + +LET first_pos = 0 +LET read_count = 512 + +FUNC lib_mmc64_writesector_part ( lib_mmc64_ws_sectornr_lo lib_mmc64_ws_sectornr_hi lib_mmc64_buffer_ptr first_pos read_count out:lib_mmc64_ws_status ) + + WORD totalwritten + BYTE writebyte + + LET lib_mmc64_ws_status = 1 + + +//To make writesector compatible with the internal +//buffer-caching done in readsector we must update +//the pointers to the "oldsector". A write will +//always be issued though +#IFDEF __LIB_MMC64_OPTIMIZE_SPEED +ASM + lda lib_mmc64_buffer_ptr + cmp #lib_fat16_fatbuffer + bne lib_mmc64_ws_buffnotequal_skip + ; here we know that the pointer sent in is indeed the internal FAT buffer. + + ;here we write down (for eventual future caller request) + ;what sector the data in the buffer will represent after we write it + lda lib_mmc64_ws_sectornr_lo + sta lib_mmc64_rs_oldsector + lda lib_mmc64_ws_sectornr_lo+1 + sta lib_mmc64_rs_oldsector+1 + lda lib_mmc64_ws_sectornr_hi + sta lib_mmc64_rs_oldsector+2 + +lib_mmc64_ws_buffnotequal_skip +ENDASM +#IFEND //__LIB_MMC64_OPTIMIZE_SPEED + + ASM + + ;first we recalc the sector nr to byte nr (that the card requires) + ;since a sector of a MMC or SD card is 512 bytes it's "shift left 9" + ;shift left 8 is done by just shifting the bytes (naturally) which + ;leaves us 1 shift left to do. + + clc + rol lib_mmc64_ws_sectornr_lo + rol lib_mmc64_ws_sectornr_lo+1 + rol lib_mmc64_ws_sectornr_hi + + lda #0 + sta lib_mmc64_ws_bytenr + lda lib_mmc64_ws_sectornr_lo + sta lib_mmc64_ws_bytenr+1 + lda lib_mmc64_ws_sectornr_lo+1 + sta lib_mmc64_ws_bytenr+2 + lda lib_mmc64_ws_sectornr_hi + sta lib_mmc64_ws_bytenr+3 + ENDASM + + //Now we send CMD24 (Write single sector command) to MMC/SD + ASM + ldy #$09 +lib_mmc64_ws_l1 + lda lib_mmc64_ws_cmd24-1,y + sta $df10 + dey + bne lib_mmc64_ws_l1 + + ldx #$ff ;we send the sequence $ff,$ff,$fe to start the transfer + stx $df10 + stx $df10 + dex + stx $df10 + + ENDASM + + //If we're about to write the whole sector we use this routine + //just like the write routine + IF first_pos == 0 + IF read_count == 512 + ASM + ;And now we can write the data to the card: + + ldx #$02 ;set up counters + ldy #$00 +lib_mmc64_ws_l2 + lda (lib_mmc64_buffer_ptr),y ;get byte from C64 memory + sta $df10 ;give it to the card + + iny ;have we copied 256 bytes ? + bne lib_mmc64_ws_l2 + inc lib_mmc64_buffer_ptr+1 ;increase memory pointer for next 256 bytes + dex ;have we copied 512 bytes ? + bne lib_mmc64_ws_l2 + ENDASM + GOTO lib_mmc64_ws_skipelse1 + ENDIF + ENDIF + + //ELSE - we want to write just a part of the sector. so this routine is used instead + LET totalwritten = 0 + WHILE totalwritten < 512 + IF first_pos == 0 + IF read_count == 0 + GOTO lib_mmc64_ws_dummywrite + ENDIF + PEEK lib_mmc64_buffer_ptr -> writebyte + POKE $df10 , writebyte + INC lib_mmc64_buffer_ptr + DEC read_count + GOTO lib_mmc64_ws_skipelse2 + ENDIF + //ELSE prepare for first position + + DEC first_pos + LABEL lib_mmc64_ws_dummywrite + ASM + lda #0 + sta $df10 ; dummywrite NULL to rest of the sector. + ENDASM + + LABEL lib_mmc64_ws_skipelse2 + INC totalwritten + WEND + +ASM + +lib_mmc64_ws_skipelse1 + + lda #$ff ;send 2 sync bytes + sta $df10 + sta $df10 +lib_mmc64_ws_l3 + sta $df10 ;wait until card has left busy state + ldx $df10 + cpx #$ff + bne lib_mmc64_ws_l3 + + ENDASM + + LET lib_mmc64_ws_status = 0 + +FEND + + +ASM +lib_mmc64_ws_cmd24 + !8 $ff,$ff,$ff +lib_mmc64_ws_bytenr + !32 0 ; byte offset on card to write + !8 $58,$ff ;CMD24 +ENDASM + +#IFEND + +LABEL lib_mmc64_skip + +#IFEND diff --git a/lib/koalalib.c65 b/lib/koalalib.c65 new file mode 100644 index 0000000..f3e8b2f --- /dev/null +++ b/lib/koalalib.c65 @@ -0,0 +1,101 @@ +//------------------------------------------------------- +// +// KOALALIB +// +// Library that shows koala pictures +// +// Author: Mattias Hansson +// License: GPL2 +// Compiler version: c65cm, v.04+ +// ZP usage: $9e-$9f, $fb-$ff +// +//------------------------------------------------------- + +#IFNDEF __LIB_KOALA +#DEFINE __LIB_KOALA = 1 + +#INCLUDE +#INCLUDE + +GOTO lib_koala_skip + +WORD lib_koala_skpicptr @ $fc + +//------------------------------------------------------- +// +//------------------------------------------------------- +FUNC lib_koala_show ( io:lib_koala_skpicptr ) + + // Koala format description (excluding CBM loading address) + // Bitmap: $0000-$1f3f (0-7999) + // Videoram: $1f40-$2327 (8000-8999) + // Colorram: $2328-$270f (9000-9999) + // Background: $2710 (10000) + + WORD lib_koala_skpoke @ $9e + BYTE lib_koala_sktemp @ $fb + + GOSUB lib_c64scr_blank + + //----------------------------------------- + // Copy picture data to the right locations + //----------------------------------------- + + LET lib_koala_skpoke = $e000 + WHILE lib_koala_skpoke < $e000+8000 + PEEK lib_koala_skpicptr -> lib_koala_sktemp + POKE lib_koala_skpoke , lib_koala_sktemp + INC lib_koala_skpicptr + INC lib_koala_skpoke + WEND + + LET lib_koala_skpoke = $c000 + WHILE lib_koala_skpoke < $c000+1000 + PEEK lib_koala_skpicptr -> lib_koala_sktemp + POKE lib_koala_skpoke , lib_koala_sktemp + INC lib_koala_skpicptr + INC lib_koala_skpoke + WEND + + LET lib_koala_skpoke = colorram + WHILE lib_koala_skpoke < colorram+1000 + PEEK lib_koala_skpicptr -> lib_koala_sktemp + POKE lib_koala_skpoke , lib_koala_sktemp + INC lib_koala_skpicptr + INC lib_koala_skpoke + WEND + + //background color (one of the colors in MCB mode) + PEEK lib_koala_skpicptr -> lib_koala_sktemp + POKE vic2+$21 , lib_koala_sktemp + + //---------------------------- + // Setup VIC2 to show picture + //---------------------------- + + //Setup bank 4 ($c000-$ffff) for VIC-II (bits 0 and 1 negated) + PEEK cia2 -> lib_koala_sktemp + AND lib_koala_sktemp WITH $fc -> lib_koala_sktemp + POKE cia2 , lib_koala_sktemp + + //Setup bitmap @ $e000 and videoram @ $c000 + PEEK vic2+$18 -> lib_koala_sktemp + AND lib_koala_sktemp WITH 7 -> lib_koala_sktemp + OR lib_koala_sktemp WITH 8 -> lib_koala_sktemp + POKE vic2+$18 , lib_koala_sktemp + + //Setup multicolor graphics + PEEK vic2+$11 -> lib_koala_sktemp + OR lib_koala_sktemp WITH 32 -> lib_koala_sktemp + POKE vic2+$11 , lib_koala_sktemp + PEEK vic2+$16 -> lib_koala_sktemp + OR lib_koala_sktemp WITH 16 -> lib_koala_sktemp + POKE vic2+$16 , lib_koala_sktemp + + GOSUB lib_c64scr_show + +FEND + +LABEL lib_koala_skip + +#IFEND diff --git a/lib/libsc.c65 b/lib/libsc.c65 new file mode 100644 index 0000000..55a3109 --- /dev/null +++ b/lib/libsc.c65 @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------ +// Library shortcuts Library +// +// Author: Mattias Hansson +// Copyright (c) : 2005 Mattias Hansson +// License: GNU LGPL 2 +// Language: 65CM v0.4+ +// Dependencies: (all libs might be included, selectively) +// +// Purpose: To shorten lib names to normal usage length +//------------------------------------------------------------------------ + +#IFNDEF __LIB_SC + #DEFINE __LIB_SC = 1 + + #IFDEF __LIB_FAT16 + #DEFINE mount = lib_fat16_mount_partition + #DEFINE fopen = lib_fat16_fopen + #DEFINE fclose = lib_fat16_fclose + #DEFINE fcloses = lib_fat16_fcloses + #DEFINE fblockread = lib_fat16_fblockread + #DEFINE fblockwrite = lib_fat16_fblockwrite + #DEFINE chdir = lib_fat16_chdir + #DEFINE dirstart = lib_fat16_dirstart + #DEFINE dirnext = lib_fat16_dirnext + #IFEND + + #IFDEF __LIB_CBMIO + #DEFINE print = lib_cbmio_print + #DEFINE printlf = lib_cbmio_printlf + #DEFINE hexoutb = lib_cbmio_hexoutb + #DEFINE hexoutw = lib_cbmio_hexoutw + #DEFINE lf = lib_cbmio_lf + #DEFINE home = lib_cbmio_home + #DEFINE input = lib_cbmio_input + #IFEND + + #IFDEF __LIB_STRING + #DEFINE strlen = lib_string_strlen + #DEFINE strchr = lib_string_strchr + #DEFINE strcpy = lib_string_strcpy + #DEFINE stpcpy = lib_string_stpcpy + #DEFINE strncpy = lib_string_strncpy + #DEFINE strcmp = lib_string_strcmp + #DEFINE memset = lib_string_memset + #DEFINE strcat = lib_string_strcat + #DEFINE strncat = lib_string_strncat + #IFEND + + #IFDEF __LIB_STRLIST + #DEFINE sl.count = lib_strlist_count + #DEFINE sl.get = lib_strlist_get + #DEFINE sl.strget = lib_strlist_strget + #DEFINE sl.add = lib_strlist_add + #DEFINE sl.free = lib_strlist_free + #IFEND + + #IFDEF __LIB_STRLIST2 + #DEFINE str_to_strlist = lib_strlist2_str_to_strlist + #IFEND + + #IFDEF __LIB_MEM + #DEFINE malloc = lib_mem_malloc + #DEFINE free = lib_mem_free + #IFEND + +#IFEND diff --git a/lib/memlib.c65 b/lib/memlib.c65 new file mode 100644 index 0000000..8f58cae --- /dev/null +++ b/lib/memlib.c65 @@ -0,0 +1,304 @@ +//----------------------------------------------------------- +// +// Memory library ( Malloc, Free ) +// +// +// Author: Mattias Hansson +// Copyright (c) : 2000-2005 Mattias Hansson +// License: GNU LGPL 2 +// Language: 65CM v0.4+ +// Dependencies: +// Target: generic 6502 +// +// Purpose: Provide some functions the help to create, update and access +// a linked list of strings. +//----------------------------------------------------------- + + +//----------------------------------------------------------- +// Internals: +// +// Makes heap-handling in a defined part of the memory. +// +// Requested memory is placed "from bottom upwards". +// Allocation entries are written from the top downwards. +// +// layout: +// +// Bottom ( e.g. $2000-$2fff ) +// -------------- +// $2000 +// . +// . area 1 allocated by application +// . +// $2020 +// $2021 +// . +// . area 2 allocated by application +// . +// $2025 +// +// . +// . +// . +// +// $2ff8 + $2ff9 = $2025 end of area 2 +// $2ffa + $2ffb = $2021 start of area 2 +// $2ffc + $2ffd = $2020 end of area 1 +// $2ffe + $2fff = $2000 start of area 1 + +#IFNDEF __LIB_MEM +#DEFINE __LIB_MEM = 1 + +#IFNDEF __LIB_MEM_START +#DEFINE __LIB_MEM_START = $$5000 +#IFEND + +#IFNDEF __LIB_MEM_END +#DEFINE __LIB_MEM_END = $$9fff +#IFEND + +#IFNDEF NULL +#DEFINE NULL = 0 +#IFEND + +GOTO lib_memlib_skip + +WORD CONST lib_memstart = __LIB_MEM_START +WORD CONST lib_memend = __LIB_MEM_END + +WORD lib_alloctable_end + +//----------------------------------------------------------- +// Init: sets upp things for the lib in mem before start +//----------------------------------------------------------- +FUNC lib_mem_init + +//set allocation table end start value + LET lib_alloctable_end GET lib_memend + INC lib_alloctable_end + +FEND + +//----------------------------------------------------------- +// Malloc: alloc memory of size size_t +// Address to allocated space is returned in result +//----------------------------------------------------------- + +FUNC lib_mem_malloc ( {WORD size_t} out:{WORD result} ) + WORD maxsize //max possible size now in heap + WORD newspaceb + WORD newspacee + WORD newtableend + + DEC size_t // 1 = e.g. $2001-$2001 ( $2001 - $2001 = 0 ) + + LET newtableend GET lib_alloctable_end + SUBT newtableend - 4 -> newtableend + + IF lib_alloctable_end > lib_memend THEN // only when first item! + + SUBT newtableend - lib_memstart -> maxsize + IF maxsize < size_t THEN + //error handling: return NULL pointer! + LET result GET NULL + GOTO lib_malloc_exit + ENDIF + + LET newspaceb GET lib_memstart + ADD newspaceb + size_t -> newspacee + + //insert first record and recalc lib_alloctable_end + DEC lib_alloctable_end + DEC lib_alloctable_end + PUTASWORD@ lib_alloctable_end VALUE newspaceb + DEC lib_alloctable_end + DEC lib_alloctable_end + PUTASWORD@ lib_alloctable_end VALUE newspacee + + LET result GET newspaceb + + GOTO lib_malloc_exit; + + ENDIF + //else if this is not the first item then: + + WORD lib_malloc_ptr + WORD lib_malloc_spaceend + WORD lib_malloc_spacestart + + LET lib_malloc_ptr GET lib_memend + SUBT lib_malloc_ptr - 3 -> lib_malloc_ptr + + GETASWORD@ lib_malloc_ptr -> lib_malloc_spaceend + + LET newspaceb GET NULL //if not found afterwards + + WHILE 1 + + //if we reached the last record in the alloc-table + IF lib_malloc_ptr = lib_alloctable_end THEN + //is there sufficient space left @ top of heap? + INC lib_malloc_spaceend + SUBT newtableend - lib_malloc_spaceend -> maxsize + + IF maxsize > size_t THEN + LET newspaceb GET lib_malloc_spaceend + ADD size_t + newspaceb -> newspacee + ENDIF + + BREAK + ENDIF + + DEC lib_malloc_ptr + DEC lib_malloc_ptr + GETASWORD@ lib_malloc_ptr -> lib_malloc_spacestart + SUBT lib_malloc_spacestart - lib_malloc_spaceend -> maxsize + DEC maxsize + //if we found a space big enough for the allocation + IF maxsize > size_t THEN + //Save the pointer to the new space end the pointer in the alloctable + LET newspaceb GET lib_malloc_spaceend + INC newspaceb // must start @ the next byte + ADD size_t + newspaceb -> newspacee + + INC lib_malloc_ptr // set it to start of record! ( for move ) +// INC lib_malloc_ptr + + BREAK + ENDIF + + DEC lib_malloc_ptr + DEC lib_malloc_ptr + GETASWORD@ lib_malloc_ptr -> lib_malloc_spaceend + + WEND + + IF newspaceb <> NULL THEN // allocate @ found space + IF lib_malloc_ptr = lib_alloctable_end THEN //allocate @ end of table + + //insert record and recalc lib_alloctable_end + DEC lib_alloctable_end + DEC lib_alloctable_end + PUTASWORD@ lib_alloctable_end VALUE newspaceb + DEC lib_alloctable_end + DEC lib_alloctable_end + PUTASWORD@ lib_alloctable_end VALUE newspacee + + //return new pointer + LET result GET newspaceb + + GOTO lib_malloc_exit + ENDIF + //else = insert into alloctable + WORD lib_malloc_from + WORD lib_malloc_to + BYTE lib_malloc_byte + + // move the rest of the stack + LET lib_malloc_from GET lib_alloctable_end + LET lib_malloc_to GET newtableend + + WHILE 1 + PEEK lib_malloc_from -> lib_malloc_byte + POKE lib_malloc_to WITH lib_malloc_byte + IF lib_malloc_from = lib_malloc_ptr THEN + BREAK + ENDIF + INC lib_malloc_from + INC lib_malloc_to + WEND + + //insert record and recalc lib_alloctable_end + DEC lib_malloc_ptr + PUTASWORD@ lib_malloc_ptr VALUE newspaceb + DEC lib_malloc_ptr + DEC lib_malloc_ptr + PUTASWORD@ lib_malloc_ptr VALUE newspacee + LET lib_alloctable_end GET newtableend + + //return new pointer + LET result GET newspaceb + + ENDIF + + IF newspaceb = NULL THEN // return null pointer + LET result GET NULL + GOTO lib_malloc_exit + ENDIF + +LABEL lib_malloc_exit + + INC size_t + +FEND + +//----------------------------------------------------------- +// Free: free alloced memory at m_ptr +//----------------------------------------------------------- + +FUNC lib_mem_free ( {WORD m_ptr} ) + WORD ptr + WORD startaddr + WORD lastrecfirst + WORD from + WORD to + BYTE tempb + + IF lib_alloctable_end > lib_memend THEN // no entries + GOTO lib_free_exit + ENDIF + + LET ptr GET lib_memend + DEC ptr + + ADD 2 + lib_alloctable_end -> lastrecfirst + + WHILE 1 + GETASWORD@ ptr -> startaddr + + IF startaddr = m_ptr THEN //found allocation! + //remove it! + IF ptr = lastrecfirst THEN + ADD 4 + lib_alloctable_end -> lib_alloctable_end + BREAK + ENDIF + + LET to GET ptr + INC to + SUBT to - 4 -> from + + WHILE 1 + PEEK from -> tempb + POKE to WITH tempb + IF from = lib_alloctable_end THEN + BREAK + ENDIF + DEC from + DEC to + WEND + + ADD 4 + lib_alloctable_end -> lib_alloctable_end + + BREAK + ENDIF + + DEC ptr + DEC ptr + + IF ptr = lib_alloctable_end THEN + BREAK + ENDIF + + DEC ptr + DEC ptr + + WEND + +LABEL lib_free_exit + +FEND + +LABEL lib_memlib_skip + +#IFEND diff --git a/lib/multdivlib.c65 b/lib/multdivlib.c65 new file mode 100644 index 0000000..cf6ecb9 --- /dev/null +++ b/lib/multdivlib.c65 @@ -0,0 +1,87 @@ +//------------------------------------------------ +// Mult/Div routines, taken from "The Fridge" +// Made with 65CM interfaces +//------------------------------------------------ + +#IFNDEF __LIB_MULTDIV +#DEFINE __LIB_MULTDIV = 1 + +GOTO lib_multdiv_skip + + +WORD lib_multdiv_acc +WORD lib_multdiv_aux +WORD lib_multdiv_ext + +//------------------------------------------------ +// * MULTIPLY ROUTINE +//* ACC*AUX -> [ACC,EXT] (low,hi) 32 bit result +//------------------------------------------------ + +FUNC lib_multdiv_mult ( io:lib_multdiv_acc lib_multdiv_aux ) +FUNC lib_multdiv_mult32 ( io:lib_multdiv_acc lib_multdiv_aux out:lib_multdiv_ext ) +ASM + + lda #0 + sta lib_multdiv_ext+1 + ldy #$11 + clc +lib_multdiv_l1 + ror lib_multdiv_ext+1 + ror + ror lib_multdiv_acc+1 + ror lib_multdiv_acc + bcc lib_multdiv_mul2 + clc + adc lib_multdiv_aux + pha + lda lib_multdiv_aux+1 + adc lib_multdiv_ext+1 + sta lib_multdiv_ext+1 + pla +lib_multdiv_mul2 + dey + bne lib_multdiv_l1 + sta lib_multdiv_ext +ENDASM + +FEND + +//------------------------------------------------ +// * DIVIDE ROUTINE +//* ACC/AUX -> ACC, remainder in EXT +//------------------------------------------------ + +FUNC lib_multdiv_div ( io:lib_multdiv_acc lib_multdiv_aux ) +FUNC lib_multdiv_divmod ( io:lib_multdiv_acc lib_multdiv_aux out:lib_multdiv_ext ) +ASM + lda #0 + sta lib_multdiv_ext+1 + ldy #$10 +lib_multdiv_l2 + asl lib_multdiv_acc + rol lib_multdiv_acc+1 + rol + rol lib_multdiv_ext+1 + pha + cmp lib_multdiv_aux + lda lib_multdiv_ext+1 + sbc lib_multdiv_aux+1 + bcc lib_multdiv_div2 + sta lib_multdiv_ext+1 + pla + sbc lib_multdiv_aux + pha + inc lib_multdiv_acc +lib_multdiv_div2 + pla + dey + bne lib_multdiv_l2 + sta lib_multdiv_ext +ENDASM + +FEND + + +LABEL lib_multdiv_skip +#IFEND diff --git a/lib/string.c65 b/lib/string.c65 new file mode 100644 index 0000000..71e2b1e --- /dev/null +++ b/lib/string.c65 @@ -0,0 +1,397 @@ +//------------------------------------------------------------------------ +// String library +// +// Author: Mattias Hansson +// Copyright (c) : 2005 Mattias Hansson +// License: GNU GPL 2 +// Language: 65CM v0.4+ +// Dependencies: +// ZP usage: $fb-$fe +// +// External functions: +// lib_string_strlen +// lib_string_strchr +// lib_string_strcpy +// lib_string_stpcpy +// lib_string_strncpy +// lib_string_strcmp +// lib_string_memset +// lib_string_strcat +// lib_string_strncat + +// +// Internal function: +// +// +// Note: Several of the lib-functions are combined to save space, where +// this can be done at a fairly small speed penalty. +// +//------------------------------------------------------------------------ + +#IFNDEF __LIB_STRING +#DEFINE __LIB_STRING = 1 + +GOTO lib_string_skip + +//Makes it possible for all modules that needs NULL +//to define the constant +#IFNDEF NULL + #DEFINE NULL = 0 +#IFEND + +WORD lib_string_strptr1 @ $fb +WORD lib_string_strptr2 @ $fd +WORD lib_string_length + +//just aliases for the mem* functions +WORD lib_string_ptr1 @ $fb +WORD lib_string_ptr2 @ $fd + +WORD lib_string_temp + + +//----------------------------------------------------------- +// lib_string_strlen +// +// Purpose: Returns the string length of a null-terminated +// string in memory. +// +// Params: +// lib_string_strptr1 - pointer to string to measure +// lib_string_length - returns the length of the string +// +// lib_string_strchr +// +// Purpose: Returns pointer to where char lib_string_sl_matchchar +// first occurs in string at lib_string_strptr1. +// Returns NULL if not found. +//----------------------------------------------------------- + +BYTE lib_string_sl_matchchar + +FUNC lib_string_strlen ( lib_string_strptr1 out:lib_string_length ) + + LET lib_string_sl_matchchar = 0 + +FUNC lib_string_strchr ( io:lib_string_strptr1 lib_string_sl_matchchar ) + + BYTE char + + LET lib_string_length = 0 + + PEEK lib_string_strptr1 -> char + + WHILE char + IF char == lib_string_sl_matchchar + SUBEND + ENDIF + INC lib_string_strptr1 + INC lib_string_length + PEEK lib_string_strptr1 -> char + WEND + + LET lib_string_strptr1 = NULL +FEND + + +//----------------------------------------------------------- +// lib_string_strcpy +// +// Purpose: Copies the null-terminated string at +// lib_string_strptr1 to lib_string_strptr2 +// +// lib_string_stpcpy +// +// Purpose: Same as strcpy but returns a pointer to the +// end of the copy in lib_string_strptr2 +// +// lib_string_strncpy +// +// Purpose: Copies the null-terminated string at +// lib_string_strptr1 to lib_string_strptr2, but +// stops after lib_string_length chars regardless +// of if the full string is copied +// +// Note: Dont send in 0 (zero) into lib_string_length. +//----------------------------------------------------------- + +FUNC lib_string_stpcpy ( lib_string_strptr1 io:lib_string_strptr2 ) + +FUNC lib_string_strcpy ( lib_string_strptr1 lib_string_strptr2 ) + + LET lib_string_length = 0 + +FUNC lib_string_strncpy ( lib_string_strptr1 lib_string_strptr2 lib_string_length ) + + BYTE char + LET char = $ff + + WHILE char + PEEK lib_string_strptr1 -> char + POKE lib_string_strptr2 , char + INC lib_string_strptr1 + INC lib_string_strptr2 + DEC lib_string_length + IF lib_string_length == 0 + BREAK + ENDIF + WEND + +FEND + +//----------------------------------------------------------- +// lib_string_memset +// +// Purpose: Sets lib_string_length bytes starting at +// lib_string_ptr1 to the value lib_string_memset_value +// +//----------------------------------------------------------- + +BYTE lib_string_memset_value +FUNC lib_string_memset ( lib_string_ptr1 lib_string_memset_value lib_string_length ) + + ADD lib_string_ptr1 + lib_string_length -> lib_string_ptr2 + DEC lib_string_ptr2 + WHILE lib_string_ptr2 >= lib_string_ptr1 + POKE lib_string_ptr2 , lib_string_memset_value + DEC lib_string_ptr2 + WEND + +FEND + +//----------------------------------------------------------- +// lib_string_strcmp +// +// purpose: Compare two strings. +// Returns 2 if str1 is less than str2 +// 1 if str1 is greater than str2 +// 0 if they are equal. +//----------------------------------------------------------- + +BYTE lib_string_strcmp_result + +FUNC lib_string_strcmp ( lib_string_strptr1 lib_string_strptr2 out:lib_string_strcmp_result ) + BYTE val1 + BYTE val2 + + LET lib_string_strcmp_result = 0 + + PEEK lib_string_strptr1 -> val1 + PEEK lib_string_strptr2 -> val2 + + WHILE val1 == val2 + INC lib_string_strptr1 + INC lib_string_strptr2 + PEEK lib_string_strptr1 -> val1 + PEEK lib_string_strptr2 -> val2 + IF val1 == 0 + BREAK + ENDIF + IF val2 == 0 + BREAK + ENDIF + WEND + + IF val1 < val2 + LET lib_string_strcmp_result = 2 + ENDIF + + IF val1 > val2 + LET lib_string_strcmp_result = 1 + ENDIF + +FEND + +//----------------------------------------------------------- +// lib_string_strstr +// +// purpose: +// Locate the position of one string in another. +// Returns a pointer to the location of the +// substring. (or NULL if not found) +//----------------------------------------------------------- + +WORD lib_string_strstr_return + +FUNC lib_string_strstr ( lib_string_strptr1 lib_string_strptr2 out:lib_string_strstr_return ) + + LET lib_string_strstr_return = NULL + WORD strlen1 + WORD strlen2 + WORD substartpos + WORD mainlastpos + BYTE mainval + BYTE subval + WORD strptr1firstmatch + BYTE innerloop + + //Ugly hack: Since "lib_string_strptr1" is used internally in strlen we remember the value in a temp variable + LET lib_string_temp = lib_string_strptr1 + CALL lib_string_strlen ( lib_string_strptr1 strlen1 ) + CALL lib_string_strlen ( lib_string_strptr2 strlen2 ) + //Ugly hack: Since "lib_string_strptr1" is used internally in strlen we restore the value from a temp variable + LET lib_string_strptr1 = lib_string_temp + + IF strlen1 == 0 + SUBEND + ENDIF + IF strlen2 == 0 + SUBEND + ENDIF + + IF strlen2 > strlen1 + SUBEND + ENDIF + + SUBT strlen1 - strlen2 -> mainlastpos + ADD lib_string_strptr1 + mainlastpos -> mainlastpos + + LET substartpos = lib_string_strptr2 + LET innerloop = 0 + PEEK lib_string_strptr2 -> subval + + WHILE lib_string_strptr1 <= mainlastpos + PEEK lib_string_strptr1 -> mainval + IF mainval = subval + LET strptr1firstmatch = lib_string_strptr1 + LET innerloop = 1 + ENDIF + WHILE mainval = subval + INC lib_string_strptr1 + INC lib_string_strptr2 + PEEK lib_string_strptr1 -> mainval + PEEK lib_string_strptr2 -> subval + IF subval = 0 + //This is the match! We found substr! + LET lib_string_strstr_return = strptr1firstmatch + SUBEND + ENDIF + WEND + IF innerloop != 0 + LET innerloop = 0 + LET lib_string_strptr1 = strptr1firstmatch + LET lib_string_strptr2 = substartpos + PEEK lib_string_strptr2 -> subval + ENDIF + INC lib_string_strptr1 + WEND + +FEND + +//----------------------------------------------------------- +// lib_string_strcat +// +// purpose: +// To concatenate two strings. +// Appends lib_string_strptr1 to the +// end of lib_string_strptr2 (overwritng +// the terminating NULL character). +// +// lib_string_strncat +// +// purpose: +// very similar to strcat except at most +// lib_string_sc_copymaxchars are +// copied from the source. result is +// always NULL-terminated. +// (i.e. result string is at most +// length( lib_string_strptr1 ) + +// lib_string_sc_copymaxchars + 1 +// +// Note: +// Be sure that memory after NULL +// in lib_string_strptr2 is avaliable +// for writing (so you don't overwrite +// something else lying there, or you +// may get some very unexpected +// results. +//----------------------------------------------------------- + +WORD lib_string_sc_copymaxchars + +FUNC lib_string_strcat ( lib_string_strptr1 lib_string_strptr2 ) + +LET lib_string_sc_copymaxchars = $ffff + +FUNC lib_string_strncat ( lib_string_strptr1 lib_string_strptr2 lib_string_sc_copymaxchars ) + + BYTE char + + //first find end of string2 + PEEK lib_string_strptr2 -> char + WHILE char + INC lib_string_strptr2 + PEEK lib_string_strptr2 -> char + WEND + + PEEK lib_string_strptr1 -> char + POKE lib_string_strptr2 , char + WHILE char + PEEK lib_string_strptr1 -> char + POKE lib_string_strptr2 , char + INC lib_string_strptr1 + INC lib_string_strptr2 + DEC lib_string_sc_copymaxchars + IF lib_string_sc_copymaxchars == 0 + POKE lib_string_strptr2 , 0 + SUBEND + ENDIF + WEND + +FEND + +//----------------------------------------------------------- +// lib_string_memcmp +// +// Purpose: +// Compare two memory areas. +// +//----------------------------------------------------------- + +BYTE lib_string_mc_res + +FUNC lib_string_memcmp ( lib_string_ptr1 lib_string_ptr2 lib_string_length out:lib_string_mc_res ) + + BYTE b1 + BYTE b2 + + LET lib_string_mc_res = 1 + WHILE lib_string_length + PEEK lib_string_ptr1 -> b1 + PEEK lib_string_ptr2 -> b2 + IF b1 != b2 + SUBEND + ENDIF + INC lib_string_ptr1 + INC lib_string_ptr2 + DEC lib_string_length + WEND + + LET lib_string_mc_res = 0 +FEND + + +//----------------------------------------------------------- +// lib_string_memcpy +// +// Purpose: +// Copy contants of a memory area to another memory area +//----------------------------------------------------------- + +FUNC lib_string_memcpy ( lib_string_ptr1 lib_string_ptr2 lib_string_length ) + + BYTE btemp + + WHILE lib_string_length + PEEK lib_string_ptr1 -> btemp + POKE lib_string_ptr2 , btemp + INC lib_string_ptr1 + INC lib_string_ptr2 + DEC lib_string_length + WEND +FEND + + +LABEL lib_string_skip + +#IFEND diff --git a/lib/strlist.c65 b/lib/strlist.c65 new file mode 100644 index 0000000..c68265e --- /dev/null +++ b/lib/strlist.c65 @@ -0,0 +1,153 @@ +//------------------------------------------------------------------------ +// Library String-List +// +// Author: Mattias Hansson +// Copyright (c) : 2005 Mattias Hansson +// License: GNU LGPL 2 +// Language: 65CM v0.4+ +// Dependencies: string.c65 memlib.c65 +// Target: generic 6502 +// +// Purpose: Provide some functions the help to create, update and access +// a linked list of strings. +//------------------------------------------------------------------------ + +#IFNDEF __LIB_STRLIST +#DEFINE __LIB_STRLIST = 1 + +GOTO lib_strlist_skip + +#INCLUDE +#INCLUDE + +//----------------------------------------------------------- +// lib_strlist_get +// +// purpose: +// To get address to an item# (0..count-1) +// (send in item# $ffff for last item) +// +// lib_strlist_strget +// +// purpose: +// To get address of an string inside item# +// (send in item# $ffff for last item) +// +// lib_strlist_count +// purpose: +// To get the # of items in the list. +// NOTE: Returns the value in computer notation. I.e. +// 1 string, count == 0; 2 strings, count == 1 etc. +//----------------------------------------------------------- + + +FUNC lib_strlist_count ( {WORD objref} out:{WORD count} ) + + WORD itemnr + LET itemnr = $ffff + +FUNC lib_strlist_get ( objref itemnr out:{WORD itemptr} ) + +FUNC lib_strlist_strget ( objref itemnr out:{WORD s} ) + + WORD nextptr + + IF objref == NULL //no list, no action + EXIT + ENDIF + + //return values reset + LET count = 0 + LET itemptr = objref + LET s = NULL + + WHILE 1 + ADD 2 + itemptr -> s + GETASWORD@ itemptr -> nextptr + IF nextptr == NULL //are we at last item? + EXIT + ENDIF + IF itemnr == count + EXIT //if searching for this item we're done. + ENDIF + INC count + LET itemptr = nextptr + //MH TAG 20051218 debug + //CALL lib_cbmio_hexoutw ( itemptr ) + WEND +FEND + +//----------------------------------------------------------- +// lib_strlist_add +// +// purpose: +// To either create a new linklist (send in NULL as +// root-node), or add a new node to an existing linklist. +// +// Params: +// objref - send in root-node-pointer to the +// list. If new list is to be created +// send in the value NULL. +// returns: address to the root node +// on successful add, or NULL on error +// (normal cause: out of memory) +//----------------------------------------------------------- + +FUNC lib_strlist_add ( io:{WORD objref} {WORD s} ) + WORD link_length + WORD link_ptr + WORD old_link_ptr + + IF objref != 0 //add new link + CALL lib_strlist_get ( objref $ffff old_link_ptr ) + ENDIF + + CALL lib_string_strlen ( s link_length ) + //Add space for: + // 1. "link to next member" (2) + // 2. null terminator (1) + ADD 3 + link_length -> link_length + CALL lib_mem_malloc ( link_length link_ptr ) + IF link_ptr == NULL // Alloc failed == out of memory + LET objref = NULL + EXIT + ENDIF + IF objref == 0 //create new list + LET objref = link_ptr //return the start of the list to the caller + ELSE //link up the new link to the end of the old list + PUTASWORD@ old_link_ptr , link_ptr + ENDIF + PUTASWORD@ link_ptr , NULL //Mark as last link + //move beyond link-pointer + INC link_ptr + INC link_ptr + //and finally add the string to memory + CALL lib_string_strcpy ( s link_ptr ) + +FEND + +//----------------------------------------------------------- +// lib_strlist_free +// +// purpose: +// To remove a strlist and free all resources allocated by +// it. +// +// Params: +// lib_strlist_f_rootptr - Address to root item. +//----------------------------------------------------------- + +FUNC lib_strlist_free ( io:{WORD objref} ) + WORD nextptr + + WHILE objref + GETASWORD@ objref -> nextptr + CALL lib_mem_free ( objref ) + LET objref = nextptr + WEND + LET objref = NULL +FEND + +LABEL lib_strlist_skip + +#IFEND diff --git a/lib/strlist2.c65 b/lib/strlist2.c65 new file mode 100644 index 0000000..b356fc1 --- /dev/null +++ b/lib/strlist2.c65 @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------ +// Library Extended String-List routines +// +// Author: Mattias Hansson +// Copyright (c) : 2005 Mattias Hansson +// License: GNU LGPL 2 +// Language: 65CM v0.4+ +// Dependencies: string.c65 memlib.c65 +// Target: generic 6502 +// +// Purpose: Provide more advanced functions for manipulating/maintaining +// stringlists. By making a additional library, developers can +// choose how much they wsnt compared to the memory size cost +// of the libraries. +//------------------------------------------------------------------------ + + +#IFNDEF __LIB_STRLIST2 +#DEFINE __LIB_STRLIST2 = 1 + +GOTO lib_strlist2_skip + +#INCLUDE + +//----------------------------------------------------------- +// lib_str_to_strlist +// +// purpose: +// To splitt a string into 0..several elements into a new +// stringlist. The separator character defines the +// "splitt indicator". +// +// Note: If several separator-chars are found together they +// are still threated as one. +// +//----------------------------------------------------------- + + FUNC lib_strlist2_str_to_strlist ( {WORD s} {BYTE separator} io:{WORD slref} ) + + BYTE b + BYTE inword + WORD start + WORD copylen + WORD tmpstr_ptr + BYTE eow //end of word + + LET inword = 0 + LET eow = 0 + + WHILE 1 + PEEK s -> b + + IF inword == 0 + IF b == 0 + EXIT + ENDIF + IF b != separator //start of new "word" + LET inword = 1 + LET start = s + ENDIF + ELSE //we are inside a "word" + IF b == 0 + LET eow = 1 + ENDIF + IF b == separator //end of this "word" + LET eow = 1 + ENDIF + + IF eow == 1 + //copy the "word" to a new string + + SUBT s - start -> copylen + //hexoutw ( copylen ) + INC copylen //make room for zero termination + lib_mem_malloc ( copylen tmpstr_ptr ) + IF tmpstr_ptr == NULL + EXIT //if errorhandling is to be added in the future, check this :-) + ENDIF + DEC copylen + lib_string_strncpy ( start tmpstr_ptr copylen ) + //manually zero terminating the copy to add (borrowing start as temp) + ADD tmpstr_ptr + copylen -> start + POKE start , NULL + lib_strlist_add ( slref tmpstr_ptr ) + lib_mem_free ( tmpstr_ptr ) + LET inword = 0 + LET eow = 0 + ENDIF + ENDIF + IF b == 0 + EXIT + ENDIF + + INC s + WEND + + FEND + +LABEL lib_strlist2_skip + +#IFEND