Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ExtractionTool/Features/MainFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ private void ExtractFile(string file)
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);
Expand Down
17 changes: 17 additions & 0 deletions SabreTools.Serialization/Models/CDROM/CDROM.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 22 additions & 0 deletions SabreTools.Serialization/Models/CDROM/DataTrack.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 42 additions & 0 deletions SabreTools.Serialization/Models/CDROM/Enums.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 31 additions & 0 deletions SabreTools.Serialization/Models/CDROM/Mode1.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 31 additions & 0 deletions SabreTools.Serialization/Models/CDROM/Mode2Form1.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions SabreTools.Serialization/Models/CDROM/Mode2Form2.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 26 additions & 0 deletions SabreTools.Serialization/Models/CDROM/Sector.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 171 additions & 0 deletions SabreTools.Serialization/Readers/CDROMVolume.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
Loading