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 void Save(Stream imageStream, bool reverseByteOrder) { byte[] emptyHeader = new byte[4]; // 2 words byte[] emptyLabel = new byte[16]; // 8 words byte[] emptyData = new byte[512]; // 256 words for (int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++) { for (int track = 0; track < _geometry.Tracks; track++) { for (int sector = 0; sector < _geometry.Sectors; sector++) { // // 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 = GetSector(cylinder, track, sector); imageStream.Write(reverseByteOrder ? SwapBytes(s.Header) : s.Header, 0, s.Header.Length); imageStream.Write(reverseByteOrder ? SwapBytes(s.Label) : s.Label, 0, s.Label.Length); imageStream.Write(reverseByteOrder ? SwapBytes(s.Data) : s.Data, 0, s.Data.Length); } } } }
public DiabloDiskSector GetSector(int cylinder, int track, int sector) { DiabloDiskSector s = _sectors[cylinder, track, sector]; // For invalid / empty sectors, return an Empty sector. if (s == null) { s = DiabloDiskSector.Empty; } return(s); }
public void SetSector(int address, DiabloDiskSector newSector) { if (address < 0 || address >= MaxAddress) { throw new InvalidOperationException("Disk address is out of range."); } int sector = address % _geometry.Sectors; int track = (address / _geometry.Sectors) % _geometry.Tracks; int cylinder = (address / (_geometry.Sectors * _geometry.Tracks)); SetSector(cylinder, track, sector, newSector); }
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."); } _sectors[cylinder, track, sector] = new DiabloDiskSector( reverseByteOrder ? SwapBytes(header) : header, reverseByteOrder ? SwapBytes(label) : label, reverseByteOrder ? SwapBytes(data) : data); } } } if (imageStream.Position != imageStream.Length) { throw new InvalidOperationException("Extra data at end of image file."); } }
private void CopyDiskWorkerThread() { // TODO: enforce state (i.e. reject out-of-order block types.) while (_running) { // Retrieve length of this block (in bytes): int length = Channel.ReadUShort() * 2; // Sanity check that length is a reasonable value. if (length > 2048) { Channel.SendAbort("Block length is invalid."); throw new InvalidOperationException(String.Format("Insane block length ({0})", length)); } // Retrieve type CopyDiskBlock blockType = (CopyDiskBlock)Channel.ReadUShort(); // Read rest of block starting at offset 4 (so deserialization works) byte[] data = new byte[length]; Channel.Read(ref data, data.Length - 4, 4); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Copydisk block type is {0}", blockType); switch (blockType) { case CopyDiskBlock.Version: { VersionYesNoBlock vbIn = (VersionYesNoBlock)Serializer.Deserialize(data, typeof(VersionYesNoBlock)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Copydisk client is version {0}, '{1}'", vbIn.Code, vbIn.Herald.ToString()); // Send the response: VersionYesNoBlock vbOut = new VersionYesNoBlock(CopyDiskBlock.Version, vbIn.Code, "LCM+L IFS CopyDisk of 26-Jan-2016"); Channel.Send(Serializer.Serialize(vbOut)); } break; case CopyDiskBlock.Login: { LoginBlock login = (LoginBlock)Serializer.Deserialize(data, typeof(LoginBlock)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Login is for user '{0}', password '{1}', connection '{2}', connection password '{3}'.", login.UserName, login.UserPassword, login.ConnName, login.ConnPassword); _userToken = AuthenticateUser(login.UserName.ToString(), login.UserPassword.ToString()); if (_userToken != null) { // // Send a "Yes" response back. // VersionYesNoBlock yes = new VersionYesNoBlock(CopyDiskBlock.Yes, 0, "Come on in, the water's fine."); Channel.Send(Serializer.Serialize(yes)); } else { // // Send a "No" response back indicating the login failure. // VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.IllegalOrIncorrectPassword, "Invalid username or password."); Channel.Send(Serializer.Serialize(no), true); } } break; case CopyDiskBlock.SendDiskParamsR: { SendDiskParamsBlock p = (SendDiskParamsBlock)Serializer.Deserialize(data, typeof(SendDiskParamsBlock)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Requested unit for reading is '{0}'", p.UnitName); // // See if the pack image exists, return HereAreDiskParams if so, or No if not. // If the image exists, save the path for future use. // // Some sanity (and security) checks: // Name must be a filename only, no paths of any kind allowed. // Oh, and the file must exist in the directory holding disk packs. // string diskPath = GetPathForDiskImage(p.UnitName.ToString()); if (!String.IsNullOrEmpty(Path.GetDirectoryName(p.UnitName.ToString())) || !File.Exists(diskPath)) { // Invalid name, return No reponse. VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Invalid unit name."); Channel.Send(Serializer.Serialize(no)); } else { // // Attempt to open the image file and read it into memory. // try { using (FileStream packStream = new FileStream(diskPath, FileMode.Open, FileAccess.Read)) { // TODO: determine pack type rather than assuming Diablo 31 _pack = new DiabloPack(DiabloDiskType.Diablo31); _pack.Load(packStream, diskPath, true /* reverse byte order */); } // Send a "HereAreDiskParams" response indicating success. // HereAreDiskParamsBFSBlock diskParams = new HereAreDiskParamsBFSBlock(_pack.Geometry); Channel.Send(Serializer.Serialize(diskParams)); } catch { // If we fail for any reason, return a "No" response. // TODO: can we be more helpful here? VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Image could not be opened."); Channel.Send(Serializer.Serialize(no)); } } } break; case CopyDiskBlock.SendDiskParamsW: { SendDiskParamsBlock p = (SendDiskParamsBlock)Serializer.Deserialize(data, typeof(SendDiskParamsBlock)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Requested unit for writing is '{0}'", p.UnitName); // // Some sanity (and security) checks: // Name must be a filename only, no paths of any kind allowed. // Oh, and the file must not exist in the directory holding disk packs. // string diskPath = GetPathForDiskImage(p.UnitName.ToString()); if (!String.IsNullOrEmpty(Path.GetDirectoryName(p.UnitName.ToString())) || File.Exists(diskPath)) { // Invalid name, return No reponse. VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitNotReady, "Invalid unit name or image already exists."); Channel.Send(Serializer.Serialize(no)); } else { // // Create a new in-memory disk image. We will write it out to disk when the transfer is completed. // // TODO: determine pack type based on disk params rather than assuming Diablo 31 _pack = new DiabloPack(DiabloDiskType.Diablo31); _pack.PackName = diskPath; // Send a "HereAreDiskParams" response indicating success. // HereAreDiskParamsBFSBlock diskParams = new HereAreDiskParamsBFSBlock(_pack.Geometry); Channel.Send(Serializer.Serialize(diskParams)); } } break; case CopyDiskBlock.HereAreDiskParams: { HereAreDiskParamsBFSBlock diskParams = (HereAreDiskParamsBFSBlock)Serializer.Deserialize(data, typeof(HereAreDiskParamsBFSBlock)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Disk params are: Type {0}, C/H/S {1}/{2}/{3}", diskParams.DiskType, diskParams.Cylinders, diskParams.Heads, diskParams.Sectors); } break; case CopyDiskBlock.RetrieveDisk: case CopyDiskBlock.StoreDisk: { TransferParametersBlock transferParameters = (TransferParametersBlock)Serializer.Deserialize(data, typeof(TransferParametersBlock)); _startAddress = _pack.DiskAddressToVirtualAddress(transferParameters.StartAddress); _endAddress = _pack.DiskAddressToVirtualAddress(transferParameters.EndAddress); // Validate that the user is allowed to store. if (blockType == CopyDiskBlock.StoreDisk) { if (_userToken.Privileges != IFSPrivileges.ReadWrite) { VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnitWriteProtected, "You do not have permission to store disk images."); Channel.Send(Serializer.Serialize(no)); break; } } Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Transfer is from block {0} to block {1}", transferParameters.StartAddress, transferParameters.EndAddress); // Validate start/end parameters if (_endAddress <= _startAddress || _startAddress > _pack.MaxAddress || _endAddress > _pack.MaxAddress) { VersionYesNoBlock no = new VersionYesNoBlock(CopyDiskBlock.No, (ushort)NoCode.UnknownCommand, "Transfer parameters are invalid."); Channel.Send(Serializer.Serialize(no)); } else { // We're OK. Save the parameters and send a Yes response. VersionYesNoBlock yes = new VersionYesNoBlock(CopyDiskBlock.Yes, 0, "You are cleared for launch."); Channel.Send(Serializer.Serialize(yes)); // // And send the requested range of pages if this is a Retrieve operation // (otherwise wait for a HereIsDiskPage block from the client.) // if (blockType == CopyDiskBlock.RetrieveDisk) { for (int i = _startAddress; i < _endAddress + 1; i++) { DiabloDiskSector sector = _pack.GetSector(i); HereIsDiskPageBlock block = new HereIsDiskPageBlock(sector.Header, sector.Label, sector.Data); Channel.Send(Serializer.Serialize(block), false /* do not flush */); if ((i % 100) == 0) { Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Sent page {0}", i); } } // Send "EndOfTransfer" block to finish the transfer. EndOfTransferBlock endTransfer = new EndOfTransferBlock(0); Channel.Send(Serializer.Serialize(endTransfer)); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Send done."); } else { _currentAddress = _startAddress; } } } break; case CopyDiskBlock.HereIsDiskPage: { if (_currentAddress > _endAddress) { Channel.SendAbort("Invalid address for page."); _running = false; break; } if ((_currentAddress % 100) == 0) { Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Received page {0}", _currentAddress); } if (data.Length < 512 + 16 + 4 + 4) { // Incomplete ("incorrigable") page, indicating an unreadable or empty sector, just copy // the header/label data in and leave an empty data page. HereIsDiskPageIncorrigableBlock diskPage = (HereIsDiskPageIncorrigableBlock)Serializer.Deserialize(data, typeof(HereIsDiskPageIncorrigableBlock)); DiabloDiskSector sector = new DiabloDiskSector(diskPage.Header, diskPage.Label, new byte[512]); _pack.SetSector(_currentAddress, sector); _currentAddress++; Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Page is empty / incorrigable."); } else { HereIsDiskPageBlock diskPage = (HereIsDiskPageBlock)Serializer.Deserialize(data, typeof(HereIsDiskPageBlock)); DiabloDiskSector sector = new DiabloDiskSector(diskPage.Header, diskPage.Label, diskPage.Data); _pack.SetSector(_currentAddress, sector); _currentAddress++; } } break; case CopyDiskBlock.EndOfTransfer: { // No data in block. If we aren't currently at the end of the transfer, the transfer has been aborted. // Do nothing right now. if (_currentAddress < _endAddress) { Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Transfer was aborted."); _running = false; } else { try { // Commit disk image to disk. using (FileStream packStream = new FileStream(_pack.PackName, FileMode.OpenOrCreate, FileAccess.Write)) { Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Saving {0}...", _pack.PackName); _pack.Save(packStream, true /* reverse byte order */); Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Saved."); } } catch (Exception e) { // Log error, reset state. Log.Write(LogType.Error, LogComponent.CopyDisk, "Failed to save pack {0} - {1}", _pack.PackName, e.Message); } } } break; case CopyDiskBlock.SendErrors: { Log.Write(LogType.Verbose, LogComponent.CopyDisk, "Sending error summary..."); // No data in block. Send list of errors we encountered. (There should always be none since we're perfect and have no disk errors.) HereAreErrorsBFSBlock errorBlock = new HereAreErrorsBFSBlock(0, 0); Channel.Send(Serializer.Serialize(errorBlock)); } break; default: Log.Write(LogType.Warning, LogComponent.CopyDisk, "Unhandled CopyDisk block {0}", blockType); break; } } if (OnExit != null) { OnExit(this); } }
public void SetSector(int cylinder, int track, int sector, DiabloDiskSector newSector) { _sectors[cylinder, track, sector] = newSector; }