Initial commit of working catalogs, init, and cp

This commit is contained in:
Michaelangel007 2017-11-05 21:55:01 -08:00
commit 4b5c85d360
5 changed files with 2900 additions and 0 deletions

331
generic.disk.cpp Normal file
View File

@ -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" );
}

612
prodos.cpp Normal file
View File

@ -0,0 +1,612 @@
#define DEBUG_MAIN 0
#include <stdio.h> // printf()
#include <stdint.h> // uint8_t
#include <sys/stat.h> // stat
#include <stdlib.h> // exit()
#include <string.h> // memcpy()
#include <time.h> // 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: <dsk> <comlocaltime_rmand> [<options>]\n"
"\n"
);
for( int iCommand = 0; iCommand < NUM_DISK_COMMANDS-1; iCommand++ )
printf( " %-7s %s\n", gaCommands[ iCommand ], gaDescriptions[ iCommand ] );
printf(
"\n"
"Where <dsk> 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<char*>( 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 <DSK> 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;
}

1399
prodos.tools.cpp Normal file

File diff suppressed because it is too large Load Diff

488
prodos.utils.cpp Normal file
View File

@ -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_, "<NO DATE>" );
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
};

70
string.utils.cpp Normal file
View File

@ -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 ".<ext>"
// ========================================================================
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;
}