github purelogiccode/CHDStudio release_3.9.0
3.9.0

2 hours ago
CHD Studio 3 9 0 Release Poster

3.9.0 (2026-10-03)

Renamed to CHD Studio

  • The project, application and executable are now CHD Studio (CHDStudio): the GitHub repository moved to https://github.com/purelogiccode/CHDStudio (the old URL redirects), the solution is CHDStudio.sln, the project folder is CHDStudio/ and the Windows executable is CHDStudio.exe. The five NuGet libraries keep their names; their repository links point at the new URL from the next package release.
  • Logs and screenshots now live under %LocalAppData%\CHDStudio (old %LocalAppData%\BatchConvertToCHD logs are not migrated); the temporary work folder fallback is now CHDStudio_Temp.

Avalonia becomes the application front end

  • The Windows-only WPF front end is gone; the cross-platform Avalonia app is now the base project and builds CHDStudio.exe. It runs on Windows, Linux and macOS; official release zips remain the two Windows architectures.
  • The application project was renamed BatchConvertToCHD.Avalonia → CHDStudio, so the folder, project and executable all carry the product name.
  • Shared code and the bundled tools moved into CHDStudio/, so the project no longer links files from another folder. Windows keeps the bundled chdman/7za/CHDSharp; Linux and macOS discover them (plus ffmpeg for MP3 tracks) on PATH.
  • UI parity with the old WPF build was restored: Alt+letter button mnemonics, click-to-sort grid columns with the original header tooltips, and accessibility names on the main controls.
  • F8 screenshots are now window-scoped: while the app window is focused, F8 captures it with RenderTargetBitmap on every platform (previously a system-wide hotkey captured the foreground window on Windows only). Screenshots are saved into the screenshots folder inside %LocalAppData%\CHDStudio, beside the logs folder, with a screenshots folder next to the app as fallback.
  • The main window has a Donate button (left of About) that opens the project's donation page, and the About window now scrolls so every acknowledgement is reachable on short screens.
  • The Close button always closes the app again: shutting down from inside the window's Closing event re-entered the close in a loop, so the window never actually closed while an operation was idle.
  • The activity log is batched and capped: log lines are flushed in batches instead of one dispatcher call per line, and the on-screen text is capped, so a very large log can no longer freeze the window. The Explorer tab now hides the log panel entirely instead of leaving a clipped strip of it over the window edge.
  • The About window is complete: it now carries the full third-party acknowledgements (CHDSharp, CHDMAN, Avalonia, the in-house image libraries, VideoGameFileSystemParser, SharpCompress, NAudio, Serilog and 7-Zip), a License section (GPL-3.0 with a link), the copyright line, and donate/release links.

