Skip to content

Latest commit

 

History

History
865 lines (719 loc) · 43.8 KB

api-xisoreader.md

File metadata and controls

865 lines (719 loc) · 43.8 KB

XisoReader API

XisoReader (XISOSharp namespace) is a static class providing all read-side operations: verification, extraction, listing, tree traversal, rewriting, volume info, directory listing, auditing, hashing, copy-out, and copy-in.

Method index

Method Description
VerifyXiso Low-level header verification; returns root directory metadata + disc offset
Extract Extract an image to a directory
UnpackImage Unpack the whole image; auto-detects the optimized layout and ISO-named default output
List List top-level entries
Tree Recursive listing with sizes and totals
Rewrite Rewrite an image into the optimized layout
DecodeXiso Main entry point (all modes)
DecodeXisoAsync Async wrapper of DecodeXiso
OpenImageStream Open an image for reading (CISO/CCI/split-aware via XisoImageSource); caller owns the stream
IsOptimizedImage Probe the optimized tag (path or stream overload)
GetVolumeInfo Volume descriptor metadata without throwing
ListDirectory Metadata of entries in a directory
ListDirectoryFlat Entry names of a directory (non-recursive convenience)
GetEntryInfo Metadata of one entry by path
ReadFileBytes In-place read of a file's bytes at an offset (no extraction; bounded to the file size)
GetSectorLayout Explicit sector layout: files/tables → sector ranges + used/free ranges
CopyOut Copy one file or directory out of an image
CopyIn Copy one host file into an image (replace or add, in place)
ComputeFileHash Hash one file (MD5/SHA-256/… via HashAlgorithmName)
ComputeDirectoryHashes Hash every file under a path
GetXexInfo Parse the Xbox 360 XEX2 header of a .xex file
GetXexTitleInfo Read XDBF/XSRC title metadata from an Xbox 360 .xex
GetTitleInfo Resolve default.xbe/default.xex/default_mp.xbe title metadata
GetXbeInfo Parse the original-Xbox XBEH header + certificate of a .xbe file
XbeAssets.GetImage Extract an XBE logo, $$XTIMAGE title image or $$XSIMAGE save image as PNG/DDS (separate XbeAssets class)
GodHeader.TryReadTitle Read GOD/SVOD XContent title metadata (separate GodHeader class)
StfsHeader.TryReadTitle Read single-file XBLA/XBLIG STFS XContent title metadata (separate StfsHeader class)
AuditXiso Deep integrity audit
Repair Fix safely-patchable issues in place (.old backup, dry-run)
Salvage Rebuild a readable image from a corrupt one (repack + re-audit)
SplitXiso / SplitXisoHalves / JoinSplitXiso Split a plain ISO into sector-aligned parts and reassemble them
GetFileTime / SetFileTime Read/write the FILETIME volume-descriptor field

VerifyXiso

public static (uint rootDirSector, uint rootDirSize, long discLseek) VerifyXiso(
    Stream fs, string isoName, int? skipSectors = null)

Verifies the header magic at all known disc offsets (or at the skipSectors offset when given) and returns the root directory table location and the detected disc offset. Rebuilt sector-0 images (descriptor at absolute offset 0) are detected as well and report discLseek = 0.

Parameter Meaning
fs Open, readable stream positioned anywhere (file, memory, block-device backed)
isoName Display name used in error messages
skipSectors Treat the XISO as starting N sectors into the file (Redump). Negative → ArgumentOutOfRangeException

Throws: XisoFormatException (invalid/corrupt), IOException (file too short), XisoEmptyException (no files).

Extract / Unpack / List / Tree / Rewrite

public static int Extract(
    string xisoPath, string? outputPath, bool llCompat,
    CancellationToken cancellationToken = default, int? skipSectors = null,
    UnpackOptions? options = null, IProgress<ProgressInfo>? progress = null)

public static int UnpackImage(
    string isoPath, string? outputPath = null,
    CancellationToken cancellationToken = default, int? skipSectors = null,
    UnpackOptions? options = null, IProgress<ProgressInfo>? progress = null)

public static int List(
    string xisoPath, bool llCompat,
    CancellationToken cancellationToken = default, int? skipSectors = null)

public static int Tree(
    string xisoPath, bool llCompat,
    CancellationToken cancellationToken = default, int? skipSectors = null)

public static int Rewrite(
    string xisoPath, string? outputPath, out string? outIsoPath,
    CancellationToken cancellationToken = default,
    string? outputName = null, int? skipSectors = null, int? prependSectors = null,
    IProgress<ProgressInfo>? progress = null, bool preserveAttributes = false)
Parameter Meaning
xisoPath Path of the ISO (for Rewrite, the source; the .old rename is internal)
outputPath null → extract into an ISO-named subdirectory of the current directory; otherwise the target directory
llCompat true = legacy linked-list right-offset calculation; false = optimized layout
outputName Rewrite only: custom output filename, used verbatim (rooted paths kept as-is; default: original name with .iso)
skipSectors Read offset (Redump video partition), in 2048-byte sectors
prependSectors Rewrite only: reserve zero-filled sectors before the filesystem
preserveAttributes Rewrite only: re-encode the source dirent attribute bits (RO/HID/SYS/NOR). Default false — extract-xiso byte parity (entries re-encoded with DIR/ARC defaults, dir=0x10/file=0x20)
progress Rewrite: structured progress channel (IProgress<ProgressInfo>) — see XisoWriter API. Extract: a FileAdded event per file actually written (skipped/excluded files are silent), plus a per-chunk FileProgress event (Size = bytes copied so far, Count = total file bytes) while each file copies
options Extract/unpack only: resume and parallelism options — see Resume interrupted unpacks

