# 3. Architecture This page describes the solution structure, the runtime startup sequence, and the high-level data flow of the application. Detailed deep dives live in [Conversion Pipeline](05-conversion-pipeline.md), [Extraction & Verification](06-extraction-and-verification.md), [Services Reference](07-services-reference.md), and [Utilities Reference](08-utilities-reference.md). --- ## 3.1 Solution Structure ``` CSharp_CHDStudio.sln ├── CHDStudio/ (Avalonia app, net10.0;net10.0-windows) │ ├── App.axaml(.cs) → startup, Serilog, exception handlers │ ├── AppConfig.cs → central configuration │ ├── MainWindow.axaml(.cs) → UI + all batch logic │ ├── AboutWindow.axaml(.cs) → about dialog │ ├── Models/ │ │ ├── FileItem.cs → bindable file row (name, size, selected) │ │ ├── GitHubRelease.cs → GitHub API release model │ │ └── PbpExtractionResult.cs → PBP extraction outcome │ ├── Services/ │ │ ├── AppHttpClient.cs → singleton HttpClient (TLS 1.2/1.3) │ │ ├── ArchiveService.cs → zip/7z/rar extraction, CSO, 7za fallback │ │ ├── BugReportApiSink.cs → Serilog sink → bug API │ │ ├── BugReportService.cs → bug report client + exclusion list │ │ ├── ChdExplorerService.cs → CHD file-system explorer + Image Info report │ │ ├── ChdSharpEncoderService.cs → in-process CHD encoder (CHDSharp library) │ │ ├── FileEventRecord.cs / FileWatchEventType.cs │ │ ├── FileWatcherService.cs → missing-file diagnostics │ │ ├── LegacyCleanupService.cs → removes legacy files/folders │ │ ├── ScreenshotService.cs → window screenshot capture (RenderTargetBitmap) │ │ ├── StatsService.cs → anonymous usage stats │ │ └── UpdateService.cs → GitHub update checks │ └── Utilities/ │ ├── BinCueGenerator.cs → auto-cue generation for bin-only archives │ ├── ChdChecksumReport.cs → whole-image + per-track SHA-1/CRC-32/XXH3 report writer │ ├── ChdInfoReport.cs → Explorer Image Info report builder │ ├── ChdSharpProgressLogger.cs → 10%-step CHDSharp progress logging │ ├── CueFileLineTransform.cs / CueFileReference.cs / CueNormalizationResult.cs │ ├── CueNormalizer.cs → encoding detection + canonicalization │ ├── CueWorkDirectory.cs(.Result) → self-contained ASCII cue work dirs │ ├── DiscImageKind.cs → what a file turned out to be │ ├── DiscImageSignature.cs → magic-byte content identification │ ├── FileExtensions.cs → all extension constants and sets │ ├── GameFileParser.cs → cue/gdi/toc referenced-file resolution │ ├── IMp3Decoder.cs / Mp3ToWavDecoder.cs │ ├── InputFileFilter.cs → drops raw images a descriptor already covers │ ├── IsoSectorValidator.cs → sector-size alignment checks │ ├── PathUtils.cs → temp dirs, path sanitizing, relative paths │ ├── RawCdImageDetector.cs → raw 2352 sector sniffing + cue staging │ ├── RetryingFileOperations.cs → retry-with-backoff delete/move │ ├── TrackBinCueBuilder.cs → multi-FILE cue for "(Track N)" bin sets │ └── Ecm/ → in-process ECM decoding │ ├── CdSectorEccEdc.cs → regenerates sector EDC + Reed-Solomon parity │ ├── EcmImageDecoder.cs → ECM block-stream decoder │ └── EcmDecodeResult.cs ├── CHDStudio.Tests/ (xUnit; Fixtures/ holds ecm-sample.ecm, rar-multipart/, MdsV2/ and laserdisc-small.avi) ├── MDSSharp/ (Alcohol 120% .mds/.mdf parsing; net8.0;net9.0;net10.0) ├── CCDSharp/ (CloneCD .ccd/.img/.sub parsing; net10.0;net8.0) ├── CSOSharp/ (CSO/CISO decompression; net10.0;net8.0) ├── PBPSharp/ (PBP/SFO parsing; net10.0;net8.0) ├── ISZSharp/ (UltraISO ISZ decompression; net8.0;net9.0;net10.0) └── References/ (third-party sources — not part of the build) ``` ### Dependency graph ``` ┌──────────────────────────────────┐ │ CHDStudio │ (Avalonia app) └───┬───────┬───────┬───────┬──────┘ Project refs │ │ │ │ ┌───────────▼─┐ ┌───▼──────▼──┐ ┌──▼──────────┐ │ CCDSharp │ │ CSOSharp │ │ PBPSharp │ └──────────────┘ └─────────────┘ └─────────────┘ ┌──────────────────────┐ ┌─────────────────────┐ │ MDSSharp │ │ ISZSharp │ └──────────────────────┘ └─────────────────────┘ NuGet: CHDSharp 1.4.3, Avalonia, SharpCompress, NAudio, Serilog ``` - The app references `MDSSharp`, `CCDSharp`, `CSOSharp`, `PBPSharp` and `ISZSharp` as project references. - All five libraries are packable and expose internals to `CHDStudio.Tests` via `InternalsVisibleTo`; `MDSSharp`, `CCDSharp`, `CSOSharp`, `PBPSharp` and `ISZSharp` all multi-target `net8.0;net9.0;net10.0`. - `CHDStudio.Tests` references the app (internals visible) plus `MDSSharp`, `CSOSharp`, `PBPSharp` and `ISZSharp` — but **not** `CCDSharp` (there are no CCDSharp unit tests today; see [Testing](11-testing.md)). > **Why ISZ and Alcohol support moved into libraries.** `ISZSharp` and `MDSSharp` were split out of the app's utilities into standalone packable projects (they are self-contained formats with redistributable value), while ECM decoding remains in-app because it is tightly coupled to the cue staging flow. The test project references both new libraries directly. --- ## 3.2 Startup Sequence ``` App ctor ├─ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance) ← legacy codepages (CP932/CP949/CP1251...) ├─ new BugReportService(...) → App.SharedBugReportService ├─ new StatsService(...) ├─ ConfigureSerilog() │ ├─ file sink: %LocalAppData%\CHDStudio\logs\CHDStudio-.log (daily, 7 retained) │ ├─ debug sink │ └─ BugReportApiSink (forwards Warning+ to the bug API) └─ subscribe: AppDomain.UnhandledException, Dispatcher.UIThread.UnhandledException, TaskScheduler.UnobservedTaskException, Exit OnStartup ├─ acquire global mutex "Global\CHDStudio_SingleInstance" (second instance → exit) ├─ ShutdownMode = OnMainWindowClose ├─ apply dark Fluent theme (Avalonia) ├─ delete legacy 7z_x64.dll / 7z_arm64.dll ├─ _statsService.RecordUsageAsync() (fire-and-forget) └─ type preloading on background thread MainWindow ctor ├─ probe chdman/7za (app directory first, then PATH); the CHDSharp encoder is built in ├─ construct services (ArchiveService, ScreenshotService, FileWatcherService) ├─ wire F8 screenshot hotkey (window KeyDown) ├─ InitializeStatusBar ├─ after 2 s: CleanupLeftoverTempDirectories + LegacyCleanupService.RunInBackground └─ log environment details MainWindow Loaded ├─ create performance counters (write/read speed) ├─ apply CLI folder argument if present ├─ CheckDependenciesAndNotifyUser (chdman presence on Windows; built-in CHDSharp always available) └─ UpdateService.CheckForNewVersionAsync (background) ``` Line references: `App.axaml.cs:35–145`, `MainWindow.axaml.cs:87–172`. --- ## 3.3 Runtime Data Flow — Conversion ``` User clicks Start Conversion └─ StartConversionButton_ClickAsync (MainWindow.axaml.cs:1275) ├─ validate paths (ValidateAndNormalizePath) ├─ read options (delete originals, smaller-first, force CD/DVD, timeout) ├─ RenewCancellationTokenSource ├─ SetControlsState(false) └─ PerformBatchConversionAsync (:1684) ├─ encoder preflight (Windows): probe chdman access + compatibility; │ continue on the built-in CHDSharp encoder when missing or failing │ (on Linux/macOS the built-in encoder is used directly) ├─ optional sort by size (smaller first) ├─ CheckDiskSpace (free space warnings) ├─ InputFileFilter + ResolveOutputCollisions (batch preflight) └─ per file: ProcessSingleFileForConversionAsync ├─ missing file? → FileWatcherService diagnostics ├─ TryResolveByContentAsync ← content before extension │ ├─ split volume set → SplitImageJoiner (MDSSharp) → classify │ ├─ Isz → ResolveIszAsync (ISZSharp) → classify │ ├─ Ecm → ResolveEcmAsync (Utilities/Ecm) → classify │ ├─ Chd → skip ("already a CHD") │ └─ container extension, plain image inside → generated cue ├─ else route by extension: │ .cso → ProcessCsoFileForConversionAsync │ archive→ ProcessArchiveFileForConversionAsync │ .pbp → ProcessPbpFileForConversionAsync (InvalidHeader → content-routed) │ .ccd → ProcessCcdFileForConversionAsync │ .mds → ProcessMdsFileForConversionAsync (MDSSharp) │ other → TryStageCueForRawImageAsync → direct conversion ├─ ValidateDependentFilesAsync (cue/gdi/toc) ├─ TryDirectConversionAsync │ └─ ConvertToChdAsync → chdman primary on Windows with the │ built-in CHDSharp fallback; built-in only │ on Linux/macOS; writes ..chdtmp, │ moves on success ├─ fallback: TryRetryConversionViaTempCopyAsync └─ HandleConversionResultAsync ├─ success → optionally delete originals + prune empty dirs └─ failure → leave the destination alone, keep source ``` ## 3.4 Runtime Data Flow — Extraction & Verification ``` Extraction: StartExtractionButton_ClickAsync (:915) └─ PerformBatchExtractionAsync (:1761) └─ per file: ExtractChdAsync (:4142) ├─ pick command: auto-detect via CHD metadata, or explicit CD/DVD/HDD ├─ ChdFile.Open (CHDSharp) — corrupt CHD → clear error, continue ├─ DVD/HDD → ExtractChdToSingleFile (streamed 4 MB buffer) └─ CD/GDI → ExtractChdTracksToDirectory (temp dir → retrying moves) Verification: StartVerificationButton_ClickAsync (:1413) └─ PerformBatchVerificationAsync (:4005) └─ per file: VerifyChdAsync (:6004) — CHDSharp Chd.CheckFile └─ optional move to Success/Failed via MoveVerifiedFileAsync (:4086) └─ RetryingFileOperations.TryMoveAsync (retries ~45 s on locks) ``` ## 3.5 Concurrency & Threading Model - **UI thread**: all Avalonia controls; dispatcher invocations are used from worker contexts (`Dispatcher.UIThread.Invoke`/`InvokeAsync`, with `DispatcherPriority.Background` for chunked list loading). - **Worker threads**: `Task.Run` for file scanning, archive extraction, chdman process orchestration, in-process CHDSharp encoding, screenshot rendering. - **Cancellation**: one `CancellationTokenSource` per operation, guarded by a `Lock` (`_cts`, `_ctsLock`, `MainWindow.axaml.cs:31–32`); cancellation is observed at every loop iteration and propagated into chdman via a linked timeout CTS. - **Chdman process**: stdout/stderr are redirected and parsed asynchronously (`OutputDataReceived`/`ErrorDataReceived`); the process is killed (`process.Kill(true)`) on cancellation/timeout, and the app waits 300 ms before temp cleanup so file handles are released. - **Speed telemetry**: `PerformanceCounter`-based disk write/read rates sampled every second (`AppConfig.WriteSpeedUpdateIntervalMs = 1000`). - **Operation state**: an interlocked `_operationRunningState` plus `SetControlsState` guards the UI against re-entrancy; a `_pendingClose` flag lets the window close gracefully mid-operation. ## 3.6 Logging Pipeline ``` LogMessage / LogWarning / LogError (MainWindow) └─ Serilog (Log.Information/Warning/Error) ├─ Debug sink ├─ File sink → %LocalAppData%\CHDStudio\logs\CHDStudio-YYYYMMDD.log └─ BugReportApiSink → BugReportService.SendBugReportAsync (Warning+ only; exclusion patterns drop known-noise; single in-flight send) ``` See [Bug Reporting System](09-bug-reporting.md) for the full contract.