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
- VerifyXiso
- Extract / List / Tree / Rewrite
- Resume interrupted unpacks
- DecodeXiso (main entry)
- DecodeXisoAsync
- GetVolumeInfo
- ReadFileBytes
- ListDirectory / GetEntryInfo
- GetSectorLayout
- CopyOut
- CopyIn
- ComputeFileHash / ComputeDirectoryHashes
- AuditXiso
- Repair
- Salvage
- Split / Join
- FILETIME
- Disc offset probing
| 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 |
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).
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.
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 openUnpackImage 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 diskpublic 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).
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.
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.
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.
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 |
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 openOne-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.
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 #19internalPathuses forward slashes, e.g."/","/subdir","/subdir/file.bin".ListDirectoryreturnsEntryInforecords:Name,IsDirectory,StartSector,FileSize,Attributes,LeftChildOffset,RightChildOffset.ListDirectoryFlatreturns just the entry names — the library behind the CLI's--lsflag.- Throws
InvalidDataExceptionwhen a path does not exist. GetEntryInforeturnsnullfor a missing path.
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 withSectorCount0.UsedRanges: merged allocated ranges (volume header + tables + file data).FreeRanges: unallocated gaps; used + free tile[0, TotalSectors)exactly.- Throws
XisoFormatExceptionon invalid images and on corrupt tables (out-of-image extents, cycles) withinvalid TOC entrymessages.
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.
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 (
0xFFtail 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
SectorAllocatorseeded fromGetSectorLayout, and the parent table is re-serialized withDirectoryEntryTableWriter. 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).
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 (
nullif not found) or every file under a directory (recursive). - Any
HashAlgorithmNameworks — the CLI exposes--md5and--sha256.
public static XexInfo? GetXexInfo(string isoPath, string internalPath)
public static XexInfo? GetXexInfo(Stream imageStream, string imageName, string internalPath) // TODO #19Parses 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.
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.
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.
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.
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.
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>.
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.
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.
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.
// 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.
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.
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
requireOptimizedTagis true; otherwise reported viaIsOptimized) - 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).
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
CreateXisowrites 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).
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
CreateXisopipeline 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 inSkipped: 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;
OutputIssuescarries the verdict (Successwhen 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).
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.
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).
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