Skip to content

Repository files navigation

XISOSharp

CI .NET NuGet NuGet downloads GitHub release License Docs Wiki Last commit Commit activity GitHub stars GitHub forks GitHub issues GitHub contributors Release date Downloads Code size Platform Avalonia PRs welcome

A pure C# port of extract-xiso for Xbox ISO (XISO / XDVDFS) images — byte-identical output, no native dependencies, no P/Invoke, just .NET. Beyond the C baseline it merges the archival power of XboxKit and the modern packing of xdvdfs into one trimmable, AOT-compatible library + CLI.

Projects

Project Description
XISOSharp.Core Core library (NuGet: XISOSharp) — full read/write engine, net8.0/net9.0/net10.0, strong-named
XISOSharp.Cli CLI project (ships binary XISOSharp(.exe), AssemblyName XISOSharp.Cli) — extract-xiso-compatible flags + 35+ extra modes
XISOSharp.Tests xUnit suite (1818 tests on net10.0; 1757 on net8.0/net9.0) — snapshot test_fixture.iso + corruption resilience + in-place repair + salvage rebuild + XBE/XEX parsing + disc-format identity + MemoryBlockDevice + xdvdfs-cli split-CSO interop + reference extract-xiso legacy-layout interop + unpack-resume/output-guard/-d-edge-case/stream-API/filesystem-destination/explorer/split-join/robustness/remap-escape/symlink coverage + process-runner/update-checker/overwrite-prompt/bug-report/internals/GUI-command coverage
XISOSharp.Benchmarks BenchmarkDotNet (AVL, Boyer-Moore, sector math)
XISOSharpTester WPF GUI — batch regression vs extract-xiso.exe
XISOSharp.BattleTests CLI-vs-reference battle harness over a random sample of real ISOs (default 1 of H:\XBOXTest, seeded): extract-xiso.exe (reference build 202609111233; list/extract/rewrite), xdvdfs.exe 0.8.3 (checksum/md5/unpack/pack/cso round-trip), xboxkit.exe 0.7 (petrify/video/random/seed/trim/wipe/zar/rebuild) — outputs compared byte-for-byte, per-exe timings reported

Documentation

Full docs live in docs/ — served as a Docsify site with a left sidebar at docs/index.html (GitHub Pages) and mirrored to the GitHub Wiki with the same side menu (_sidebar.md for Pages; .github/workflows/wiki.yml + .github/sync-wiki.ps1 generate the wiki Home.md + _Sidebar.md):

Left menu: open docs/index.html locally or via Pages — docs/_sidebar.md is the sidebar. The GitHub Wiki uses the generated _Sidebar.md from the same file.

Install

NuGet (library)

dotnet add package XISOSharp
# or
Install-Package XISOSharp

Package targets net8.0, net9.0, net10.0, zero runtime dependencies (BCL only), strong-named, IsTrimmable+IsAotCompatible, snupkg via SourceLink.

CLI (tool)

git clone https://github.com/purelogiccode/XISOSharp.git
cd XISOSharp

# framework-dependent (needs .NET 10 SDK, pinned in global.json)
dotnet build XISOSharp.Cli -c Release
# bin: XISOSharp.Cli/bin/Release/net10.0/XISOSharp.Cli(.exe)

# self-contained trimmed single-file (no runtime needed) for all seven RIDs
./publish-cli.ps1
# binaries land in publish/<rid>/XISOSharp(.exe), ~14 MB each (renamed from the XISOSharp.Cli assembly name)

# or one RID manually (single-file + trimmed come from the csproj defaults)
dotnet publish XISOSharp.Cli -c Release -r linux-x64 --self-contained
# RIDs: win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64 (XboxKit parity, incl. 32-bit)

GUI (desktop app, Avalonia, dark-only)

# self-contained single-file (no runtime needed) for all six RIDs
./publish-gui.ps1
# binaries land in publish-gui/<rid>/XISOSharp.Gui(.exe), ~80 MB each

XISOSharp.Gui is a modern dark-theme front-end (left navigation rail, card layout, green accent) that drives the XISOSharp CLI as a child process via the shared core ProcessRunner + ToolLocator (override → sibling → PATH + -v probe; extract/create/rewrite/rebuild/compress/zar/decompress/validate/batch plus list/tree/info/unpack/copy-out/checksum, live log, cancel, overwrite -y/-n switch). The ZAR tab packs an ISO/XISO/Redump image into a .zar (ZArchive/zstd, loadable in Xenia canary) and offers overwrite/skip/auto-rename collision policies. Files and folders can be dragged onto the window: a single image queues Extract, a single .cso queues Decompress, multiple images queue Rewrite, and a folder queues Batch (when it contains *.iso) or Create. Input==output mistakes are refused before the CLI runs. It finds the CLI next to itself, on PATH, or via the Settings tab (persisted atomically to %AppData%/XISOSharp/gui-settings.json). Picker results marshal back via Dispatcher.UIThread, and commands gate on CanExecute while a run is active. Header actions open the PureLogicCode donation page (Donate), an About dialog with the version, description, credits, and project links (About), or close the app (Exit). Press F8 to save a PNG screenshot of the active window: the image goes to a Screenshot folder beside the app, falling back to %LocalAppData%/XISOSharp/Screenshot when the app folder is not writable. At startup the GUI probes the latest GitHub release and, when a newer version exists, offers to open the release page (XISO_NO_UPDATE_CHECK=1 opts out). Headless helpers: XISOSharp.Gui --probe-cli [path] and XISOSharp.Gui --self-test [cliPath].

