diff --git a/DiscImageChef.Decoders/ChangeLog b/DiscImageChef.Decoders/ChangeLog index 0651a55e..d9ce2698 100644 --- a/DiscImageChef.Decoders/ChangeLog +++ b/DiscImageChef.Decoders/ChangeLog @@ -1,3 +1,12 @@ +2016-10-07 Natalia Portillo + + * Apple2.cs: Adds support for marshaling a whole disk, tracks + and sectors as well as decoding sectors in 5and3 and 6and2 + GCRs. + + * AppleSony.cs: Adds support for marshaling a whole disk, + tracks and sectors as well as decoding GCR sectors. + 2016-08-22 Natalia Portillo * LisaTag.cs: Removed temporal variable. diff --git a/DiscImageChef.Decoders/Floppy/Apple2.cs b/DiscImageChef.Decoders/Floppy/Apple2.cs index 7b656195..482a0120 100644 --- a/DiscImageChef.Decoders/Floppy/Apple2.cs +++ b/DiscImageChef.Decoders/Floppy/Apple2.cs @@ -30,7 +30,11 @@ // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ +using System; +using System.Collections.Generic; +using System.IO; using System.Runtime.InteropServices; +using DiscImageChef.Console; namespace DiscImageChef.Decoders.Floppy { @@ -42,7 +46,7 @@ namespace DiscImageChef.Decoders.Floppy /// /// GCR-encoded Apple ][ GCR floppy track /// - public struct RawTrack + public class RawTrack { /// /// Track preamble, set to self-sync 0xFF, between 40 and 95 bytes @@ -54,12 +58,12 @@ namespace DiscImageChef.Decoders.Floppy /// /// GCR-encoded Apple ][ GCR floppy sector /// - public struct RawSector + public class RawSector { /// /// Address field /// - public RawDataField addressField; + public RawAddressField addressField; /// /// Track preamble, set to self-sync 0xFF, between 5 and 10 bytes /// @@ -77,7 +81,7 @@ namespace DiscImageChef.Decoders.Floppy /// /// GCR-encoded Apple ][ GCR floppy sector address field /// - public struct RawAddressField + public class RawAddressField { /// /// Always 0xD5, 0xAA, 0x96 @@ -122,7 +126,7 @@ namespace DiscImageChef.Decoders.Floppy /// /// GCR-encoded Apple ][ GCR floppy sector data field /// - public struct RawDataField + public class RawDataField { /// /// Always 0xD5, 0xAA, 0xAD @@ -142,6 +146,500 @@ namespace DiscImageChef.Decoders.Floppy [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] epilogue; } + + static readonly byte[] ReadTable5and3 = + { + // 00h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 10h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 20h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 30h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 40h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 50h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 60h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 70h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 80h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 90h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // A0h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x01, 0x02, 0x03, + // B0h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x04, 0x05, 0x06, 0xFF, 0xFF, 0x07, 0x08, 0xFF, 0x09, 0x0A, 0x0B, + // C0h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // D0h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0x0D, 0xFF, 0xFF, 0x0E, 0x0F, 0xFF, 0x10, 0x11, 0x12, + // E0h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x13, 0x14, 0xFF, 0x15, 0x16, 0x17, + // F0h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0x19, 0x1A, 0xFF, 0xFF, 0x1B, 0x1C, 0xFF, 0x1D, 0x1E, 0x1F + }; + + static readonly byte[] ReadTable6and2 = + { + // 00h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 10h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 20h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 30h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 40h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 50h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 60h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 70h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 80h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // 90h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x02, 0x03, 0xFF, 0x04, 0x05, 0x06, + // A0h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x08, 0xFF, 0xFF, 0xFF, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + // B0h + 0xFF, 0xFF, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xFF, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, + // C0h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1B, 0xFF, 0x1C, 0x1D, 0x1E, + // D0h + 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0x20, 0x21, 0xFF, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + // E0h + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x29, 0x2A, 0x2B, 0xFF, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, + // F0h + 0xFF, 0xFF, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xFF, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F + }; + + /// + /// Decodes the 5and3 encoded data + /// + /// 5and3 encoded data. + public static byte[] Decode5and3(byte[] data) + { + if(data == null || data.Length != 410) + return null; + + byte[] buffer = new byte[data.Length]; + byte carry = 0; + for(int i = 0; i < data.Length; i++) + { + carry ^= ReadTable5and3[data[i]]; + buffer[i] = carry; + } + + byte[] output = new byte[256]; + for(int i = 0; i < 51; i++) + { + byte b1 = buffer[51 * 3 - i]; + byte b2 = buffer[51 * 2 - i]; + byte b3 = buffer[51 - i]; + byte b4 = (byte)(((b1 & 2) << 1 | (b2 & 2) | (b3 & 2) >> 1) & 0xFF); + byte b5 = (byte)(((b1 & 1) << 2 | (b2 & 1) << 1 | (b3 & 1)) & 0xFF); + output[250 - 5 * i] = (byte)(((buffer[i + 51 * 3 + 1] << 3) | ((b1 >> 2) & 0x7)) & 0xFF); + output[251 - 5 * i] = (byte)(((buffer[i + 51 * 4 + 1] << 3) | ((b2 >> 2) & 0x7)) & 0xFF); + output[252 - 5 * i] = (byte)(((buffer[i + 51 * 5 + 1] << 3) | ((b3 >> 2) & 0x7)) & 0xFF); + output[253 - 5 * i] = (byte)(((buffer[i + 51 * 6 + 1] << 3) | b4) & 0xFF); + output[254 - 5 * i] = (byte)(((buffer[i + 51 * 7 + 1] << 3) | b5) & 0xFF); + } + output[255] = (byte)(((buffer[409] << 3) | (buffer[0] & 0x7)) & 0xFF); + + return output; + } + + /// + /// Decodes the 6and2 encoded data + /// + /// 6and2 encoded data. + public static byte[] Decode6and2(byte[] data) + { + if(data == null || data.Length != 342) + return null; + + byte[] buffer = new byte[data.Length]; + byte carry = 0; + for(int i = 0; i < data.Length; i++) + { + carry ^= ReadTable6and2[data[i]]; + buffer[i] = carry; + } + + byte[] output = new byte[256]; + for(uint i = 0; i < 256; i++) + { + output[i] = (byte)((buffer[86 + i] << 2) & 0xFF); + + if(i < 86) + { + output[i] |= (byte)((((buffer[i] & 1) << 1)) & 0xFF); + output[i] |= (byte)((((buffer[i] & 2) >> 1)) & 0xFF); + } + else if(i < 86 * 2) + { + output[i] |= (byte)((((buffer[i - 86] & 4) >> 1)) & 0xFF); + output[i] |= (byte)((((buffer[i - 86] & 8) >> 3)) & 0xFF); + } + else + { + output[i] |= (byte)((((buffer[i - 86 * 2] & 0x10) >> 3)) & 0xFF); + output[i] |= (byte)((((buffer[i - 86 * 2] & 0x20) >> 5)) & 0xFF); + } + } + + return output; + } + + public static byte[] DecodeSector(RawSector sector) + { + if(sector.addressField.prologue[0] == 0xD5 && + sector.addressField.prologue[1] == 0xAA) + { + // Pre DOS 3.3 + if(sector.addressField.prologue[2] == 0xB5) + return Decode5and3(sector.dataField.data); + // DOS 3.3 + if(sector.addressField.prologue[2] == 0x96) + return Decode6and2(sector.dataField.data); + // Unknown + return null; + } + + // Not Apple ][ GCR? + return null; + } + + public static RawSector MarshalSector(byte[] data, int offset = 0) + { + int temp; + return MarshalSector(data, out temp, offset); + } + + public static RawSector MarshalSector(byte[] data, out int endOffset, int offset = 0) + { + endOffset = offset; + + // Not an Apple ][ GCR sector + if(data == null || data.Length < 363) + return null; + + RawSector sector; + int position = offset; + MemoryStream gaps; + bool onSync; + int syncCount; + + try + { + while(position < data.Length) + { + // Prologue found + if(data[position] == 0xD5 && data[position + 1] == 0xAA) + { + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Prologue found at {0}", position); + + // Epilogue not in correct position + if(data[position + 11] != 0xDE || data[position + 12] != 0xAA) + return null; + + sector = new RawSector(); + sector.addressField = new RawAddressField(); + sector.addressField.prologue = new byte[3]; + sector.addressField.prologue[0] = data[position]; + sector.addressField.prologue[1] = data[position + 1]; + sector.addressField.prologue[2] = data[position + 2]; + sector.addressField.volume = new byte[2]; + sector.addressField.volume[0] = data[position + 3]; + sector.addressField.volume[1] = data[position + 4]; + sector.addressField.track = new byte[2]; + sector.addressField.track[0] = data[position + 5]; + sector.addressField.track[1] = data[position + 6]; + sector.addressField.sector = new byte[2]; + sector.addressField.sector[0] = data[position + 7]; + sector.addressField.sector[1] = data[position + 8]; + sector.addressField.checksum = new byte[2]; + sector.addressField.checksum[0] = data[position + 9]; + sector.addressField.checksum[1] = data[position + 10]; + sector.addressField.epilogue = new byte[3]; + sector.addressField.epilogue[0] = data[position + 11]; + sector.addressField.epilogue[1] = data[position + 12]; + sector.addressField.epilogue[2] = data[position + 13]; + + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Volume {0}", (((sector.addressField.volume[0] & 0x55) << 1) | (sector.addressField.volume[1] & 0x55)) & 0xFF); + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Track {0}", (((sector.addressField.track[0] & 0x55) << 1) | (sector.addressField.track[1] & 0x55)) & 0xFF); + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Sector {0}", (((sector.addressField.sector[0] & 0x55) << 1) | (sector.addressField.sector[1] & 0x55)) & 0xFF); + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Checksum {0}", (((sector.addressField.checksum[0] & 0x55) << 1) | (sector.addressField.checksum[1] & 0x55)) & 0xFF); + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Epilogue {0:X2}{1:X2}{2:X2}", sector.addressField.epilogue[0], sector.addressField.epilogue[1], sector.addressField.epilogue[2]); + + position += 14; + syncCount = 0; + onSync = false; + gaps = new MemoryStream(); + + while(data[position] == 0xFF) + { + gaps.WriteByte(data[position]); + syncCount++; + onSync = syncCount >= 5; + position++; + } + + // Lost sync + if(!onSync) + return null; + + // Prologue not found + if(data[position] != 0xD5 || data[position + 1] != 0xAA) + return null; + + sector.innerGap = gaps.ToArray(); + sector.dataField = new RawDataField(); + + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Inner gap has {0} bytes", sector.innerGap.Length); + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Prologue found at {0}", position); + sector.dataField.prologue = new byte[3]; + sector.dataField.prologue[0] = data[position]; + sector.dataField.prologue[1] = data[position + 1]; + sector.dataField.prologue[2] = data[position + 2]; + position += 3; + + gaps = new MemoryStream(); + // Read data until epilogue is found + while(data[position + 1] != 0xDE || data[position + 2] != 0xAA) + { + gaps.WriteByte(data[position]); + position++; + + // No space left for epilogue + if(position + 4 > data.Length) + return null; + } + + sector.dataField.data = gaps.ToArray(); + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Data has {0} bytes", sector.dataField.data.Length); + sector.dataField.checksum = data[position]; + sector.dataField.epilogue = new byte[3]; + sector.dataField.epilogue[0] = data[position + 1]; + sector.dataField.epilogue[1] = data[position + 2]; + sector.dataField.epilogue[2] = data[position + 3]; + + position += 4; + gaps = new MemoryStream(); + // Read gap, if any + while(position < data.Length && data[position] == 0xFF) + { + gaps.WriteByte(data[position]); + position++; + } + + // Reduces last sector gap so doesn't eat next tracks's gap + if(gaps.Length > 5) + { + gaps.SetLength(gaps.Length / 2); + position -= (int)gaps.Length; + } + + sector.gap = gaps.ToArray(); + // Return current position to be able to read separate sectors + endOffset = position; + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Got {0} bytes of gap", sector.gap.Length); + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Finished sector at {0}", position); + return sector; + } + + if(data[position] == 0xFF) + position++; + // Found data that is not sync or a prologue + else + return null; + } + } + catch(IndexOutOfRangeException) + { + return null; + } + + return null; + } + + public static byte[] MarshalAddressField(RawAddressField addressField) + { + if(addressField == null) + return null; + + MemoryStream raw = new MemoryStream(); + raw.Write(addressField.prologue, 0, addressField.prologue.Length); + raw.Write(addressField.volume, 0, addressField.volume.Length); + raw.Write(addressField.track, 0, addressField.track.Length); + raw.Write(addressField.sector, 0, addressField.sector.Length); + raw.Write(addressField.checksum, 0, addressField.checksum.Length); + raw.Write(addressField.epilogue, 0, addressField.epilogue.Length); + + return raw.ToArray(); + } + + public static byte[] MarshalSector(RawSector sector) + { + if(sector == null) + return null; + + MemoryStream raw = new MemoryStream(); + raw.Write(sector.addressField.prologue, 0, sector.addressField.prologue.Length); + raw.Write(sector.addressField.volume, 0, sector.addressField.volume.Length); + raw.Write(sector.addressField.track, 0, sector.addressField.track.Length); + raw.Write(sector.addressField.sector, 0, sector.addressField.sector.Length); + raw.Write(sector.addressField.checksum, 0, sector.addressField.checksum.Length); + raw.Write(sector.addressField.epilogue, 0, sector.addressField.epilogue.Length); + raw.Write(sector.innerGap, 0, sector.innerGap.Length); + raw.Write(sector.dataField.prologue, 0, sector.dataField.prologue.Length); + raw.Write(sector.dataField.data, 0, sector.dataField.data.Length); + raw.WriteByte(sector.dataField.checksum); + raw.Write(sector.dataField.epilogue, 0, sector.dataField.epilogue.Length); + raw.Write(sector.gap, 0, sector.gap.Length); + + return raw.ToArray(); + } + + public static RawTrack MarshalTrack(byte[] data, int offset = 0) + { + int temp; + return MarshalTrack(data, out temp, offset); + } + + public static RawTrack MarshalTrack(byte[] data, out int endOffset, int offset = 0) + { + int position = offset; + bool firstSector = true; + bool onSync = false; + MemoryStream gaps = new MemoryStream(); + int count = 0; + List sectors = new List(); + byte[] trackNumber = new byte[2]; + endOffset = offset; + + while(position < data.Length && data[position] == 0xFF) + { + gaps.WriteByte(data[position]); + count++; + position++; + onSync = count >= 5; + } + + if(position >= data.Length) + return null; + + if(!onSync) + return null; + + while(position < data.Length) + { + int oldPosition = position; + RawSector sector = MarshalSector(data, out position, position); + if(sector == null) + break; + + if(firstSector) + { + trackNumber[0] = sector.addressField.track[0]; + trackNumber[1] = sector.addressField.track[1]; + firstSector = false; + } + + if(sector.addressField.track[0] != trackNumber[0] || + sector.addressField.track[1] != trackNumber[1]) + { + position = oldPosition; + break; + } + + DicConsole.DebugWriteLine("Apple ][ GCR Decoder", "Adding sector {0} of track {1}", (((sector.addressField.sector[0] & 0x55) << 1) | (sector.addressField.sector[1] & 0x55)) & 0xFF, (((sector.addressField.track[0] & 0x55) << 1) | (sector.addressField.track[1] & 0x55)) & 0xFF); + sectors.Add(sector); + } + + if(sectors.Count == 0) + return null; + + RawTrack track = new RawTrack(); + track.gap = gaps.ToArray(); + track.sectors = sectors.ToArray(); + endOffset = position; + return track; + } + + public static byte[] MarshalTrack(RawTrack track) + { + if(track == null) + return null; + + MemoryStream raw = new MemoryStream(); + raw.Write(track.gap, 0, track.gap.Length); + foreach(RawSector sector in track.sectors) + { + byte[] rawSector = MarshalSector(sector); + raw.Write(rawSector, 0, rawSector.Length); + } + return raw.ToArray(); + } + + public static List MarshalDisk(byte[] data, int offset = 0) + { + int temp; + return MarshalDisk(data, out temp, offset); + } + + public static List MarshalDisk(byte[] data, out int endOffset, int offset = 0) + { + endOffset = offset; + List tracks = new List(); + int position = offset; + + RawTrack track = MarshalTrack(data, out position, position); + while(track != null) + { + tracks.Add(track); + track = MarshalTrack(data, out position, position); + } + + if(tracks.Count == 0) + return null; + + endOffset = position; + return tracks; + } + + public static byte[] MarshalDisk(List disk) + { + return MarshalDisk(disk.ToArray()); + } + + public static byte[] MarshalDisk(RawTrack[] disk) + { + if(disk == null) + return null; + + MemoryStream raw = new MemoryStream(); + foreach(RawTrack track in disk) + { + byte[] rawTrack = MarshalTrack(track); + raw.Write(rawTrack, 0, rawTrack.Length); + } + return raw.ToArray(); + } + + public static bool IsApple2GCR(byte[] data) + { + int position = 0; + RawSector sector = MarshalSector(data, out position, 0); + + return sector != null && position != 0; + } } } diff --git a/DiscImageChef.Decoders/Floppy/AppleSony.cs b/DiscImageChef.Decoders/Floppy/AppleSony.cs index 254239cd..4853dd2b 100644 --- a/DiscImageChef.Decoders/Floppy/AppleSony.cs +++ b/DiscImageChef.Decoders/Floppy/AppleSony.cs @@ -30,6 +30,9 @@ // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ +using System; +using System.Collections.Generic; +using System.IO; using System.Runtime.InteropServices; namespace DiscImageChef.Decoders.Floppy @@ -45,7 +48,7 @@ namespace DiscImageChef.Decoders.Floppy /// /// GCR-encoded Apple Sony GCR floppy track /// - public struct RawTrack + public class RawTrack { /// /// Track preamble, set to self-sync 0xFF, 36 bytes @@ -57,7 +60,7 @@ namespace DiscImageChef.Decoders.Floppy /// /// GCR-encoded Apple Sony GCR floppy sector /// - public struct RawSector + public class RawSector { /// /// Address field @@ -80,7 +83,7 @@ namespace DiscImageChef.Decoders.Floppy /// /// GCR-encoded Apple Sony GCR floppy sector address field /// - public struct RawAddressField + public class RawAddressField { /// /// Always 0xD5, 0xAA, 0x96 @@ -117,7 +120,7 @@ namespace DiscImageChef.Decoders.Floppy /// /// GCR-encoded Apple ][ GCR floppy sector data field /// - public struct RawDataField + public class RawDataField { /// /// Always 0xD5, 0xAA, 0xAD @@ -144,6 +147,392 @@ namespace DiscImageChef.Decoders.Floppy [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public byte[] epilogue; } + + public static byte[] DecodeSector(RawSector sector) + { + if(sector.addressField.prologue[0] == 0xD5 && + sector.addressField.prologue[1] == 0xAA && + sector.addressField.prologue[2] == 0x96) + { + uint ck1, ck2, ck3; + byte carry; + byte w1, w2, w3, w4; + byte[] bf1 = new byte[175]; + byte[] bf2 = new byte[175]; + byte[] bf3 = new byte[175]; + byte[] nib_data = sector.dataField.data; + MemoryStream ms = new MemoryStream(); + + int j = 0; + w3 = 0; + for(int i = 0; i <= 174; i++) + { + w4 = nib_data[j++]; + w1 = nib_data[j++]; + w2 = nib_data[j++]; + + if(i != 174) w3 = nib_data[j++]; + + bf1[i] = (byte)(((w1 & 0x3F) | ((w4 << 2) & 0xC0)) & 0x0F); + bf2[i] = (byte)(((w2 & 0x3F) | ((w4 << 4) & 0xC0)) & 0x0F); + bf3[i] = (byte)(((w3 & 0x3F) | ((w4 << 6) & 0xC0)) & 0x0F); + } + + j = 0; + ck1 = 0; + ck2 = 0; + ck3 = 0; + while(true) + { + ck1 = (ck1 & 0xFF) << 1; + if((ck1 & 0x0100) > 0) + ck1++; + + carry = (byte)((bf1[j] ^ ck1) & 0xFF); + ck3 += carry; + if((ck1 & 0x0100) > 0) + { + ck3++; + ck1 &= 0xFF; + } + ms.WriteByte(carry); + + carry = (byte)((bf2[j] ^ ck3) & 0xFF); + ck2 += carry; + if (ck3 > 0xFF) + { + ck2++; + ck3 &= 0xFF; + } + ms.WriteByte(carry); + + if (ms.Length == 524) break; + + carry = (byte)((bf3[j] ^ ck2) & 0xFF); + ck1 += carry; + if (ck2 > 0xFF) + { + ck1++; + ck2 &= 0xFF; + } + ms.WriteByte(carry); + j++; + } + + return ms.ToArray(); + } + + // Not Apple Sony GCR? + return null; + } + + public static RawSector MarshalSector(byte[] data, int offset = 0) + { + int temp; + return MarshalSector(data, out temp, offset); + } + + public static RawSector MarshalSector(byte[] data, out int endOffset, int offset = 0) + { + endOffset = offset; + + // Not an Apple ][ GCR sector + if(data == null || data.Length < 363) + return null; + + RawSector sector; + int position = offset; + MemoryStream gaps; + bool onSync; + int syncCount; + + try + { + while(position < data.Length) + { + // Prologue found + if(data[position] == 0xD5 && data[position + 1] == 0xAA && data[position + 2] == 0x96) + { + // Epilogue not in correct position + if(data[position + 8] != 0xDE || data[position + 9] != 0xAA) + return null; + + sector = new RawSector(); + sector.addressField = new RawAddressField(); + sector.addressField.prologue = new byte[3]; + sector.addressField.prologue[0] = data[position]; + sector.addressField.prologue[1] = data[position + 1]; + sector.addressField.prologue[2] = data[position + 2]; + sector.addressField.track = data[position + 3]; + sector.addressField.sector = data[position + 4]; + sector.addressField.side = data[position + 5]; + sector.addressField.format = (AppleEncodedFormat)data[position + 6]; + sector.addressField.checksum = data[position + 7]; + sector.addressField.epilogue = new byte[2]; + sector.addressField.epilogue[0] = data[position + 8]; + sector.addressField.epilogue[1] = data[position + 9]; + + position += 10; + syncCount = 0; + onSync = false; + gaps = new MemoryStream(); + + while(data[position] == 0xFF) + { + gaps.WriteByte(data[position]); + syncCount++; + onSync = syncCount >= 5; + position++; + } + + // Lost sync + if(!onSync) + return null; + + // Prologue not found + if(data[position] != 0xDE || data[position + 1] != 0xAA || data[position + 2] != 0xAD) + return null; + + sector.innerGap = gaps.ToArray(); + sector.dataField = new RawDataField(); + sector.dataField.prologue = new byte[3]; + sector.dataField.prologue[0] = data[position]; + sector.dataField.prologue[1] = data[position + 1]; + sector.dataField.prologue[2] = data[position + 2]; + sector.dataField.spare = data[position + 3]; + position += 4; + + gaps = new MemoryStream(); + // Read data until epilogue is found + while(data[position + 4] != 0xD5 || data[position + 5] != 0xAA) + { + gaps.WriteByte(data[position]); + position++; + + // No space left for epilogue + if(position + 7 > data.Length) + return null; + } + + sector.dataField.data = gaps.ToArray(); + sector.dataField.checksum = new byte[4]; + sector.dataField.checksum[0] = data[position]; + sector.dataField.checksum[1] = data[position + 2]; + sector.dataField.checksum[2] = data[position + 3]; + sector.dataField.checksum[3] = data[position + 4]; + sector.dataField.epilogue = new byte[2]; + sector.dataField.epilogue[0] = data[position + 5]; + sector.dataField.epilogue[1] = data[position + 6]; + + position += 7; + gaps = new MemoryStream(); + // Read gap, if any + while(position < data.Length && data[position] == 0xFF) + { + gaps.WriteByte(data[position]); + position++; + } + + // Reduces last sector gap so doesn't eat next tracks's gap + if(gaps.Length > 5) + { + gaps.SetLength(gaps.Length / 2); + position -= (int)gaps.Length; + } + + sector.gap = gaps.ToArray(); + // Return current position to be able to read separate sectors + endOffset = position; + return sector; + } + + if(data[position] == 0xFF) + position++; + // Found data that is not sync or a prologue + else + return null; + } + } + catch(IndexOutOfRangeException) + { + return null; + } + + return null; + } + + public static byte[] MarshalAddressField(RawAddressField addressField) + { + if(addressField == null) + return null; + + MemoryStream raw = new MemoryStream(); + raw.Write(addressField.prologue, 0, addressField.prologue.Length); + raw.WriteByte(addressField.track); + raw.WriteByte(addressField.sector); + raw.WriteByte(addressField.side); + raw.WriteByte((byte)addressField.format); + raw.WriteByte(addressField.checksum); + + return raw.ToArray(); + } + + public static byte[] MarshalSector(RawSector sector) + { + if(sector == null) + return null; + + MemoryStream raw = new MemoryStream(); + raw.Write(sector.addressField.prologue, 0, sector.addressField.prologue.Length); + raw.WriteByte(sector.addressField.track); + raw.WriteByte(sector.addressField.sector); + raw.WriteByte(sector.addressField.side); + raw.WriteByte((byte)sector.addressField.format); + raw.WriteByte(sector.addressField.checksum); + raw.Write(sector.innerGap, 0, sector.innerGap.Length); + raw.Write(sector.dataField.prologue, 0, sector.dataField.prologue.Length); + raw.WriteByte(sector.dataField.spare); + raw.Write(sector.dataField.data, 0, sector.dataField.data.Length); + raw.Write(sector.dataField.checksum, 0, sector.dataField.checksum.Length); + raw.Write(sector.dataField.epilogue, 0, sector.dataField.epilogue.Length); + raw.Write(sector.gap, 0, sector.gap.Length); + + return raw.ToArray(); + } + + public static RawTrack MarshalTrack(byte[] data, int offset = 0) + { + int temp; + return MarshalTrack(data, out temp, offset); + } + + public static RawTrack MarshalTrack(byte[] data, out int endOffset, int offset = 0) + { + int position = offset; + bool firstSector = true; + bool onSync = false; + MemoryStream gaps = new MemoryStream(); + int count = 0; + List sectors = new List(); + byte trackNumber = 0; + byte sideNumber = 0; + endOffset = offset; + + while(position < data.Length && data[position] == 0xFF) + { + gaps.WriteByte(data[position]); + count++; + position++; + onSync = count >= 5; + } + + if(position >= data.Length) + return null; + + if(!onSync) + return null; + + while(position < data.Length) + { + int oldPosition = position; + RawSector sector = MarshalSector(data, out position, position); + if(sector == null) + break; + + if(firstSector) + { + trackNumber = sector.addressField.track; + sideNumber = sector.addressField.side; + firstSector = false; + } + + if(sector.addressField.track != trackNumber || + sector.addressField.side != sideNumber) + { + position = oldPosition; + break; + } + + sectors.Add(sector); + } + + if(sectors.Count == 0) + return null; + + RawTrack track = new RawTrack(); + track.gap = gaps.ToArray(); + track.sectors = sectors.ToArray(); + endOffset = position; + return track; + } + + public static byte[] MarshalTrack(RawTrack track) + { + if(track == null) + return null; + + MemoryStream raw = new MemoryStream(); + raw.Write(track.gap, 0, track.gap.Length); + foreach(RawSector sector in track.sectors) + { + byte[] rawSector = MarshalSector(sector); + raw.Write(rawSector, 0, rawSector.Length); + } + return raw.ToArray(); + } + + public static List MarshalDisk(byte[] data, int offset = 0) + { + int temp; + return MarshalDisk(data, out temp, offset); + } + + public static List MarshalDisk(byte[] data, out int endOffset, int offset = 0) + { + endOffset = offset; + List tracks = new List(); + int position = offset; + + RawTrack track = MarshalTrack(data, out position, position); + while(track != null) + { + tracks.Add(track); + track = MarshalTrack(data, out position, position); + } + + if(tracks.Count == 0) + return null; + + endOffset = position; + return tracks; + } + + public static byte[] MarshalDisk(List disk) + { + return MarshalDisk(disk.ToArray()); + } + + public static byte[] MarshalDisk(RawTrack[] disk) + { + if(disk == null) + return null; + + MemoryStream raw = new MemoryStream(); + foreach(RawTrack track in disk) + { + byte[] rawTrack = MarshalTrack(track); + raw.Write(rawTrack, 0, rawTrack.Length); + } + return raw.ToArray(); + } + + public static bool IsAppleSonyGCR(byte[] data) + { + int position = 0; + RawSector sector = MarshalSector(data, out position, 0); + + return sector != null && position != 0; + } } } diff --git a/DiscImageChef.DiscImages/AppleNIB.cs b/DiscImageChef.DiscImages/AppleNIB.cs index 703304a4..3446ee2e 100644 --- a/DiscImageChef.DiscImages/AppleNIB.cs +++ b/DiscImageChef.DiscImages/AppleNIB.cs @@ -5,11 +5,11 @@ // Filename : AppleNIB.cs // Author(s) : Natalia Portillo // -// Component : Component +// Component : Disc image plugins. // // --[ Description ] ---------------------------------------------------------- // -// Description +// Manages Apple nibbelized disc images. // // --[ License ] -------------------------------------------------------------- // @@ -29,13 +29,536 @@ // ---------------------------------------------------------------------------- // Copyright © 2011-2016 Natalia Portillo // ****************************************************************************/ + using System; -namespace DiscImageChef.DiscImages +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DiscImageChef.CommonTypes; +using DiscImageChef.Console; +using DiscImageChef.Decoders.Floppy; +using DiscImageChef.Filters; + +namespace DiscImageChef.ImagePlugins { - public class AppleNIB - { - public AppleNIB() - { - } - } -} + // TODO: Checksum sectors + public class AppleNIB : ImagePlugin + { + Dictionary longSectors; + Dictionary cookedSectors; + Dictionary addressFields; + + readonly ulong[] dosSkewing = { 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 }; + readonly ulong[] proDosSkewing = { 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 }; + + readonly byte[] pascal_sign = { 0x08, 0xA5, 0x0F, 0x29 }; + readonly byte[] pascal2_sign = { 0xFF, 0xA2, 0x00, 0x8E }; + readonly byte[] dos_sign = { 0xA2, 0x02, 0x8E, 0x52 }; + readonly byte[] sos_sign = { 0xC9, 0x20, 0xF0, 0x3E }; + readonly byte[] apple3_sign = { 0x8D, 0xD0, 0x03, 0x4C, 0xC7, 0xA4 }; + readonly byte[] cpm_sign = { 0xA2, 0x55, 0xA9, 0x00, 0x9D, 0x00, 0x0D, 0xCA }; + readonly byte[] prodos_string = { 0x50, 0x52, 0x4F, 0x44, 0x4F, 0x53 }; + readonly byte[] pascal_string = { 0x53, 0x59, 0x53, 0x54, 0x45, 0x2E, 0x41, 0x50, 0x50, 0x4C, 0x45 }; + readonly byte[] dri_string = { 0x43, 0x4F, 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x20, 0x28, 0x43, + 0x29, 0x20, 0x31, 0x39, 0x37, 0x39, 0x2C, 0x20, 0x44, 0x49, 0x47, 0x49, 0x54, 0x41, 0x4C, 0x20, + 0x52, 0x45, 0x53, 0x45, 0x41, 0x52, 0x43, 0x48}; + + public AppleNIB() + { + Name = "Apple NIB"; + PluginUUID = new Guid("AE171AE8-6747-49CC-B861-9D450B7CD42E"); + ImageInfo = new ImageInfo(); + ImageInfo.readableSectorTags = new List(); + ImageInfo.readableMediaTags = new List(); + ImageInfo.imageHasPartitions = false; + ImageInfo.imageHasSessions = false; + ImageInfo.imageVersion = null; + ImageInfo.imageApplication = null; + ImageInfo.imageApplicationVersion = null; + ImageInfo.imageCreator = null; + ImageInfo.imageComments = null; + ImageInfo.mediaManufacturer = null; + ImageInfo.mediaModel = null; + ImageInfo.mediaSerialNumber = null; + ImageInfo.mediaBarcode = null; + ImageInfo.mediaPartNumber = null; + ImageInfo.mediaSequence = 0; + ImageInfo.lastMediaSequence = 0; + ImageInfo.driveManufacturer = null; + ImageInfo.driveModel = null; + ImageInfo.driveSerialNumber = null; + ImageInfo.driveFirmwareRevision = null; + } + + public override bool IdentifyImage(Filter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) + return false; + + byte[] test = new byte[512]; + stream.Read(test, 0, 512); + + return Apple2.IsApple2GCR(test); + } + + public override bool OpenImage(Filter imageFilter) + { + Stream stream = imageFilter.GetDataForkStream(); + stream.Seek(0, SeekOrigin.Begin); + + if(stream.Length < 512) + return false; + + byte[] buffer = new byte[stream.Length]; + stream.Read(buffer, 0, buffer.Length); + + DicConsole.DebugWriteLine("Apple NIB Plugin", "Decoding whole image"); + List tracks = Apple2.MarshalDisk(buffer); + DicConsole.DebugWriteLine("Apple NIB Plugin", "Got {0} tracks", tracks.Count); + + Dictionary rawSectors = new Dictionary(); + + int spt = 0; + bool allTracksEqual = true; + for(int i = 1; i < tracks.Count; i++) + { + allTracksEqual &= tracks[i - 1].sectors.Length == tracks[i].sectors.Length; + } + + if(allTracksEqual) + spt = tracks[0].sectors.Length; + + bool skewed = spt == 16; + ulong[] skewing = dosSkewing; + + // Detect ProDOS skewed disks + if(skewed) + { + byte[] sector1 = null; + byte[] sector0 = null; + + foreach(Apple2.RawSector sector in tracks[0].sectors) + { + if(sector.addressField.sector.SequenceEqual(new byte[] { 170, 171 })) + sector1 = Apple2.DecodeSector(sector); + if(sector.addressField.sector.SequenceEqual(new byte[] { 170, 170 })) + sector0 = Apple2.DecodeSector(sector); + } + + if(sector1 != null) + { + byte[] tmpAt0Sz4 = new byte[4]; + byte[] tmpAt0Sz6 = new byte[6]; + byte[] tmpAt0Sz8 = new byte[8]; + byte[] tmpAt3Sz6 = new byte[6]; + byte[] tmpAt24Sz36 = new byte[36]; + byte[] tmpAt33Sz6 = new byte[6]; + + Array.Copy(sector1, 0, tmpAt0Sz4, 0, 4); + Array.Copy(sector1, 0, tmpAt0Sz6, 0, 6); + Array.Copy(sector1, 0, tmpAt0Sz8, 0, 8); + Array.Copy(sector1, 3, tmpAt3Sz6, 0, 6); + Array.Copy(sector1, 24, tmpAt24Sz36, 0, 36); + Array.Copy(sector1, 33, tmpAt33Sz6, 0, 6); + + if(tmpAt0Sz4.SequenceEqual(sos_sign)) + skewing = proDosSkewing; + if(tmpAt0Sz4.SequenceEqual(dos_sign)) + skewing = proDosSkewing; + if(tmpAt0Sz4.SequenceEqual(pascal2_sign)) + skewing = proDosSkewing; + if(tmpAt0Sz6.SequenceEqual(apple3_sign)) + skewing = proDosSkewing; + if(tmpAt0Sz8.SequenceEqual(cpm_sign)) + skewing = proDosSkewing; + if(tmpAt3Sz6.SequenceEqual(prodos_string)) + skewing = proDosSkewing; + if(tmpAt24Sz36.SequenceEqual(dri_string)) + skewing = proDosSkewing; + if(tmpAt33Sz6.SequenceEqual(prodos_string)) + skewing = proDosSkewing; + + if(sector0 != null) + { + byte[] tmpAt215Sz12 = new byte[12]; + Array.Copy(sector0, 215, tmpAt215Sz12, 0, 12); + if(tmpAt215Sz12.SequenceEqual(pascal_string) && tmpAt0Sz4.SequenceEqual(pascal_sign)) + skewing = proDosSkewing; + } + + DicConsole.DebugWriteLine("Apple NIB Plugin", "Image is skewed"); + } + } + + for(int i = 0; i < tracks.Count; i++) + { + foreach(Apple2.RawSector sector in tracks[i].sectors) + { + if(skewed && spt != 0) + { + ulong sectorNo = (ulong)((((sector.addressField.sector[0] & 0x55) << 1) | (sector.addressField.sector[1] & 0x55)) & 0xFF); + DicConsole.DebugWriteLine("Apple NIB Plugin", "Hardware sector {0} of track {1} goes to logical sector {2}", sectorNo, i, skewing[sectorNo] + (ulong)(i * spt)); + rawSectors.Add(skewing[sectorNo] + (ulong)(i * spt), sector); + ImageInfo.sectors++; + } + else + { + rawSectors.Add(ImageInfo.sectors, sector); + ImageInfo.sectors++; + } + } + } + + DicConsole.DebugWriteLine("Apple NIB Plugin", "Got {0} sectors", ImageInfo.sectors); + + DicConsole.DebugWriteLine("Apple NIB Plugin", "Cooking sectors"); + + longSectors = new Dictionary(); + cookedSectors = new Dictionary(); + addressFields = new Dictionary(); + + foreach(KeyValuePair kvp in rawSectors) + { + byte[] cooked = Apple2.DecodeSector(kvp.Value); + byte[] raw = Apple2.MarshalSector(kvp.Value); + byte[] addr = Apple2.MarshalAddressField(kvp.Value.addressField); + longSectors.Add(kvp.Key, raw); + cookedSectors.Add(kvp.Key, cooked); + addressFields.Add(kvp.Key, addr); + } + + ImageInfo.imageSize = (ulong)imageFilter.GetDataForkLength(); + ImageInfo.imageCreationTime = imageFilter.GetCreationTime(); + ImageInfo.imageLastModificationTime = imageFilter.GetLastWriteTime(); + ImageInfo.imageName = Path.GetFileNameWithoutExtension(imageFilter.GetFilename()); + if(ImageInfo.sectors == 455) + ImageInfo.mediaType = MediaType.Apple32SS; + else if(ImageInfo.sectors == 560) + ImageInfo.mediaType = MediaType.Apple33SS; + else + ImageInfo.mediaType = MediaType.Unknown; + ImageInfo.sectorSize = 256; + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; + ImageInfo.readableSectorTags.Add(SectorTagType.FloppyAddressMark); + + return true; + } + + public override bool ImageHasPartitions() + { + return false; + } + + public override ulong GetImageSize() + { + return ImageInfo.imageSize; + } + + public override ulong GetSectors() + { + return ImageInfo.sectors; + } + + public override uint GetSectorSize() + { + return ImageInfo.sectorSize; + } + + public override string GetImageFormat() + { + return "Apple nibbles"; + } + + public override string GetImageVersion() + { + return ImageInfo.imageVersion; + } + + public override string GetImageApplication() + { + return ImageInfo.imageApplication; + } + + public override string GetImageApplicationVersion() + { + return ImageInfo.imageApplicationVersion; + } + + public override string GetImageCreator() + { + return ImageInfo.imageCreator; + } + + public override DateTime GetImageCreationTime() + { + return ImageInfo.imageCreationTime; + } + + public override DateTime GetImageLastModificationTime() + { + return ImageInfo.imageLastModificationTime; + } + + public override string GetImageName() + { + return ImageInfo.imageName; + } + + public override string GetImageComments() + { + return ImageInfo.imageComments; + } + + public override MediaType GetMediaType() + { + switch(ImageInfo.sectors) + { + case 455: + return MediaType.Apple32SS; + case 560: + return MediaType.Apple33SS; + default: + return MediaType.Unknown; + } + } + + public override byte[] ReadSector(ulong sectorAddress) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + byte[] temp; + cookedSectors.TryGetValue(sectorAddress, out temp); + return temp; + } + + public override byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + if(sectorAddress + length > ImageInfo.sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSector(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + + public override byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + if(tag != SectorTagType.FloppyAddressMark) + throw new FeatureUnsupportedImageException(string.Format("Tag {0} not supported by image format", tag)); + + byte[] temp; + addressFields.TryGetValue(sectorAddress, out temp); + return temp; + } + + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + if(sectorAddress + length > ImageInfo.sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + if(tag != SectorTagType.FloppyAddressMark) + throw new FeatureUnsupportedImageException(string.Format("Tag {0} not supported by image format", tag)); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSectorTag(sectorAddress + i, tag); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + + public override byte[] ReadSectorLong(ulong sectorAddress) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + byte[] temp; + longSectors.TryGetValue(sectorAddress, out temp); + return temp; + } + + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), string.Format("Sector address {0} not found", sectorAddress)); + + if(sectorAddress + length > ImageInfo.sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + MemoryStream ms = new MemoryStream(); + + for(uint i = 0; i < length; i++) + { + byte[] sector = ReadSectorLong(sectorAddress + i); + ms.Write(sector, 0, sector.Length); + } + + return ms.ToArray(); + } + + #region Unsupported features + + public override byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override string GetMediaManufacturer() + { + return null; + } + + public override string GetMediaModel() + { + return null; + } + + public override string GetMediaSerialNumber() + { + return null; + } + + public override string GetMediaBarcode() + { + return null; + } + + public override string GetMediaPartNumber() + { + return null; + } + + public override int GetMediaSequence() + { + return 0; + } + + public override int GetLastDiskSequence() + { + return 0; + } + + public override string GetDriveManufacturer() + { + return null; + } + + public override string GetDriveModel() + { + return null; + } + + public override string GetDriveSerialNumber() + { + return null; + } + + public override List GetPartitions() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetTracks() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessions() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public override bool? VerifySector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, out List FailingLBAs, out List UnknownLBAs) + { + FailingLBAs = new List(); + UnknownLBAs = new List(); + for(ulong i = 0; i < ImageInfo.sectors; i++) + UnknownLBAs.Add(i); + return null; + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List FailingLBAs, out List UnknownLBAs) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override bool? VerifyMediaImage() + { + return null; + } + + #endregion + } +} \ No newline at end of file diff --git a/DiscImageChef.DiscImages/ChangeLog b/DiscImageChef.DiscImages/ChangeLog index 2adc2a25..bb7730d4 100644 --- a/DiscImageChef.DiscImages/ChangeLog +++ b/DiscImageChef.DiscImages/ChangeLog @@ -1,3 +1,9 @@ +2016-10-07 Natalia Portillo + + * AppleNIB.cs: + * DiscImageChef.DiscImages.csproj: Adds support for Apple + nibble disk images. + 2016-09-30 Natalia Portillo * NDIF.cs: diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj index c586c52d..e019f1b0 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj +++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj @@ -69,6 +69,7 @@ +