From 4b5c85d360e7b8cfb4c9ffcb344b26da83de1fde Mon Sep 17 00:00:00 2001 From: Michaelangel007 Date: Sun, 5 Nov 2017 21:55:01 -0800 Subject: [PATCH] Initial commit of working catalogs, init, and cp --- generic.disk.cpp | 331 +++++++++++ prodos.cpp | 612 ++++++++++++++++++++ prodos.tools.cpp | 1399 ++++++++++++++++++++++++++++++++++++++++++++++ prodos.utils.cpp | 488 ++++++++++++++++ string.utils.cpp | 70 +++ 5 files changed, 2900 insertions(+) create mode 100644 generic.disk.cpp create mode 100644 prodos.cpp create mode 100644 prodos.tools.cpp create mode 100644 prodos.utils.cpp create mode 100644 string.utils.cpp diff --git a/generic.disk.cpp b/generic.disk.cpp new file mode 100644 index 0000000..fa66be2 --- /dev/null +++ b/generic.disk.cpp @@ -0,0 +1,331 @@ +#define DEBUG_DSK 0 +#define DEBUG_DSK_LOAD 0 +#define DEBUG_DSK_SAVE 0 +#define DEBUG_DSK_INTERLEAVE 0 + +#ifdef __WIN32 +#else + // OSX / BSD / Linux + #define stricmp strcasecmp +#endif + +// --- Buffer --- + + // 5 1/4" 140K (single sided) + const size_t DSK_SIZE_514 = 256 * 16 * 35; // 143,360 + + // 3 1/2" = 800K (double sided) + const size_t DSK_SIZE_312 = 2 * 400 * 1024; // 819,200 + + // 32 MB Hard Disk + const size_t DSK_SIZE_32M = 32 * 1024 * 1024; + + const int DSK_SECTOR_SIZE = 256; + + + size_t gnDskSize = 0; + uint8_t gaDsk[ DSK_SIZE_32M ]; + + uint16_t DskGet16( int offset ) + { + uint16_t n = 0 + | (((uint16_t)gaDsk[ offset + 0 ]) << 0) + | (((uint16_t)gaDsk[ offset + 1 ]) << 8) + ; + return n; + } + + uint32_t DskGet24( int offset ) + { + uint32_t n = 0 + | (((uint32_t)gaDsk[ offset + 0 ]) << 0) + | (((uint32_t)gaDsk[ offset + 1 ]) << 8) + | (((uint32_t)gaDsk[ offset + 2 ]) << 16) + ; + return n; + } + + void DskPut16( int offset, int val ) + { + gaDsk[ offset + 0 ] = (val >> 0) & 0xFF; + gaDsk[ offset + 1 ] = (val >> 8) & 0xFF; + } + + void DskPut24( int offset, int val ) + { + gaDsk[ offset + 0 ] = (val >> 0) & 0xFF; + gaDsk[ offset + 1 ] = (val >> 8) & 0xFF; + gaDsk[ offset + 2 ] = (val >>16) & 0xFF; + } + + +// --- Name --- + + const char *gpDskName = NULL; + + +// --- Sector Interleave --- + + enum SectorOrder_e + { + INTERLEAVE_AUTO_DETECT = 0, + INTERLEAVE_DOS33_ORDER, + INTERLEAVE_PRODOS_ORDER + }; + int giInterleaveLastUsed = INTERLEAVE_AUTO_DETECT; + + // Map Physical <-> Logical + const uint8_t gaInterleave_DSK[ 16 ] = + { + //0 1 2 3 4 5 6 7 8 9 A B C D E F // logical order + 0x0,0xE,0xD,0xC,0xB,0xA,0x9,0x8,0x7,0x6,0x5,0x4,0x3,0x2,0x1,0xF // physical order + }; + + void Interleave_Forward( int iSector, size_t *src_, size_t *dst_ ) + { + *src_ = gaInterleave_DSK[ iSector ]*DSK_SECTOR_SIZE; + *dst_ = iSector *DSK_SECTOR_SIZE; // linearize + }; + void Interleave_Reverse( int iSector, size_t *src_, size_t *dst_ ) + { + *src_ = iSector *DSK_SECTOR_SIZE; // un-linearize + *dst_ = gaInterleave_DSK[ iSector ]*DSK_SECTOR_SIZE; + } + + +// --- Size --- + + size_t File_Size( FILE *pFile ) + { + fseek( pFile, 0L, SEEK_END ); + size_t size = ftell( pFile ); + fseek( pFile, 0L, SEEK_SET ); + + return size; + } + + int DSK_GetNumTracks() + { + return gnDskSize / (16 * DSK_SECTOR_SIZE); + } + + +// @returns true if able to determine interleave +// ======================================================================== +bool DskGetInterleave( const char *dsk_filename ) +{ + if( !dsk_filename ) + return false; + + // .do = DOS + // .po = PRO + // .dsk = DOS + const size_t nLen = strlen( dsk_filename ); + giInterleaveLastUsed = INTERLEAVE_AUTO_DETECT; + + if( nLen < 1 ) + return false; + + if( giInterleaveLastUsed == INTERLEAVE_AUTO_DETECT ) + { + const char *pExt = file_GetExtension( dsk_filename ); + if( pExt ) + { + if( stricmp( pExt, ".do" ) == 0 ) giInterleaveLastUsed = INTERLEAVE_DOS33_ORDER ; + if( stricmp( pExt, ".po" ) == 0 ) giInterleaveLastUsed = INTERLEAVE_PRODOS_ORDER; + if( stricmp( pExt, ".dsk") == 0 ) giInterleaveLastUsed = INTERLEAVE_DOS33_ORDER ; + } + +#if DEBUG_DSK_INTERLEAVE + printf( "DskLoad() auto-detect interleave: " ); + if( giInterleaveLastUsed == INTERLEAVE_DOS33_ORDER ) printf( "DOS3.3 \n" ); + if( giInterleaveLastUsed == INTERLEAVE_PRODOS_ORDER) printf( "ProDOS \n" ); + if( giInterleaveLastUsed == INTERLEAVE_AUTO_DETECT ) printf( "unknown\n" ); +#endif + } + +#if 0 + if( giInterleaveLastUsed == INTERLEAVE_AUTO_DETECT ) + { + printf( "WARNING: Defaulting to DOS 3.3 sector interleave\n" ); + giInterleaveLastUsed = INTERLEAVE_DOS33_ORDER; + } +#endif + + return giInterleaveLastUsed != INTERLEAVE_AUTO_DETECT; +} + + +// @return true = OK, false = ERROR +// ======================================================================== +bool DskLoad( const char *dsk_filename, SectorOrder_e sector_order ) +{ + if( !dsk_filename ) + return false; + + int valid = 0; + + FILE *pFile = fopen( dsk_filename, "rb" ); + if( pFile ) + { + gnDskSize = File_Size( pFile ); + +#if DEBUG_DSK_LOAD + printf( "==========\n" ); + printf( "gnDskSize: %06X\n", gnDskSize ); + printf( "==========\n" ); +#endif + + if( gnDskSize > DSK_SIZE_32M ) + { + printf( "ERROR: Disk size %s > %s !\n", itoa_comma( gnDskSize ), itoa_comma( DSK_SIZE_32M ) ); + gnDskSize = 0; + valid = -1; + } + else + { + if( giInterleaveLastUsed == INTERLEAVE_PRODOS_ORDER ) + fread( gaDsk, 1, gnDskSize, pFile ); + else + if( giInterleaveLastUsed == INTERLEAVE_DOS33_ORDER ) + { + int nTracks = DSK_GetNumTracks(); + size_t offset = 0; + +#if DEBUG_DSK_LOAD + printf( "Tracks: %d\n", nTracks ); +#endif + + for( int iTracks = 0; iTracks < nTracks; iTracks++ ) + { + for( int iSector = 0; iSector < 16; iSector++ ) + { + size_t src; + size_t dst; + Interleave_Forward( iSector, &src, &dst ); + +#if DEBUG_DSK_LOAD + if( iTracks == 0 ) + printf( "T0S%X -> %06X\n", iSector, src ); +#endif + fseek( pFile, offset + src, SEEK_SET ); + fread( &gaDsk[ offset + dst ], 1, DSK_SECTOR_SIZE, pFile ); + } + + offset += 16 * DSK_SECTOR_SIZE; + } + } + valid = 1; + } + + fclose( pFile ); + }; + +#if DEBUG_DSK_LOAD + printf( "DEBUG: DISK: Read Valid: %d\n", valid ); +#endif + + return (valid == 1); +} + + +// ======================================================================== +bool DskSave() +{ + FILE *pFile = fopen( gpDskName, "w+b" ); + + if( !pFile ) + { + printf( "ERROR: Couldn't write to: %s\n", gpDskName ); + } + + fseek( pFile, 0L, SEEK_SET ); + + if( giInterleaveLastUsed == INTERLEAVE_PRODOS_ORDER ) + { + int wrote = fwrite( gaDsk, 1, gnDskSize, pFile ); + (void) wrote; +#if DEBUG_DSK_SAVE + printf( "DskSave() wrote .po: %d\n", wrote ); +#endif + } + else + if( giInterleaveLastUsed == INTERLEAVE_DOS33_ORDER ) + { + int nTracks = DSK_GetNumTracks(); + size_t offset = 0; + +#if DEBUG_DSK_SAVE + printf( "Tracks: %d\n", nTracks ); +#endif + + for( int iTracks = 0; iTracks < nTracks; iTracks++ ) + { + size_t dst = offset; + + for( int iSector = 0; iSector < 16; iSector++ ) + { + size_t src; + size_t dst; + Interleave_Reverse( iSector, &src, &dst ); + +#if DEBUG_DSK_SAVE + if( iTracks == 0 ) + printf( "%06X -> T0S%X\n", dst, iSector ); +#endif + + fseek( pFile, offset + dst, SEEK_SET ); + fwrite( &gaDsk[ offset + src ], 1, DSK_SECTOR_SIZE, pFile ); + } + + offset += 16 * DSK_SECTOR_SIZE; + } + } + + fclose( pFile ); + + return true; +} + + + +// ======================================================================== +void DskDump( int start, int end ) +{ + printf( "----: 0 1 2 3 4 5 6 7 - 8 9 A B C D E F |0123456789ABCDEF|\n" ); + + while( start < end ) + { + printf( "%04X: ", start ); + + for( int byte = 0; byte < 16; byte++ ) + { + if( byte == 8 ) + printf("- " ); + printf( "%02X ", gaDsk[ start + byte ] ); + } + + printf( "|" ); + + for( int byte = 0; byte < 16; byte++ ) + { + uint8_t c = gaDsk[ start + byte ]; + c &= 0x7F; + + if( c < 0x20 ) + c = '.'; + if( c == 0x7F ) + c = '.'; + + + printf( "%c", c ); + } + + printf( "|\n" ); + + start += 16; + } + + printf( "\n" ); +} + diff --git a/prodos.cpp b/prodos.cpp new file mode 100644 index 0000000..2f89c43 --- /dev/null +++ b/prodos.cpp @@ -0,0 +1,612 @@ +#define DEBUG_MAIN 0 + + #include // printf() + #include // uint8_t + #include // stat + #include // exit() + #include // memcpy() + #include // time() localtime() + + #include "itoa.comma.h" + #include "string.utils.cpp" + #include "generic.disk.cpp" + #include "prodos.utils.cpp" + #include "prodos.tools.cpp" + +/* + prodos cp foo.dsk file1 [file2 ...] + +TODO: + PRODOS_VOLUME=test.po + -path / + +*/ + + enum DISK_COMMANDS_e + { + DISK_COMMAND_CAT_SHORT = 0 // cat + ,DISK_COMMAND_FILE_ADD // cp - Copy host filesystem to virtual DSK + ,DISK_COMMAND_CAT_LONG // catalog + ,DISK_COMMAND_CAT_LONG2 // dir - alias for catalog + ,DISK_COMMAND_FILE_GET // get - Copy virtual DSK to host file system + ,DISK_COMMAND_VOL_INIT // init + ,DISK_COMMAND_CAT_NAMES // ls + ,DISK_COMMAND_DIR_CREATE // mkdir + ,DISK_COMMAND_FILE_DELETE // rm + ,DISK_COMMAND_DIR_DELETE // rmdir + ,DISK_COMMAND_INVALID + ,NUM_DISK_COMMANDS + }; + + const char *gaCommands[ NUM_DISK_COMMANDS ] = + { + "cat" // CAT__SHORT + ,"cp" // FILE_ADD + ,"catalog" // CAT__LONG + ,"dir" // CAT__LONG + ,"get" // FILE_GET + ,"init" // VOL__INIT + ,"ls" // CAT__NAMES + ,"mkdir" // DIR__CREATE + ,"rm" // FILE_DELETE + ,"rmdir" // DIR__DELETE + ,"" + }; + + const char *gaDescriptions[ NUM_DISK_COMMANDS ] = + { + "Catalog (short form)" // CAT__SHORT + ,"Catalog (long form)" // CAT__LONG + ,"Add file(s) to volume" // FILE_ADD + ,"Catalog (long form)" // CAT__LONG + ,"Extract file from volume" // FILE_GET + ,"Format disk" // VOL__INIT + ,"Catalog (file names only)" // CAT__NAMES + ,"Create a sub-directory" // DIR__CREATE + ,"Delete file from volume" // FILE_DELETE + ,"Remove a sub-directory" // DIR__DELETE + ,"" + }; + + char sPath[ 256 ] = "/"; + const char *gpPath = NULL; + + int gnDay = 0; + int gnMonth = 0; + int gnYear = 0; + + int giOptions = 0; // index of arg that is 1st command +// int gnOptions = 0; // number of args minus config options + +// ======================================================================== +int usage() +{ + printf( +"Usage: []\n" +"\n" + ); + + for( int iCommand = 0; iCommand < NUM_DISK_COMMANDS-1; iCommand++ ) + printf( " %-7s %s\n", gaCommands[ iCommand ], gaDescriptions[ iCommand ] ); + + printf( +"\n" +"Where is a virtual disk image with an extension of:\n" +"\n" +" .dsk\n" +" .do\n" +" .po\n" +"\n" +"NOTE: To skip always having to specify the <.dsk> name set the environment variable:\n" +"\n" +" PRODOS_VOLUME\n" +"e.g.\n" +" PRODOS_VOLUME=path/to/volume.po\n" +"\n" +"Three different disk sizes are accepted for init\n" +"\n" +" prodos test.dsk init -size=140 # 5 1/4\" (140KB)\n" +" prodos test.dsk init -size=800 # 3 1/2\" (800KB)\n" +" prodos test.dsk init -size=32 #HardDisk (32 MB)\n" +"\n" +"Examples:\n" +"\n" +" prodos test.dsk ls\n" +" prodos test.dsk cat\n" +" prodos test.dsk cp foo1 foo2 /\n" +" prodos test.dsk mkdir bar\n" +" prodos test.dsk cp foo2 /bar\n" +" prodos test.dsk get /bar/foo2\n" +" prodos test.dsk rm /bar/foo2\n" +" prodos test.dsk rmdir /bar\n" +" prodos test.dsk init /TEST\n" +"\n" + ); + + return 1; +} + + +// ======================================================================== +void setTimeNow( ProDOS_FileHeader_t *entry ) +{ + time_t now = time( NULL ); + struct tm *time = localtime( &now ); + + // http://www.manpages.info/macosx/ctime.3.html + gnMonth = time->tm_mon + 1; // 0-11 + gnDay = time->tm_mday; // 1-31 + gnYear = time->tm_year % 100; + + entry->date = ProDOS_DateToInt( gnMonth, gnDay, gnYear ); +} + +/* + -date=MM/DD/YY + -time=HR:MN[a|p] + -type=BIN + -type=$## + -aux=$#### + -access=$## +*/ +// @return false if fatel error +// ======================================================================== +bool getCopyConfig( ProDOS_FileHeader_t *entry, const char *arg ) +{ + size_t nLenPrefix = strlen( arg ); // Total length of: option=val + size_t nLenSuffix = 0; // Length of value + + const char *pVal = 0; + int val = 0; + + if( strncmp( arg, "date=", 5 ) == 0 ) + { + nLenSuffix = nLenPrefix - 5; + pVal = arg + 5; + + // Default to current date + int mon = gnMonth; + int day = gnDay; + int yar = gnYear; + + //-date=MM/DD/YY + //-date=DD-MON-YR + if( (nLenSuffix != 8) || nLenSuffix != 9 ) + { + printf( "ERROR: Invalid date. Format is MM/DD/YY or DD-MON-YR\n" ); + return false; + } + + if( nLenSuffix == 8 ) + { // 01234567 + // MM/DD/YY + if( (pVal[ 2 ] != '/') || (pVal[ 5 ] != '/') ) + { + printf( "ERROR: Invalid date: Format is MM/DD/YY. e.g. 12/31/17\n" ); + return false; + } + + mon = atoi( pVal + 0 ); + day = atoi( pVal + 3 ); + yar = atoi( pVal + 6 ); + } + + if( nLenSuffix == 9 ) + { // 012345678 + // DD-MON-YY + if( (pVal[ 2 ] != '-') || (pVal[ 6 ] != '-') ) + { + printf( "ERROR: Invalid date: Format is DD-MON-YR. e.g. 01-JAN-17\n" ); + return false; + } + + day = atoi( pVal + 0 ); + mon = prodos_DateMonthToInt( pVal + 3 ); + yar = atoi( pVal + 7 ); + } + + entry->date = ProDOS_DateToInt( mon, day, yar ); + + } + else + if( strncmp( arg, "time=", 5 ) == 0 ) + { + nLenSuffix = nLenPrefix - 5; + pVal = arg + 5; + } + else + if( strncmp( arg, "type=", 5 ) == 0 ) + { + nLenSuffix = nLenPrefix - 5; + pVal = arg + 5; + + if( pVal[0] == '$' ) + { + val = getHexVal( pVal + 1 ); + if( val < 0x00 ) val = 0x00; + if( val > 0xFF ) val = 0xFF; + entry->type = val; + } + else + { + char sExt[ 4 ]; + int nLen = string_CopyUpper( sExt, pVal, 3 ); + + for( int iType = 0; iType < 256; iType++ ) + { + if( stricmp( pVal, gaProDOS_FileTypes[ iType ] ) == 0 ) + { + entry->type = iType; + break; + } + } + } + } + else + if( strncmp( arg, "aux=", 4 ) == 0 ) + { + nLenSuffix = nLenPrefix - 4; + pVal = arg + 4; + + val = getHexVal( pVal ); + if( val < 0x0000 ) val = 0x0000; + if( val > 0xFFFF ) val = 0xFFFF; + entry->aux = val; + } + else + if( strncmp( arg, "access=", 7 ) == 0 ) + { + nLenSuffix = nLenPrefix - 7; + pVal = arg + 7; + + val = getHexVal( pVal ); + if( val < 0x00 ) val = 0x00; + if( val > 0xFF ) val = 0xFF; + entry->access = val; + } + + return true; +} + +// ======================================================================== +bool doCopy( ProDOS_FileHeader_t *entry, const char *filename ) +{ + const char *pSrcFileName = filename; + size_t nSrcLen = strlen( pSrcFileName ); + + char *pExt = const_cast( file_GetExtension( pSrcFileName ) ); + char sExt[ 5 ] = ".???"; + size_t nExtLen = 0; + bool bCopiedName = false; + +#if DEBUG_MAIN + printf( "----------\n" ); + printf( "+ %s\n", pSrcFileName ); + printf( " Len: \n", nSrcLen ); + printf( "----------\n" ); +#endif + + // Chop extension down to leading '.' plus max 3 chars + if( pExt ) + { + size_t nLen = string_CopyUpper( sExt, pExt, 4 ); // 3 char extension + + pExt = sExt; + nExtLen = nLen; + } + + if( nSrcLen > 15 ) + { + // Chop off part of name until it fits + + // If we have an extension, chop the prefix preserving extension + // If no extension, chop the prefix + if( pExt ) + { + int end = nSrcLen - 4; + int len1 = string_CopyUpper( gEntry.name + 0, pSrcFileName, end ); + int len2 = string_CopyUpper( gEntry.name + end, pExt ); + + gEntry.len = len1 + len2; + bCopiedName = true; + } + } + + if( !bCopiedName ) + { + gEntry.len = string_CopyUpper( gEntry.name, pSrcFileName ); + } + +#if DEBUG_MAIN + printf( "Entry.name: %s\n", gEntry.name ); +#endif + + if( pExt ) + { + for( int iExt = 0; iExt < 256; iExt++ ) + { + const char *tExt = gaProDOS_FileTypes[ iExt ]; + if( tExt[0] == '?' ) + continue; + + int nExtLen = 3; + if( tExt[2] == ' ' ) + nExtLen = 2; + + bool bFoundExt = true; + for( int i = 0; i < nExtLen; i++ ) + if( sExt[ 1 + i ] != tExt[ i ] ) + bFoundExt = false; + + if( bFoundExt ) + { +#if DEBUG_MAIN +printf( "Auto-detect file type: $%02X %s\n", iExt, tExt ); +#endif + gEntry.type = iExt; + break; + } + } + } + +#if DEBUG_MAIN +#endif + + bool bStatus = ProDOS_FileAdd( gpPath, pSrcFileName, &gEntry ); + return bStatus; +} + + +// ======================================================================== +const char* getVirtualPath( int nArg, const char *aArg[], int *iArg, bool inc ) +{ + if( *iArg < nArg ) + { + const char *pSrc = aArg[ *iArg ]; + char *pDst = sPath; + int nLen = string_CopyUpper( pDst, pSrc, 255 ); + + if( inc ) + *iArg++; + + return sPath; + } + + return NULL; +} + + +// ======================================================================== +void errorBadInterleave() +{ + printf( "ERROR: Unable to detect sector interleave. e.g. .do, .po, or .dsk\n" ); + exit(1); +} + +void errorBadDisk() +{ + printf( "ERROR: Unable to read ProDOS disk: %s\n", gpDskName ); + exit( 1 ); +} + +// ======================================================================== +void readVolume( int nArg, const char *aArg[], int *iArg ) +{ + if( gpDskName ) + { + if( !DskGetInterleave( gpDskName ) ) + errorBadInterleave(); + + if( !DskLoad( gpDskName, (SectorOrder_e) giInterleaveLastUsed ) ) + errorBadDisk(); + } + + gpPath = getVirtualPath( nArg, aArg, iArg, true ); + +#if DEBUG_MAIN + printf( "path: %s\n", gpPath ); +#endif + + prodos_GetVolumeHeader( &gVolume, PRODOS_ROOT_BLOCK ); + +#if DEBUG_MAIN + printf( "Loaded...\n" ); +#endif +} + + +// ======================================================================== +int main( const int nArg, const char *aArg[] ) +{ + int iArg = 1; // DSK is 1st arg + char *pathname_filename = NULL; + char *auto_dsk_name = getenv( "PRODOS_VOLUME" ); + + gpDskName = auto_dsk_name; + + if( auto_dsk_name ) + { + printf( "INFO: Using auto ProDOS disk name: %s\n", auto_dsk_name ); + } + else + { + if( iArg < nArg ) + { + gpDskName = aArg[ iArg ]; + iArg++; +#if DEBUG_MAIN + printf( "Found disk name from command line: %s\n", gpDskName ); +#endif + } + } + + const char *pCommand = iArg < nArg + ? aArg[ iArg ] + : NULL + ; + int iCommand = DISK_COMMAND_INVALID; + if( pCommand ) + for( iCommand = 0; iCommand < NUM_DISK_COMMANDS-1; iCommand++ ) + { + if( strcmp( aArg[ iArg ], gaCommands[ iCommand ] ) == 0 ) + { + iArg++; + break; + } + } + +#if DEBUG_MAIN + printf( "iCommand: %d\n", iCommand ); + printf( "pCommand: %s\n", pCommand ); + printf( "pPathName %s\n", gpPath ); +#endif + + switch( iCommand ) + { + case DISK_COMMAND_CAT_LONG: + case DISK_COMMAND_CAT_LONG2: + readVolume( nArg, aArg, &iArg ); + ProDOS_CatalogLong( gpPath ); + break; + + case DISK_COMMAND_CAT_NAMES: + readVolume( nArg, aArg, &iArg ); + ProDOS_CatalogNames( gpPath ); + break; + + case DISK_COMMAND_CAT_SHORT: + readVolume( nArg, aArg, &iArg ); + ProDOS_CatalogShort( gpPath ); + break; + + // prodos cp file1 [file2 ...] / + case DISK_COMMAND_FILE_ADD: + { +#if DEBUG_MAIN + printf( "DEBUG: cp\n" ); +#endif + + readVolume( nArg, aArg, &iArg ); + + // Check for at least 1 destination (path) + int iDst = nArg - 1; + gpPath = getVirtualPath( nArg, aArg, &iDst, false ); + + // Check for at least 1 source + bool bFilesAdded = false; + + prodos_InitFileHeader( &gEntry ); + setTimeNow( &gEntry ); + +#if DEBUG_MAIN + printf( "DEBUG: zero'd file entry\n" ); + printf( "iArg: %d\n", iArg ); + printf( "nArg: %d\n", nArg ); +#endif + + if( iArg == nArg ) + { + printf( "ERROR: Need virtual destination path. e.g. /\n" ); + break; + } + else + for( ; iArg < nArg-1; iArg++ ) + { + const char *pArg = &aArg[iArg][0]; + +#if DEBUG_MAIN + printf( "DEBUG: %s\n", pArg ); +#endif + + if( pArg[0] == '-' ) + bFilesAdded = getCopyConfig( &gEntry, pArg+1 ); + else + { + bFilesAdded = doCopy( &gEntry, pArg ); + + prodos_InitFileHeader( &gEntry ); // prep for next file + setTimeNow( &gEntry ); + } + + if( !bFilesAdded ) + break; + } + + if( bFilesAdded ) + DskSave(); + + break; + } + + case DISK_COMMAND_FILE_DELETE: + readVolume( nArg, aArg, &iArg ); +// ProDOS_FileDelete( pathname_filename ); + break; + + case DISK_COMMAND_FILE_GET: + readVolume( nArg, aArg, &iArg ); +// ProDOS_FileExtract( pathname_filename ); + break; + + case DISK_COMMAND_VOL_INIT: + gnDskSize = DSK_SIZE_312; // TODO: --size=140 --size=800 --size=32 + if( !DskGetInterleave( gpDskName ) ) + errorBadInterleave(); + +#if DEBUG_MAIN + printf( "iArg: %d / %d\n", iArg, nArg ); +#endif + + for( ; iArg < nArg; iArg++ ) + { + const char *pArg = &aArg[iArg][0]; + +#if DEBUG_MAIN + printf( "#%d: %s\n", iArg, pArg ); +#endif + + if( pArg[0] == '-' ) + { + if( strncmp( pArg+1,"size=", 5 ) == 0 ) + { + int size = atoi( pArg + 6 ); + + if( size == 140 ) gnDskSize = DSK_SIZE_514; + if( size == 800 ) gnDskSize = DSK_SIZE_312; + if( size == 32 ) gnDskSize = DSK_SIZE_32M; + +#if DEBUG_MAIN + printf( "INIT: size = %s\n", itoa_comma( gnDskSize ) ); +#endif + } + else + return printf( "ERROR: Unknown option: %s\n", pArg ); + } + else + break; + } + + gpPath = getVirtualPath( nArg, aArg, &iArg, false ); + +#if DEBUG_MAIN + printf( "INIT: path: %s\n", gpPath ); + printf( "iArg: %d / %d\n", iArg, nArg ); +#endif + + if( gpPath ) + { + ProDOS_Init( gpPath ); + DskSave(); + } + else + return printf( "ERROR: Need virtual volume name. e.g. /TEST\n" ); + + break; + + default: + return usage(); + break; + } + + return 0; +} diff --git a/prodos.tools.cpp b/prodos.tools.cpp new file mode 100644 index 0000000..627beeb --- /dev/null +++ b/prodos.tools.cpp @@ -0,0 +1,1399 @@ +#define DEBUG_ADD 0 +#define DEBUG_BITMAP 0 +#define DEBUG_DATE 0 +#define DEBUG_DIR 0 +#define DEBUG_FILE 0 // File Entry +#define DEBUG_FIND_FILE 0 +#define DEBUG_FREE 0 // Free Blocks +#define DEBUG_INIT 0 +#define DEBUG_META 0 // Bitmap Pages +#define DEBUG_PATH 0 +#define DEBUG_TIME 0 +#define DEBUG_VOLUME 0 + +// --- ProDOS crap --- + + struct SubDirInfo_t + { + uint8_t res75 ; // $75 - magic number + uint8_t pad7[7]; + }; + + struct VolumeExtra_t + { + uint16_t bitmap_block; + uint16_t total_blocks; + }; + + struct SubDirExtra_t + { + uint16_t parent_block; + uint8_t parent_entry_num; + uint8_t parent_entry_len; + }; + + // ============================================================ + + // Due to packing/aignment this may NOT be size identical + // But it is in the correct order + // See: ProDOS_GetVolumeHeader; + struct ProDOS_VolumeHeader_t + { ; //Rel Size Abs + uint8_t kind ; // +0 1 $04 \ Hi nibble Storage Type + uint8_t len ; // +0 / Lo nibble + char name[ 16 ] ; // +1 15 $05 15 on disk but we NULL terminate for convenience +// --- diff from file --- + union + { + uint8_t pad8[ 8 ] ; //+16 8 $14 only in root + SubDirInfo_t info ; // only in sub-dir + } ; +// --- same as file --- + uint16_t date ; //+24 2 $1C + uint16_t time ; //+26 2 $1E + uint8_t cur_ver ; //+28 1 $20 + uint8_t min_ver ; //+29 1 $21 + uint8_t access ; //+30 1 $22 + uint8_t entry_len ; //+31 1 $23 Size of each directory entry in bytes + uint8_t entry_num ; //+32 1 $24 Number of directory entries per block + uint16_t file_count ; //+33 2 $25 Active entries including directories, excludes volume header + union // -- + { // -- + VolumeExtra_t meta ; //+34 2 $27 + SubDirExtra_t subdir; //+36 2 $29 + } ; //============ + } ; // 38 $2B + + void prodos_GetVolumeHeader( ProDOS_VolumeHeader_t *meta_, int block ) + { + int base = block*PRODOS_BLOCK_SIZE + 4; // skip prev/next dir block double linked list + ProDOS_VolumeHeader_t info; + + info.kind = (gaDsk [ base + 0 ] >> 4) & 0xF; + info.len = (gaDsk [ base + 0 ] >> 0) & 0xF; + + for( int i = 0; i < 16; i++ ) + info.name[ i ] = 0; + + for( int i = 0; i < info.len; i++ ) + info.name[ i ] = gaDsk [ base+i+ 1 ]; + + for( int i = 0; i < 8; i++ ) + info.pad8[ i ] = gaDsk [ base+i+16 ]; + + info.date = DskGet16( base + 24 ); + info.time = DskGet16( base + 26 ); + info.cur_ver = gaDsk [ base + 28 ]; + info.min_ver = gaDsk [ base + 29 ]; + info.access = gaDsk [ base + 30 ]; + info.entry_len = gaDsk [ base + 31 ]; // @ 0x423 + info.entry_num = gaDsk [ base + 32 ]; + info.file_count = DskGet16( base + 33 ); + + info.meta.bitmap_block = DskGet16( base + 35 ); + info.meta.total_blocks = DskGet16( base + 37 ); + +#if DEBUG_VOLUME + printf( "DEBUG: VOLUME: name: %s\n", info.name ); + printf( "DEBUG: VOLUME: date: %04X\n", info.date ); + printf( "DEBUG: VOLUME: time: %04X\n", info.time ); + printf( "DEBUG: VOLUME: cVer: %02X\n", info.cur_ver ); + printf( "DEBUG: VOLUME: mVer: %02X\n", info.min_ver ); + printf( "DEBUG: VOLUME:acces: %02X\n", info.access ); + printf( "DEBUG: VOLUME:e.len: %02X\n", info.entry_len ); + printf( "DEBUG: VOLUME:e.num: %02X\n", info.entry_num ); + printf( "DEBUG: VOLUME:files: %04X ", info.file_count ); + printf( "(%d)\n", info.file_count ); + +if( block == PRODOS_ROOT_BLOCK ) +{ + printf( "DEBUG: ROOT: bitmap: %04X\n", info.meta.bitmap_block ); + printf( "DEBUG: ROOT: blocks: %04X\n", info.meta.total_blocks ); +} else { + printf( "DEBUG: SUBDIR: parent: %04X\n", info.subdir.parent_block ); + printf( "DEBUG: SUBDIR: pa.len: %02X\n", info.subdir.parent_entry_num ); + printf( "DEBUG: SUBDIR: pa.num: %02X\n", info.subdir.parent_entry_len ); +} +#endif + + if( meta_ ) + *meta_ = info; + }; + + + void prodos_SetVolumeHeader( ProDOS_VolumeHeader_t *volume, int block ) + { + if( !volume ) + return; + + int base = block*PRODOS_BLOCK_SIZE + 4; // skip prev/next dir block double linked list + + uint8_t KindLen = 0 + | ((volume->kind & 0xF) << 4) + | ((volume->len & 0xF) << 0) + ; + + gaDsk[ base + 0 ] = KindLen; + + for( int i = 0; i < volume->len; i++ ) + gaDsk [ base+ 1+i ] = volume->name[ i ]; + + for( int i = 0; i < 8; i++ ) + gaDsk [ base+16+i ] = volume->pad8[ i ]; + + DskPut16( base + 24 , volume->date ); + DskPut16( base + 26 , volume->time ); + gaDsk [ base + 28 ] = volume->cur_ver ; + gaDsk [ base + 29 ] = volume->min_ver ; + gaDsk [ base + 30 ] = volume->access ; + gaDsk [ base + 31 ] = volume->entry_len; + gaDsk [ base + 32 ] = volume->entry_num; + DskPut16( base + 33 , volume->file_count ); + DskPut16( base + 35 , volume->meta.bitmap_block ); + DskPut16( base + 37 , volume->meta.total_blocks ); + } + + // ============================================================ + struct ProDOS_FileHeader_t + { ; //Rel Size Hex + uint8_t kind ; // +0 1 $00 \ Hi nibble Storage Type + uint8_t len ; // +0 / Lo nibble + char name[ 16 ] ; // +1 15 $05 15 on disk but we NULL terminate for convenience +// --- diff from volume --- + uint8_t type ; //+16 1 $10 User Type + uint16_t inode ; //+17 2 $11 + uint16_t blocks ; //+19 2 $13 + uint32_t size ; //+21 3 $15 EOF address - on disk is 3 bytes, but 32-bit for convenience +// --- same as volume --- + uint16_t date ; //+24 2 $18 + uint16_t time ; //+26 2 $1A + uint8_t cur_ver ; //+28 1 $1C + uint8_t min_ver ; //+29 1 $1D // 0 = ProDOS 1.0 + uint8_t access ; //+30 1 $1E +// --- diff from subdir + uint8_t aux ; //+31 2 $1F Load Address for Binary +// --- diff from volume --- + uint16_t mod_date ; //+33 2 $21 + uint16_t mod_time ; //+35 2 $23 + uint16_t dir_block ; //+37 2 $25 pointer to directory block + ; //============ + }; ; // 39 $27 + + // ------------------------------------------------------------ + void prodos_GetFileHeader( int offset, ProDOS_FileHeader_t *file_ ) + { + ProDOS_FileHeader_t info; + + info.kind = (gaDsk [ offset + 0 ] >> 4) & 0xF; + info.len = (gaDsk [ offset + 0 ] >> 0) & 0xF; + + for( int i = 0; i < 16; i++ ) + info.name[ i ] = 0; + + for( int i = 0; i < info.len; i++ ) + info.name[ i ] = gaDsk [ offset + 1+i]; + + info.type = gaDsk [ offset + 16 ]; + info.inode = DskGet16( offset + 17 ); + info.blocks = DskGet16( offset + 19 ); + info.size = DskGet24( offset + 21 ); + info.date = DskGet16( offset + 24 ); + info.time = DskGet16( offset + 26 ); + info.cur_ver = gaDsk [ offset + 28 ]; + info.min_ver = gaDsk [ offset + 29 ]; + info.access = gaDsk [ offset + 30 ]; + info.aux = DskGet16( offset + 31 ); + info.mod_date = DskGet16( offset + 33 ); + info.mod_time = DskGet16( offset + 35 ); + info.dir_block = DskGet16( offset + 37 ); + +#if DEBUG_FILE + printf( "DEBUG: FILE: name: %s\n" , info.name ); + printf( "DEBUG: FILE: kind: $%02X\n", info.kind ); + printf( "DEBUG: FILE: type: $%02X\n", info.type ); + printf( "DEBUG: FILE:inode: $%04X\n", info.inode ); + printf( "DEBUG: FILE:block: $%04X\n", info.blocks ); + printf( "DEBUG: FILE: size: $%06X\n", info.size ); + printf( "DEBUG: FILE: date: $%02X\n", info.date ); + printf( "DEBUG: FILE: time: $%02X\n", info.time ); + printf( "DEBUG: FILE: cVer: $%02X\n", info.cur_ver ); + printf( "DEBUG: FILE: mVer: $%02X\n", info.min_ver ); + printf( "DEBUG: FILE: aux: $%04X\n", info.aux ); + printf( "DEBUG: FILE:mDate: $%02X\n", info.mod_date ); + printf( "DEBUG: FILE:mTime: $%02X\n", info.mod_time ); + printf( "DEBUG: FILE:dBloc: $%04X\n", info.dir_block ); + printf( "-----\n" ); +#endif + + if( file_ ) + *file_ = info; + } + + void prodos_PutFileHeader( int offset, ProDOS_FileHeader_t *file ) + { + int base = offset; + + uint8_t KindLen = 0 + | ((file->kind & 0xF) << 4) + | ((file->len & 0xF) << 0) + ; + + gaDsk [ base + 0 ] = KindLen; + + for( int i = 0; i < file->len; i++ ) + gaDsk [ base + 1+i] = file->name[ i ]; + + gaDsk [ base + 16 ] = file->type; + DskPut16( base + 17 , file->inode ); + DskPut16( base + 19 , file->blocks ); + DskPut24( base + 21 , file->size ); + + DskPut16( base + 24 , file->date ); + DskPut16( base + 26 , file->time ); + gaDsk [ base + 28 ] = file->cur_ver ; + gaDsk [ base + 29 ] = file->min_ver ; + gaDsk [ base + 30 ] = file->access ; + + gaDsk [ base + 31 ] = file->aux ; + DskPut16( base + 33 , file->mod_date ); + DskPut16( base + 35 , file->mod_time ); + DskPut16( base + 37 , file->dir_block ); + } + + void prodos_InitFileHeader( ProDOS_FileHeader_t *entry ) + { + memset( entry, 0, sizeof( ProDOS_FileHeader_t ) ); + } + +// Globals ________________________________________________________________ + + int gnLastDirBlock = 0; + char gpLastDirName[ 16 ]; + + int gnLastDirMaxFiles = 0; + + ProDOS_VolumeHeader_t gVolume; + ProDOS_FileHeader_t gEntry; // Default fields for cp + +// @return total blocks allocated for directory +// ------------------------------------------------------------------------ +int prodos_BlockGetDirectoryCount( int offset ) +{ + int blocks = 0; + int next_block = 0; + + do + { + blocks++; + next_block = DskGet16( offset + 2 ); + offset = next_block * PRODOS_BLOCK_SIZE; + } while( next_block ); + + return blocks; +} + + +// @returns 0 if no free block +// ------------------------------------------------------------------------ +int prodos_BlockGetFirstFree( ProDOS_VolumeHeader_t *volume ) +{ + if( !volume ) + { + printf( "ERROR: Volume never read\n" ); + return 0; + } + + int bitmap = volume->meta.bitmap_block; + int offset = bitmap * PRODOS_BLOCK_SIZE; + int size = (gnDskSize + 7) / 8; + int block = 0; + +#if DEBUG_FREE + printf( "DskSize: %06x\n", gnDskSize ); + printf( "Bitmap @ %04X\n", bitmap ); + printf( "Offset : %06X\n", offset ); + printf( "Bytes : %06X\n", size ); +#endif + + for( int byte = 0; byte < size; byte++ ) + { + int mask = 0x80; + do + { + if( gaDsk[ offset + byte ] & mask ) + return block; + + mask >>= 1; + block++; + } + while( mask ); + } + +#if DEBUG_FREE + printf( "ZERO free block\n" ); +#endif + + return 0; +} + + +// @return number of total disk blocks +// ------------------------------------------------------------------------ +int prodos_BlockInitFree( ProDOS_VolumeHeader_t *volume ) +{ + int bitmap = volume->meta.bitmap_block; + int offset = bitmap * PRODOS_BLOCK_SIZE; + int blocks = (gnDskSize + PRODOS_BLOCK_SIZE - 1) / PRODOS_BLOCK_SIZE; + int size = (blocks + 7) / 8; + +#if DEBUG_INIT + printf( "offset: %06X\n", offset ); + printf( "Total Blocks: %d\n", blocks ); + printf( "Bitmap Blocks: %d\n", (size + PRODOS_BLOCK_SIZE - 1) / PRODOS_BLOCK_SIZE ); +#endif + + memset( &gaDsk[ offset ], 0xFF, size ); + +#if DEBUG_INIT + printf( "Bitmap after init\n" ); + DskDump( offset, offset + 64 ); + printf( "\n" ); +#endif + + volume->meta.total_blocks = blocks; + return (size + PRODOS_BLOCK_SIZE - 1) / PRODOS_BLOCK_SIZE; +} + + +// ------------------------------------------------------------------------ +bool prodos_BlockSetFree( ProDOS_VolumeHeader_t *volume, int block ) +{ + if( !volume ) + { + printf( "ERROR: Volume never read\n" ); + return false; + } + + int bitmap = volume->meta.bitmap_block; + int offset = bitmap * PRODOS_BLOCK_SIZE; + + int byte = block / 8; + int bits = block % 8; + int mask = 0x80 >> bits; + + gaDsk[ offset + byte ] |= mask; + + return true; +} + +// ------------------------------------------------------------------------ +bool prodos_BlockSetUsed( ProDOS_VolumeHeader_t *volume, int block ) +{ + if( !volume ) + { + printf( "ERROR: Volume never read\n" ); + return false; + } + + int bitmap = volume->meta.bitmap_block; + int offset = bitmap * PRODOS_BLOCK_SIZE; + + int byte = block / 8; + int bits = block % 8; + int mask = 0x80 >> bits; + + gaDsk[ offset + byte ] |= mask; + gaDsk[ offset + byte ] ^= mask; + + return true; +} + + +// @return 0 if couldn't find a free directory entry, else offset +// ------------------------------------------------------------------------ +int prodos_DirGetFirstFree( ProDOS_VolumeHeader_t *volume, int base ) +{ + int next_block; + int prev_block; + + int offset = base + 4; + + // Try to find the file in this directory + do + { + prev_block = DskGet16( base + 0 ); + next_block = DskGet16( base + 2 ); + + for( int iFile = 0; iFile < volume->entry_num; iFile++ ) + { + ProDOS_FileHeader_t file; + prodos_GetFileHeader( offset, &file ); + +#if DEBUG_DIR + printf( "DEBUG: DIR: " ); + printf( "#%2d %s\n", iFile, file.name ); + printf( "... Type: $%1X, %s\n", file.kind, prodos_KindToString( file.kind ) ); +#endif + + if( file.kind == ProDOS_KIND_DEL) + return offset; + + if( file.len == 0 ) + return offset; + + offset += volume->entry_len; + } + + base = next_block * PRODOS_BLOCK_SIZE; + offset = base + 4; + + } while( next_block ); + + return 0; +} + + +// @return 0 if couldn't find file, else offset +// ------------------------------------------------------------------------ +int prodos_FindFile( ProDOS_VolumeHeader_t *volume, const char *path, int base = 0x400 ) +{ + if( !path ) + return base; + + size_t nPathLen = strlen( path ); + if( nPathLen == 0 ) + return base; + +#if DEBUG_FIND_FILE + printf( "DEBUG: PATH: %s\n", path ); + printf( "DEBUG: .len: %d\n", nPathLen ); +#endif + + if( path[0] == '/' ) + path++; + + // Get path head + int iDirName = nPathLen; + char sDirName[16]; + int i; + + for( i = 0; i < nPathLen; i++ ) + if( path[i] == '/' ) + { + iDirName = i - 1; + break; + } + + memcpy( sDirName, path, iDirName ); + sDirName[ iDirName ] = 0; + + strcpy( gpLastDirName, sDirName ); + +#if DEBUG_FIND_FILE + printf( "DEBUG: FIND: [%d]\n", iDirName ); + printf( "DEBUG: SUBD: %s\n" , sDirName ); + printf( "DEBUG: base: %04X\n", base ); +#endif + + int next_block; + int prev_block; + + int offset = base + 4; // skip prev,next block pointers + + // Try to find the file in this directory + do + { + prev_block = DskGet16( base + 0 ); + next_block = DskGet16( base + 2 ); + + for( int iFile = 0; iFile < volume->entry_num; iFile++ ) + { + ProDOS_FileHeader_t file; + prodos_GetFileHeader( offset, &file ); + +#if DEBUG_FIND_FILE + printf( "... %s\n", file.name ); + printf( "... Type: $%1X, %s\n", file.kind, prodos_KindToString( file.kind ) ); +#endif + + if((file.len == 0) + || (file.name[0] == 0) + || (file.kind == ProDOS_KIND_DEL) + || (file.kind == ProDOS_KIND_ROOT) + ) + goto next_file; + +/* + if( file.kind != ProDOS_KIND_DIR ) + goto next_file; +*/ + + if( strcmp( file.name, sDirName ) == 0 ) + { + const char *next = path + iDirName; + int addr = file.inode * PRODOS_BLOCK_SIZE; + + gnLastDirBlock = file.inode; + gnLastDirMaxFiles = file.size / volume->entry_num; + +#if DEBUG_FIND_FILE + printf( "***\n" ); + printf( " DIR -> %04X\n", addr ); + printf( " path -> %s\n" , next ); +#endif + + return prodos_FindFile( volume, next, addr ); + } + +next_file: + offset += volume->entry_len; + ; + } + + base = next_block * PRODOS_BLOCK_SIZE; + offset = base + 4; + + } while( next_block ); + +#if DEBUG_FIND_FILE + printf( "*** File Not Found\n" ); +#endif + + return 0; +} + + +// ------------------------------------------------------------------------ +int prodos_BlockGetPath( const char *path ) +{ + int offset = PRODOS_ROOT_BLOCK*PRODOS_BLOCK_SIZE; // Block 2 * 0x200 Bytes/Block = 0x400 abs offset + + // Scan Directory ... + strcpy( gpLastDirName, gVolume.name ); + + gnLastDirBlock = offset; + int nDirBlocks = prodos_BlockGetDirectoryCount( offset ); + gnLastDirMaxFiles = nDirBlocks * gVolume.entry_num; + +#if DEBUG_PATH + printf( "Directory Entry Bytes : $%04X\n", gVolume.entry_len ); + printf( "Directory Entries/Block: %d\n" , gVolume.entry_num ); + printf( "VOLUME Directory Blocks: %d\n" , nDirBlocks ); + printf( "...Alt. max dir files : %d\n" ,(nDirBlocks * PRODOS_BLOCK_SIZE) / volume.entry_len ); // 2nd way to verify +#endif + + if( path == NULL ) + return offset; + + if( strcmp( path, "/" ) == 0 ) + return offset; + + return prodos_FindFile( &gVolume, path, offset ); +} + + +// @returns Total Free Blocks +// ------------------------------------------------------------------------ +int prodos_BlockGetFreeTotal( ProDOS_VolumeHeader_t *volume ) +{ + int blocks = volume->meta.total_blocks; + int ALIGN = 8 * PRODOS_BLOCK_SIZE; + int pages = (blocks + ALIGN) / ALIGN; + int inode = volume->meta.bitmap_block; + int base = inode * PRODOS_BLOCK_SIZE; + + int free = 0; + int byte; + int bits; + +#if DEBUG_BITMAP + printf( "DEBUG: BITMAP: Blocks: %d\n" , blocks ); + printf( "DEBUG: BITMAP: iNode : @%04X\n", inode ); + printf( "DEBUG: BITMAP: Pages : %d\n" , pages ); + printf( "DEBUG: BITMAP: offset: $%06X\n", base ); +#endif + + while( pages --> 0 ) + { + for( byte = 0; byte < PRODOS_BLOCK_SIZE; byte++ ) + { + uint8_t bitmap = gaDsk[ base + byte ]; + +#if DEBUG_META +if( bitmap ) + printf( "free @ base: %8X, offset: %04X\n", base, byte ); +#endif + + for( bits = 0; bits < 8; bits++, bitmap >>= 1 ) + if( bitmap & 1 ) + free++; + } + + base += PRODOS_BLOCK_SIZE; + } + + return free; +} + + +// ------------------------------------------------------------------------ +void prodos_Summary( ProDOS_VolumeHeader_t *volume, int files, int iFirstFree ) +{ + if( !volume ) + return; + + int sum = volume->meta.total_blocks; + int free = prodos_BlockGetFreeTotal( volume ); + int used = sum - free; + + int max = gnLastDirMaxFiles; + if( !max ) + max = 1; + + if( !sum ) + sum = 1; + + // Max files in (65,535-2) blocks * 13 files/block = 851,929 directory entries + + printf( "========\n" ); + printf( "Files: %7s / %s", itoa_comma( files ), itoa_comma( gnLastDirMaxFiles ) ); + printf( " (%5.2f%%)\n" , (100. * files) / max ); + printf( "Blocks: \n" ); + printf( " Free: %6s (%5.2f%%)" , itoa_comma( free ), (100. * free) / sum ); + if( iFirstFree ) + printf( ", 1st: @ $%04X = %s", iFirstFree, itoa_comma( iFirstFree ) ); + printf( "\n" ); + printf( " Used: %6s (%5.2f%%)\n", itoa_comma( used ), (100. * used) / sum ); + printf( " Total: %6s\n", itoa_comma( sum ) ); +} + + +// ======================================================================== +bool ProDOS_GetVolumeName( char *pVolume_ ) +{ + bool bValid = false; + + int vol_length = gaDsk[ 0x404 ] & 0xF; + + if( vol_length < 16 ) + { + for( int i = 0; i < vol_length; i++ ) + *pVolume_++ = gaDsk[ 0x405 + i ]; + } + + *pVolume_ = 0; + + return bValid; +} + + +/* + Root + <-- print blank line + /VOLUME ...
... + \_____/ + name + + Sub-dir + + /VOLUME/dir1/subdir1 <-- printed + subdir1 ...
... + \_____/ + name +*/ +void prodos_PathFullyQualified( ProDOS_VolumeHeader_t *volume, const char *path, int base, char *name_ ) +{ + // We have a sub-directory + if( base != PRODOS_ROOT_BLOCK*PRODOS_BLOCK_SIZE ) + { + printf( "/%s", volume->name ); + printf( "/%s\n", path[0] == '/' ? path+1 : path ); + + strcpy( name_, " Name" ); + } + else + { + printf( "\n" ); + sprintf( name_, "/%s", volume->name ); + } +} + + +// ======================================================================== +bool ProDOS_CatalogLong( const char *path = NULL ) +{ + int files = 0; + int base = prodos_BlockGetPath( path ); + int offset = base + 4; // skip prev,next block pointers + +#if DEBUG_DIR + printf( "DEBUG: CATALOG: Dir @ %04X\n", base ); +#endif + + if( !base ) + { + if( path ) + printf( "ERROR: Couldn't find directory: %s\n", path ); + return false; + } + + int prev_block = 0; + int next_block = 0; + + char dirname[ 15 + 3 + 1 ]; + prodos_PathFullyQualified( &gVolume, path, base, dirname ); + + printf( "dnb??iwr %-16s" + " Blocks Size Type Aux Kind iNode Dir Ver Min Create Time Modified Time \n", dirname ); + printf( "-------- ---------------- ------ ------- ------- ----- ----- ----- ----- --- --- --------- ------ --------- ------\n" ); +// printf( "dnb00011 0123456789ABCDEF 65536 $123456 BIN $FF $0000 sap F @0000 @0000 v00 v00 11-JAN-01 1:23a 11-JAN-01 12:34p\n" ); + + do + { + prev_block = DskGet16( base + 0 ); + next_block = DskGet16( base + 2 ); + + for( int iFile = 0; iFile < gVolume.entry_num; iFile++ ) + { + ProDOS_VolumeHeader_t dir; + ProDOS_FileHeader_t file; + prodos_GetFileHeader( offset, &file ); + + + if( (file.len == 0) || (file.name[0] == 0) ) + goto next_file; + + if( file.kind == ProDOS_KIND_ROOT ) // Skip root Volume name entry + goto next_file; + + if( file.kind == ProDOS_KIND_SUB ) + prodos_GetVolumeHeader( &dir, base/PRODOS_BLOCK_SIZE ); + else + files++; + + char sAccess[16]; + prodos_AccessToString( file.access, &sAccess[0] ); + + char sCreateDate[ 16 ]; + char sCreateTime[ 16 ]; + prodos_DateToString( file.date, &sCreateDate[0] ); + prodos_TimeToString( file.time, &sCreateTime[0] ); + + char sAccessDate[ 16 ]; + char sAccessTime[ 16 ]; + prodos_DateToString( file.mod_date, &sAccessDate[0] ); + prodos_TimeToString( file.mod_time, &sAccessTime[0] ); + + printf( "%s " , sAccess ); + + if( file.kind == ProDOS_KIND_DIR ) + printf( "/" ); + else + { + if( !(file.access & ACCESS_W) ) + printf( "*" ); // File Locked -- can't write to it + else + printf( " " ); + } + + printf( "%-15s " , file.name ); + + // Sub-dir header entry doesn't have these fields + // type + // inode + // blocks + // size + // aux + if( file.kind == ProDOS_KIND_SUB ) { + printf( "------ " ); + printf( "$------ " ); + printf( "--- " ); + printf( "$-- " ); + printf( "$---- " ); + } else { + printf( "%6d " , file.blocks ); // * 512 bytes/block = 33,554,432 bytes max ProDOS volume + printf( "$%06X " , file.size ); + printf( "%s " , gaProDOS_FileTypes[ file.type ] ); + printf( "$%02X " , file.type ); + printf( "$%04X " , file.aux ); + } + + printf( "%s " , gaProDOS_KindDescription[ file.kind ] ); + printf( "%1X " , file.kind ); + + if( file.kind == ProDOS_KIND_SUB ) + { + printf( "*%04X ", dir.subdir.parent_block ); + printf( "@---- " ); + } else { + printf( "@%04X " , file.inode ); // FFFF = 65536 + printf( "@%04X " , file.dir_block ); + } + printf( "%1d.%1d " , file.cur_ver/16, file.cur_ver & 15 ); + printf( "v%02X " , file.min_ver ); + + printf( "%s " , sCreateDate ); // Created + printf( "%s " , sCreateTime ); + printf( "%s " , sAccessDate ); // Modified + printf( "%s " , sAccessTime ); + printf( "\n" ); + + if( file.kind == ProDOS_KIND_SUB ) + { + } + +next_file: + offset += gVolume.entry_len; + } + + base = next_block * PRODOS_BLOCK_SIZE; + offset = base + 4; // skip prev,next block pointers + + } while( next_block ); + + int iFirstFree = prodos_BlockGetFirstFree( &gVolume ); + prodos_Summary( &gVolume, files, iFirstFree ); + + return true; +} + + +bool ProDOS_CatalogNames( const char *path = NULL ) +{ + int files = 0; + int base = prodos_BlockGetPath( path ); + int offset = base + 4; // skip prev,next block pointers + + int prev_block = 0; + int next_block = 0; + + if( !base ) + { + if( path ) + printf( "ERROR: Couldn't find directory: %s\n", path ); + return false; + } + + do + { + prev_block = DskGet16( base + 0 ); + next_block = DskGet16( base + 2 ); + + for( int iFile = 0; iFile < gVolume.entry_num; iFile++ ) + { + ProDOS_FileHeader_t file; + prodos_GetFileHeader( offset, &file ); + + if( (file.len == 0) || (file.name[0] == 0) ) + goto next_file; + + if( file.kind == ProDOS_KIND_ROOT ) + goto next_file; + + // TODO: Skip sub-dir kind: $D or $E ? + + printf( "%s", file.name ); + + if( file.kind == ProDOS_KIND_DIR ) // skip root volume name + printf( "/" ); + + printf( "\n" ); +next_file: + offset += gVolume.entry_len; + } + + base = next_block * PRODOS_BLOCK_SIZE; + offset = base + 4; // skip prev,next block pointers + + } while( next_block ); + + return true; +} + +// ======================================================================== +bool ProDOS_CatalogShort( const char *path = NULL ) +{ +#if DEBUG_DIR + printf( "ProDOS_CatalogShort()\n" ); + + int addr; + +printf( "==========\n" ); + addr = 2*PRODOS_BLOCK_SIZE + 5; + printf( "$%04X: ", addr ); + + for( int i = 0; i < 16; i++ ) + printf( "%c", gaDsk[ addr + i ] ); // Block 2a + printf( "\n" ); + + addr = 5*PRODOS_BLOCK_SIZE/2 + 17; + printf( "$%04X: ", addr ); + + for( int i = 0; i < 16; i++ ) + printf( "%c", gaDsk[ addr + i ] ); // Block 2b + printf( "\n" ); + + addr = 3*PRODOS_BLOCK_SIZE + 1; + printf( "$%04X: ", addr ); + + for( int i = 0; i < 16; i++ ) + printf( "%c", gaDsk[ addr + i ] ); // Block 3a + printf( "\n" ); +printf( "==========\n" ); +#endif + + int files = 0; + int base = prodos_BlockGetPath( path ); + int offset = base + 4; // skip prev,next block pointers + + int prev_block = 0; + int next_block = 0; + + if( !base ) + { + if( path ) + printf( "ERROR: Couldn't find directory: %s\n", path ); + return false; + } + + char dirname[ 15 + 3 + 1 ]; + prodos_PathFullyQualified( &gVolume, path, base, dirname ); + + printf( "%-16s" + " Blocks Type Modified Time \n", dirname ); + printf( "---------------- ------ ---- --------- ------\n" ); +// printf( "0123456789ABCDEF 65536 BIN 11-JAN-01 12:34p\n" ); + + do + { + prev_block = DskGet16( base + 0 ); + next_block = DskGet16( base + 2 ); + + for( int iFile = 0; iFile < gVolume.entry_num; iFile++ ) + { + ProDOS_FileHeader_t file; + prodos_GetFileHeader( offset, &file ); + + if( (file.len == 0) || (file.name[0] == 0) ) + goto next_file; + + if( file.kind == ProDOS_KIND_ROOT ) + goto next_file; + + files++; + + char sAccessDate[ 16 ]; + char sAccessTime[ 16 ]; + prodos_DateToString( file.mod_date, &sAccessDate[0] ); + prodos_TimeToString( file.mod_time, &sAccessTime[0] ); + + if( file.kind == ProDOS_KIND_DIR ) // skip root volume name + printf( "/" ); + else + { + if( !(file.access & ACCESS_W) ) + printf( "*" ); // File Locked -- can't write to it + else + printf( " " ); + } + + printf( "%-15s " , file.name ); + printf( "%6d " , file.blocks ); // * 512 bytes/block = 33,554,432 bytes max ProDOS volume + printf( "%s " , gaProDOS_FileTypes[ file.type ] ); + printf( "%s " , sAccessDate ); // Modified + printf( "%s " , sAccessTime ); + printf( "\n" ); + +next_file: + offset += gVolume.entry_len; + } + + base = next_block * PRODOS_BLOCK_SIZE; + offset = base + 4; // skip prev,next block pointers + + } while( next_block ); + + prodos_Summary( &gVolume, files, 0 ); + + return true; +} + + +// dst <- src +// ======================================================================== +bool ProDOS_FileAdd( const char *to_path, const char *from_filename, ProDOS_FileHeader_t *attribs ) +{ +#if DEBUG_ADD + printf( "Adding:\n" ); + printf( " ... %s" , from_filename ); + printf( " --> %s\n", to_path ); +#endif + + int base = prodos_BlockGetPath( to_path ); + if( !base ) + { + printf( "ERROR: Couldn't find destination directory: %s\n", to_path ); + return false; + } + +#if DEBUG_ADD + printf( "DEBUG: ADD: Path @ %06X\n", base ); +#endif + + +// int nDirBlocks = prodos_BlockGetDirectoryCount(); +// int nDirEntriesTotal = 0; +// int nDirEntriesFree = 0; + + // Do we have room to add it to this directory? + int iDirBlock = base / PRODOS_BLOCK_SIZE; + + ProDOS_VolumeHeader_t dir; + prodos_GetVolumeHeader( &dir, iDirBlock ); + + if( dir.file_count + 1 >= gnLastDirMaxFiles ) + { + printf( "ERROR: Not enough room in directory: %d files\n", dir.file_count ); + return false; + } + + int iKind = ProDOS_KIND_DEL; + FILE *pSrcFile = fopen( from_filename, "rb" ); + + if( !pSrcFile ) + { + printf( "ERROR: Couldn't open source file: %s\n", from_filename ); + return false; + } + + + // Block Management + + size_t nSrcSize = File_Size( pSrcFile ); + int nBlocksData = (nSrcSize + PRODOS_BLOCK_SIZE - 1) / PRODOS_BLOCK_SIZE; + int nBlocksIndex = 0; // Num Blocks needed for meta (Index) + int nBlocksTotal = 0; + int nBlocksFree = 0; + + int iNode = 0; // Master Index, Single Index, or 0 if none + int iIndexBase = 0; // Single Index + int iMasterIndex = 0; // master block points to N IndexBlocks + + + if( nSrcSize > gnDskSize ) + { + printf( "ERROR: Source file (%s bytes) doesn't fit on volume (%s bytes).\n", itoa_comma( nSrcSize ), itoa_comma( gnDskSize ) ); + return false; + } + + + uint8_t *buffer = new uint8_t[ nSrcSize + PRODOS_BLOCK_SIZE ]; + memset( buffer, 0, nSrcSize + PRODOS_BLOCK_SIZE ); + + size_t actual = fread( buffer, 1, nSrcSize, pSrcFile ); + fclose( pSrcFile ); + + // Find Free Index Blocks + if( nSrcSize <= 1*PRODOS_BLOCK_SIZE ) // <= 512, 1 Blocks + iKind = ProDOS_KIND_SEED; + else + if( nSrcSize > 256*PRODOS_BLOCK_SIZE ) // >= 128K, 257-65536 Blocks + { + iKind = ProDOS_KIND_TREE; + nBlocksIndex = (nBlocksData + (PRODOS_BLOCK_SIZE/2-1)) / (PRODOS_BLOCK_SIZE / 2); + nBlocksIndex++; // include master index block + } + else + if( nSrcSize > PRODOS_BLOCK_SIZE ) // <= 128K, 2-256 blocks + { + iKind = ProDOS_KIND_SAPL; + nBlocksIndex = 1; // single index = PRODOS_BLOCK_SIZE/2 = 256 data blocks + } + + // Verify we have room in the current directory + int iEntryOffset = prodos_DirGetFirstFree( &gVolume, base ); + +#if DEBUG_ADD + printf( "File Entry offset in Dir: %06X\n", iEntryOffset ); +#endif + + // Verify we have room for index + blocks + nBlocksFree = prodos_BlockGetFreeTotal( &gVolume ); + nBlocksTotal = nBlocksIndex + nBlocksData; + + if( nBlocksFree < nBlocksTotal ) + { + printf( "ERROR: Not enough free room on volume: (%d < %d)\n", nBlocksFree, nBlocksTotal ); + return false; + } + +#if DEBUG_ADD + printf( "DEBUG: ADD: Kind : %d %s\n" , iKind, gaProDOS_KindDescription[ iKind ] ); + printf( "DEBUG: ADD: Size : %06X %s\n", nSrcSize, itoa_comma( nSrcSize ) ); + printf( "DEBUG: ADD: Index Blocks: %d\n" , nBlocksIndex ); + printf( "DEBUG: ADD: Data Blocks: %d\n" , nBlocksData ); + printf( "DEBUG: ADD: First free # %06X\n" , iEntryOffset ); + printf( "DEBUG: ADD: Free Blocks: %d\n" , nBlocksFree ); + printf( "DEBUG: ADD: Total Blocks: %d\n" , nBlocksTotal ); +#endif + + for( int iBlock = 0; iBlock < nBlocksIndex; iBlock++ ) + { + int iMetaBlock = prodos_BlockGetFirstFree( &gVolume ); + +#if DEBUG_ADD + printf( "DEBUG: ADD: Found Free Block @ %04X\n", free ); +#endif + if( !iMetaBlock ) + { + printf( "ERROR: Couldn't allocate index block\n" ); + return false; + } + + if( iBlock == 0 ) + { + iNode = iMetaBlock; + iIndexBase = iMetaBlock * PRODOS_BLOCK_SIZE; + } + else + { + if( iKind == ProDOS_KIND_TREE ) + { + gaDsk[ iMasterIndex + iBlock + 0 ] = (iMetaBlock >> 0) & 0xFF; + gaDsk[ iMasterIndex + iBlock + 256 ] = (iMetaBlock >> 8) & 0xFF; + } + } + + prodos_BlockSetUsed( &gVolume, iMetaBlock ); + } + + // Copy Data + int src = 0; + int dst; + + for( int iBlock = 0; iBlock < nBlocksData; iBlock++ ) + { + int iDataBlock = prodos_BlockGetFirstFree( &gVolume ); + if( !iDataBlock ) + { + printf( "ERROR: Couldn't allocate data block\n" ); + return false; + } + + if( iBlock == 0 ) + if( iKind == ProDOS_KIND_SEED ) + iNode = iDataBlock; + + dst = iDataBlock * PRODOS_BLOCK_SIZE; + memcpy( &gaDsk[ dst ], &buffer[ src ], PRODOS_BLOCK_SIZE ); + src += PRODOS_BLOCK_SIZE; + + prodos_BlockSetUsed( &gVolume, iDataBlock ); + + if( iKind == ProDOS_KIND_SAPL ) + { + // Update single index block + if( iIndexBase ) + { + gaDsk[ iIndexBase + iBlock + 0 ] = (iDataBlock >> 0) & 0xFF; + gaDsk[ iIndexBase + iBlock + 256 ] = (iDataBlock >> 8) & 0xFF; + } + } + else + if( iKind == ProDOS_KIND_TREE ) + { + // Master Index Block already up-to-date + + // Update multiple index blocks + printf( "TODO: Master Index Block + Data Block: %d\n", iBlock ); + } + } + + // Update Directory with Entry + ProDOS_FileHeader_t entry; + + if( attribs ) + entry = *attribs; + else + memset( &entry, 0, sizeof( entry ) ); + + entry.kind = iKind; + entry.inode = iNode; + entry.blocks = nBlocksTotal; + entry.size = nSrcSize; + entry.dir_block = iDirBlock; + +#if DEBUG_ADD + printf( "==========\n" ); + printf( "DEBUG: ADD: kind: $%X\n" , iKind ); + printf( "DEBUG: ADD: inode: @%04X\n", iNode ); + printf( "DEBUG: ADD: blocks: $%04X\n", nBlocksTotal ); + printf( "DEBUG: ADD:dir_blk: @%04X\n", entry.dir_block ); + printf( "==========\n" ); +#endif + + prodos_PutFileHeader( iEntryOffset, &entry ); + + // Update Directory with Header + dir.file_count++; + prodos_SetVolumeHeader( &dir, base ); + +#if DEBUG_ADD + int addr; + + addr = 2 * PRODOS_BLOCK_SIZE; + DskDump( addr, addr + 4 + gVolume.entry_len*3 ); + + printf( "\n" ); + + addr = gVolume.meta.bitmap_block * PRODOS_BLOCK_SIZE; + printf( "Volume bitmap \n" ); + DskDump( addr, addr + 64 ); + + ProDOS_VolumeHeader_t test; + prodos_GetVolumeHeader( &test, base ); + + printf( "DEBUG: ADD: ----- all done! -----\n" ); + printf( "\n" ); +#endif + + return true; +} + + +// ======================================================================== +void ProDOS_FileDelete( const char *path ) +{ +} + + +// TODO: ProDOS_VolumeHeader_t *config +// ======================================================================== +void ProDOS_Init( const char *path ) +{ + // Zero disk + memset( gaDsk, 0, gnDskSize ); + + // Copy Boot Sector + // TODO: + + // Init Bitmap + + // Create blocks for root directory + int nRootDirBlocks = 4; + int iPrevDirBlock = 0; + int iNextDirBlock = 0; + int iOffset; + + gVolume.meta.bitmap_block = PRODOS_ROOT_BLOCK + nRootDirBlocks; + int nBitmapBlocks = prodos_BlockInitFree( &gVolume ); + + // Set boot blocks as in-use + // PRODOS_ROOT_BLOCK == 2 + prodos_BlockSetUsed( &gVolume, 0 ); + prodos_BlockSetUsed( &gVolume, 1 ); + +#if DEBUG_BITMAP + int offset = gVolume.meta.bitmap_block * PRODOS_BLOCK_SIZE; + printf( "Bitmap after flaggings Blocks 0, 1\n" ); + DskDump( offset, offset + 32 ); +#endif + + for( int iBlock = 0; iBlock < nRootDirBlocks; iBlock++ ) + { + iNextDirBlock = prodos_BlockGetFirstFree( &gVolume ); + iOffset = iNextDirBlock * PRODOS_BLOCK_SIZE; + prodos_BlockSetUsed( &gVolume, iNextDirBlock ); + + // Double Linked List + // [0] = prev + // [2] = next -- will be set on next allocation + DskPut16( iOffset + 0, iPrevDirBlock ); +// DskPut16( iOffset + 2, 0 ); // OPTIMIZATION: disk zeroed above + + if( iBlock ) + { + // Fixup previous directory block with pointer to this one + iOffset = iPrevDirBlock * PRODOS_BLOCK_SIZE; + DskPut16( iOffset + 2, iNextDirBlock ); + } + + iPrevDirBlock = iNextDirBlock; + } + +#if DEBUG_BITMAP + printf( "Bitmap after root directory\n" ); + DskDump( offset, offset + 64 ); +#endif + + // Alloc Bitmap Blocks + for( int iBlock = 0; iBlock < nBitmapBlocks; iBlock++ ) + { + int iBitmap = prodos_BlockGetFirstFree( &gVolume ); + prodos_BlockSetUsed( &gVolume, iBitmap ); + } + +#if DEBUG_BITMAP + printf( "Bitmap after flag for bitmaps\n" ); + DskDump( offset, offset + 64 ); +#endif + + gVolume.entry_len = 0x27; + gVolume.entry_num = PRODOS_BLOCK_SIZE / gVolume.entry_len; + + // Note: + // .file_count = 0, since no files added + // OPTIMIZATION: disk zeroed above + if( *path == '/' ) + path++; + + size_t nLen = strlen( path ); + + gVolume.kind = ProDOS_KIND_ROOT; + gVolume.len = nLen; + string_CopyUpper( gVolume.name, path, 15 ); + + gVolume.access = 0 + | ACCESS_D + | ACCESS_N + | ACCESS_B +// | ACCESS_Z4 -- not used +// | ACCESS_Z3 -- not used +// | ACCESS_I -- no point to making the volume invis + | ACCESS_W + | ACCESS_R + ; + +/* + // TODO: + if( config ) gVolume.access = config.access; + if( config ) gVolume.date = config.date; + if( config ) gVolume.time = config.time; +*/ + +#if DEBUG_BITMAP + printf( "Bitmap before SetVolume\n" ); + DskDump( offset, offset + 32 ); +#endif + + prodos_SetVolumeHeader( &gVolume, PRODOS_ROOT_BLOCK ); + +#if DEBUG_INIT + int addr; + + addr = 2 * PRODOS_BLOCK_SIZE; + DskDump( addr, addr + 64 ); + + printf( "\n" ); + + addr = 3 * PRODOS_BLOCK_SIZE; + DskDump( addr, addr + 64 ); + + printf( "\n" ); + +// addr = gVolume.meta.bitmap_block * PRODOS_BLOCK_SIZE; + printf( "Volume bitmap after SetVolumeHeader()\n" ); + DskDump( offset, offset + 64 ); +#endif +} + diff --git a/prodos.utils.cpp b/prodos.utils.cpp new file mode 100644 index 0000000..6205c61 --- /dev/null +++ b/prodos.utils.cpp @@ -0,0 +1,488 @@ +// Access _________________________________________________________________ + + const size_t PRODOS_BLOCK_SIZE = 0x200; // 512 bytes/block + const size_t PRODOS_ROOT_BLOCK = 2; + const int PRODOS_MAX_FILENAME = 15; +// const int PRODOS_MAX_PATH = 64; // TODO: Verify + + const uint8_t ACCESS_D = 0x80; // Can destroy + const uint8_t ACCESS_N = 0x40; // Can rename + const uint8_t ACCESS_B = 0x20; // Can backup + const uint8_t ACCESS_Z4= 0x10; // not used - must be zero? + const uint8_t ACCESS_Z3= 0x08; + const uint8_t ACCESS_I = 0x04; // invisible + const uint8_t ACCESS_W = 0x02; // Can write + const uint8_t ACCESS_R = 0x01; // Can read + + void prodos_AccessToString( uint8_t access, char *text_ ) +#if 0 + { // 76543210 + char in [9] = "dnb--iwr"; + char out[9] = "--------"; + + char *src = in ; + char *dst = out; +#endif + { + const char *src = "dnb--iwr"; + /* */ char *dst = text_; + + int mask = 0x80; + while( mask ) + { + *dst = access & mask + ? *src + : '-' + ; + + dst++; + src++; + mask >>= 1; + } + + *dst = 0; + +// sprintf( text_, out ); + } + +// Storage Kind ___________________________________________________________ + + enum ProDOS_Kind_e + { + ProDOS_KIND_DEL = 0x0, + ProDOS_KIND_SEED = 0x1, // Single Block + ProDOS_KIND_SAPL = 0x2, // 1st Block is allocated blocks + ProDOS_KIND_TREE = 0x3, + ProDOS_KIND_PAS = 0x4, // http://www.1000bit.it/support/manuali/apple/technotes/pdos/tn.pdos.25.html + ProDOS_KIND_GSOS = 0x5, // http://www.1000bit.it/support/manuali/apple/technotes/pdos/tn.pdos.25.html + ProDOS_KIND_DIR = 0xD, // parent block entry for sub-directory + ProDOS_KIND_SUB = 0xE, // subdir header entry to find parent directory; meta to reach parent + ProDOS_KIND_ROOT = 0xF, // volume header entry for root directory + NUM_PRODOS_KIND = 16 + }; + + const char *gaProDOS_KindDescription[ NUM_PRODOS_KIND ] = // Storage Type + { + "del" // 0 Deleted + ,"sed" // 1 Seedling size <= 512 bytes == 1*PRODOS_BLOCK_SIZE + ,"sap" // 2 Sapling size <= 131,072 bytes == 256*PRODOS_BLOCK_SIZE -- interleaved: lo blocks * 256, hi blocks * 256 + ,"tre" // 3 Tree size <= 32 MB + ,"pas" // 4 Pascal Volume + ,"gs " // 5 GS/OS extended file data+resource fork + ,"? 6" // 6 + ,"? 7" // 7 + ,"? 8" // 8 + ,"? 9" // 9 + ,"? A" // A + ,"? B" // B + ,"? C" // C + ,"dir" // D Sub-directory + ,"sub" // E Sub-directory header + ,"vol" // F Volume directory header + }; + const char *prodos_KindToString( uint8_t type ) + { + type &= 0xF; + return gaProDOS_KindDescription[ type ]; + } + +// Date ___________________________________________________________________ + + const char *gaMonths[16] = + { + "bug" // 0 + ,"JAN" // 1 + ,"FEB" // 2 + ,"MAR" // 3 + ,"APR" // 4 + ,"MAY" // 5 + ,"JUN" // 6 + ,"JUL" // 7 + ,"AUG" // 8 + ,"SEP" // 9 + ,"OCT" // 10 + ,"NOV" // 11 + ,"DEC" // 12 + ,"bug" // 13 Should never happen + ,"bug" // 14 Should never happen + ,"bug" // 15 Should never happen + }; + + // Converts date to 9 chars: DD-MON-YR format + // NULL terminates the string + // ------------------------------------------------------------ + void prodos_DateToString( uint16_t date, char *text_ ) + { + if( date == 0 ) + sprintf( text_, "" ); + else + { + // 76543210 + // 3 210 + // FEDCBA98 76543210 + // yyyyyyym mmmddddd + int day = (date >> 0) & 0x1F; + int mon = (date >> 5) & 0x0F; + int yar = (date >> 9) & 0x7F; + +#if DEBUG_DATE + printf( "DEBUG: DATE: day: % 2d\n", day ); + printf( "DEBUG: DATE: mon: %d\n" , mon ); + printf( "DEBUG: DATE: yar: %2d\n" , yar % 100 ); +#endif + + sprintf( text_, "%*s%d-%s-%2d", (day < 10), "", day, gaMonths[ mon ], yar % 100 ); + } + } + + uint16_t ProDOS_DateToInt( int mon, int day, int year ) + { + if( day < 1 ) day = 1; + if( day > 31 ) day = 31; + + if( mon < 1 ) mon = 1; + if( mon > 12 ) mon = 12; + + if( year < 0 ) year = 0; + if( year > 99 ) year = 99; + + // FEDCBA98 76543210 + // yyyyyyym mmmddddd + int16_t date = 0 + | (day << 0) + | (mon << 5) + | (year << 9) + ; + + return date; + } + + int prodos_DateMonthToInt( const char *month ) + { + for( int iMonth = 1; iMonth <= 12; iMonth++ ) + if( stricmp( month, gaMonths[ iMonth ] ) == 0 ) + return iMonth; + + return 0; + } + + +// Time ___________________________________________________________________ + + // Converts time to 6 chars: HH:MMm + // ------------------------------------------------------------ + void prodos_TimeToString( uint16_t time, char *text_ ) + { + if( time == 0 ) + sprintf( text_, " " ); + else + { + // FEDCBA98 76543210 + // 000hhhhh 00mmmmmm + int min = (time >> 0) & 0x3F; + int hour = (time >> 8) & 0x1F; + + char median = (hour < 12) + ? 'a' + : 'p' + ; + +#if DEBUG_TIME + printf( "DEBUG: TIME: time: %02X\n", time ); + printf( "DEBUG: TIME: min : %02X\n", min ); + printf( "DEBUG: TIME: hour: %02X\n", hour ); + printf( "DEBUG: TIME: medn: %c\n" ,median); +#endif + + if( hour >= 12 ) + hour -= 12; + else + if( hour == 0 ) + hour += 12; + + sprintf( text_, "%*s%d:%02d%c", (hour < 10), "", hour, min, median ); + } + } + +// User Type ______________________________________________________________ + + // User type + // http://www.1000bit.it/support/manuali/apple/technotes/ftyp/ft.about.html + // http://www.kreativekorp.com/miscpages/a2info/filetypes.shtml + + const char *gaProDOS_FileTypes[ 256 ] = + { +// $0x Types: General + "UNK" // $00 Unknown + , "BAD" // $01 Bad Block + , "PCD" // $02 Pascal Code + , "PTX" // $03 Pascal Text + , "TXT" // $04 ASCII Text; aux type = 0 sequential, <> 0 random-acess + , "PDA" // $05 Pascal Data + , "BIN" // $06 Binary File; aux type = load address + , "FNT" // $07 Apple III Font + , "FOT" // $08 HiRes/Double HiRes Graphics + , "BA3" // $09 Apple /// BASIC Program + , "DA3" // $0A Apple /// BASIC Data + , "WPF" // $0B Generic Word Processing + , "SOS" // $0C SOS System File + , "?0D" // $0D ??? + , "?0E" // $0E ??? + , "DIR" // $0F ProDOS Directory +// $1x Ty pes: Productivity + , "RPD" // $10 RPS Data + , "RPI" // $11 RPS Index + , "AFD" // $12 AppleFile Discard + , "AFM" // $13 AppleFile Model + , "AFR" // $14 AppleFile Report + , "SCL" // $15 Screen Library + , "PFS" // $16 PFS Document + , "?17" // $17 ??? + , "?18" // $18 ??? + , "ADB" // $19 AppleWorks Database + , "AWP" // $1A AppleWorks Word Processing + , "ASP" // $1B AppleWorks Spreadsheet + , "?1C" // $1C ??? + , "?1D" // $1D ??? + , "?1E" // $1E ??? + , "?1F" // $1F ??? +// $2x Ty pes: Code + , "TDM" // $20 Desktop Manager File + , "IPS" // $21 Instant Pascal Source + , "UPV" // $22 UCSD Pascal Volume + , "?23" // $23 ??? + , "?24" // $24 ??? + , "?25" // $25 ??? + , "?26" // $26 ??? + , "?27" // $27 ??? + , "?28" // $28 ??? + , "3SD" // $29 Apple /// SOS Directory + , "8SC" // $2A Source Code + , "8OB" // $2B Object Code + , "8IC" // $2C Interpreted Code; aux type = $8003 Apex Program File + , "8LD" // $2D Language Data + , "P8C" // $2E ProDOS 8 Code Module + , "?2F" // $2F ??? +// $3x unused + , "?30" // $30 ??? + , "?31" // $31 ??? + , "?32" // $32 ??? + , "?33" // $33 ??? + , "?34" // $34 ??? + , "?35" // $35 ??? + , "?36" // $36 ??? + , "?37" // $37 ??? + , "?38" // $38 ??? + , "?39" // $39 ??? + , "?3A" // $3A ??? + , "?3B" // $3B ??? + , "?3C" // $3C ??? + , "?3D" // $3D ??? + , "?3E" // $3E ??? + , "?3F" // $3F ??? +// $4x Types: Miscellaneous + , "?40" // $40 ??? + , "OCR" // $41 Optical Character Recognition + , "FTD" // $42 File Type Definitions + , "PER" // $43 --- missing from Kreative: Peripheral data + , "?44" // $44 ??? + , "?45" // $45 ??? + , "?46" // $46 ??? + , "?47" // $47 ??? + , "?48" // $48 ??? + , "?49" // $49 ??? + , "?4A" // $4A ??? + , "?4B" // $4B ??? + , "?4C" // $4C ??? + , "?4D" // $4D ??? + , "?4E" // $4E ??? + , "?4F" // $4F ??? +// $5x Types: Apple IIgs General + , "GWP" // $50 Apple IIgs Word Processing + , "GSS" // $51 Apple IIgs Spreadsheet + , "GDB" // $52 Apple IIgs Database + , "DRW" // $53 Object Oriented Graphics + , "GDP" // $54 Apple IIgs Desktop Publishing + , "HMD" // $55 HyperMedia + , "EDU" // $56 Educational Program Data + , "STN" // $57 Stationery + , "HLP" // $58 Help File + , "COM" // $59 Communications + , "CFG" // $5A Configuration + , "ANM" // $5B Animation + , "MUM" // $5C Multimedia + , "ENT" // $5D Entertainment + , "DVU" // $5E Development Utility + , "FIN" // $5F --- missing from Kreative: FIN +// $6x Types: PC Transporter + , "PRE" // $60 PC Pre-Boot + , "?61" // $61 ??? + , "?62" // $62 ??? + , "?63" // $63 ??? + , "?64" // $64 ??? + , "?65" // $65 ??? + , "NCF" // $66 ProDOS File Navigator Command File + , "?67" // $67 ??? + , "?68" // $68 ??? + , "?69" // $69 ??? + , "?6A" // $6A ??? + , "BIO" // $6B PC Transporter BIOS + , "?6C" // $6C ??? + , "DVR" // $6D PC Transporter Driver + , "PRE" // $6E PC Transporter Pre-Boot + , "HDV" // $6F PC Transporter Hard Disk Image +// $7x Types: Kreative Software + , "SN2" // $70 Sabine's Notebook 2.0 + , "KMT" // $71 + , "DSR" // $72 + , "BAN" // $73 + , "CG7" // $74 + , "TNJ" // $75 + , "SA7" // $76 + , "KES" // $77 + , "JAP" // $78 + , "CSL" // $79 + , "TME" // $7A + , "TLB" // $7B + , "MR7" // $7C + , "MLR" // $7D Mika City + , "MMM" // $7E + , "JCP" // $7F +// $8x Types: GEOS + , "GES" // $80 System File + , "GEA" // $81 Desk Accessory + , "GEO" // $82 Application + , "GED" // $83 Document + , "GEF" // $84 Font + , "GEP" // $85 Printer Driver + , "GEI" // $86 Input Driver + , "GEX" // $87 Auxiliary Driver + , "?88" // $88 ??? + , "GEV" // $89 Swap File + , "?8A" // $8A ??? + , "GEC" // $8B Clock Driver + , "GEK" // $8C Interface Card Driver + , "GEW" // $8D Formatting Data + , "?8E" // $8E ??? + , "?8F" // $8F ??? +// $9x unused + , "?90" // $90 ??? + , "?91" // $91 ??? + , "?92" // $92 ??? + , "?93" // $93 ??? + , "?94" // $94 ??? + , "?95" // $95 ??? + , "?96" // $96 ??? + , "?97" // $97 ??? + , "?98" // $98 ??? + , "?99" // $99 ??? + , "?9A" // $9A ??? + , "?9B" // $9B ??? + , "?9C" // $9C ??? + , "?9D" // $9D ??? + , "?9E" // $9E ??? + , "?9F" // $9F ??? +// $Ax Types: Apple IIgs BASIC + , "WP " // $A0 WordPerfect + , "?A1" // $A1 ??? + , "?A2" // $A2 ??? + , "?A3" // $A3 ??? + , "?A4" // $A4 ??? + , "?A5" // $A5 ??? + , "?A6" // $A6 ??? + , "?A7" // $A7 ??? + , "?A8" // $A8 ??? + , "?A9" // $A9 ??? + , "?AA" // $AA ??? + , "GSB" // $AB Apple IIgs BASIC Program + , "TDF" // $AC Apple IIgs BASIC TDF + , "BDF" // $AD Apple IIgs BASIC Data + , "?AE" // $AE ??? + , "?AF" // $AF ??? +// $Bx Types: Apple IIgs System + , "SRC" // $B0 Apple IIgs Source Code + , "OBJ" // $B1 Apple IIgs Object Code + , "LIB" // $B2 Apple IIgs Library + , "S16" // $B3 Apple IIgs Application Program + , "RTL" // $B4 Apple IIgs Runtime Library + , "EXE" // $B5 Apple IIgs Shell Script + , "PIF" // $B6 Apple IIgs Permanent INIT + , "TIF" // $B7 Apple IIgs Temporary INIT + , "NDA" // $B8 Apple IIgs New Desk Accessory + , "CDA" // $B9 Apple IIgs Classic Desk Accessory + , "TOL" // $BA Apple IIgs Tool + , "DRV" // $BB Apple IIgs Device Driver + , "LDF" // $BC Apple IIgs Generic Load File + , "FST" // $BD Apple IIgs File System Translator + , "?BE" // $BE ??? + , "DOC" // $BF Apple IIgs Document +// $Cx Ty pes: Graphics + , "PNT" // $C0 Apple IIgs Packed Super HiRes + , "PIC" // $C1 Apple IIgs Super HiRes; aux_type = $2 = Super HiRes 3200 + , "ANI" // $C2 PaintWorks Animation + , "PAL" // $C3 PaintWorks Palette + , "?C4" // $C4 ??? + , "OOG" // $C5 Object-Oriented Graphics + , "SCR" // $C6 Script + , "CDV" // $C7 Apple IIgs Control Panel + , "FON" // $C8 Apple IIgs Font + , "FND" // $C9 Apple IIgs Finder Data + , "ICN" // $CA Apple IIgs Icon File + , "?CB" // $CB ??? + , "?CC" // $CC ??? + , "?CD" // $CD ??? + , "?CE" // $CE ??? + , "?CF" // $CF ??? +//$Dx Types: Audio + , "?D0" // $D0 ??? + , "?D1" // $D1 ??? + , "?D2" // $D2 ??? + , "?D3" // $D3 ??? + , "?D4" // $D4 ??? + , "MUS" // $D5 Music + , "INS" // $D6 Instrument + , "MDI" // $D7 MIDI + , "SND" // $D8 Apple IIgs Audio + , "?D9" // $D9 ??? + , "?DA" // $DA ??? + , "DBM" // $DB DB Master Document + , "?DC" // $DC ??? + , "?DD" // $DD ??? + , "?DE" // $DE ??? + , "?DF" // $DF ??? +// $Ex Types: Miscellaneous + , "LBR" // $E0 Archive + , "?E1" // $E1 ??? + , "ATK" // $E2 AppleTalk Data; aux_type = $FFFF - EasyMount Alias + , "?E3" // $E3 ??? + , "?E4" // $E4 ??? + , "?E5" // $E5 ??? + , "?E6" // $E6 ??? + , "?E7" // $E7 ??? + , "?E8" // $E8 ??? + , "?E9" // $E9 ??? + , "?EA" // $EA ??? + , "?EB" // $EB ??? + , "?EC" // $EC ??? + , "?ED" // $ED ??? + , "R16" // $EE EDASM 816 Relocatable Code + , "PAR" // $EFPascal Area +// $Fx Types: System + , "CMD" // $F0 ProDOS Command File + , "OVL" // $F1 User Defined 1 + , "UD2" // $F2 User Defined 2 + , "UD3" // $F3 User Defined 3 + , "UD4" // $F4 User Defined 4 + , "BAT" // $F5 User Defined 5 + , "UD6" // $F6 User Defined 6 + , "UD7" // $F7 User Defined 7 + , "PRG" // $F8 User Defined 8 + , "P16" // $F9 ProDOS-16 System File + , "INT" // $FA Integer BASIC Program + , "IVR" // $FB Integer BASIC Variables + , "BAS" // $FC Applesoft BASIC Program; aux type = $0801 + , "VAR" // $FD Applesoft BASIC Variables + , "REL" // $FE EDASM Relocatable Code + , "SYS" // $FF ProDOS-8 System File + }; + diff --git a/string.utils.cpp b/string.utils.cpp new file mode 100644 index 0000000..180f543 --- /dev/null +++ b/string.utils.cpp @@ -0,0 +1,70 @@ + +// Copy string, converting to uppercase. Will NULL terminate destination +// @param nLen - copy up to N chars. If zero will calculate source string length +// @return Length of string not including NULL terminator +// ======================================================================== +size_t string_CopyUpper( char *pDst, const char *pSrc, int nLen = 0 ) +{ + if( !nLen ) + nLen = strlen( pSrc ); + + char *pBeg = pDst; + + for( int i = 0; i < nLen; i++ ) + { + char c = *pSrc++; + + if( c == 0 ) // Don't copy past end-of-string + break; + + if((c >= 'a') && (c <= 'z' )) + c -= ('a' - 'A'); + + *pDst++ = c; + } + + *pDst = 0; + + return (pDst - pBeg); +} + + +// @return NULL if no extension found, else return pointer to "." +// ======================================================================== +const char* file_GetExtension( const char *pFileName ) +{ + int nLen = strlen( pFileName ); + + const char *pSrc = pFileName + nLen - 1; + const char *pBeg = pFileName; + + while( pSrc >= pBeg ) + { + if( *pSrc == '.' ) + return pSrc; + + pSrc--; + } + + return NULL; +} + + + +// Convert text to integer in base 16 +// Optional prefix of $ +// ======================================================================== +int getHexVal( const char *text ) +{ + int n = 0; + const char *pSrc = text; + + if( !text ) + return n; + + if( pSrc[0] == '$' ) + pSrc++; + + n = strtoul( pSrc, NULL, 16 ); + return n; +}