mirror of
https://github.com/Michaelangel007/apple2_prodos_utils.git
synced 2024-11-26 21:49:37 +00:00
Initial commit of working catalogs, init, and cp
This commit is contained in:
commit
4b5c85d360
331
generic.disk.cpp
Normal file
331
generic.disk.cpp
Normal 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
612
prodos.cpp
Normal 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
1399
prodos.tools.cpp
Normal file
File diff suppressed because it is too large
Load Diff
488
prodos.utils.cpp
Normal file
488
prodos.utils.cpp
Normal 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
70
string.utils.cpp
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user