| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
parent directory.. | ||||
ISZSharp is a managed C# library for decompressing UltraISO ISZ disc images back to the plain images they were made from. It reads whole images and split sets, all four chunk storage kinds (zero-elided, stored, zlib, bzip2), the obfuscated tables and stripped bzip2 headers real UltraISO files carry, and validates UltraISO's own checksum when the file provides one.
The library is the ISZ decompression engine used by CHD Studio, where it restores an ISZ to a plain image before converting it to CHD with chdman or CHDSharp.
| Requirement | Value |
|---|---|
| Target frameworks | net8.0, net9.0, net10.0 |
| Runtime | .NET 8, .NET 9 or .NET 10 |
| Platforms | Windows, Linux, macOS (pure managed code) |
| Dependencies | SharpCompress 0.50.4 (bzip2) |
The package contains a separate assembly for each target framework, so the correct build is selected automatically by NuGet.
Read-only: ISZSharp can open, inspect and decompress ISZ files. It cannot create or modify them, and it cannot decrypt an encrypted image — UltraISO itself has to save one as a plain ISO first.
dotnet add package ISZSharpor with the Package Manager console:
Install-Package ISZSharpusing ISZSharp;
var iszPath = @"C:\Games\Breath of Fire IV.isz";
var isoPath = @"C:\Games\Breath of Fire IV.iso";
var header = await IszDecoder.TryReadHeaderAsync(iszPath, CancellationToken.None);
if (header is null)
{
Console.Error.WriteLine("Not an ISZ image.");
return;
}
var unusable = header.GetUnusableReason();
if (unusable is not null)
{
Console.Error.WriteLine($"Cannot decode it: {unusable}");
return;
}
var result = await IszDecoder.DecodeAsync(
iszPath,
isoPath,
message => Console.WriteLine(message),
CancellationToken.None
);
Console.WriteLine(result.Success ? $"Wrote {result.OutputPath}" : $"Failed: {result.FailureReason}");TryReadHeaderAsync reads only the first 64 bytes, so a file can be checked before any space is committed to the restored image. It returns null when the file does not start with an ISZ header; I/O failures (for example a missing or unreadable file) are thrown.
using ISZSharp;
var header = await IszDecoder.TryReadHeaderAsync(iszPath, CancellationToken.None);
if (header is not null)
{
Console.WriteLine($"Version: {header.Version}");
Console.WriteLine($"Sector size: {header.SectorSize} bytes ({header.TotalSectors:N0} sectors)");
Console.WriteLine($"Restored size: {header.ImageSizeBytes:N0} bytes");
Console.WriteLine($"Chunks: {header.ChunkCount:N0} x {header.ChunkSize:N0} bytes");
Console.WriteLine($"Pointer width: {header.PointerLength} bytes");
Console.WriteLine($"Split: {header.IsSegmented}");
Console.WriteLine($"Encrypted: {header.IsEncrypted} ({header.EncryptionDescription})");
Console.WriteLine($"UltraISO checksum: {(header.HasChecksums ? "present" : "absent")}");
// What the decoder would say about it, or null when it can be decoded.
Console.WriteLine($"Usable: {header.GetUnusableReason() ?? "yes"}");
}The header's offsets are absolute file offsets into the first segment, so ChunkTableOffset, SegmentTableOffset and DataOffset can be inspected directly.
DecodeAsync writes the restored image and reports progress through the log callback roughly every 10%, plus notes about the header and any split. Cancellation is checked between chunks; a cancelled or failed decode deletes the partial output before returning or throwing.
using ISZSharp;
var result = await IszDecoder.DecodeAsync(
iszPath,
isoPath,
message => Console.WriteLine(message),
cancellationToken
);
if (result.Success)
{
// result.SectorSize tells you how the restored image should be classified:
// 2048 is a plain ISO/DVD image, 2352 is a raw CD image, and so on.
Console.WriteLine($"Restored {result.OutputPath} ({result.SectorSize}-byte sectors).");
}
else
{
Console.Error.WriteLine(result.FailureReason);
}GetSegmentPath returns the file that holds segment n (the .isz itself is segment 0), following whichever naming scheme the first segment uses. GetDecodedFileName gives the restored image's name.
using ISZSharp;
foreach (var path in new[] { @"D:\roms\Game.isz", @"D:\roms\Game.part001.isz" })
{
for (var segment = 0; segment < 3; segment++)
Console.WriteLine(IszDecoder.GetSegmentPath(path, segment));
}
// D:\roms\Game.isz
// D:\roms\Game.i01
// D:\roms\Game.i02
// D:\roms\Game.part001.isz
// D:\roms\Game.part002.isz
// D:\roms\Game.part003.isz
Console.WriteLine(IszDecoder.GetDecodedFileName(@"D:\roms\Game.part001.isz")); // Game.part001.isoSegments must sit in the same folder as the first file. Missing segments are named in the failure reason, and a segment whose volume serial number does not match the first file's is refused rather than spliced in.
DecodeAsync never throws for file-content problems; it returns an IszDecodeResult whose FailureReason is written for the end user (it says what to do about the problem, not just what went wrong). An OperationCanceledException still propagates when the token is cancelled.
| Situation | FailureReason says |
|---|---|
| Not an ISZ | the file does not start with an ISZ header |
| Encrypted | the image is encrypted (AES-256) and this tool cannot decrypt it |
| Later segment opened directly | this is segment N of a split image, not the first one |
| Missing segment | the image is split across N segments and <name>.i01 is not in the same folder |
| Foreign segment | segment <name>.i01 belongs to a different ISZ image (volume serial number does not match) |
| Truncated file or short segment | the ISZ decompressed to N bytes but its header declares M |
| Checksum mismatch | the restored image does not match the checksum the ISZ header declares |
| Damaged compressed data | the compressed data inside the ISZ is damaged |
| Corrupt chunk table | the chunk table size does not fit the file, so it is corrupt or truncated |
| Short non-final chunk | chunk N decompressed to fewer bytes than the chunk size the header declares |
| Unknown version | the ISZ header declares format version N, and only version 1 is understood |
A decode that fails after writing has started deletes the partial image before returning, so a failed DecodeAsync never leaves a short image behind.
All types live in the ISZSharp namespace.
The entry point. A static class, because an ISZ is decoded in one pass to a file.
| Member | Description |
|---|---|
| static Task<IszHeader?> TryReadHeaderAsync(string path, CancellationToken token) | Reads and parses the header, or returns null when the file exists but is not an ISZ image. IOException and UnauthorizedAccessException propagate for missing or unreadable files. |
| static Task<IszDecodeResult> DecodeAsync(string iszPath, string destinationPath, Action<string> onLog, CancellationToken token) | Decompresses the image (whole or split) to destinationPath, reporting progress through onLog. |
| static string GetDecodedFileName(string iszPath) | The name the restored image should be given: the stem plus .iso. |
| static string GetSegmentPath(string firstSegmentPath, int segmentIndex) | Path of the given segment, following the first file's naming scheme. |
| static (IszChunkType Type, int StoredLength) ReadChunkEntry(byte[] chunkTable, int index, int pointerLength) | Decodes one (already de-obfuscated) chunk table entry; exposed for testing the bit-packing. |
DecodeAsync reads the whole chunk table up front but streams the chunk data, so memory use is bounded by the chunk size, not by the image size.
The parsed header of the first segment. A read-only record.
| Member | Description |
|---|---|
| const int Length / const int ExtendedLength | 48 (the specification's header) and 64 (with UltraISO's checksum fields). |
| const string Signature | "IsZ!". |
| int HeaderSize, int Version, uint VolumeSerialNumber | Header shape and the serial that ties segments together. |
| int SectorSize, uint TotalSectors | The stored image's sector geometry. |
| int PasswordMode | 0 none, 1 password, 2–4 AES-128/192/256. |
| long SegmentSize, uint ChunkCount, uint ChunkSize, int PointerLength | Splitting and chunk-table layout. |
| int SegmentNumber, uint ChunkTableOffset, uint SegmentTableOffset, uint DataOffset | This segment's identity and the absolute offsets of its tables and data. |
| uint? UncompressedCrc, uint? DataSize, uint? StoredCrc | The 64-byte header's checksum fields, null for a 48-byte header. |
| long ImageSizeBytes | TotalSectors × SectorSize, computed in 64-bit. |
| bool IsEncrypted, bool IsSegmented, bool HasChecksums | Header classification. |
| string EncryptionDescription, string Summary | Human-readable descriptions for logs. |
| static bool HasSignature(ReadOnlySpan<byte> header) | True when the bytes open with IsZ!. |
| static IszHeader? TryRead(ReadOnlySpan<byte> header) | Parses a header from at least 48 bytes; reads the checksums when 64 bytes and a 64-byte header size are present. |
| string? GetUnusableReason() | Why the image cannot be decoded, phrased for the user, or null when it can. |
How one chunk is stored, from the top two bits of its table entry:
| Value | Spec name | Meaning |
|---|---|---|
| Zero | ADI_ZERO | The chunk is all zeros and stores no bytes; the entry records its uncompressed length. |
| Stored | ADI_DATA | Stored verbatim. |
| ZLib | ADI_ZLIB | Deflate inside a zlib wrapper. |
| BZip2 | ADI_BZ2 | bzip2, stored without the BZh header. |
One entry of a split image's segment table: Size, ChunkCount, FirstChunkNumber, ChunkOffset, LeftSize, and IsTerminator for the zero-size entry that ends the table.
| Member | Description |
|---|---|
| bool Success | True when OutputPath holds the complete image. |
| string? OutputPath | The written image, or null on failure. |
| int SectorSize | Sector size the header declared, for classifying the restored image. |
| string? FailureReason | User-facing explanation, or null on success. |
ISZ files from UltraISO and its imitators vary in ways the published specification does not describe; the behaviours below come from comparing the two independent open-source readers, libMirage's ISZ filter and isz-tool, both of which were checked against real files.
An ISZ file starts with a 48-byte header, which UltraISO extends to 64 bytes:
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | Signature IsZ! |
| 4 | 1 | Header size (48 or 64) |
| 5 | 1 | Version (1) |
| 6 | 4 | Volume serial number |
| 10 | 2 | Sector size |
| 12 | 4 | Total sectors |
| 16 | 1 | Encryption mode |
| 17 | 8 | Segment size |
| 25 | 4 | Chunk count |
| 29 | 4 | Chunk size |
| 33 | 1 | Chunk pointer width |
| 34 | 1 | Segment number (first = 0) |
| 35 | 4 | Chunk table offset (0 = none) |
| 39 | 4 | Segment table offset (0 = whole file) |
| 43 | 4 | Data offset |
| 47 | 1 | Reserved |
| 48 | 4 | CRC32 of the restored image (64-byte header only) |
| 52 | 4 | Data size (64-byte header only) |
| 56 | 4 | Reserved (64-byte header only) |
| 60 | 4 | CRC32 of the stored data (64-byte header only) |
A split image follows with a segment table of 24-byte entries (size, chunk count, first chunk, chunk offset, left-over bytes), terminated by a zero-size entry. Then comes the chunk table: one little-endian entry per chunk whose top two bits are the storage kind and whose remaining bits are the stored length. The rest of the file is chunk data, stored back to back; a chunk may straddle a segment boundary, which is why ISZSharp reads all segments as one stream. A file with no chunk table has no table at all and its data begins straight after the header.
Decoding walks the chunk table once, decompresses each chunk, and writes the image capped at TotalSectors × SectorSize, so a writer that padded its final chunk cannot lengthen the image. The bytes written are counted and checked against the declared size, and fed to the CRC32 when the header carries one; either mismatch deletes the output and reports the file as truncated or damaged.
The library lives in the CHD Studio repository under ISZSharp/.
git clone https://github.com/purelogiccode/CHDStudio.git
cd CHDStudio
dotnet build ISZSharp/ISZSharp.csproj -c Release
dotnet pack ISZSharp/ISZSharp.csproj -c Release -o artifactsThe test suite for the library lives in CHDStudio.Tests/:
dotnet test CHDStudio.Tests/CHDStudio.Tests.csproj -c Release --filter "FullyQualifiedName~Isz"ISZSharp is released under the MIT license.
| Back | FazBrowse Home | New Git URL |