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 }; } }