// Pi1541 - A Commodore 1541 disk drive emulator
// Copyright(C) 2018 Stephen White
//
// This file is part of Pi1541.
//
// Pi1541 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 3 of the License, or
// (at your option) any later version.
//
// Pi1541 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 Pi1541. If not, see .
// Pete Rittwage and Markus Brenner's code was heavly referenced and functions converted to CPP
// Used with Pete Rittwage's permission
#include "DiskImage.h"
#include "gcr.h"
#include "debug.h"
#include
#include
#include "lz.h"
extern "C"
{
#include "rpi-gpio.h"
}
unsigned char DiskImage::readBuffer[READBUFFER_SIZE];
static unsigned char compressionBuffer[HALF_TRACK_COUNT * NIB_TRACK_LENGTH];
static const unsigned short SECTOR_LENGTH = 256;
static const unsigned short SECTOR_LENGTH_WITH_CHECKSUM = 260;
static const unsigned char GCR_SYNC_BYTE = 0xff;
static const unsigned char GCR_GAP_BYTE = 0x55;
static const int SECTOR_HEADER_LENGTH = 8;
static const unsigned MAX_D64_SIZE = 0x30000;
#define NIB_HEADER_SIZE 0xFF
int gap_match_length = 7; // Used by gcr.cpp
const unsigned char DiskImage::SectorsPerTrack[42] =
{
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, // 1 - 17
19, 19, 19, 19, 19, 19, 19, // 18 - 24
18, 18, 18, 18, 18, 18, // 25 - 30
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, // 31 - 40
17, 17 // 41 - 42
// total 683-768 sectors
};
DiskImage::DiskImage()
: readOnly(false)
, dirty(false)
, attachedImageSize(0)
, fileInfo(0)
{
memset(tracks, 0x55, sizeof(tracks));
}
void DiskImage::Close()
{
switch (diskType)
{
case D64:
CloseD64();
break;
case G64:
CloseG64();
break;
case NIB:
CloseNIB();
break;
case NBZ:
CloseNBZ();
break;
default:
break;
}
memset(tracks, 0x55, sizeof(tracks));
memset(trackLengths, 0, sizeof(trackLengths));
diskType = NONE;
fileInfo = 0;
}
void DiskImage::DumpTrack(unsigned track)
{
unsigned char* src = tracks[track];
unsigned trackLength = trackLengths[track];
DEBUG_LOG("track = %d trackLength = %d\r\n", track, trackLength);
for (unsigned index = 0; index < trackLength; ++index)
{
DEBUG_LOG("%d %02x\r\n", index, src[index]);
}
}
bool DiskImage::OpenD64(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
{
Close();
this->fileInfo = fileInfo;
unsigned offset = 0;
if (size > MAX_D64_SIZE)
size = MAX_D64_SIZE;
attachedImageSize = size;
for (unsigned halfTrackIndex = 0; halfTrackIndex < HALF_TRACK_COUNT; ++halfTrackIndex)
{
unsigned char track = (halfTrackIndex >> 1);
unsigned char* dest = tracks[halfTrackIndex];
trackLengths[halfTrackIndex] = SectorsPerTrack[track] * GCR_SECTOR_LENGTH;
if ((halfTrackIndex & 1) == 0)
{
if (offset < size) // This will allow for >35 tracks.
{
trackUsed[halfTrackIndex] = true;
//DEBUG_LOG("Track %d used\r\n", halfTrackIndex);
for (unsigned sectorNo = 0; sectorNo < SectorsPerTrack[track]; ++sectorNo)
{
convert_sector_to_GCR(diskImage + offset, dest, track + 1, sectorNo, diskImage + 0x165A2, 0);
dest += 361;
offset += SECTOR_LENGTH;
}
}
else
{
trackUsed[halfTrackIndex] = false;
//DEBUG_LOG("Track %d not used\r\n", halfTrackIndex);
}
}
else
{
trackUsed[halfTrackIndex] = false;
//DEBUG_LOG("Track %d not used\r\n", halfTrackIndex);
}
}
diskType = D64;
return true;
}
bool DiskImage::WriteD64()
{
if (readOnly)
return true;
FIL fp;
FRESULT res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE);
if (res == FR_OK)
{
u32 bytesToWrite;
u32 bytesWritten;
int track, sector;
BYTE id[3];
BYTE d64data[MAXBLOCKSONDISK * 256], *d64ptr;
int blocks_to_save = 0;
DEBUG_LOG("Writing D64 file...\r\n");
memset(d64data, 0, sizeof(d64data));
if (!GetID(34, id))
{
DEBUG_LOG("Cannot find directory sector.\r\n");
return false;
}
d64ptr = d64data;
for (track = 0; track <= 40 * 2; track += 2)
{
if (trackUsed[track])
{
//printf("Track %d\n", track);
for (sector = 0; sector < SectorsPerTrack[track / 2]; sector++)
{
ConvertSector(track, sector, d64ptr);
d64ptr += 256;
blocks_to_save++;
}
}
}
bytesToWrite = blocks_to_save * 256;
SetACTLed(true);
if (f_write(&fp, d64data, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
{
SetACTLed(false);
DEBUG_LOG("Cannot write d64 data.\r\n");
f_close(&fp);
return false;
}
f_close(&fp);
f_utime(fileInfo->fname, fileInfo);
SetACTLed(false);
DEBUG_LOG("Converted %d blocks into D64 file\r\n", blocks_to_save);
return true;
}
else
{
DEBUG_LOG("Failed to open %s for write\r\n", fileInfo->fname);
return false;
}
}
void DiskImage::CloseD64()
{
if (dirty)
{
WriteD64();
dirty = false;
}
attachedImageSize = 0;
}
bool DiskImage::OpenG64(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
{
Close();
this->fileInfo = fileInfo;
attachedImageSize = size;
if (memcmp(diskImage, "GCR-1541", 8) == 0)
{
//DEBUG_LOG("Is G64\r\n");
unsigned char numTracks = diskImage[9];
//DEBUG_LOG("numTracks = %d\r\n", numTracks);
unsigned char* data = diskImage + 12;
unsigned char* speedZoneData = diskImage + 0x15c;
unsigned short trackLength = 0;
unsigned track;
for (track = 0; track < numTracks; ++track)
{
unsigned offset = *(unsigned*)data;
data += 4;
//DEBUG_LOG("Track = %d Offset = %x\r\n", track, offset);
trackDensity[track] = *(unsigned*)(speedZoneData + track * 4);
if (offset == 0)
{
trackLengths[track] = capacity_max[trackDensity[track]];
trackUsed[track] = false;
}
else
{
unsigned char* trackData = diskImage + offset;
trackLength = *(unsigned short*)(trackData);
//DEBUG_LOG("trackLength = %d offset = %d\r\n", trackLength, offset);
trackData += 2;
trackLengths[track] = trackLength;
memcpy(tracks[track], trackData, trackLength);
trackUsed[track] = true;
//DEBUG_LOG("%d has data\r\n", track);
}
}
diskType = G64;
return true;
}
return false;
}
static bool WriteDwords(FIL* fp, u32* values, u32 amount)
{
u32 index;
u32 bytesToWrite = 4;
u32 bytesWritten;
for (index = 0; index < amount; ++index)
{
if (f_write(fp, &values[index], bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
return false;
}
return true;
}
bool DiskImage::WriteG64()
{
if (readOnly)
return true;
FIL fp;
FRESULT res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE);
if (res == FR_OK)
{
u32 bytesToWrite;
u32 bytesWritten;
int track_inc = 1;
BYTE header[12];
DWORD gcr_track_p[MAX_HALFTRACKS_1541] = { 0 };
DWORD gcr_speed_p[MAX_HALFTRACKS_1541] = { 0 };
BYTE gcr_track[NIB_TRACK_LENGTH + 2];
size_t track_len;
int index = 0, track;
BYTE buffer[NIB_TRACK_LENGTH], tempfillbyte;
DEBUG_LOG("Writing G64 file...\r\n");
//DEBUG_LOG("G64 Track Length = %d", G64_TRACK_MAXLEN);
strcpy((char *)header, "GCR-1541");
header[8] = 0;
header[9] = MAX_HALFTRACKS_1541;
header[10] = (BYTE)(G64_TRACK_MAXLEN % 256);
header[11] = (BYTE)(G64_TRACK_MAXLEN / 256);
bytesToWrite = sizeof(header);
SetACTLed(true);
if (f_write(&fp, header, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
{
SetACTLed(false);
DEBUG_LOG("Cannot write G64 header.\r\n");
f_close(&fp);
return false;
}
SetACTLed(false);
for (track = 0; track < MAX_HALFTRACKS_1541; track += track_inc)
{
if (trackLengths[track] == 0 || !trackUsed[track])
{
gcr_track_p[track] = 0;
gcr_speed_p[track] = 0;
}
else
{
gcr_track_p[track] = 0xc + (MAX_TRACKS_1541 * 16) + (index++ * (G64_TRACK_MAXLEN + 2));
gcr_speed_p[track] = trackDensity[track] & 3;
}
}
SetACTLed(true);
WriteDwords(&fp, (u32*)gcr_track_p, MAX_HALFTRACKS_1541);
WriteDwords(&fp, (u32*)gcr_speed_p, MAX_HALFTRACKS_1541);
SetACTLed(false);
for (track = 0; track < MAX_HALFTRACKS_1541; track += track_inc)
{
track_len = trackLengths[track];
if (track_len>G64_TRACK_MAXLEN) track_len = G64_TRACK_MAXLEN;
if (!track_len || !trackUsed[track]) continue;
tempfillbyte = 0x55;
memset(&gcr_track[2], tempfillbyte, G64_TRACK_MAXLEN);
gcr_track[0] = (BYTE)(track_len % 256);
gcr_track[1] = (BYTE)(track_len / 256);
memcpy(buffer, tracks[track], track_len);
memcpy(gcr_track + 2, buffer, track_len);
bytesToWrite = G64_TRACK_MAXLEN + 2;
SetACTLed(true);
if (f_write(&fp, gcr_track, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
{
SetACTLed(false);
DEBUG_LOG("Cannot write track data.\r\n");
f_close(&fp);
return false;
}
SetACTLed(false);
}
f_close(&fp);
DEBUG_LOG("nSuccessfully saved G64\r\n");
return true;
}
else
{
DEBUG_LOG("Failed to open %s for write\r\n", fileInfo->fname);
return false;
}
}
void DiskImage::CloseG64()
{
if (dirty)
{
WriteG64();
dirty = false;
}
attachedImageSize = 0;
}
bool DiskImage::OpenNIB(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
{
int track, t_index = 0, h_index = 0;
Close();
this->fileInfo = fileInfo;
attachedImageSize = size;
if (memcmp(diskImage, "MNIB-1541-RAW", 13) == 0)
{
for (track = 0; track < (MAX_TRACKS_1541 * 2); ++track)
{
trackLengths[track] = capacity_max[trackDensity[track]];
trackUsed[track] = false;
}
while (diskImage[0x10 + h_index])
{
track = diskImage[0x10 + h_index] - 2;
unsigned char v = diskImage[0x11 + h_index];
trackDensity[track] = (v & 0x03);
DEBUG_LOG("Converting NIB track %d (%d.%d)\r\n", track, track >> 1, track & 1 ? 5 : 0);
unsigned char* nibdata = diskImage + (t_index * NIB_TRACK_LENGTH) + 0x100;
int align;
trackLengths[track] = extract_GCR_track(tracks[track], nibdata, &align
//, ALIGN_GAP
, ALIGN_NONE
, capacity_min[trackDensity[track]],
capacity_max[trackDensity[track]]);
trackUsed[track] = true;
h_index += 2;
t_index++;
}
DEBUG_LOG("Successfully parsed NIB data for %d tracks\n", t_index);
diskType = NIB;
return true;
}
return false;
}
bool DiskImage::WriteNIB()
{
if (readOnly)
return true;
FIL fp;
FRESULT res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE);
if (res == FR_OK)
{
u32 bytesToWrite;
u32 bytesWritten;
int track;
char header[0x100];
int header_entry = 0;
DEBUG_LOG("Converting to NIB format...\n");
memset(header, 0, sizeof(header));
sprintf(header, "MNIB-1541-RAW%c%c%c", 1, 0, 0);
for (track = 0; track < (MAX_TRACKS_1541 * 2); ++track)
{
if (trackUsed[track])
{
header[0x10 + (header_entry * 2)] = (BYTE)track + 2;
header[0x10 + (header_entry * 2) + 1] = trackDensity[track];
header_entry++;
}
}
bytesToWrite = sizeof(header);
SetACTLed(true);
if (f_write(&fp, header, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
{
DEBUG_LOG("Cannot write track data.\r\n");
}
else
{
bytesToWrite = NIB_TRACK_LENGTH;
for (track = 0; track < (MAX_TRACKS_1541 * 2); ++track)
{
if (trackUsed[track])
{
if (f_write(&fp, tracks[track], bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
{
DEBUG_LOG("Cannot write track data.\r\n");
}
}
}
}
SetACTLed(false);
f_close(&fp);
DEBUG_LOG("nSuccessfully saved NIB\r\n");
return true;
}
else
{
DEBUG_LOG("Failed to open %s for write\r\n", fileInfo->fname);
return false;
}
}
void DiskImage::CloseNIB()
{
if (dirty)
{
WriteNIB();
dirty = false;
}
attachedImageSize = 0;
}
bool DiskImage::OpenNBZ(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
{
Close();
if ((size = LZ_Uncompress(diskImage, compressionBuffer, size)))
{
if (OpenNIB(fileInfo, compressionBuffer, size))
{
diskType = NIB;
return true;
}
}
return false;
}
bool DiskImage::WriteNBZ()
{
bool success = false;
if (readOnly)
return true;
SetACTLed(true);
if (WriteNIB())
{
FIL fp;
FRESULT res = f_open(&fp, fileInfo->fname, FA_READ);
if (res == FR_OK)
{
u32 bytesRead;
f_read(&fp, readBuffer, READBUFFER_SIZE, &bytesRead);
f_close(&fp);
DEBUG_LOG("Reloaded %s - %d for compression\r\n", fileInfo->fname, bytesRead);
bytesRead = LZ_Compress(readBuffer, compressionBuffer, bytesRead);
if (bytesRead)
{
res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE);
if (res == FR_OK)
{
u32 bytesToWrite = bytesRead;
u32 bytesWritten;
if (f_write(&fp, compressionBuffer, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
{
DEBUG_LOG("Cannot write NBZ data.\r\n");
}
else
{
success = true;
}
f_close(&fp);
}
}
}
}
SetACTLed(false);
return success;
}
void DiskImage::CloseNBZ()
{
if (dirty)
{
WriteNBZ();
dirty = false;
}
attachedImageSize = 0;
}
bool DiskImage::GetDecodedSector(u32 track, u32 sector, u8* buffer)
{
if (track > 0)
{
track = (track - 1) * 2;
if (trackUsed[track])
return ConvertSector(track, sector, buffer);
}
return false;
}
DiskImage::DiskType DiskImage::GetDiskImageTypeViaExtention(const char* diskImageName)
{
char* ext = strrchr((char*)diskImageName, '.');
if (ext)
{
if (toupper((char)ext[1]) == 'G' && ext[2] == '6' && ext[3] == '4')
return G64;
else if (toupper((char)ext[1]) == 'N' && toupper((char)ext[2]) == 'I' && toupper((char)ext[3]) == 'B')
return NIB;
else if (toupper((char)ext[1]) == 'N' && toupper((char)ext[2]) == 'B' && toupper((char)ext[3]) == 'Z')
return NBZ;
else if (toupper((char)ext[1]) == 'D' && ext[2] == '6' && ext[3] == '4')
return D64;
else if (toupper((char)ext[1]) == 'L' && toupper((char)ext[2]) == 'S' && toupper((char)ext[3]) == 'T')
return LST;
}
return NONE;
}
bool DiskImage::IsDiskImageExtention(const char* diskImageName)
{
return GetDiskImageTypeViaExtention(diskImageName) != NONE;
}
bool DiskImage::IsLSTExtention(const char* diskImageName)
{
char* ext = strrchr((char*)diskImageName, '.');
if (ext && toupper((char)ext[1]) == 'L' && toupper((char)ext[2]) == 'S' && toupper((char)ext[3]) == 'T')
return true;
return false;
}
bool DiskImage::ConvertSector(unsigned track, unsigned sector, unsigned char* data)
{
unsigned char buffer[SECTOR_LENGTH_WITH_CHECKSUM];
unsigned char checkSum;
int index;
int bitIndex;
bitIndex = FindSectorHeader(track, sector, 0);
if (bitIndex < 0)
return false;
bitIndex = FindSync(track, bitIndex, (SECTOR_LENGTH_WITH_CHECKSUM * 2) * 8);
if (bitIndex < 0)
return false;
DecodeBlock(track, bitIndex, buffer, SECTOR_LENGTH_WITH_CHECKSUM / 4);
checkSum = buffer[257];
for (index = 0; index < SECTOR_LENGTH; ++index)
{
data[index] = buffer[index + 1];
checkSum ^= data[index];
}
if (buffer[0] != 0x07)
return false; // No data block
return checkSum == 0;
}
void DiskImage::DecodeBlock(unsigned track, int bitIndex, unsigned char* buf, int num)
{
int shift, i, j;
unsigned char gcr[5];
unsigned char byte;
unsigned char* offset;
unsigned char* end = tracks[track] + trackLengths[track];
shift = bitIndex & 7;
offset = tracks[track] + (bitIndex >> 3);
byte = offset[0] << shift;
for (i = 0; i < num; i++, buf += 4)
{
for (j = 0; j < 5; j++)
{
offset++;
if (offset >= end)
offset = tracks[track];
if (shift)
{
gcr[j] = byte | ((offset[0] << shift) >> 8);
byte = offset[0] << shift;
}
else
{
gcr[j] = byte;
byte = offset[0];
}
}
convert_4bytes_from_GCR(gcr, buf);
}
}
int DiskImage::FindSync(unsigned track, int bitIndex, int maxBits, int* syncStartIndex)
{
int readShiftRegister = 0;
unsigned char byte = tracks[track][bitIndex >> 3] << (bitIndex & 7);
bool prevBitZero = true;
while (maxBits--)
{
if (byte & 0x80)
{
if (syncStartIndex && prevBitZero)
*syncStartIndex = bitIndex;
prevBitZero = false;
readShiftRegister = (readShiftRegister << 1) | 1;
}
else
{
prevBitZero = true;
if (~readShiftRegister & 0x3ff)
readShiftRegister <<= 1;
else
return bitIndex;
}
if (~bitIndex & 7)
{
bitIndex++;
byte <<= 1;
}
else
{
bitIndex++;
if (bitIndex >= NIB_TRACK_LENGTH * 8)
bitIndex = 0;
byte = tracks[track][bitIndex >> 3];
}
}
return -1;
}
int DiskImage::FindSectorHeader(unsigned track, unsigned sector, unsigned char* id)
{
unsigned char header[10];
int bitIndex;
int bitIndexPrev;
bitIndex = 0;
bitIndexPrev = -1;
for (;;)
{
bitIndex = FindSync(track, bitIndex, NIB_TRACK_LENGTH * 8);
if (bitIndexPrev == bitIndex)
break;
if (bitIndexPrev < 0)
bitIndexPrev = bitIndex;
DecodeBlock(track, bitIndex, header, 2);
if (header[0] == 0x08 && header[2] == sector)
{
if (id)
{
id[0] = header[5];
id[1] = header[4];
}
return bitIndex;
}
}
return -1;
}
unsigned DiskImage::GetID(unsigned track, unsigned char* id)
{
if (FindSectorHeader(track, 0, id) >= 0)
return 1;
return 0;
}
unsigned DiskImage::LastTrackUsed()
{
unsigned i;
unsigned lastTrackUsed = 0;
for (i = 0; i < HALF_TRACK_COUNT; ++i)
{
if (trackUsed[i])
lastTrackUsed = i;
}
return lastTrackUsed;
}