Files

312 lines
13 KiB
C#
Raw Permalink Normal View History

2026-03-13 10:51:31 -04:00
using System.IO;
using SabreTools.Data.Models.XboxExecutable;
using SabreTools.IO.Extensions;
2026-03-24 19:17:25 -04:00
using SabreTools.Matching;
using SabreTools.Numerics.Extensions;
2026-03-13 10:51:31 -04:00
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
// Get the relative TLS address
long tlsOffset = initialOffset + header.TLSAddress;
// Get the absolute TLS address
for (int i = 0; i < header.NumberOfSections; i++)
{
var sectionStart = xbe.SectionHeaders[i].VirtualAddress;
var sectionEnd = xbe.SectionHeaders[i].VirtualAddress + xbe.SectionHeaders[i].VirtualSize;
if (tlsOffset >= sectionStart && tlsOffset < sectionEnd)
{
// TLS offset is relative to .rdata section start
tlsOffset = xbe.SectionHeaders[i].RawAddress + (tlsOffset - sectionStart);
break;
}
// Fallback if relative address not in any section
if (i == (header.NumberOfSections - 1))
tlsOffset -= header.BaseAddress;
}
2026-03-13 11:05:31 -04:00
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;
}
}
}