Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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; }
}
}
41 changes: 41 additions & 0 deletions SabreTools.Serialization/Models/CDROM/Enums.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;

namespace SabreTools.Data.Models.CDROM
{
/// <summary>
/// Enum for a sector's mode
/// </summary>
/// <see href="https://ecma-international.org/wp-content/uploads/ECMA-130_2nd_edition_june_1996.pdf"/>
public enum SectorMode : byte
{
/// <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 Mode 2 Form 1
/// </summary>
MODE2_FORM1,

/// <summary>
/// CD-ROM Mode 2 Form 2
/// </summary>
MODE2_FORM2,

/// <summary>
/// CD-ROM Unknown Mode
/// </summary>
UNKNOWN,
}
}
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