mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-07-08 18:06:41 +00:00
PC Engine CD-ROM Header Support (#91)
This commit is contained in:
26
SabreTools.Data.Models/PCEngineCDROM/BootSector.cs
Normal file
26
SabreTools.Data.Models/PCEngineCDROM/BootSector.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace SabreTools.Data.Models.PCEngineCDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard format for the first sector of the CD-ROM header
|
||||
/// This is likely read by the system to verify the disc's authenticity
|
||||
/// </summary>
|
||||
public sealed class BootSector
|
||||
{
|
||||
/// <summary>
|
||||
/// Copyright notice and credits
|
||||
/// </summary>
|
||||
/// <remarks>Shift-JIS format, lines separated by NULL character, ends in NULL</remarks>
|
||||
public byte[] CopyrightString { get; set; } = new byte[806];
|
||||
|
||||
/// <summary>
|
||||
/// HuC6280 machine code, presumably for initialization
|
||||
/// Note that the actual Boot ROM is on the System Card
|
||||
/// </summary>
|
||||
public byte[] BootROM { get; set; } = new byte[432];
|
||||
|
||||
/// <summary>
|
||||
/// Reserved zeroed padding bytes to fill remainder of sector
|
||||
/// </summary>
|
||||
public byte[] Padding { get; set; } = new byte[810];
|
||||
}
|
||||
}
|
||||
23
SabreTools.Data.Models/PCEngineCDROM/Constants.cs
Normal file
23
SabreTools.Data.Models/PCEngineCDROM/Constants.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace SabreTools.Data.Models.PCEngineCDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// PC Engine CDROM constant values and arrays
|
||||
/// </summary>
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard block size for PC Engine CDROM disc images
|
||||
/// </summary>
|
||||
public static readonly int SectorSize = 2048;
|
||||
|
||||
/// <summary>
|
||||
/// Start of a PC Engine CDROM Header
|
||||
/// </summary>
|
||||
public static readonly byte[] MagicBytes = [0x82, 0xB1, 0x82, 0xCC, 0x83, 0x76, 0x83, 0x8D, 0x83, 0x4F, 0x83, 0x89, 0x83, 0x80, 0x82, 0xCC];
|
||||
|
||||
/// <summary>
|
||||
/// Start of an empty CD-ROM pregap sector
|
||||
/// </summary>
|
||||
public static readonly byte[] PregapBytes = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
}
|
||||
}
|
||||
49
SabreTools.Data.Models/PCEngineCDROM/Enums.cs
Normal file
49
SabreTools.Data.Models/PCEngineCDROM/Enums.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace SabreTools.Data.Models.PCEngineCDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// Opening Mode flags
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum OpenMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// "Data Read to VRAM"
|
||||
/// Bit set if data is to be read to VRAM
|
||||
/// </summary>
|
||||
READ_TO_VRAM = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// "Data Read to ADPCM Buffer"
|
||||
/// Bit set if data is to be read to the ADPCM buffer
|
||||
/// </summary>
|
||||
READ_TO_ADPCM = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// "BG Display"
|
||||
/// Bit set if background display is off
|
||||
/// Unset if it should be on
|
||||
/// </summary>
|
||||
DISPLAY_OFF = 0x20,
|
||||
|
||||
/// <summary>
|
||||
/// "ADPCM Play"
|
||||
/// Bit set if ADPCM should not play
|
||||
/// Unset if it should play
|
||||
/// </summary>
|
||||
ADCPM_DISABLED = 0x40,
|
||||
|
||||
/// <summary>
|
||||
/// "ADPCM Play Mode"
|
||||
/// Bit set if ADPCM should play on repeat
|
||||
/// Unset if it should play only once
|
||||
/// </summary>
|
||||
ADPCM_REPEAT = 0x80,
|
||||
|
||||
/// <summary>
|
||||
/// Mask for bits that are reserved
|
||||
/// </summary>
|
||||
RESERVED_MASK = 0x1C,
|
||||
}
|
||||
}
|
||||
19
SabreTools.Data.Models/PCEngineCDROM/Header.cs
Normal file
19
SabreTools.Data.Models/PCEngineCDROM/Header.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace SabreTools.Data.Models.PCEngineCDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the standard header of PC Engine CD-ROM
|
||||
/// Located at the first non-zero user data sector of the first data track
|
||||
/// </summary>
|
||||
public sealed class Header
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard sector of data to verify the disc
|
||||
/// </summary>
|
||||
public BootSector BootSector { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initial Program Loader for the PC Engine
|
||||
/// </summary>
|
||||
public IPL IPL { get; set; } = new();
|
||||
}
|
||||
}
|
||||
133
SabreTools.Data.Models/PCEngineCDROM/IPL.cs
Normal file
133
SabreTools.Data.Models/PCEngineCDROM/IPL.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using SabreTools.Numerics;
|
||||
|
||||
namespace SabreTools.Data.Models.PCEngineCDROM
|
||||
{
|
||||
/// <summary>
|
||||
/// IPL (Initial Program Loader) Block Data Format
|
||||
/// Format information from the Hu7 CD System BIOS Manual "CD-ROM BIOS Ver1.00"
|
||||
/// </summary>
|
||||
public sealed class IPL
|
||||
{
|
||||
/// <summary>
|
||||
/// "Load Start Record Number of CD"
|
||||
/// "Top record no. where the program is contained"
|
||||
/// The offset (entry point) of the initial start up program machine code
|
||||
/// </summary>
|
||||
/// <remarks>Big-Endian</remarks>
|
||||
public UInt24 IPLBLK { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// "Load Block Length of CD"
|
||||
/// "No. of records for program to read"
|
||||
/// Number of sectors to read for the start up program, from the entry point
|
||||
/// </summary>
|
||||
public byte IPLBLN { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// "Program Load Address"
|
||||
/// "Main memory address for program read"
|
||||
/// Address for where the program code should be placed into memory
|
||||
/// </summary>
|
||||
/// <remarks>Little-Endian</remarks>
|
||||
public ushort IPLSTA { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// "Program Execute Address"
|
||||
/// "Starting address of execution after program read"
|
||||
/// Address for where the program code should be executed from memory, after reading
|
||||
/// </summary>
|
||||
/// <remarks>Little-Endian</remarks>
|
||||
public ushort IPLJMP { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// "IPL Set MPR2-6 (+ max_mapping)"
|
||||
/// "Bank no. to set to MPR before program read"
|
||||
/// "When calling BIOS or using interrupt routine from BIOS, MPR0,1,7 cannot be changed"
|
||||
/// Memory Page Register
|
||||
/// MPR0 (I/O, $0000 to $1FFF)
|
||||
/// MPR1 (WORK RAM, $2000 to $3FFF)
|
||||
/// MPR2-6 (USER AREA, $4000 to $5FFF, ..., $C000 to $DFFF)
|
||||
/// MPR7 (BIOS ROM, $E000 to $FFFF)
|
||||
/// </summary>
|
||||
public byte[] IPLMPR { get; set; } = new byte[5];
|
||||
|
||||
/// <summary>
|
||||
/// "Opening mode"
|
||||
/// </summary>
|
||||
public OpenMode OpenMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// "Opening Graphic Data Record Number"
|
||||
/// "Specifies the top record of data to load"
|
||||
/// </summary>
|
||||
/// <remarks>Big-Endian</remarks>
|
||||
public UInt24 GRPBLK { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// "Opening Graphic Data Length"
|
||||
/// "Specifies the total record that contains color palette data, BAT data, and BG font data"
|
||||
/// </summary>
|
||||
public byte GRPBLN { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// "Opening Graphic Data Read Address"
|
||||
/// "Specifies the top VRAM address into which BG font data is read"
|
||||
/// </summary>
|
||||
/// <remarks>Little-Endian</remarks>
|
||||
public ushort GRPADR { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// "Opening ADPCM Data Record Number"
|
||||
/// "Specifies the top record of data to load"
|
||||
/// </summary>
|
||||
/// <remarks>Big-Endian</remarks>
|
||||
public UInt24 ADPBLK { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// "Opening ADPCM Data Length"
|
||||
/// "Specifies the number of ADPCM data record"
|
||||
/// </summary>
|
||||
public byte ADPBLN { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// "Opening ADPCM Sampling Rate"
|
||||
/// "Specifies the ADPCM sampling rate"
|
||||
/// </summary>
|
||||
public byte ADPRATE { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reserved bytes, zeroed
|
||||
/// </summary>
|
||||
/// <remarks>7 bytes</remarks>
|
||||
public byte[] Reserved { get; set; } = new byte[7];
|
||||
|
||||
/// <summary>
|
||||
/// "ID String"
|
||||
/// Null-terminated ASCII string
|
||||
/// "PC Engine CD-ROM SYSTEM\0"
|
||||
/// </summary>
|
||||
/// <remarks>24 bytes, NULL terminated</remarks>
|
||||
public byte[] SystemString { get; set; } = new byte[24];
|
||||
|
||||
/// <summary>
|
||||
/// Null-terminated ASCII string
|
||||
/// "Copyright HUDSON SOFT / NEC Home Electronics,Ltd.\0"
|
||||
/// </summary>
|
||||
/// <remarks>50 bytes, NULL terminated</remarks>
|
||||
public byte[] CopyrightString { get; set; } = new byte[50];
|
||||
|
||||
/// <summary>
|
||||
/// "Program Name"
|
||||
/// ASCII string, not null-terminated
|
||||
/// </summary>
|
||||
/// <remarks>16 bytes, padding with trailing SPACE \x20</remarks>
|
||||
public byte[] ProgramName { get; set; } = new byte[16];
|
||||
|
||||
/// <summary>
|
||||
/// Additional optional string
|
||||
/// ASCII string, not null-terminated
|
||||
/// </summary>
|
||||
/// <remarks>6 bytes, padding with trailing SPACE \x20</remarks>
|
||||
public byte[] AdditionalString { get; set; } = new byte[16];
|
||||
}
|
||||
}
|
||||
72
SabreTools.Serialization.Readers.Test/PCEngineCDROMTests.cs
Normal file
72
SabreTools.Serialization.Readers.Test/PCEngineCDROMTests.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Serialization.Readers.Test
|
||||
{
|
||||
public class PCEngineCDROMTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var deserializer = new PCEngineCDROM();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var deserializer = new PCEngineCDROM();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var deserializer = new PCEngineCDROM();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var deserializer = new PCEngineCDROM();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var deserializer = new PCEngineCDROM();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var deserializer = new PCEngineCDROM();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
SabreTools.Serialization.Readers/PCEngineCDROM.cs
Normal file
90
SabreTools.Serialization.Readers/PCEngineCDROM.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.Data.Models.PCEngineCDROM;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Matching;
|
||||
using SabreTools.Numerics.Extensions;
|
||||
|
||||
namespace SabreTools.Serialization.Readers
|
||||
{
|
||||
public class PCEngineCDROM : BaseBinaryReader<Header>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Header? Deserialize(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data is null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
// Simple check for a valid stream length
|
||||
if (data.Length - data.Position < 2 * Constants.SectorSize)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Verify expected signature
|
||||
var magic = data.PeekBytes(16);
|
||||
if (!magic.EqualsExactly(Constants.MagicBytes))
|
||||
return null;
|
||||
|
||||
// Deserialize the Header
|
||||
var header = new Header();
|
||||
header.BootSector = ParseBootSector(data);
|
||||
header.IPL = ParseIPL(data);
|
||||
|
||||
return header;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a BootSector
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled BootSector on success, null on error</returns>
|
||||
public static BootSector ParseBootSector(Stream data)
|
||||
{
|
||||
var bootSector = new BootSector();
|
||||
|
||||
bootSector.CopyrightString = data.ReadBytes(806);
|
||||
bootSector.BootROM = data.ReadBytes(432);
|
||||
bootSector.Padding = data.ReadBytes(810);
|
||||
|
||||
return bootSector;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a IPL
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled IPL on success, null on error</returns>
|
||||
public static IPL ParseIPL(Stream data)
|
||||
{
|
||||
var ipl = new IPL();
|
||||
|
||||
ipl.IPLBLK = data.ReadUInt24BigEndian();
|
||||
ipl.IPLBLN = data.ReadByteValue();
|
||||
ipl.IPLSTA = data.ReadUInt16LittleEndian();
|
||||
ipl.IPLJMP = data.ReadUInt16LittleEndian();
|
||||
ipl.IPLMPR = data.ReadBytes(5);
|
||||
ipl.OpenMode = (OpenMode)data.ReadByteValue();
|
||||
ipl.GRPBLK = data.ReadUInt24BigEndian();
|
||||
ipl.GRPBLN = data.ReadByteValue();
|
||||
ipl.GRPADR = data.ReadUInt16LittleEndian();
|
||||
ipl.ADPBLK = data.ReadUInt24BigEndian();
|
||||
ipl.ADPBLN = data.ReadByteValue();
|
||||
ipl.ADPRATE = data.ReadByteValue();
|
||||
ipl.Reserved = data.ReadBytes(7);
|
||||
ipl.SystemString = data.ReadBytes(24);
|
||||
ipl.CopyrightString = data.ReadBytes(50);
|
||||
ipl.ProgramName = data.ReadBytes(16);
|
||||
ipl.AdditionalString = data.ReadBytes(6);
|
||||
|
||||
return ipl;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
SabreTools.Wrappers.Test/PCEngineCDROMTests.cs
Normal file
60
SabreTools.Wrappers.Test/PCEngineCDROMTests.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Wrappers.Test
|
||||
{
|
||||
public class PCEngineCDROMTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var actual = PCEngineCDROM.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var actual = PCEngineCDROM.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var actual = PCEngineCDROM.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var actual = PCEngineCDROM.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var actual = PCEngineCDROM.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var actual = PCEngineCDROM.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
SabreTools.Wrappers.Test/PCEngineDiscImageTests.cs
Normal file
60
SabreTools.Wrappers.Test/PCEngineDiscImageTests.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Wrappers.Test
|
||||
{
|
||||
public class PCEngineDiscImageTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var actual = PCEngineDiscImage.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var actual = PCEngineDiscImage.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var actual = PCEngineDiscImage.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var actual = PCEngineDiscImage.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var actual = PCEngineDiscImage.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var actual = PCEngineDiscImage.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
SabreTools.Wrappers/PCEngineCDROM.Printing.cs
Normal file
83
SabreTools.Wrappers/PCEngineCDROM.Printing.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using SabreTools.Data.Models.PCEngineCDROM;
|
||||
using SabreTools.Text.Extensions;
|
||||
|
||||
namespace SabreTools.Wrappers
|
||||
{
|
||||
public partial class PCEngineCDROM : IPrintable
|
||||
{
|
||||
#if NETCOREAPP
|
||||
/// <inheritdoc/>
|
||||
public string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions);
|
||||
#else
|
||||
/// <inheritdoc/>
|
||||
public string ExportJSON() => Newtonsoft.Json.JsonConvert.SerializeObject(Model, _jsonSerializerOptions);
|
||||
#endif
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void PrintInformation(StringBuilder builder)
|
||||
{
|
||||
builder.AppendLine("PC Engine CD-ROM Header Information:");
|
||||
builder.AppendLine("-------------------------");
|
||||
builder.AppendLine();
|
||||
|
||||
Print(builder, Model.BootSector);
|
||||
Print(builder, Model.IPL);
|
||||
}
|
||||
|
||||
internal static void Print(StringBuilder builder, BootSector bootSector)
|
||||
{
|
||||
builder.AppendLine(" Boot Sector:");
|
||||
builder.AppendLine(" -------------------------");
|
||||
|
||||
#if NET462_OR_GREATER || NETCOREAPP
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
#endif
|
||||
|
||||
var message = Environment.NewLine + " " + Encoding.GetEncoding(932).GetString(bootSector.CopyrightString).Replace("\0", Environment.NewLine + " ");
|
||||
|
||||
builder.AppendLine(message, " Copyright String");
|
||||
builder.AppendLine(bootSector.BootROM, " HuC6280 Machine Code");
|
||||
if (bootSector.Padding is null)
|
||||
builder.AppendLine(bootSector.Padding, " Padding Bytes");
|
||||
else if (Array.TrueForAll(bootSector.Padding, b => b == 0))
|
||||
builder.AppendLine("Zeroed", " Padding Bytes");
|
||||
else
|
||||
builder.AppendLine("Not Zeroed", " Padding Bytes");
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
internal static void Print(StringBuilder builder, IPL ipl)
|
||||
{
|
||||
builder.AppendLine(" Initial Program Loader:");
|
||||
builder.AppendLine(" -------------------------");
|
||||
|
||||
builder.AppendLine((uint)ipl.IPLBLK, " Load Start Record Number of CD");
|
||||
builder.AppendLine(ipl.IPLBLN, " Load Block Length of CD");
|
||||
builder.AppendLine(ipl.IPLSTA, " Program Load Address");
|
||||
builder.AppendLine(ipl.IPLJMP, " Program Execute Address");
|
||||
builder.AppendLine(ipl.IPLMPR, " Memory Page Register (2-6)");
|
||||
builder.AppendLine((byte)ipl.OpenMode, " OpenMode");
|
||||
builder.AppendLine((uint)ipl.GRPBLK, " Opening Graphic Data Record Number");
|
||||
builder.AppendLine(ipl.GRPBLN, " Opening Graphic Data Length");
|
||||
builder.AppendLine(ipl.GRPADR, " Opening Graphic Data Read Address");
|
||||
builder.AppendLine((uint)ipl.ADPBLK, " Opening ADPCM Data Record Number");
|
||||
builder.AppendLine(ipl.ADPBLN, " Opening ADPCM Data Length");
|
||||
builder.AppendLine(ipl.ADPRATE, " Opening ADPCM Sampling Rate");
|
||||
if (ipl.Reserved is not null && Array.TrueForAll(ipl.Reserved, b => b == 0))
|
||||
builder.AppendLine($"Zeroed", " Reserved Bytes");
|
||||
else
|
||||
builder.AppendLine(ipl.Reserved, " Reserved Bytes");
|
||||
|
||||
builder.AppendLine(Encoding.GetEncoding(932).GetString(ipl.SystemString), " ID String");
|
||||
builder.AppendLine(Encoding.GetEncoding(932).GetString(ipl.CopyrightString), " Copyright String");
|
||||
builder.AppendLine(Encoding.GetEncoding(932).GetString(ipl.ProgramName), " Program Name");
|
||||
builder.AppendLine(Encoding.GetEncoding(932).GetString(ipl.AdditionalString), " Additional String");
|
||||
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
123
SabreTools.Wrappers/PCEngineCDROM.cs
Normal file
123
SabreTools.Wrappers/PCEngineCDROM.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Data.Models.PCEngineCDROM;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Matching;
|
||||
using SabreTools.Numerics.Extensions;
|
||||
|
||||
namespace SabreTools.Wrappers
|
||||
{
|
||||
public partial class PCEngineCDROM : WrapperBase<Header>
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string DescriptionString => "PC Engine CD-ROM² / TurboGrafx-CD Header";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extension Properties
|
||||
|
||||
/// <inheritdoc cref="Header.BootSector"/>
|
||||
public BootSector BootSector => Model.BootSector;
|
||||
|
||||
/// <inheritdoc cref="Header.IPL"/>
|
||||
public IPL IPL => Model.IPL;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineCDROM(Header model, byte[] data) : base(model, data) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineCDROM(Header model, byte[] data, int offset) : base(model, data, offset) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineCDROM(Header model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineCDROM(Header model, Stream data) : base(model, data) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineCDROM(Header model, Stream data, long offset) : base(model, data, offset) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineCDROM(Header model, Stream data, long offset, long length) : base(model, data, offset, length) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a PCEngineCDROM Header from a byte array and offset
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing the PCEngineCDROM Header</param>
|
||||
/// <param name="offset">Offset within the array to parse</param>
|
||||
/// <returns>A PCEngineCDROM Header wrapper on success, null on failure</returns>
|
||||
public static PCEngineCDROM? Create(byte[]? data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data is 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 a PCEngineCDROM Header from a Stream
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the PCEngineCDROM Header</param>
|
||||
/// <returns>A PCEngineCDROM Header wrapper on success, null on failure</returns>
|
||||
public static PCEngineCDROM? Create(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data is null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Quit early if no data in stream
|
||||
if (data.Length - data.Position < 2 * Constants.SectorSize)
|
||||
return null;
|
||||
|
||||
// PC Engine CD-ROM header can exist after some amount of pre-gap (check 250 sectors)
|
||||
for (int i = 0; i < 250; i++)
|
||||
{
|
||||
byte[] startBytes = data.PeekBytes(16);
|
||||
if (startBytes.EqualsExactly(Constants.MagicBytes))
|
||||
break;
|
||||
else if (startBytes.EqualsExactly(Constants.PregapBytes) && data.Length - data.Position >= 3 * Constants.SectorSize)
|
||||
data.SeekIfPossible(Constants.SectorSize, SeekOrigin.Current);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cache the current offset (after the pregap)
|
||||
long currentOffset = data.Position;
|
||||
|
||||
var model = new Serialization.Readers.PCEngineCDROM().Deserialize(data);
|
||||
if (model is null)
|
||||
return null;
|
||||
|
||||
// Reset stream
|
||||
data.Seek(currentOffset, SeekOrigin.Begin);
|
||||
|
||||
return new PCEngineCDROM(model, data, currentOffset);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
126
SabreTools.Wrappers/PCEngineDiscImage.cs
Normal file
126
SabreTools.Wrappers/PCEngineDiscImage.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Data.Models.PCEngineCDROM;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Matching;
|
||||
using SabreTools.Numerics.Extensions;
|
||||
|
||||
namespace SabreTools.Wrappers
|
||||
{
|
||||
public partial class PCEngineDiscImage : WrapperBase<Header>
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string DescriptionString => "PC Engine CD-ROM² / TurboGrafx-CD Disc Image";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extension Properties
|
||||
|
||||
/// <inheritdoc cref="Header.BootSector"/>
|
||||
public BootSector BootSector => Model.BootSector;
|
||||
|
||||
/// <inheritdoc cref="Header.IPL"/>
|
||||
public IPL IPL => Model.IPL;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineDiscImage(Header model, byte[] data) : base(model, data) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineDiscImage(Header model, byte[] data, int offset) : base(model, data, offset) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineDiscImage(Header model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineDiscImage(Header model, Stream data) : base(model, data) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineDiscImage(Header model, Stream data, long offset) : base(model, data, offset) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PCEngineDiscImage(Header model, Stream data, long offset, long length) : base(model, data, offset, length) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a PCEngineCDROM Header from a byte array and offset
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing the PCEngineCDROM Header</param>
|
||||
/// <param name="offset">Offset within the array to parse</param>
|
||||
/// <returns>A PCEngineCDROM Header wrapper on success, null on failure</returns>
|
||||
public static PCEngineCDROM? Create(byte[]? data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data is 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 a PCEngineCDROM Header from a Stream
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the PCEngineCDROM Header</param>
|
||||
/// <returns>A PCEngineCDROM Header wrapper on success, null on failure</returns>
|
||||
public static PCEngineCDROM? Create(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data is null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Create user data sub-stream
|
||||
var userData = new Data.Extensions.CDROMExtensions.UserDataStream(data);
|
||||
|
||||
// Quit early if no data in stream
|
||||
if (userData.Length - userData.Position < 2 * Constants.SectorSize)
|
||||
return null;
|
||||
|
||||
// PC Engine CD-ROM header can exist after some amount of pre-gap (check 250 sectors)
|
||||
for (int i = 0; i < 250; i++)
|
||||
{
|
||||
byte[] startBytes = userData.PeekBytes(16);
|
||||
if (startBytes.EqualsExactly(Constants.MagicBytes))
|
||||
break;
|
||||
else if (startBytes.EqualsExactly(Constants.PregapBytes) && userData.Length - userData.Position >= 3 * Constants.SectorSize)
|
||||
userData.SeekIfPossible(Constants.SectorSize, SeekOrigin.Current);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cache the current offset (after the pregap)
|
||||
long currentOffset = userData.Position;
|
||||
|
||||
var model = new Serialization.Readers.PCEngineCDROM().Deserialize(userData);
|
||||
if (model is null)
|
||||
return null;
|
||||
|
||||
// Reset stream
|
||||
userData.Seek(currentOffset, SeekOrigin.Begin);
|
||||
|
||||
return new PCEngineCDROM(model, userData, currentOffset);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,14 @@ namespace SabreTools.Wrappers
|
||||
// Cache the current offset
|
||||
long initialOffset = stream.Position;
|
||||
|
||||
// Attempt to open standard ISO9660 CDROM wrapper
|
||||
var cdromWrapper = CDROM.Create(stream);
|
||||
if (cdromWrapper is not null)
|
||||
return cdromWrapper;
|
||||
|
||||
// Reset position in stream
|
||||
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to get a 3DO / M2 disc image wrapper
|
||||
var operaDiscImageWrapper = OperaDiscImage.Create(stream);
|
||||
if (operaDiscImageWrapper is not null)
|
||||
@@ -107,12 +115,14 @@ namespace SabreTools.Wrappers
|
||||
// Reset position in stream
|
||||
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
||||
|
||||
// Fallback to standard CDROM wrapper
|
||||
var cdromWrapper = CDROM.Create(stream);
|
||||
if (cdromWrapper is null)
|
||||
return null;
|
||||
// Try to get a PC Engine CDROM wrapper
|
||||
// This reads a lot for detection, do this step last
|
||||
var pcEngineDiscImageWrapper = PCEngineDiscImage.Create(stream);
|
||||
if (pcEngineDiscImageWrapper is not null)
|
||||
return pcEngineDiscImageWrapper;
|
||||
|
||||
return cdromWrapper;
|
||||
// No known filesystems found
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -129,7 +139,7 @@ namespace SabreTools.Wrappers
|
||||
// Cache the current offset
|
||||
long initialOffset = stream.Position;
|
||||
|
||||
// Try to get an Xbox disc image wrapper
|
||||
// Try to get an Xbox disc image wrapper (must be before ISO9660)
|
||||
var xboxWrapper = XboxISO.Create(stream);
|
||||
if (xboxWrapper is not null)
|
||||
return xboxWrapper;
|
||||
@@ -137,6 +147,14 @@ namespace SabreTools.Wrappers
|
||||
// Reset position in stream
|
||||
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to standard ISO9660 wrapper
|
||||
var isoWrapper = ISO9660.Create(stream);
|
||||
if (isoWrapper is not null)
|
||||
return isoWrapper;
|
||||
|
||||
// Reset position in stream
|
||||
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
||||
|
||||
// Try to get a 3DO / M2 disc image wrapper
|
||||
var operaFSWrapper = OperaFS.Create(stream);
|
||||
if (operaFSWrapper is not null)
|
||||
@@ -145,12 +163,14 @@ namespace SabreTools.Wrappers
|
||||
// Reset position in stream
|
||||
stream.SeekIfPossible(initialOffset, SeekOrigin.Begin);
|
||||
|
||||
// Fallback to standard ISO9660 wrapper
|
||||
var isoWrapper = ISO9660.Create(stream);
|
||||
if (isoWrapper is null)
|
||||
return null;
|
||||
// Try to get a PC Engine CDROM disc image wrapper
|
||||
// This reads a lot for detection, do this step last
|
||||
var pcEngineCDROMWrapper = PCEngineCDROM.Create(stream);
|
||||
if (pcEngineCDROMWrapper is not null)
|
||||
return pcEngineCDROMWrapper;
|
||||
|
||||
return isoWrapper;
|
||||
// No known filesystems found
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -335,6 +355,14 @@ namespace SabreTools.Wrappers
|
||||
return WrapperType.CDROM;
|
||||
}
|
||||
|
||||
// Some CD-ROM images have no sync bytes in pregap
|
||||
if (magic.StartsWith([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||
&& (extension.Equals("bin", StringComparison.OrdinalIgnoreCase)
|
||||
|| extension.Equals("skeleton", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return WrapperType.CDROM;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CFB
|
||||
|
||||
Reference in New Issue
Block a user