2016-07-28 18:13:49 +01:00
|
|
|
// /***************************************************************************
|
|
|
|
|
// The Disc Image Chef
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
// Filename : TeleDisk.cs
|
|
|
|
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
|
|
|
|
//
|
2017-12-19 03:50:57 +00:00
|
|
|
// Component : Disk image plugins.
|
2016-07-28 18:13:49 +01:00
|
|
|
//
|
|
|
|
|
// --[ Description ] ----------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
// Manages Sydex TeleDisk disk images.
|
|
|
|
|
//
|
|
|
|
|
// --[ License ] --------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
// This library is free software; you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU Lesser General Public License as
|
|
|
|
|
// published by the Free Software Foundation; either version 2.1 of the
|
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
|
//
|
|
|
|
|
// This library 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
|
|
|
|
|
// Lesser General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
|
|
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
//
|
|
|
|
|
// ----------------------------------------------------------------------------
|
2017-12-19 03:50:57 +00:00
|
|
|
// Copyright © 2011-2018 Natalia Portillo
|
2017-09-27 14:54:27 +01:00
|
|
|
// For Advanced Compression support (aka LZH):
|
|
|
|
|
// Copyright © 2017 Miodrag Milanovic
|
|
|
|
|
// Copyright © 1988 Haruhiko OKUMURA
|
|
|
|
|
// Copyright © 1988 Haruyasu YOSHIZAKI
|
|
|
|
|
// Copyright © 1988 Kenji RIKITAKE
|
2016-07-28 18:13:49 +01:00
|
|
|
// ****************************************************************************/
|
2014-04-19 21:21:08 +01:00
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2017-12-19 19:33:46 +00:00
|
|
|
using System.IO;
|
2015-11-23 21:44:58 +00:00
|
|
|
using DiscImageChef.CommonTypes;
|
2017-12-19 19:33:46 +00:00
|
|
|
using DiscImageChef.Console;
|
2016-09-05 17:37:31 +01:00
|
|
|
using DiscImageChef.Filters;
|
2015-10-18 22:04:03 +01:00
|
|
|
|
2014-06-15 23:39:34 +01:00
|
|
|
namespace DiscImageChef.ImagePlugins
|
2014-04-19 21:21:08 +01:00
|
|
|
{
|
2017-12-19 20:33:03 +00:00
|
|
|
// Created following notes from Dave Dunfield
|
|
|
|
|
// http://www.classiccmp.org/dunfield/img54306/td0notes.txt
|
|
|
|
|
public class TeleDisk : ImagePlugin
|
|
|
|
|
{
|
|
|
|
|
#region Internal Structures
|
|
|
|
|
struct TD0Header
|
|
|
|
|
{
|
|
|
|
|
/// <summary>"TD" or "td" depending on compression</summary>
|
|
|
|
|
public ushort signature;
|
|
|
|
|
/// <summary>Sequence, but TeleDisk seems to complaing if != 0</summary>
|
|
|
|
|
public byte sequence;
|
|
|
|
|
/// <summary>Random, same byte for all disks in the same set</summary>
|
|
|
|
|
public byte diskSet;
|
|
|
|
|
/// <summary>TeleDisk version, major in high nibble, minor in low nibble</summary>
|
|
|
|
|
public byte version;
|
|
|
|
|
/// <summary>Data rate</summary>
|
|
|
|
|
public byte dataRate;
|
|
|
|
|
/// <summary>BIOS drive type</summary>
|
|
|
|
|
public byte driveType;
|
|
|
|
|
/// <summary>Stepping used</summary>
|
|
|
|
|
public byte stepping;
|
|
|
|
|
/// <summary>If set means image only allocates sectors marked in-use by FAT12</summary>
|
|
|
|
|
public byte dosAllocation;
|
|
|
|
|
/// <summary>Sides of disk</summary>
|
|
|
|
|
public byte sides;
|
|
|
|
|
/// <summary>CRC of all the previous</summary>
|
|
|
|
|
public ushort crc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct TDCommentBlockHeader
|
|
|
|
|
{
|
|
|
|
|
/// <summary>CRC of comment block after crc field</summary>
|
|
|
|
|
public ushort crc;
|
|
|
|
|
/// <summary>Length of comment</summary>
|
|
|
|
|
public ushort length;
|
|
|
|
|
public byte year;
|
|
|
|
|
public byte month;
|
|
|
|
|
public byte day;
|
|
|
|
|
public byte hour;
|
|
|
|
|
public byte minute;
|
|
|
|
|
public byte second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct TDTrackHeader
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Sectors in the track, 0xFF if end of disk image (there is no spoon)</summary>
|
|
|
|
|
public byte sectors;
|
|
|
|
|
/// <summary>Cylinder the head was on</summary>
|
|
|
|
|
public byte cylinder;
|
|
|
|
|
/// <summary>Head/side used</summary>
|
|
|
|
|
public byte head;
|
|
|
|
|
/// <summary>Lower byte of CRC of previous fields</summary>
|
|
|
|
|
public byte crc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct TDSectorHeader
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Cylinder as stored on sector address mark</summary>
|
|
|
|
|
public byte cylinder;
|
|
|
|
|
/// <summary>Head as stored on sector address mark</summary>
|
|
|
|
|
public byte head;
|
|
|
|
|
/// <summary>Sector number as stored on sector address mark</summary>
|
|
|
|
|
public byte sectorNumber;
|
|
|
|
|
/// <summary>Sector size</summary>
|
|
|
|
|
public byte sectorSize;
|
|
|
|
|
/// <summary>Sector flags</summary>
|
|
|
|
|
public byte flags;
|
|
|
|
|
/// <summary>Lower byte of TeleDisk CRC of sector header, data header and data block</summary>
|
|
|
|
|
public byte crc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct TDDataHeader
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Size of all data (encoded) + next field (1)</summary>
|
|
|
|
|
public ushort dataSize;
|
|
|
|
|
/// <summary>Encoding used for data block</summary>
|
|
|
|
|
public byte dataEncoding;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Internal Constants
|
|
|
|
|
// "TD" as little endian uint.
|
|
|
|
|
const ushort tdMagic = 0x4454;
|
|
|
|
|
// "td" as little endian uint. Means whole file is compressed (aka Advanced Compression)
|
|
|
|
|
const ushort tdAdvCompMagic = 0x6474;
|
|
|
|
|
|
|
|
|
|
// DataRates
|
|
|
|
|
const byte DataRate250kbps = 0x00;
|
|
|
|
|
const byte DataRate300kbps = 0x01;
|
|
|
|
|
const byte DataRate500kbps = 0x02;
|
|
|
|
|
|
|
|
|
|
// TeleDisk drive types
|
|
|
|
|
const byte DriveType525HD_DDDisk = 0x00;
|
|
|
|
|
const byte DriveType525HD = 0x01;
|
|
|
|
|
const byte DriveType525DD = 0x02;
|
|
|
|
|
const byte DriveType35DD = 0x03;
|
|
|
|
|
const byte DriveType35HD = 0x04;
|
|
|
|
|
const byte DriveType8inch = 0x05;
|
|
|
|
|
const byte DriveType35ED = 0x06;
|
|
|
|
|
|
|
|
|
|
// Stepping
|
|
|
|
|
const byte SteppingSingle = 0x00;
|
|
|
|
|
const byte SteppingDouble = 0x01;
|
|
|
|
|
const byte SteppingEvenOnly = 0x02;
|
|
|
|
|
// If this bit is set, there is a comment block
|
|
|
|
|
const byte CommentBlockPresent = 0x80;
|
|
|
|
|
|
|
|
|
|
// CRC polynomial
|
|
|
|
|
const ushort TeleDiskCRCPoly = 0xA097;
|
|
|
|
|
|
|
|
|
|
// Sector sizes table
|
|
|
|
|
const byte SectorSize128 = 0x00;
|
|
|
|
|
const byte SectorSize256 = 0x01;
|
|
|
|
|
const byte SectorSize512 = 0x02;
|
|
|
|
|
const byte SectorSize1K = 0x03;
|
|
|
|
|
const byte SectorSize2K = 0x04;
|
|
|
|
|
const byte SectorSize4K = 0x05;
|
|
|
|
|
const byte SectorSize8K = 0x06;
|
|
|
|
|
|
|
|
|
|
// Flags
|
|
|
|
|
// Address mark repeats inside same track
|
|
|
|
|
const byte FlagsSectorDuplicate = 0x01;
|
|
|
|
|
// Sector gave CRC error on reading
|
|
|
|
|
const byte FlagsSectorCRCError = 0x02;
|
|
|
|
|
// Address mark indicates deleted sector
|
|
|
|
|
const byte FlagsSectorDeleted = 0x04;
|
|
|
|
|
// Sector skipped as FAT said it's unused
|
|
|
|
|
const byte FlagsSectorSkipped = 0x10;
|
|
|
|
|
// There was an address mark, but no data following
|
|
|
|
|
const byte FlagsSectorDataless = 0x20;
|
|
|
|
|
// There was data without address mark
|
|
|
|
|
const byte FlagsSectorNoID = 0x40;
|
|
|
|
|
|
|
|
|
|
// Data block encodings
|
|
|
|
|
// Data is copied as is
|
|
|
|
|
const byte dataBlockCopy = 0x00;
|
|
|
|
|
// Data is encoded as a pair of len.value uint16s
|
|
|
|
|
const byte dataBlockPattern = 0x01;
|
|
|
|
|
// Data is encoded as RLE
|
|
|
|
|
const byte dataBlockRLE = 0x02;
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Internal variables
|
|
|
|
|
TD0Header header;
|
|
|
|
|
TDCommentBlockHeader commentHeader;
|
|
|
|
|
byte[] commentBlock;
|
|
|
|
|
// LBA, data
|
|
|
|
|
uint totalDiskSize;
|
|
|
|
|
bool ADiskCRCHasFailed;
|
|
|
|
|
List<ulong> SectorsWhereCRCHasFailed;
|
|
|
|
|
// Cylinder by head, sector data matrix
|
|
|
|
|
byte[][][][] sectorsData;
|
|
|
|
|
Stream inStream;
|
|
|
|
|
byte[] leadOut;
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
public TeleDisk()
|
|
|
|
|
{
|
|
|
|
|
Name = "Sydex TeleDisk";
|
|
|
|
|
PluginUUID = new Guid("0240B7B1-E959-4CDC-B0BD-386D6E467B88");
|
|
|
|
|
ImageInfo = new ImageInfo();
|
|
|
|
|
ImageInfo.readableSectorTags = new List<SectorTagType>();
|
|
|
|
|
ImageInfo.readableMediaTags = new List<MediaTagType>();
|
|
|
|
|
ImageInfo.imageHasPartitions = false;
|
|
|
|
|
ImageInfo.imageHasSessions = false;
|
|
|
|
|
ImageInfo.imageApplication = "Sydex TeleDisk";
|
|
|
|
|
ImageInfo.imageComments = null;
|
|
|
|
|
ImageInfo.imageCreator = 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;
|
|
|
|
|
ADiskCRCHasFailed = false;
|
|
|
|
|
SectorsWhereCRCHasFailed = new List<ulong>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool IdentifyImage(Filter imageFilter)
|
|
|
|
|
{
|
|
|
|
|
header = new TD0Header();
|
|
|
|
|
byte[] headerBytes = new byte[12];
|
|
|
|
|
Stream stream = imageFilter.GetDataForkStream();
|
|
|
|
|
stream.Seek(0, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
stream.Read(headerBytes, 0, 12);
|
|
|
|
|
|
|
|
|
|
header.signature = BitConverter.ToUInt16(headerBytes, 0);
|
|
|
|
|
|
|
|
|
|
if(header.signature != tdMagic && header.signature != tdAdvCompMagic) return false;
|
|
|
|
|
|
|
|
|
|
header.sequence = headerBytes[2];
|
|
|
|
|
header.diskSet = headerBytes[3];
|
|
|
|
|
header.version = headerBytes[4];
|
|
|
|
|
header.dataRate = headerBytes[5];
|
|
|
|
|
header.driveType = headerBytes[6];
|
|
|
|
|
header.stepping = headerBytes[7];
|
|
|
|
|
header.dosAllocation = headerBytes[8];
|
|
|
|
|
header.sides = headerBytes[9];
|
|
|
|
|
header.crc = BitConverter.ToUInt16(headerBytes, 10);
|
|
|
|
|
|
|
|
|
|
byte[] headerBytesForCRC = new byte[10];
|
|
|
|
|
Array.Copy(headerBytes, headerBytesForCRC, 10);
|
|
|
|
|
ushort calculatedHeaderCRC = TeleDiskCRC(0x0000, headerBytesForCRC);
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.signature = 0x{0:X4}", header.signature);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.sequence = 0x{0:X2}", header.sequence);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.diskSet = 0x{0:X2}", header.diskSet);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.version = 0x{0:X2}", header.version);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.dataRate = 0x{0:X2}", header.dataRate);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.driveType = 0x{0:X2}", header.driveType);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.stepping = 0x{0:X2}", header.stepping);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.dosAllocation = 0x{0:X2}", header.dosAllocation);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.sides = 0x{0:X2}", header.sides);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.crc = 0x{0:X4}", header.crc);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "calculated header crc = 0x{0:X4}", calculatedHeaderCRC);
|
|
|
|
|
|
|
|
|
|
// We need more checks as the magic is too simply.
|
|
|
|
|
// This may deny legal images
|
|
|
|
|
|
|
|
|
|
// That would be much of a coincidence
|
|
|
|
|
if(header.crc == calculatedHeaderCRC) return true;
|
|
|
|
|
|
|
|
|
|
if(header.sequence != 0x00) return false;
|
|
|
|
|
|
|
|
|
|
if(header.dataRate != DataRate250kbps && header.dataRate != DataRate300kbps &&
|
|
|
|
|
header.dataRate != DataRate500kbps) return false;
|
|
|
|
|
|
|
|
|
|
if(header.driveType != DriveType35DD && header.driveType != DriveType35ED &&
|
|
|
|
|
header.driveType != DriveType35HD && header.driveType != DriveType525DD &&
|
|
|
|
|
header.driveType != DriveType525HD && header.driveType != DriveType525HD_DDDisk &&
|
|
|
|
|
header.driveType != DriveType8inch) return false;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool OpenImage(Filter imageFilter)
|
|
|
|
|
{
|
|
|
|
|
header = new TD0Header();
|
|
|
|
|
byte[] headerBytes = new byte[12];
|
|
|
|
|
inStream = imageFilter.GetDataForkStream();
|
|
|
|
|
MemoryStream stream = new MemoryStream();
|
|
|
|
|
inStream.Seek(0, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
inStream.Read(headerBytes, 0, 12);
|
|
|
|
|
stream.Write(headerBytes, 0, 12);
|
|
|
|
|
|
|
|
|
|
header.signature = BitConverter.ToUInt16(headerBytes, 0);
|
|
|
|
|
|
|
|
|
|
if(header.signature != tdMagic && header.signature != tdAdvCompMagic) return false;
|
|
|
|
|
|
|
|
|
|
header.sequence = headerBytes[2];
|
|
|
|
|
header.diskSet = headerBytes[3];
|
|
|
|
|
header.version = headerBytes[4];
|
|
|
|
|
header.dataRate = headerBytes[5];
|
|
|
|
|
header.driveType = headerBytes[6];
|
|
|
|
|
header.stepping = headerBytes[7];
|
|
|
|
|
header.dosAllocation = headerBytes[8];
|
|
|
|
|
header.sides = headerBytes[9];
|
|
|
|
|
header.crc = BitConverter.ToUInt16(headerBytes, 10);
|
|
|
|
|
|
|
|
|
|
ImageInfo.imageName = Path.GetFileNameWithoutExtension(imageFilter.GetFilename());
|
|
|
|
|
ImageInfo.imageVersion = string.Format("{0}.{1}", (header.version & 0xF0) >> 4, header.version & 0x0F);
|
|
|
|
|
ImageInfo.imageApplication = ImageInfo.imageVersion;
|
|
|
|
|
|
|
|
|
|
byte[] headerBytesForCRC = new byte[10];
|
|
|
|
|
Array.Copy(headerBytes, headerBytesForCRC, 10);
|
|
|
|
|
ushort calculatedHeaderCRC = TeleDiskCRC(0x0000, headerBytesForCRC);
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.signature = 0x{0:X4}", header.signature);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.sequence = 0x{0:X2}", header.sequence);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.diskSet = 0x{0:X2}", header.diskSet);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.version = 0x{0:X2}", header.version);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.dataRate = 0x{0:X2}", header.dataRate);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.driveType = 0x{0:X2}", header.driveType);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.stepping = 0x{0:X2}", header.stepping);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.dosAllocation = 0x{0:X2}", header.dosAllocation);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.sides = 0x{0:X2}", header.sides);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "header.crc = 0x{0:X4}", header.crc);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "calculated header crc = 0x{0:X4}", calculatedHeaderCRC);
|
|
|
|
|
|
|
|
|
|
// We need more checks as the magic is too simply.
|
|
|
|
|
// This may deny legal images
|
|
|
|
|
|
|
|
|
|
// That would be much of a coincidence
|
|
|
|
|
if(header.crc != calculatedHeaderCRC)
|
|
|
|
|
{
|
|
|
|
|
ADiskCRCHasFailed = true;
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Calculated CRC does not coincide with stored one.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(header.sequence != 0x00) return false;
|
|
|
|
|
|
|
|
|
|
if(header.dataRate != DataRate250kbps && header.dataRate != DataRate300kbps &&
|
|
|
|
|
header.dataRate != DataRate500kbps) return false;
|
|
|
|
|
|
|
|
|
|
if(header.driveType != DriveType35DD && header.driveType != DriveType35ED &&
|
|
|
|
|
header.driveType != DriveType35HD && header.driveType != DriveType525DD &&
|
|
|
|
|
header.driveType != DriveType525HD && header.driveType != DriveType525HD_DDDisk &&
|
|
|
|
|
header.driveType != DriveType8inch) return false;
|
|
|
|
|
|
|
|
|
|
if(header.signature == tdAdvCompMagic)
|
|
|
|
|
{
|
|
|
|
|
int rd;
|
|
|
|
|
byte[] obuf = new byte[BUFSZ];
|
|
|
|
|
inStream.Seek(12, SeekOrigin.Begin);
|
|
|
|
|
stream.Seek(12, SeekOrigin.Begin);
|
|
|
|
|
init_Decode();
|
|
|
|
|
do { if((rd = Decode(out obuf, BUFSZ)) > 0) stream.Write(obuf, 0, rd); }
|
|
|
|
|
while(rd == BUFSZ);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Not using Stream.CopyTo() because it's failing with LZIP
|
|
|
|
|
byte[] copybuf = new byte[inStream.Length];
|
|
|
|
|
inStream.Seek(0, SeekOrigin.Begin);
|
|
|
|
|
inStream.Read(copybuf, 0, copybuf.Length);
|
|
|
|
|
stream.Seek(0, SeekOrigin.Begin);
|
|
|
|
|
stream.Write(copybuf, 0, copybuf.Length);
|
|
|
|
|
copybuf = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream.Seek(12, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
ImageInfo.imageCreationTime = DateTime.MinValue;
|
|
|
|
|
|
|
|
|
|
if((header.stepping & CommentBlockPresent) == CommentBlockPresent)
|
|
|
|
|
{
|
|
|
|
|
commentHeader = new TDCommentBlockHeader();
|
|
|
|
|
|
|
|
|
|
byte[] commentHeaderBytes = new byte[10];
|
|
|
|
|
byte[] commentBlockForCRC;
|
|
|
|
|
|
|
|
|
|
stream.Read(commentHeaderBytes, 0, 10);
|
|
|
|
|
commentHeader.crc = BitConverter.ToUInt16(commentHeaderBytes, 0);
|
|
|
|
|
commentHeader.length = BitConverter.ToUInt16(commentHeaderBytes, 2);
|
|
|
|
|
commentHeader.year = commentHeaderBytes[4];
|
|
|
|
|
commentHeader.month = commentHeaderBytes[5];
|
|
|
|
|
commentHeader.day = commentHeaderBytes[6];
|
|
|
|
|
commentHeader.hour = commentHeaderBytes[7];
|
|
|
|
|
commentHeader.minute = commentHeaderBytes[8];
|
|
|
|
|
commentHeader.second = commentHeaderBytes[9];
|
|
|
|
|
|
|
|
|
|
commentBlock = new byte[commentHeader.length];
|
|
|
|
|
stream.Read(commentBlock, 0, commentHeader.length);
|
|
|
|
|
|
|
|
|
|
commentBlockForCRC = new byte[commentHeader.length + 8];
|
|
|
|
|
Array.Copy(commentHeaderBytes, 2, commentBlockForCRC, 0, 8);
|
|
|
|
|
Array.Copy(commentBlock, 0, commentBlockForCRC, 8, commentHeader.length);
|
|
|
|
|
|
|
|
|
|
ushort cmtcrc = TeleDiskCRC(0, commentBlockForCRC);
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Comment header");
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tcommentheader.crc = 0x{0:X4}", commentHeader.crc);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tCalculated CRC = 0x{0:X4}", cmtcrc);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tcommentheader.length = {0} bytes",
|
|
|
|
|
commentHeader.length);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tcommentheader.year = {0}", commentHeader.year);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tcommentheader.month = {0}", commentHeader.month);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tcommentheader.day = {0}", commentHeader.day);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tcommentheader.hour = {0}", commentHeader.hour);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tcommentheader.minute = {0}", commentHeader.minute);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tcommentheader.second = {0}", commentHeader.second);
|
|
|
|
|
|
|
|
|
|
ADiskCRCHasFailed |= cmtcrc != commentHeader.crc;
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < commentBlock.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
// Replace NULLs, used by TeleDisk as newline markers, with UNIX newline marker
|
|
|
|
|
if(commentBlock[i] == 0x00) commentBlock[i] = 0x0A;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImageInfo.imageComments = System.Text.Encoding.ASCII.GetString(commentBlock);
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Comment");
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "{0}", ImageInfo.imageComments);
|
|
|
|
|
|
|
|
|
|
ImageInfo.imageCreationTime = new DateTime(commentHeader.year + 1900, commentHeader.month + 1,
|
|
|
|
|
commentHeader.day, commentHeader.hour, commentHeader.minute,
|
|
|
|
|
commentHeader.second, DateTimeKind.Unspecified);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(ImageInfo.imageCreationTime == DateTime.MinValue)
|
|
|
|
|
ImageInfo.imageCreationTime = imageFilter.GetCreationTime();
|
|
|
|
|
ImageInfo.imageLastModificationTime = imageFilter.GetLastWriteTime();
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Image created on {0}", ImageInfo.imageCreationTime);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Image modified on {0}", ImageInfo.imageLastModificationTime);
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Parsing image");
|
|
|
|
|
|
|
|
|
|
totalDiskSize = 0;
|
|
|
|
|
ImageInfo.imageSize = 0;
|
|
|
|
|
|
|
|
|
|
int totalCylinders = -1;
|
|
|
|
|
int totalHeads = -1;
|
|
|
|
|
int maxSector = -1;
|
|
|
|
|
int totalSectors = 0;
|
|
|
|
|
long currentPos = stream.Position;
|
|
|
|
|
ImageInfo.sectorSize = uint.MaxValue;
|
|
|
|
|
ImageInfo.sectorsPerTrack = uint.MaxValue;
|
|
|
|
|
|
|
|
|
|
// Count cylinders
|
|
|
|
|
while(true)
|
|
|
|
|
{
|
|
|
|
|
TDTrackHeader TDTrack = new TDTrackHeader();
|
|
|
|
|
|
|
|
|
|
TDTrack.sectors = (byte)stream.ReadByte();
|
|
|
|
|
TDTrack.cylinder = (byte)stream.ReadByte();
|
|
|
|
|
TDTrack.head = (byte)stream.ReadByte();
|
|
|
|
|
TDTrack.crc = (byte)stream.ReadByte();
|
|
|
|
|
|
|
|
|
|
if(TDTrack.cylinder > totalCylinders) totalCylinders = TDTrack.cylinder;
|
|
|
|
|
if(TDTrack.head > totalHeads) totalHeads = TDTrack.head;
|
|
|
|
|
|
|
|
|
|
if(TDTrack.sectors == 0xFF) // End of disk image
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
for(byte processedSectors = 0; processedSectors < TDTrack.sectors; processedSectors++)
|
|
|
|
|
{
|
|
|
|
|
TDSectorHeader TDSector = new TDSectorHeader();
|
|
|
|
|
TDDataHeader TDData = new TDDataHeader();
|
|
|
|
|
byte[] dataSizeBytes = new byte[2];
|
|
|
|
|
byte[] data;
|
|
|
|
|
|
|
|
|
|
TDSector.cylinder = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.head = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.sectorNumber = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.sectorSize = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.flags = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.crc = (byte)stream.ReadByte();
|
|
|
|
|
|
|
|
|
|
if(TDSector.sectorNumber > maxSector) maxSector = TDSector.sectorNumber;
|
|
|
|
|
|
|
|
|
|
if((TDSector.flags & FlagsSectorDataless) != FlagsSectorDataless &&
|
|
|
|
|
(TDSector.flags & FlagsSectorSkipped) != FlagsSectorSkipped)
|
|
|
|
|
{
|
|
|
|
|
stream.Read(dataSizeBytes, 0, 2);
|
|
|
|
|
TDData.dataSize = BitConverter.ToUInt16(dataSizeBytes, 0);
|
|
|
|
|
TDData.dataSize--; // Sydex decided to including dataEncoding byte as part of it
|
|
|
|
|
TDData.dataEncoding = (byte)stream.ReadByte();
|
|
|
|
|
data = new byte[TDData.dataSize];
|
|
|
|
|
stream.Read(data, 0, TDData.dataSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(128 << TDSector.sectorSize < ImageInfo.sectorSize)
|
|
|
|
|
ImageInfo.sectorSize = (uint)(128 << TDSector.sectorSize);
|
|
|
|
|
|
|
|
|
|
totalSectors++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
totalCylinders++;
|
|
|
|
|
totalHeads++;
|
|
|
|
|
|
|
|
|
|
if(totalCylinders <= 0 || totalHeads <= 0)
|
|
|
|
|
throw new ImageNotSupportedException("No cylinders or heads found");
|
|
|
|
|
|
|
|
|
|
bool hasLeadOutOnHead0 = false;
|
|
|
|
|
bool hasLeadOutOnHead1 = false;
|
|
|
|
|
ImageInfo.cylinders = (ushort)totalCylinders;
|
|
|
|
|
ImageInfo.heads = (byte)totalHeads;
|
|
|
|
|
|
|
|
|
|
// Count sectors per track
|
|
|
|
|
stream.Seek(currentPos, SeekOrigin.Begin);
|
|
|
|
|
while(true)
|
|
|
|
|
{
|
|
|
|
|
TDTrackHeader TDTrack = new TDTrackHeader();
|
|
|
|
|
|
|
|
|
|
TDTrack.sectors = (byte)stream.ReadByte();
|
|
|
|
|
TDTrack.cylinder = (byte)stream.ReadByte();
|
|
|
|
|
TDTrack.head = (byte)stream.ReadByte();
|
|
|
|
|
TDTrack.crc = (byte)stream.ReadByte();
|
|
|
|
|
|
|
|
|
|
if(TDTrack.sectors == 0xFF) // End of disk image
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if(TDTrack.sectors < ImageInfo.sectorsPerTrack)
|
|
|
|
|
{
|
|
|
|
|
if(TDTrack.cylinder + 1 == totalCylinders)
|
|
|
|
|
{
|
|
|
|
|
hasLeadOutOnHead0 |= TDTrack.head == 0;
|
|
|
|
|
hasLeadOutOnHead1 |= TDTrack.head == 1;
|
|
|
|
|
if(ImageInfo.cylinders == totalCylinders) ImageInfo.cylinders--;
|
|
|
|
|
}
|
|
|
|
|
else ImageInfo.sectorsPerTrack = TDTrack.sectors;
|
|
|
|
|
}
|
|
|
|
|
for(byte processedSectors = 0; processedSectors < TDTrack.sectors; processedSectors++)
|
|
|
|
|
{
|
|
|
|
|
TDSectorHeader TDSector = new TDSectorHeader();
|
|
|
|
|
TDDataHeader TDData = new TDDataHeader();
|
|
|
|
|
byte[] dataSizeBytes = new byte[2];
|
|
|
|
|
byte[] data;
|
|
|
|
|
|
|
|
|
|
TDSector.cylinder = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.head = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.sectorNumber = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.sectorSize = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.flags = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.crc = (byte)stream.ReadByte();
|
|
|
|
|
|
|
|
|
|
if((TDSector.flags & FlagsSectorDataless) != FlagsSectorDataless &&
|
|
|
|
|
(TDSector.flags & FlagsSectorSkipped) != FlagsSectorSkipped)
|
|
|
|
|
{
|
|
|
|
|
stream.Read(dataSizeBytes, 0, 2);
|
|
|
|
|
TDData.dataSize = BitConverter.ToUInt16(dataSizeBytes, 0);
|
|
|
|
|
TDData.dataSize--; // Sydex decided to including dataEncoding byte as part of it
|
|
|
|
|
TDData.dataEncoding = (byte)stream.ReadByte();
|
|
|
|
|
data = new byte[TDData.dataSize];
|
|
|
|
|
stream.Read(data, 0, TDData.dataSize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sectorsData = new byte[totalCylinders][][][];
|
|
|
|
|
// Total sectors per track
|
|
|
|
|
uint[][] spts = new uint[totalCylinders][];
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin",
|
|
|
|
|
"Found {0} cylinders and {1} heads with a maximum sector number of {2}",
|
|
|
|
|
totalCylinders, totalHeads, maxSector);
|
|
|
|
|
|
|
|
|
|
// Create heads
|
|
|
|
|
for(int i = 0; i < totalCylinders; i++)
|
|
|
|
|
{
|
|
|
|
|
sectorsData[i] = new byte[totalHeads][][];
|
|
|
|
|
spts[i] = new uint[totalHeads];
|
|
|
|
|
|
|
|
|
|
for(int j = 0; j < totalHeads; j++) sectorsData[i][j] = new byte[maxSector + 1][];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Decode the image
|
|
|
|
|
stream.Seek(currentPos, SeekOrigin.Begin);
|
|
|
|
|
while(true)
|
|
|
|
|
{
|
|
|
|
|
TDTrackHeader TDTrack = new TDTrackHeader();
|
|
|
|
|
byte[] TDTrackForCRC = new byte[3];
|
|
|
|
|
byte TDTrackCalculatedCRC;
|
|
|
|
|
|
|
|
|
|
TDTrack.sectors = (byte)stream.ReadByte();
|
|
|
|
|
TDTrack.cylinder = (byte)stream.ReadByte();
|
|
|
|
|
TDTrack.head = (byte)stream.ReadByte();
|
|
|
|
|
TDTrack.crc = (byte)stream.ReadByte();
|
|
|
|
|
|
|
|
|
|
TDTrackForCRC[0] = TDTrack.sectors;
|
|
|
|
|
TDTrackForCRC[1] = TDTrack.cylinder;
|
|
|
|
|
TDTrackForCRC[2] = TDTrack.head;
|
|
|
|
|
|
|
|
|
|
TDTrackCalculatedCRC = (byte)(TeleDiskCRC(0, TDTrackForCRC) & 0xFF);
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Track follows");
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tTrack cylinder: {0}\t", TDTrack.cylinder);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tTrack head: {0}\t", TDTrack.head);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tSectors in track: {0}\t", TDTrack.sectors);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tTrack header CRC: 0x{0:X2} (calculated 0x{1:X2})\t",
|
|
|
|
|
TDTrack.crc, TDTrackCalculatedCRC);
|
|
|
|
|
|
|
|
|
|
ADiskCRCHasFailed |= TDTrackCalculatedCRC != TDTrack.crc;
|
|
|
|
|
|
|
|
|
|
if(TDTrack.sectors == 0xFF) // End of disk image
|
|
|
|
|
{
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "End of disk image arrived");
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Total of {0} data sectors, for {1} bytes",
|
|
|
|
|
totalSectors, totalDiskSize);
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(byte processedSectors = 0; processedSectors < TDTrack.sectors; processedSectors++)
|
|
|
|
|
{
|
|
|
|
|
TDSectorHeader TDSector = new TDSectorHeader();
|
|
|
|
|
TDDataHeader TDData = new TDDataHeader();
|
|
|
|
|
byte[] dataSizeBytes = new byte[2];
|
|
|
|
|
byte[] data;
|
|
|
|
|
byte[] decodedData;
|
|
|
|
|
|
|
|
|
|
TDSector.cylinder = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.head = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.sectorNumber = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.sectorSize = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.flags = (byte)stream.ReadByte();
|
|
|
|
|
TDSector.crc = (byte)stream.ReadByte();
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\tSector follows");
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\t\tAddressMark cylinder: {0}", TDSector.cylinder);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\t\tAddressMark head: {0}", TDSector.head);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\t\tAddressMark sector number: {0}",
|
|
|
|
|
TDSector.sectorNumber);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\t\tSector size: {0}", TDSector.sectorSize);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\t\tSector flags: 0x{0:X2}", TDSector.flags);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\t\tSector CRC (plus headers): 0x{0:X2}",
|
|
|
|
|
TDSector.crc);
|
|
|
|
|
|
|
|
|
|
uint LBA = (uint)((TDSector.cylinder * header.sides * ImageInfo.sectorsPerTrack) +
|
|
|
|
|
(TDSector.head * ImageInfo.sectorsPerTrack) + (TDSector.sectorNumber - 1));
|
|
|
|
|
if((TDSector.flags & FlagsSectorDataless) != FlagsSectorDataless &&
|
|
|
|
|
(TDSector.flags & FlagsSectorSkipped) != FlagsSectorSkipped)
|
|
|
|
|
{
|
|
|
|
|
stream.Read(dataSizeBytes, 0, 2);
|
|
|
|
|
TDData.dataSize = BitConverter.ToUInt16(dataSizeBytes, 0);
|
|
|
|
|
TDData.dataSize--; // Sydex decided to including dataEncoding byte as part of it
|
|
|
|
|
ImageInfo.imageSize += TDData.dataSize;
|
|
|
|
|
TDData.dataEncoding = (byte)stream.ReadByte();
|
|
|
|
|
data = new byte[TDData.dataSize];
|
|
|
|
|
stream.Read(data, 0, TDData.dataSize);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\t\tData size (in-image): {0}", TDData.dataSize);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\t\tData encoding: 0x{0:X2}",
|
|
|
|
|
TDData.dataEncoding);
|
|
|
|
|
|
|
|
|
|
decodedData = DecodeTeleDiskData(TDSector.sectorSize, TDData.dataEncoding, data);
|
|
|
|
|
|
|
|
|
|
byte TDSectorCalculatedCRC = (byte)(TeleDiskCRC(0, decodedData) & 0xFF);
|
|
|
|
|
|
|
|
|
|
if(TDSectorCalculatedCRC != TDSector.crc)
|
|
|
|
|
{
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin",
|
|
|
|
|
"Sector {0}:{3}:{4} calculated CRC 0x{1:X2} differs from stored CRC 0x{2:X2}",
|
|
|
|
|
TDTrack.cylinder, TDSectorCalculatedCRC, TDSector.crc,
|
|
|
|
|
TDTrack.cylinder, TDSector.sectorNumber);
|
|
|
|
|
if((TDSector.flags & FlagsSectorNoID) != FlagsSectorNoID) SectorsWhereCRCHasFailed.Add(LBA);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else decodedData = new byte[128 << TDSector.sectorSize];
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "\t\tLBA: {0}", LBA);
|
|
|
|
|
|
|
|
|
|
if((TDSector.flags & FlagsSectorNoID) != FlagsSectorNoID)
|
|
|
|
|
{
|
|
|
|
|
if(sectorsData[TDTrack.cylinder][TDTrack.head][TDSector.sectorNumber] != null)
|
|
|
|
|
{
|
|
|
|
|
if((TDSector.flags & FlagsSectorDuplicate) == FlagsSectorDuplicate)
|
|
|
|
|
{
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin",
|
|
|
|
|
"\t\tSector {0} on cylinder {1} head {2} is duplicate, and marked so",
|
|
|
|
|
TDSector.sectorNumber, TDSector.cylinder, TDSector.head);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin",
|
|
|
|
|
"\t\tSector {0} on cylinder {1} head {2} is duplicate, but is not marked so",
|
|
|
|
|
TDSector.sectorNumber, TDSector.cylinder, TDSector.head);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
sectorsData[TDTrack.cylinder][TDTrack.head][TDSector.sectorNumber] = decodedData;
|
|
|
|
|
totalDiskSize += (uint)decodedData.Length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MemoryStream leadOutMs = new MemoryStream();
|
|
|
|
|
if(hasLeadOutOnHead0)
|
|
|
|
|
{
|
|
|
|
|
for(int i = 0; i < sectorsData[totalCylinders - 1][0].Length; i++)
|
|
|
|
|
{
|
|
|
|
|
if(sectorsData[totalCylinders - 1][0][i] != null)
|
|
|
|
|
leadOutMs.Write(sectorsData[totalCylinders - 1][0][i], 0,
|
|
|
|
|
sectorsData[totalCylinders - 1][0][i].Length);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(hasLeadOutOnHead1)
|
|
|
|
|
{
|
|
|
|
|
for(int i = 0; i < sectorsData[totalCylinders - 1][1].Length; i++)
|
|
|
|
|
{
|
|
|
|
|
if(sectorsData[totalCylinders - 1][1][i] != null)
|
|
|
|
|
leadOutMs.Write(sectorsData[totalCylinders - 1][1][i], 0,
|
|
|
|
|
sectorsData[totalCylinders - 1][1][i].Length);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(leadOutMs.Length != 0)
|
|
|
|
|
{
|
|
|
|
|
leadOut = leadOutMs.ToArray();
|
|
|
|
|
ImageInfo.readableMediaTags.Add(MediaTagType.Floppy_LeadOut);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImageInfo.sectors = ImageInfo.cylinders * ImageInfo.heads * ImageInfo.sectorsPerTrack;
|
|
|
|
|
ImageInfo.mediaType = DecodeTeleDiskDiskType();
|
|
|
|
|
|
|
|
|
|
ImageInfo.xmlMediaType = XmlMediaType.BlockMedia;
|
|
|
|
|
|
|
|
|
|
DicConsole.VerboseWriteLine("TeleDisk image contains a disk of type {0}", ImageInfo.mediaType);
|
|
|
|
|
if(!string.IsNullOrEmpty(ImageInfo.imageComments))
|
|
|
|
|
DicConsole.VerboseWriteLine("TeleDisk comments: {0}", ImageInfo.imageComments);
|
|
|
|
|
|
|
|
|
|
inStream.Dispose();
|
|
|
|
|
stream.Dispose();
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
FileStream debugFs = new FileStream("debug.img", FileMode.CreateNew, FileAccess.Write);
|
|
|
|
|
for(ulong i = 0; i < ImageInfo.sectors; i++)
|
|
|
|
|
debugFs.Write(ReadSector(i), 0, (int)ImageInfo.sectorSize);
|
|
|
|
|
debugFs.Dispose();
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool ImageHasPartitions()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.imageHasPartitions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override ulong GetImageSize()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.imageSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override ulong GetSectors()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.sectors;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override uint GetSectorSize()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.sectorSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override byte[] ReadSector(ulong sectorAddress)
|
|
|
|
|
{
|
|
|
|
|
(ushort cylinder, byte head, byte sector) = LbaToChs(sectorAddress);
|
|
|
|
|
|
|
|
|
|
if(cylinder >= sectorsData.Length)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found");
|
|
|
|
|
|
|
|
|
|
if(head >= sectorsData[cylinder].Length)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found");
|
|
|
|
|
|
|
|
|
|
if(sector > sectorsData[cylinder][head].Length)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found");
|
|
|
|
|
|
|
|
|
|
return sectorsData[cylinder][head][sector];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override byte[] ReadSectors(ulong sectorAddress, uint length)
|
|
|
|
|
{
|
|
|
|
|
if(sectorAddress > ImageInfo.sectors - 1)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found");
|
|
|
|
|
|
|
|
|
|
if(sectorAddress + length > ImageInfo.sectors)
|
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available");
|
|
|
|
|
|
|
|
|
|
MemoryStream buffer = new MemoryStream();
|
|
|
|
|
for(uint i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
byte[] sector = ReadSector(sectorAddress + i);
|
|
|
|
|
if(sector == null) sector = new byte[ImageInfo.sectorSize];
|
|
|
|
|
buffer.Write(sector, 0, sector.Length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return buffer.ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(ushort cylinder, byte head, byte sector) LbaToChs(ulong lba)
|
|
|
|
|
{
|
|
|
|
|
ushort cylinder = (ushort)(lba / (ImageInfo.heads * ImageInfo.sectorsPerTrack));
|
|
|
|
|
byte head = (byte)((lba / ImageInfo.sectorsPerTrack) % ImageInfo.heads);
|
|
|
|
|
byte sector = (byte)((lba % ImageInfo.sectorsPerTrack) + 1);
|
|
|
|
|
|
|
|
|
|
return (cylinder, head, sector);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override byte[] ReadSectorLong(ulong sectorAddress)
|
|
|
|
|
{
|
|
|
|
|
return ReadSectors(sectorAddress, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override byte[] ReadSectorsLong(ulong sectorAddress, uint length)
|
|
|
|
|
{
|
|
|
|
|
return ReadSectors(sectorAddress, length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetImageFormat()
|
|
|
|
|
{
|
|
|
|
|
return "Sydex TeleDisk";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetImageVersion()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.imageVersion;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetImageApplication()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.imageApplication;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetImageApplicationVersion()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.imageApplicationVersion;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override DateTime GetImageCreationTime()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.imageCreationTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override DateTime GetImageLastModificationTime()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.imageLastModificationTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetImageName()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.imageName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override MediaType GetMediaType()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.mediaType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool? VerifySector(ulong sectorAddress)
|
|
|
|
|
{
|
|
|
|
|
return !SectorsWhereCRCHasFailed.Contains(sectorAddress);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool? VerifySector(ulong sectorAddress, uint track)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool? VerifySectors(ulong sectorAddress, uint length, out List<ulong> FailingLBAs,
|
|
|
|
|
out List<ulong> UnknownLBAs)
|
|
|
|
|
{
|
|
|
|
|
FailingLBAs = new List<ulong>();
|
|
|
|
|
UnknownLBAs = new List<ulong>();
|
|
|
|
|
|
|
|
|
|
for(ulong i = sectorAddress; i < sectorAddress + length; i++)
|
|
|
|
|
if(SectorsWhereCRCHasFailed.Contains(sectorAddress)) FailingLBAs.Add(sectorAddress);
|
|
|
|
|
|
|
|
|
|
return FailingLBAs.Count <= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List<ulong> FailingLBAs,
|
|
|
|
|
out List<ulong> UnknownLBAs)
|
|
|
|
|
{
|
|
|
|
|
FailingLBAs = new List<ulong>();
|
|
|
|
|
UnknownLBAs = new List<ulong>();
|
|
|
|
|
|
|
|
|
|
for(ulong i = sectorAddress; i < sectorAddress + length; i++) UnknownLBAs.Add(i);
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool? VerifyMediaImage()
|
|
|
|
|
{
|
|
|
|
|
return ADiskCRCHasFailed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Private methods
|
|
|
|
|
static ushort TeleDiskCRC(ushort crc, byte[] buffer)
|
|
|
|
|
{
|
|
|
|
|
int counter = 0;
|
|
|
|
|
|
|
|
|
|
while(counter < buffer.Length)
|
|
|
|
|
{
|
|
|
|
|
crc ^= (ushort)((buffer[counter] & 0xFF) << 8);
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 8; i++)
|
|
|
|
|
{
|
|
|
|
|
if((crc & 0x8000) > 0) crc = (ushort)((crc << 1) ^ TeleDiskCRCPoly);
|
|
|
|
|
else crc = (ushort)(crc << 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
counter++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return crc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static byte[] DecodeTeleDiskData(byte sectorSize, byte encodingType, byte[] encodedData)
|
|
|
|
|
{
|
|
|
|
|
byte[] decodedData;
|
|
|
|
|
switch(sectorSize)
|
|
|
|
|
{
|
|
|
|
|
case SectorSize128:
|
|
|
|
|
decodedData = new byte[128];
|
|
|
|
|
break;
|
|
|
|
|
case SectorSize256:
|
|
|
|
|
decodedData = new byte[256];
|
|
|
|
|
break;
|
|
|
|
|
case SectorSize512:
|
|
|
|
|
decodedData = new byte[512];
|
|
|
|
|
break;
|
|
|
|
|
case SectorSize1K:
|
|
|
|
|
decodedData = new byte[1024];
|
|
|
|
|
break;
|
|
|
|
|
case SectorSize2K:
|
|
|
|
|
decodedData = new byte[2048];
|
|
|
|
|
break;
|
|
|
|
|
case SectorSize4K:
|
|
|
|
|
decodedData = new byte[4096];
|
|
|
|
|
break;
|
|
|
|
|
case SectorSize8K:
|
|
|
|
|
decodedData = new byte[8192];
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
throw new ImageNotSupportedException(string.Format("Sector size {0} is incorrect.", sectorSize));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch(encodingType)
|
|
|
|
|
{
|
|
|
|
|
case dataBlockCopy:
|
|
|
|
|
Array.Copy(encodedData, decodedData, decodedData.Length);
|
|
|
|
|
break;
|
|
|
|
|
case dataBlockPattern:
|
|
|
|
|
{
|
|
|
|
|
int ins = 0;
|
|
|
|
|
int outs = 0;
|
|
|
|
|
while(ins < encodedData.Length)
|
|
|
|
|
{
|
|
|
|
|
ushort repeatNumber;
|
|
|
|
|
byte[] repeatValue = new byte[2];
|
|
|
|
|
|
|
|
|
|
repeatNumber = BitConverter.ToUInt16(encodedData, ins);
|
|
|
|
|
Array.Copy(encodedData, ins + 2, repeatValue, 0, 2);
|
|
|
|
|
byte[] decodedPiece = new byte[repeatNumber * 2];
|
|
|
|
|
ArrayHelpers.ArrayFill(decodedPiece, repeatValue);
|
|
|
|
|
Array.Copy(decodedPiece, 0, decodedData, outs, decodedPiece.Length);
|
|
|
|
|
ins += 4;
|
|
|
|
|
outs += decodedPiece.Length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Input data size: {0} bytes",
|
|
|
|
|
encodedData.Length);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Processed input: {0} bytes",
|
|
|
|
|
ins);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Output data size: {0} bytes",
|
|
|
|
|
decodedData.Length);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "(Block pattern decoder): Processed Output: {0} bytes",
|
|
|
|
|
outs);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case dataBlockRLE:
|
|
|
|
|
{
|
|
|
|
|
int ins = 0;
|
|
|
|
|
int outs = 0;
|
|
|
|
|
while(ins < encodedData.Length)
|
|
|
|
|
{
|
|
|
|
|
byte Run;
|
|
|
|
|
byte Length;
|
|
|
|
|
byte Encoding;
|
|
|
|
|
byte[] Piece;
|
|
|
|
|
|
|
|
|
|
Encoding = encodedData[ins];
|
|
|
|
|
if(Encoding == 0x00)
|
|
|
|
|
{
|
|
|
|
|
Length = encodedData[ins + 1];
|
|
|
|
|
Array.Copy(encodedData, ins + 2, decodedData, outs, Length);
|
|
|
|
|
ins += (2 + Length);
|
|
|
|
|
outs += Length;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Length = (byte)(Encoding * 2);
|
|
|
|
|
Run = encodedData[ins + 1];
|
|
|
|
|
byte[] Part = new byte[Length];
|
|
|
|
|
Array.Copy(encodedData, ins + 2, Part, 0, Length);
|
|
|
|
|
Piece = new byte[Length * Run];
|
|
|
|
|
ArrayHelpers.ArrayFill(Piece, Part);
|
|
|
|
|
Array.Copy(Piece, 0, decodedData, outs, Piece.Length);
|
|
|
|
|
ins += (2 + Length);
|
|
|
|
|
outs += Piece.Length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Input data size: {0} bytes",
|
|
|
|
|
encodedData.Length);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Processed input: {0} bytes", ins);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Output data size: {0} bytes",
|
|
|
|
|
decodedData.Length);
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "(RLE decoder): Processed Output: {0} bytes", outs);
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
throw new ImageNotSupportedException(string.Format("Data encoding {0} is incorrect.",
|
|
|
|
|
encodingType));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return decodedData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MediaType DecodeTeleDiskDiskType()
|
|
|
|
|
{
|
|
|
|
|
switch(header.driveType)
|
|
|
|
|
{
|
|
|
|
|
case DriveType525DD:
|
|
|
|
|
case DriveType525HD_DDDisk:
|
|
|
|
|
case DriveType525HD:
|
|
|
|
|
{
|
|
|
|
|
switch(totalDiskSize)
|
|
|
|
|
{
|
|
|
|
|
case 163840:
|
|
|
|
|
{
|
|
|
|
|
// Acorn disk uses 256 bytes/sector
|
|
|
|
|
if(ImageInfo.sectorSize == 256) return MediaType.ACORN_525_SS_DD_40;
|
|
|
|
|
// DOS disks use 512 bytes/sector
|
|
|
|
|
return MediaType.DOS_525_SS_DD_8;
|
|
|
|
|
}
|
|
|
|
|
case 184320:
|
|
|
|
|
{
|
|
|
|
|
// Atari disk uses 256 bytes/sector
|
|
|
|
|
if(ImageInfo.sectorSize == 256) return MediaType.ATARI_525_DD;
|
|
|
|
|
// DOS disks use 512 bytes/sector
|
|
|
|
|
return MediaType.DOS_525_SS_DD_9;
|
|
|
|
|
}
|
|
|
|
|
case 327680:
|
|
|
|
|
{
|
|
|
|
|
// Acorn disk uses 256 bytes/sector
|
|
|
|
|
if(ImageInfo.sectorSize == 256) return MediaType.ACORN_525_SS_DD_80;
|
|
|
|
|
// DOS disks use 512 bytes/sector
|
|
|
|
|
return MediaType.DOS_525_DS_DD_8;
|
|
|
|
|
}
|
|
|
|
|
case 368640: return MediaType.DOS_525_DS_DD_9;
|
|
|
|
|
case 1228800: return MediaType.DOS_525_HD;
|
|
|
|
|
case 102400: return MediaType.ACORN_525_SS_SD_40;
|
|
|
|
|
case 204800: return MediaType.ACORN_525_SS_SD_80;
|
|
|
|
|
case 655360: return MediaType.ACORN_525_DS_DD;
|
|
|
|
|
case 92160: return MediaType.ATARI_525_SD;
|
|
|
|
|
case 133120: return MediaType.ATARI_525_ED;
|
|
|
|
|
case 1310720: return MediaType.NEC_525_HD;
|
|
|
|
|
case 1261568: return MediaType.SHARP_525;
|
|
|
|
|
case 839680: return MediaType.FDFORMAT_525_DD;
|
|
|
|
|
case 1304320: return MediaType.ECMA_99_8;
|
|
|
|
|
case 1223424: return MediaType.ECMA_99_15;
|
|
|
|
|
case 1061632: return MediaType.ECMA_99_26;
|
|
|
|
|
case 80384: return MediaType.ECMA_66;
|
|
|
|
|
case 325632: return MediaType.ECMA_70;
|
|
|
|
|
case 653312: return MediaType.ECMA_78;
|
|
|
|
|
case 737280: return MediaType.ECMA_78_2;
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown 5,25\" disk with {0} bytes",
|
|
|
|
|
totalDiskSize);
|
|
|
|
|
return MediaType.Unknown;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case DriveType35DD:
|
|
|
|
|
case DriveType35ED:
|
|
|
|
|
case DriveType35HD:
|
|
|
|
|
{
|
|
|
|
|
switch(totalDiskSize)
|
|
|
|
|
{
|
|
|
|
|
case 322560: return MediaType.Apricot_35;
|
|
|
|
|
case 327680: return MediaType.DOS_35_SS_DD_8;
|
|
|
|
|
case 368640: return MediaType.DOS_35_SS_DD_9;
|
|
|
|
|
case 655360: return MediaType.DOS_35_DS_DD_8;
|
|
|
|
|
case 737280: return MediaType.DOS_35_DS_DD_9;
|
|
|
|
|
case 1474560: return MediaType.DOS_35_HD;
|
|
|
|
|
case 2949120: return MediaType.DOS_35_ED;
|
|
|
|
|
case 1720320: return MediaType.DMF;
|
|
|
|
|
case 1763328: return MediaType.DMF_82;
|
|
|
|
|
case 1884160: // Irreal size, seen as BIOS with TSR, 23 sectors/track
|
|
|
|
|
case 1860608: // Real data size, sum of all sectors
|
|
|
|
|
return MediaType.XDF_35;
|
|
|
|
|
case 819200: return MediaType.CBM_35_DD;
|
|
|
|
|
case 901120: return MediaType.CBM_AMIGA_35_DD;
|
|
|
|
|
case 1802240: return MediaType.CBM_AMIGA_35_HD;
|
|
|
|
|
case 1310720: return MediaType.NEC_35_HD_8;
|
|
|
|
|
case 1228800: return MediaType.NEC_35_HD_15;
|
|
|
|
|
case 1261568: return MediaType.SHARP_35;
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown 3,5\" disk with {0} bytes",
|
|
|
|
|
totalDiskSize);
|
|
|
|
|
return MediaType.Unknown;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case DriveType8inch:
|
|
|
|
|
{
|
|
|
|
|
switch(totalDiskSize)
|
|
|
|
|
{
|
|
|
|
|
case 81664: return MediaType.IBM23FD;
|
|
|
|
|
case 242944: return MediaType.IBM33FD_128;
|
|
|
|
|
case 287488: return MediaType.IBM33FD_256;
|
|
|
|
|
case 306432: return MediaType.IBM33FD_512;
|
|
|
|
|
case 499200: return MediaType.IBM43FD_128;
|
|
|
|
|
case 574976: return MediaType.IBM43FD_256;
|
|
|
|
|
case 995072: return MediaType.IBM53FD_256;
|
|
|
|
|
case 1146624: return MediaType.IBM53FD_512;
|
|
|
|
|
case 1222400: return MediaType.IBM53FD_1024;
|
|
|
|
|
case 256256:
|
|
|
|
|
// Same size, with same disk geometry, for DEC RX01, NEC and ECMA, return ECMA
|
|
|
|
|
return MediaType.ECMA_54;
|
|
|
|
|
case 512512:
|
|
|
|
|
{
|
|
|
|
|
// DEC disk uses 256 bytes/sector
|
|
|
|
|
if(ImageInfo.sectorSize == 256) return MediaType.RX02;
|
|
|
|
|
// ECMA disks use 128 bytes/sector
|
|
|
|
|
return MediaType.ECMA_59;
|
|
|
|
|
}
|
|
|
|
|
case 1261568: return MediaType.NEC_8_DD;
|
|
|
|
|
case 1255168: return MediaType.ECMA_69_8;
|
|
|
|
|
case 1177344: return MediaType.ECMA_69_15;
|
|
|
|
|
case 1021696: return MediaType.ECMA_69_26;
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown 8\" disk with {0} bytes",
|
|
|
|
|
totalDiskSize);
|
|
|
|
|
return MediaType.Unknown;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
DicConsole.DebugWriteLine("TeleDisk plugin", "Unknown drive type {1} with {0} bytes", totalDiskSize,
|
|
|
|
|
header.driveType);
|
|
|
|
|
return MediaType.Unknown;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override byte[] ReadDiskTag(MediaTagType tag)
|
|
|
|
|
{
|
|
|
|
|
if(tag == MediaTagType.Floppy_LeadOut)
|
|
|
|
|
{
|
|
|
|
|
if(leadOut != null) return leadOut;
|
|
|
|
|
|
|
|
|
|
throw new FeatureNotPresentImageException("Lead-out not present in disk image");
|
|
|
|
|
}
|
|
|
|
|
else throw new FeatureUnsupportedImageException("Feature not supported by image format");
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Unsupported features
|
|
|
|
|
public override byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag)
|
|
|
|
|
{
|
|
|
|
|
throw new FeatureUnsupportedImageException("Feature not supported by image format");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag)
|
|
|
|
|
{
|
|
|
|
|
throw new FeatureUnsupportedImageException("Feature not supported by image format");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetImageCreator()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.imageCreator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetImageComments()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.imageComments;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetMediaManufacturer()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.mediaManufacturer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetMediaModel()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.mediaModel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetMediaSerialNumber()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.mediaSerialNumber;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetMediaBarcode()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.mediaBarcode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetMediaPartNumber()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.mediaPartNumber;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int GetMediaSequence()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.mediaSequence;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int GetLastDiskSequence()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.lastMediaSequence;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetDriveManufacturer()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.driveManufacturer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetDriveModel()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.driveModel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override string GetDriveSerialNumber()
|
|
|
|
|
{
|
|
|
|
|
return ImageInfo.driveSerialNumber;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override List<Partition> GetPartitions()
|
|
|
|
|
{
|
|
|
|
|
throw new FeatureUnsupportedImageException("Feature not supported by image format");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override List<Track> GetTracks()
|
|
|
|
|
{
|
|
|
|
|
throw new FeatureUnsupportedImageException("Feature not supported by image format");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override List<Track> GetSessionTracks(Session session)
|
|
|
|
|
{
|
|
|
|
|
throw new FeatureUnsupportedImageException("Feature not supported by image format");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override List<Track> GetSessionTracks(ushort session)
|
|
|
|
|
{
|
|
|
|
|
throw new FeatureUnsupportedImageException("Feature not supported by image format");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override List<Session> GetSessions()
|
|
|
|
|
{
|
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
#endregion Unsupported features
|
|
|
|
|
|
|
|
|
|
#region LZH decompression from MAME
|
|
|
|
|
/* This region is under following license:
|
|
|
|
|
* Copyright © 2017 Miodrag Milanovic
|
|
|
|
|
* Adapted to C#, Copyright © 2017 Natalia Portillo
|
|
|
|
|
*
|
|
|
|
|
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
|
|
|
*
|
|
|
|
|
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
|
|
|
*
|
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation
|
|
|
|
|
* and/or other materials provided with the distribution.
|
|
|
|
|
*
|
|
|
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software
|
|
|
|
|
* without specific prior written permission.
|
|
|
|
|
*
|
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
|
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
|
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
|
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Based on Japanese version 29-NOV-1988
|
|
|
|
|
* LZSS coded by Haruhiko OKUMURA
|
|
|
|
|
* Adaptive Huffman Coding coded by Haruyasu YOSHIZAKI
|
|
|
|
|
* Edited and translated to English by Kenji RIKITAKE
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const int BUFSZ = 512;
|
|
|
|
|
|
|
|
|
|
/* LZSS Parameters */
|
|
|
|
|
|
|
|
|
|
const int N = 4096; /* Size of string buffer */
|
|
|
|
|
const int F = 60; /* Size of look-ahead buffer */
|
|
|
|
|
const int THRESHOLD = 2;
|
|
|
|
|
const int NIL = N; /* End of tree's node */
|
|
|
|
|
|
|
|
|
|
/* Huffman coding parameters */
|
|
|
|
|
|
|
|
|
|
const int N_CHAR = (256 - THRESHOLD + F);
|
|
|
|
|
/* character code (= 0..N_CHAR-1) */
|
|
|
|
|
const int T = (N_CHAR * 2 - 1); /* Size of table */
|
|
|
|
|
const int R = (T - 1); /* root position */
|
|
|
|
|
const int MAX_FREQ = 0x8000;
|
|
|
|
|
/* update when cumulative frequency */
|
|
|
|
|
/* reaches to this value */
|
|
|
|
|
|
|
|
|
|
struct tdlzhuf
|
|
|
|
|
{
|
|
|
|
|
public ushort r,
|
|
|
|
|
bufcnt,
|
|
|
|
|
bufndx,
|
|
|
|
|
bufpos, // string buffer
|
|
|
|
|
// the following to allow block reads from input in next_word()
|
|
|
|
|
ibufcnt,
|
|
|
|
|
ibufndx; // input buffer counters
|
|
|
|
|
public byte[] inbuf; // input buffer
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
tdlzhuf tdctl;
|
|
|
|
|
byte[] text_buf = new byte[N + F - 1];
|
|
|
|
|
ushort[] freq = new ushort[T + 1]; /* cumulative freq table */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* pointing parent nodes.
|
|
|
|
|
* area [T..(T + N_CHAR - 1)] are pointers for leaves
|
|
|
|
|
*/
|
|
|
|
|
short[] prnt = new short[T + N_CHAR];
|
|
|
|
|
|
|
|
|
|
/* pointing children nodes (son[], son[] + 1)*/
|
|
|
|
|
short[] son = new short[T];
|
|
|
|
|
|
|
|
|
|
ushort getbuf;
|
|
|
|
|
byte getlen;
|
|
|
|
|
|
|
|
|
|
long data_read(out byte[] buf, long size)
|
|
|
|
|
{
|
|
|
|
|
if(size > inStream.Length - inStream.Position) size = inStream.Length - inStream.Position;
|
|
|
|
|
|
|
|
|
|
buf = new byte[size];
|
|
|
|
|
inStream.Read(buf, 0, (int)size);
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Tables for encoding/decoding upper 6 bits of
|
|
|
|
|
* sliding dictionary pointer
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* decoder table */
|
|
|
|
|
readonly byte[] d_code =
|
|
|
|
|
{
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
|
|
|
|
|
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
|
|
|
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
|
|
|
|
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05,
|
|
|
|
|
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07,
|
|
|
|
|
0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
|
|
|
|
|
0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
|
|
|
|
|
0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0E, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, 0x10, 0x10,
|
|
|
|
|
0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14,
|
|
|
|
|
0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1A,
|
|
|
|
|
0x1B, 0x1B, 0x1C, 0x1C, 0x1D, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23,
|
|
|
|
|
0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, 0x2C, 0x2C,
|
|
|
|
|
0x2D, 0x2D, 0x2E, 0x2E, 0x2F, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
|
|
|
|
|
0x3C, 0x3D, 0x3E, 0x3F,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
readonly byte[] d_len =
|
|
|
|
|
{
|
|
|
|
|
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
|
|
|
|
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04,
|
|
|
|
|
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
|
|
|
|
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
|
|
|
|
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
|
|
|
|
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
|
|
|
|
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
|
|
|
|
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
|
|
|
|
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
|
|
|
|
|
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
|
|
|
|
|
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
|
|
|
|
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
|
|
|
|
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
|
|
|
|
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
|
|
|
|
|
0x08, 0x08, 0x08, 0x08,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int next_word()
|
|
|
|
|
{
|
|
|
|
|
if(tdctl.ibufndx >= tdctl.ibufcnt)
|
|
|
|
|
{
|
|
|
|
|
tdctl.ibufndx = 0;
|
|
|
|
|
tdctl.ibufcnt = (ushort)data_read(out tdctl.inbuf, BUFSZ);
|
|
|
|
|
if(tdctl.ibufcnt <= 0) return (-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while(getlen <= 8)
|
|
|
|
|
{
|
|
|
|
|
// typically reads a word at a time
|
|
|
|
|
getbuf |= (ushort)(tdctl.inbuf[tdctl.ibufndx++] << (8 - getlen));
|
|
|
|
|
getlen += 8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GetBit() /* get one bit */
|
|
|
|
|
{
|
|
|
|
|
short i;
|
|
|
|
|
if(next_word() < 0) return (-1);
|
|
|
|
|
|
|
|
|
|
i = (short)getbuf;
|
|
|
|
|
getbuf <<= 1;
|
|
|
|
|
getlen--;
|
|
|
|
|
if(i < 0) return (1);
|
|
|
|
|
else return (0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GetByte() /* get a byte */
|
|
|
|
|
{
|
|
|
|
|
ushort i;
|
|
|
|
|
if(next_word() != 0) return (-1);
|
|
|
|
|
|
|
|
|
|
i = getbuf;
|
|
|
|
|
getbuf <<= 8;
|
|
|
|
|
getlen -= 8;
|
|
|
|
|
i = (ushort)(i >> 8);
|
|
|
|
|
return ((int)i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* initialize freq tree */
|
|
|
|
|
|
|
|
|
|
void StartHuff()
|
|
|
|
|
{
|
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
|
|
for(i = 0; i < N_CHAR; i++)
|
|
|
|
|
{
|
|
|
|
|
freq[i] = 1;
|
|
|
|
|
son[i] = (short)(i + T);
|
|
|
|
|
prnt[i + T] = (short)i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
|
j = N_CHAR;
|
|
|
|
|
while(j <= R)
|
|
|
|
|
{
|
|
|
|
|
freq[j] = (ushort)(freq[i] + freq[i + 1]);
|
|
|
|
|
son[j] = (short)i;
|
|
|
|
|
prnt[i] = prnt[i + 1] = (short)j;
|
|
|
|
|
i += 2;
|
|
|
|
|
j++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
freq[T] = 0xffff;
|
|
|
|
|
prnt[R] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* reconstruct freq tree */
|
|
|
|
|
|
|
|
|
|
void reconst()
|
|
|
|
|
{
|
|
|
|
|
short i, j, k;
|
|
|
|
|
ushort f, l;
|
|
|
|
|
|
|
|
|
|
/* halven cumulative freq for leaf nodes */
|
|
|
|
|
j = 0;
|
|
|
|
|
for(i = 0; i < T; i++)
|
|
|
|
|
{
|
|
|
|
|
if(son[i] >= T)
|
|
|
|
|
{
|
|
|
|
|
freq[j] = (ushort)((freq[i] + 1) / 2);
|
|
|
|
|
son[j] = son[i];
|
|
|
|
|
j++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* make a tree : first, connect children nodes */
|
|
|
|
|
for(i = 0, j = N_CHAR; j < T; i += 2, j++)
|
|
|
|
|
{
|
|
|
|
|
k = (short)(i + 1);
|
|
|
|
|
f = freq[j] = (ushort)(freq[i] + freq[k]);
|
|
|
|
|
for(k = (short)(j - 1); f < freq[k]; k--) { }
|
|
|
|
|
|
|
|
|
|
;
|
|
|
|
|
k++;
|
|
|
|
|
l = (ushort)((j - k) * 2);
|
|
|
|
|
|
|
|
|
|
Array.ConstrainedCopy(freq, k, freq, k + 1, l);
|
|
|
|
|
freq[k] = f;
|
|
|
|
|
Array.ConstrainedCopy(son, k, son, k + 1, l);
|
|
|
|
|
son[k] = i;
|
|
|
|
|
}
|
|
|
|
|
/* connect parent nodes */
|
|
|
|
|
for(i = 0; i < T; i++)
|
|
|
|
|
{
|
|
|
|
|
if((k = son[i]) >= T) { prnt[k] = i; }
|
|
|
|
|
else { prnt[k] = prnt[k + 1] = i; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* update freq tree */
|
|
|
|
|
|
|
|
|
|
void update(int c)
|
|
|
|
|
{
|
|
|
|
|
int i, j, k, l;
|
|
|
|
|
|
|
|
|
|
if(freq[R] == MAX_FREQ) { reconst(); }
|
|
|
|
|
c = prnt[c + T];
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
k = ++freq[c];
|
|
|
|
|
|
|
|
|
|
/* swap nodes to keep the tree freq-ordered */
|
|
|
|
|
if(k > freq[l = c + 1])
|
|
|
|
|
{
|
|
|
|
|
while(k > freq[++l]) { }
|
|
|
|
|
|
|
|
|
|
;
|
|
|
|
|
l--;
|
|
|
|
|
freq[c] = freq[l];
|
|
|
|
|
freq[l] = (ushort)k;
|
|
|
|
|
|
|
|
|
|
i = son[c];
|
|
|
|
|
prnt[i] = (short)l;
|
|
|
|
|
if(i < T) prnt[i + 1] = (short)l;
|
|
|
|
|
|
|
|
|
|
j = son[l];
|
|
|
|
|
son[l] = (short)i;
|
|
|
|
|
|
|
|
|
|
prnt[j] = (short)c;
|
|
|
|
|
if(j < T) prnt[j + 1] = (short)c;
|
|
|
|
|
son[c] = (short)j;
|
|
|
|
|
|
|
|
|
|
c = l;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
while((c = prnt[c]) != 0); /* do it until reaching the root */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
short DecodeChar()
|
|
|
|
|
{
|
|
|
|
|
int ret;
|
|
|
|
|
ushort c;
|
|
|
|
|
|
|
|
|
|
c = (ushort)son[R];
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* start searching tree from the root to leaves.
|
|
|
|
|
* choose node #(son[]) if input bit == 0
|
|
|
|
|
* else choose #(son[]+1) (input bit == 1)
|
|
|
|
|
*/
|
|
|
|
|
while(c < T)
|
|
|
|
|
{
|
|
|
|
|
if((ret = GetBit()) < 0) return (-1);
|
|
|
|
|
|
|
|
|
|
c += (ushort)ret;
|
|
|
|
|
c = (ushort)son[c];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c -= T;
|
|
|
|
|
update(c);
|
|
|
|
|
return (short)c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
short DecodePosition()
|
|
|
|
|
{
|
|
|
|
|
short bit;
|
|
|
|
|
ushort i, j, c;
|
|
|
|
|
|
|
|
|
|
/* decode upper 6 bits from given table */
|
|
|
|
|
if((bit = (short)GetByte()) < 0) return (-1);
|
|
|
|
|
|
|
|
|
|
i = (ushort)bit;
|
|
|
|
|
c = (ushort)(d_code[i] << 6);
|
|
|
|
|
j = d_len[i];
|
|
|
|
|
|
|
|
|
|
/* input lower 6 bits directly */
|
|
|
|
|
j -= 2;
|
|
|
|
|
while(j-- > 0)
|
|
|
|
|
{
|
|
|
|
|
if((bit = (short)GetBit()) < 0) return (-1);
|
|
|
|
|
|
|
|
|
|
i = (ushort)((i << 1) + bit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (short)(c | (i & 0x3f));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* DeCompression
|
|
|
|
|
|
|
|
|
|
split out initialization code to init_Decode()
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void init_Decode()
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
getbuf = 0;
|
|
|
|
|
getlen = 0;
|
|
|
|
|
tdctl = new tdlzhuf();
|
|
|
|
|
tdctl.ibufcnt = tdctl.ibufndx = 0; // input buffer is empty
|
|
|
|
|
tdctl.bufcnt = 0;
|
|
|
|
|
StartHuff();
|
|
|
|
|
for(i = 0; i < N - F; i++) text_buf[i] = 0x20;
|
|
|
|
|
|
|
|
|
|
tdctl.r = N - F;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Decode(out byte[] buf, int len) /* Decoding/Uncompressing */
|
|
|
|
|
{
|
|
|
|
|
short c, pos;
|
|
|
|
|
buf = new byte[len];
|
|
|
|
|
int count; // was an unsigned long, seems unnecessary
|
|
|
|
|
for(count = 0; count < len;)
|
|
|
|
|
{
|
|
|
|
|
if(tdctl.bufcnt == 0)
|
|
|
|
|
{
|
|
|
|
|
if((c = DecodeChar()) < 0) return (count); // fatal error
|
|
|
|
|
|
|
|
|
|
if(c < 256)
|
|
|
|
|
{
|
|
|
|
|
buf[count] = (byte)c;
|
|
|
|
|
text_buf[tdctl.r++] = (byte)c;
|
|
|
|
|
tdctl.r &= (N - 1);
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if((pos = DecodePosition()) < 0) return (count); // fatal error
|
|
|
|
|
|
|
|
|
|
tdctl.bufpos = (ushort)((tdctl.r - pos - 1) & (N - 1));
|
|
|
|
|
tdctl.bufcnt = (ushort)(c - 255 + THRESHOLD);
|
|
|
|
|
tdctl.bufndx = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// still chars from last string
|
|
|
|
|
while(tdctl.bufndx < tdctl.bufcnt && count < len)
|
|
|
|
|
{
|
|
|
|
|
c = text_buf[(tdctl.bufpos + tdctl.bufndx) & (N - 1)];
|
|
|
|
|
buf[count] = (byte)c;
|
|
|
|
|
tdctl.bufndx++;
|
|
|
|
|
text_buf[tdctl.r++] = (byte)c;
|
|
|
|
|
tdctl.r &= (N - 1);
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
// reset bufcnt after copy string from text_buf[]
|
|
|
|
|
if(tdctl.bufndx >= tdctl.bufcnt) tdctl.bufndx = tdctl.bufcnt = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (count); // count == len, success
|
|
|
|
|
}
|
|
|
|
|
#endregion LZH decompression from MAME
|
|
|
|
|
}
|
2017-09-27 14:54:27 +01:00
|
|
|
}
|