Virtu/Virtu/DiskDsk.cs

325 lines
11 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(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
};
}
}