New CHD Explorer tab

  • Browse a CHD like a disc: pick a .chd, choose the file-system parser matching its console/system (35 formats, PlayStation auto-detection by default), and navigate the folder tree in a grid. Double-click a folder to open it, a file to extract and open it, or use Extract to save a folder to disk.
  • Image Info: an expander under the parser drop-down shows a read-only report built from the CHD header and map — version, hunk/unit sizes, logical size, image type (CD/GD-ROM/DVD/HDD/A-V), codecs, metadata tags, the CD/GD-ROM track table and the per-codec hunk distribution (the scan is capped at the first 1,000,000 hunks for very large images).
  • Backed by VideoGameFileSystemParser 1.3.0 (https://www.nuget.org/packages/VideoGameFileSystemParser), rebuilt against CHDSharp 1.4.3 and published for this release.

Laserdisc A/V support

  • .avi laserdisc captures convert with createld: the built-in CHDSharp encoder assembles every video frame into MAME's raw chav layout and compresses it with the avhu codec (delta-RLE Huffman video + per-channel mono FLAC audio), one frame per hunk, exactly like chdman createld; the Force CD/DVD overrides are ignored for an AVI.
  • A/V CHDs extract back to AVI in-process: the Extract tab gained a Laserdisc (.avi) format, and Auto-detection now reads the CHD's own flags — a CHD with no CD/DVD/HDD metadata is an A/V image and is extracted with CHDSharp's MAME-parity ExtractLaserDisc writer (YUY2 video + PCM audio). chdman extractld/extractraw remain the automatic fallback when the library cannot decode the CHD.

Verification checksum reports

  • Optional <name>.checksums.txt next to each verified CHD (checkbox on the Verify tab): one whole-image SHA-1, CRC-32 and XXH3-64 plus per-track SHA-1, CRC-32 and XXH3-64 for CD/GD-ROM images. The whole-image hashes are computed over the decompressed image, so they match a hash of the extracted data rather than the CHD header's combined hash. The report follows the CHD into the Success folder, and a report that cannot be written is a warning — never a verification failure.

Live tool output, progress and read-ahead

  • Every chdman output line now reaches the activity log, progress included, with completion lines ("Compression complete"/"Extraction complete"/"final ratio") marked with a check. The on-screen log caps a single line at 2,000 characters and appends at most 200 lines per flush, so a chatty tool cannot stall the UI, and the file sink rolls at 10 MB.
  • The built-in CHDSharp encoder and reader log progress every 10% for conversion (ratio=), verification, extraction and checksum hashing, so long library operations visibly keep running without one line per hunk.
  • Extraction pre-decompresses 16 hunks in the background (ChdFile.ConfigureReadAhead), so sequential reads overlap the disk writes on the single-file (DVD/HDD) and multi-track (CD/GDI) paths.

Built-in CHD encoder — no CHDSharp CLI

  • CHDSharp now runs in-process (the NuGet library, not the bundled CHDSharp.exe CLI, which has been removed). On Linux and macOS it is the encoder, so those builds need no bundled native tool; on Windows the bundled chdman remains the primary encoder and the built-in CHDSharp is the automatic fallback. It mirrors chdman's commands and defaults (createcd/createdvd/createhd/createraw) and produces byte-identical CHDs.
  • Conversion can no longer fail for a missing encoder; a missing Windows chdman.exe only logs that the built-in encoder will be used. The status bar shows CHDSharp as always available.
  • Official 7-Zip 26.03 console binaries for Linux and macOS are now bundled (7zz, static builds on Linux and a universal binary on macOS) and copied next to the app for the matching runtime; 7-Zip-License.txt is included. Windows keeps 7za.exe.

Reliability fixes

  • CSO v2 / ZSO images decode correctly. The reader applied CSO v1 index semantics to version 2: LZ4 blocks (high bit set) were returned as raw compressed bytes and stored or deflate blocks were pushed through the LZ4 decoder. Blocks are now classified the way the format defines them — compressed only when smaller than a full block, with the high bit selecting LZ4 over deflate — and a short final stored block is zero-padded instead of failing.
  • A truncated CHD is never accepted as a success. When chdman exited nonzero but left a non-empty file (the classic disk-full case), the app treated it as success and could delete the source. The output is now validated with the built-in reader first, and a partial file is discarded.
  • Deleting originals never reaches outside the input folder. A descriptor naming ..\..\other\game.bin (or an absolute path) could make the batch delete files it never converted; referenced files outside the selected input folder are now kept and reported.
  • Archived discs keep their folder structure. Two same-named discs in different archive subfolders (Disc1/game.cue, Disc2/game.cue) both produced game.chd, and the second silently replaced the first; the archive's internal path is now preserved. The same-batch duplicate guard keeps the first product and reports any remaining collision.
  • Extracting next to an existing disc set no longer replaces it. The "extract into a subfolder instead of overwriting" rule now also covers cue/gdi sets (checking their BIN too) and applies to the chdman fallback, not just the built-in reader. Cancelling a fallback extraction now kills the chdman child instead of leaving it running.
  • A PBP whose volume descriptor declares zero sectors no longer extracts an empty BIN and reports success; every index entry is written when the size is unknown.
  • Expected network and user conditions no longer file bug reports: an update check that times out (now bounded to 30 seconds instead of the shared client's 100) and a CHD Explorer parse failure caused by choosing the wrong file-system parser are logged as network/user conditions instead of being sent to the bug tracker.
  • PBP I/O failures carry the underlying OS error (PBPSharp 1.1.3): an open or extraction that fails with IoError records the file, the OS message and whether it was a read or a write, so a locked file, a failing disk or a full drive is identifiable from the log and the bug report.
  • CD-R/CD-RW MDS v2 descriptors read their track lengths from the extra block like pressed CDs, and a .ccd claiming a huge TocEntries value can no longer make the parser allocate unbounded tracks.
  • MP3 decoding on Linux/macOS is cancellable: ffmpeg is awaited with the operation token and killed on cancel instead of blocking the window in an uncancellable WaitForExit().
  • Real .mdx containers are read: the MDS v2 parser applied the 1 MB v1 descriptor cap before checking the version, so every genuine single-file MDX (which embeds the whole image) was rejected, and the whole file was loaded into memory. The header is now read first, the descriptor is decrypted/decompressed from a stream, and track data is decoded per footer: the footer's track_data_length (the sectors actually stored) is used instead of the extra block's logical length, so a pregap kept in the data file is no longer truncated, and a track split across several data files decodes every footer in order. The decoder also streams groups instead of buffering whole tracks, and a compressed descriptor that legitimately expands beyond the file size is no longer rejected.
  • chdman createcd no longer receives an invalid -us 2352 switch. A cue referencing a .raw track made the Windows primary encoder exit with "Option '-us' not valid for this command" and silently fall back; chdman derives the unit size from the cue's track types, so the switch is now only passed to createraw — including when a .raw input is combined with Force CD or Force DVD, which previously still appended it to createcd/createdvd.
  • The built-in encoder rejects inputs chdman refuses. A createdvd/createhd/createraw input whose size is not a whole number of units is now rejected up front instead of dropping the partial trailing unit and reporting success (after which the source could be deleted).
  • Explorer extraction never replaces existing files: extracting an entry into a folder that already holds a file or folder of the same name now lands in a numbered subfolder, the same isolation the Extraction tab applies; a staged directory is renamed into place in one step when the destination is free, and a temp extraction opened in a viewer is deleted after the viewer exits instead of 30 seconds later.
  • The same-batch duplicate-output guard now covers the built-in encoder too, and a reservation is released when the final move fails, so a failed product cannot block a later input that resolves to the same CHD.
  • Temp folders no longer litter every drive. GetBestTempDirectory prefers the system temp folder whenever its path is safe for chdman and its volume has room, instead of always picking the roomiest drive and creating CHDStudio_Temp at its root; the drive-root fallback is only used when %TEMP% is unsafe or too small, and an emptied CHDStudio_Temp folder is removed after use and at startup (stale Explorer extractions older than a day are cleaned up as well).
  • The speed card shows chdman's throughput. The write-speed sampler measured the application's own process I/O, which stays near zero while the bundled chdman (a separate process) does the writing, so the card read 0.0 MB/s for every chdman conversion and looked stalled. It now samples the chdman child process, while the in-process CHDSharp encoder keeps using the app's counter.
  • The activity log keeps its mode instructions when the tab changes: the clear is now synchronous, so the welcome/ready lines queued by the same selection change are no longer discarded.
  • Completion dialogs are awaited: the batch summary is shown before a close requested during the run is honoured, and the encoder notice is dismissed before the update prompt appears.
  • Smaller fixes: a file-system-watcher history clear is atomic with event recording; a Windows drive root is no longer trimmed to a drive-relative path when building a temp folder; the ISZ chunk table and PBP SFO string length are bounded before their int casts; a CCD .img with a partial trailing sector is rejected instead of silently dropped; archive bin sets are grouped case-sensitively on case-sensitive file systems; a bare .bin with a companion cue now converts through the cue; 7-Zip arguments are passed via ArgumentList so quotes in paths cannot inject switches; the bug-report throttle's safety-net timer can no longer clear the flag of a newer send; a corrupt CSO index, MDX descriptor or ISZ chunk table is rejected before a huge allocation.
  • A laserdisc AVI that fails both encoders is no longer misdiagnosed: the post-failure sector-alignment diagnostic exempts .avi, so the real encoder error is shown instead of "file size is not divisible by any standard sector size".
  • A temp extraction opened in an external viewer is no longer deleted too early on Linux/macOS: xdg-open/open exit as soon as they hand the file to the viewer, so a quick exit now gets the fixed grace delay instead of removing the file while the viewer is still starting.
  • A CSO whose header declares zero uncompressed bytes no longer "extracts" to an empty ISO and reports success; it is reported as an invalid header. A temp directory is only accepted as writable when it really is, and a descriptor's referenced-file deletion check compares paths case-sensitively on case-sensitive file systems.
  • MDS v2 footers are read like the format reference: a track whose footer count is zero but that carries a footer is decoded from that footer instead of being treated as uncompressed, and a later fragment of a split track that names no data file is reported instead of silently reading the first data file again.
  • MDS v2 pregaps are resolved per track: the footer's stored length tells whether each track's pregap is in the data file (length = missing, length + pregap = stored, the layout libMirage validates). An image that keeps one track's pregap and omits another's is now rebuilt selectively — only the missing pregaps are materialized as zeros — so every INDEX 00/INDEX 01 lands at the LBA the descriptor names instead of the cue drifting after the first omitted pregap. The first track's pregap before LBA 0 is never materialized, matching libMirage's NULL pregap for track 1.

Housekeeping

  • Version bumps: application 3.9.0; libraries CCDSharp 1.0.1, CSOSharp 1.0.1, ISZSharp 1.0.2, MDSSharp 1.2.0 (MDS v2/MDX), PBPSharp 1.1.3; Meziantou.Analyzer 3.0.294.
  • Release script strips native .pdb debug symbols from the zip and verifies the Avalonia native libraries are present.
  • Docs, AGENTS.md and CI updated for the new base project; tests now reference the Avalonia assembly.
  • Test suite grew to 1116 tests (1088 unit + 28 integration), including new CSO v2 stored/LZ4/deflate, PBP zero-size, corrupt-CCD, MDX container, encoder divisibility, IoThroughputCounter, checksum-report, Image Info, CHDSharp-progress, MDS v2 per-track pregap and createld regression tests (with a committed laserdisc AVI fixture).
  • The docs/ folder is the single source of truth for both documentation homes: GitHub Pages (Jekyll/just-the-docs navigation) and the GitHub wiki (docs/_Sidebar.md side menu, index.md → Home.md, WhatsNew.md synced from the repository root).

Don't miss a new CHDStudio release

NewReleases is sending notifications on new releases.