/* This file is part of ContrAlto. ContrAlto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ContrAlto 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with ContrAlto. If not, see . */ using System; using System.IO; namespace Contralto.IO { /// /// DiskGeometry encapsulates the geometry of a disk. /// public struct DiskGeometry { public DiskGeometry(int cylinders, int tracks, int sectors) { Cylinders = cylinders; Tracks = tracks; Sectors = sectors; } public int Cylinders; public int Tracks; public int Sectors; } public enum DiabloDiskType { Diablo31, Diablo44 } /// /// DiabloDiskSector encapsulates the records contained in a single Alto disk sector /// on a Diablo disk. This includes the header, label, and data records. /// public class DiabloDiskSector { public DiabloDiskSector(byte[] header, byte[] label, byte[] data) { if (header.Length != 4 || label.Length != 16 || data.Length != 512) { throw new InvalidOperationException("Invalid sector header/label/data length."); } Header = GetUShortArray(header); Label = GetUShortArray(label); Data = GetUShortArray(data); } private ushort[] GetUShortArray(byte[] data) { if ((data.Length % 2) != 0) { throw new InvalidOperationException("Array length must be even."); } ushort[] array = new ushort[data.Length / 2]; int offset = 0; for(int i=0;i /// Encapsulates disk image data for all disk packs used with the /// standard Alto Disk Controller (i.e. the 31 and 44, which differ /// only in the number of cylinders) /// public class DiabloPack { public DiabloPack(DiabloDiskType type) { _diskType = type; _packName = null; _geometry = new DiskGeometry(type == DiabloDiskType.Diablo31 ? 203 : 406, 2, 12); _sectors = new DiabloDiskSector[_geometry.Cylinders, _geometry.Tracks, _geometry.Sectors]; } public DiskGeometry Geometry { get { return _geometry; } } public string PackName { get { return _packName; } } public void Load(Stream imageStream, string path, bool reverseByteOrder) { _packName = path; for(int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++) { for(int track = 0; track < _geometry.Tracks; track++) { for(int sector = 0; sector < _geometry.Sectors; sector++) { byte[] header = new byte[4]; // 2 words byte[] label = new byte[16]; // 8 words byte[] data = new byte[512]; // 256 words // // Bitsavers images have an extra word in the header for some reason. // ignore it. // TODO: should support different formats ("correct" raw, Alto CopyDisk format, etc.) // imageStream.Seek(2, SeekOrigin.Current); if (imageStream.Read(header, 0, header.Length) != header.Length) { throw new InvalidOperationException("Short read while reading sector header."); } if (imageStream.Read(label, 0, label.Length) != label.Length) { throw new InvalidOperationException("Short read while reading sector label."); } if (imageStream.Read(data, 0, data.Length) != data.Length) { throw new InvalidOperationException("Short read while reading sector data."); } if (reverseByteOrder) { SwapBytes(header); SwapBytes(label); SwapBytes(data); } _sectors[cylinder, track, sector] = new DiabloDiskSector(header, label, data); } } } if (imageStream.Position != imageStream.Length) { throw new InvalidOperationException("Extra data at end of image file."); } } public void Save(Stream imageStream) { for (int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++) { for (int track = 0; track < _geometry.Tracks; track++) { for (int sector = 0; sector < _geometry.Sectors; sector++) { byte[] header = new byte[4]; // 2 words byte[] label = new byte[16]; // 8 words byte[] data = new byte[512]; // 256 words // // Bitsavers images have an extra word in the header for some reason. // We will follow this 'standard' when writing out. // TODO: should support different formats ("correct" raw, Alto CopyDisk format, etc.) // byte[] dummy = new byte[2]; imageStream.Write(dummy, 0, 2); DiabloDiskSector s = _sectors[cylinder, track, sector]; WriteWordBuffer(imageStream, s.Header); WriteWordBuffer(imageStream, s.Label); WriteWordBuffer(imageStream, s.Data); } } } } public DiabloDiskSector GetSector(int cylinder, int track, int sector) { return _sectors[cylinder, track, sector]; } private void WriteWordBuffer(Stream imageStream, ushort[] buffer) { // TODO: this is beyond inefficient for(int i=0;i> 8)); } } private void SwapBytes(byte[] data) { for(int i=0;i