Skip to the content.

8. Utilities Reference

All classes live in BatchConvertToCHD/Utilities/ (and Models/ where noted).


8.1 PathUtils

internal static class PathUtils (PathUtils.cs:12)

Member Behavior
SanitizeFileName(name) Replaces invalid filename chars with _; collapses a trailing period to _; falls back to a GUID when the result is empty/all underscores.
GetSafeTempFileName(original, desiredExt, tempDir) Sanitized base name + desired extension (leading dot stripped), combined under tempDir.
GetSafeRelativePath(relativeTo, path) Path.GetRelativePath when both paths share a root; otherwise "." (same folder). Used to preserve the directory structure in outputs.
IsSameOrInsideDirectory(root, candidate) True when candidate is the same directory as root or nested inside it. Compares normalized full paths, so D:\Games, D:\Games\ and D:\Games\..\Games all match, and appends the separator before the prefix test so D:\Games2 is not read as being inside D:\Games. Never throws — a bad path returns false, because callers only use it to decide whether to log a note.
ReserveFreeSubdirectory(parentDirectory, baseName) Returns a path under parentDirectory named after a sanitized baseName that nothing currently occupies, stepping through Name (2), Name (3) … up to 999 and then falling back to a GUID suffix. Tests for both a directory and a file of that name, since an extension-less file would block the directory just as a folder would. Does not create the directory — the caller decides whether it is needed. Used by extraction to divert a disc that would otherwise overwrite existing files (see Extraction §6).
GetBestTempDirectory(inputFilePath, outputFolderPath, tempDirPrefix, requiredBytes) Selects the best temp root: candidates = input-file root, output-folder root, system temp root, and every ready fixed drive. Requires ≥ 1 GiB free; when requiredBytes > 0 prefers a drive with enough free space (most free among those); probes writability (create+delete a writetest_<guid> dir); falls back to system temp with an informational log. Base path is {root}\BatchConvertToCHD_Temp unless it equals the system temp root. Final: {base}\{tempDirPrefix}{guid}.
GetPossibleTempBasePaths() System temp plus every existing X:\BatchConvertToCHD_Temp on fixed drives — used by startup cleanup.
CreateTempDirectoryOnSameVolume(referencePath, tempDirPrefix) Creates and returns a temp directory on the same volume as referencePath, or null when none can be created there. Prefers the system temp directory when it already happens to be on that volume (no special permissions needed), otherwise {root}\BatchConvertToCHD_Temp — the same layout GetBestTempDirectory uses, so startup cleanup already finds it.
ValidateAndNormalizePath(path, pathName, onLog, onError) GetFullPath + existence check with friendly errors.

GetBestTempDirectory vs CreateTempDirectoryOnSameVolume. The first picks the roomiest drive, which is what you want when a whole disc image is about to be written. The second pins the directory to one volume, which is what a generated cue needs: chdman joins a cue’s FILE entry to the cue’s own directory and cannot follow an absolute path, so a cue on the wrong volume simply cannot reach its image (see Conversion Pipeline §5.8). Using the roomiest-drive helper for cue staging silently disabled the generated-cue feature whenever the source drive was not the emptiest drive.


8.2 CueNormalizer & CueWorkDirectory

CueNormalizer (CueNormalizer.cs)

internal static class CueNormalizer — produces a canonical, chdman-safe cue.

CueWorkDirectory (CueWorkDirectory.cs)

internal static class CueWorkDirectory — builds a self-contained ASCII work directory when the cue can’t be handed to chdman as-is.

Why this exists

chdman’s cue parser does not skip a UTF-8 BOM — the first token becomes "\uFEFFFILE" and chdman reports couldn't find bin file [] even when every bin exists. Non-UTF-8 text (Korean/Cyrillic), non-ASCII names/paths, and zero-padding name mismatches produce the same class of failure. Normalization + work directories eliminate all of them.


8.3 GameFileParser

internal static class GameFileParser (GameFileParser.cs:11)


8.4 BinCueGenerator

internal static class BinCueGenerator (BinCueGenerator.cs:13)

Generates cue files for bin-only archives (no descriptor in the archive).


8.5 IsoSectorValidator

internal static class IsoSectorValidator (IsoSectorValidator.cs:14)


8.6 Mp3ToWavDecoder & IMp3Decoder

internal sealed class Mp3ToWavDecoder : IMp3Decoder (Mp3ToWavDecoder.cs:16)


8.7 RetryingFileOperations

internal static class RetryingFileOperations (RetryingFileOperations.cs:10)

File operations that survive transient locks (antivirus, indexer, explorer):


8.8 FileExtensions

internal static class FileExtensions (FileExtensions.cs:11)

All constants are lowercase; every lookup is case-insensitive (StringComparer.OrdinalIgnoreCase).

Constant Value Constant Value
Cue .cue Zip .zip
Iso .iso SevenZip .7z
Img .img Rar .rar
Gdi .gdi Cso .cso
Toc .toc Pbp .pbp
Raw .raw Isz .isz
Ccd .ccd Bin .bin
Mds .mds Sub .sub
Ecm .ecm Chd .chd
SplitFirstNumbered .001 SplitFirstAlcohol .i00

Sets (with ...Set case-insensitive twins):

Notes:

