mirror of
https://github.com/AppleWin/AppleWin.git
synced 2024-11-17 21:04:45 +00:00
4bc75093b8
Supports: - all "woz test images" v1.3 (WOZ1, WOZ2) are working, except 3.5" - additionally: Frogger (spiradisc), Choplifter (not Enhanced //e!), Lode Runner, Marble Madness, Skyfox. - woz images can be .gz or .zip compressed (ie. same as other supported images) - save-state Limitations: - read-only, so WOZ images are forced to be write-protected . as a result, games that need r/w images won't work (Stickybear Town Builder, Wizardry) - 5.25" only (not 3.5")
1928 lines
57 KiB
C++
1928 lines
57 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-2010, 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: Disk Image Helper
|
|
*
|
|
* Author: Tom
|
|
*/
|
|
|
|
|
|
#include "StdAfx.h"
|
|
#include "Common.h"
|
|
|
|
#include "zlib.h"
|
|
#include "unzip.h"
|
|
|
|
#include "CPU.h"
|
|
#include "DiskImage.h"
|
|
#include "DiskImageHelper.h"
|
|
#include "Memory.h"
|
|
|
|
|
|
/* DO logical order 0 1 2 3 4 5 6 7 8 9 A B C D E F */
|
|
/* physical order 0 D B 9 7 5 3 1 E C A 8 6 4 2 F */
|
|
|
|
/* PO logical order 0 E D C B A 9 8 7 6 5 4 3 2 1 F */
|
|
/* physical order 0 2 4 6 8 A C E 1 3 5 7 9 B D F */
|
|
|
|
BYTE CImageBase::ms_DiskByte[0x40] =
|
|
{
|
|
0x96,0x97,0x9A,0x9B,0x9D,0x9E,0x9F,0xA6,
|
|
0xA7,0xAB,0xAC,0xAD,0xAE,0xAF,0xB2,0xB3,
|
|
0xB4,0xB5,0xB6,0xB7,0xB9,0xBA,0xBB,0xBC,
|
|
0xBD,0xBE,0xBF,0xCB,0xCD,0xCE,0xCF,0xD3,
|
|
0xD6,0xD7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,
|
|
0xDF,0xE5,0xE6,0xE7,0xE9,0xEA,0xEB,0xEC,
|
|
0xED,0xEE,0xEF,0xF2,0xF3,0xF4,0xF5,0xF6,
|
|
0xF7,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF
|
|
};
|
|
|
|
BYTE CImageBase::ms_SectorNumber[NUM_SECTOR_ORDERS][0x10] =
|
|
{
|
|
{0x00,0x08,0x01,0x09,0x02,0x0A,0x03,0x0B, 0x04,0x0C,0x05,0x0D,0x06,0x0E,0x07,0x0F},
|
|
{0x00,0x07,0x0E,0x06,0x0D,0x05,0x0C,0x04, 0x0B,0x03,0x0A,0x02,0x09,0x01,0x08,0x0F},
|
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}
|
|
};
|
|
|
|
LPBYTE CImageBase::ms_pWorkBuffer = NULL;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CImageBase::ReadTrack(ImageInfo* pImageInfo, const int nTrack, LPBYTE pTrackBuffer, const UINT uTrackSize)
|
|
{
|
|
const long offset = pImageInfo->uOffset + nTrack * uTrackSize;
|
|
memcpy(pTrackBuffer, &pImageInfo->pImageBuffer[offset], uTrackSize);
|
|
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
bool CImageBase::WriteTrack(ImageInfo* pImageInfo, const int nTrack, LPBYTE pTrackBuffer, const UINT uTrackSize)
|
|
{
|
|
const long Offset = pImageInfo->uOffset + nTrack * uTrackSize;
|
|
memcpy(&pImageInfo->pImageBuffer[Offset], pTrackBuffer, uTrackSize);
|
|
|
|
if (pImageInfo->FileType == eFileNormal)
|
|
{
|
|
if (pImageInfo->hFile == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
SetFilePointer(pImageInfo->hFile, Offset, NULL, FILE_BEGIN);
|
|
|
|
DWORD dwBytesWritten;
|
|
BOOL bRes = WriteFile(pImageInfo->hFile, pTrackBuffer, uTrackSize, &dwBytesWritten, NULL);
|
|
_ASSERT(dwBytesWritten == uTrackSize);
|
|
if (!bRes || dwBytesWritten != uTrackSize)
|
|
return false;
|
|
}
|
|
else if (pImageInfo->FileType == eFileGZip)
|
|
{
|
|
// Write entire compressed image each time (dirty track change or dirty disk removal)
|
|
gzFile hGZFile = gzopen(pImageInfo->szFilename, "wb");
|
|
if (hGZFile == NULL)
|
|
return false;
|
|
|
|
int nLen = gzwrite(hGZFile, pImageInfo->pImageBuffer, pImageInfo->uImageSize);
|
|
if (nLen != pImageInfo->uImageSize)
|
|
return false;
|
|
|
|
int nRes = gzclose(hGZFile);
|
|
hGZFile = NULL;
|
|
if (nRes != Z_OK)
|
|
return false;
|
|
}
|
|
else if (pImageInfo->FileType == eFileZip)
|
|
{
|
|
// Write entire compressed image each time (dirty track change or dirty disk removal)
|
|
// NB. Only support Zip archives with a single file
|
|
zipFile hZipFile = zipOpen(pImageInfo->szFilename, APPEND_STATUS_CREATE);
|
|
if (hZipFile == NULL)
|
|
return false;
|
|
|
|
int nRes = zipOpenNewFileInZip(hZipFile, pImageInfo->szFilenameInZip, &pImageInfo->zipFileInfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_BEST_SPEED);
|
|
if (nRes != ZIP_OK)
|
|
return false;
|
|
|
|
nRes = zipWriteInFileInZip(hZipFile, pImageInfo->pImageBuffer, pImageInfo->uImageSize);
|
|
if (nRes != ZIP_OK)
|
|
return false;
|
|
|
|
nRes = zipCloseFileInZip(hZipFile);
|
|
if (nRes != ZIP_OK)
|
|
return false;
|
|
|
|
nRes = zipClose(hZipFile, NULL);
|
|
if (nRes != ZIP_OK)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
_ASSERT(0);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CImageBase::ReadBlock(ImageInfo* pImageInfo, const int nBlock, LPBYTE pBlockBuffer)
|
|
{
|
|
long Offset = pImageInfo->uOffset + nBlock * HD_BLOCK_SIZE;
|
|
|
|
if (pImageInfo->FileType == eFileNormal)
|
|
{
|
|
if (pImageInfo->hFile == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
SetFilePointer(pImageInfo->hFile, Offset, NULL, FILE_BEGIN);
|
|
|
|
DWORD dwBytesRead;
|
|
BOOL bRes = ReadFile(pImageInfo->hFile, pBlockBuffer, HD_BLOCK_SIZE, &dwBytesRead, NULL);
|
|
if (!bRes || dwBytesRead != HD_BLOCK_SIZE)
|
|
return false;
|
|
}
|
|
else if ((pImageInfo->FileType == eFileGZip) || (pImageInfo->FileType == eFileZip))
|
|
{
|
|
memcpy(pBlockBuffer, &pImageInfo->pImageBuffer[Offset], HD_BLOCK_SIZE);
|
|
}
|
|
else
|
|
{
|
|
_ASSERT(0);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
bool CImageBase::WriteBlock(ImageInfo* pImageInfo, const int nBlock, LPBYTE pBlockBuffer)
|
|
{
|
|
long Offset = pImageInfo->uOffset + nBlock * HD_BLOCK_SIZE;
|
|
const bool bGrowImageBuffer = (UINT)Offset+HD_BLOCK_SIZE > pImageInfo->uImageSize;
|
|
|
|
if (pImageInfo->FileType == eFileGZip || pImageInfo->FileType == eFileZip)
|
|
{
|
|
if (bGrowImageBuffer)
|
|
{
|
|
// Horribly inefficient! (Unzip to a normal file if you want better performance!)
|
|
const UINT uNewImageSize = Offset+HD_BLOCK_SIZE;
|
|
BYTE* pNewImageBuffer = new BYTE [uNewImageSize];
|
|
|
|
memcpy(pNewImageBuffer, pImageInfo->pImageBuffer, pImageInfo->uImageSize);
|
|
memset(&pNewImageBuffer[pImageInfo->uImageSize], 0, uNewImageSize-pImageInfo->uImageSize); // Should always be HD_BLOCK_SIZE (so this is redundant)
|
|
|
|
delete [] pImageInfo->pImageBuffer;
|
|
pImageInfo->pImageBuffer = pNewImageBuffer;
|
|
pImageInfo->uImageSize = uNewImageSize;
|
|
}
|
|
|
|
memcpy(&pImageInfo->pImageBuffer[Offset], pBlockBuffer, HD_BLOCK_SIZE);
|
|
}
|
|
|
|
if (pImageInfo->FileType == eFileNormal)
|
|
{
|
|
if (pImageInfo->hFile == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
SetFilePointer(pImageInfo->hFile, Offset, NULL, FILE_BEGIN);
|
|
|
|
DWORD dwBytesWritten;
|
|
BOOL bRes = WriteFile(pImageInfo->hFile, pBlockBuffer, HD_BLOCK_SIZE, &dwBytesWritten, NULL);
|
|
if (!bRes || dwBytesWritten != HD_BLOCK_SIZE)
|
|
return false;
|
|
|
|
if (bGrowImageBuffer)
|
|
pImageInfo->uImageSize += HD_BLOCK_SIZE;
|
|
}
|
|
else if (pImageInfo->FileType == eFileGZip)
|
|
{
|
|
// Write entire compressed image each time a block is written
|
|
gzFile hGZFile = gzopen(pImageInfo->szFilename, "wb");
|
|
if (hGZFile == NULL)
|
|
return false;
|
|
|
|
int nLen = gzwrite(hGZFile, pImageInfo->pImageBuffer, pImageInfo->uImageSize);
|
|
if (nLen != pImageInfo->uImageSize)
|
|
return false;
|
|
|
|
int nRes = gzclose(hGZFile);
|
|
hGZFile = NULL;
|
|
if (nRes != Z_OK)
|
|
return false;
|
|
}
|
|
else if (pImageInfo->FileType == eFileZip)
|
|
{
|
|
// Write entire compressed image each time a block is written
|
|
// NB. Only support Zip archives with a single file
|
|
zipFile hZipFile = zipOpen(pImageInfo->szFilename, APPEND_STATUS_CREATE);
|
|
if (hZipFile == NULL)
|
|
return false;
|
|
|
|
int nRes = zipOpenNewFileInZip(hZipFile, pImageInfo->szFilenameInZip, &pImageInfo->zipFileInfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_BEST_SPEED);
|
|
if (nRes != ZIP_OK)
|
|
return false;
|
|
|
|
nRes = zipWriteInFileInZip(hZipFile, pImageInfo->pImageBuffer, pImageInfo->uImageSize);
|
|
if (nRes != ZIP_OK)
|
|
return false;
|
|
|
|
nRes = zipCloseFileInZip(hZipFile);
|
|
if (nRes != ZIP_OK)
|
|
return false;
|
|
|
|
nRes = zipClose(hZipFile, NULL);
|
|
if (nRes != ZIP_OK)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
_ASSERT(0);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
LPBYTE CImageBase::Code62(int sector)
|
|
{
|
|
// CONVERT THE 256 8-BIT BYTES INTO 342 6-BIT BYTES, WHICH WE STORE
|
|
// STARTING AT 4K INTO THE WORK BUFFER.
|
|
{
|
|
LPBYTE sectorbase = ms_pWorkBuffer+(sector << 8);
|
|
LPBYTE resultptr = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE;
|
|
BYTE offset = 0xAC;
|
|
while (offset != 0x02)
|
|
{
|
|
BYTE value = 0;
|
|
#define ADDVALUE(a) value = (value << 2) | \
|
|
(((a) & 0x01) << 1) | \
|
|
(((a) & 0x02) >> 1)
|
|
ADDVALUE(*(sectorbase+offset)); offset -= 0x56;
|
|
ADDVALUE(*(sectorbase+offset)); offset -= 0x56;
|
|
ADDVALUE(*(sectorbase+offset)); offset -= 0x53;
|
|
#undef ADDVALUE
|
|
*(resultptr++) = value << 2;
|
|
}
|
|
*(resultptr-2) &= 0x3F;
|
|
*(resultptr-1) &= 0x3F;
|
|
int loop = 0;
|
|
while (loop < 0x100)
|
|
*(resultptr++) = *(sectorbase+(loop++));
|
|
}
|
|
|
|
// EXCLUSIVE-OR THE ENTIRE DATA BLOCK WITH ITSELF OFFSET BY ONE BYTE,
|
|
// CREATING A 343RD BYTE WHICH IS USED AS A CHECKSUM. STORE THE NEW
|
|
// BLOCK OF 343 BYTES STARTING AT 5K INTO THE WORK BUFFER.
|
|
{
|
|
BYTE savedval = 0;
|
|
LPBYTE sourceptr = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE;
|
|
LPBYTE resultptr = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE+0x400;
|
|
int loop = 342;
|
|
while (loop--)
|
|
{
|
|
*(resultptr++) = savedval ^ *sourceptr;
|
|
savedval = *(sourceptr++);
|
|
}
|
|
*resultptr = savedval;
|
|
}
|
|
|
|
// USING A LOOKUP TABLE, CONVERT THE 6-BIT BYTES INTO DISK BYTES. A VALID
|
|
// DISK BYTE IS A BYTE THAT HAS THE HIGH BIT SET, AT LEAST TWO ADJACENT
|
|
// BITS SET (EXCLUDING THE HIGH BIT), AND AT MOST ONE PAIR OF CONSECUTIVE
|
|
// ZERO BITS. THE CONVERTED BLOCK OF 343 BYTES IS STORED STARTING AT 4K
|
|
// INTO THE WORK BUFFER.
|
|
{
|
|
LPBYTE sourceptr = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE+0x400;
|
|
LPBYTE resultptr = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE;
|
|
int loop = 343;
|
|
while (loop--)
|
|
*(resultptr++) = ms_DiskByte[(*(sourceptr++)) >> 2];
|
|
}
|
|
|
|
return ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CImageBase::Decode62(LPBYTE imageptr)
|
|
{
|
|
// IF WE HAVEN'T ALREADY DONE SO, GENERATE A TABLE FOR CONVERTING
|
|
// DISK BYTES BACK INTO 6-BIT BYTES
|
|
static BOOL tablegenerated = 0;
|
|
static BYTE sixbitbyte[0x80];
|
|
if (!tablegenerated)
|
|
{
|
|
ZeroMemory(sixbitbyte,0x80);
|
|
int loop = 0;
|
|
while (loop < 0x40) {
|
|
sixbitbyte[ms_DiskByte[loop]-0x80] = loop << 2;
|
|
loop++;
|
|
}
|
|
tablegenerated = 1;
|
|
}
|
|
|
|
// USING OUR TABLE, CONVERT THE DISK BYTES BACK INTO 6-BIT BYTES
|
|
{
|
|
LPBYTE sourceptr = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE;
|
|
LPBYTE resultptr = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE+0x400;
|
|
int loop = 343;
|
|
while (loop--)
|
|
*(resultptr++) = sixbitbyte[*(sourceptr++) & 0x7F];
|
|
}
|
|
|
|
// EXCLUSIVE-OR THE ENTIRE DATA BLOCK WITH ITSELF OFFSET BY ONE BYTE
|
|
// TO UNDO THE EFFECTS OF THE CHECKSUMMING PROCESS
|
|
{
|
|
BYTE savedval = 0;
|
|
LPBYTE sourceptr = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE+0x400;
|
|
LPBYTE resultptr = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE;
|
|
int loop = 342;
|
|
while (loop--)
|
|
{
|
|
*resultptr = savedval ^ *(sourceptr++);
|
|
savedval = *(resultptr++);
|
|
}
|
|
}
|
|
|
|
// CONVERT THE 342 6-BIT BYTES INTO 256 8-BIT BYTES
|
|
{
|
|
LPBYTE lowbitsptr = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE;
|
|
LPBYTE sectorbase = ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE+0x56;
|
|
BYTE offset = 0xAC;
|
|
while (offset != 0x02)
|
|
{
|
|
if (offset >= 0xAC)
|
|
{
|
|
*(imageptr+offset) = (*(sectorbase+offset) & 0xFC)
|
|
| (((*lowbitsptr) & 0x80) >> 7)
|
|
| (((*lowbitsptr) & 0x40) >> 5);
|
|
}
|
|
|
|
offset -= 0x56;
|
|
*(imageptr+offset) = (*(sectorbase+offset) & 0xFC)
|
|
| (((*lowbitsptr) & 0x20) >> 5)
|
|
| (((*lowbitsptr) & 0x10) >> 3);
|
|
|
|
offset -= 0x56;
|
|
*(imageptr+offset) = (*(sectorbase+offset) & 0xFC)
|
|
| (((*lowbitsptr) & 0x08) >> 3)
|
|
| (((*lowbitsptr) & 0x04) >> 1);
|
|
|
|
offset -= 0x53;
|
|
lowbitsptr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CImageBase::DenibblizeTrack(LPBYTE trackimage, SectorOrder_e SectorOrder, int nibbles)
|
|
{
|
|
ZeroMemory(ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
|
|
|
|
// SEARCH THROUGH THE TRACK IMAGE FOR EACH SECTOR. FOR EVERY SECTOR
|
|
// WE FIND, COPY THE NIBBLIZED DATA FOR THAT SECTOR INTO THE WORK
|
|
// BUFFER AT OFFSET 4K. THEN CALL DECODE62() TO DENIBBLIZE THE DATA
|
|
// IN THE BUFFER AND WRITE IT INTO THE FIRST PART OF THE WORK BUFFER
|
|
// OFFSET BY THE SECTOR NUMBER.
|
|
|
|
#ifdef _DEBUG
|
|
UINT16 bmWrittenSectorAddrFields = 0x0000;
|
|
BYTE uWriteDataFieldPrologueCount = 0;
|
|
#endif
|
|
|
|
int offset = 0;
|
|
int partsleft = NUM_SECTORS*2+1; // TC: 32+1 prologues - need 1 extra if trackimage starts between Addr Field & Data Field
|
|
int sector = -1;
|
|
while (partsleft--)
|
|
{
|
|
BYTE byteval[3] = {0,0,0};
|
|
int bytenum = 0;
|
|
int loop = nibbles;
|
|
while ((loop--) && (bytenum < 3))
|
|
{
|
|
if (bytenum)
|
|
byteval[bytenum++] = *(trackimage+offset++);
|
|
else if (*(trackimage+offset++) == 0xD5)
|
|
bytenum = 1;
|
|
|
|
if (offset >= nibbles)
|
|
offset = 0;
|
|
}
|
|
|
|
if ((bytenum == 3) && (byteval[1] == 0xAA))
|
|
{
|
|
int loop = 0;
|
|
int tempoffset = offset;
|
|
while (loop < 384) // TODO-TC: Why 384? Only need 343 for Decode62()
|
|
{
|
|
*(ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE+loop++) = *(trackimage+tempoffset++);
|
|
if (tempoffset >= nibbles)
|
|
tempoffset = 0;
|
|
}
|
|
|
|
if (byteval[2] == 0x96)
|
|
{
|
|
sector = ((*(ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE+4) & 0x55) << 1)
|
|
| (*(ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE+5) & 0x55);
|
|
|
|
#ifdef _DEBUG
|
|
_ASSERT( sector < NUM_SECTORS );
|
|
if (partsleft != 0)
|
|
{
|
|
_ASSERT( (bmWrittenSectorAddrFields & (1<<sector)) == 0 );
|
|
bmWrittenSectorAddrFields |= (1<<sector);
|
|
}
|
|
#endif
|
|
}
|
|
else if (byteval[2] == 0xAD)
|
|
{
|
|
if (sector >= 0 && sector < NUM_SECTORS)
|
|
{
|
|
#ifdef _DEBUG
|
|
uWriteDataFieldPrologueCount++;
|
|
_ASSERT(uWriteDataFieldPrologueCount <= NUM_SECTORS);
|
|
#endif
|
|
Decode62(ms_pWorkBuffer+(ms_SectorNumber[SectorOrder][sector] << 8));
|
|
}
|
|
sector = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
DWORD CImageBase::NibblizeTrack(LPBYTE trackimagebuffer, SectorOrder_e SectorOrder, int track)
|
|
{
|
|
ZeroMemory(ms_pWorkBuffer+TRACK_DENIBBLIZED_SIZE, TRACK_DENIBBLIZED_SIZE);
|
|
LPBYTE imageptr = trackimagebuffer;
|
|
BYTE sector = 0;
|
|
|
|
// WRITE GAP ONE, WHICH CONTAINS 48 SELF-SYNC BYTES
|
|
int loop;
|
|
for (loop = 0; loop < 48; loop++)
|
|
*(imageptr++) = 0xFF;
|
|
|
|
while (sector < 16)
|
|
{
|
|
// WRITE THE ADDRESS FIELD, WHICH CONTAINS:
|
|
// - PROLOGUE (D5AA96)
|
|
// - VOLUME NUMBER ("4 AND 4" ENCODED)
|
|
// - TRACK NUMBER ("4 AND 4" ENCODED)
|
|
// - SECTOR NUMBER ("4 AND 4" ENCODED)
|
|
// - CHECKSUM ("4 AND 4" ENCODED)
|
|
// - EPILOGUE (DEAAEB)
|
|
*(imageptr++) = 0xD5;
|
|
*(imageptr++) = 0xAA;
|
|
*(imageptr++) = 0x96;
|
|
#define CODE44A(a) ((((a) >> 1) & 0x55) | 0xAA)
|
|
#define CODE44B(a) (((a) & 0x55) | 0xAA)
|
|
*(imageptr++) = CODE44A(m_uVolumeNumber);
|
|
*(imageptr++) = CODE44B(m_uVolumeNumber);
|
|
*(imageptr++) = CODE44A((BYTE)track);
|
|
*(imageptr++) = CODE44B((BYTE)track);
|
|
*(imageptr++) = CODE44A(sector);
|
|
*(imageptr++) = CODE44B(sector);
|
|
*(imageptr++) = CODE44A(m_uVolumeNumber ^ ((BYTE)track) ^ sector);
|
|
*(imageptr++) = CODE44B(m_uVolumeNumber ^ ((BYTE)track) ^ sector);
|
|
#undef CODE44A
|
|
#undef CODE44B
|
|
*(imageptr++) = 0xDE;
|
|
*(imageptr++) = 0xAA;
|
|
*(imageptr++) = 0xEB;
|
|
|
|
// WRITE GAP TWO, WHICH CONTAINS SIX SELF-SYNC BYTES
|
|
for (loop = 0; loop < 6; loop++)
|
|
*(imageptr++) = 0xFF;
|
|
|
|
// WRITE THE DATA FIELD, WHICH CONTAINS:
|
|
// - PROLOGUE (D5AAAD)
|
|
// - 343 6-BIT BYTES OF NIBBLIZED DATA, INCLUDING A 6-BIT CHECKSUM
|
|
// - EPILOGUE (DEAAEB)
|
|
*(imageptr++) = 0xD5;
|
|
*(imageptr++) = 0xAA;
|
|
*(imageptr++) = 0xAD;
|
|
CopyMemory(imageptr, Code62(ms_SectorNumber[SectorOrder][sector]), 343);
|
|
imageptr += 343;
|
|
*(imageptr++) = 0xDE;
|
|
*(imageptr++) = 0xAA;
|
|
*(imageptr++) = 0xEB;
|
|
|
|
// WRITE GAP THREE, WHICH CONTAINS 27 SELF-SYNC BYTES
|
|
for (loop = 0; loop < 27; loop++)
|
|
*(imageptr++) = 0xFF;
|
|
|
|
sector++;
|
|
}
|
|
|
|
return imageptr-trackimagebuffer;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CImageBase::SkewTrack(const int nTrack, const int nNumNibbles, const LPBYTE pTrackImageBuffer)
|
|
{
|
|
int nSkewBytes = (nTrack*768) % nNumNibbles;
|
|
CopyMemory(ms_pWorkBuffer, pTrackImageBuffer, nNumNibbles);
|
|
CopyMemory(pTrackImageBuffer, ms_pWorkBuffer+nSkewBytes, nNumNibbles-nSkewBytes);
|
|
CopyMemory(pTrackImageBuffer+nNumNibbles-nSkewBytes, ms_pWorkBuffer, nSkewBytes);
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
bool CImageBase::IsValidImageSize(const DWORD uImageSize)
|
|
{
|
|
m_uNumTracksInImage = 0;
|
|
|
|
if ((TRACKS_MAX>TRACKS_STANDARD) && (uImageSize > TRACKS_MAX*TRACK_DENIBBLIZED_SIZE))
|
|
return false; // >160KB
|
|
|
|
//
|
|
|
|
bool bValidSize = false;
|
|
|
|
if (uImageSize >= (TRACKS_STANDARD+1)*TRACK_DENIBBLIZED_SIZE)
|
|
{
|
|
// Is uImageSize == 140KB + n*4K? (where n>=1)
|
|
bValidSize = (((uImageSize - TRACKS_STANDARD*TRACK_DENIBBLIZED_SIZE) % TRACK_DENIBBLIZED_SIZE) == 0);
|
|
}
|
|
else
|
|
{
|
|
// TODO: Applewin.chm mentions images of size 143,616 bytes ("Disk Image Formats")
|
|
bValidSize = ( ((uImageSize >= 143105) && (uImageSize <= 143364)) ||
|
|
(uImageSize == 143403) ||
|
|
(uImageSize == 143488) );
|
|
}
|
|
|
|
if (bValidSize)
|
|
m_uNumTracksInImage = uImageSize / TRACK_DENIBBLIZED_SIZE;
|
|
|
|
return bValidSize;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// DOS ORDER (DO) FORMAT IMPLEMENTATION
|
|
class CDoImage : public CImageBase
|
|
{
|
|
public:
|
|
CDoImage(void) {}
|
|
virtual ~CDoImage(void) {}
|
|
|
|
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
|
|
{
|
|
if (!IsValidImageSize(dwImageSize))
|
|
return eMismatch;
|
|
|
|
// CHECK FOR A DOS ORDER IMAGE OF A DOS DISKETTE
|
|
{
|
|
int loop = 0;
|
|
bool bMismatch = false;
|
|
while ((loop++ < 15) && !bMismatch)
|
|
{
|
|
if (*(pImage+0x11002+(loop << 8)) != loop-1)
|
|
bMismatch = true;
|
|
}
|
|
if (!bMismatch)
|
|
return eMatch;
|
|
}
|
|
|
|
// CHECK FOR A DOS ORDER IMAGE OF A PRODOS DISKETTE
|
|
{
|
|
int loop = 1;
|
|
bool bMismatch = false;
|
|
while ((loop++ < 5) && !bMismatch)
|
|
{
|
|
if ((*(LPWORD)(pImage+(loop << 9)+0x100) != ((loop == 5) ? 0 : 6-loop)) ||
|
|
(*(LPWORD)(pImage+(loop << 9)+0x102) != ((loop == 2) ? 0 : 8-loop)))
|
|
bMismatch = true;
|
|
}
|
|
if (!bMismatch)
|
|
return eMatch;
|
|
}
|
|
|
|
return ePossibleMatch;
|
|
}
|
|
|
|
virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
|
|
{
|
|
const UINT track = PhaseToTrack(phase);
|
|
ReadTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
|
|
*pNibbles = NibblizeTrack(pTrackImageBuffer, eDOSOrder, track);
|
|
if (!enhanceDisk)
|
|
SkewTrack(track, *pNibbles, pTrackImageBuffer);
|
|
}
|
|
|
|
virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
|
|
{
|
|
const UINT track = PhaseToTrack(phase);
|
|
DenibblizeTrack(pTrackImageBuffer, eDOSOrder, nNibbles);
|
|
WriteTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
|
|
}
|
|
|
|
virtual bool AllowCreate(void) { return true; }
|
|
virtual UINT GetImageSizeForCreate(void) { return TRACK_DENIBBLIZED_SIZE * TRACKS_STANDARD; }
|
|
|
|
virtual eImageType GetType(void) { return eImageDO; }
|
|
virtual const char* GetCreateExtensions(void) { return ".do;.dsk"; }
|
|
virtual const char* GetRejectExtensions(void) { return ".nib;.iie;.po;.prg"; }
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
// PRODOS ORDER (PO) FORMAT IMPLEMENTATION
|
|
class CPoImage : public CImageBase
|
|
{
|
|
public:
|
|
CPoImage(void) {}
|
|
virtual ~CPoImage(void) {}
|
|
|
|
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
|
|
{
|
|
if (!IsValidImageSize(dwImageSize))
|
|
return eMismatch;
|
|
|
|
// CHECK FOR A PRODOS ORDER IMAGE OF A DOS DISKETTE
|
|
{
|
|
int loop = 4;
|
|
bool bMismatch = false;
|
|
while ((loop++ < 13) && !bMismatch)
|
|
{
|
|
if (*(pImage+0x11002+(loop << 8)) != 14-loop)
|
|
bMismatch = true;
|
|
}
|
|
if (!bMismatch)
|
|
return eMatch;
|
|
}
|
|
|
|
// CHECK FOR A PRODOS ORDER IMAGE OF A PRODOS DISKETTE
|
|
{
|
|
int loop = 1;
|
|
bool bMismatch = false;
|
|
while ((loop++ < 5) && !bMismatch)
|
|
{
|
|
if ((*(LPWORD)(pImage+(loop << 9) ) != ((loop == 2) ? 0 : loop-1)) ||
|
|
(*(LPWORD)(pImage+(loop << 9)+2) != ((loop == 5) ? 0 : loop+1)))
|
|
bMismatch = true;
|
|
}
|
|
if (!bMismatch)
|
|
return eMatch;
|
|
}
|
|
|
|
return ePossibleMatch;
|
|
}
|
|
|
|
virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
|
|
{
|
|
const UINT track = PhaseToTrack(phase);
|
|
ReadTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
|
|
*pNibbles = NibblizeTrack(pTrackImageBuffer, eProDOSOrder, track);
|
|
if (!enhanceDisk)
|
|
SkewTrack(track, *pNibbles, pTrackImageBuffer);
|
|
}
|
|
|
|
virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
|
|
{
|
|
const UINT track = PhaseToTrack(phase);
|
|
DenibblizeTrack(pTrackImageBuffer, eProDOSOrder, nNibbles);
|
|
WriteTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
|
|
}
|
|
|
|
virtual eImageType GetType(void) { return eImagePO; }
|
|
virtual const char* GetCreateExtensions(void) { return ".po"; }
|
|
virtual const char* GetRejectExtensions(void) { return ".do;.iie;.nib;.prg;.woz"; }
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
// NIBBLIZED 6656-NIBBLE (NIB) FORMAT IMPLEMENTATION
|
|
class CNib1Image : public CImageBase
|
|
{
|
|
public:
|
|
CNib1Image(void) {}
|
|
virtual ~CNib1Image(void) {}
|
|
|
|
static const UINT NIB1_TRACK_SIZE = NIBBLES_PER_TRACK_NIB;
|
|
|
|
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
|
|
{
|
|
if (dwImageSize < NIB1_TRACK_SIZE*TRACKS_STANDARD || dwImageSize % NIB1_TRACK_SIZE != 0 || dwImageSize > NIB1_TRACK_SIZE*TRACKS_MAX)
|
|
return eMismatch;
|
|
|
|
m_uNumTracksInImage = dwImageSize / NIB1_TRACK_SIZE;
|
|
return eMatch;
|
|
}
|
|
|
|
virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
|
|
{
|
|
const UINT track = PhaseToTrack(phase);
|
|
ReadTrack(pImageInfo, track, pTrackImageBuffer, NIB1_TRACK_SIZE);
|
|
*pNibbles = NIB1_TRACK_SIZE;
|
|
}
|
|
|
|
virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
|
|
{
|
|
_ASSERT(nNibbles == NIB1_TRACK_SIZE); // Must be true - as nNibbles gets init'd by ImageReadTrace()
|
|
const UINT track = PhaseToTrack(phase);
|
|
WriteTrack(pImageInfo, track, pTrackImageBuffer, nNibbles);
|
|
}
|
|
|
|
virtual bool AllowCreate(void) { return true; }
|
|
virtual UINT GetImageSizeForCreate(void) { return NIB1_TRACK_SIZE * TRACKS_STANDARD; }
|
|
|
|
virtual eImageType GetType(void) { return eImageNIB1; }
|
|
virtual const char* GetCreateExtensions(void) { return ".nib"; }
|
|
virtual const char* GetRejectExtensions(void) { return ".do;.iie;.po;.prg;.woz"; }
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
// NIBBLIZED 6384-NIBBLE (NB2) FORMAT IMPLEMENTATION
|
|
class CNib2Image : public CImageBase
|
|
{
|
|
public:
|
|
CNib2Image(void) {}
|
|
virtual ~CNib2Image(void) {}
|
|
|
|
static const UINT NIB2_TRACK_SIZE = 6384;
|
|
|
|
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
|
|
{
|
|
if (dwImageSize != NIB2_TRACK_SIZE*TRACKS_STANDARD)
|
|
return eMismatch;
|
|
|
|
m_uNumTracksInImage = TRACKS_STANDARD;
|
|
return eMatch;
|
|
}
|
|
|
|
virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
|
|
{
|
|
const UINT track = PhaseToTrack(phase);
|
|
ReadTrack(pImageInfo, track, pTrackImageBuffer, NIB2_TRACK_SIZE);
|
|
*pNibbles = NIB2_TRACK_SIZE;
|
|
}
|
|
|
|
virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
|
|
{
|
|
_ASSERT(nNibbles == NIB2_TRACK_SIZE); // Must be true - as nNibbles gets init'd by ImageReadTrace()
|
|
const UINT track = PhaseToTrack(phase);
|
|
WriteTrack(pImageInfo, track, pTrackImageBuffer, nNibbles);
|
|
}
|
|
|
|
virtual eImageType GetType(void) { return eImageNIB2; }
|
|
virtual const char* GetCreateExtensions(void) { return ".nb2"; }
|
|
virtual const char* GetRejectExtensions(void) { return ".do;.iie;.po;.prg;.woz;.2mg;.2img"; }
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
// HDV image
|
|
class CHDVImage : public CImageBase
|
|
{
|
|
public:
|
|
CHDVImage(void) {}
|
|
virtual ~CHDVImage(void) {}
|
|
|
|
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
|
|
{
|
|
m_uNumTracksInImage = dwImageSize / TRACK_DENIBBLIZED_SIZE; // Set to non-zero
|
|
|
|
// An HDV image can be any size (so if Ext == ".hdv" then accept any size)
|
|
if (*pszExt && !_tcscmp(pszExt, ".hdv"))
|
|
return eMatch;
|
|
|
|
if (dwImageSize < UNIDISK35_800K_SIZE)
|
|
return eMismatch;
|
|
|
|
return eMatch;
|
|
}
|
|
|
|
virtual bool Read(ImageInfo* pImageInfo, UINT nBlock, LPBYTE pBlockBuffer)
|
|
{
|
|
return ReadBlock(pImageInfo, nBlock, pBlockBuffer);
|
|
}
|
|
|
|
virtual bool Write(ImageInfo* pImageInfo, UINT nBlock, LPBYTE pBlockBuffer)
|
|
{
|
|
if (pImageInfo->bWriteProtected)
|
|
return false;
|
|
|
|
return WriteBlock(pImageInfo, nBlock, pBlockBuffer);
|
|
}
|
|
|
|
virtual eImageType GetType(void) { return eImageHDV; }
|
|
virtual const char* GetCreateExtensions(void) { return ".hdv"; }
|
|
virtual const char* GetRejectExtensions(void) { return ".do;.iie;.prg"; }
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
// SIMSYSTEM IIE (IIE) FORMAT IMPLEMENTATION
|
|
class CIIeImage : public CImageBase
|
|
{
|
|
public:
|
|
CIIeImage(void) : m_pHeader(NULL) {}
|
|
virtual ~CIIeImage(void) { delete [] m_pHeader; }
|
|
|
|
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
|
|
{
|
|
if (strncmp((const char *)pImage, "SIMSYSTEM_IIE", 13) || (*(pImage+13) > 3))
|
|
return eMismatch;
|
|
|
|
m_uNumTracksInImage = TRACKS_STANDARD; // Assume default # tracks
|
|
return eMatch;
|
|
}
|
|
|
|
virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
|
|
{
|
|
UINT track = PhaseToTrack(phase);
|
|
|
|
// IF WE HAVEN'T ALREADY DONE SO, READ THE IMAGE FILE HEADER
|
|
if (!m_pHeader)
|
|
{
|
|
m_pHeader = (LPBYTE) VirtualAlloc(NULL, 88, MEM_COMMIT, PAGE_READWRITE);
|
|
if (!m_pHeader)
|
|
{
|
|
*pNibbles = 0;
|
|
return;
|
|
}
|
|
ZeroMemory(m_pHeader, 88);
|
|
DWORD dwBytesRead;
|
|
SetFilePointer(pImageInfo->hFile, 0, NULL,FILE_BEGIN);
|
|
ReadFile(pImageInfo->hFile, m_pHeader, 88, &dwBytesRead, NULL);
|
|
}
|
|
|
|
// IF THIS IMAGE CONTAINS USER DATA, READ THE TRACK AND NIBBLIZE IT
|
|
if (*(m_pHeader+13) <= 2)
|
|
{
|
|
ConvertSectorOrder(m_pHeader+14);
|
|
SetFilePointer(pImageInfo->hFile, track*TRACK_DENIBBLIZED_SIZE+30, NULL, FILE_BEGIN);
|
|
ZeroMemory(ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE);
|
|
DWORD bytesread;
|
|
ReadFile(pImageInfo->hFile, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE, &bytesread, NULL);
|
|
*pNibbles = NibblizeTrack(pTrackImageBuffer, eSIMSYSTEMOrder, track);
|
|
}
|
|
// OTHERWISE, IF THIS IMAGE CONTAINS NIBBLE INFORMATION, READ IT DIRECTLY INTO THE TRACK BUFFER
|
|
else
|
|
{
|
|
*pNibbles = *(LPWORD)(m_pHeader+track*2+14);
|
|
LONG Offset = 88;
|
|
while (track--)
|
|
Offset += *(LPWORD)(m_pHeader+track*2+14);
|
|
SetFilePointer(pImageInfo->hFile, Offset, NULL,FILE_BEGIN);
|
|
ZeroMemory(pTrackImageBuffer, *pNibbles);
|
|
DWORD dwBytesRead;
|
|
ReadFile(pImageInfo->hFile, pTrackImageBuffer, *pNibbles, &dwBytesRead, NULL);
|
|
}
|
|
}
|
|
|
|
virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
|
|
{
|
|
// note: unimplemented
|
|
}
|
|
|
|
virtual eImageType GetType(void) { return eImageIIE; }
|
|
virtual const char* GetCreateExtensions(void) { return ".iie"; }
|
|
virtual const char* GetRejectExtensions(void) { return ".do.;.nib;.po;.prg;.woz;.2mg;.2img"; }
|
|
|
|
private:
|
|
void ConvertSectorOrder(LPBYTE sourceorder)
|
|
{
|
|
int loop = 16;
|
|
while (loop--)
|
|
{
|
|
BYTE found = 0xFF;
|
|
int loop2 = 16;
|
|
while (loop2-- && (found == 0xFF))
|
|
{
|
|
if (*(sourceorder+loop2) == loop)
|
|
found = loop2;
|
|
}
|
|
|
|
if (found == 0xFF)
|
|
found = 0;
|
|
|
|
ms_SectorNumber[2][loop] = found;
|
|
}
|
|
}
|
|
|
|
private:
|
|
LPBYTE m_pHeader;
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
// RAW PROGRAM IMAGE (APL) FORMAT IMPLEMENTATION
|
|
class CAplImage : public CImageBase
|
|
{
|
|
public:
|
|
CAplImage(void) {}
|
|
virtual ~CAplImage(void) {}
|
|
|
|
virtual bool Boot(ImageInfo* ptr)
|
|
{
|
|
SetFilePointer(ptr->hFile, 0, NULL, FILE_BEGIN);
|
|
WORD address = 0;
|
|
WORD length = 0;
|
|
DWORD bytesread;
|
|
ReadFile(ptr->hFile, &address, sizeof(WORD), &bytesread, NULL);
|
|
ReadFile(ptr->hFile, &length , sizeof(WORD), &bytesread, NULL);
|
|
if ((((WORD)(address+length)) <= address) ||
|
|
(address >= 0xC000) ||
|
|
(address+length-1 >= 0xC000))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ReadFile(ptr->hFile, mem+address, length, &bytesread, NULL);
|
|
int loop = 192;
|
|
while (loop--)
|
|
*(memdirty+loop) = 0xFF;
|
|
|
|
regs.pc = address;
|
|
return true;
|
|
}
|
|
|
|
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
|
|
{
|
|
DWORD dwLength = *(LPWORD)(pImage+2);
|
|
bool bRes = (((dwLength+4) == dwImageSize) ||
|
|
((dwLength+4+((256-((dwLength+4) & 255)) & 255)) == dwImageSize));
|
|
|
|
return !bRes ? eMismatch : ePossibleMatch;
|
|
}
|
|
|
|
virtual bool AllowBoot(void) { return true; }
|
|
virtual bool AllowRW(void) { return false; }
|
|
|
|
virtual eImageType GetType(void) { return eImageAPL; }
|
|
virtual const char* GetCreateExtensions(void) { return ".apl"; }
|
|
virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.iie;.nib;.po;.woz;.2mg;.2img"; }
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
class CPrgImage : public CImageBase
|
|
{
|
|
public:
|
|
CPrgImage(void) {}
|
|
virtual ~CPrgImage(void) {}
|
|
|
|
virtual bool Boot(ImageInfo* pImageInfo)
|
|
{
|
|
SetFilePointer(pImageInfo->hFile, 5, NULL, FILE_BEGIN);
|
|
|
|
WORD address = 0;
|
|
WORD length = 0;
|
|
DWORD bytesread;
|
|
|
|
ReadFile(pImageInfo->hFile, &address, sizeof(WORD), &bytesread, NULL);
|
|
ReadFile(pImageInfo->hFile, &length , sizeof(WORD), &bytesread, NULL);
|
|
|
|
length <<= 1;
|
|
if ((((WORD)(address+length)) <= address) ||
|
|
(address >= 0xC000) ||
|
|
(address+length-1 >= 0xC000))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SetFilePointer(pImageInfo->hFile,128,NULL,FILE_BEGIN);
|
|
ReadFile(pImageInfo->hFile, mem+address, length, &bytesread, NULL);
|
|
|
|
int loop = 192;
|
|
while (loop--)
|
|
*(memdirty+loop) = 0xFF;
|
|
|
|
regs.pc = address;
|
|
return true;
|
|
}
|
|
|
|
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
|
|
{
|
|
return (*(LPDWORD)pImage == 0x214C470A) ? eMatch : eMismatch; // "!LG\x0A"
|
|
}
|
|
|
|
virtual bool AllowBoot(void) { return true; }
|
|
virtual bool AllowRW(void) { return false; }
|
|
|
|
virtual eImageType GetType(void) { return eImagePRG; }
|
|
virtual const char* GetCreateExtensions(void) { return ".prg"; }
|
|
virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.iie;.nib;.po;.woz;.2mg;.2img"; }
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
class CWOZEmptyTrack
|
|
{
|
|
public:
|
|
CWOZEmptyTrack(void)
|
|
{
|
|
m_pWOZEmptyTrack = new BYTE[CWOZHelper::EMPTY_TRACK_SIZE];
|
|
|
|
srand(1); // Use a fixed seed for determinism
|
|
for (UINT i = 0; i < CWOZHelper::EMPTY_TRACK_SIZE; i++)
|
|
{
|
|
BYTE n = 0;
|
|
for (UINT j = 0; j < 8; j++)
|
|
{
|
|
if (rand() < ((RAND_MAX * 3) / 10)) // ~30% of buffer are 1 bits
|
|
n |= 1 << j;
|
|
}
|
|
m_pWOZEmptyTrack[i] = n;
|
|
}
|
|
}
|
|
virtual ~CWOZEmptyTrack(void) { delete m_pWOZEmptyTrack; }
|
|
|
|
void ReadEmptyTrack(LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount)
|
|
{
|
|
memcpy(pTrackImageBuffer, m_pWOZEmptyTrack, CWOZHelper::EMPTY_TRACK_SIZE);
|
|
*pNibbles = CWOZHelper::EMPTY_TRACK_SIZE;
|
|
*pBitCount = CWOZHelper::EMPTY_TRACK_SIZE * 8;
|
|
return;
|
|
}
|
|
|
|
private:
|
|
BYTE* m_pWOZEmptyTrack;
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
class CWOZ1Image : public CImageBase, private CWOZEmptyTrack
|
|
{
|
|
public:
|
|
CWOZ1Image(void) {}
|
|
virtual ~CWOZ1Image(void) {}
|
|
|
|
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
|
|
{
|
|
CWOZHelper::WOZHeader* pWozHdr = (CWOZHelper::WOZHeader*) pImage;
|
|
|
|
if (pWozHdr->id1 != CWOZHelper::ID1_WOZ1 || pWozHdr->id2 != CWOZHelper::ID2)
|
|
return eMismatch;
|
|
|
|
if (pWozHdr->crc32)
|
|
{
|
|
// TODO: check crc
|
|
}
|
|
|
|
m_uNumTracksInImage = CWOZHelper::MAX_TRACKS_5_25;
|
|
return eMatch;
|
|
}
|
|
|
|
virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
|
|
{
|
|
BYTE*& pTrackMap = pImageInfo->pTrackMap;
|
|
|
|
const int trackFromTMAP = pTrackMap[(UINT)(phase * 2)];
|
|
if (trackFromTMAP == 0xFF)
|
|
return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount);
|
|
|
|
ReadTrack(pImageInfo, trackFromTMAP, pTrackImageBuffer, CWOZHelper::WOZ1_TRACK_SIZE);
|
|
CWOZHelper::TRKv1* pTRK = (CWOZHelper::TRKv1*) &pTrackImageBuffer[CWOZHelper::WOZ1_TRK_OFFSET];
|
|
*pNibbles = pTRK->bytesUsed;
|
|
*pBitCount = pTRK->bitCount;
|
|
}
|
|
|
|
virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
|
|
{
|
|
// TODO
|
|
_ASSERT(0);
|
|
}
|
|
|
|
// TODO: Uncomment and fix-up if we want to allow .woz image creation (eg. for INIT or FORMAT)
|
|
// virtual bool AllowCreate(void) { return true; }
|
|
// virtual UINT GetImageSizeForCreate(void) { return 0; }//TODO
|
|
|
|
virtual eImageType GetType(void) { return eImageWOZ1; }
|
|
virtual const char* GetCreateExtensions(void) { return ".woz"; }
|
|
virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.nib;.iie;.po;.prg"; }
|
|
};
|
|
|
|
//-------------------------------------
|
|
|
|
class CWOZ2Image : public CImageBase, private CWOZEmptyTrack
|
|
{
|
|
public:
|
|
CWOZ2Image(void) {}
|
|
virtual ~CWOZ2Image(void) {}
|
|
|
|
virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt)
|
|
{
|
|
CWOZHelper::WOZHeader* pWozHdr = (CWOZHelper::WOZHeader*) pImage;
|
|
|
|
if (pWozHdr->id1 != CWOZHelper::ID1_WOZ2 || pWozHdr->id2 != CWOZHelper::ID2)
|
|
return eMismatch;
|
|
|
|
if (pWozHdr->crc32)
|
|
{
|
|
// TODO: check crc
|
|
}
|
|
|
|
m_uNumTracksInImage = CWOZHelper::MAX_TRACKS_5_25;
|
|
return eMatch;
|
|
}
|
|
|
|
virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk)
|
|
{
|
|
BYTE*& pTrackMap = pImageInfo->pTrackMap;
|
|
|
|
const int trackFromTMAP = pTrackMap[(UINT)(phase * 2)];
|
|
if (trackFromTMAP == 0xFF)
|
|
return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount);
|
|
|
|
CWOZHelper::TRKv2* pTRKS = (CWOZHelper::TRKv2*) &pImageInfo->pImageBuffer[pImageInfo->uOffset];
|
|
CWOZHelper::TRKv2* pTRK = &pTRKS[trackFromTMAP];
|
|
*pBitCount = pTRK->bitCount;
|
|
*pNibbles = (pTRK->bitCount+7) / 8;
|
|
|
|
_ASSERT(*pNibbles <= NIBBLES_PER_TRACK_WOZ2);
|
|
if (*pNibbles > NIBBLES_PER_TRACK_WOZ2)
|
|
return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount); // TODO: Enlarge track buffer, but for now just return an empty track
|
|
|
|
memcpy(pTrackImageBuffer, &pImageInfo->pImageBuffer[pTRK->startBlock*512], *pNibbles);
|
|
}
|
|
|
|
virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles)
|
|
{
|
|
// TODO
|
|
_ASSERT(0);
|
|
}
|
|
|
|
// TODO: Uncomment and fix-up if we want to allow .woz image creation (eg. for INIT or FORMAT)
|
|
// virtual bool AllowCreate(void) { return true; }
|
|
// virtual UINT GetImageSizeForCreate(void) { return 0; }//TODO
|
|
|
|
virtual eImageType GetType(void) { return eImageWOZ2; }
|
|
virtual const char* GetCreateExtensions(void) { return ".woz"; }
|
|
virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.nib;.iie;.po;.prg"; }
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
eDetectResult CMacBinaryHelper::DetectHdr(LPBYTE& pImage, DWORD& dwImageSize, DWORD& dwOffset)
|
|
{
|
|
// DETERMINE WHETHER THE FILE HAS A 128-BYTE MACBINARY HEADER
|
|
if ((dwImageSize > uMacBinHdrSize) &&
|
|
(!*pImage) &&
|
|
(*(pImage+1) < 120) &&
|
|
(!*(pImage+*(pImage+1)+2)) &&
|
|
(*(pImage+0x7A) == 0x81) &&
|
|
(*(pImage+0x7B) == 0x81))
|
|
{
|
|
pImage += uMacBinHdrSize;
|
|
dwImageSize -= uMacBinHdrSize;
|
|
dwOffset = uMacBinHdrSize;
|
|
return eMatch;
|
|
}
|
|
|
|
return eMismatch;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
eDetectResult C2IMGHelper::DetectHdr(LPBYTE& pImage, DWORD& dwImageSize, DWORD& dwOffset)
|
|
{
|
|
Header2IMG* pHdr = (Header2IMG*) pImage;
|
|
|
|
if (dwImageSize < sizeof(Header2IMG) || pHdr->FormatID != FormatID_2IMG || pHdr->HeaderSize != sizeof(Header2IMG))
|
|
return eMismatch;
|
|
|
|
// https://github.com/AppleWin/AppleWin/issues/317
|
|
// Work around some lazy implementations of the spec that set this value to 0 instead of the correct 1.
|
|
if (pHdr->Version > 1)
|
|
return eMismatch;
|
|
|
|
if (dwImageSize < sizeof(Header2IMG)+pHdr->DiskDataLength)
|
|
return eMismatch;
|
|
|
|
//
|
|
|
|
pImage += sizeof(Header2IMG);
|
|
dwImageSize = pHdr->DiskDataLength;
|
|
dwOffset += sizeof(Header2IMG);
|
|
|
|
switch (pHdr->ImageFormat)
|
|
{
|
|
case e2IMGFormatDOS33:
|
|
{
|
|
if (pHdr->DiskDataLength < TRACKS_STANDARD*TRACK_DENIBBLIZED_SIZE)
|
|
return eMismatch;
|
|
}
|
|
break;
|
|
case e2IMGFormatProDOS:
|
|
{
|
|
// FYI: GS image: PassengersontheWind.2mg has DiskDataLength==0
|
|
dwImageSize = pHdr->DiskDataLength ? pHdr->DiskDataLength : pHdr->NumBlocks * HD_BLOCK_SIZE;
|
|
|
|
if (m_bIsFloppy)
|
|
{
|
|
if (dwImageSize < TRACKS_STANDARD*TRACK_DENIBBLIZED_SIZE)
|
|
return eMismatch;
|
|
|
|
if (dwImageSize <= TRACKS_MAX*TRACK_DENIBBLIZED_SIZE)
|
|
break;
|
|
|
|
if (dwImageSize == UNIDISK35_800K_SIZE) // TODO: For //c+, eg. Prince of Persia (Original 3.5 floppy for IIc+).2MG
|
|
return eMismatch;
|
|
|
|
return eMismatch;
|
|
}
|
|
}
|
|
break;
|
|
case e2IMGFormatNIBData:
|
|
{
|
|
if (pHdr->DiskDataLength != TRACKS_STANDARD*NIBBLES_PER_TRACK_NIB)
|
|
return eMismatch;
|
|
}
|
|
break;
|
|
default:
|
|
return eMismatch;
|
|
}
|
|
|
|
memcpy(&m_Hdr, pHdr, sizeof(m_Hdr));
|
|
return eMatch;
|
|
}
|
|
|
|
BYTE C2IMGHelper::GetVolumeNumber(void)
|
|
{
|
|
if (m_Hdr.ImageFormat != e2IMGFormatDOS33 || !m_Hdr.Flags.bDOS33VolumeNumberValid)
|
|
return DEFAULT_VOLUME_NUMBER;
|
|
|
|
return m_Hdr.Flags.VolumeNumber;
|
|
}
|
|
|
|
bool C2IMGHelper::IsLocked(void)
|
|
{
|
|
return m_Hdr.Flags.bDiskImageLocked;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Pre: already matched the WOZ header
|
|
eDetectResult CWOZHelper::ProcessChunks(const LPBYTE pImage, const DWORD dwImageSize, DWORD& dwOffset, BYTE*& pTrackMap)
|
|
{
|
|
UINT32* pImage32 = (uint32_t*) (pImage + sizeof(WOZHeader));
|
|
UINT32 imageSizeRemaining = dwImageSize - sizeof(WOZHeader);
|
|
|
|
while(imageSizeRemaining > 8)
|
|
{
|
|
UINT32 chunkId = *pImage32++;
|
|
UINT32 chunkSize = *pImage32++;
|
|
imageSizeRemaining -= 8;
|
|
|
|
switch(chunkId)
|
|
{
|
|
case INFO_CHUNK_ID:
|
|
m_pInfo = (InfoChunkv2*)(pImage32-2);
|
|
if (m_pInfo->v1.version > InfoChunk::maxSupportedVersion)
|
|
return eMismatch;
|
|
if (m_pInfo->v1.diskType != InfoChunk::diskType5_25)
|
|
return eMismatch;
|
|
break;
|
|
case TMAP_CHUNK_ID:
|
|
pTrackMap = (uint8_t*)pImage32;
|
|
break;
|
|
case TRKS_CHUNK_ID:
|
|
dwOffset = dwImageSize - imageSizeRemaining; // offset into image of track data
|
|
break;
|
|
case WRIT_CHUNK_ID: // WOZ v2 (optional)
|
|
break;
|
|
case META_CHUNK_ID: // (optional)
|
|
break;
|
|
default: // no idea what this chunk is, so skip it
|
|
_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
pImage32 = (UINT32*) ((BYTE*)pImage32 + chunkSize);
|
|
imageSizeRemaining -= chunkSize;
|
|
_ASSERT(imageSizeRemaining >= 0);
|
|
if (imageSizeRemaining < 0)
|
|
return eMismatch;
|
|
}
|
|
|
|
return eMatch;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// NB. Of the 6 cases (floppy/harddisk x gzip/zip/normal) only harddisk-normal isn't read entirely to memory
|
|
// - harddisk-normal-create also doesn't create a max size image-buffer
|
|
|
|
// DETERMINE THE FILE'S EXTENSION AND CONVERT IT TO LOWERCASE
|
|
void CImageHelperBase::GetCharLowerExt(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize)
|
|
{
|
|
LPCTSTR pImageFileExt = pszImageFilename;
|
|
|
|
if (_tcsrchr(pImageFileExt, TEXT('\\')))
|
|
pImageFileExt = _tcsrchr(pImageFileExt, TEXT('\\'))+1;
|
|
|
|
if (_tcsrchr(pImageFileExt, TEXT('.')))
|
|
pImageFileExt = _tcsrchr(pImageFileExt, TEXT('.'));
|
|
|
|
_tcsncpy(pszExt, pImageFileExt, uExtSize);
|
|
pszExt[uExtSize - 1] = 0;
|
|
|
|
CharLowerBuff(pszExt, _tcslen(pszExt));
|
|
}
|
|
|
|
void CImageHelperBase::GetCharLowerExt2(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize)
|
|
{
|
|
TCHAR szFilename[MAX_PATH];
|
|
_tcsncpy(szFilename, pszImageFilename, MAX_PATH);
|
|
szFilename[MAX_PATH - 1] = 0;
|
|
|
|
TCHAR* pLastDot = _tcsrchr(szFilename, TEXT('.'));
|
|
if (pLastDot)
|
|
*pLastDot = 0;
|
|
|
|
GetCharLowerExt(pszExt, szFilename, uExtSize);
|
|
}
|
|
|
|
//-----------------
|
|
|
|
ImageError_e CImageHelperBase::CheckGZipFile(LPCTSTR pszImageFilename, ImageInfo* pImageInfo)
|
|
{
|
|
gzFile hGZFile = gzopen(pszImageFilename, "rb");
|
|
if (hGZFile == NULL)
|
|
return eIMAGE_ERROR_UNABLE_TO_OPEN_GZ;
|
|
|
|
const UINT MAX_UNCOMPRESSED_SIZE = GetMaxImageSize() + 1; // +1 to detect images that are too big
|
|
pImageInfo->pImageBuffer = new BYTE[MAX_UNCOMPRESSED_SIZE];
|
|
|
|
int nLen = gzread(hGZFile, pImageInfo->pImageBuffer, MAX_UNCOMPRESSED_SIZE);
|
|
if (nLen < 0 || nLen == MAX_UNCOMPRESSED_SIZE)
|
|
return eIMAGE_ERROR_BAD_SIZE;
|
|
|
|
int nRes = gzclose(hGZFile);
|
|
hGZFile = NULL;
|
|
if (nRes != Z_OK)
|
|
return eIMAGE_ERROR_GZ;
|
|
|
|
//
|
|
|
|
// Strip .gz then try to determine the file's extension and convert it to lowercase
|
|
TCHAR szExt[_MAX_EXT] = "";
|
|
GetCharLowerExt2(szExt, pszImageFilename, _MAX_EXT);
|
|
|
|
DWORD dwSize = nLen;
|
|
DWORD dwOffset = 0;
|
|
CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, pImageInfo->bWriteProtected, pImageInfo->pTrackMap, pImageInfo->optimalBitTiming);
|
|
|
|
if (!pImageType)
|
|
return eIMAGE_ERROR_UNSUPPORTED;
|
|
|
|
const eImageType Type = pImageType->GetType();
|
|
if (Type == eImageAPL || Type == eImageIIE || Type == eImagePRG)
|
|
return eIMAGE_ERROR_UNSUPPORTED;
|
|
|
|
SetImageInfo(pImageInfo, eFileGZip, dwOffset, pImageType, dwSize);
|
|
return eIMAGE_ERROR_NONE;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
ImageError_e CImageHelperBase::CheckZipFile(LPCTSTR pszImageFilename, ImageInfo* pImageInfo, std::string& strFilenameInZip)
|
|
{
|
|
unzFile hZipFile = unzOpen(pszImageFilename);
|
|
if (hZipFile == NULL)
|
|
return eIMAGE_ERROR_UNABLE_TO_OPEN_ZIP;
|
|
|
|
unz_global_info global_info;
|
|
unz_file_info file_info;
|
|
char szFilename[MAX_PATH];
|
|
memset(szFilename, 0, sizeof(szFilename));
|
|
int nRes = 0, nLen = 0;
|
|
|
|
try
|
|
{
|
|
nRes = unzGetGlobalInfo(hZipFile, &global_info);
|
|
if (nRes != UNZ_OK)
|
|
throw eIMAGE_ERROR_ZIP;
|
|
|
|
nRes = unzGoToFirstFile(hZipFile); // Only support 1st file in zip archive for now
|
|
if (nRes != UNZ_OK)
|
|
throw eIMAGE_ERROR_ZIP;
|
|
|
|
nRes = unzGetCurrentFileInfo(hZipFile, &file_info, szFilename, MAX_PATH, NULL, 0, NULL, 0);
|
|
if (nRes != UNZ_OK)
|
|
throw eIMAGE_ERROR_ZIP;
|
|
|
|
const UINT uFileSize = file_info.uncompressed_size;
|
|
if (uFileSize > GetMaxImageSize())
|
|
throw eIMAGE_ERROR_BAD_SIZE;
|
|
|
|
pImageInfo->pImageBuffer = new BYTE[uFileSize];
|
|
|
|
nRes = unzOpenCurrentFile(hZipFile);
|
|
if (nRes != UNZ_OK)
|
|
throw eIMAGE_ERROR_ZIP;
|
|
|
|
nLen = unzReadCurrentFile(hZipFile, pImageInfo->pImageBuffer, uFileSize);
|
|
if (nLen < 0)
|
|
{
|
|
unzCloseCurrentFile(hZipFile); // Must CloseCurrentFile before Close
|
|
throw eIMAGE_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
nRes = unzCloseCurrentFile(hZipFile);
|
|
if (nRes != UNZ_OK)
|
|
throw eIMAGE_ERROR_ZIP;
|
|
}
|
|
catch (ImageError_e error)
|
|
{
|
|
if (hZipFile)
|
|
unzClose(hZipFile);
|
|
|
|
return error;
|
|
}
|
|
|
|
nRes = unzClose(hZipFile);
|
|
hZipFile = NULL;
|
|
if (nRes != UNZ_OK)
|
|
return eIMAGE_ERROR_ZIP;
|
|
|
|
strncpy(pImageInfo->szFilenameInZip, szFilename, MAX_PATH);
|
|
pImageInfo->szFilenameInZip[MAX_PATH-1] = 0;
|
|
memcpy(&pImageInfo->zipFileInfo.tmz_date, &file_info.tmu_date, sizeof(file_info.tmu_date));
|
|
pImageInfo->zipFileInfo.dosDate = file_info.dosDate;
|
|
pImageInfo->zipFileInfo.internal_fa = file_info.internal_fa;
|
|
pImageInfo->zipFileInfo.external_fa = file_info.external_fa;
|
|
pImageInfo->uNumEntriesInZip = global_info.number_entry;
|
|
strFilenameInZip = szFilename;
|
|
|
|
//
|
|
|
|
// Determine the file's extension and convert it to lowercase
|
|
TCHAR szExt[_MAX_EXT] = "";
|
|
GetCharLowerExt(szExt, szFilename, _MAX_EXT);
|
|
|
|
DWORD dwSize = nLen;
|
|
DWORD dwOffset = 0;
|
|
CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, pImageInfo->bWriteProtected, pImageInfo->pTrackMap, pImageInfo->optimalBitTiming);
|
|
|
|
if (!pImageType)
|
|
{
|
|
if (global_info.number_entry > 1)
|
|
return eIMAGE_ERROR_UNSUPPORTED_MULTI_ZIP;
|
|
|
|
return eIMAGE_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
const eImageType Type = pImageType->GetType();
|
|
if (Type == eImageAPL || Type == eImageIIE || Type == eImagePRG)
|
|
return eIMAGE_ERROR_UNSUPPORTED;
|
|
|
|
if (global_info.number_entry > 1)
|
|
pImageInfo->bWriteProtected = 1; // Zip archives with multiple files are read-only (for now)
|
|
|
|
SetImageInfo(pImageInfo, eFileZip, dwOffset, pImageType, dwSize);
|
|
return eIMAGE_ERROR_NONE;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
ImageError_e CImageHelperBase::CheckNormalFile(LPCTSTR pszImageFilename, ImageInfo* pImageInfo, const bool bCreateIfNecessary)
|
|
{
|
|
// TRY TO OPEN THE IMAGE FILE
|
|
|
|
HANDLE& hFile = pImageInfo->hFile;
|
|
|
|
if (!pImageInfo->bWriteProtected)
|
|
{
|
|
hFile = CreateFile(pszImageFilename,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
(LPSECURITY_ATTRIBUTES)NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
}
|
|
|
|
// File may have read-only attribute set, so try to open as read-only.
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
hFile = CreateFile(
|
|
pszImageFilename,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
(LPSECURITY_ATTRIBUTES)NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL );
|
|
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
pImageInfo->bWriteProtected = true;
|
|
}
|
|
|
|
if ((hFile == INVALID_HANDLE_VALUE) && bCreateIfNecessary)
|
|
hFile = CreateFile(
|
|
pszImageFilename,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ,
|
|
(LPSECURITY_ATTRIBUTES)NULL,
|
|
CREATE_NEW,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL );
|
|
|
|
// IF WE AREN'T ABLE TO OPEN THE FILE, RETURN
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
return eIMAGE_ERROR_UNABLE_TO_OPEN;
|
|
|
|
// Determine the file's extension and convert it to lowercase
|
|
TCHAR szExt[_MAX_EXT] = "";
|
|
GetCharLowerExt(szExt, pszImageFilename, _MAX_EXT);
|
|
|
|
DWORD dwSize = GetFileSize(hFile, NULL);
|
|
DWORD dwOffset = 0;
|
|
CImageBase* pImageType = NULL;
|
|
|
|
if (dwSize > 0)
|
|
{
|
|
if (dwSize > GetMaxImageSize())
|
|
return eIMAGE_ERROR_BAD_SIZE;
|
|
|
|
bool bTempDetectBuffer;
|
|
const UINT uDetectSize = GetMinDetectSize(dwSize, &bTempDetectBuffer);
|
|
|
|
pImageInfo->pImageBuffer = new BYTE [dwSize];
|
|
|
|
DWORD dwBytesRead;
|
|
BOOL bRes = ReadFile(hFile, pImageInfo->pImageBuffer, dwSize, &dwBytesRead, NULL);
|
|
if (!bRes || dwSize != dwBytesRead)
|
|
{
|
|
delete [] pImageInfo->pImageBuffer;
|
|
pImageInfo->pImageBuffer = NULL;
|
|
return eIMAGE_ERROR_BAD_SIZE;
|
|
}
|
|
|
|
pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, pImageInfo->bWriteProtected, pImageInfo->pTrackMap, pImageInfo->optimalBitTiming);
|
|
if (bTempDetectBuffer)
|
|
{
|
|
delete [] pImageInfo->pImageBuffer;
|
|
pImageInfo->pImageBuffer = NULL;
|
|
}
|
|
}
|
|
else // Create (or pre-existing zero-length file)
|
|
{
|
|
if (pImageInfo->bWriteProtected)
|
|
return eIMAGE_ERROR_ZEROLENGTH_WRITEPROTECTED; // Can't be formatted, so return error
|
|
|
|
pImageType = GetImageForCreation(szExt, &dwSize);
|
|
if (pImageType && dwSize)
|
|
{
|
|
pImageInfo->pImageBuffer = new BYTE [dwSize];
|
|
|
|
if (pImageType->GetType() != eImageNIB1)
|
|
{
|
|
ZeroMemory(pImageInfo->pImageBuffer, dwSize);
|
|
}
|
|
else
|
|
{
|
|
// Fill zero-length image buffer with alternating high-bit-set nibbles (GH#196, GH#338)
|
|
for (UINT i=0; i<dwSize; i+=2)
|
|
{
|
|
pImageInfo->pImageBuffer[i+0] = 0x80; // bit7 set, but 0x80 is an invalid nibble
|
|
pImageInfo->pImageBuffer[i+1] = 0x81; // bit7 set, but 0x81 is an invalid nibble
|
|
}
|
|
}
|
|
|
|
// As a convenience, resize the file to the complete size (GH#506)
|
|
// - this also means that a save-state done mid-way through a format won't reference an image file with a partial size (GH#494)
|
|
DWORD dwBytesWritten = 0;
|
|
BOOL res = WriteFile(hFile, pImageInfo->pImageBuffer, dwSize, &dwBytesWritten, NULL);
|
|
if (!res || dwBytesWritten != dwSize)
|
|
return eIMAGE_ERROR_FAILED_TO_INIT_ZEROLENGTH;
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
if (!pImageType)
|
|
{
|
|
CloseHandle(hFile);
|
|
hFile = INVALID_HANDLE_VALUE;
|
|
|
|
if (dwSize == 0)
|
|
DeleteFile(pszImageFilename);
|
|
|
|
return eIMAGE_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
SetImageInfo(pImageInfo, eFileNormal, dwOffset, pImageType, dwSize);
|
|
return eIMAGE_ERROR_NONE;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CImageHelperBase::SetImageInfo(ImageInfo* pImageInfo, FileType_e eFileGZip, DWORD dwOffset, CImageBase* pImageType, DWORD dwSize)
|
|
{
|
|
pImageInfo->FileType = eFileGZip;
|
|
pImageInfo->uOffset = dwOffset;
|
|
pImageInfo->pImageType = pImageType;
|
|
pImageInfo->uImageSize = dwSize;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
ImageError_e CImageHelperBase::Open( LPCTSTR pszImageFilename,
|
|
ImageInfo* pImageInfo,
|
|
const bool bCreateIfNecessary,
|
|
std::string& strFilenameInZip)
|
|
{
|
|
pImageInfo->hFile = INVALID_HANDLE_VALUE;
|
|
|
|
ImageError_e Err;
|
|
const size_t uStrLen = strlen(pszImageFilename);
|
|
|
|
if (uStrLen > GZ_SUFFIX_LEN && _stricmp(pszImageFilename+uStrLen-GZ_SUFFIX_LEN, GZ_SUFFIX) == 0)
|
|
{
|
|
Err = CheckGZipFile(pszImageFilename, pImageInfo);
|
|
}
|
|
else if (uStrLen > ZIP_SUFFIX_LEN && _stricmp(pszImageFilename+uStrLen-ZIP_SUFFIX_LEN, ZIP_SUFFIX) == 0)
|
|
{
|
|
Err = CheckZipFile(pszImageFilename, pImageInfo, strFilenameInZip);
|
|
}
|
|
else
|
|
{
|
|
Err = CheckNormalFile(pszImageFilename, pImageInfo, bCreateIfNecessary);
|
|
}
|
|
|
|
if (pImageInfo->pImageType == NULL && Err == eIMAGE_ERROR_NONE)
|
|
Err = eIMAGE_ERROR_UNSUPPORTED;
|
|
|
|
if (Err != eIMAGE_ERROR_NONE)
|
|
return Err;
|
|
|
|
DWORD uNameLen = GetFullPathName(pszImageFilename, MAX_PATH, pImageInfo->szFilename, NULL);
|
|
if (uNameLen == 0 || uNameLen >= MAX_PATH)
|
|
Err = eIMAGE_ERROR_FAILED_TO_GET_PATHNAME;
|
|
|
|
return eIMAGE_ERROR_NONE;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
void CImageHelperBase::Close(ImageInfo* pImageInfo, const bool bDeleteFile)
|
|
{
|
|
if (pImageInfo->hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle(pImageInfo->hFile);
|
|
pImageInfo->hFile = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (bDeleteFile)
|
|
{
|
|
DeleteFile(pImageInfo->szFilename);
|
|
}
|
|
|
|
pImageInfo->szFilename[0] = 0;
|
|
|
|
delete [] pImageInfo->pImageBuffer;
|
|
pImageInfo->pImageBuffer = NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CDiskImageHelper::CDiskImageHelper(void) :
|
|
CImageHelperBase(true)
|
|
{
|
|
m_vecImageTypes.push_back( new CDoImage );
|
|
m_vecImageTypes.push_back( new CPoImage );
|
|
m_vecImageTypes.push_back( new CNib1Image );
|
|
m_vecImageTypes.push_back( new CNib2Image );
|
|
m_vecImageTypes.push_back( new CHDVImage ); // Used to trap inserting a small .hdv (or a non-140K .2mg) into a floppy drive! Small means <GetMaxFloppyImageSize()
|
|
m_vecImageTypes.push_back( new CIIeImage );
|
|
m_vecImageTypes.push_back( new CAplImage );
|
|
m_vecImageTypes.push_back( new CPrgImage );
|
|
m_vecImageTypes.push_back( new CWOZ1Image );
|
|
m_vecImageTypes.push_back( new CWOZ2Image );
|
|
}
|
|
|
|
CImageBase* CDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool& writeProtected, BYTE*& pTrackMap, BYTE& optimalBitTiming)
|
|
{
|
|
dwOffset = 0;
|
|
m_MacBinaryHelper.DetectHdr(pImage, dwSize, dwOffset);
|
|
m_Result2IMG = m_2IMGHelper.DetectHdr(pImage, dwSize, dwOffset);
|
|
|
|
// CALL THE DETECTION FUNCTIONS IN ORDER, LOOKING FOR A MATCH
|
|
eImageType imageType = eImageUNKNOWN;
|
|
eImageType possibleType = eImageUNKNOWN;
|
|
|
|
if (m_Result2IMG == eMatch)
|
|
{
|
|
if (m_2IMGHelper.IsImageFormatDOS33())
|
|
imageType = eImageDO;
|
|
else if (m_2IMGHelper.IsImageFormatProDOS())
|
|
imageType = eImagePO;
|
|
|
|
if (imageType != eImageUNKNOWN)
|
|
{
|
|
CImageBase* pImageType = GetImage(imageType);
|
|
if (!pImageType || !pImageType->IsValidImageSize(dwSize))
|
|
imageType = eImageUNKNOWN;
|
|
}
|
|
}
|
|
|
|
if (imageType == eImageUNKNOWN)
|
|
{
|
|
for (UINT uLoop=0; uLoop < GetNumImages() && imageType == eImageUNKNOWN; uLoop++)
|
|
{
|
|
if (*pszExt && _tcsstr(GetImage(uLoop)->GetRejectExtensions(), pszExt))
|
|
continue;
|
|
|
|
eDetectResult Result = GetImage(uLoop)->Detect(pImage, dwSize, pszExt);
|
|
if (Result == eMatch)
|
|
imageType = GetImage(uLoop)->GetType();
|
|
else if ((Result == ePossibleMatch) && (possibleType == eImageUNKNOWN))
|
|
possibleType = GetImage(uLoop)->GetType();
|
|
}
|
|
}
|
|
|
|
if (imageType == eImageUNKNOWN)
|
|
imageType = possibleType;
|
|
|
|
CImageBase* pImageType = GetImage(imageType);
|
|
if (!pImageType)
|
|
return NULL;
|
|
|
|
if (imageType == eImageWOZ1 || imageType == eImageWOZ2)
|
|
{
|
|
if (m_WOZHelper.ProcessChunks(pImage, dwSize, dwOffset, pTrackMap) != eMatch)
|
|
return NULL;
|
|
|
|
// if (m_WOZHelper.IsWriteProtected() && !writeProtected) // Force write-protected until writing is supported
|
|
writeProtected = true;
|
|
|
|
optimalBitTiming = m_WOZHelper.GetOptimalBitTiming();
|
|
}
|
|
else
|
|
{
|
|
if (pImageType->AllowRW())
|
|
{
|
|
const UINT uNumTracksInImage = GetNumTracksInImage(pImageType);
|
|
_ASSERT(uNumTracksInImage); // Should've been set by Image's Detect()
|
|
if (uNumTracksInImage == 0)
|
|
SetNumTracksInImage(pImageType, (dwSize > 0) ? TRACKS_STANDARD : 0); // Assume default # tracks
|
|
}
|
|
|
|
if (m_Result2IMG == eMatch)
|
|
{
|
|
pImageType->SetVolumeNumber( m_2IMGHelper.GetVolumeNumber() );
|
|
|
|
if (m_2IMGHelper.IsLocked() && !writeProtected)
|
|
writeProtected = true;
|
|
}
|
|
else
|
|
{
|
|
pImageType->SetVolumeNumber(DEFAULT_VOLUME_NUMBER);
|
|
}
|
|
}
|
|
|
|
return pImageType;
|
|
}
|
|
|
|
CImageBase* CDiskImageHelper::GetImageForCreation(const TCHAR* pszExt, DWORD* pCreateImageSize)
|
|
{
|
|
// WE CREATE ONLY DOS ORDER (DO) OR 6656-NIBBLE (NIB) FORMAT FILES
|
|
for (UINT uLoop = 0; uLoop < GetNumImages(); uLoop++)
|
|
{
|
|
if (!GetImage(uLoop)->AllowCreate())
|
|
continue;
|
|
|
|
if (*pszExt && _tcsstr(GetImage(uLoop)->GetCreateExtensions(), pszExt))
|
|
{
|
|
CImageBase* pImageType = GetImage(uLoop);
|
|
SetNumTracksInImage(pImageType, TRACKS_STANDARD); // Assume default # tracks
|
|
|
|
*pCreateImageSize = pImageType->GetImageSizeForCreate();
|
|
if (*pCreateImageSize == (UINT)-1)
|
|
return NULL;
|
|
|
|
return pImageType;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
UINT CDiskImageHelper::GetMaxImageSize(void)
|
|
{
|
|
// TODO: This doesn't account for .2mg files with comments after the disk-image
|
|
return UNIDISK35_800K_SIZE + m_MacBinaryHelper.GetMaxHdrSize() + m_2IMGHelper.GetMaxHdrSize();
|
|
}
|
|
|
|
UINT CDiskImageHelper::GetMinDetectSize(const UINT uImageSize, bool* pTempDetectBuffer)
|
|
{
|
|
*pTempDetectBuffer = false;
|
|
return uImageSize;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CHardDiskImageHelper::CHardDiskImageHelper(void) :
|
|
CImageHelperBase(false)
|
|
{
|
|
m_vecImageTypes.push_back( new CHDVImage );
|
|
}
|
|
|
|
CImageBase* CHardDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool& writeProtected, BYTE*& pTrackMap, BYTE& optimalBitTiming)
|
|
{
|
|
dwOffset = 0;
|
|
m_Result2IMG = m_2IMGHelper.DetectHdr(pImage, dwSize, dwOffset);
|
|
|
|
eImageType ImageType = eImageUNKNOWN;
|
|
|
|
for (UINT uLoop=0; uLoop < GetNumImages() && ImageType == eImageUNKNOWN; uLoop++)
|
|
{
|
|
if (*pszExt && _tcsstr(GetImage(uLoop)->GetRejectExtensions(), pszExt))
|
|
continue;
|
|
|
|
eDetectResult Result = GetImage(uLoop)->Detect(pImage, dwSize, pszExt);
|
|
if (Result == eMatch)
|
|
ImageType = GetImage(uLoop)->GetType();
|
|
|
|
_ASSERT(Result != ePossibleMatch);
|
|
}
|
|
|
|
CImageBase* pImageType = GetImage(ImageType);
|
|
|
|
if (pImageType)
|
|
{
|
|
if (m_Result2IMG == eMatch)
|
|
{
|
|
if (m_2IMGHelper.IsLocked() && !writeProtected)
|
|
writeProtected = true;
|
|
}
|
|
}
|
|
|
|
pTrackMap = 0; // TODO: WOZ
|
|
optimalBitTiming = 0; // TODO: WOZ
|
|
|
|
return pImageType;
|
|
}
|
|
|
|
CImageBase* CHardDiskImageHelper::GetImageForCreation(const TCHAR* pszExt, DWORD* pCreateImageSize)
|
|
{
|
|
// NB. Not supported for HardDisks
|
|
// - Would need to create a default 16-block file like CiderPress
|
|
|
|
for (UINT uLoop = 0; uLoop < GetNumImages(); uLoop++)
|
|
{
|
|
if (!GetImage(uLoop)->AllowCreate())
|
|
continue;
|
|
|
|
if (*pszExt && _tcsstr(GetImage(uLoop)->GetCreateExtensions(), pszExt))
|
|
{
|
|
CImageBase* pImageType = GetImage(uLoop);
|
|
|
|
*pCreateImageSize = pImageType->GetImageSizeForCreate();
|
|
if (*pCreateImageSize == (UINT)-1)
|
|
return NULL;
|
|
|
|
return pImageType;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
UINT CHardDiskImageHelper::GetMaxImageSize(void)
|
|
{
|
|
// TODO: This doesn't account for .2mg files with comments after the disk-image
|
|
return HARDDISK_32M_SIZE + m_2IMGHelper.GetMaxHdrSize();
|
|
}
|
|
|
|
UINT CHardDiskImageHelper::GetMinDetectSize(const UINT uImageSize, bool* pTempDetectBuffer)
|
|
{
|
|
*pTempDetectBuffer = true;
|
|
return m_2IMGHelper.GetMaxHdrSize();
|
|
}
|