Logging, bug reports, and telemetry

The CLI, GUI, and Tester all route every log write through Serilog (file + Debug sinks, plus a console sink restricted to Warning+). Logs roll daily under %LocalAppData%\<app>\logs\<app>-<date>.log (14-day retention; temp-dir fallback).

Every Warning or above event is forwarded to the PureLogicCode bug-report API with the required Environment / Error / Exception sections (date, app name/version, OS, architecture, bitness, platform version, processor count, base directory, temp path; error message; exception type/message/source/stack trace). Expected operational feedback — usage/validation refusals, missing-file probes, non-zero CLI exits, unreadable dropped folders — is tagged NoBugReport and stays in the local log only.

Each application launch also sends a fire-and-forget usage ping to the ApplicationStats API (POST /ApplicationStats/stats). Test hosts and --self-test runs never send.

Opt-outs (environment variables):

XISO_DISABLE_BUGREPORT=1   # no bug reports
XISO_DISABLE_STATS=1       # no launch stats
XISO_NO_UPDATE_CHECK=1     # no GitHub update check

Using the CLI

The CLI binary is XISOSharp(.exe). It is extract-xiso-compatible (-c/-x/-l/-r/-d/-D/-m/-q/-Q/-s/-X/-h/-v) plus XboxKit + xdvdfs verbs. Flags must precede positionals; -h/--help/-v exit 0. -v prints the XISOSharp v<version> for <platform> banner. Double-clicking the exe prints usage and waits for a keypress instead of closing. Every launch also checks the latest GitHub release (daily-cached) and prints an [UPDATE] notice with the release URL when a newer release_<version>_<rid>.zip is available — opt out with XISO_NO_UPDATE_CHECK=1. Full project readme (all args + examples): XISOSharp.Cli/ — deep reference: docs/cli.md.

Basics

# Extract (auto-detects RAW/GLOBAL/XGD2/XGD3/Hybrid/XGD1 + rebuilt sector-0)
XISOSharp -d ./out game.iso
XISOSharp --unpack game.iso              # auto-named ./game/
XISOSharp --unpack game.iso ./out

# List / tree / info / audit
XISOSharp -l game.iso
XISOSharp -t game.iso                     # recursive with sizes
XISOSharp -i game.iso /                  # volume + dir entries
XISOSharp --ls game.iso /media           # flat directory
XISOSharp -V game.iso game2.iso         # deep audit (header/cycles/bounds/0x48 + tag status)

# Create / pack / rewrite
XISOSharp -c ./game_files                # -> ./game_files.iso
XISOSharp -c ./game_files custom.iso
XISOSharp -s -X "**/*.tmp" -X "**/node_modules/**" -c ./src ./out.iso
XISOSharp --pack ./game_files            # dir → create
XISOSharp --pack game.iso                # iso → rewrite (keeps .old)
XISOSharp -r game.iso                    # rewrite optimized (skips if already in!xiso)
XISOSharp -r -D game.iso                 # + delete .old
XISOSharp -c --file-time 0 ./game_files det.iso  # deterministic: byte-identical output

# Copy-out / hash / XEX / XBE / batch
XISOSharp --copy-out game.iso /media ./media_out
XISOSharp --copy-in game.iso ./my-config.ini /config.ini  # patch one file in (keeps .old backup; host dirs rejected fast)
XISOSharp --md5 game.iso                 # or --sha256
XISOSharp --xex-info game360.iso /default.xex
XISOSharp --xbe-info game.iso /default.xbe  # title ID/name, media, region
XISOSharp --sector-layout game.iso          # full sector map (extents, used/free ranges)
XISOSharp --ranges game.iso                 # system vs file sector spans
XISOSharp --is-optimized game.iso           # optimized-tag probe (supports --skip-sectors)
XISOSharp --batch -d ./out ./isos        # all *.iso sorted (extract/list/tree/rewrite/audit only)
XISOSharp --batch --batch-recursive -r ./isos

# Resume an interrupted unpack (skip files already on disk, logged as "skip: <path>")
XISOSharp --skip-existing --unpack game.iso ./out
XISOSharp --skip-existing --batch ./isos -d ./out   # bulk runs resume too
XISOSharp --skip-existing --copy-out game.iso /media ./media_out

# Safety: an -o that points back at the input (or its .old backup) is refused (exit 1)
XISOSharp -r -o game.iso game.iso        # Error: ... is the same file as the input

Audit, repair & salvage (no reference tool does this)

# Diagnose first: header, full tree walk, sector bounds, cycles, names
XISOSharp -V game.iso                    # Result: PASS, or FAIL + issue list

# Fixable in place (reserved bits, missing tag, separators in names; keeps .old)
XISOSharp --repair game.iso
XISOSharp --dry-run --repair game.iso    # preview only, changes nothing
XISOSharp --repair --no-backup game.iso  # skip the .old backup

