Move XBE models, add skeleton reader

This commit is contained in:
Matt Nadareski
2026-03-13 10:51:31 -04:00
parent 3459c99b29
commit 1d83d40b9b
12 changed files with 319 additions and 88 deletions

View File

@@ -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 |

View File

@@ -198,20 +198,5 @@ namespace SabreTools.Data.Models.Xbox
{ 'L', "USA / Europe" },
{ 'W', "World" },
};
/// <summary>
/// XBox Executable magic number ("XBEH")
/// </summary>
public static readonly byte[] XbeMagicBytes = [0x58, 0x42, 0x45, 0x48];
/// <summary>
/// XBox Executable magic number ("XBEH")
/// </summary>
public const string XbeMagicString = "XBEH";
/// <summary>
/// XBox Executable magic number ("XBEH")
/// </summary>
public const uint XbeMagicUInt32 = 0x48454258;
}
}

View File

@@ -1,50 +0,0 @@
namespace SabreTools.Data.Models.Xbox
{
/// <summary>
/// XBox Executable format
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
public class XBE
{
/// <summary>
/// XBE header
/// </summary>
public XBEHeader? Header { get; set; }
/// <summary>
/// Certificate structure pointed to by <see cref="XBEHeader.CertificateAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public XBECertificate? Certificate { get; set; }
/// <summary>
/// Section headers pointed to by <see cref="XBEHeader.SectionHeadersAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public XBESectionHeader[] SectionHeaders { get; set; } = [];
/// <summary>
/// Thread Local Storage (TLS) structure pointed to by <see cref="XBEHeader.TLSAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public XBEThreadLocalStorage? ThreadLocalStorage { get; set; }
/// <summary>
/// Library versions pointed to by <see cref="XBEHeader.LibraryVersionsAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public XBELibraryVersion[] LibraryVersions { get; set; } = [];
/// <summary>
/// Kernel library version pointed to by <see cref="XBEHeader.KernelLibraryVersionAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public XBELibraryVersion? KernelLibraryVersion { get; set; }
/// <summary>
/// XAPI library version pointed to by <see cref="XBEHeader.XAPILibraryVersionAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public XBELibraryVersion? XAPILibraryVersion { get; set; }
}
}

View File

@@ -1,10 +1,10 @@
namespace SabreTools.Data.Models.Xbox
namespace SabreTools.Data.Models.XboxExecutable
{
/// <summary>
/// XBox Executable certificate
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
public class XBECertificate
public class Certificate
{
/// <summary>
/// Number of bytes that should be reserved for this certificate.
@@ -38,12 +38,12 @@ namespace SabreTools.Data.Models.Xbox
/// <summary>
/// Allowed media types for this .XBE.
/// </summary>
public XbeAllowedMediaTypes AllowedMediaTypes { get; set; }
public AllowedMediaTypes AllowedMediaTypes { get; set; }
/// <summary>
/// Game region for this .XBE.
/// </summary>
public XbeGameRegion GameRegion { get; set; }
public GameRegion GameRegion { get; set; }
/// <summary>
/// Game ratings for this .XBE. It is typically safe to set this to 0xFFFFFFFF.
@@ -73,6 +73,6 @@ namespace SabreTools.Data.Models.Xbox
/// <summary>
/// 16 x 16-byte Signature Keys. An unsigned .XBE can just zero these out.
/// </summary>
public byte[,] AlternateSignatureKeys { get; set; } = new byte[16, 16];
public byte[][] AlternateSignatureKeys { get; set; } = new byte[16][];
}
}

View File

@@ -0,0 +1,21 @@
namespace SabreTools.Data.Models.XboxExecutable
{
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
public static class Constants
{
/// <summary>
/// XBox Executable magic number ("XBEH")
/// </summary>
public static readonly byte[] MagicBytes = [0x58, 0x42, 0x45, 0x48];
/// <summary>
/// XBox Executable magic number ("XBEH")
/// </summary>
public const string MagicString = "XBEH";
/// <summary>
/// XBox Executable magic number ("XBEH")
/// </summary>
public const uint MagicUInt32 = 0x48454258;
}
}

View File

