Files
AppleWin/source/ProDOS_Utils.cpp
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

1013 lines
35 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
*/
#include "StdAfx.h"
#include "ProDOS_Utils.h"
#include "Log.h"
#include "ProDOS_FileSystem.h"
#include "CmdLine.h"
#include "DiskImage.h"
#include "DiskImageHelper.h"
#include "FrameBase.h"
#include "../resource/resource.h"
#if __cplusplus >= 201703L // Compiler option: /std:c++17
#include <filesystem>
#endif
enum SectorOrder_e
{
INTERLEAVE_AUTO_DETECT
, INTERLEAVE_DOS33_ORDER
, INTERLEAVE_PRODOS_ORDER
};
namespace
{
//===========================================================================
int Util_GetTrackSectorOffset( const int nTrack, const int nSector )
{
return (TRACK_DENIBBLIZED_SIZE * nTrack) + (nSector * 256);
}
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
enum { DSK_SECTOR_SIZE = 256 };
// Map Physical <-> Logical
const uint8_t g_aInterleave_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 Util_Disk_InterleaveForward( int iSector, size_t *src_, size_t *dst_ )
{
*src_ = g_aInterleave_DSK[ iSector ]*DSK_SECTOR_SIZE;
*dst_ = iSector *DSK_SECTOR_SIZE; // linearize
};
void Util_Disk_InterleaveReverse( int iSector, size_t *src_, size_t *dst_ )
{
*src_ = iSector *DSK_SECTOR_SIZE; // un-linearize
*dst_ = g_aInterleave_DSK[ iSector ]*DSK_SECTOR_SIZE;
}
// With C++17 could use std::filesystem::path
// returns '.dsk'
// Alt. PathFindExtensionA()
std::string Util_GetFileNameExtension(const std::string& pathname)
{
#if __cplusplus >= 201703L // Compiler option: /std:c++17
const std::filesystem::path path( pathname );
return path.extension().generic_string();
#else
const size_t nLength = pathname.length();
const size_t iExtension = pathname.rfind( '.', nLength );
if (iExtension != std::string::npos)
return pathname.substr( iExtension, nLength );
else
return std::string("");
#endif
}
SectorOrder_e Util_Disk_CalculateSectorOrder(const std::string& pathname)
{
SectorOrder_e eOrder = INTERLEAVE_AUTO_DETECT;
std::string sExtension = Util_GetFileNameExtension( pathname );
if (sExtension.length())
{
/**/ if (sExtension == ".dsk") eOrder = INTERLEAVE_DOS33_ORDER;
else if (sExtension == ".do" ) eOrder = INTERLEAVE_DOS33_ORDER;
else if (sExtension == ".po" ) eOrder = INTERLEAVE_PRODOS_ORDER;
else if (sExtension == ".hdv") eOrder = INTERLEAVE_PRODOS_ORDER;
}
return eOrder;
}
// Swizzle sectors in DOS33 order to ProDOS order in-place
//===========================================================================
void Util_ProDOS_ForwardSectorInterleave (uint8_t *pDiskBytes, const size_t nDiskSize, const SectorOrder_e eSectorOrder)
{
if (eSectorOrder == INTERLEAVE_DOS33_ORDER)
{
size_t nOffset = 0;
std::vector<uint8_t> pSource(pDiskBytes, pDiskBytes + nDiskSize);
const int nTracks = nDiskSize / TRACK_DENIBBLIZED_SIZE;
for( int iTrack = 0; iTrack < nTracks; iTrack++ )
{
for( int iSector = 0; iSector < 16; iSector++ )
{
size_t nSrc;
size_t nDst;
Util_Disk_InterleaveForward( iSector, &nSrc, &nDst );
memcpy( pDiskBytes + nOffset + nDst, pSource.data() + nOffset + nSrc, DSK_SECTOR_SIZE );
}
nOffset += TRACK_DENIBBLIZED_SIZE;
}
}
}
// See:
// * https://prodos8.com/docs/technote/30/
// * 2.2.3 Sparse Files
// https://prodos8.com/docs/techref/file-use/
// * B.3.6 - Sparse Files
// https://prodos8.com/docs/techref/file-organization/
//===========================================================================
bool Util_ProDOS_IsFileBlockSparse( int nOffset, const uint8_t* pFileData, const size_t nFileSize )
{
if ((size_t)nOffset >= nFileSize)
return false;
const int nEnd = (nOffset + PRODOS_BLOCK_SIZE ) > nFileSize
? nOffset + (nFileSize % PRODOS_BLOCK_SIZE)
: nOffset + PRODOS_BLOCK_SIZE;
for (int iOffset = nOffset ; iOffset < nEnd; iOffset++ )
{
if (pFileData[ iOffset ])
{
return false;
}
}
return true;
}
// Swizzles sectors in ProDOS order to DOS33 order in-place
//===========================================================================
void Util_ProDOS_ReverseSectorInterleave (uint8_t *pDiskBytes, const size_t nDiskSize, const SectorOrder_e eSectorOrder)
{
// Swizle from ProDOS to DOS33 order
if (eSectorOrder == INTERLEAVE_DOS33_ORDER)
{
size_t nOffset = 0;
std::vector<uint8_t> pSource(pDiskBytes, pDiskBytes + nDiskSize);
const int nTracks = nDiskSize / TRACK_DENIBBLIZED_SIZE;
for( int iTrack = 0; iTrack < nTracks; iTrack++ )
{
for( int iSector = 0; iSector < 16; iSector++ )
{
size_t nSrc;
size_t nDst;
Util_Disk_InterleaveReverse( iSector, &nSrc, &nDst );
memcpy( pDiskBytes + nOffset + nDst, pSource.data() + nOffset + nSrc, DSK_SECTOR_SIZE );
}
nOffset += TRACK_DENIBBLIZED_SIZE;
}
}
}
// NB Assumes pDiskBytes are in ProDOS order!
//===========================================================================
void Util_ProDOS_FormatFileSystem (uint8_t *pDiskBytes, const size_t nDiskSize, const char *pVolumeName)
{
const size_t nBootBlockSize = DSK_SECTOR_SIZE * 2;
memset( pDiskBytes + nBootBlockSize, 0, nDiskSize - nBootBlockSize );
// Create blocks for root directory
int nRootDirBlocks = 4;
int iPrevDirBlock = 0;
int iNextDirBlock = 0;
int iOffset;
ProDOS_VolumeHeader_t tVolume;
ProDOS_VolumeHeader_t *pVolume = &tVolume;
memset( pVolume, 0, sizeof(ProDOS_VolumeHeader_t) );
// Init Bitmap
pVolume->meta.bitmap_block = (uint16_t) (PRODOS_ROOT_BLOCK + nRootDirBlocks);
int nBitmapBlocks = ProDOS_BlockInitFree( pDiskBytes, nDiskSize, pVolume );
// Set boot blocks as in-use
for( int iBlock = 0; iBlock < PRODOS_ROOT_BLOCK; iBlock++ )
ProDOS_BlockSetUsed( pDiskBytes, pVolume, iBlock );
for( int iBlock = 0; iBlock < nRootDirBlocks; iBlock++ )
{
iNextDirBlock = ProDOS_BlockGetFirstFree( pDiskBytes, nDiskSize, pVolume );
iOffset = iNextDirBlock * PRODOS_BLOCK_SIZE;
ProDOS_BlockSetUsed( pDiskBytes, pVolume, iNextDirBlock );
// Double Linked List
// [0] = prev
// [2] = next -- will be set on next allocation
ProDOS_Put16( pDiskBytes, iOffset + 0, iPrevDirBlock );
ProDOS_Put16( pDiskBytes, iOffset + 2, 0 );
if( iBlock )
{
// Fixup previous directory block with pointer to this one
iOffset = iPrevDirBlock * PRODOS_BLOCK_SIZE;
ProDOS_Put16( pDiskBytes, iOffset + 2, iNextDirBlock );
}
iPrevDirBlock = iNextDirBlock;
}
// Alloc Bitmap Blocks
for( int iBlock = 0; iBlock < nBitmapBlocks; iBlock++ )
{
int iBitmap = ProDOS_BlockGetFirstFree( pDiskBytes, nDiskSize, pVolume );
ProDOS_BlockSetUsed( pDiskBytes, pVolume, iBitmap );
}
pVolume->entry_len = 0x27;
pVolume->entry_num = (uint8_t) (PRODOS_BLOCK_SIZE / pVolume->entry_len);
pVolume->file_count = 0;
if( *pVolumeName == '/' )
pVolumeName++;
size_t nLen = strlen( pVolumeName );
pVolume->kind = PRODOS_KIND_ROOT;
pVolume->len = (uint8_t) nLen;
ProDOS_String_CopyUpper( pVolume->name, pVolumeName, 15 );
pVolume->access = 0
| ACCESS_D
| ACCESS_N
| ACCESS_B
| ACCESS_W
| ACCESS_R
;
// TODO:
//tVolume.date = config.date;
//tVolume.time = config.time;
ProDOS_SetVolumeHeader( pDiskBytes, pVolume, PRODOS_ROOT_BLOCK );
}
//===========================================================================
bool Util_ProDOS_AddFile (uint8_t* pDiskBytes, const size_t nDiskSize, const char* pVolumeName, const uint8_t* pFileData, const size_t nFileSize, ProDOS_FileHeader_t &meta, const bool bAllowSparseFile = false)
{
assert( pFileData );
int iBase = ProDOS_BlockGetPathOffset( pDiskBytes, nullptr, "/" ); // On an empty disk this will be PRODOS_ROOT_OFFSET
int iDirBlock = iBase / PRODOS_BLOCK_SIZE;
ProDOS_VolumeHeader_t tVolume;
ProDOS_VolumeHeader_t *pVolume = &tVolume;
memset( pVolume, 0, sizeof(ProDOS_VolumeHeader_t) );
ProDOS_GetVolumeHeader( pDiskBytes, pVolume, iDirBlock );
// Verify we have room in the current directory
int iFreeOffset = ProDOS_DirGetFirstFreeEntryOffset( pDiskBytes, pVolume, iBase );
if (iFreeOffset <= 0)
return false; // disk full
int iKind = PRODOS_KIND_DEL;
int nBlocksData = (nFileSize + 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
// Calculate size of meta blocks and kind of file
if (nFileSize <= 1*PRODOS_BLOCK_SIZE) // <= 512, 1 Block
{
iKind = PRODOS_KIND_SEED;
}
else
if (nFileSize > 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( nFileSize > PRODOS_BLOCK_SIZE ) // <= 128K, 2-256 blocks
{
iKind = PRODOS_KIND_SAPL;
nBlocksIndex = 1; // single index = PRODOS_BLOCK_SIZE/2 = 256 data blocks
}
// We simply can't set nBlocksTotal
// nBlocksTotal = nBlocksIndex + nBlocksData;
// Since we may have sparse blocks
nBlocksTotal = nBlocksIndex;
for( int iBlock = 0; iBlock < nBlocksIndex; iBlock++ )
{
// Blank Volume
// 0 ] Boot Apple //e
// 1 ] Boot Apple ///
// 2 \ Root Directory
// 3 |
// 4 |
// 5 /
// 6 Volume Bitmap
// 7 First Free Block
int iMetaBlock = ProDOS_BlockGetFirstFree( pDiskBytes, nDiskSize, pVolume );
if( !iMetaBlock )
{
return false; // out of disk space, no free room
}
// First Block has meta information -- blocks used for file
if (iBlock == 0)
{
iNode = iMetaBlock;
iIndexBase = iMetaBlock * PRODOS_BLOCK_SIZE;
}
else
{
// PRODOS_KIND_SEED doesn't have an index block
if (iKind == PRODOS_KIND_TREE)
{
ProDOS_PutIndexBlock( pDiskBytes, iMasterIndex, iBlock, iMetaBlock );
}
// Not implemented PRODOS_KIND_TREE
assert( iKind != PRODOS_KIND_TREE );
}
ProDOS_BlockSetUsed( pDiskBytes, pVolume, iMetaBlock );
#if _DEBUG
LogOutput( "0x---- FileBlock: --/-- MetaBlock: $%02X\n", iMetaBlock );
#endif
}
// Copy Data
const uint8_t *pSrc = pFileData;
const size_t nSlack = nFileSize % PRODOS_BLOCK_SIZE;
const size_t nLastBlockSize = nSlack ? nSlack : PRODOS_BLOCK_SIZE;
const bool PRODOS_FILE_TYPE_TREE_NOT_IMPLEMENTED = false;
for( int iBlock = 0; iBlock < nBlocksData; iBlock++ )
{
int iDataBlock = ProDOS_BlockGetFirstFree( pDiskBytes, nDiskSize, pVolume );
if( !iDataBlock )
{
return false;
}
// if file size <= 512 bytes
if (iBlock == 0)
{
if (iKind == PRODOS_KIND_SEED)
{
iNode = iDataBlock;
}
}
int iDstOffset = iDataBlock * PRODOS_BLOCK_SIZE;
uint8_t *pDst = pDiskBytes + iDstOffset;
bool bLastBlock = iBlock == (nBlocksData - 1);
// Any 512-byte block containing all zeroes doesn't need to be written to disk.
// Instead we write an index block of $0000 to tell ProDOS that this block is a sparse block.
bool bIsSparseBlock = Util_ProDOS_IsFileBlockSparse( iBlock * PRODOS_BLOCK_SIZE, pFileData, nFileSize );
#if _DEBUG
LogOutput( "0x%04" SIZE_T_FMT " FileBlock: %2d/%2d DataBlock: $%02X LastBlock=%d Sparse=%d\n"
, iBlock*PRODOS_BLOCK_SIZE
, iBlock+1
, nBlocksData
, iDataBlock
, bLastBlock
, bIsSparseBlock
);
#endif
if (bIsSparseBlock && bAllowSparseFile)
{
if (iKind == PRODOS_KIND_SAPL)
{
ProDOS_PutIndexBlock( pDiskBytes, iIndexBase, iBlock, 0 ); // Update File Bitmap
}
else
if (iKind == PRODOS_KIND_TREE)
{
assert( PRODOS_FILE_TYPE_TREE_NOT_IMPLEMENTED ); // TODO: ProDOS tree file not implemented
}
}
else
{
if (bLastBlock)
{
if (nLastBlockSize)
{
memcpy( pDst, pSrc, nLastBlockSize );
nBlocksTotal++;
ProDOS_BlockSetUsed( pDiskBytes, pVolume, iDataBlock ); // Update Volume Bitmap
}
else
{
iIndexBase = 0; // Last block has zero size. Don't update index block
}
}
else
{
memcpy( pDst, pSrc, PRODOS_BLOCK_SIZE );
nBlocksTotal++;
ProDOS_BlockSetUsed( pDiskBytes, pVolume, iDataBlock ); // Update Volume Bitmap
}
if (iKind == PRODOS_KIND_SAPL)
{
// Update single index block
if (iIndexBase)
{
ProDOS_PutIndexBlock( pDiskBytes, iIndexBase, iBlock, iDataBlock ); // Update File Bitmap
}
}
else
if (iKind == PRODOS_KIND_TREE)
{
// Update multiple index blocks
assert( PRODOS_FILE_TYPE_TREE_NOT_IMPLEMENTED ); // TODO: ProDOS tree file not implemented
}
}
pSrc += PRODOS_BLOCK_SIZE;
}
// Update directory entry with meta information
meta.inode = iNode;
meta.blocks = nBlocksTotal; // Note: File Entry DOES include index + non-sparse data block(s)
meta.dir_block = iDirBlock;
ProDOS_PutFileHeader( pDiskBytes, iFreeOffset, &meta );
// Update Directory with Header
pVolume->file_count++;
ProDOS_SetVolumeHeader( pDiskBytes, pVolume, iDirBlock );
return true;
}
//===========================================================================
bool Util_ProDOS_CopyBASIC ( uint8_t *pDiskBytes, const size_t nDiskSize, const char *pVolumeName, FrameBase *pFrame )
{
const size_t nFileSize = 0x2800; // 10,240 -> 20 blocks + 1 index block = 21 blocks
const uint8_t *pFileData = (uint8_t *) pFrame->GetResource(IDR_FILE_BASIC17, "FIRMWARE", nFileSize);
// Acc dnb??iwr /PRODOS.2.4.3 Blocks Size Type Aux Kind iNode Dir Ver Min Create Time Modified Time
// --- -------- ---------------- ------ ------- ------- ----- ----- ----- ----- --- --- --------- ------ --------- ------
// $21 --b----r *BASIC.SYSTEM 21 $002800 SYS $FF $2000 sap 2 @0029 @0002 2.4 v00 13-JAN-18 9:09a 30-AUG-16 7:56a
int bAccess = 0
| ACCESS_B
| ACCESS_R
;
ProDOS_FileHeader_t meta;
memset( &meta, 0, sizeof(ProDOS_FileHeader_t) );
const char *pName = "BASIC.SYSTEM";
const uint16_t nDateCreate = ProDOS_PackDate( 18, 1, 13 ); // YYMMDD, NOTE: Jan starts at 1, not 0.
const uint16_t nTimeCreate = ProDOS_PackTime( 9, 9 );
const uint16_t nDateModify = ProDOS_PackDate( 16, 8, 30 ); // YYMMDD
const uint16_t nTimeModify = ProDOS_PackTime( 7, 56 );
meta.kind = PRODOS_KIND_SAPL;
strcpy( meta.name, pName );
meta.len = strlen( pName ) & 0xF;
meta.type = 0xFF; // SYS
// .inode = TBD
// .blocks = TBD
meta.size = nFileSize;
meta.date = nDateCreate;
meta.time = nTimeCreate;
meta.cur_ver = 0x24;
meta.min_ver = 0x00;
meta.access = bAccess;
meta.aux = 0x2000;
meta.mod_date = nDateModify;
meta.mod_time = nTimeModify;
// .dir_block = TBD;
return Util_ProDOS_AddFile( pDiskBytes, nDiskSize, pVolumeName, pFileData, nFileSize, meta, true );
}
//=======Util_ProDOS_CopyBitsyBoot====================================================================
bool Util_ProDOS_CopyBitsyBoot( uint8_t *pDiskBytes, const size_t nDiskSize, const char *pVolumeName, FrameBase *pFrame )
{
const size_t nFileSize = 0x16D; // < 512 bytes -> SEED, only 1 data block
const uint8_t *pFileData = (uint8_t *) pFrame->GetResource(IDR_FILE_BITSY_BOOT, "FIRMWARE", nFileSize);
// Acc dnb??iwr /PRODOS.2.4.2 Blocks Size Type Aux Kind iNode Dir Ver Min Create Time Modified Time
// --- -------- ---------------- ------ ------- ------- ----- ----- ----- ----- --- --- --------- ------ --------- ------
// $21 --b----r *BITSY.BOOT 1 $00016D SYS $FF $2000 sed 1 @003D @0002 2.4 v00 13-JAN-18 9:09a 15-SEP-16 9:49a
int bAccess = 0
| ACCESS_B
| ACCESS_R
;
ProDOS_FileHeader_t meta;
memset( &meta, 0, sizeof(ProDOS_FileHeader_t) );
const char *pName = "BITSY.BOOT";
const uint16_t nDateCreate = ProDOS_PackDate( 18, 1, 13 ); // YYMMDD, NOTE: Jan starts at 1, not 0.
const uint16_t nTimeCreate = ProDOS_PackTime( 9, 9 );
const uint16_t nDateModify = ProDOS_PackDate( 16, 9, 15 ); // YYMMDD
const uint16_t nTimeModify = ProDOS_PackTime( 9, 49 );
meta.kind = PRODOS_KIND_SEED; // File size is <= 512 bytes
strcpy( meta.name, pName );
meta.len = strlen( pName ) & 0xF;
meta.type = 0xFF; // SYS
// .inode = TBD
// .blocks = TBD
meta.size = nFileSize;
meta.date = nDateCreate;
meta.time = nTimeCreate;
meta.cur_ver = 0x24;
meta.min_ver = 0x00;
meta.access = bAccess;
meta.aux = 0x2000;
meta.mod_date = nDateModify;
meta.mod_time = nTimeModify;
// .dir_block = TBD;
return Util_ProDOS_AddFile( pDiskBytes, nDiskSize, pVolumeName, pFileData, nFileSize, meta );
}
//===========================================================================
bool Util_ProDOS_CopyBitsyBye ( uint8_t *pDiskBytes, const size_t nDiskSize, const char *pVolumeName, FrameBase *pFrame )
{
const size_t nFileSize = 0x0038; // < 512 bytes -> SEED, only 1 data block
const uint8_t *pFileData = (uint8_t*) pFrame->GetResource(IDR_FILE_BITSY_BYE, "FIRMWARE", nFileSize);
// Acc dnb??iwr /PRODOS.2.4.2 Blocks Size Type Aux Kind iNode Dir Ver Min Create Time Modified Time
// --- -------- ---------------- ------ ------- ------- ----- ----- ----- ----- --- --- --------- ------ --------- ------
// $21 --b----r *QUIT.SYSTEM 1 $000038 SYS $FF $2000 sed 1 @0027 @0002 2.4 v00 13-JAN-18 9:09a 15-SEP-16 9:41a
int bAccess = 0
| ACCESS_B
| ACCESS_R
;
ProDOS_FileHeader_t meta;
memset( &meta, 0, sizeof(ProDOS_FileHeader_t) );
const char *pName = "QUIT.SYSTEM";
const uint16_t nDateCreate = ProDOS_PackDate( 18, 1, 13 ); // YYMMDD, NOTE: Jan starts at 1, not 0.
const uint16_t nTimeCreate = ProDOS_PackTime( 9, 9 );
const uint16_t nDateModify = ProDOS_PackDate( 16, 9, 15 ); // YYMMDD
const uint16_t nTimeModify = ProDOS_PackTime( 9, 41 );
meta.kind = PRODOS_KIND_SEED; // File size is <= 512 bytes
strcpy( meta.name, pName );
meta.len = strlen( pName ) & 0xF;
meta.type = 0xFF; // SYS
// .inode = TBD
// .blocks = TBD
meta.size = nFileSize;
meta.date = nDateCreate;
meta.time = nTimeCreate;
meta.cur_ver = 0x24;
meta.min_ver = 0x00;
meta.access = bAccess;
meta.aux = 0x2000;
meta.mod_date = nDateModify;
meta.mod_time = nTimeModify;
// .dir_block = TBD;
return Util_ProDOS_AddFile( pDiskBytes, nDiskSize, pVolumeName, pFileData, nFileSize, meta );
}
//===========================================================================
bool Util_ProDOS_CopyDOS ( uint8_t *pDiskBytes, const size_t nDiskSize, const char *pVolumeName, FrameBase *pFrame )
{
const size_t nFileSize = 0x42E8; // 17,128 -> 34 blocks but last block is sparse -> 33 data blocks + 1 index block
const uint8_t *pFileData = (uint8_t*) pFrame->GetResource(IDR_OS_PRODOS243, "FIRMWARE", nFileSize);
// Acc dnb??iwr /PRODOS.2.4.3 Blocks Size Type Aux Kind iNode Dir Ver Min Create Time Modified Time
// --- -------- ---------------- ------ ------- ------- ----- ----- ----- ----- --- --- --------- ------ --------- ------
// $E3 dnb---wr PRODOS 34 $0042E8 SYS $FF $0000 sap 2 @0007 @0002 0.0 v80 30-DEC-23 2:43a 30-DEC-23 2:43a
int bAccess = 0
| ACCESS_D
| ACCESS_N
| ACCESS_B
| ACCESS_W
| ACCESS_R
;
ProDOS_FileHeader_t meta;
memset( &meta, 0, sizeof(ProDOS_FileHeader_t) );
const char *pName = "PRODOS";
const uint16_t nDateCreate = ProDOS_PackDate( 23, 12, 30 ); // NOTE: Jan starts at 1, not 0.
const uint16_t nTimeCreate = ProDOS_PackTime( 2, 43 ); // Version. "Cute".
const uint16_t nDateModify = ProDOS_PackDate( 23, 12, 30 );
const uint16_t nTimeModify = ProDOS_PackTime( 2, 43 );
meta.kind = PRODOS_KIND_SAPL;
strcpy( meta.name, pName );
meta.len = strlen( pName ) & 0xF;
meta.type = 0xFF; // SYS
// .inode = TBD
// .blocks = TBD
meta.size = nFileSize;
meta.date = nDateCreate;
meta.time = nTimeCreate;
meta.cur_ver = 0x00;
meta.min_ver = 0x80;
meta.access = bAccess;
meta.aux = 0x0000; // ignored for SYS, since load address = $2000
meta.mod_date = nDateModify;
meta.mod_time = nTimeModify;
// .dir_block = TBD;
return Util_ProDOS_AddFile( pDiskBytes, nDiskSize, pVolumeName, pFileData, nFileSize, meta, true );
}
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// bSectorsUsed is 16-bits bitmask of sectors.
// 1 = Free
// 0 = Used
//===========================================================================
void Util_DOS33_SetTrackSectorUsage ( uint8_t *pVTOC, const int nTrack, const uint16_t bSectorsFree )
{
int nOffset = 0x38 + (nTrack * 4);
// Byte Sectors
// 0 FEDC BA98
// 1 7654 3210
// 2 -Wasted-
// 3 -Wasted-
#if _DEBUG
std::string sFree;
std::string sUsed;
for( int iSector = 15; iSector >= 0; iSector-- )
{
bool bFree = (bSectorsFree >> iSector) & 1;
sFree.append( 1, (char)( bFree) | '0' );
sUsed.append( 1, (char)(!bFree) ^ '0' );
if ((iSector & 3) == 0)
{
sFree.append( 1, ' ' );
sUsed.append( 1, ' ' );
}
}
LogOutput("Track: %02X, VTOC[ %02X ]: %02X %02X %02X %02X -> %02X %02X, Free: %s, Used: %s\n"
, nTrack
, nOffset
, pVTOC[ nOffset + 0 ]
, pVTOC[ nOffset + 1 ]
, pVTOC[ nOffset + 2 ]
, pVTOC[ nOffset + 3 ]
, (bSectorsFree >> 8) & 0xFF
, (bSectorsFree >> 0) & 0xFF
, sFree.c_str()
, sUsed.c_str()
);
#endif
pVTOC[ nOffset + 0 ] = (bSectorsFree >> 8) & 0xFF;
pVTOC[ nOffset + 1 ] = (bSectorsFree >> 0) & 0xFF;
pVTOC[ nOffset + 2 ] = 0x00;
pVTOC[ nOffset + 3 ] = 0x00;
}
//===========================================================================
void Util_DOS33_SetTrackUsed ( uint8_t *pDiskBytes, const int nVTOC_Track, int nTrackUsed )
{
int nOffset = Util_GetTrackSectorOffset( nVTOC_Track, 0 );
uint8_t *pVTOC = &pDiskBytes[ nOffset ];
Util_DOS33_SetTrackSectorUsage( pVTOC, nTrackUsed, 0x0000 );
}
//===========================================================================
void Util_DOS33_FormatFileSystem ( uint8_t *pDiskBytes, const size_t nDiskSize, const int nVTOC_Track )
{
const int nTracks = nDiskSize / TRACK_DENIBBLIZED_SIZE;
int nOffset;
assert (nTracks <= TRACKS_MAX);
// Update CATALOG next track/sector
for( int iSector = 0xF; iSector > 1; iSector-- )
{
nOffset = Util_GetTrackSectorOffset( nVTOC_Track, iSector );
pDiskBytes[ nOffset + 1 ] = nVTOC_Track;
pDiskBytes[ nOffset + 2 ] = iSector - 1;
}
// Last sector in CATALOG has no link
nOffset = Util_GetTrackSectorOffset( nVTOC_Track, 1 );
pDiskBytes[ nOffset + 1 ] = 0;
pDiskBytes[ nOffset + 2 ] = 0;
// FTOC = 256 bytes
// - HeaderSize = 0x0C
// 0x00 Wasted byte
// 0x01 Track Next FTOC
// 0x02 Sector Next FTOC
// 0x03 Wasted byte
// 0x04 Wasted byte
// 0x05 Sector offset of file in this T/S
// 0x06 Sector offset of file in this T/S
// 0x07 Wasted byte
// 0x08 Wasted byte
// 0x09 Wasted byte
// 0x0A Wasted byte
// 0x0B Wasted byte
// / 2 bytes for next Track/Sector
// = 122 entries
const uint8_t FTOC_ENTRIES = (256 - 12) / 2;
nOffset = Util_GetTrackSectorOffset( nVTOC_Track, 0 );
pDiskBytes[ nOffset + 0x1 ] = nVTOC_Track; // CATALOG = T11
pDiskBytes[ nOffset + 0x2 ] = 0xF; // CATALOG = S0F
pDiskBytes[ nOffset + 0x3 ] = 0x3; // DOS 3.3
pDiskBytes[ nOffset + 0x6 ] = DEFAULT_VOLUME_NUMBER; // Volume
pDiskBytes[ nOffset + 0x27 ] = FTOC_ENTRIES; // TrackSector pairs
pDiskBytes[ nOffset + 0x30 ] = nVTOC_Track; // Last Track Allocated
pDiskBytes[ nOffset + 0x31 ] = 1; // Direction = +1
pDiskBytes[ nOffset + 0x34 ] = nTracks; // Number of Tracks based on image size
pDiskBytes[ nOffset + 0x35 ] = 16; // 16 Sectors/Track
pDiskBytes[ nOffset + 0x36 ] = 0x00; // 256 Bytes/Sector Lo
pDiskBytes[ nOffset + 0x37 ] = 0x01; // 256 Bytes/Sector Hi
uint8_t *pVTOC = &pDiskBytes[ nOffset ];
for( int iTrack = 0; iTrack < nTracks; iTrack++ )
{
/**/ if (iTrack == 0) Util_DOS33_SetTrackSectorUsage(pVTOC, iTrack, 0x0000); // Track T00 can NEVER be free due to stupid DOS 3.3 design (1 byte bug of `BNE` instead of `BPL`)
else if (iTrack == nVTOC_Track) Util_DOS33_SetTrackSectorUsage(pVTOC, iTrack, 0x0000);
else /* */ Util_DOS33_SetTrackSectorUsage(pVTOC, iTrack, 0xFFFF); // Tracks T01-T10, T12-T22 are free for use
}
}
}
// public functions
void New_DOSProDOS_Disk( const char * pTitle, const std::string & pathname, const size_t nDiskSize,
const bool bIsDOS33, const bool bNewDiskCopyBitsyBoot, const bool bNewDiskCopyBitsyBye, const bool bNewDiskCopyBASIC, const bool bNewDiskCopyProDOS,
FrameBase *pFrame )
{
FILE *hFile = fopen( pathname.c_str(), "wb");
if (hFile)
{
std::vector<uint8_t> pDiskBytes(nDiskSize, 0);
if (bIsDOS33)
{
// File System
int VTOC_TRACK = 0x11; // TODO: Allow user to over-ride via command-line argument? --vtoc 17
Util_DOS33_FormatFileSystem( pDiskBytes.data(), nDiskSize, VTOC_TRACK );
// Boot Sector + OS
const size_t nDOS33Size = 3 * 16 * 256; // First 3 tracks * 16 sectors/track * 256 bytes/sector
const BYTE *pDOS33Data = pFrame->GetResource(IDR_OS_DOS33, "FIRMWARE", nDOS33Size);
if (pDOS33Data)
{
memcpy( pDiskBytes.data(), pDOS33Data, nDOS33Size );
// DOS 3.3 resides on Track 0, 1, 2
// Track 0 was already reserved when the file system was formatted.
Util_DOS33_SetTrackUsed( pDiskBytes.data(), VTOC_TRACK, 1 );
Util_DOS33_SetTrackUsed( pDiskBytes.data(), VTOC_TRACK, 2 );
}
else
{
pFrame->FrameMessageBox( "WARNING: Could't find built-in DOS 3.3 Operating System!\n\nDisk will have no boot sector.", pTitle, MB_OK|MB_ICONINFORMATION);
}
}
else // ProDOS
{
const size_t nBootSectorsSize = 2 * 512;
const uint8_t *pBootSectorsData = (uint8_t*) pFrame->GetResource(IDR_BOOT_SECTOR_PRODOS243, "FIRMWARE", nBootSectorsSize);
assert(pBootSectorsData);
SectorOrder_e eSectorOrder = INTERLEAVE_PRODOS_ORDER;
const char *pVolumeName = "BLANK";
Util_ProDOS_ForwardSectorInterleave( pDiskBytes.data(), nDiskSize, eSectorOrder );
Util_ProDOS_FormatFileSystem ( pDiskBytes.data(), nDiskSize, pVolumeName );
memcpy(pDiskBytes.data(), pBootSectorsData, nBootSectorsSize);
if (bNewDiskCopyBitsyBoot) Util_ProDOS_CopyBitsyBoot( pDiskBytes.data(), nDiskSize, pVolumeName, pFrame );
if (bNewDiskCopyBitsyBye) Util_ProDOS_CopyBitsyBye ( pDiskBytes.data(), nDiskSize, pVolumeName, pFrame );
if (bNewDiskCopyBASIC) Util_ProDOS_CopyBASIC ( pDiskBytes.data(), nDiskSize, pVolumeName, pFrame );
if (bNewDiskCopyProDOS) Util_ProDOS_CopyDOS ( pDiskBytes.data(), nDiskSize, pVolumeName, pFrame );
Util_ProDOS_ReverseSectorInterleave( pDiskBytes.data(), nDiskSize, eSectorOrder );
}
fwrite( pDiskBytes.data(), 1, nDiskSize, hFile );
fclose( hFile );
}
else
{
pFrame->FrameMessageBox( "ERROR: Couldn't open new disk image.", pTitle, MB_OK );
}
}
void New_Blank_Disk( const char * pTitle, const std::string & pathname,
const size_t nDiskSize, const bool bIsHardDisk, FrameBase *pFrame )
{
FILE *hFile = fopen( pathname.c_str(), "wb");
if (hFile)
{
size_t nDiskBuffer = nDiskSize;
std::vector<char> pDiskBuffer(nDiskSize, 0);
// See: firmware/BootSector/bootsector.a
std::vector<uint8_t> pBootSector;
bool bUseAppleWinBootSector = !g_cmdLine.nBootSectorFileSize; // Can have custom boot loader via command line: -bootsector <file>
if (bUseAppleWinBootSector)
{
const size_t bootSectorSize = 256;
const BYTE *pAppleWinBootSector = pFrame->GetResource(IDR_BOOT_SECTOR, "FIRMWARE", bootSectorSize);
if (pAppleWinBootSector)
{
pBootSector.resize(bootSectorSize);
memcpy(pBootSector.data(), pAppleWinBootSector, pBootSector.size());
if (bIsHardDisk)
{
// Modify boot message depending on type of disk
// Floppy: THIS IS AN EMPTY DATA DISK.
// Hard : THIS IS AN EMPTY HARD DISK.
// ^^^^
size_t nOffsetData = pBootSector[ 0xFF ]; // MAGIC NUMBER: Last byte has offset of text message. See: firmware/BootSector/bootsector.a
if (nOffsetData > 0)
{
pBootSector[ nOffsetData+0 ] = 0x80 | 'H';
pBootSector[ nOffsetData+1 ] = 0x80 | 'A';
pBootSector[ nOffsetData+2 ] = 0x80 | 'R';
pBootSector[ nOffsetData+3 ] = 0x80 | 'D';
}
}
}
else
{
pFrame->FrameMessageBox( "WARNING: Could't find built-in AppleWin boot sector!\n\nDisk will have no boot sector.", pTitle, MB_OK|MB_ICONINFORMATION);
}
}
else
{
FILE *pFile = fopen( g_cmdLine.sBootSectorFileName.c_str(), "rb" );
if (pFile)
{
size_t nSize = MIN(g_cmdLine.nBootSectorFileSize, nDiskSize );
pBootSector.resize(nSize);
size_t nRead = fread( pBootSector.data(), 1, nSize, pFile );
assert( nRead == nSize );
fclose( pFile );
if (g_cmdLine.nBootSectorFileSize > nDiskSize)
{
std::string sMessage( StrFormat(
"WARNING: Custom boot sector (%zu) is larger then the disk image (%zu)!\n"
"\n"
"Restricting boot sector to disk image size."
, g_cmdLine.nBootSectorFileSize
, nDiskSize
));
pFrame->FrameMessageBox( sMessage.c_str(), pTitle, MB_OK | MB_ICONINFORMATION);
}
}
else
{
pFrame->FrameMessageBox( "WARNING: Couldn't open custom boot sector file.\n\nNo boot sector written.", pTitle, MB_OK | MB_ICONERROR );
}
}
memcpy( pDiskBuffer.data(), pBootSector.data(), pBootSector.size() );
fwrite( pDiskBuffer.data(), 1, nDiskSize, hFile );
fclose( hFile );
}
else
{
pFrame->FrameMessageBox( "ERROR: Couldn't open new disk image.", pTitle, MB_OK );
}
}
void Format_ProDOS_Disk( const std::string & pathname, FrameBase *pFrame )
{
FILE *hFile = fopen( pathname.c_str(), "r+b" );
if (hFile)
{
fseek( hFile, 0, SEEK_END );
size_t nDiskSize = ftell( hFile );
fseek( hFile, 0, SEEK_SET );
size_t nMaxDiskSize = HARDDISK_32M_SIZE;
if (nDiskSize > nMaxDiskSize)
{
std::string sMessage(StrFormat(
"ERROR: Disk Image Size (%zu bytes) > maximum ProDOS volume size (%zu bytes)"
, nDiskSize
, nMaxDiskSize
));
pFrame->FrameMessageBox( sMessage.c_str(), "Format", MB_ICONWARNING | MB_OK);
}
else
{
std::vector<uint8_t> pDiskBytes(nDiskSize);
size_t nReadSize = fread( pDiskBytes.data(), 1, nDiskSize, hFile );
assert( nReadSize == nDiskSize );
// We can have DOS or ProDOS sector interleaving
// Extension Interleave
// .bin Unknown, assume DOS
// .dsk DOS
// .po ProDOS
// .hdv ProDOS
SectorOrder_e eSectorOrder = Util_Disk_CalculateSectorOrder( pathname );
if (eSectorOrder == INTERLEAVE_AUTO_DETECT)
{
int res = pFrame->FrameMessageBox(
"Unable to auto-detect the disk image sector order!\n"
"\n"
"Is this image using a ProDOS sector order?\n"
"(No will use DOS 3.3 sector order)"
, "Format", MB_ICONWARNING|MB_YESNO);
eSectorOrder = (res == IDYES)
? INTERLEAVE_PRODOS_ORDER
: INTERLEAVE_DOS33_ORDER
;
}
assert (eSectorOrder != INTERLEAVE_AUTO_DETECT);
const char *pVolumeName = "BLANK";
Util_ProDOS_ForwardSectorInterleave( pDiskBytes.data(), nDiskSize, eSectorOrder );
Util_ProDOS_FormatFileSystem ( pDiskBytes.data(), nDiskSize, pVolumeName );
Util_ProDOS_ReverseSectorInterleave( pDiskBytes.data(), nDiskSize, eSectorOrder );
fseek( hFile, 0, SEEK_SET );
size_t nWroteSize = fwrite( pDiskBytes.data(), 1, nReadSize, hFile );
if (nWroteSize != nDiskSize)
{
pFrame->FrameMessageBox( "ERROR: Unable to write ProDOS File System", "Format", MB_ICONWARNING | MB_OK);
}
}
fclose( hFile );
}
}
void Format_DOS33_Disk( const std::string & pathname, FrameBase *pFrame )
{
FILE *hFile = fopen( pathname.c_str(), "r+b" );
if (hFile)
{
fseek( hFile, 0, SEEK_END );
size_t nDiskSize = ftell( hFile );
fseek( hFile, 0, SEEK_SET );
// Verify floppy size is <= 160KB (max 40 tracks) since that is the largest supported by DOS 3.3
// TODO: Maybe use CImageBase::IsValidImageSize() ?
size_t nMinDiskSize = 34 * TRACK_DENIBBLIZED_SIZE;
size_t nMaxDiskSize = TRACKS_MAX * TRACK_DENIBBLIZED_SIZE;
std::string sMessage;
if (nDiskSize < nMinDiskSize)
{
sMessage = StrFormat( "ERROR: Disk image size (%zu bytes) < minimum DOS 3.3 image size (%zu bytes)", nDiskSize, nMinDiskSize );
pFrame->FrameMessageBox( sMessage.c_str(), "Format", MB_ICONERROR | MB_OK);
}
else
if (nDiskSize > nMaxDiskSize)
{
sMessage = StrFormat( "ERROR: Disk image size (%zu bytes) > maximum DOS 3.3 image size (%zu bytes)", nDiskSize, nMaxDiskSize );
pFrame->FrameMessageBox( sMessage.c_str(), "Format", MB_ICONERROR | MB_OK);
}
else
{
std::vector<uint8_t> pDiskBytes(nDiskSize);
size_t nReadSize = fread( pDiskBytes.data(), 1, nDiskSize, hFile );
assert( nReadSize == nDiskSize );
int VTOC_TRACK = 0x11; // TODO: Allow user to over-ride via command-line argument? --vtoc 17
Util_DOS33_FormatFileSystem( pDiskBytes.data(), nDiskSize, VTOC_TRACK );
fseek( hFile, 0, SEEK_SET );
size_t nWroteSize = fwrite( pDiskBytes.data(), 1, nReadSize, hFile );
if (nWroteSize != nDiskSize)
{
pFrame->FrameMessageBox( "ERROR: Unable to write DOS 3.3 file system", "Format", MB_ICONWARNING | MB_OK);
}
}
fclose( hFile );
}
else
{
pFrame->FrameMessageBox( "ERROR: Unable to open disk image for writing DOS 3.3 file system", "Format", MB_ICONWARNING | MB_OK);
}
}