From 1d83d40b9bf0ac5f36162ce15604b5cb4489e36f Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 13 Mar 2026 10:51:31 -0400 Subject: [PATCH] Move XBE models, add skeleton reader --- SabreTools.Serialization/Models/README.MD | 1 + .../Models/Xbox/Constants.cs | 15 -- SabreTools.Serialization/Models/Xbox/XBE.cs | 50 ---- .../Certificate.cs} | 10 +- .../Models/XboxExecutable/Constants.cs | 21 ++ .../Models/{Xbox => XboxExecutable}/Enums.cs | 12 +- .../Models/XboxExecutable/Executable.cs | 50 ++++ .../XBEHeader.cs => XboxExecutable/Header.cs} | 6 +- .../LibraryVersion.cs} | 6 +- .../SectionHeader.cs} | 6 +- .../ThreadLocalStorage.cs} | 6 +- .../Readers/XboxExecutable.cs | 224 ++++++++++++++++++ 12 files changed, 319 insertions(+), 88 deletions(-) delete mode 100644 SabreTools.Serialization/Models/Xbox/XBE.cs rename SabreTools.Serialization/Models/{Xbox/XBECertificate.cs => XboxExecutable/Certificate.cs} (89%) create mode 100644 SabreTools.Serialization/Models/XboxExecutable/Constants.cs rename SabreTools.Serialization/Models/{Xbox => XboxExecutable}/Enums.cs (90%) create mode 100644 SabreTools.Serialization/Models/XboxExecutable/Executable.cs rename SabreTools.Serialization/Models/{Xbox/XBEHeader.cs => XboxExecutable/Header.cs} (98%) rename SabreTools.Serialization/Models/{Xbox/XBELibraryVersion.cs => XboxExecutable/LibraryVersion.cs} (86%) rename SabreTools.Serialization/Models/{Xbox/XBESectionHeader.cs => XboxExecutable/SectionHeader.cs} (93%) rename SabreTools.Serialization/Models/{Xbox/XBEThreadLocalStorage.cs => XboxExecutable/ThreadLocalStorage.cs} (89%) create mode 100644 SabreTools.Serialization/Readers/XboxExecutable.cs diff --git a/SabreTools.Serialization/Models/README.MD b/SabreTools.Serialization/Models/README.MD index f9bb537c..309e62ab 100644 --- a/SabreTools.Serialization/Models/README.MD +++ b/SabreTools.Serialization/Models/README.MD @@ -14,6 +14,7 @@ Not all of this information was able to be gathered directly from the files in q | [Atari 7800 Development Wiki](https://7800.8bitdev.org/index.php/Main_Page) | Atari7800Cart | | [BYTE*](https://web.archive.org/web/20240703222951/https://bytepointer.com/index.htm) | NewExecutable | | [cabextract/libmspack](https://www.cabextract.org.uk/) | LZ | +| [cxbx](https://www.caustik.com/cxbx/) | XboxExecutable | | [DBox](https://dbox.tools/) | Xbox | | [DebugInfo.com](https://www.debuginfo.com/index.html) | PortableExecutable | | [Devilsclaw](https://devilsclaws.net/) | PFF | diff --git a/SabreTools.Serialization/Models/Xbox/Constants.cs b/SabreTools.Serialization/Models/Xbox/Constants.cs index 0a437f5d..e258b0e4 100644 --- a/SabreTools.Serialization/Models/Xbox/Constants.cs +++ b/SabreTools.Serialization/Models/Xbox/Constants.cs @@ -198,20 +198,5 @@ namespace SabreTools.Data.Models.Xbox { 'L', "USA / Europe" }, { 'W', "World" }, }; - - /// - /// XBox Executable magic number ("XBEH") - /// - public static readonly byte[] XbeMagicBytes = [0x58, 0x42, 0x45, 0x48]; - - /// - /// XBox Executable magic number ("XBEH") - /// - public const string XbeMagicString = "XBEH"; - - /// - /// XBox Executable magic number ("XBEH") - /// - public const uint XbeMagicUInt32 = 0x48454258; } } diff --git a/SabreTools.Serialization/Models/Xbox/XBE.cs b/SabreTools.Serialization/Models/Xbox/XBE.cs deleted file mode 100644 index bd1ea48d..00000000 --- a/SabreTools.Serialization/Models/Xbox/XBE.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace SabreTools.Data.Models.Xbox -{ - /// - /// XBox Executable format - /// - /// - public class XBE - { - /// - /// XBE header - /// - public XBEHeader? Header { get; set; } - - /// - /// Certificate structure pointed to by - /// - /// TODO: Determine if address is real or virtual - public XBECertificate? Certificate { get; set; } - - /// - /// Section headers pointed to by - /// - /// TODO: Determine if address is real or virtual - public XBESectionHeader[] SectionHeaders { get; set; } = []; - - /// - /// Thread Local Storage (TLS) structure pointed to by - /// - /// TODO: Determine if address is real or virtual - public XBEThreadLocalStorage? ThreadLocalStorage { get; set; } - - /// - /// Library versions pointed to by - /// - /// TODO: Determine if address is real or virtual - public XBELibraryVersion[] LibraryVersions { get; set; } = []; - - /// - /// Kernel library version pointed to by - /// - /// TODO: Determine if address is real or virtual - public XBELibraryVersion? KernelLibraryVersion { get; set; } - - /// - /// XAPI library version pointed to by - /// - /// TODO: Determine if address is real or virtual - public XBELibraryVersion? XAPILibraryVersion { get; set; } - } -} diff --git a/SabreTools.Serialization/Models/Xbox/XBECertificate.cs b/SabreTools.Serialization/Models/XboxExecutable/Certificate.cs similarity index 89% rename from SabreTools.Serialization/Models/Xbox/XBECertificate.cs rename to SabreTools.Serialization/Models/XboxExecutable/Certificate.cs index d3ce5998..b58f0185 100644 --- a/SabreTools.Serialization/Models/Xbox/XBECertificate.cs +++ b/SabreTools.Serialization/Models/XboxExecutable/Certificate.cs @@ -1,10 +1,10 @@ -namespace SabreTools.Data.Models.Xbox +namespace SabreTools.Data.Models.XboxExecutable { /// /// XBox Executable certificate /// /// - public class XBECertificate + public class Certificate { /// /// Number of bytes that should be reserved for this certificate. @@ -38,12 +38,12 @@ namespace SabreTools.Data.Models.Xbox /// /// Allowed media types for this .XBE. /// - public XbeAllowedMediaTypes AllowedMediaTypes { get; set; } + public AllowedMediaTypes AllowedMediaTypes { get; set; } /// /// Game region for this .XBE. /// - public XbeGameRegion GameRegion { get; set; } + public GameRegion GameRegion { get; set; } /// /// Game ratings for this .XBE. It is typically safe to set this to 0xFFFFFFFF. @@ -73,6 +73,6 @@ namespace SabreTools.Data.Models.Xbox /// /// 16 x 16-byte Signature Keys. An unsigned .XBE can just zero these out. /// - public byte[,] AlternateSignatureKeys { get; set; } = new byte[16, 16]; + public byte[][] AlternateSignatureKeys { get; set; } = new byte[16][]; } } diff --git a/SabreTools.Serialization/Models/XboxExecutable/Constants.cs b/SabreTools.Serialization/Models/XboxExecutable/Constants.cs new file mode 100644 index 00000000..0fb39cad --- /dev/null +++ b/SabreTools.Serialization/Models/XboxExecutable/Constants.cs @@ -0,0 +1,21 @@ +namespace SabreTools.Data.Models.XboxExecutable +{ + /// + public static class Constants + { + /// + /// XBox Executable magic number ("XBEH") + /// + public static readonly byte[] MagicBytes = [0x58, 0x42, 0x45, 0x48]; + + /// + /// XBox Executable magic number ("XBEH") + /// + public const string MagicString = "XBEH"; + + /// + /// XBox Executable magic number ("XBEH") + /// + public const uint MagicUInt32 = 0x48454258; + } +} diff --git a/SabreTools.Serialization/Models/Xbox/Enums.cs b/SabreTools.Serialization/Models/XboxExecutable/Enums.cs similarity index 90% rename from SabreTools.Serialization/Models/Xbox/Enums.cs rename to SabreTools.Serialization/Models/XboxExecutable/Enums.cs index 6b4f9c46..758b6bb6 100644 --- a/SabreTools.Serialization/Models/Xbox/Enums.cs +++ b/SabreTools.Serialization/Models/XboxExecutable/Enums.cs @@ -1,13 +1,13 @@ using System; -namespace SabreTools.Data.Models.Xbox +namespace SabreTools.Data.Models.XboxExecutable { /// /// Allowed media types for this .XBE /// /// [Flags] - public enum XbeAllowedMediaTypes : uint + public enum AllowedMediaTypes : uint { XBEIMAGE_MEDIA_TYPE_HARD_DISK = 0x00000001, XBEIMAGE_MEDIA_TYPE_DVD_X2 = 0x00000002, @@ -29,7 +29,7 @@ namespace SabreTools.Data.Models.Xbox /// /// [Flags] - public enum XbeGameRegion : uint + public enum GameRegion : uint { XBEIMAGE_GAME_REGION_NA = 0x00000001, XBEIMAGE_GAME_REGION_JAPAN = 0x00000002, @@ -42,7 +42,7 @@ namespace SabreTools.Data.Models.Xbox /// /// [Flags] - public enum XbeInitializationFlags : uint + public enum InitializationFlags : uint { MountUtilityDrive = 0x00000001, FormatUtilityDrive = 0x00000002, @@ -55,7 +55,7 @@ namespace SabreTools.Data.Models.Xbox /// /// [Flags] - public enum XbeLibraryFlags : uint + public enum LibraryFlags : uint { /// 13-Bit Mask QFEVersion = 0x1FFF, @@ -72,7 +72,7 @@ namespace SabreTools.Data.Models.Xbox /// /// [Flags] - public enum XbeSectionFlags : uint + public enum SectionFlags : uint { Writable = 0x00000001, Preload = 0x00000002, diff --git a/SabreTools.Serialization/Models/XboxExecutable/Executable.cs b/SabreTools.Serialization/Models/XboxExecutable/Executable.cs new file mode 100644 index 00000000..a83a9b58 --- /dev/null +++ b/SabreTools.Serialization/Models/XboxExecutable/Executable.cs @@ -0,0 +1,50 @@ +namespace SabreTools.Data.Models.XboxExecutable +{ + /// + /// XBox Executable format + /// + /// + public class Executable + { + /// + /// XBE header + /// + public Header? Header { get; set; } + + /// + /// Certificate structure pointed to by + /// + /// TODO: Determine if address is real or virtual + public Certificate? Certificate { get; set; } + + /// + /// Section headers pointed to by + /// + /// TODO: Determine if address is real or virtual + public SectionHeader[] SectionHeaders { get; set; } = []; + + /// + /// Thread Local Storage (TLS) structure pointed to by + /// + /// TODO: Determine if address is real or virtual + public ThreadLocalStorage? ThreadLocalStorage { get; set; } + + /// + /// Library versions pointed to by + /// + /// TODO: Determine if address is real or virtual + public LibraryVersion[] LibraryVersions { get; set; } = []; + + /// + /// Kernel library version pointed to by + /// + /// TODO: Determine if address is real or virtual + public LibraryVersion? KernelLibraryVersion { get; set; } + + /// + /// XAPI library version pointed to by + /// + /// TODO: Determine if address is real or virtual + public LibraryVersion? XAPILibraryVersion { get; set; } + } +} diff --git a/SabreTools.Serialization/Models/Xbox/XBEHeader.cs b/SabreTools.Serialization/Models/XboxExecutable/Header.cs similarity index 98% rename from SabreTools.Serialization/Models/Xbox/XBEHeader.cs rename to SabreTools.Serialization/Models/XboxExecutable/Header.cs index cf6cfb79..f88a875a 100644 --- a/SabreTools.Serialization/Models/Xbox/XBEHeader.cs +++ b/SabreTools.Serialization/Models/XboxExecutable/Header.cs @@ -1,10 +1,10 @@ -namespace SabreTools.Data.Models.Xbox +namespace SabreTools.Data.Models.XboxExecutable { /// /// XBox Executable format header /// /// - public class XBEHeader + public class Header { /// /// "XBEH" @@ -60,7 +60,7 @@ namespace SabreTools.Data.Models.Xbox /// /// Various flags for this .XBE file. /// - public XbeInitializationFlags InitializationFlags { get; set; } + public InitializationFlags InitializationFlags { get; set; } /// /// Address to the Image entry point, after the .XBE is loaded into memory. This is where execution starts. diff --git a/SabreTools.Serialization/Models/Xbox/XBELibraryVersion.cs b/SabreTools.Serialization/Models/XboxExecutable/LibraryVersion.cs similarity index 86% rename from SabreTools.Serialization/Models/Xbox/XBELibraryVersion.cs rename to SabreTools.Serialization/Models/XboxExecutable/LibraryVersion.cs index d06457b0..ddcba94c 100644 --- a/SabreTools.Serialization/Models/Xbox/XBELibraryVersion.cs +++ b/SabreTools.Serialization/Models/XboxExecutable/LibraryVersion.cs @@ -1,10 +1,10 @@ -namespace SabreTools.Data.Models.Xbox +namespace SabreTools.Data.Models.XboxExecutable { /// /// XBox Executable library version /// /// - public class XBELibraryVersion + public class LibraryVersion { /// /// 8-byte name of this library. (i.e. "XAPILIB") @@ -29,6 +29,6 @@ namespace SabreTools.Data.Models.Xbox /// /// Various flags for this library. /// - public XbeLibraryFlags LibraryFlags { get; set; } + public LibraryFlags LibraryFlags { get; set; } } } diff --git a/SabreTools.Serialization/Models/Xbox/XBESectionHeader.cs b/SabreTools.Serialization/Models/XboxExecutable/SectionHeader.cs similarity index 93% rename from SabreTools.Serialization/Models/Xbox/XBESectionHeader.cs rename to SabreTools.Serialization/Models/XboxExecutable/SectionHeader.cs index 72738dbf..42a8e105 100644 --- a/SabreTools.Serialization/Models/Xbox/XBESectionHeader.cs +++ b/SabreTools.Serialization/Models/XboxExecutable/SectionHeader.cs @@ -1,16 +1,16 @@ -namespace SabreTools.Data.Models.Xbox +namespace SabreTools.Data.Models.XboxExecutable { /// /// XBox Executable format section header /// /// /// - public class XBESectionHeader + public class SectionHeader { /// /// Various flags for this .XBE section. /// - public XbeSectionFlags SectionFlags { get; set; } + public SectionFlags SectionFlags { get; set; } /// /// Address of memory to load this section at. diff --git a/SabreTools.Serialization/Models/Xbox/XBEThreadLocalStorage.cs b/SabreTools.Serialization/Models/XboxExecutable/ThreadLocalStorage.cs similarity index 89% rename from SabreTools.Serialization/Models/Xbox/XBEThreadLocalStorage.cs rename to SabreTools.Serialization/Models/XboxExecutable/ThreadLocalStorage.cs index 49c31feb..8264d7e0 100644 --- a/SabreTools.Serialization/Models/Xbox/XBEThreadLocalStorage.cs +++ b/SabreTools.Serialization/Models/XboxExecutable/ThreadLocalStorage.cs @@ -1,10 +1,10 @@ -namespace SabreTools.Data.Models.Xbox +namespace SabreTools.Data.Models.XboxExecutable { /// - /// XBox Executable TLS + /// XBox Executable thread-local storage /// /// - public class XBEThreadLocalStorage + public class ThreadLocalStorage { /// /// Address, after the .XBE is loaded into memory, of this .XBE's TLS Data. diff --git a/SabreTools.Serialization/Readers/XboxExecutable.cs b/SabreTools.Serialization/Readers/XboxExecutable.cs new file mode 100644 index 00000000..e745d00b --- /dev/null +++ b/SabreTools.Serialization/Readers/XboxExecutable.cs @@ -0,0 +1,224 @@ +using System.IO; +using SabreTools.Data.Models.XboxExecutable; +using SabreTools.IO.Extensions; +using static SabreTools.Data.Models.XboxExecutable.Constants; + +#pragma warning disable IDE0017 // Simplify object initialization +namespace SabreTools.Serialization.Readers +{ + public class XboxExecutable : BaseBinaryReader + { + /// + public override Executable? Deserialize(Stream? data) + { + // If the data is invalid + if (data is null || !data.CanRead) + return null; + + try + { + // Cache the current offset + long initialOffset = data.Position; + + // Create a new executable to fill + var xbe = new Executable(); + + #region ParseHeader + + // Parse the file header + var header = ParseHeader(data); + if (!header.MagicNumber.EqualsExactly(MagicBytes)) + return null; + + // Set the file header + xbe.Header = header; + + #endregion + + #region Certificate + + // TODO: Seek to and parse certificate + + #endregion + + #region Section Headers + + // TODO: Seek to and parse section headers + + #endregion + + #region TLS + + // TODO: Seek to and parse TLS + + #endregion + + #region Library Versions + + // TODO: Seek to and parse library versions + + #endregion + + #region Kernel Library Version + + // TODO: Seek to and parse kernel library version + + #endregion + + #region XAPI Library Version + + // TODO: Seek to and parse XAPI library version + + #endregion + + return xbe; + } + catch + { + // Ignore the actual error + return null; + } + } + + /// + /// Parse a Stream into a Certificate + /// + /// Stream to parse + /// Filled Certificate on success, null on error + public static Certificate ParseCertificate(Stream data) + { + var obj = new Certificate(); + + obj.SizeOfCertificate = data.ReadUInt32LittleEndian(); + obj.TimeDate = data.ReadUInt32LittleEndian(); + obj.TitleID = data.ReadUInt32LittleEndian(); + obj.TitleName = data.ReadBytes(0x50); + + obj.AlternativeTitleIDs = new uint[16]; + for (int i = 0; i < obj.AlternativeTitleIDs.Length; i++) + { + obj.AlternativeTitleIDs[i] = data.ReadUInt32LittleEndian(); + } + + obj.AllowedMediaTypes = (AllowedMediaTypes)data.ReadUInt32LittleEndian(); + obj.GameRegion = (GameRegion)data.ReadUInt32LittleEndian(); + obj.GameRatings = data.ReadUInt32LittleEndian(); + obj.DiskNumber = data.ReadUInt32LittleEndian(); + obj.Version = data.ReadUInt32LittleEndian(); + obj.LANKey = data.ReadBytes(16); + obj.SignatureKey = data.ReadBytes(16); + + obj.AlternateSignatureKeys = new byte[16][]; + for (int i = 0; i < obj.AlternateSignatureKeys.Length; i++) + { + obj.AlternateSignatureKeys[i] = data.ReadBytes(16); + } + + return obj; + } + + /// + /// Parse a Stream into a Header + /// + /// Stream to parse + /// Filled Header on success, null on error + public static Header ParseHeader(Stream data) + { + var obj = new Header(); + + obj.MagicNumber = data.ReadBytes(4); + obj.DigitalSignature = data.ReadBytes(256); + obj.BaseAddress = data.ReadUInt32LittleEndian(); + obj.SizeOfHeaders = data.ReadUInt32LittleEndian(); + obj.SizeOfImage = data.ReadUInt32LittleEndian(); + obj.SizeOfImageHeader = data.ReadUInt32LittleEndian(); + obj.TimeDate = data.ReadUInt32LittleEndian(); + obj.CertificateAddress = data.ReadUInt32LittleEndian(); + obj.NumberOfSections = data.ReadUInt32LittleEndian(); + obj.SectionHeadersAddress = data.ReadUInt32LittleEndian(); + obj.InitializationFlags = (InitializationFlags)data.ReadUInt32LittleEndian(); + obj.EntryPoint = data.ReadUInt32LittleEndian(); + obj.TLSAddress = data.ReadUInt32LittleEndian(); + obj.PEStackCommit = data.ReadUInt32LittleEndian(); + obj.PEHeapReserve = data.ReadUInt32LittleEndian(); + obj.PEHeapCommit = data.ReadUInt32LittleEndian(); + obj.PEBaseAddress = data.ReadUInt32LittleEndian(); + obj.PESizeOfImage = data.ReadUInt32LittleEndian(); + obj.PEChecksum = data.ReadUInt32LittleEndian(); + obj.PETimeDate = data.ReadUInt32LittleEndian(); + obj.DebugPathNameAddress = data.ReadUInt32LittleEndian(); + obj.DebugFileNameAddress = data.ReadUInt32LittleEndian(); + obj.DebugUnicodeFileNameAddress = data.ReadUInt32LittleEndian(); + obj.KernelImageThunkAddress = data.ReadUInt32LittleEndian(); + obj.NonKernelImportDirectoryAddress = data.ReadUInt32LittleEndian(); + obj.NumberOfLibraryVersions = data.ReadUInt32LittleEndian(); + obj.LibraryVersionsAddress = data.ReadUInt32LittleEndian(); + obj.KernelLibraryVersionAddress = data.ReadUInt32LittleEndian(); + obj.XAPILibraryVersionAddress = data.ReadUInt32LittleEndian(); + obj.LogoBitmapAddress = data.ReadUInt32LittleEndian(); + obj.LogoBitmapSize = data.ReadUInt32LittleEndian(); + + return obj; + } + + /// + /// Parse a Stream into a LibraryVersion + /// + /// Stream to parse + /// Filled LibraryVersion on success, null on error + public static LibraryVersion ParseLibraryVersion(Stream data) + { + var obj = new LibraryVersion(); + + obj.LibraryName = data.ReadBytes(8); + obj.MajorVersion = data.ReadUInt16LittleEndian(); + obj.MinorVersion = data.ReadUInt16LittleEndian(); + obj.BuildVersion = data.ReadUInt16LittleEndian(); + obj.LibraryFlags = (LibraryFlags)data.ReadUInt32LittleEndian(); + + return obj; + } + + /// + /// Parse a Stream into a SectionHeader + /// + /// Stream to parse + /// Filled SectionHeader on success, null on error + public static SectionHeader ParseSectionHeader(Stream data) + { + var obj = new SectionHeader(); + + obj.SectionFlags = (SectionFlags)data.ReadUInt32LittleEndian(); + obj.VirtualAddress = data.ReadUInt32LittleEndian(); + obj.VirtualSize = data.ReadUInt32LittleEndian(); + obj.RawAddress = data.ReadUInt32LittleEndian(); + obj.RawSize = data.ReadUInt32LittleEndian(); + obj.SectionNameAddress = data.ReadUInt32LittleEndian(); + obj.SectionNameReferenceCount = data.ReadUInt32LittleEndian(); + obj.HeadSharedPageReferenceCountAddress = data.ReadUInt32LittleEndian(); + obj.TailSharedPageReferenceCountAddress = data.ReadUInt32LittleEndian(); + obj.SectionDigest = data.ReadBytes(20); + + return obj; + } + + /// + /// Parse a Stream into a ThreadLocalStorage + /// + /// Stream to parse + /// Filled ThreadLocalStorage on success, null on error + public static ThreadLocalStorage ParseThreadLocalStorage(Stream data) + { + var obj = new ThreadLocalStorage(); + + obj.DataStartAddress = data.ReadUInt32LittleEndian(); + obj.DataEndAddress = data.ReadUInt32LittleEndian(); + obj.TLSIndexAddress = data.ReadUInt32LittleEndian(); + obj.TLSCallbackAddress = data.ReadUInt32LittleEndian(); + obj.SizeOfZeroFill = data.ReadUInt32LittleEndian(); + obj.Characteristics = data.ReadUInt32LittleEndian(); + + return obj; + } + } +}