An extension missing from AllSupportedInputExtensionsForConversion is invisible. This set gates the folder scan, so content-based handling for an unregistered extension can never run. .isz demonstrated the failure mode: the “genuinely compressed ISZ is not supported” message existed but was unreachable for actual .isz files, because they were never offered in the first place.


8.9 Content Identification

DiscImageSignature & DiscImageKind (DiscImageSignature.cs, DiscImageKind.cs)

internal static class DiscImageSignature — identifies what a file actually is from its leading bytes, regardless of its name.

RawCdImageDetector (RawCdImageDetector.cs)

Recognises raw 2352-byte CD sectors and stages a cue for them.

SplitImageJoiner (SplitImageJoiner.cs)

TrackBinCueBuilder (TrackBinCueBuilder.cs)

Pregaps cannot be recovered from file names. Each track is declared at the start of its own file, so an audio track whose pregap lived at the end of the previous track can start up to two seconds early. Nothing is lost, and the log states the assumption.

InputFileFilter (InputFileFilter.cs)


8.10 ISZ Support (Utilities/Isz/)

Decompresses UltraISO .isz images. Written against EZB Systems’ ISZ File Format Specification 1.00.

Type Role
IszHeader The packed 48-byte header, every field read at its documented offset. ImageSizeBytes (= TotalSectors × SectorSize, computed in 64-bit so dual-layer DVDs don’t overflow), IsEncrypted, IsSegmented, EncryptionDescription, Summary, and GetUnusableReason() which refuses encryption, a zero-sector or zero-chunk header, an implausible chunk size, an unreadable pointer width and a missing chunk table.
IszChunkType The four storage kinds: Zero, Stored, ZLib, BZip2 (spec names ADI_ZERO, ADI_DATA, ADI_ZLIB, ADI_BZ2).
IszSegment One segment-table entry: size, chunk count, first chunk number, chunk offset, left-over bytes. IsTerminator marks the zero-size entry that ends the table.
IszDecoder TryReadHeaderAsync, GetDecodedFileName, GetSegmentPath, ReadChunkEntry and DecodeAsync.
IszDecodeResult Success, OutputPath, SectorSize, FailureReason.

Behaviour worth knowing:


8.11 ECM Support (Utilities/Ecm/)

Decodes ECM (Error Code Modeler) files in-process — no external tool.

Type Role
CdSectorEccEdc Regenerates a raw sector’s error detection and correction fields: ComputeEdc, WriteSyncAndMode, GenerateMode1, GenerateMode2Form1, GenerateMode2Form2. SectorSize = 2352, Mode2DataSize = 2336.
EcmImageDecoder Signature ("ECM\0"), GetDecodedFileName, DecodeAsync. Parses the block stream and writes the restored image.
EcmDecodeResult Success, OutputPath, BytesWritten, FailureReason.

The format: after the 4-byte signature comes a sequence of blocks, each introduced by a variable-length number whose low two bits give the kind — literal bytes, Mode 1, Mode 2 Form 1, Mode 2 Form 2 — and whose remaining bits give the count. A 4-byte checksum of the whole restored image closes the file.

Why this is safe to hand-write. Getting Reed-Solomon parity subtly wrong produces data that reads correctly while the image’s hash never matches a known-good dump — which is why an earlier version drove Neill Corlett’s external UNECM binary instead. The decoder is now verified byte for byte against that tool using a committed fixture the tool itself produced; see Testing.


8.12 Alcohol 120% Support (Utilities/Mds/)

Type Role
MdsParser Parses the .mds descriptor: signature, session table, track table; locates the .mdf; rejects descriptors whose session count is implausible.
MdsDisc The parsed model. RawSectorSize = 2352, RawPlusSubchannelSize = 2448, CookedSectorSize = 2048; IsDvdImage, IsPlainRawCd, NeedsSubchannelStrip, AllTracksDescribable.
MdsTrack One track: number, mode, sector size, start LBA, and CueTrackType (null when the mode cannot be expressed in a cue).
MdsInputPreparer PrepareAsyncResult(CuePath, DvdImagePath, FailureReason), plus StripSubchannelAsync, WriteCueAsync and FormatMsf.

The three shapes it handles, and the reasoning, are in Conversion Pipeline §5.7.

The .mds layout is not published; it was recovered by inspection and is documented here so the parser can be re-derived: signature MEDIA DESCRIPTOR at 0x00; u16 session count at 0x14; u32 session-block offset at 0x50; session blocks are 24 bytes with the track count at +0x0A and a u32 track-block offset at +0x14; track blocks are 80 bytes with the mode at +0x00, POINT at +0x04, a u16 sector size at +0x10 and a u32 start LBA at +0x24. Mode bytes: 0xA9 audio, 0xAA Mode 1, 0xEC Mode 2, 0xE2 Mode 2 Form 1, 0xE3 Mode 2 Form 2. A POINT outside 1–99 is lead-in or lead-out.


8.13 Models

FileItem (Models/FileItem.cs)

Bindable row for the file list DataGrids: FileName (relative path when searching subfolders), FullPath, FileSize (long), IsSelected (INotifyPropertyChanged). DisplaySize formats bytes with binary units B/KB/MB/GB/TB ({size:0.##} {suffix}).

PbpExtractionResult (Models/PbpExtractionResult.cs)

Success, CueFilePaths (list), OutputFolder, ErrorCode (PbpError? — distinguishes “not a PlayStation disc image” from real failures), Error (human-readable failure description preserved from PBPSharp).