# Truncated / structurally damaged: rebuild what is still reachable
XISOSharp --salvage game.iso             # -> game.salvaged.iso (source untouched)
XISOSharp --salvage --repair-out fixed.iso game.iso

Redump & disc offsets

# Video partition precedes game partition — auto-probed, or explicit
XISOSharp --skip-sectors 129824 -d ./out redump.iso     # GLOBAL/XGD2
XISOSharp -c --prepend-sectors 16640 ./files redump.iso # XGD3
XISOSharp -c --prepend-sectors 283392 ./files hybrid.iso # Hybrid 0x89D80000
XISOSharp -r --skip-sectors 283392 game.iso             # rewrite offset image to bare

# Validate lossless round-trip (flavors imply --validate; mismatch exits 2)
XISOSharp validate --validate-checksums game.redump.iso rebuilt.iso
XISOSharp -r --validate --validate-strict --validate-report report.json game.iso

Archival (Redump lossless, XboxKit parity)

# Extract components
XISOSharp --video game.redump.iso                  # -> game.video.iso (L0 head + L1 tail)
XISOSharp --random game.iso                        # -> game.filler (gap bytes)
XISOSharp --seed game.iso                          # -> game.seed (XGD1 PRNG brute-force, 4-byte LE)
XISOSharp --wipe -o wiped.iso game.iso             # zero filler gaps
XISOSharp --trim -o trimmed.iso game.iso           # truncate after last extent
XISOSharp --petrify game.iso                       # -> skeleton.iso + .hash (SHA-1 per file)
XISOSharp --update game.redump.iso                 # XGD3 -> su20076000_00000000 (+ zeroes it in video)
XISOSharp --zar -o game.zar game.iso               # ZArchive/zstd
XISOSharp --zar --jobs 4 --policy auto-rename game1.iso game2.iso  # parallel + skip|overwrite|auto-rename

# Aliases (mirrors xboxkit -a/-b/-c)
XISOSharp --all game.redump.iso                    # --random --seed --trim --update --video --wipe
XISOSharp --best game.redump.iso                   # --trim --wipe
XISOSharp --compress game.iso                      # --petrify --update --video --zar

# Security sectors (rebuild only; 4096-sector ranges)
XISOSharp --video game.redump.iso
XISOSharp rebuild --security-sectors sectors.txt -o rebuilt.iso # or:

# Rebuild lossless Redump from components
XISOSharp rebuild game.xiso video.iso filler.bin su20076000_00000000 -o rebuilt.redump.iso
XISOSharp rebuild game.xiso video.iso seed.bin -o rebuilt.redump.iso          # XGD1 seed variant
XISOSharp rebuild game.xiso video.iso --security-sectors sectors.txt -o rebuilt.redump.iso

Packing & compression (xdvdfs parity)

# Ordered remapping (wax captures, ! negation, xdvdfs.toml, --dry-run, \: colon escaping)
XISOSharp build-image ./src -m "bin:/" -m "assets/**:/assets/{1}" -O out.iso
XISOSharp build-image -D -m "!secret/**" -m "**:/{0}" ./src      # dry-run
XISOSharp build-image -f xdvdfs.toml ./src -O out.iso

# TOML generation
XISOSharp image-spec from -O dist/image.iso -m "bin:/" -m "assets:/{0}" xdvdfs.toml
# -> stdout if specPath omitted

# CISO (v2 LZ4 default, byte-identical to modern xdvdfs compress; v1 DEFLATE via --ciso-version 1)
XISOSharp compress ./game_dir game.cso --ciso-level 9       # 0=store; 1..9 = LZ4 acceleration 10-level
XISOSharp cso game.iso game.cso --ciso-split 0              # single .cso (default splits at ~4 GiB)
XISOSharp decompress game.1.cso game.iso                    # also reads split .1.cso/.2.cso parts
XISOSharp uncso game.cso                                    # decso alias

# Plain-ISO split/join (FATX-friendly) + deterministic checksum
XISOSharp split --size half game.iso                        # -> game.1.iso/game.2.iso
XISOSharp join --output rejoined.iso game.1.iso             # joinsplit alias

# Deterministic image checksum (SHA3-256 over sorted BTreeMap, xdvdfs compat)
XISOSharp checksum game.iso
XISOSharp checksum --silent game1.iso game2.iso            # hex only, multiple images
XISOSharp --checksum game.iso --silent                     # flag form

Exit codes: 0 success/-v/-h/validate pass, 1 usage/I/O, 2 validation failure (validate command or -r --validate on mismatch).

Using the Library

