-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
This page describes how SimpleXisoDrive is structured, how a mount is created, and how file operations flow through the system.
The application is split into a shared core (SimpleXisoDrive.Core) and two front ends: the Windows
Dokan app (SimpleXisoDrive) and the Linux/macOS FUSE 3 app (SimpleXisoDrive.Unix). The core owns
image parsing, the virtual file system and the services; the front ends only implement the mount
backend, the command line and the platform UX. The diagram below shows the Windows front end; the
Unix front end replaces XboxIsoVfsDokan/Dokan with FuseFileSystem/FuseInterop over
libfuse3/macFUSE, and DriveLetterSelector with temporary mount directories.
flowchart TD
subgraph User["User"]
Explorer["Windows Explorer"]
Console["Console / CLI"]
end
subgraph App["SimpleXisoDrive.exe"]
Program["Program<br/>CLI, lifecycle, global error handlers"]
DokanOps["XboxIsoVfsDokan<br/>IDokanOperations"]
Vfs["VfsContainer<br/>facade and volume selection"]
Volumes["XisoVfsVolume / ZarVfsVolume / ImageIsoVfsVolume<br/>path resolution, caching and virtual image.iso"]
Xiso["XisoExplorer / XisoReader<br/>XISOSharp image access"]
Chd["ChdFile / ChdImageStream<br/>CHDSharp hunk decompression"]
Zar["ZArchiveReader<br/>zstd block cache"]
Services["Services<br/>logging, bug reports, stats, updates"]
end
subgraph External["External"]
Dokan["Dokan kernel driver + dokan2.dll"]
Image["Xbox ISO/XISO or ZArchive file on disk"]
Api["PureLogic / GitHub endpoints"]
end
Explorer --> Dokan
Console --> Program
Dokan --> DokanOps
DokanOps --> Vfs
Vfs --> Volumes
Volumes --> Xiso
Volumes --> Zar
Xiso --> Image
Zar --> Image
Program --> Vfs
Program --> Services
DokanOps --> Services
Services --> Api
| Component | Type | Responsibility |
|---|---|---|
Program |
internal static class |
Entry point, CLI parsing, path resolution, mount lifecycle, console UX, global exception handlers. |
VfsContainer |
public class |
Facade over the selected volume: opens the image through VfsVolumeFactory and forwards entry lookups, directory listings, reads, and metadata. Implements IDisposable. |
IVfsVolume |
public interface |
The read-only volume contract (size, creation time, label, file-system name, entry lookup, listing, reads). Implemented by XisoVfsVolume, ZarVfsVolume and ImageIsoVfsVolume. |
IVfsEntry |
public interface |
A file or directory entry (name, directory flag, size, Windows attributes). Implemented by the XISO entry type in XisoVfsVolume (XDVDFS), the ZArchive entry type, and the synthetic entry in ImageIsoVfsVolume. |
VfsVolumeFactory |
internal static class |
Detects the image format: .zar opens as an archive, .chd opens through CHDSharp, everything else as an Xbox ISO (with a CHD/ZArchive magic fallback for renamed files). Archives with a single embedded XISO file mount that image; otherwise the archived tree mounts directly. With --image-iso it wraps the volume in ImageIsoVfsVolume, synthesizing a virtual XISO for a ZArchive tree when needed. |
XisoVfsVolume |
internal sealed class |
Opens an Xbox ISO/XISO (path or stream), delegates all parsing to XISOSharp, caches entries and directory listings, and serves file reads. Path-based images use a keep-open XisoExplorer; embedded images and decompressed CHD images use XisoReader stream APIs. |
ChdImageSource |
internal static class |
Opens an Xbox ISO CHD with CHDSharp (ChdFile.Open + ChdFile.OpenAsStream), rejects CD/GD-ROM CHDs up front, and returns a ChdImageStream over the decompressed image. The decompressed image must then parse as XDVDFS before the mount succeeds. |
ZarVfsVolume |
internal sealed class |
Exposes a ZArchive directory tree: resolves paths through ZArchiveReader, caches entries, and serves decompressed file data. |
ReaderOwningVfsVolume |
internal sealed class |
Decorates an embedded-XISO volume so the ZArchive reader that backs ZArchiveReader.OpenRead is disposed with the volume. |
ImageIsoVfsVolume |
internal sealed class |
Decorator enabled by --image-iso: adds a virtual read-only image.iso file at the volume root, backed by IRawImageSource, while the normal tree stays browsable. |
IRawImageSource / StreamRawImageSource
|
internal |
Reads raw image bytes at an offset from a seekable stream (plain ISO, CISO block device, decompressed CHD, or embedded XISO) or from the in-memory virtual XISO layout. Reads are serialized for Dokan's concurrent callbacks. |
VirtualXisoImageSource |
internal sealed class |
Synthesizes an XISO for a ZArchive directory tree entirely in memory: builds the directory tables with DirectoryEntryTableWriter, allocates sectors with SectorAllocator, emits the volume descriptor/ECMA-119 header/optimized tag, and serves file data from the archive on demand. No extraction, no temporary files. |
XboxIsoVfsDokan |
internal sealed class (Windows) |
Implements DokanNet's IDokanOperations. Maps Windows file system requests to VfsContainer calls, enforces read-only behaviour, and normalizes paths. |
FuseFileSystem |
internal sealed class (Unix) |
Mounts the VfsContainer through the FUSE 3 high-level API: getattr/open/read/statfs/readdir/init callbacks for Linux and macOS, read-only enforcement, and signal-driven unmounting through fuse_exit. |
FuseInterop |
internal static class (Unix) |
Platform-aware P/Invoke layer: resolves and loads libfuse3/macFUSE, declares the Linux and macOS fuse_operations layouts, picks the exported fuse_new entry point per platform, and pokes the mount with statfs to wake the loop. |
FuseAvailability |
internal static class (Unix) |
Probes the FUSE library, /dev/fuse and fusermount3, and prints installation guidance when FUSE is missing. |
XisoExplorer |
XISOSharp (external) |
Keep-open XISO image handle used by path-based mounts: eager volume probing, directory listing, entry lookup, and bounded file read streams. |
XisoReader |
XISOSharp (external) |
Static stream APIs used for images embedded in archives: volume probing (including rebuilt sector-0 images), directory listing, entry lookup, and raw data reads. |
SerilogDokanLogger |
public sealed class |
Routes DokanNet's internal log messages into Serilog. |
InvalidImageException |
public class |
Signals that a file is not a readable Xbox ISO/XISO image, Xbox ISO CHD or ZArchive. |
| Service | Responsibility |
|---|---|
LoggingSetup |
Builds the global Serilog logger (console + rolling file + bug report sink). |
ApiKeyProvider |
Decrypts the double-encrypted API key once at startup for the bug report and stats services. |
BugReport |
Builds bug reports, writes error.log, sends reports to the remote API, writes critical_error.log as a last resort. |
BugReportSink |
Serilog sink that forwards Warning and higher events to BugReport, with rate limiting. |
CheckAccess |
Queries whether the current process has administrator rights. |
StatsService |
Fire-and-forget anonymous launch statistics. |
UpdateChecker |
Queries GitHub for the latest release and offers to open the release page. |
-
Program.Mainconfigures Serilog viaLoggingSetup.ConfigureLogger. Failure is non-fatal; the application continues without logging. -
Program.RunAsyncsets the console theme, clears the screen, and installs two global handlers:AppDomain.CurrentDomain.UnhandledExceptionTaskScheduler.UnobservedTaskException
Both route exceptions to
BugReport.LogFatalException. -
The application reports launch statistics (
StatsService.ReportLaunch, fire-and-forget) and verifies its mount backend: the Dokan runtime (%SystemRoot%\System32\dokan2.dll) on Windows, or the FUSE library plus/dev/fuseon Linux (FuseAvailability.Check). -
Arguments are parsed and the image path is resolved (see Command-Line Reference).
-
UpdateChecker.CheckForUpdateAsyncruns; on Windows an available update is offered through a message box (skipped when the console is redirected), on Unix through the console prompt. -
RunMountAsyncbuilds theVfsContainer(which selects anIVfsVolumeviaVfsVolumeFactory) and mounts the Dokan file system (Windows) or the FUSE file system (Unix). -
The process blocks until
Ctrl+C, a key press (drag-and-drop mode),fusermount3 -u/umount(Unix), or a failure. -
On shutdown the
VfsContaineris disposed, the file stream is closed, pending bug reports get a bounded grace period (BugReport.WaitForPendingReportsAsync), andLog.CloseAndFlush()is called.
sequenceDiagram
participant P as Program
participant V as VfsContainer
participant F as VfsVolumeFactory
participant X as XisoVfsVolume
participant C as ChdImageSource
participant Z as ZarVfsVolume
participant DK as Dokan
P->>V: new VfsContainer(imagePath)
V->>F: Open(imagePath)
alt .iso / .xiso (or extensionless ISO)
F->>X: new XisoVfsVolume(path)
X->>X: XisoExplorer keep-open + volume probe
else .chd (or renamed CHD)
F->>C: OpenOrThrow(path)
C->>C: ChdFile.Open + OpenAsStream (reject CD/GD-ROM)
F->>X: new XisoVfsVolume(decompressed stream)
X->>X: XisoReader stream volume probe
else .zar (or renamed archive)
F->>Z: new ZarVfsVolume(reader)
Z->>Z: open ZArchiveReader and tree
end
F-->>V: IVfsVolume
P->>DK: new Dokan + DokanInstanceBuilder
P->>DK: Build(XboxIsoVfsDokan)
DK-->>P: DokanInstance (mounted)
loop Until unmount
DK->>P: filesystem callbacks (on thread pool)
end
P->>V: Dispose()
V->>X: Dispose() or Z: Dispose()
P->>P: return 0
Key points:
-
VfsContainerconstruction performs all format validation before Dokan is involved, so an invalid image fails fast withInvalidImageException. A.zararchive that embeds a single XISO image mounts throughXisoVfsVolumeoverZArchiveReader.OpenRead; a directory-tree archive mounts throughZarVfsVolume. - A
.chdfile opens throughChdImageSource: CD/GD-ROM CHDs are rejected immediately, and the decompressed image is mounted throughXisoVfsVolumeover aChdImageStream. If the decompressed bytes are not XDVDFS the mount fails with a CHD-specificInvalidImageException. - With
--image-iso,VfsVolumeFactorywraps the volume inImageIsoVfsVolume. Plain ISO and CISO inputs, CHDs and embedded-XISO archives serveimage.isoon demand from the raw image stream (a CHD uses a second independentChdImageStreamso raw-image reads never race the volume stream); a ZArchive directory tree is synthesized into a virtual XISO in memory (layout built immediately, file data read from the archive on demand), so the mount appears without any extraction. - Dokan options depend on privileges and flags:
- always
WriteProtection | CurrentSession; -
MountManageronly when elevated; -
DebugMode | StderrOutputwhen--debugis set.
- always
- Drive letters are normalized from
Z:\toZ:because the Dokan driver rejects the trailing backslash form on some versions.
A typical file read in Explorer becomes:
- Explorer issues a
ReadFilerequest to the Dokan kernel driver. - DokanNet dispatches the request to
XboxIsoVfsDokan.ReadFileon a worker thread. -
XboxIsoVfsDokanuses theIVfsEntrystored inIDokanFileInfo.Context(or resolves it by path if the context is missing), checks that it is not a directory, and clamps the request to the file size. -
VfsContainer.ReadFileforwards the request to the activeIVfsVolume.ImageIsoVfsVolumeroutes the syntheticimage.isoentry to itsIRawImageSource(a serialized seek-and-read over the raw image stream); every other entry goes to the wrapped volume. -
XisoVfsVolumeserves the read through XISOSharp. Path-based volumes open a bounded stream withXisoExplorer.OpenReadStream(CISO-aware, serialized by the explorer's internal lock); embedded stream volumes — including decompressed CHDs — compute the absolute byte offsetDiscLseek + StartSector * 2048 + offsetand read directly under the volume's stream lock, where a CHD read decompresses the covering hunk on demand through CHDSharp.ZarVfsVolumecallsZArchiveReader.ReadFromFile, which resolves the covering 64 KiB block(s), decompresses them through a 4 MiB LRU cache, and copies the requested range. - The number of bytes read is returned to Dokan, which hands the buffer to the kernel.
Reads are streamed directly from the image; only the ZAR block cache (64 blocks, 4 MiB) keeps decompressed data in memory.
The active volume implementation maintains two per-instance caches:
| Cache | Key | Value |
|---|---|---|
| Entry cache | Normalized virtual path (\Games\Halo\default.xbe) |
Entry (XISO entry for XISO images, ZArchive node for ZAR) |
| Children cache | Normalized directory path | List of entries |
For XISO images, directory listings come from XISOSharp's hardened TOC walk: the visited offset set prevents cycles, each directory table is capped at a fixed number of entries, and separator-bearing file names abort the walk. The volume itself caches entries and child lists in concurrent dictionaries, so repeated lookups and re-listings are served without touching the image.
For ZArchive trees, children come from the flat file tree in the archive. The volume caps each
directory at 100,000 enumerated entries (the underlying reader validates node counts against the
table), takes child node handles directly from TryGetDirEntry (the library clamps crafted counts),
and caches entries as they are discovered.
XisoExplorer in keep-open mode serializes its operations on an internal lock, so concurrent Dokan
requests cannot interleave seeks; the stream-backed XISO volume guards its held stream with its own
lock. ZArchiveReader is internally thread-safe (single lock plus a 4 MiB LRU block cache); both
volume caches are concurrent dictionaries.
The application uses a layered approach:
| Layer | Strategy |
|---|---|
XISOSharp (XisoExplorer / XisoReader) |
Signals invalid images with XisoFormatException/InvalidDataException; the volume translates them. |
XisoVfsVolume / ZarVfsVolume
|
Catch failures per operation, log, return null/empty. Construction failures are wrapped in InvalidImageException. |
XboxIsoVfsDokan |
Every public operation is wrapped by ExecuteWithReporting, which logs the failing operation and returns DokanResult.Error instead of propagating. |
Program |
Converts known exceptions (InvalidImageException, DokanException, DllNotFoundException) into user-facing guidance and returns exit code 1. |
| Global handlers | Unhandled and unobserved exceptions are written to error.log and reported through the remote bug report API when configured. |
Because BugReportSink is attached at Warning level, any error logged through Serilog is also
persisted to error.log and, if configured, forwarded to the developer API. See
Privacy and Networking.
- Dokan invokes callbacks on thread pool threads; multiple operations can be in flight at once.
- All access to the shared image handle is guarded: a keep-open
XisoExplorerserializes its operations on an internal lock, and the stream-backed XISO volume locks its held stream. ZArchive reads are serialized insideZArchiveReaderwith its own lock. - The mount initialization itself runs on the main async flow; the process then waits on a
TaskCompletionSourceregistered with the cancellation token. -
Ctrl+Cis handled by cancelling the token; the cancellation is cooperative and triggers a clean unmount rather than an abrupt exit.
CSharp_SimpleXisoDrive/
|-- CSharp_SimpleXisoDrive.sln
|-- global.json # .NET SDK 10.0.0, rollForward latestMajor
|-- ReadMe.md
|-- WhatsNew.md # release highlights
|-- .editorconfig # analyzer rule suppressions
|-- docs/ # this documentation (wiki + Pages side menu)
|-- SimpleXisoDrive.Core/ # shared class library (net10.0)
| |-- ImagePathResolver.cs # extension/directory/current-dir resolution
| |-- ConsoleKeyPress.cs # shared interactive key wait
| |-- InvalidImageException.cs
| |-- VfsContainer.cs # facade over the selected volume
| |-- Interfaces/ # IVfsVolume, IVfsEntry, IRawImageSource
| |-- Models/ # stats and bug report request bodies
| |-- Services/ # logging, bug reports, stats, updates, API key
| `-- Vfs/ # XisoVfsVolume, ZarVfsVolume, CHD, image.iso,
| # VfsVolumeFactory and ownership decorators
|-- SimpleXisoDrive/ # Windows application (net10.0-windows, Dokan)
| |-- Program.cs
| |-- CommandLineParser.cs # argument parsing/validation
| |-- DriveLetterSelector.cs # free M-R drive letter for drag-and-drop
| |-- DokanInstallation.cs # dokan2.dll/dokan2.sys detection
| |-- WindowsUpdatePrompt.cs # native message box for update notifications
| |-- XboxIsoVfsDokan.cs
| |-- SerilogDokanLogger.cs
| |-- UsageText.cs
| `-- icon/xiso.ico, icon/xiso.png
|-- SimpleXisoDrive.Unix/ # Linux/macOS application (net10.0, FUSE 3)
| |-- Program.cs
| `-- Fuse/
| |-- FuseFileSystem.cs # high-level API mount + callbacks
| |-- FuseInterop.cs # library loading, layouts, P/Invoke
| `-- FuseAvailability.cs # libfuse3 / /dev/fuse / fusermount3 checks
|-- SimpleXisoDrive.Tests/ # xUnit test project (net10.0-windows)
`-- SimpleXisoDrive.Unix.Tests/ # xUnit test project (net10.0)
| Package | Version | Purpose |
|---|---|---|
DokanNet |
2.3.0.3 | Managed wrapper over the Dokan user-mode file system library. |
Serilog |
4.4.0 | Structured logging core. |
Serilog.Sinks.Console |
6.1.1 | Console log output. |
Serilog.Sinks.File |
7.0.0 | Rolling file log output. |
XISOSharp |
1.4.1 | Xbox ISO/XISO image access: volume probing (including rebuilt sector-0 images), directory traversal, and file reads for the XISO volume. |
CHDSharp |
1.4.3 | Pure-C# CHD reader: opens Xbox ISO CHDs and decompresses hunks on demand through ChdImageStream. |
ZArchiveSharp |
1.4.0 | Pure-C# ZArchive reader/writer; the mount-friendly reader API (node handles, entry streams, failure reasons) is used to mount .zar volumes. |
Meziantou.Analyzer |
3.0.290 | Build-time code analyzers. |
Roslynator.Analyzers |
5.0.0 | Build-time code analyzers. |
Test projects: Microsoft.NET.Test.Sdk 18.10.1, xunit 2.9.3, xunit.runner.visualstudio 4.0.0,
coverlet.collector 10.1.0.
-
Read-only by construction. No mutation path exists in the VFS layer; Dokan operations that
would modify state return
DokanResult.AccessDenieddirectly. -
Validate before mounting. Format detection and validation happen in
VfsVolumeFactoryand the volume constructors, so the user gets a clear error before a drive letter is consumed. - Parsing delegated to XISOSharp. The application no longer reads XDVDFS bytes itself; volume probing, TOC walking, and attribute mapping come from the library, so fixes and hardening apply to every consumer at once.
- CHD is an Xbox-ISO-only feature. CHDSharp handles the container and codecs, but the mount accepts a CHD only when its decompressed image parses as XDVDFS. CD/GD-ROM CHDs are rejected before any hunk is read, so the mount never exposes non-Xbox content as if it were an Xbox disc.
- Fail-safe traversal. Cycle detection and per-table entry limits protect against malformed or malicious images rather than trusting the tree structure.
- Stream, do not load. File content is read on demand and never cached; ZAR blocks are decompressed individually through a small bounded cache, keeping memory usage independent of image size.
- Log-and-continue at the edges. Internal failures are contained and surfaced as I/O errors to Windows, keeping the mounted volume stable for other files.
User guide
Technical reference
Development
Project