c65gm/lib/fat16/fat16lib.c65

1566 lines
54 KiB
Text

//------------------------------------------------------------------------
// 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 <multdivlib.c65>
#INCLUDE <fat16/mmc64lib.c65>
//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 <fat16/fat16lowlib.c65>
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 #<lib_fat16_max_open_files
sta |max_files|
ENDASM
POINTER descr TO lib_fat16_file_descriptors
WHILE max_files
GETASWORD@ descr -> 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