Core APIs live in the XISOSharp namespace; data models and enums (UnpackOptions, ProcessRunResult, ExplorerNode, XisoExplorerOptions, VolumeInfo, ProgressInfo, …) live in XISOSharp.Models; interfaces (IBlockDevice, IFilesystem) live in XISOSharp.Interfaces. Static XisoReader/XisoWriter plus archival types (XisoRedump, XisoOperations, XisoRanges, XisoSkeleton, XisoZarchive, XgdTables, XboxPrng, SecuritySectors), xdvdfs types (WaxGlob, RemapFilesystem, XisoChecksum, CisoWriter/CisoReader, BlockDevice/*), repair types (XisoRepairer, XisoSalvager), explorer/split/validate (XisoExplorer, XisoAttributes, XisoSplitter, XisoValidator, XisoPatcher), safety types (UnpackOptions, XisoPaths), typed records (VolumeInfo, EntryInfo, AuditResult, RepairResult, SalvageResult, XexInfo, XbeInfo, ValidationResult, ProgressInfo), CancellationToken + IProgress<ProgressInfo> + *Async everywhere.

Layouts: readers auto-detect RAW, GLOBAL/XGD2, XGD3, XGD2 Hybrid, and XGD1 by probing known disc offsets, and also accept rebuilt "sector-0" XISOs whose descriptor sits at absolute offset 0 (DiscLseek = 0, DescriptorSector = 0) — supported end-to-end by explorer, reads, XisoRanges, CreateZar, and in-place CopyIn. Writers emit the standard sector-32 layout; RebuildRedump rejects sector-0 inputs (repack to the standard layout first).

Extract / list / info

using XISOSharp;
using XISOSharp.Models;         // UnpackOptions, typed records/enums
using XISOSharp.DataStructures; // AvlNode, etc.

// Extract (llCompat auto via tag; pass false for optimized, true for legacy)
int rc = XisoReader.Extract("game.iso", "./out", llCompat: false);
int rc2 = XisoReader.UnpackImage("game.iso", "./out"); // auto IsOptimized, skipSectors aware

// Resume an interrupted unpack: files already on disk with matching sizes are
// skipped (logged as "skip: <path>") instead of rewritten (xdvdfs #190)
var resume = new UnpackOptions { SkipExisting = true };
int rc3 = XisoReader.UnpackImage("game.iso", "./out", options: resume);
XisoReader.CopyOut("game.iso", "/media", "./media_out", resume);

// Unpack without touching the disk (IFilesystem destinations, TODO #7):
// MemoryFilesystem (in-process bytes) or LocalFilesystem/custom stores
var memory = new MemoryFilesystem();
XisoReader.UnpackImage("game.iso", memory);          // lands at the fs root
byte[] xbe = memory.ReadAllBytes("default.xbe");

// Cancellation is honored per entry; extract also reports FileAdded per written file
using var cts = new CancellationTokenSource();
var progress = new Progress<ProgressInfo>(info => { /* ... */ }); // needs using XISOSharp.Models
int rc4 = XisoReader.Extract("game.iso", "./out", llCompat: false,
    cancellationToken: cts.Token, progress: progress);

// List / tree / directory
XisoReader.List("game.iso", llCompat: false);
XisoReader.Tree("game.iso", llCompat: false);
IReadOnlyList<EntryInfo> entries = XisoReader.ListDirectory("game.iso", "/");
IReadOnlyList<string> names = XisoReader.ListDirectoryFlat("game.iso", "/media");
EntryInfo? e = XisoReader.GetEntryInfo("game.iso", "/default.xbe");

// Volume & copy-out
VolumeInfo vol = XisoReader.GetVolumeInfo("game.iso"); // IsValid, RootDirSector/Size, DiscLseek, DiscFormat, FileLength, CreationTime, DescriptorSector
XisoReader.CopyOut("game.iso", "/media", "./media_out");

// In-place file reads (no extraction; .iso or .cso; bounded to the file size)
byte[] header = new byte[512];
int n = XisoReader.ReadFileBytes("game.iso", "/default.xbe", header, fileOffset: 0);

// Copy-in: patch one host file into the image in place (replace or add;
// keeps game.iso.old backup unless createBackup: false)
XisoReader.CopyIn("game.iso", "./my-config.ini", "/config.ini");

// Hash / audit / validate
byte[]? md5 = XisoReader.ComputeFileHash("game.iso", "/default.xbe", System.Security.Cryptography.HashAlgorithmName.MD5);
var hashes = XisoReader.ComputeDirectoryHashes("game.iso", "/", System.Security.Cryptography.HashAlgorithmName.SHA256);
AuditResult audit = XisoReader.AuditXiso("game.iso"); // header/cycles/bounds/0x48/0x0000; IsOptimized reports the tag
ValidationResult vr = XisoValidator.ValidateConversion("src.iso", "out.iso", verifyChecksums: true);
XisoValidator.LogResult(vr, "src.iso", "out.iso");
XisoValidator.WriteReport(vr, "src.iso", "out.iso", "report.json");

// XEX2 (Xbox 360)
XexInfo? xex = XisoReader.GetXexInfo("game360.iso", "/default.xex");
Console.WriteLine($"{xex?.TitleId:X8} entry 0x{xex?.EntryPoint:X8} region {xex?.Region}");

Repair, salvage & executable info (no reference tool does this)

// Audit, then fix what is safely patchable in place (keeps game.iso.old)
AuditResult audit = XisoReader.AuditXiso("game.iso");
if (!audit.IsValid)
{
    RepairResult rep = XisoReader.Repair("game.iso"); // reserved bits, tag, separators
    Console.WriteLine($"fixed {rep.Fixed.Count}, remaining {rep.Remaining.Count}");
}

