2026-03-13 10:51:31 -04:00
|
|
|
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
|
|
|
|
|
|
2026-03-13 11:05:31 -04:00
|
|
|
// Get the certificate address
|
|
|
|
|
long certificateOffset = initialOffset + (header.CertificateAddress - header.BaseAddress);
|
|
|
|
|
if (certificateOffset >= initialOffset && certificateOffset < data.Length)
|
|
|
|
|
{
|
|
|
|
|
// Seek to the certificate
|
|
|
|
|
data.SeekIfPossible(certificateOffset, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
// Set the certificate
|
|
|
|
|
xbe.Certificate = ParseCertificate(data);
|
|
|
|
|
}
|
2026-03-13 10:51:31 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Section Headers
|
|
|
|
|
|
2026-03-13 11:05:31 -04:00
|
|
|
// Get the section table address
|
|
|
|
|
long sectionTableOffset = initialOffset + (header.SectionHeadersAddress - header.BaseAddress);
|
|
|
|
|
if (sectionTableOffset >= initialOffset && sectionTableOffset < data.Length)
|
|
|
|
|
{
|
|
|
|
|
// Seek to the section table
|
|
|
|
|
data.SeekIfPossible(sectionTableOffset, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
// Set the section table
|
|
|
|
|
xbe.SectionHeaders = new SectionHeader[xbe.Header.NumberOfSections];
|
|
|
|
|
for (int i = 0; i < xbe.Header.NumberOfSections; i++)
|
|
|
|
|
{
|
|
|
|
|
xbe.SectionHeaders[i] = ParseSectionHeader(data);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-13 10:51:31 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region TLS
|
|
|
|
|
|
2026-03-13 11:05:31 -04:00
|
|
|
// Get the TLS address
|
|
|
|
|
long tlsOffset = initialOffset + (header.TLSAddress - header.BaseAddress);
|
|
|
|
|
if (tlsOffset >= initialOffset && tlsOffset < data.Length)
|
|
|
|
|
{
|
|
|
|
|
// Seek to the TLS
|
|
|
|
|
data.SeekIfPossible(tlsOffset, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
// Set the TLS
|
|
|
|
|
xbe.ThreadLocalStorage = ParseThreadLocalStorage(data);
|
|
|
|
|
}
|
2026-03-13 10:51:31 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Library Versions
|
|
|
|
|
|
2026-03-13 11:05:31 -04:00
|
|
|
// Get the library versions table address
|
|
|
|
|
long libraryVersionsOffset = initialOffset + (header.LibraryVersionsAddress - header.BaseAddress);
|
|
|
|
|
if (libraryVersionsOffset >= initialOffset && libraryVersionsOffset < data.Length)
|
|
|
|
|
{
|
|
|
|
|
// Seek to the library versions table
|
|
|
|
|
data.SeekIfPossible(libraryVersionsOffset, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
// Set the library versions table
|
|
|
|
|
xbe.LibraryVersions = new LibraryVersion[xbe.Header.NumberOfLibraryVersions];
|
|
|
|
|
for (int i = 0; i < xbe.Header.NumberOfLibraryVersions; i++)
|
|
|
|
|
{
|
|
|
|
|
xbe.LibraryVersions[i] = ParseLibraryVersion(data);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-13 10:51:31 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Kernel Library Version
|
|
|
|
|
|
2026-03-13 11:05:31 -04:00
|
|
|
// Get the kernel library version address
|
|
|
|
|
long klvOffset = initialOffset + (header.KernelLibraryVersionAddress - header.BaseAddress);
|
|
|
|
|
if (klvOffset >= initialOffset && klvOffset < data.Length)
|
|
|
|
|
{
|
|
|
|
|
// Seek to the kernel library version
|
|
|
|
|
data.SeekIfPossible(klvOffset, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
// Set the kernel library version
|
|
|
|
|
xbe.KernelLibraryVersion = ParseLibraryVersion(data);
|
|
|
|
|
}
|
2026-03-13 10:51:31 -04:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region XAPI Library Version
|
|
|
|
|
|
2026-03-13 11:05:31 -04:00
|
|
|
// Get the XAPI library version address
|
|
|
|
|
long xapiOffset = initialOffset + (header.XAPILibraryVersionAddress - header.BaseAddress);
|
|
|
|
|
if (xapiOffset >= initialOffset && xapiOffset < data.Length)
|
|
|
|
|
{
|
|
|
|
|
// Seek to the XAPI library version
|
|
|
|
|
data.SeekIfPossible(xapiOffset, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
// Set the XAPI library version
|
|
|
|
|
xbe.XAPILibraryVersion = ParseLibraryVersion(data);
|
|
|
|
|
}
|
2026-03-13 10:51:31 -04:00
|
|
|
|
|
|
|
|
#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();
|
2026-03-14 20:51:22 -04:00
|
|
|
obj.TitleID = data.ReadUInt32LittleEndian();
|
2026-03-13 10:51:31 -04:00
|
|
|
obj.TitleName = data.ReadBytes(0x50);
|
|
|
|
|
|
2026-03-14 20:51:22 -04:00
|
|
|
obj.AlternativeTitleIDs = new uint[16];
|
2026-03-13 10:51:31 -04:00
|
|
|
for (int i = 0; i < obj.AlternativeTitleIDs.Length; i++)
|
|
|
|
|
{
|
2026-03-14 20:51:22 -04:00
|
|
|
obj.AlternativeTitleIDs[i] = data.ReadUInt32LittleEndian();
|
2026-03-13 10:51:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 12:29:17 -04:00
|
|
|
obj.OriginalCertificateSize = data.ReadUInt32LittleEndian();
|
|
|
|
|
obj.OnlineService = data.ReadUInt32LittleEndian();
|
|
|
|
|
obj.SecurityFlags = data.ReadUInt32LittleEndian();
|
|
|
|
|
obj.CodeEncKey = data.ReadBytes(16);
|
|
|
|
|
|
2026-03-13 10:51:31 -04:00
|
|
|
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();
|
2026-03-13 12:10:45 -04:00
|
|
|
obj.LibraryFlags = (LibraryFlags)data.ReadUInt16LittleEndian();
|
2026-03-13 10:51:31 -04:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|