326 lines
12 KiB
C#
326 lines
12 KiB
C#
using System;
|
|
using System.IO;
|
|
using Jellyfish.Library;
|
|
|
|
namespace Jellyfish.Virtu
|
|
{
|
|
public enum SectorSkew { None = 0, Dos, ProDos };
|
|
|
|
public sealed class DiskDsk : Disk525
|
|
{
|
|
public DiskDsk() { }
|
|
public DiskDsk(string name, byte[] data, bool isWriteProtected, SectorSkew sectorSkew) :
|
|
base(name, data, isWriteProtected)
|
|
{
|
|
_sectorSkew = SectorSkewMode[(int)sectorSkew];
|
|
}
|
|
|
|
public DiskDsk(string name, Stream stream, bool isWriteProtected, SectorSkew sectorSkew) :
|
|
base(name, new byte[TrackCount * SectorCount * SectorSize], isWriteProtected)
|
|
{
|
|
if (stream == null)
|
|
{
|
|
throw new ArgumentNullException("stream");
|
|
}
|
|
|
|
stream.ReadBlock(Data);
|
|
_sectorSkew = SectorSkewMode[(int)sectorSkew];
|
|
}
|
|
|
|
public override void ReadTrack(int number, int fraction, byte[] buffer)
|
|
{
|
|
int track = number / 2;
|
|
|
|
_trackBuffer = buffer;
|
|
_trackOffset = 0;
|
|
|
|
WriteNibble(0xFF, 48); // gap 0
|
|
|
|
for (int sector = 0; sector < SectorCount; sector++)
|
|
{
|
|
WriteNibble(0xD5); // address prologue
|
|
WriteNibble(0xAA);
|
|
WriteNibble(0x96);
|
|
|
|
WriteNibble44(Volume);
|
|
WriteNibble44(track);
|
|
WriteNibble44(sector);
|
|
WriteNibble44(Volume ^ track ^ sector);
|
|
|
|
WriteNibble(0xDE); // address epilogue
|
|
WriteNibble(0xAA);
|
|
WriteNibble(0xEB);
|
|
WriteNibble(0xFF, 8);
|
|
|
|
WriteNibble(0xD5); // data prologue
|
|
WriteNibble(0xAA);
|
|
WriteNibble(0xAD);
|
|
|
|
WriteDataNibbles((track * SectorCount + _sectorSkew[sector]) * SectorSize);
|
|
|
|
WriteNibble(0xDE); // data epilogue
|
|
WriteNibble(0xAA);
|
|
WriteNibble(0xEB);
|
|
WriteNibble(0xFF, 16);
|
|
}
|
|
}
|
|
|
|
public override void WriteTrack(int number, int fraction, byte[] buffer)
|
|
{
|
|
if (IsWriteProtected)
|
|
return;
|
|
|
|
int track = number / 2;
|
|
|
|
_trackBuffer = buffer;
|
|
_trackOffset = 0;
|
|
int sectorsDone = 0;
|
|
|
|
for (int sector = 0; sector < SectorCount; sector++)
|
|
{
|
|
if (!Read3Nibbles(0xD5, 0xAA, 0x96, 0x304))
|
|
break; // no address prologue
|
|
|
|
/*int readVolume = */ReadNibble44();
|
|
|
|
int readTrack = ReadNibble44();
|
|
if (readTrack != track)
|
|
break; // bad track number
|
|
|
|
int readSector = ReadNibble44();
|
|
if (readSector > SectorCount)
|
|
break; // bad sector number
|
|
if ((sectorsDone & (0x1 << readSector)) != 0)
|
|
break; // already done this sector
|
|
|
|
if (ReadNibble44() != (Volume ^ readTrack ^ readSector))
|
|
break; // bad address checksum
|
|
|
|
if ((ReadNibble() != 0xDE) || (ReadNibble() != 0xAA))
|
|
break; // bad address epilogue
|
|
|
|
if (!Read3Nibbles(0xD5, 0xAA, 0xAD, 0x20))
|
|
break; // no data prologue
|
|
|
|
if (!ReadDataNibbles((track * SectorCount + _sectorSkew[sector]) * SectorSize))
|
|
break; // bad data checksum
|
|
|
|
if ((ReadNibble() != 0xDE) || (ReadNibble() != 0xAA))
|
|
break; // bad data epilogue
|
|
|
|
sectorsDone |= 0x1 << sector;
|
|
}
|
|
|
|
if (sectorsDone != 0xFFFF)
|
|
throw new InvalidOperationException("disk error"); // TODO: we should alert the user and "dump" a NIB
|
|
}
|
|
|
|
private byte ReadNibble()
|
|
{
|
|
byte data = _trackBuffer[_trackOffset];
|
|
if (_trackOffset++ == TrackSize)
|
|
{
|
|
_trackOffset = 0;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
private bool Read3Nibbles(byte data1, byte data2, byte data3, int maxReads)
|
|
{
|
|
bool result = false;
|
|
while (--maxReads > 0)
|
|
{
|
|
if (ReadNibble() != data1)
|
|
continue;
|
|
|
|
if (ReadNibble() != data2)
|
|
continue;
|
|
|
|
if (ReadNibble() != data3)
|
|
continue;
|
|
|
|
result = true;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private int ReadNibble44()
|
|
{
|
|
return (((ReadNibble() << 1) | 0x1) & ReadNibble());
|
|
}
|
|
|
|
private byte ReadTranslatedNibble()
|
|
{
|
|
byte data = NibbleToByte[ReadNibble()];
|
|
// TODO: check that invalid nibbles aren't used
|
|
// (put 0xFFs for invalid nibbles in the table)
|
|
//if (data == 0xFF)
|
|
//{
|
|
//throw an exception
|
|
//}
|
|
return data;
|
|
}
|
|
|
|
private bool ReadDataNibbles(int sectorOffset)
|
|
{
|
|
byte a, x, y;
|
|
|
|
y = SecondaryBufferLength;
|
|
a = 0;
|
|
do // fill and de-nibblize secondary buffer
|
|
{
|
|
a = _secondaryBuffer[--y] = (byte)(a ^ ReadTranslatedNibble());
|
|
}
|
|
while (y > 0);
|
|
|
|
do // fill and de-nibblize secondary buffer
|
|
{
|
|
a = _primaryBuffer[y++] = (byte)(a ^ ReadTranslatedNibble());
|
|
}
|
|
while (y != 0);
|
|
|
|
int checksum = a ^ ReadTranslatedNibble(); // should be 0
|
|
|
|
x = y = 0;
|
|
do // decode data
|
|
{
|
|
if (x == 0)
|
|
{
|
|
x = SecondaryBufferLength;
|
|
}
|
|
a = (byte)((_primaryBuffer[y] << 2) | SwapBits[_secondaryBuffer[--x] & 0x03]);
|
|
_secondaryBuffer[x] >>= 2;
|
|
Data[sectorOffset + y] = a;
|
|
}
|
|
while (++y != 0);
|
|
|
|
return (checksum == 0);
|
|
}
|
|
|
|
private void WriteNibble(int data)
|
|
{
|
|
_trackBuffer[_trackOffset++] = (byte)data;
|
|
}
|
|
|
|
private void WriteNibble(int data, int count)
|
|
{
|
|
while (count-- > 0)
|
|
{
|
|
WriteNibble(data);
|
|
}
|
|
}
|
|
|
|
private void WriteNibble44(int data)
|
|
{
|
|
WriteNibble((data >> 1) | 0xAA);
|
|
WriteNibble(data | 0xAA);
|
|
}
|
|
|
|
private void WriteDataNibbles(int sectorOffset)
|
|
{
|
|
byte a, x, y;
|
|
|
|
for (x = 0; x < SecondaryBufferLength; x++)
|
|
{
|
|
_secondaryBuffer[x] = 0; // zero secondary buffer
|
|
}
|
|
|
|
y = 2;
|
|
do // fill buffers
|
|
{
|
|
x = 0;
|
|
do
|
|
{
|
|
a = Data[sectorOffset + --y];
|
|
_secondaryBuffer[x] = (byte)((_secondaryBuffer[x] << 2) | SwapBits[a & 0x03]); // b1,b0 -> secondary buffer
|
|
_primaryBuffer[y] = (byte)(a >> 2); // b7-b2 -> primary buffer
|
|
}
|
|
while (++x < SecondaryBufferLength);
|
|
}
|
|
while (y != 0);
|
|
|
|
y = SecondaryBufferLength;
|
|
do // write secondary buffer
|
|
{
|
|
WriteNibble(ByteToNibble[_secondaryBuffer[y] ^ _secondaryBuffer[y - 1]]);
|
|
}
|
|
while (--y != 0);
|
|
|
|
a = _secondaryBuffer[0];
|
|
do // write primary buffer
|
|
{
|
|
WriteNibble(ByteToNibble[a ^ _primaryBuffer[y]]);
|
|
a = _primaryBuffer[y];
|
|
}
|
|
while (++y != 0);
|
|
|
|
WriteNibble(ByteToNibble[a]); // data checksum
|
|
}
|
|
|
|
private byte[] _trackBuffer;
|
|
private int _trackOffset;
|
|
private byte[] _primaryBuffer = new byte[0x100];
|
|
private const int SecondaryBufferLength = 0x56;
|
|
private byte[] _secondaryBuffer = new byte[SecondaryBufferLength + 1];
|
|
private int[] _sectorSkew;
|
|
private const int Volume = 0xFE;
|
|
|
|
private static readonly byte[] SwapBits = { 0, 2, 1, 3 };
|
|
|
|
private static readonly int[] SectorSkewNone = new int[SectorCount]
|
|
{
|
|
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
|
|
};
|
|
|
|
private static readonly int[] SectorSkewDos = new int[SectorCount]
|
|
{
|
|
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF
|
|
};
|
|
|
|
private static readonly int[] SectorSkewProDos = new int[SectorCount]
|
|
{
|
|
0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF
|
|
};
|
|
|
|
private const int SectorSkewCount = 3;
|
|
|
|
private static readonly int[][] SectorSkewMode = new int[SectorSkewCount][]
|
|
{
|
|
SectorSkewNone, SectorSkewDos, SectorSkewProDos
|
|
};
|
|
|
|
private static readonly byte[] ByteToNibble = new byte[]
|
|
{
|
|
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
|
|
};
|
|
|
|
private static readonly byte[] NibbleToByte = new byte[]
|
|
{
|
|
// padding for offset (not used)
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
|
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
|
0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
|
|
|
|
// nibble translate table
|
|
0x00, 0x01, 0x98, 0x99, 0x02, 0x03, 0x9C, 0x04, 0x05, 0x06,
|
|
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x07, 0x08, 0xA8, 0xA9, 0xAA, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
|
|
0xB0, 0xB1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xB8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
|
|
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x1B, 0xCC, 0x1C, 0x1D, 0x1E,
|
|
0xD0, 0xD1, 0xD2, 0x1F, 0xD4, 0xD5, 0x20, 0x21, 0xD8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
|
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x29, 0x2A, 0x2B, 0xE8, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
|
|
0xF0, 0xF1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xF8, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
|
|
};
|
|
}
|
|
}
|