// Truncated / structurally damaged: rebuild every reachable entry into a
// fresh image (source only read, never written; CISO input allowed)
SalvageResult salv = XisoReader.Salvage("game.iso"); // -> game.salvaged.iso
SalvageResult salv2 = XisoReader.Salvage("game.iso", "fixed.iso");
Console.WriteLine($"carried {salv.Copied.Count}, dropped {salv.Skipped.Count}, pass: {salv.Success}");

// XBE (original Xbox): header + certificate of an executable inside the image
XbeInfo? xbe = XisoReader.GetXbeInfo("game.iso", "/default.xbe");
Console.WriteLine($"{xbe?.TitleName} [{xbe?.TitleId:X8}] media 0x{xbe?.AllowedMedia:X8}");

// Split / join FATX-friendly parts (xdvdfs #97)
IReadOnlyList<string> parts = XisoReader.SplitXiso("game.iso", "game", 4L * 1024 * 1024 * 1024);
string rejoined = XisoReader.JoinSplitXiso(parts[0], "rejoined.iso");

// FILETIME volume descriptor (xdvdfs-compatible semantics)
DateTimeOffset stamped = XisoReader.GetFileTime("game.iso");
XisoReader.SetFileTime("game.iso", DateTimeOffset.UtcNow);

Create / rewrite

// Simple create (1:1 directory → ISO) — convenience
int rc = XisoWriter.PackFromDirectory("source_dir", "out/game.iso",
    excludePatterns: ["**/*.tmp", "**/node_modules/**"],
    progressCallback: (cur, total) => Console.Write($"\r{cur}/{total}"),
    progress: myProgress); // IProgress<ProgressInfo> FileCount/DirCount/DirAdded/FileAdded/FileProgress/FinishedPacking

// Full control (mirrors extract-xiso.c three-pass layout)
int rc2 = XisoWriter.CreateXiso(
    rootDirectory: "source_dir", outputDirectory: "./out", inRoot: null, sourceStream: null,
    out string? outIsoPath, inName: "game.iso", progressCallback: null,
    prependSectors: 129824, // GLOBAL/XGD2
    excludePatterns: ["**/$SystemUpdate/**"],
    cancellationToken: ct);

// Rewrite optimized (AVL) — in place by default; an explicit -o-equivalent
// outputName pointing at the input (or its .old backup) throws IOException
int rw = XisoReader.Rewrite("game.iso", outputPath: "./out", out string? rewritten,
    outputName: "game.opt.iso");
var (res, outPath) = await XisoWriter.CreateXisoAsync("source_dir", "./out", null, null, "game.iso", null, ct);
var (res2, out2) = await XisoReader.DecodeXisoAsync("game.iso", "./out", ExtractMode.Rewrite, llCompat: false, ct);

Redump archival

using XISOSharp;

// Video (L0 head + L1 tail via XgdTables VIDEO_L*_LENGTH, PVD 0x832D)
bool ok = XisoRedump.TryExtractVideo("game.redump.iso", "game.video.iso", out var videoPath);

// Filler gaps via ranges (sys + file extents)
byte[] filler = XisoOperations.ExtractFiller("game.iso");           // GapBytes = xisoLength - MergeRanges(sys,file)
uint? seed = XisoOperations.ExtractSeed("game.iso");                // XGD1 only, XboxPrng brutal 4-byte LE
bool hasSeed = XisoOperations.TryExtractSeed("game.iso", out uint seedVal);

// Wipe / trim / petrify
int wiped = XisoOperations.WipeFiller("game.iso", "wiped.iso");     // zero filler extents
int trimmed = XisoOperations.TrimXiso("game.iso", "trimmed.iso");   // (last.End+1)*2048
int petr = XisoSkeleton.Petrify("game.iso", "skeleton.iso", "hash.txt"); // zeroed + SHA-1 hex lines

// System update (XGD3 tail scan ABCDABCD)
bool upd = XisoRedump.TryExtractUpdate("game.redump.iso", "su20076000_00000000", "game.video.iso");

// Security sectors (4096-aligned ranges)
int[] sectors = SecuritySectors.Parse("sectors.txt"); // validates 4095 length, sorted

// Rebuild lossless Redump (L0+l0Padding+game+l1Padding+L1, PRNG or filler file)
int rebuilt = XisoRedump.RebuildRedump(
    xisoPath: "game.xiso", videoPath: "game.video.iso",
    fillerOrSeedPath: "filler.bin", // or seed.bin for XGD1
    updatePath: "su20076000_00000000",
    outputRedumpPath: "rebuilt.redump.iso",
    securitySectors: sectors, progress: myProgress, ct: ct);

// Ranges (XboxKit GetValidSectors / GetXISORanges parity)
var (sys, file) = XisoRanges.GetXisoRanges("game.iso", isoOffset: 0);
var merged = XisoRanges.MergeRanges(sys, file);
var files = XisoRanges.CollectFileEntries(File.OpenRead("game.iso"), isoOffset: 0); // sorted by Offset

