Files
AppleWin/source/ProDOS_FileSystem.h
Andrea 75cc573d0e Make ProDOS utils cross platform. (#1406)
* Move ProDOS utils to separate file.

Signed-off-by: Andrea Odetti <mariofutire@gmail.com>

* Use std::vector to fix and avoid memory leaks.

Signed-off-by: Andrea Odetti <mariofutire@gmail.com>

* Remove TEXT and TCHAR.

Signed-off-by: Andrea Odetti <mariofutire@gmail.com>

---------

Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
2025-06-13 08:51:59 -07:00

573 lines
20 KiB
C++

/*
AppleWin : An Apple //e emulator for Windows
Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2014, Tom Charlesworth, Michael Pohoreski
AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* Description: Add ProDOS File System support for formatting disk images
* Author: Michael Pohoreski
*/
// --- ProDOS Consts ---
const size_t PRODOS_BLOCK_SIZE = 0x200; // 512 bytes/block
const size_t PRODOS_ROOT_BLOCK = 2;
const int PRODOS_ROOT_OFFSET = PRODOS_ROOT_BLOCK * PRODOS_BLOCK_SIZE;
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; // not used - must be zero?
const uint8_t ACCESS_I = 0x04; // invisible
const uint8_t ACCESS_W = 0x02; // Can write
const uint8_t ACCESS_R = 0x01; // Can read
enum PRODOS_Kind_e
{
PRODOS_KIND_DEL = 0x0, // Deleted file or unused
PRODOS_KIND_SEED = 0x1, // No meta block, single block contains the data
PRODOS_KIND_SAPL = 0x2, // 1st Block is allocated blocks
PRODOS_KIND_TREE = 0x3, // Multiple meta blocks
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
};
// --- ProDOS Structs ---
struct ProDOS_SubDirInfo_t
{
uint8_t res75 ; // $75 - magic number
uint8_t pad7[7];
};
struct ProDOS_VolumeExtra_t
{
uint16_t bitmap_block;
uint16_t total_blocks;
};
struct ProDOS_SubDirExtra_t
{
uint16_t parent_block;
uint8_t parent_entry_num;
uint8_t parent_entry_len;
};
// Due to default compiler packing/alignment this may NOT be byte-size identical on disk but it is in the correct order.
// NOTE: This does NOT need to be packed/aligned since fields are read/written on a per entry basis.
// See: ProDOS_GetVolumeHeader(), ProDOS_SetVolumeHeader()
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
ProDOS_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 // --
{ // --
ProDOS_VolumeExtra_t meta ; //+34 2 $27
ProDOS_SubDirExtra_t subdir; //+36 2 $29
} ; //============
} ; // 38 $2B
// Due to default compiler packing/alignment this may NOT be byte-size identical on disk but it is in the correct order.
// NOTE: This does NOT need to be packed/aligned since fields are read/written on a per-entry basis.
// See: ProDOS_GetFileHeader(), ProDOS_SetFileHeader()
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
// --- Prototypes ---
void ProDOS_GetFileHeader( uint8_t *pDiskBytes, int nOffset, ProDOS_FileHeader_t *pFileHeader_ );
// --- Date/Time ---
// | Byte 1 | Byte 0 | Bytes
// +-------------------------------+-------------------------------+
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Bits
// +---------------------------------------------------------------+
// <-----------Year-----------> <----Month----> <-------Day-------> Date
uint16_t ProDOS_PackDate ( int year, int month, int day )
{
uint16_t date = 0
| ((year & 0x7F) << 9)
| ((month & 0x0F) << 5)
| ((day & 0x1F) << 0)
;
return date;
}
// | Byte 1 | Byte 0 | Bytes
// +---------------------------------------------------------------+
// | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Bits
// +---------------------------------------------------------------+
// <----0----> <------Hours------> <--0--> <-------Minutes-------> Time
uint16_t ProDOS_PackTime (int hours, int minutes)
{
uint16_t time = 0
| ((hours & 0x1F) << 8)
| ((minutes & 0x3F) << 0)
;
return time;
}
// --- ProDOS Utility Byte Functions ---
// Helpers for 8 bit, 16-bit, 24-bit values
// ------------------------------------------------------------------------
inline uint16_t ProDOS_Get16 ( uint8_t *pDiskBytes, int offset )
{
uint16_t n = 0
| (((uint16_t)pDiskBytes[ offset + 0 ]) << 0)
| (((uint16_t)pDiskBytes[ offset + 1 ]) << 8)
;
return n;
}
// ------------------------------------------------------------------------
inline uint32_t ProDOS_Get24 ( uint8_t *pDiskBytes, int offset )
{
uint32_t n = 0
| (((uint32_t)pDiskBytes[ offset + 0 ]) << 0)
| (((uint32_t)pDiskBytes[ offset + 1 ]) << 8)
| (((uint32_t)pDiskBytes[ offset + 2 ]) << 16)
;
return n;
}
// ------------------------------------------------------------------------
inline void ProDOS_Put16 ( uint8_t *pDiskBytes, int offset, int val )
{
pDiskBytes[ offset + 0 ] = (val >> 0) & 0xFF;
pDiskBytes[ offset + 1 ] = (val >> 8) & 0xFF;
}
// ------------------------------------------------------------------------
inline void ProDOS_Put24 ( uint8_t *pDiskBytes, int offset, int val )
{
pDiskBytes[ offset + 0 ] = (val >> 0) & 0xFF;
pDiskBytes[ offset + 1 ] = (val >> 8) & 0xFF;
pDiskBytes[ offset + 2 ] = (val >>16) & 0xFF;
}
// --- ProDOS Block Functions ---
// ------------------------------------------------------------------------
inline int ProDOS_BlockGetDirectoryBlockCount ( uint8_t *pDiskBytes, int nOffset )
{
int nBlocks = 0;
int nNextBlock = 0;
do
{
nBlocks++;
nNextBlock = ProDOS_Get16( pDiskBytes, nOffset + 2 );
nOffset = nNextBlock * PRODOS_BLOCK_SIZE;
} while( nNextBlock );
return nBlocks;
}
// ------------------------------------------------------------------------
int ProDOS_BlockGetFirstFree ( uint8_t *pDiskBytes, size_t nDiskSize, ProDOS_VolumeHeader_t *pVolume )
{
if( !pVolume )
{
return 0;
}
int bitmap = pVolume->meta.bitmap_block;
int offset = bitmap * PRODOS_BLOCK_SIZE;
int size = (nDiskSize + 7) / 8;
int block = 0;
for( int byte = 0; byte < size; byte++ )
{
int mask = 0x80;
do
{
if( pDiskBytes[ offset + byte ] & mask )
return block;
mask >>= 1;
block++;
}
while( mask );
}
return 0;
}
// ------------------------------------------------------------------------
int ProDOS_BlockGetPathOffset ( uint8_t *pDiskBytes, ProDOS_VolumeHeader_t *pVolume, const char *pProDOSPath )
{
int nOffset = PRODOS_ROOT_OFFSET; // Block 2 * 0x200 Bytes/Block = 0x400 abs offset
#if _DEBUG
ProDOS_VolumeHeader_t tVolume;
memset( &tVolume, 0, sizeof(tVolume) );
int iDirBlock = nOffset / PRODOS_BLOCK_SIZE;
if (!pVolume)
pVolume = &tVolume;
int nDirBlocks = ProDOS_BlockGetDirectoryBlockCount( pDiskBytes, nOffset );
LogOutput( "Directory Entry Bytes : $%04X\n", pVolume->entry_len );
LogOutput( "Directory Entries/Block: %d\n" , pVolume->entry_num );
LogOutput( "VOLUME Directory Blocks: %d\n" , nDirBlocks );
// LogOutput( "...Alt. max dir files : %d\n" ,(nDirBlocks * PRODOS_BLOCK_SIZE) / pVolume->entry_len ); // 2nd way to verify
#endif
if( !pProDOSPath )
return nOffset;
if (strcmp( pProDOSPath, "/" ) == 0)
return nOffset;
// NOT IMPLEMENTED
// return ProDOS_FindFile( pDiskBytes, pVolume, pProDOSPath, nOffset );
return 0;
}
// ------------------------------------------------------------------------
int ProDOS_BlockInitFree ( uint8_t *pDiskBytes, size_t nDiskSize, ProDOS_VolumeHeader_t *volume )
{
int bitmap = volume->meta.bitmap_block;
int offset = bitmap * PRODOS_BLOCK_SIZE;
int blocks = (nDiskSize + PRODOS_BLOCK_SIZE - 1) / PRODOS_BLOCK_SIZE;
int size = (blocks + 7) / 8;
memset( &pDiskBytes[ offset ], 0xFF, size );
volume->meta.total_blocks = blocks;
return (size + PRODOS_BLOCK_SIZE - 1) / PRODOS_BLOCK_SIZE;
}
// ------------------------------------------------------------------------
bool ProDOS_BlockSetUsed ( uint8_t *pDiskBytes, ProDOS_VolumeHeader_t *pVolume, int block )
{
if( !pVolume )
{
return false;
}
int bitmap = pVolume->meta.bitmap_block;
int offset = bitmap * PRODOS_BLOCK_SIZE;
int byte = block / 8;
int bits = block % 8;
int mask = 0x80 >> bits;
pDiskBytes[ offset + byte ] |= mask;
pDiskBytes[ offset + byte ] ^= mask;
return true;
}
// ------------------------------------------------------------------------
inline int ProDOS_GetIndexBlock ( uint8_t *pDiskBytes, int offset, int index )
{
int block = 0
| (pDiskBytes[ offset + index + 0 ] << 0)
| (pDiskBytes[ offset + index + 256 ] << 8)
;
return block;
}
/*
000:lo0 lo1 lo2 ...
100:hi0 hi1 hi2 ...
*/
// ------------------------------------------------------------------------
inline void ProDOS_PutIndexBlock ( uint8_t *pDiskBytes, int offset, int index, int block )
{
#if 0
std::string message;
message = StrFormat( "File Bitmap @ Block $%04X: $%02X: Block $%04X\n", offset/PRODOS_BLOCK_SIZE, index, block );
OutputDebugStringA( message.c_str() );
#endif
pDiskBytes[ offset + index + 0 ] = (block >> 0) & 0xFF;
pDiskBytes[ offset + index + 256 ] = (block >> 8) & 0xFF;
}
// --- ProDOS Directory Functions ---
// @param nBase DiskImageOffset of directory
// returns DiskImageOffset
// ------------------------------------------------------------------------
int ProDOS_DirGetFirstFreeEntryOffset ( uint8_t *pDiskBytes, ProDOS_VolumeHeader_t *pVolume, int nBase )
{
int iNextBlock;
int iPrevBlock;
int iOffset = nBase + 4; // Prev Block, Next Block
// Try to find a free file entry in this directory
do
{
iPrevBlock = ProDOS_Get16( pDiskBytes, nBase + 0 );
iNextBlock = ProDOS_Get16( pDiskBytes, nBase + 2 );
for( int iFile = 0; iFile < pVolume->entry_num; iFile++ )
{
ProDOS_FileHeader_t file;
ProDOS_GetFileHeader( pDiskBytes, iOffset, &file );
if (file.kind == PRODOS_KIND_DEL)
return iOffset;
if (file.len == 0)
return iOffset;
iOffset += pVolume->entry_len;
}
nBase = iNextBlock * PRODOS_BLOCK_SIZE;
iOffset = nBase + 4;
} while( iNextBlock );
return 0;
}
// --- ProDOS Volume Functions ---
// ------------------------------------------------------------------------
void ProDOS_GetVolumeHeader( uint8_t *pDiskBytes, ProDOS_VolumeHeader_t *pVolumeHeader_, int iBlock )
{
int base = iBlock*PRODOS_BLOCK_SIZE + 4; // skip prev/next dir block double linked list
ProDOS_VolumeHeader_t info;
info.kind = (pDiskBytes[ base + 0 ] >> 4) & 0xF;
info.len = (pDiskBytes[ base + 0 ] >> 0) & 0xF;
for( int i = 0; i < PRODOS_MAX_FILENAME+1; i++ )
info.name[ i ] = 0;
for( int i = 0; i < info.len; i++ )
info.name[ i ] = pDiskBytes[ base+i+ 1 ];
for( int i = 0; i < 8; i++ )
info.pad8[ i ] = pDiskBytes[ base+i+16 ];
info.date = ProDOS_Get16( pDiskBytes, base + 24 );
info.time = ProDOS_Get16( pDiskBytes, base + 26 );
info.cur_ver = pDiskBytes[ base + 28 ];
info.min_ver = pDiskBytes[ base + 29 ];
info.access = pDiskBytes[ base + 30 ];
info.entry_len = pDiskBytes[ base + 31 ];
info.entry_num = pDiskBytes[ base + 32 ];
info.file_count = ProDOS_Get16( pDiskBytes, base + 33 );
info.meta.bitmap_block = ProDOS_Get16( pDiskBytes, base + 35 );
info.meta.total_blocks = ProDOS_Get16( pDiskBytes, base + 37 );
#if _DEBUG
LogOutput( "PRODOS: VOLUME: name: %s\n", info.name );
LogOutput( "PRODOS: VOLUME: date: %04X\n", info.date );
LogOutput( "PRODOS: VOLUME: time: %04X\n", info.time );
LogOutput( "PRODOS: VOLUME: cVer: %02X\n", info.cur_ver );
LogOutput( "PRODOS: VOLUME: mVer: %02X\n", info.min_ver );
LogOutput( "PRODOS: VOLUME:acces: %02X\n", info.access );
LogOutput( "PRODOS: VOLUME:e.len: %02X\n", info.entry_len );
LogOutput( "PRODOS: VOLUME:e.num: %02X\n", info.entry_num );
LogOutput( "PRODOS: VOLUME:files: %04X ", info.file_count );
LogOutput( "(%d)\n", info.file_count );
if (iBlock == PRODOS_ROOT_BLOCK)
{
LogOutput( "PRODOS: ROOT: bitmap: %04X\n", info.meta.bitmap_block );
LogOutput( "PRODOS: ROOT: blocks: %04X\n", info.meta.total_blocks );
}
else
{
LogOutput( "PRODOS: SUBDIR: parent: %04X\n", info.subdir.parent_block );
LogOutput( "PRODOS: SUBDIR: pa.len: %02X\n", info.subdir.parent_entry_num );
LogOutput( "PRODOS: SUBDIR: pa.num: %02X\n", info.subdir.parent_entry_len );
}
#endif
if ( pVolumeHeader_ )
*pVolumeHeader_ = info;
};
// ------------------------------------------------------------------------
void ProDOS_SetVolumeHeader ( uint8_t *pDiskBytes, ProDOS_VolumeHeader_t *pVolume, int iBlock )
{
if( !pVolume )
return;
int base = iBlock*PRODOS_BLOCK_SIZE + 4; // skip prev/next dir block double linked list
uint8_t KindLen = 0
| ((pVolume->kind & 0xF) << 4)
| ((pVolume->len & 0xF) << 0)
;
pDiskBytes[ base + 0 ] = KindLen;
for( int i = 0; i < pVolume->len; i++ )
pDiskBytes[ base+ 1+i ] = pVolume->name[ i ];
for( int i = 0; i < 8; i++ )
pDiskBytes[ base+16+i ] = pVolume->pad8[ i ];
ProDOS_Put16( pDiskBytes, base + 24 , pVolume->date );
ProDOS_Put16( pDiskBytes, base + 26 , pVolume->time );
pDiskBytes[ base + 28 ] = pVolume->cur_ver ;
pDiskBytes[ base + 29 ] = pVolume->min_ver ;
pDiskBytes[ base + 30 ] = pVolume->access ;
pDiskBytes[ base + 31 ] = pVolume->entry_len;
pDiskBytes[ base + 32 ] = pVolume->entry_num;
ProDOS_Put16( pDiskBytes, base + 33 , pVolume->file_count );
ProDOS_Put16( pDiskBytes, base + 35 , pVolume->meta.bitmap_block );
ProDOS_Put16( pDiskBytes, base + 37 , pVolume->meta.total_blocks );
}
// --- ProDOS File Functions ---
// ------------------------------------------------------------
void ProDOS_GetFileHeader ( uint8_t *pDiskBytes, int nOffset, ProDOS_FileHeader_t *pFileHeader_ )
{
ProDOS_FileHeader_t info;
info.kind = (pDiskBytes[ nOffset + 0 ] >> 4) & 0xF;
info.len = (pDiskBytes[ nOffset + 0 ] >> 0) & 0xF;
for( int i = 0; i < PRODOS_MAX_FILENAME+1; i++ )
info.name[ i ] = 0;
for( int i = 0; i < info.len; i++ )
info.name[ i ] = pDiskBytes[ nOffset + 1+i];
info.type = pDiskBytes[ nOffset + 16 ];
info.inode = ProDOS_Get16( pDiskBytes, nOffset + 17 );
info.blocks = ProDOS_Get16( pDiskBytes, nOffset + 19 );
info.size = ProDOS_Get24( pDiskBytes, nOffset + 21 );
info.date = ProDOS_Get16( pDiskBytes, nOffset + 24 );
info.time = ProDOS_Get16( pDiskBytes, nOffset + 26 );
info.cur_ver = pDiskBytes[ nOffset + 28 ];
info.min_ver = pDiskBytes[ nOffset + 29 ];
info.access = pDiskBytes[ nOffset + 30 ];
info.aux = ProDOS_Get16( pDiskBytes, nOffset + 31 );
info.mod_date = ProDOS_Get16( pDiskBytes, nOffset + 33 );
info.mod_time = ProDOS_Get16( pDiskBytes, nOffset + 35 );
info.dir_block = ProDOS_Get16( pDiskBytes, nOffset + 37 );
if ( pFileHeader_ )
*pFileHeader_ = info;
}
// ------------------------------------------------------------------------
void ProDOS_PutFileHeader ( uint8_t *pDiskBytes, int nOffset, ProDOS_FileHeader_t *pMeta )
{
int base = nOffset;
uint8_t KindLen = 0
| ((pMeta->kind & 0xF) << 4)
| ((pMeta->len & 0xF) << 0)
;
pDiskBytes [ base + 0 ] = KindLen;
for( int i = 0; i < pMeta->len; i++ )
pDiskBytes [ base + 1+i] = pMeta->name[ i ];
pDiskBytes[ base + 16 ] = pMeta->type;
ProDOS_Put16( pDiskBytes, base + 17 , pMeta->inode );
ProDOS_Put16( pDiskBytes, base + 19 , pMeta->blocks );
ProDOS_Put24( pDiskBytes, base + 21 , pMeta->size );
ProDOS_Put16( pDiskBytes, base + 24 , pMeta->date );
ProDOS_Put16( pDiskBytes, base + 26 , pMeta->time );
pDiskBytes[ base + 28 ] = pMeta->cur_ver ;
pDiskBytes[ base + 29 ] = pMeta->min_ver ;
pDiskBytes[ base + 30 ] = pMeta->access ;
ProDOS_Put16( pDiskBytes, base + 31 , pMeta->aux );
ProDOS_Put16( pDiskBytes, base + 33 , pMeta->mod_date );
ProDOS_Put16( pDiskBytes, base + 35 , pMeta->mod_time );
ProDOS_Put16( pDiskBytes, base + 37 , pMeta->dir_block );
}
// ------------------------------------------------------------------------
size_t ProDOS_String_CopyUpper( char *pDst, const char *pSrc, int nLen = 0 )
{
char *pBeg = pDst;
if( !nLen )
nLen = strlen( pSrc );
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);
}