@@ -1,13 +1,13 @@
using System;
namespace SabreTools.Data.Models.Xbox
namespace SabreTools.Data.Models.XboxExecutable
{
/// <summary>
/// Allowed media types for this .XBE
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
[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
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
[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
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
[Flags]
public enum XbeInitializationFlags : uint
public enum InitializationFlags : uint
{
MountUtilityDrive = 0x00000001,
FormatUtilityDrive = 0x00000002,
@@ -55,7 +55,7 @@ namespace SabreTools.Data.Models.Xbox
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
[Flags]
public enum XbeLibraryFlags : uint
public enum LibraryFlags : uint
{
/// <remarks>13-Bit Mask</remarks>
QFEVersion = 0x1FFF,
@@ -72,7 +72,7 @@ namespace SabreTools.Data.Models.Xbox
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
[Flags]
public enum XbeSectionFlags : uint
public enum SectionFlags : uint
{
Writable = 0x00000001,
Preload = 0x00000002,

View File

@@ -0,0 +1,50 @@
namespace SabreTools.Data.Models.XboxExecutable
{
/// <summary>
/// XBox Executable format
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
public class Executable
{
/// <summary>
/// XBE header
/// </summary>
public Header? Header { get; set; }
/// <summary>
/// Certificate structure pointed to by <see cref="Header.CertificateAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public Certificate? Certificate { get; set; }
/// <summary>
/// Section headers pointed to by <see cref="Header.SectionHeadersAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public SectionHeader[] SectionHeaders { get; set; } = [];
/// <summary>
/// Thread Local Storage (TLS) structure pointed to by <see cref="Header.TLSAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public ThreadLocalStorage? ThreadLocalStorage { get; set; }
/// <summary>
/// Library versions pointed to by <see cref="Header.LibraryVersionsAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public LibraryVersion[] LibraryVersions { get; set; } = [];
/// <summary>
/// Kernel library version pointed to by <see cref="Header.KernelLibraryVersionAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public LibraryVersion? KernelLibraryVersion { get; set; }
/// <summary>
/// XAPI library version pointed to by <see cref="Header.XAPILibraryVersionAddress"/>
/// </summary>
/// TODO: Determine if address is real or virtual
public LibraryVersion? XAPILibraryVersion { get; set; }
}
}

View File

@@ -1,10 +1,10 @@
namespace SabreTools.Data.Models.Xbox
namespace SabreTools.Data.Models.XboxExecutable
{
/// <summary>
/// XBox Executable format header
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
public class XBEHeader
public class Header
{
/// <summary>
/// "XBEH"
@@ -60,7 +60,7 @@ namespace SabreTools.Data.Models.Xbox
/// <summary>
/// Various flags for this .XBE file.
/// </summary>
public XbeInitializationFlags InitializationFlags { get; set; }
public InitializationFlags InitializationFlags { get; set; }
/// <summary>
/// Address to the Image entry point, after the .XBE is loaded into memory. This is where execution starts.

View File

@@ -1,10 +1,10 @@
namespace SabreTools.Data.Models.Xbox
namespace SabreTools.Data.Models.XboxExecutable
{
/// <summary>
/// XBox Executable library version
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
public class XBELibraryVersion
public class LibraryVersion
{
/// <summary>
/// 8-byte name of this library. (i.e. "XAPILIB")
@@ -29,6 +29,6 @@ namespace SabreTools.Data.Models.Xbox
/// <summary>
/// Various flags for this library.
/// </summary>
public XbeLibraryFlags LibraryFlags { get; set; }
public LibraryFlags LibraryFlags { get; set; }
}
}

View File

@@ -1,16 +1,16 @@
namespace SabreTools.Data.Models.Xbox
namespace SabreTools.Data.Models.XboxExecutable
{
/// <summary>
/// XBox Executable format section header
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
/// <see cref="COFF.SectionHeader"/>
public class XBESectionHeader
public class SectionHeader
{
/// <summary>
/// Various flags for this .XBE section.
/// </summary>
public XbeSectionFlags SectionFlags { get; set; }
public SectionFlags SectionFlags { get; set; }
/// <summary>
/// Address of memory to load this section at.

View File

@@ -1,10 +1,10 @@
namespace SabreTools.Data.Models.Xbox
namespace SabreTools.Data.Models.XboxExecutable
{
/// <summary>
/// XBox Executable TLS
/// XBox Executable thread-local storage
/// </summary>
/// <see href="https://www.caustik.com/cxbx/download/xbe.htm"/>
public class XBEThreadLocalStorage
public class ThreadLocalStorage
{
/// <summary>
/// Address, after the .XBE is loaded into memory, of this .XBE's TLS Data.

View File

@@ -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<Executable>
{
/// <inheritdoc/>
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;
}
}
/// <summary>
/// Parse a Stream into a Certificate
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled Certificate on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a Header
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled Header on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a LibraryVersion
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LibraryVersion on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a SectionHeader
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled SectionHeader on success, null on error</returns>
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;
}
/// <summary>
/// Parse a Stream into a ThreadLocalStorage
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled ThreadLocalStorage on success, null on error</returns>
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;
}
}
}