// ZAR (zstd)
bool zar = XisoZarchive.CreateZar("game.iso", "game.zar");

// Tables & PRNG
int videoType = XgdTables.GetVideoType("game.redump.iso"); // via PVD 0x832D → WAVE_PVD
int isoType = XgdTables.GetRedumpIsoTypeBySize(new FileInfo("game.redump.iso").Length);
var prng = new XboxPrng(seedVal); prng.SimulateSectors(100); prng.WriteSectors(stream, 100);

Packing, CISO & checksums (xdvdfs parity)

// Build-image ordered remapping (WaxGlob *,**,?,[],{a,b} + {0}/{n} captures, ! negation)
var rules = new List<RemapRule>
{
    new("bin", "/"),
    new("assets/**", "/assets/{1}"), // {1} = first ** capture
    new("!secret/**", ""),            // exclusion (IsExclusion)
};
var preview = RemapFilesystem.DryRunRemap("./src", rules); // HostPath→ImagePath without writing
int built = RemapFilesystem.BuildImage("./src", "out.iso", rules, progress: myProgress);
string toml = RemapFilesystem.GenerateSpecText(rules, "dist/out.iso");
RemapFilesystem.WriteSpec("xdvdfs.toml", rules, "dist/out.iso");
var loaded = RemapFilesystem.ParseSpecFile("xdvdfs.toml"); // preserve-order [map_rules]

// CISO (pure-managed: v2 LZ4 default byte-identical to xdvdfs (lz4_flex port), v1 DEFLATE, threshold +12)
int cso = CisoWriter.CompressToCso("game.iso", "game.cso", level: 9, splitBytes: CisoWriter.DefaultSplitPoint);
int iso = CisoReader.DecompressToIso("game.1.cso", "rebuilt.iso"); // split .N.cso input supported
bool isCso = CisoReader.IsCso("game.cso"); // magic CISO + blockSize 2048 + ver 1/2

// BlockDevice — in-memory golden fixtures without temp files (no_std parity)
using var mem = new MemoryBlockDevice(File.ReadAllBytes("game.iso"));
var (rootSector, rootSize, discLseek) = XisoReader.VerifyXiso(mem, "game.iso");
AuditResult a2 = XisoReader.AuditXiso(mem);
using var cisoDev = new CisoBlockDevice(new FileBlockDevice(File.OpenRead("game.cso"))); // single-sector cache
var files2 = XisoReader.ListDirectory(cisoDev, "/");

// Deterministic image checksum (SHA3-256, SortedDictionary Ordinal, /path UTF-8 + streamed data — xdvdfs compat)
byte[] hash = XisoChecksum.ComputeImageChecksum("game.iso");
string hex = XisoChecksum.ComputeImageChecksumHex("game.iso"); // 64-char lowercase hex
// CLI prints "hex<TAB>path", --silent → hex only

Async, progress & cancellation

var cts = new CancellationTokenSource();
var progress = new Progress<ProgressInfo>(info =>
{
    switch (info.Type)
    {
        case ProgressInfoType.FileCount: Console.WriteLine($"{info.Count} files"); break;
        case ProgressInfoType.FileAdded: Console.WriteLine($"added {info.Path} @ {info.Sector}"); break;
        case ProgressInfoType.FinishedPacking: Console.WriteLine("done"); break;
    }
});

// All long-running ops accept CancellationToken + IProgress<ProgressInfo>
var (rc, outPath) = await XisoWriter.CreateXisoAsync("src", "./out", null, null, "game.iso", null, cts.Token, progress: progress);
int ex = await XisoReader.DecodeXisoAsync("game.iso", "./out", ExtractMode.Extract, llCompat: false, cancellationToken: cts.Token);
int rb = await Task.Run(() => XisoRedump.RebuildRedump("a.xiso","v.iso","f.bin",null,"out.iso", null, progress, cts.Token));

Errors are typed: XisoFormatException (corrupt), XisoEmptyException (no files), XisoFileTooLargeException (>4 GB, FileName/FileSize), ExtractErrorException (ErrorCode ErrEndOfSector/ErrIsoRewritten/ErrIsoNoFiles). Logger (Out/Error/Quiet/RealQuiet/MediaEnable/RemoveSystemUpdate/XboxDiscLseek) is redirectable for embedding.

Comparison

File-by-file against References/ — extract-xiso build 202609111233 (extract-xiso.c, same internal version 2.7.1, with the empty-subdirectory rewrite fix), XboxKit-0.7 (LibXGD/), xdvdfs-0.8.3 (xdvdfs-core/cli). Single matrix (✅ native, 🟡 partial/opt-in, ❌ absent, — n/a):

