mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-02-04 05:36:12 +00:00
CD-ROM (Volume Descriptor Set) support (#37)
* CD-ROM Wrapper * Fix CDROMVolume Reader * Fix SectorMode enum
This commit is contained in:
@@ -165,6 +165,11 @@ namespace ExtractionTool.Features
|
||||
bzip2.Extract(OutputPath, Debug);
|
||||
break;
|
||||
|
||||
// CD-ROM bin file
|
||||
case CDROM cdrom:
|
||||
cdrom.Extract(OutputPath, Debug);
|
||||
break;
|
||||
|
||||
// CFB
|
||||
case CFB cfb:
|
||||
cfb.Extract(OutputPath, Debug);
|
||||
|
||||
17
SabreTools.Serialization/Models/CDROM/CDROM.cs
Normal file
17
SabreTools.Serialization/Models/CDROM/CDROM.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SabreTools.Data.Models.CDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// A CD-ROM disc image, made up of multiple data tracks, ISO 10149 / ECMA-130
|
||||
/// Specifically not a mixed-mode CD disc image, pure CD-ROM disc
|
||||
/// </summary>
|
||||
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-130_2nd_edition_june_1996.pdf"/>
|
||||
public sealed class CDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// CD-ROM data tracks
|
||||
/// </summary>
|
||||
public DataTrack[] Tracks { get; set; } = [];
|
||||
}
|
||||
}
|
||||
22
SabreTools.Serialization/Models/CDROM/DataTrack.cs
Normal file
22
SabreTools.Serialization/Models/CDROM/DataTrack.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
|
||||
namespace SabreTools.Data.Models.CDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// A CD-ROM data track containing a ISO9660 / ECMA-119 filesystem
|
||||
/// </summary>
|
||||
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-130_2nd_edition_june_1996.pdf"/>
|
||||
public sealed class DataTrack
|
||||
{
|
||||
/// <summary>
|
||||
/// CD-ROM data sectors
|
||||
/// </summary>
|
||||
public Sector[] Sectors { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// ISO9660 volume within the data track
|
||||
/// </summary>
|
||||
public Volume Volume { get; set; }
|
||||
}
|
||||
}
|
||||
42
SabreTools.Serialization/Models/CDROM/Enums.cs
Normal file
42
SabreTools.Serialization/Models/CDROM/Enums.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.Data.Models.CDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum for a CD-ROM's sector mode
|
||||
/// Explicitly does not contain non-CD-ROM modes like AUDIO, CDG, CDI, and length-specific modes
|
||||
/// </summary>
|
||||
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-130_2nd_edition_june_1996.pdf"/>
|
||||
public enum SectorMode
|
||||
{
|
||||
/// <summary>
|
||||
/// CD-ROM Unknown Mode
|
||||
/// </summary>
|
||||
UNKNOWN,
|
||||
|
||||
/// <summary>
|
||||
/// CD-ROM Mode 0 (All bytes after header are 0x00)
|
||||
/// </summary>
|
||||
MODE0,
|
||||
|
||||
/// <summary>
|
||||
/// CD-ROM Mode 1
|
||||
/// </summary>
|
||||
MODE1,
|
||||
|
||||
/// <summary>
|
||||
/// CD-ROM Mode 2 (Formless)
|
||||
/// </summary>
|
||||
MODE2,
|
||||
|
||||
/// <summary>
|
||||
/// CD-ROM XA Mode 2 Form 1
|
||||
/// </summary>
|
||||
MODE2_FORM1,
|
||||
|
||||
/// <summary>
|
||||
/// CD-ROM XA Mode 2 Form 2
|
||||
/// </summary>
|
||||
MODE2_FORM2,
|
||||
}
|
||||
}
|
||||
31
SabreTools.Serialization/Models/CDROM/Mode1.cs
Normal file
31
SabreTools.Serialization/Models/CDROM/Mode1.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SabreTools.Data.Models.CDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// A CD-ROM Mode1 sector
|
||||
/// </summary>
|
||||
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-130_2nd_edition_june_1996.pdf"/>
|
||||
public sealed class Mode1 : Sector
|
||||
{
|
||||
/// <summary>
|
||||
/// User Data, 2048 bytes
|
||||
/// </summary>
|
||||
public byte[] UserData { get; set; } = new byte[2048];
|
||||
|
||||
/// <summary>
|
||||
/// Error Detection Code, 4 bytes
|
||||
/// </summary>
|
||||
public byte[] EDC { get; set; } = new byte[4];
|
||||
|
||||
/// <summary>
|
||||
/// Reserved 8 bytes
|
||||
/// </summary>
|
||||
public byte[] Intermediate { get; set; } = new byte[8];
|
||||
|
||||
/// <summary>
|
||||
/// Error Correction Code, 4 bytes
|
||||
/// </summary>
|
||||
public byte[] ECC { get; set; } = new byte[276];
|
||||
}
|
||||
}
|
||||
31
SabreTools.Serialization/Models/CDROM/Mode2Form1.cs
Normal file
31
SabreTools.Serialization/Models/CDROM/Mode2Form1.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SabreTools.Data.Models.CDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// A CD-ROM Mode 2 Form 1 sector
|
||||
/// </summary>
|
||||
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-130_2nd_edition_june_1996.pdf"/>
|
||||
public sealed class Mode2Form1 : Sector
|
||||
{
|
||||
/// <summary>
|
||||
/// Mode 2 subheader, 8 bytes
|
||||
/// </summary>
|
||||
public byte[] Subheader { get; set; } = new byte[8];
|
||||
|
||||
/// <summary>
|
||||
/// User data, 2048 bytes
|
||||
/// </summary>
|
||||
public byte[] UserData { get; set; } = new byte[2048];
|
||||
|
||||
/// <summary>
|
||||
/// Error Detection Code, 4 bytes
|
||||
/// </summary>
|
||||
public byte[] EDC { get; set; } = new byte[4];
|
||||
|
||||
/// <summary>
|
||||
/// Error Correction Code, 4 bytes
|
||||
/// </summary>
|
||||
public byte[] ECC { get; set; } = new byte[276];
|
||||
}
|
||||
}
|
||||
27
SabreTools.Serialization/Models/CDROM/Mode2Form2.cs
Normal file
27
SabreTools.Serialization/Models/CDROM/Mode2Form2.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SabreTools.Data.Models.CDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// A CD-ROM Mode 2 Form 2 sector
|
||||
/// Larger user data at expense of no error correction, just error detection
|
||||
/// </summary>
|
||||
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-130_2nd_edition_june_1996.pdf"/>
|
||||
public sealed class Mode2Form2 : Sector
|
||||
{
|
||||
/// <summary>
|
||||
/// Mode 2 subheader, 8 bytes
|
||||
/// </summary>
|
||||
public byte[] Subheader { get; set; } = new byte[8];
|
||||
|
||||
/// <summary>
|
||||
/// User data, 2324 bytes
|
||||
/// </summary>
|
||||
public byte[] UserData { get; set; } = new byte[2324];
|
||||
|
||||
/// <summary>
|
||||
/// Error Detection Code, 4 bytes
|
||||
/// </summary>
|
||||
public byte[] EDC { get; set; } = new byte[4];
|
||||
}
|
||||
}
|
||||
26
SabreTools.Serialization/Models/CDROM/Sector.cs
Normal file
26
SabreTools.Serialization/Models/CDROM/Sector.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SabreTools.Data.Models.CDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// A CD-ROM sector
|
||||
/// </summary>
|
||||
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-130_2nd_edition_june_1996.pdf"/>
|
||||
public abstract class Sector
|
||||
{
|
||||
/// <summary>
|
||||
/// Sync pattern, 12 bytes
|
||||
/// </summary>
|
||||
public byte[] SyncPattern { get; set; } = new byte[12];
|
||||
|
||||
/// <summary>
|
||||
/// Sector Address, 3 bytes
|
||||
/// </summary>
|
||||
public byte[] Address { get; set; } = new byte[3];
|
||||
|
||||
/// <summary>
|
||||
/// CD-ROM mode
|
||||
/// </summary>
|
||||
public byte Mode { get; set; }
|
||||
}
|
||||
}
|
||||
171
SabreTools.Serialization/Readers/CDROMVolume.cs
Normal file
171
SabreTools.Serialization/Readers/CDROMVolume.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Data.Extensions;
|
||||
using SabreTools.Data.Models.CDROM;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace SabreTools.Serialization.Readers
|
||||
{
|
||||
public class CDROMVolume : ISO9660
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const int SectorSize = 2352;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Volume? Deserialize(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// Simple check for a valid stream length
|
||||
if (SectorSize * (Constants.SystemAreaSectors + 2) > data.Length - data.Position)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Create a new Volume to fill
|
||||
var volume = new Volume();
|
||||
|
||||
// Read the System Area
|
||||
volume.SystemArea = ParseCDROMSystemArea(data);
|
||||
|
||||
// Read the set of Volume Descriptors
|
||||
var vdSet = ParseCDROMVolumeDescriptorSet(data);
|
||||
if (vdSet == null || vdSet.Length == 0)
|
||||
return null;
|
||||
|
||||
volume.VolumeDescriptorSet = vdSet;
|
||||
|
||||
// Only the VolumeDescriptorSet can be read using the CDROMVolume Reader
|
||||
// TODO: CDROM Reader that outputs CDROM.DataTrack that uses custom Stream to wrap ISO9660 for Volume
|
||||
|
||||
return volume;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into the System Area
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled byte[] on success, null on error</returns>
|
||||
public static byte[]? ParseCDROMSystemArea(Stream data)
|
||||
{
|
||||
var systemArea = new byte[Constants.SystemAreaSectors * Constants.MinimumSectorSize];
|
||||
// Process in sectors
|
||||
for (int i = 0; i < Constants.SystemAreaSectors; i++)
|
||||
{
|
||||
// Ignore sector header
|
||||
var mode = SkipSectorHeader(data);
|
||||
|
||||
// Read user data
|
||||
var userData = data.ReadBytes(Constants.MinimumSectorSize);
|
||||
|
||||
// Copy user data into System Area
|
||||
Buffer.BlockCopy(userData, 0, systemArea, i * Constants.MinimumSectorSize, Constants.MinimumSectorSize);
|
||||
|
||||
// Ignore sector trailer
|
||||
SkipSectorTrailer(data, mode);
|
||||
}
|
||||
|
||||
return systemArea;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a CD-ROM Stream into the System Area
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled byte[] on success, null on error</returns>
|
||||
public static VolumeDescriptor[]? ParseCDROMVolumeDescriptorSet(Stream data)
|
||||
{
|
||||
var obj = new List<VolumeDescriptor>();
|
||||
|
||||
bool setTerminated = false;
|
||||
while (data.Position < data.Length)
|
||||
{
|
||||
// Ignore sector header
|
||||
var mode = SkipSectorHeader(data);
|
||||
|
||||
var volumeDescriptor = ParseVolumeDescriptor(data, Constants.MinimumSectorSize);
|
||||
|
||||
// Ignore sector trailer
|
||||
SkipSectorTrailer(data, mode);
|
||||
|
||||
// If no valid volume descriptor could be read, return the current set
|
||||
if (volumeDescriptor == null)
|
||||
return [.. obj];
|
||||
|
||||
// If the set has already been terminated and the returned volume descriptor is not another terminator,
|
||||
// assume the read volume descriptor is not a valid volume descriptor and return the current set
|
||||
if (setTerminated && volumeDescriptor.Type != VolumeDescriptorType.VOLUME_DESCRIPTOR_SET_TERMINATOR)
|
||||
{
|
||||
// Reset stream to before the just-read volume descriptor
|
||||
data.SeekIfPossible(-SectorSize, SeekOrigin.Current);
|
||||
return [.. obj];
|
||||
}
|
||||
|
||||
// Add the valid read volume descriptor to the set
|
||||
obj.Add(volumeDescriptor);
|
||||
|
||||
// If the set terminator was read, set the set terminated flag (further set terminators may be present)
|
||||
if (!setTerminated && volumeDescriptor.Type == VolumeDescriptorType.VOLUME_DESCRIPTOR_SET_TERMINATOR)
|
||||
setTerminated = true;
|
||||
}
|
||||
|
||||
return [.. obj];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip the header bytes of a CD-ROM sector
|
||||
/// </summary>
|
||||
private static SectorMode SkipSectorHeader(Stream data)
|
||||
{
|
||||
// Ignore sector header
|
||||
_ = data.ReadBytes(15);
|
||||
|
||||
// Read sector mode
|
||||
byte mode = data.ReadByteValue();
|
||||
if (mode == 0)
|
||||
return SectorMode.MODE0;
|
||||
else if (mode == 1)
|
||||
return SectorMode.MODE1;
|
||||
else if (mode == 2)
|
||||
{
|
||||
// Ignore subheader
|
||||
var subheader = data.ReadBytes(8);
|
||||
if ((subheader[2] & 0x20) == 0x20)
|
||||
return SectorMode.MODE2_FORM2;
|
||||
else
|
||||
return SectorMode.MODE2_FORM1;
|
||||
}
|
||||
else
|
||||
return SectorMode.UNKNOWN;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip the trailer bytes of a CD-ROM sector
|
||||
/// </summary>
|
||||
private static void SkipSectorTrailer(Stream data, SectorMode mode)
|
||||
{
|
||||
if (mode == SectorMode.MODE1 || mode == SectorMode.MODE0 || mode == SectorMode.UNKNOWN)
|
||||
{
|
||||
_ = data.ReadBytes(288);
|
||||
}
|
||||
else if (mode == SectorMode.MODE2 || mode == SectorMode.MODE2_FORM1 || mode == SectorMode.MODE2_FORM2)
|
||||
{
|
||||
// TODO: Better deal with Form 2
|
||||
_ = data.ReadBytes(280);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace SabreTools.Serialization
|
||||
WrapperType.BFPK => BFPK.Create(data),
|
||||
WrapperType.BSP => BSP.Create(data),
|
||||
WrapperType.BZip2 => BZip2.Create(data),
|
||||
WrapperType.CDROM => CDROM.Create(data),
|
||||
WrapperType.CFB => CFB.Create(data),
|
||||
WrapperType.CHD => CHD.Create(data),
|
||||
WrapperType.CIA => CIA.Create(data),
|
||||
@@ -202,6 +203,15 @@ namespace SabreTools.Serialization
|
||||
|
||||
#endregion
|
||||
|
||||
#region CDROM
|
||||
|
||||
if (magic.StartsWith([0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) &&
|
||||
(extension.Equals("bin", StringComparison.OrdinalIgnoreCase) ||
|
||||
extension.Equals("skeleton", StringComparison.OrdinalIgnoreCase)))
|
||||
return WrapperType.CDROM;
|
||||
|
||||
#endregion
|
||||
|
||||
#region CFB
|
||||
|
||||
if (magic.StartsWith(Data.Models.CFB.Constants.SignatureBytes))
|
||||
|
||||
9
SabreTools.Serialization/Wrappers/CDROM.Extraction.cs
Normal file
9
SabreTools.Serialization/Wrappers/CDROM.Extraction.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
public partial class CDROM : IExtractable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override bool Extract(string outputDirectory, bool includeDebug)
|
||||
=> false;
|
||||
}
|
||||
}
|
||||
27
SabreTools.Serialization/Wrappers/CDROM.Printing.cs
Normal file
27
SabreTools.Serialization/Wrappers/CDROM.Printing.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using SabreTools.Data.Extensions;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
public partial class CDROM : IPrintable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public new void PrintInformation(StringBuilder builder)
|
||||
{
|
||||
builder.AppendLine("CD-ROM Data Track Information:");
|
||||
builder.AppendLine("-------------------------");
|
||||
builder.AppendLine();
|
||||
|
||||
if (Model.SystemArea == null || Model.SystemArea.Length == 0)
|
||||
builder.AppendLine(Model.SystemArea, "System Area");
|
||||
else if (Array.TrueForAll(Model.SystemArea, b => b == 0))
|
||||
builder.AppendLine("Zeroed", "System Area");
|
||||
else
|
||||
builder.AppendLine("Not Zeroed", "System Area");
|
||||
builder.AppendLine();
|
||||
|
||||
Print(builder, Model.VolumeDescriptorSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
SabreTools.Serialization/Wrappers/CDROM.cs
Normal file
90
SabreTools.Serialization/Wrappers/CDROM.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System.IO;
|
||||
using SabreTools.Data.Models.ISO9660;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
public partial class CDROM : ISO9660
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string DescriptionString => "CD-ROM Data Track";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CDROM(Volume model, byte[] data) : base(model, data) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CDROM(Volume model, byte[] data, int offset) : base(model, data, offset) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CDROM(Volume model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CDROM(Volume model, Stream data) : base(model, data) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CDROM(Volume model, Stream data, long offset) : base(model, data, offset) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CDROM(Volume model, Stream data, long offset, long length) : base(model, data, offset, length) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create an CDROM data track from a byte array and offset
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing the archive</param>
|
||||
/// <param name="offset">Offset within the array to parse</param>
|
||||
/// <returns>A CDROM data track wrapper on success, null on failure</returns>
|
||||
public new static CDROM? Create(byte[]? data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and use that
|
||||
var dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return Create(dataStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an CDROM data track from a Stream
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the archive</param>
|
||||
/// <returns>A CDROM data track wrapper on success, null on failure</returns>
|
||||
public new static CDROM? Create(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Cache the current offset
|
||||
long currentOffset = data.Position;
|
||||
|
||||
var model = new Readers.CDROMVolume().Deserialize(data);
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return new CDROM(model, data, currentOffset);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,11 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// </summary>
|
||||
BZip2,
|
||||
|
||||
/// <summary>
|
||||
/// CD-ROM bin file
|
||||
/// </summary>
|
||||
CDROM,
|
||||
|
||||
/// <summary>
|
||||
/// Compound File Binary
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user