UnpackImage is the convenience form of Extract: it probes the optimized-tag marker to pick llCompat automatically and defaults the output directory to the ISO name (minus .iso), so callers never need to know the image layout.

All return 0 on success.

Stream-based overloads

Extract, UnpackImage, List, Tree, DecodeXiso, and DecodeXisoAsync each have a Stream overload taking (Stream imageStream, string imageName, ...) in place of the input path — for memory, network, or embedded-resource images. The random-access primitives have them too (TODO #19): GetVolumeInfo, ListDirectory, GetEntryInfo, CopyOut, ComputeFileHash, and GetXexInfo — these are what XisoExplorer uses to support .cso, .cci and split inputs. The stream must be readable and seekable (ArgumentException otherwise) and is left open; imageName (typically the file name) drives output naming and messages. IsOptimizedImage has a matching Stream overload (position restored), and OpenImageStream - the XisoImageSource opener the path APIs use internally — is public too. Rewrite stays path-only (it is file-identity based, the .old dance), so the stream DecodeXiso refuses ExtractMode.Rewrite with ArgumentException.

using var image = new MemoryStream(File.ReadAllBytes("game.iso"));
int rc = XisoReader.UnpackImage(image, "game.iso", "./out"); // stream stays open

Filesystem-based overloads (TODO #7, xdvdfs #166)

UnpackImage also takes any IFilesystem destination in place of outputPath:

public static int UnpackImage(
    string isoPath, IFilesystem filesystem,
    CancellationToken cancellationToken = default, int? skipSectors = null,
    UnpackOptions? options = null, IProgress<ProgressInfo>? progress = null)

public static int UnpackImage(
    Stream imageStream, string imageName, IFilesystem filesystem,
    CancellationToken cancellationToken = default, int? skipSectors = null,
    UnpackOptions? options = null, IProgress<ProgressInfo>? progress = null)

Files land at the filesystem root (no ISO-named subdirectory, no process working-directory changes), with the same per-file hardening as the disk path: skip-existing resume probes the destination filesystem, truncated data and failed writes throw the same ExtractFileException errors, and zero-byte files are created. See IFilesystem for the LocalFilesystem / MemoryFilesystem implementations and the path rules.

var memory = new MemoryFilesystem();
XisoReader.UnpackImage("game.iso", memory);
byte[] defaultXbe = memory.ReadAllBytes("default.xbe"); // never touched the disk

Resume interrupted unpacks

public sealed class UnpackOptions
{
    public const int MaxParallelism = 64;
    public bool SkipExisting { get; set; }
    public bool ContinueOnError { get; set; }
    public ExtractNamePolicy NamePolicy { get; set; } // Reject (default) / Sanitize / Skip
    public int MaxDegreeOfParallelism { get; set; } // 1..64, default 1
    public bool ShouldSkip(string destPath, long fileSize);
    public bool ShouldSkip(string destPath, long fileSize, IFilesystem filesystem);
}

When SkipExisting is set, Extract / UnpackImage / CopyOut leave any destination file already holding the same byte count untouched (logged as skip: <path>) instead of overwriting it — re-running an interrupted unpack completes the missing files instead of redoing the finished ones. XISO stores no per-file timestamps, so size is the identity signal: a same-size file is assumed to be a complete earlier write, while a missing or short file (a torn write) is rewritten. Cancellation is honored per entry (OperationCanceledException), and extraction never changes the process working directory, even when the run aborts.

When ContinueOnError is set, a per-file failure (uncreatable destination, truncated data, failed write) is recorded and skipped instead of aborting; an uncreatable directory skips its subtree. The run still throws at the end — an ExtractErrorException with code ErrExtractFailed listing every failure (the xdvdfs Failed to unpack image wrapper) — so the failure is visible to callers. Corrupt directory tables are contained by the traversal hardening: under ContinueOnError the bad subtree is skipped and recorded; otherwise the named TOC error aborts.

When MaxDegreeOfParallelism is greater than 1, Extract / UnpackImage walk the directory tree and create directories on one thread, queue the file entries, then copy independent files with that many workers. Output is byte-identical to a sequential run, FileAdded progress and completion log lines stay in traversal order, and SkipExisting / ContinueOnError keep their semantics. Plain ISO sources are read positionally; CISO/CCI/GOD/split and in-memory sources serialize reads behind a lock. Destination filesystem calls are serialized, so non-thread-safe stores such as MemoryFilesystem remain correct. CopyOut ignores the option (it copies one file).

NamePolicy controls entry names that are unsafe on the destination host. Reject (the default) preserves the historical fail-fast behavior; Sanitize replaces separators, drive-relative colons, trailing dots/spaces, DOS device names and other Windows-invalid punctuation with _, appends ~N when the result collides in the same directory, and logs each rename; Skip omits the entry and, for a directory, its whole subtree. The policy applies to the ISO-family walkers, StfsPackage.ExtractTo and XisoZarchive.ExtractZar; an explicit CopyOut destination is caller-chosen and is unaffected.

XisoReader.UnpackImage("damaged.iso", "out", options: new UnpackOptions
{
    NamePolicy = ExtractNamePolicy.Sanitize,
});

The directory walk itself is iterative within a table: an explicit in-order stack bounds intra-table AVL depth by Constants.MaxTocEntriesPerTable and Constants.MaxTocStackFrames, while MaxTocDepth now applies only to subdirectory nesting. A pathological left-skewed table therefore lists, trees, extracts and rewrites without exhausting the process stack.

The same treatment covers every other table walker: AuditXiso, the XisoRanges sector/entry maps, XisoRepairer, XisoSalvager and XisoZarchive.CreateZar all use an explicit in-order stack inside a table and keep MaxTocDepth for real subdirectory nesting only. A 4,096-entry left spine audits, maps, repairs, salvages and packs to ZAR just like a balanced table; cycles and out-of-table pointers still fail with the named TOC error (or are reported/dropped by the audit, repair and salvage gates).

Extraction errors

public sealed class ExtractFileException : ExtractErrorException
{
    public string InternalPath { get; }  // entry inside the image
    public string DestPath { get; }      // destination being written
    public uint StartSector { get; }     // partition-relative data sector
    public long FileSize { get; }        // reported size in bytes
    public long BytesRead { get; }       // bytes read before a short read (-1 otherwise)
}

Every per-file extraction failure carries this context (TODO #9, xdvdfs #187): Message reads Failed to extract "<entry>" (sector N, M bytes) -> "<dest>": <reason>, with the OS error as InnerException. New ExtractError codes: ErrFileTruncated (-5004, image data ends early), ErrFileWrite (-5005, destination create/write failed), ErrExtractFailed (-5006, end-of-run summary under ContinueOnError). Extracted files are integrity-checked: out-of-image data ranges are refused before the destination is created, and the on-disk length is re-statted after the copy.

DecodeXiso (main entry)

public static int DecodeXiso(
    string xisoPath,
    string? outputPath,
    ExtractMode mode,
    out string? outIsoPath,
    bool llCompat,
    CancellationToken cancellationToken = default,
    string? outputName = null,
    int? skipSectors = null,
    int? prependSectors = null,
    IProgress<ProgressInfo>? progress = null,
    UnpackOptions? unpackOptions = null)

The generic entry point used by the wrappers above. mode is one of ExtractMode: Extract, List, Tree, Rewrite, GenerateAvl (internal use), or Verify. The Stream overload is covered under Stream-based overloads.

DecodeXisoAsync

public static async Task<(int Result, string? OutIsoPath)> DecodeXisoAsync(
    string xisoPath, string? outputPath, ExtractMode mode,
    bool llCompat = false, CancellationToken cancellationToken = default,
    string? outputName = null, int? skipSectors = null, int? prependSectors = null,
    IProgress<ProgressInfo>? progress = null, UnpackOptions? unpackOptions = null)

Runs DecodeXiso on the thread pool. Returns the result code and, in rewrite mode, the output path.

GetVolumeInfo

public static VolumeInfo GetVolumeInfo(string isoPath)
public static VolumeInfo GetVolumeInfo(string isoPath, int? skipSectors) // explicit Redump offset
public static VolumeInfo GetVolumeInfo(Stream imageStream, string imageName = "memory") // TODO #19
public static VolumeInfo GetVolumeInfo(Stream imageStream, string imageName, int? skipSectors)

Reads the volume descriptor without throwing on validation errors. Returns a VolumeInfo record:

Member Type Meaning
IsValid bool Header magic found
RootDirSector uint Root directory table sector
RootDirSize uint Root directory table size (bytes)
DiscLseek long Detected disc offset
DiscFormat string Friendly disc-layout identity from DiscLseek: RAW, GLOBAL (XGD2), XGD3, XGD2 Hybrid, XGD1, or Unknown (invalid volumes always report Unknown)
FileLength long File size
TotalSectors long Total sectors
CreationTime DateTimeOffset? Descriptor FILETIME as UTC time; null when invalid (raw 0 = 1601-01-01). Agrees with GetFileTime
FileTimeRaw ulong Raw FILETIME field as stored
DescriptorSector int Partition-relative descriptor sector: 32 for every standard layout (the partition shift is DiscLseek), 0 for rebuilt sector-0 images; -1 when invalid

ReadFileBytes

public static int ReadFileBytes(string isoPath, string internalPath, Span<byte> buffer, long fileOffset)
public static int ReadFileBytes(Stream imageStream, string imageName, string internalPath,
    Span<byte> buffer, long fileOffset) // stream left open

One-shot in-place file read with no extraction to disk (VFS/Dokan ReadFile shape). The read is clamped to the entry's own FileSize, never the image length, so a corrupt TOC cannot leak the next file's sectors; fileOffset at/past the file end returns 0. Plain .iso, .cso, .cci and split .1.cso/.1.cci/.1.iso inputs all work. Throws InvalidDataException for a missing path or a directory target, and ArgumentOutOfRangeException for a negative offset.

byte[] buf = new byte[512];
int n = XisoReader.ReadFileBytes("game.iso", "/default.xbe", buf, fileOffset: 0x100);

For repeated reads, see XisoExplorer.OpenReadStream in Utilities — it returns a bounded, seekable Stream over the file's extent.

ListDirectory / ListDirectoryFlat / GetEntryInfo

public static IReadOnlyList<EntryInfo> ListDirectory(string isoPath, string internalPath = "/")
public static IReadOnlyList<EntryInfo> ListDirectory(string isoPath, string internalPath, int? skipSectors)
public static IReadOnlyList<EntryInfo> ListDirectory(Stream imageStream, string imageName, string internalPath = "/") // TODO #19
public static IReadOnlyList<EntryInfo> ListDirectory(Stream imageStream, string imageName, string internalPath, int? skipSectors)
public static IReadOnlyList<string> ListDirectoryFlat(string isoPath, string internalPath = "/")
public static EntryInfo? GetEntryInfo(string isoPath, string internalPath)
public static EntryInfo? GetEntryInfo(Stream imageStream, string imageName, string internalPath) // TODO #19
  • internalPath uses forward slashes, e.g. "/", "/subdir", "/subdir/file.bin".
  • ListDirectory returns EntryInfo records: Name, IsDirectory, StartSector, FileSize, Attributes, LeftChildOffset, RightChildOffset.
  • ListDirectoryFlat returns just the entry names — the library behind the CLI's --ls flag.
  • Throws InvalidDataException when a path does not exist.
  • GetEntryInfo returns null for a missing path.

GetSectorLayout

public static SectorLayout GetSectorLayout(string isoPath)
public static SectorLayout GetSectorLayout(string isoPath, int? skipSectors)

Explicit sector layout (xdvdfs #49, TODO #4) — the library behind low-level disk analysis and the allocator input for in-place patching (TODO #5):

public record FileSectorExtent(string Path, bool IsDirectory, uint StartSector, uint SectorCount, uint FileSize);
public record SectorRange(uint StartSector, uint SectorCount);
public record SectorLayout(VolumeInfo Volume, IReadOnlyList<FileSectorExtent> Entries,
    IReadOnlyList<SectorRange> UsedRanges, IReadOnlyList<SectorRange> FreeRanges, long TotalSectors);
  • All sectors are partition-relative (same numbering as EntryInfo.StartSector).
  • Entries: one extent per file plus one per directory table (IsDirectory, FileSize = table byte size), sorted by start sector. Empty files appear with SectorCount 0.
  • UsedRanges: merged allocated ranges (volume header + tables + file data).
  • FreeRanges: unallocated gaps; used + free tile [0, TotalSectors) exactly.
  • Throws XisoFormatException on invalid images and on corrupt tables (out-of-image extents, cycles) with invalid TOC entry messages.

CopyOut

public static void CopyOut(string isoPath, string internalPath, string destPath,
    UnpackOptions? options = null, CancellationToken cancellationToken = default,
    IProgress<ProgressInfo>? progress = null)
public static void CopyOut(Stream imageStream, string imageName, string internalPath, string destPath, // TODO #19
    UnpackOptions? options = null, CancellationToken cancellationToken = default,
    IProgress<ProgressInfo>? progress = null)

Copies one file — or an entire directory, recursively — out of the image to destPath without a full extraction. With SkipExisting, up-to-date destinations are skipped per Resume interrupted unpacks. Each copied file reports per-chunk FileProgress events on progress.

CopyIn

public static void CopyIn(string isoPath, string hostFile, string internalPath,
    bool createBackup = true)
// same behavior via XisoPatcher.CopyIntoImage(isoPath, hostFile, internalPath, createBackup)

Copies one host file into the image, modifying it in place — the reverse of CopyOut (xdvdfs #165, TODO #5). Replaces internalPath when it exists, or adds it as a new file when only its parent directory exists (paths work like the reader APIs: /-separated, case-insensitive). Copying directories in is not supported: a host directory fails fast with InvalidDataException (not the FileNotFoundException a missing host file raises), before anything is written.

Two cases, chosen automatically (image size never changes):

  • Fits the existing allocation — data is overwritten in place (0xFF tail fill) and only the parent table's 8-byte entry record (start sector + file size) is updated. Every other byte is untouched.
  • Larger, or a new file — a free run is allocated via SectorAllocator seeded from GetSectorLayout, and the parent table is re-serialized with DirectoryEntryTableWriter. If the grown table no longer fits, it moves and only the grandparent link (or the volume-header root fields, when the root table moves) is updated — the cascade is exactly one table deep.

A <iso>.old backup of the pre-patch image is written first (replacing any previous backup) unless createBackup is false. Copied-in .xbe files get the same media-enable patch as packed ones; the volume timestamp is preserved. InvalidDataException covers malformed paths, missing parents, directory targets, and out-of-space (need N sectors but only M free); corrupt images fail with XisoFormatException before anything is written. CLI: --copy-in <iso> <host> <path> (--no-backup skips the backup).

ComputeFileHash / ComputeDirectoryHashes

public static byte[]? ComputeFileHash(
    string isoPath, string internalPath, HashAlgorithmName algorithm)
public static byte[]? ComputeFileHash( // TODO #19
    Stream imageStream, string imageName, string internalPath, HashAlgorithmName algorithm)

public static IReadOnlyList<(string Path, byte[] Hash)> ComputeDirectoryHashes(
    string isoPath, string internalPath, HashAlgorithmName algorithm)
  • Hash one file (null if not found) or every file under a directory (recursive).
  • Any HashAlgorithmName works — the CLI exposes --md5 and --sha256.

GetXexInfo

public static XexInfo? GetXexInfo(string isoPath, string internalPath)
public static XexInfo? GetXexInfo(Stream imageStream, string imageName, string internalPath) // TODO #19

Parses the Xbox 360 executable (XEX2) header of a .xex file inside the image. All fields are read big-endian per the XEX2 specification (see xex2_info.h in xenia):

XexInfo member Meaning
ModuleFlags Title / DLL / user-mode etc. bit flags
HeaderSize XEX header region size (typically 0x4000)
EntryPoint Entry point RVA (optional header)
ImageBaseAddress Image base address (optional header)
ImageSize / LoadAddress Security info
Region NTSC-U / NTSC-J / PAL bit flags
AllowedMediaTypes Media type bitmask (hard disk, DVD-9, …)
MediaId / TitleId / Version Execution info
Platform / DiscNumber / DiscCount Execution info
EncryptionType / CompressionType File format info

Returns null when the path does not exist, points to a directory, or the file is not an XEX2 executable. Validated against retail Xbox 360 Redump images (Perfect Dark Zero, Payday 2). The CLI exposes this as --xex-info.

GetXexTitleInfo

public static XexTitleInfo? GetXexTitleInfo(string isoPath, string internalPath)
public static XexTitleInfo? GetXexTitleInfo(Stream imageStream, string imageName, string internalPath)

Reads the plaintext XDBF resource from the XEX header-section table and returns the title name plus publisher, developer, genre, description and optional thumbnail. No decryption or image decompression is involved: XDBF lives in a header section named after the title ID, and its XSTR/XSRC entries are read directly. Returns null when the path is not an XEX2 executable; a valid XEX without a readable XDBF returns an XexTitleInfo with an empty title name. TitleInfo (from GetTitleInfo) sets Kind = TitleKind.Xex and exposes this record through TitleInfo.Xex.

GetTitleInfo

public static TitleInfo? GetTitleInfo(string isoPath)
public static TitleInfo? GetTitleInfo(Stream imageStream, string imageName)

Resolves the root executable in order: default.xbe (original Xbox), then default.xex (Xbox 360), then default_mp.xbe. Returns title name, title ID, region, disc and version. TitleInfo.Kind identifies the source (Xbe or Xex); TitleInfo.Xbe is set for XBE titles and TitleInfo.Xex (with the full XexTitleInfo) for XEX titles. Returns null when no supported root executable is present. Plain ISO, split ISO, CISO and CCI inputs are accepted.

GetXbeInfo

public static XbeInfo? GetXbeInfo(string isoPath, string internalPath)
public static XbeInfo? GetXbeInfo(Stream imageStream, string imageName, string internalPath)

Parses the original-Xbox executable (XBEH) header + certificate of a .xbe file inside the image — the OG-Xbox counterpart to GetXexInfo, which no reference tool offers. All fields are read little-endian per the XBE specification (see xbe.h in Cxbx-Reloaded):

XbeInfo member Meaning
BaseAddress Image base address (retail: 0x00010000)
EntryPoint Entry point address
SectionCount / InitFlags Section count, init flags
CertSize Certificate size (retail: 464, 0x1D0 — anything else is rejected)
CertTimeDate Certificate timestamp (raw DWORD)
TitleId Title ID
TitleName Title name (UTF-16, up to 40 chars)
AlternateTitleIds 16 alternate title IDs (usually zero)
AllowedMedia Hard disk / DVD-CD / DVD-5-RO / DVD-9-RO / DVD-5-RW / DVD-9-RW / dongle / media board
GameRegion North America / Japan / rest-of-world bitmask (0x07 = worldwide)
GameRatings Ratings bitmask (raw DWORD)
DiskNumber / Version Multi-disc number, game version (raw DWORD)

The certificate address is a load pointer (file offset = address − base); out-of-range, below-base, and truncated certificates return null instead of reading out of bounds. Returns null when the path does not exist, points to a directory, or the file is not an XBEH executable. The CLI exposes this as --xbe-info; XisoExplorer has a matching GetXbeInfo method.

XBE image assets (XbeAssets)

public static XbeImage? GetImage(string imagePath, string internalPath, XbeImageKind kind)
public static XbeImage? GetImage(Stream imageStream, string imageName, string internalPath,
    XbeImageKind kind)
public static XbeImage? GetLogo(string imagePath, string internalPath)
public static XbeImage? GetTitleImage(string imagePath, string internalPath)
public static XbeImage? GetSaveImage(string imagePath, string internalPath)

Extracts the Microsoft logo bitmap, the $$XTIMAGE title image or the $$XSIMAGE save image from an XBE. The section walk is bounded by the XBE header/section table. XbeImage carries the raw payload plus a BCL-generated PNG and a DDS (passthrough when the payload already is a DDS, otherwise a standard header is written around the decoded texture). Supported payloads are DDS/DXT1/DXT3/DXT5/BGR24/BGRA32, XPR0/DXT1/DXT3/DXT5, the 54-byte DDS-like title-image header followed by BGR24/DXT data, and the 100×17 logo bitmap (BGRA32, grayscale or RLE). Unsupported payloads are returned with Png/Dds set to null rather than throwing. XbeInfo.HasLogoBitmap, HasTitleImage and HasSaveImage report presence without decoding. The CLI exposes this as --xbe-image <image> <path> --type logo|title|save -o out.png|out.dds.

GOD header title metadata (GodHeader)

public static bool IsGodContainer(string path)
public static bool TryReadTitle(string path, out GodTitleInfo? info)
public static GodTitleInfo? ReadTitle(string path)
public static bool TryReadVolume(string path, out GodVolumeInfo? info)
public static bool TryReadVolume(string path, FileShare share, out GodVolumeInfo? info)

Reads the plaintext XContent header of a Games-on-Demand (GOD/SVOD) container without mounting it. path may be the header file or its <header>.data directory. The parser validates the CON/LIVE/PIRS signature and the GOD/NXE content type, then reads the title ID (0x360), disc number/count (0x366/0x367) and UTF-16BE title name (0x411, 0x80 bytes). Names are sanitised for FATX (non-ASCII/control characters removed, \/:*?"<>| replaced with _) and fall back to the 8-digit hex title ID when empty. GodTitleInfo carries HeaderPath, TitleName, TitleId, TitleIdHex, DiscNumber and DiscCount.

TryReadVolume additionally parses the SVOD device descriptor (start block, data-block count, enhanced-GDF flag) and the DataNNNN fragment list, returning a GodVolumeInfo with the logical image length and fragment paths. The GodBlockDevice (XISOSharp.BlockDevice) exposes that mapping as a read-only IBlockDevice, and XisoImageSource detects a GOD header or its .data folder and routes every read verb through the decompressed view. The CLI exposes this as title-info <header-or-.data-folder> [--json], decompress <header> [out.iso], and convert <header> <out.iso|out.cci|out.cso>.

Single-file STFS package metadata (StfsHeader)

public static bool IsStfsPackage(string path)
public static bool TryReadTitle(string path, out StfsTitleInfo? info)
public static StfsTitleInfo? ReadTitle(string path)

Reads the same XContent header from a single-file Xbox 360 STFS package: XBLA (content type 0x000D0000) or XBLIG (0x00000002; Community Game 0x02000000 is accepted as XBLIG). These packages have no <header>.data directory, so the package file itself is passed. The parser returns StfsTitleInfo with PackagePath, Kind (Xbla/Xblig), TitleName, TitleId, TitleIdHex, DiscNumber, DiscCount and the raw ContentType. GOD/NXE containers are handled by GodHeader.

Single-file STFS package contents (StfsPackage)

public static bool IsStfsPackage(string? path)
public static StfsPackage Open(string path, FileShare share = FileShare.Read)
public static bool TryOpen(string path, out StfsPackage? package)

public StfsPackageInfo Info { get; }
public IReadOnlyList<StfsEntry> Entries { get; }
public StfsEntry? Find(string? path)
public byte[] ReadAllBytes(StfsEntry entry)
public void CopyTo(StfsEntry entry, Stream destination)
public int ExtractTo(string outputDirectory, bool skipExisting = false,
    IProgress<ProgressInfo>? progress = null, CancellationToken ct = default)
public StfsValidationReport Validate(CancellationToken ct = default)

StfsPackage reads the STFS file table, follows contiguous and hash-chain block lists through the level-0/level-1/level-2 hash-table interleave (including writable-format primary/secondary tables) and exposes the package as a read-only tree. StfsPackageInfo reports the XContent title fields plus the volume layout; StfsEntry carries the path, size, block range, contiguity flag and FAT timestamps. XisoChecksum.ComputeStfsChecksum hashes the same sorted-path tree as ISO/ZAR, so equivalent trees compare equal. Validate performs a deep SHA-1 hash-tree check (per-block hashes, level tables, top hash and header hash) and returns a StfsValidationReport; package signatures are not verified. STFS writing and rebuilding remain out of scope.

Checksum (SHA3-256)

public static byte[] ComputeImageChecksum(string isoPath, CancellationToken ct = default);
public static string ComputeImageChecksumHex(string isoPath, CancellationToken ct = default);

In XISOSharp namespace via XisoChecksum (xdvdfs checksum compat): deterministic SHA3-256 over SortedDictionary Ordinal /DIR/FILE UTF-8 path bytes + streamed file data (BCL SHA3_256 when the OS supports it, else the pure-managed FIPS 202 fallback in Sha3.cs — identical digests, no extra dependency). NOT SHA256 of full image. CLI checksum prints hex tab path. See xdvdfs Compat.

Archival (Redump) — via XisoRedump / XisoOperations / XisoRanges / XboxPrng

// Video / update
bool XisoRedump.TryExtractVideo(string redumpPath, string? outputVideoPath, out string? outPath);
bool XisoRedump.TryExtractUpdate(string redumpPath, string? outputUpdatePath, string? outputVideoPath = null);

// Filler / seed / wipe / trim / petrify
byte[] XisoOperations.ExtractFiller(string isoPath, long isoOffset = 0);
uint? XisoOperations.ExtractSeed(string isoPath); // XGD1 only, brute-force
int XisoOperations.WipeFiller(string inputPath, string outputPath);
int XisoOperations.TrimXiso(string inputPath, string? outputPath);
int XisoSkeleton.Petrify(string inputPath, string? skeletonPath, string? hashPath);

// Rebuild lossless
int XisoRedump.RebuildRedump(string xisoPath, string videoPath, string? fillerOrSeedPath, string? updatePath, string outputRedumpPath, int[]? securitySectors = null, IProgress<ProgressInfo>? progress = null, CancellationToken ct = default);

// Ranges
(IReadOnlyList<(long Start,long End)> SysRanges, IReadOnlyList<(long Start,long End)> FileRanges) XisoRanges.GetXisoRanges(string isoPath, long isoOffset = 0);
IReadOnlyList<(long Start,long End)> XisoRanges.MergeRanges(IEnumerable<(long,long)> a, IEnumerable<(long,long)> b);
IReadOnlyList<(string Path, long Offset, uint Size)> XisoRanges.CollectFileEntries(FileStream fs, long isoOffset);

All ported from LibXGD/XGD.cs:11 tables (XgdTables.cs) + XDVDFS.cs (GetValidSectors/ProcessXISO). See Archival and xdvdfs Compat.

Block-Device overloads

public static (uint rootDirSector, uint rootDirSize, long discLseek) VerifyXiso(IBlockDevice dev, string isoName, int? skipSectors = null);
public static AuditResult AuditXiso(IBlockDevice dev, string isoName = "memory");
public static AuditResult AuditXiso(IBlockDevice dev, string isoName, bool requireOptimizedTag);
public static IReadOnlyList<EntryInfo> ListDirectory(IBlockDevice dev, string internalPath = "/");

IBlockDevice stack under XISOSharp/BlockDevice/ (FileBlockDevice/MemoryBlockDevice/OffsetBlockDevice/StreamBlockDevice/CisoBlockDevice/CciBlockDevice). Mirror xdvdfs-core/src/blockdev.rs - enables in-memory golden fixtures + CISO/CCI random-access via single-sector caches without no_std target. All FileStream overloads delegate to these.

AuditXiso

public static AuditResult AuditXiso(string isoPath)
public static AuditResult AuditXiso(string isoPath, bool requireOptimizedTag)

Deep integrity audit — the library behind the CLI's -V flag:

  • header magic at all known offsets
  • optimized-tag presence at offset 31337 (an issue only when requireOptimizedTag is true; otherwise reported via IsOptimized)
  • full directory tree walk with cycle detection
  • sector bounds for every entry (file and directory)
  • reserved attribute bits (0x08, 0x40)
  • filename validity

requireOptimizedTag defaults to true (the 1-arg overload), preserving the original behavior. Pass false to audit raw (unconverted) images: a missing optimized tag no longer fails the audit, so structurally valid Redump-style dumps pass. Both overloads accept plain .iso and .cso images.

Returns AuditResult (IsValid, FilesChecked, DirsChecked, Issues, IsOptimized).

Repair

public static RepairResult Repair(string isoPath, bool createBackup = true, bool dryRun = false)
// same behavior via XisoRepairer.RepairInPlace(isoPath, createBackup, dryRun)

Fixes the audit's safely-patchable (class-C) issues in place — the only repair verb any of the reference tools offers (TODO #26, Phase 1):

  • Reserved attribute bits — the entry's attribute byte is rewritten with Constants.MaskAttributes (& 0xB7); readers already mask, so this is pure normalization.
  • Missing optimized tag — the exact tag bytes CreateXiso writes are stored at offset 31337 (only when the file is long enough to hold them).
  • Separators in filenames — length-preserving _ substitution (the name length byte is untouched, so the table layout cannot shift). When two names would collide, the entry is left alone and reported.

Every patch is length-preserving: the image size never changes and every other byte is untouched. Pointers from records with reserved bits are not trusted (the collect walk skips descending into them until their fix is decided, then converges over bounded passes), so a corrupt directory entry can never send repairs wandering into file data. Each pass ends with a re-audit — repairing a clean image is a no-op success.

Truncation, structural (depth/chain/cycle), and refused (bad magic/root) classes are reported, never patched (Phase 2 salvage rebuild). CISO/CCI containers and split parts are refused with InvalidDataException (decompress/reassemble first); invalid images fail with XisoFormatException before anything is written. A <iso>.old backup is written first (replacing any previous backup) unless createBackup is false. Returns RepairResult (Fixed, Remaining, BackupPath, DryRun; Success when the image now passes). CLI: --repair <file> (--dry-run previews; --no-backup skips the backup).

Salvage

public static SalvageResult Salvage(string sourcePath, string? outputPath = null)
// same behavior via XisoSalvager.Salvage(sourcePath, outputPath)

Rebuilds a readable image from a corrupt one — the answer to the audit's truncation and structural classes, which no in-place patch can fix (TODO #26, Phase 2):

  • A bounded walk reusing the auditor's hardening limits (subdirectory depth cap, per-table entry cap, cycle tracking) copies every entry reachable without tripping a truncation or structural gate into a staging directory, then repacks it through the CreateXiso pipeline into a fresh plain .iso.
  • Intra-table left/right skew is walked with an explicit stack, so only real subdirectory nesting counts toward the depth cap.
  • Class-C quirks do not block salvage: reserved bits are masked and path separators in names are sanitized to _. Anything uncarriable is reported in Skipped: unreadable entries, orphaned right-link tails, separator-collision (or host-unusable) names, and subtrees past the depth gate (dropped whole — carrying them would rebuild an image that still fails the audit).
  • The source is only ever read, never written: no backup is needed and CISO/CCI input is allowed (reads go through the decompressed view). Split-ISO parts read as the truncated images they are; split CISO/CCI sets decompress through the container view. A missing tree root (class R) throws XisoFormatException — there is nothing to salvage with.
  • The rebuilt image is re-audited; OutputIssues carries the verdict (Success when it passes). A salvage that still fails is reported, not hidden.

outputPath defaults to the source's directory plus the source stem with a .salvaged.iso suffix (XisoSalvager.DefaultOutputPath); a numbered container part is named after the game (game.2.cci → game.salvaged.iso). An existing file is overwritten and a missing parent directory is created. Returns SalvageResult (Copied, Skipped, OutputPath, OutputIssues). CLI: --salvage <file> (--repair-out <path> overrides the output; existing files follow the -y/-n convention).

Split / Join

public static IReadOnlyList<string> SplitXiso(string isoPath, string outputBase, long partSizeBytes,
    CancellationToken cancellationToken = default, IProgress<ProgressInfo>? progress = null)
public static IReadOnlyList<string> SplitXisoHalves(string isoPath, string outputBase,
    CancellationToken cancellationToken = default, IProgress<ProgressInfo>? progress = null)
public static string JoinSplitXiso(string firstPartPath, string outputPath,
    CancellationToken cancellationToken = default, IProgress<ProgressInfo>? progress = null)
// facades over XisoSplitter.Split / SplitHalves / Join (TODO #17, xdvdfs #97)

Splits a plain .iso into FATX-friendly parts and reassembles them (the plain-ISO counterpart to CSO --ciso-split). Parts are a pure byte partition at sector-aligned cut points (<base>.1.iso, <base>.2.iso, …), so concatenation restores the image bit-for-bit. SplitXisoHalves cuts into two halves; both return the part paths in join order. Inputs are pre-validated, the joined output is post-validated, existing targets are refused, and partial outputs are removed on failure/cancel. CLI: split / join.

FILETIME

public static ulong GetFileTimeRaw(string isoPath, int? skipSectors = null)
public static DateTimeOffset GetFileTime(string isoPath, int? skipSectors = null)
public static ulong GetFileTimeRaw(IBlockDevice dev, string isoName = "memory", int? skipSectors = null)
public static DateTimeOffset GetFileTime(IBlockDevice dev, string isoName = "memory", int? skipSectors = null)
public static void SetFileTime(string isoPath, ulong fileTime, int? skipSectors = null)
public static void SetFileTime(string isoPath, DateTimeOffset dateTime, int? skipSectors = null)

Read and write the Windows FILETIME field of the volume descriptor (xdvdfs-compatible: 0 is 1601-01-01, the deterministic-image value). The ulong overloads work with raw values; the DateTimeOffset overloads convert (see FileTimeHelper). CLI: --filetime <image> (ISO-8601 + raw) and --set-filetime <image> <value> (ISO-8601, decimal raw, 0x hex, 'now', or '0'); --file-time <value> fixes the stamp at create time (-c, --pack, build-image).

Disc offset probing

VerifyXiso probes the header at these offsets, in order (via Constants.cs:127 + XgdTables.cs + IBlockDevice offset probes):

# Offset Layout
1 0x00000000 RAW
2 0x0FD90000 GLOBAL / XGD2
3 0x02080000 XGD3
4 0x89D80000 Hybrid (XGD2-Hybrid) — native since 2026-08-26 (Xgd2HybridLseekOffset)
5 0x18300000 XGD1

The rebuilt XISO layout is also accepted: the volume descriptor sits at the very start of the file (absolute offset 0) instead of partition sector 32. It reports DiscLseek = 0 and DescriptorSector = 0, and sector numbers stay partition-relative. Standard images keep offset 0 zeroed, so the candidates never collide.

When skipSectors is provided, probing is skipped and the header must be at skipSectors × 2048 + 0x10000.

See also: Library Overview · XisoWriter API · Utilities & Types · XISO Format