Capability XISOSharp extract-xiso 202609111233 XboxKit 0.7 xdvdfs 0.8.3
Reading
Extract / Unpack ✅ ✅ ✅ ✅
List top-level / Tree recursive ✅ 🟡 list only ❌ ✅
info / ls / xex-info / xbe-info ✅ ❌ ❌ 🟡 info/ls only
Per-file MD5 / SHA-256 ✅ ❌ ❌ 🟡 MD5
SHA3-256 image checksum (checksum) ✅ ❌ ❌ ✅
copy-out single file/dir ✅ ❌ ❌ ✅
copy-in single file (in-place patch + .old backup) ✅ ❌ ❌ ❌ (open #165)
Resume interrupted unpack (--skip-existing / UnpackOptions.SkipExisting) ✅ ❌ ❌ ❌ (open #190)
Deep audit -V (header/cycles/bounds/0x48/0x0000 + tag status) ✅ ❌ ❌ ❌
In-place repair --repair (reserved bits/tag/separators + .old backup) ✅ ❌ ❌ ❌
Salvage rebuild --salvage (carry reachable entries into fresh .iso + re-audit) ✅ ❌ ❌ ❌
validate + --validate* JSON report ✅ ❌ ❌ ❌
Disc probe RAW/GLOBAL/XGD3/Hybrid/XGD1 (5) ✅ 🟡 4 ✅ +tables 🟡 4 (XDVD_OFFSETS, no Hybrid)
Empty-dir sentinel 0xFFFF + 0x0000 header ✅ 🟡 0xFFFF only 🟡 0xFFFF only ✅
Reserved bits 0x08/0x40 masked ✅ ❌ ❌ 🟡 flag only
llCompat linked-list fix (auto via tag) ✅ ✅ ❌ ❌
Encoding Latin-1 / WINDOWS_1252 ✅ 🟡 raw bytes (FORCE_ASCII dead) 🟡 ASCII ✅
ECMA-119 descriptors 0x8000 ✅ ✅ ❌ 🟡 sector 32
Optimized tag in!xiso at 31337 ✅ ✅ ❌ ❌
CISO decompress (DEFLATE v1 + LZ4 v2, single + split parts) ✅ ❌ ❌ ✅
.cso/.1.cso input auto-detect in all verbs (img.rs parity) ✅ ❌ ❌ ✅
BlockDevice random-access (File/Memory/Offset/Ciso) ✅ ❌ ❌ ✅
Track/TOC parsing ✅* — — 🟡
Writing
Write V5 (optimized AVL) ✅ ✅ 🟡 ranges only ✅
FileModulus 0x10000 + sector 0xFF pad ✅ ✅ 🟡 range-copy only 🟡 0x00 pad
Empty dir → 1 sector 0xFF sentinel ✅ ✅ ❌ ✅
.xbe media patch E8…7D→EB (Boyer-Moore, overlap 7) ✅ ✅ ❌ ❌
Media patch disable -m ✅ ✅ ❌ ❌
Rewrite byte-parity (-r SHA-256 identical on real dumps: DIR/ARC attr defaults + empty-file frontier sectors) ✅ ✅ (reference) ❌ ❌
Source attribute preservation on rewrite (--preserve-attrs, RO/HID/SYS/NOR) ✅ ❌ (always normalizes) ❌ ❌
Empty-file start sector = allocation frontier (SectorAllocator zero-count) ✅ ✅ (reference) ❌ ❌
Custom -o filename ✅ ❌ — —
Redump / Archival
--video L0 head + L1 tail (PVD 0x832D) ✅ ❌ ✅ ❌
--random filler gaps (GetXisoRanges/MergeRanges) ✅ ❌ ✅ ❌
--seed XGD1 PRNG brute-force 4-byte LE ✅ ❌ ✅ ❌
--wipe zero filler ✅ ❌ ✅ ❌
--trim truncate after last extent ✅ ❌ ✅ ❌
--petrify skeleton + SHA-1 per file ✅ ❌ ✅ ❌
--update tail su20076000_00000000 (XGD3) ✅ ❌ ✅ ❌
--zar ZArchive/zstd (byte-identical to xboxkit: discovery-order name table) ✅ ❌ ✅ ❌
ZArchive read/write/pack/extract library (ZArchiveSharp, pure C#, zero packages, incl. RFC 8878 zstd encoder + decoder) ✅ ❌ ❌ ❌
rebuild from .zar sidecar (XboxKit roadmap "coming soon") ✅ ❌ ❌ ❌
rebuild lossless (L0/l0Padding+game+l1Padding+L1) ✅ ❌ ✅ ❌
--security-sectors 4096-aligned sectors.txt (rebuild only) ✅ ❌ 🟡 sectors.txt sidecar ❌
--jobs parallel --zar + --policy skip|overwrite|auto-rename ✅ ❌ ❌ ❌
Aliases --all/--best/--compress ✅ ❌ ✅ ❌
Wave tables XISO_OFFSET/REDUMP_ISO_LENGTH/VIDEO_Lx/WAVE_PVD ✅ ❌ ✅ ❌
--skip-sectors / --prepend-sectors arbitrary ✅ ❌ 🟡 built-in tables ❌
Packing / xdvdfs
build-image ordered host/**:image/{n} + ! + {0} ✅ ❌ ❌ ✅
image-spec from TOML preserve-order ✅ ❌ ❌ ✅
CISO compress v2 LZ4 (byte-identical lz4_flex port, fixed align 2) + v1 DEFLATE align 0/1/2, threshold +12 ✅ ❌ ❌ ✅
--ciso-level 0..9 / --ciso-version 1|2|auto / --ciso-split (default split 0xffbf6000) ✅ ❌ ❌ ✅
Split CSO output .1.cso/.2.cso… + split input (ciso::split parity, golden-tested vs xdvdfs-cli 0.8.3 incl. multi-part) ✅ ❌ ❌ ✅
Split plain ISO split/join/joinsplit (sector-aligned .1.iso…, FATX 4G cap) ✅ ❌ ❌ ❌
wax glob */**/?/[]/{a,b} ✅ ❌ ❌ ✅
xdvdfs.toml [map_rules] ✅ ❌ ❌ ✅
--dry-run preview ✅ ❌ ❌ ✅
RemapFilesystem / SectorAllocator ✅ ❌ ❌ ✅
API
Byte-range reads ✅ — 🟡 ✅
LBA sector reads ✅ — ✅ ✅
Thread-safe random-access ✅ ❌ ❌ ✅
CancellationToken ✅ ❌ ❌ ❌
IProgress<ProgressInfo> (FileCount/DirCount/DirAdded/FileAdded/FileProgress/FinishedPacking) ✅ 🟡 progress_callback ❌ 🟡 ProgressInfo
*Async (Task.Run) ✅ ❌ ❌ 🟡 maybe-async
Parallel verify / encode ✅ verify ❌ ❌ ❌
Typed errors (XisoFormatException etc.) ✅ ❌ ❌ 🟡 InvalidVolume
VerifyXiso(IBlockDevice) overload ✅ ❌ ❌ ✅ trait
Glob -X / WaxGlob captures ✅ 🟡 -s only ❌ ✅ wax
CLI
-c repeatable + -X excludes + -s ✅ 🟡 no -X — ✅ pack
--pack dir→create / iso→rewrite ✅ ❌ — ✅
Batch --batch sorted + --batch-recursive ✅ 🟡 explicit args only ❌ 🟡 checksum multi
Input==output safety guard (refuse -o onto input/.old/split part) ✅ — (no -o flag) ❌ ❌ (open #36)
Quiet -q / silent -Q (checksum --silent → hex only) ✅ ✅ 🟡 ❌
Help -h/--help / banner -v XISOSharp v<version> ✅ ✅ 🟡 -h only 🟡 clap
Exit 0/1 + 2 for validate --strict ✅ 🟡 0/1 only ❌ ❌
Extras
Extraction to dir ✅ ✅ ✅ ✅
Platform detection (OperatingSystem.Is*) ✅ 🟡 #if 🟡 ✅
Per-hunk CRC / full-image verify ✅ — — 🟡 SHA3
Multi-target builds ✅ net8/net9/net10 — ✅ net6–net10 (LibXGD net20–net10) — (Rust)
Strong-name signing (snk) ✅ — ❌ —
Trimmable + AOT (IsTrimmable/IsAotCompatible) ✅ — ❌ ✅ native/no_std
Native dependencies none none none (managed NuGet GrindCore/SabreTools) bincode/serde/clap/wax/ciso/md-5/sha3
Distribution NuGet + XISOSharp bin cmake bin dotnet publish + NuGet LibXGD cargo crate

*XISOSharp Track/TOC = GetVolumeInfo/ListDirectory/GetXisoRanges sector map; 🟡 = partial/opt-in/different pad or trimmed-only semantics. xdvdfs CISO = ciso crate 0.2 — LZ4 v2 with fixed align 2; DEFLATE v1 neither written nor read (ciso reader rejects version != 2), and no decompress verb (.cso read via CSOBlockDevice only). The original extract-xiso -p is parsed but unhandled (dead); FORCE_ASCII is a dead macro.

Build

git clone https://github.com/purelogiccode/XISOSharp.git
cd XISOSharp
dotnet build CSharp_XISOSharp.sln            # Debug
dotnet build CSharp_XISOSharp.sln -c Release # Release (same outputs; no implicit pack)
dotnet test -c Release                       # 1818 tests on net10.0 (1757 on net8/9; `XISOSharp.Tests`; ZArchiveSharp comes from NuGet)

Projects: XISOSharp.Core (net8.0/net9.0/net10.0) packs explicitly (dotnet pack; GeneratePackageOnBuild is off); XISOSharp.Cli (net8.0/net9.0/net10.0, ships net10.0); XISOSharp.Tests (net8.0/net9.0/net10.0); XISOSharpTester (net10.0-windows WPF). ZArchiveSharp (net8.0/net9.0/net10.0 ZArchive library) + ZArchiveSharp.Tests + ZArchiveSharp.Benchmarks moved to the sibling ../CSharp_ZArchiveSharp repo (own solution); XISOSharp consumes the library as NuGet package ZArchiveSharp 1.4.0. CI builds on ubuntu/windows/macos.

Requirements

  • .NET 8 SDK or newer (pinned 10.0.301 in global.json, rollForward: latestMinor)

License

MIT — see LICENSE.

About

A pure C# port of extract-xiso for Xbox ISO (XISO / XDVDFS) images — byte-identical output, no native dependencies, no P/Invoke, just .NET. Beyond the C baseline it merges the archival power of XboxKit and the modern packing of xdvdfs into one trimmable, AOT-compatible library + CLI.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages