mirror of
https://github.com/Michaelangel007/apple2_prodos_utils.git
synced 2024-09-07 09:54:23 +00:00
1752 lines
52 KiB
C++
1752 lines
52 KiB
C++
#define DEBUG_ADD 0
|
|
#define DEBUG_ATTRIB 0
|
|
#define DEBUG_BITMAP 0
|
|
#define DEBUG_DATE 0
|
|
#define DEBUG_DIR 0
|
|
#define DEBUG_EXTRACT 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_* Private functions
|
|
ProDOS_* Public functions
|
|
*/
|
|
|
|
#define min(a,b) ((a < b) ? a : b)
|
|
#define max(a,b) ((a < b) ? b : a)
|
|
|
|
// --- 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 Filename Length
|
|
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
|
|
uint16_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
|
|
|
|
#if DEBUG_FILE
|
|
// ------------------------------------------------------------
|
|
void prodos_DumpFileHeader( const ProDOS_FileHeader_t &info )
|
|
{
|
|
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
|
|
|
|
// ------------------------------------------------------------
|
|
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
|
|
prodos_DumpFileHeader( info );
|
|
#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 ;
|
|
|
|
DskPut16( 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 ) );
|
|
|
|
// Default ot Read/Write/Rename/Destroy
|
|
entry->access = 0
|
|
| ACCESS_D
|
|
| ACCESS_N
|
|
| ACCESS_W
|
|
| ACCESS_R
|
|
;
|
|
}
|
|
|
|
// Globals ________________________________________________________________
|
|
|
|
int gnLastDirBlock = 0;
|
|
char gpLastDirName[ 16 ];
|
|
int gnLastDirMaxFiles = 0;
|
|
ProDOS_FileHeader_t gtLastDirFile;
|
|
|
|
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
|
|
// Sets:
|
|
// gtLastDirFile
|
|
// gnLastDirBlock
|
|
// gnLastDirMaxFiles
|
|
// ------------------------------------------------------------------------
|
|
int prodos_FindFile( ProDOS_VolumeHeader_t *volume, const char *path, int base = PRODOS_ROOT_OFFSET )
|
|
{
|
|
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];
|
|
size_t i;
|
|
|
|
// Split path
|
|
for( i = 0; i < nPathLen; i++ )
|
|
if( path[i] == '/' )
|
|
{
|
|
iDirName = i;
|
|
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;
|
|
gtLastDirFile = file;
|
|
|
|
#if DEBUG_FIND_FILE
|
|
prodos_DumpFileHeader( gtLastDirFile );
|
|
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_OFFSET; // 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;
|
|
|
|
memset( >LastDirFile, 0, sizeof( gtLastDirFile ) );
|
|
|
|
|
|
#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_MetaGetFileName( ProDOS_FileHeader_t *pEntry, char sAttrib[ PRODOS_MAX_PATH ] )
|
|
{
|
|
if( !pEntry )
|
|
return;
|
|
|
|
// Attribute meta-data
|
|
const char sExt[] = "._META";
|
|
const size_t nExt = strlen( sExt );
|
|
int nAttrib = string_CopyUpper( sAttrib + 0, pEntry->name, pEntry->len );
|
|
/* */ string_CopyUpper( sAttrib + nAttrib, sExt , nExt );
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
bool prodos_MetaLoad(ProDOS_FileHeader_t* pEntry)
|
|
{
|
|
char sAttrib[ PRODOS_MAX_PATH ];
|
|
prodos_MetaGetFileName( pEntry, sAttrib );
|
|
|
|
printf( "Loading meta... %s\n", sAttrib );
|
|
FILE *pFileMeta = fopen( sAttrib, "r" );
|
|
|
|
if( !pFileMeta )
|
|
{
|
|
printf( "INFO.: Couldn't open attribute file for reads: %s\n", sAttrib );
|
|
return false;
|
|
}
|
|
|
|
int value;
|
|
|
|
fscanf( pFileMeta, "access = $%X\n", &value ); pEntry->access = value; // 02
|
|
fscanf( pFileMeta, "aux = $%X\n", &value ); pEntry->aux = value; // 04
|
|
fscanf( pFileMeta, "type = $%X\n", &value ); pEntry->type = value; // 02
|
|
fscanf( pFileMeta, "kind = $%X\n", &value ); pEntry->kind = value; // 02
|
|
fscanf( pFileMeta, "date = $%X\n", &value ); pEntry->date = value; // 04
|
|
fscanf( pFileMeta, "time = $%X\n", &value ); pEntry->time = value; // 04
|
|
fscanf( pFileMeta, "version = $%X\n", &value ); pEntry->cur_ver = value; // 02
|
|
fscanf( pFileMeta, "minver = $%X\n", &value ); pEntry->min_ver = value; // 02
|
|
fscanf( pFileMeta, "moddate = $%X\n", &value ); pEntry->mod_date = value; // 04
|
|
fscanf( pFileMeta, "modtime = $%X\n", &value ); pEntry->mod_time = value; // 04
|
|
|
|
#if DEBUG_ATTRIB
|
|
printf( "access = $%02X\n", pEntry->access );
|
|
printf( "aux = $%04X\n", pEntry->aux );
|
|
printf( "type = $%02X\n", pEntry->type );
|
|
printf( "kind = $%02X\n", pEntry->kind );
|
|
printf( "date = $%04X\n", pEntry->date );
|
|
printf( "time = $%04X\n", pEntry->time );
|
|
printf( "version = $%02X\n", pEntry->cur_ver );
|
|
printf( "minver = $%02X\n", pEntry->min_ver );
|
|
printf( "moddate = $%04X\n", pEntry->mod_date );
|
|
printf( "modtime = $%04X\n", pEntry->mod_time );
|
|
#endif
|
|
|
|
fclose( pFileMeta );
|
|
return true;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
bool prodos_MetaSave( ProDOS_FileHeader_t *pEntry )
|
|
{
|
|
char sAttrib[ PRODOS_MAX_PATH ];
|
|
prodos_MetaGetFileName( pEntry, sAttrib );
|
|
|
|
printf( "Saving meta... %s\n", sAttrib );
|
|
FILE *pFileMeta = fopen( sAttrib, "w+b" );
|
|
|
|
if( !pFileMeta )
|
|
{
|
|
printf( "ERROR: Couldnt' open attribute file for writing: %s\n", sAttrib );
|
|
return false;
|
|
}
|
|
|
|
fprintf( pFileMeta, "access = $%02X\n", pEntry->access );
|
|
fprintf( pFileMeta, "aux = $%04X\n", pEntry->aux );
|
|
fprintf( pFileMeta, "type = $%02X\n", pEntry->type );
|
|
fprintf( pFileMeta, "kind = $%02X\n", pEntry->kind );
|
|
fprintf( pFileMeta, "date = $%04X\n", pEntry->date );
|
|
fprintf( pFileMeta, "time = $%04X\n", pEntry->time );
|
|
fprintf( pFileMeta, "version = $%02X\n", pEntry->cur_ver );
|
|
fprintf( pFileMeta, "minver = $%02X\n", pEntry->min_ver );
|
|
fprintf( pFileMeta, "moddate = $%04X\n", pEntry->mod_date );
|
|
fprintf( pFileMeta, "modtime = $%04X\n", pEntry->mod_time );
|
|
|
|
fclose( pFileMeta );
|
|
return true;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
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[ PRODOS_ROOT_OFFSET + 4 ] & 0xF;
|
|
|
|
if( vol_length < 16 )
|
|
{
|
|
for( int i = 0; i < vol_length; i++ )
|
|
*pVolume_++ = gaDsk[ PRODOS_ROOT_OFFSET + 5 + i ];
|
|
}
|
|
|
|
*pVolume_ = 0;
|
|
|
|
return bValid;
|
|
}
|
|
|
|
|
|
/*
|
|
Root
|
|
<-- print blank line
|
|
/VOLUME ... <HEADER> ...
|
|
\_____/
|
|
name
|
|
|
|
Sub-dir
|
|
|
|
/VOLUME/dir1/subdir1 <-- printed
|
|
subdir1 ... <HEADER> ...
|
|
\_____/
|
|
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( "Acc 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( "$%02X " , file.access );
|
|
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 DEBUG_ATTRIB
|
|
printf( "Source File Size: %06X (%d)\n", nSrcSize, nSrcSize );
|
|
#endif
|
|
|
|
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 )
|
|
{
|
|
DskPutIndexBlock( iMasterIndex, iBlock, iMetaBlock );
|
|
}
|
|
}
|
|
|
|
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 )
|
|
{
|
|
DskPutIndexBlock( iIndexBase, iBlock, iDataBlock );
|
|
}
|
|
}
|
|
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 )
|
|
{
|
|
}
|
|
|
|
|
|
// Copy a file from the virtual file system back to the host
|
|
// ProDOS attributes are saved in <file>._META
|
|
// ========================================================================
|
|
bool ProDOS_FileExtract( const char *path )
|
|
{
|
|
int base = prodos_BlockGetPath( path );
|
|
ProDOS_FileHeader_t *pEntry = >LastDirFile;
|
|
|
|
#if DEBUG_EXTRACT
|
|
printf( "DEBUG: EXTRACT: path: %s\n", path );
|
|
printf( "DEBUG: EXTRACT: Dir @ %04X\n", base );
|
|
#endif
|
|
|
|
#if DEBUG_FILE
|
|
prodos_DumpFileHeader( gtLastDirFile );
|
|
#endif
|
|
|
|
if( !base )
|
|
{
|
|
printf( "ERROR: Couldn't file to extract: %s\n", path );
|
|
return false;
|
|
}
|
|
|
|
if( !pEntry->len )
|
|
{
|
|
printf( "ERROR: Couldn't get file information\n" );
|
|
return false;
|
|
}
|
|
|
|
const int kind = pEntry->kind;
|
|
if( false
|
|
|| kind == ProDOS_KIND_DIR
|
|
|| kind == ProDOS_KIND_SUB
|
|
|| kind == ProDOS_KIND_ROOT
|
|
)
|
|
{
|
|
printf( "ERROR: Can't extract a file of a directory type\n" );
|
|
return false;
|
|
}
|
|
|
|
if( !prodos_MetaSave( pEntry ) )
|
|
return false;
|
|
|
|
int addr = pEntry->inode * PRODOS_BLOCK_SIZE;
|
|
int size = pEntry->size;
|
|
|
|
// TODO:
|
|
// printf( "WARNING: File exists. Use -i to ask if should over-write.\n" );
|
|
printf( "Saving data... %s\n", pEntry->name );
|
|
FILE *pFileData = fopen( pEntry->name, "w+b" );
|
|
|
|
if( !pFileData )
|
|
{
|
|
printf( "ERROR: Couldn't open data file for writing: %s\n", pEntry->name );
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
switch( kind )
|
|
{
|
|
case ProDOS_KIND_SEED: // <= 512 bytes
|
|
{
|
|
fwrite( &gaDsk[ addr ], 1, size, pFileData );
|
|
break;
|
|
}
|
|
case ProDOS_KIND_SAPL: // <= 128 KB
|
|
{
|
|
int nBlock = pEntry->blocks - 1; // 1st block is index block
|
|
int nBytes = size;
|
|
#if DEBUG_EXTRACT
|
|
printf( "ProDOS File Size: $%06X (%d)\n", size, size );
|
|
printf( "i-node (8-bit) : @ $%04X\n" , pEntry->inode );
|
|
// printf( "Filename Length : %04X\n", pEntry->len );
|
|
printf( "File Blocks : $%04X (%d)\n", pEntry->blocks, pEntry->blocks ); // Includes i-nodes
|
|
printf( "Total Blocks : $%04X (%d)\n", nBlock , nBlock );
|
|
#endif
|
|
for( int iBlock = 0; iBlock < nBlock; iBlock++ )
|
|
{
|
|
int iDataBlock = DskGetIndexBlock( addr, iBlock );
|
|
int iDataOffset = iDataBlock * PRODOS_BLOCK_SIZE;
|
|
int nSlack = min( nBytes, PRODOS_BLOCK_SIZE);
|
|
nBytes -= PRODOS_BLOCK_SIZE;
|
|
#if DEBUG_EXTRACT
|
|
int bLastBlock = (iBlock == (nBlock - 1));
|
|
printf( "Block: %02X/%02X @ %04X, LastBlock? %d, Bytes: %6d, Slack: %3d\n", iBlock, nBlock-1, iDataBlock, bLastBlock, nBytes + PRODOS_BLOCK_SIZE, nSlack );
|
|
#endif
|
|
fwrite( &gaDsk[ iDataOffset ], 1, nSlack, pFileData );
|
|
}
|
|
|
|
// File size is larger then blocks used on disk?!
|
|
if( nBytes > 0 )
|
|
{
|
|
// pad with zeroes
|
|
#if DEBUG_EXTRACT
|
|
int nSlack = min( nBytes, PRODOS_BLOCK_SIZE);
|
|
printf( "PADDING extra ZERO Bytes: %6d, Slack: %3d\n", nBytes, nSlack );
|
|
#endif
|
|
fwrite( &gaTmp, 1, nBytes, pFileData );
|
|
}
|
|
break;
|
|
}
|
|
case ProDOS_KIND_TREE:
|
|
{
|
|
printf( "ERROR: Tree files are not yet handled.\n" );
|
|
break;
|
|
}
|
|
default:
|
|
printf( "ERROR: Unhandled file type '%s' ($%02X)\n", gaProDOS_KindDescription[ kind ], kind );
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose( pFileData );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// TODO: ProDOS_VolumeHeader_t *config
|
|
// ========================================================================
|
|
void ProDOS_Init( const char *path )
|
|
{
|
|
// Zero disk
|
|
memset( gaDsk, 0, gnDskSize );
|
|
memset( gaTmp, 0, PRODOS_BLOCK_SIZE );
|
|
|
|
// Copy Boot Sector
|
|
// TODO: Use ProDOS 2.4.1 boot sector
|
|
|
|
// Create blocks for root directory
|
|
int nRootDirBlocks = 4;
|
|
int iPrevDirBlock = 0;
|
|
int iNextDirBlock = 0;
|
|
int iOffset;
|
|
|
|
// Init Bitmap
|
|
gVolume.meta.bitmap_block = (uint16_t) (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 = (uint8_t) (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 = (uint8_t) 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
|
|
}
|
|
|
|
|
|
// Read T0S0 and save it to a file on the host
|
|
// @returns true if succes
|
|
// ========================================================================
|
|
bool ProDOS_ExtractBootSector( const char *pBootSectorFileName )
|
|
{
|
|
FILE *pDstFile = fopen( pBootSectorFileName, "w+b" );
|
|
|
|
if( !pDstFile )
|
|
{
|
|
printf( "ERROR: Couldn't open boot sector filename for writing: %s\n", pBootSectorFileName );
|
|
return false;
|
|
}
|
|
|
|
const size_t BLOCK_0_BEG = 0x0*DSK_SECTOR_SIZE;
|
|
const size_t BLOCK_0_END = 0x1*DSK_SECTOR_SIZE;
|
|
|
|
fwrite( &gaDsk[ BLOCK_0_BEG ], 1, 256, pDstFile );
|
|
fwrite( &gaDsk[ BLOCK_0_END ], 1, 256, pDstFile );
|
|
|
|
#if _DEBUG
|
|
for( int sector = 0; sector < 16; ++sector )
|
|
{
|
|
printf( "T0S%1X: ", sector );
|
|
for( int byte = 0; byte < 4; ++byte )
|
|
{
|
|
printf( "%02X ", gaDsk[ sector*DSK_SECTOR_SIZE + byte ] );
|
|
}
|
|
printf( "\n" );
|
|
}
|
|
#endif
|
|
|
|
fclose( pDstFile );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Read a file from the host and replace T0S0
|
|
// @returns true if sucess, else false on error
|
|
// ========================================================================
|
|
bool ProDOS_ReplaceBootSector( const char *pBootSectorFileName )
|
|
{
|
|
FILE *pSrcFile = fopen( pBootSectorFileName, "rb" );
|
|
|
|
if( !pSrcFile )
|
|
{
|
|
printf( "ERROR: Couldn't open boot sector filename for reading: %s\n", pBootSectorFileName );
|
|
return false;
|
|
}
|
|
|
|
size_t size = File_Size( pSrcFile );
|
|
|
|
const size_t BLOCK_0_BEG = 0x0*DSK_SECTOR_SIZE;
|
|
const size_t BLOCK_0_END = 0x1*DSK_SECTOR_SIZE;
|
|
|
|
memset( &gaDsk[ BLOCK_0_BEG ], 0, 256 ); // Block 0 Beg = T0S0
|
|
memset( &gaDsk[ BLOCK_0_END ], 0, 256 ); // Block 0 End = T0SE
|
|
|
|
if( size < 512 )
|
|
printf( "INFO.: Boot sector < 512 bytes. Padding boot sector with zeroes.\n" );
|
|
|
|
if( size > 512 )
|
|
{
|
|
printf( "WARNING: Boot sector > 512 bytes. Truncating to first 512 bytes.\n" );
|
|
size = 512;
|
|
}
|
|
|
|
const size_t prefix = min( size , 256 );
|
|
const size_t suffix = min( size-256, 256 );
|
|
|
|
#if _DEBUG
|
|
printf( "prefix: $%02X (#%3d)\n", (int) prefix & 0xFF, (int) prefix );
|
|
printf( "suffix: $%02X (#%3d)\n", (int) prefix & 0xFF, (int) suffix );
|
|
#endif
|
|
|
|
if( prefix ) fread( &gaDsk[ BLOCK_0_BEG ], 1, prefix, pSrcFile );
|
|
if( suffix ) fread( &gaDsk[ BLOCK_0_END ], 1, suffix, pSrcFile );
|
|
|
|
#if 0
|
|
if (size <= 256)
|
|
{
|
|
fread( &gaDsk[ BLOCK_0_BEG ], 1, size, pSrcFile );
|
|
}
|
|
else
|
|
{
|
|
// First 256 bytes to T0S0
|
|
fread( &gaDsk[ BLOCK_0_BEG ], 1, 256, pSrcFile );
|
|
|
|
// Second 256 bytes to T0SE
|
|
if( size < 512 )
|
|
fread( &gaDsk[ BLOCK_0_END ], 1, size, pSrcFile );
|
|
else
|
|
fread( &gaDsk[ BLOCK_0_END ], 1, 512, pSrcFile );
|
|
}
|
|
#endif
|
|
|
|
fclose( pSrcFile );
|
|
|
|
// NOTE: Caller will do: DskSave();
|
|
|
|
return true;
|
|
}
|
|
|