Complete API documentation for ZArchiveSharp. All types are in the ZArchiveSharp namespace unless otherwise noted.
| Namespace | Contents |
|---|---|
ZArchiveSharp |
Archive reader/writer, format structures, tool |
ZArchiveSharp.Zstd |
zstd encoder/decoder, compression options |
ZArchiveSharp.Seekable |
Seekable zstd format (Foot + Head) |
ZArchiveSharp.Pipeline |
Pipeline engine, batch operations, progress |
Writes .zar archive files. Faithful port of zarchivewriter.cpp.
// Stream-based (recommended)
public ZArchiveWriter(Stream output, IZarBlockCompressor? compressor = null)
// Callback-based (for advanced scenarios)
public ZArchiveWriter(
Action<int> newOutputFile,
Action<byte[], int, int> writeOutputData,
IZarBlockCompressor? compressor = null)
public void StartNewFile(string path)
Begins writing a new file entry. Path is relative to the archive root, using / or \ as separators.
Parameters:
path — Relative file path (e.g., "readme.txt", "data/config.json")Exceptions:
InvalidOperationException — If already writing a fileArgumentException — If path is empty or invalidpublic void AppendData(ReadOnlySpan<byte> data)
public void AppendData(byte[] data, int offset, int count)
public void AppendData(Stream input)
Appends data to the current file. Data is buffered and compressed in 64 KiB blocks.
The Stream overload pumps the stream to end (64 KiB takes), so entry streams
need no manual buffering; all three forms produce identical bytes.
Parameters:
data — Bytes to appendExceptions:
InvalidOperationException — If no file is being writtenpublic void Finalize()
Writes the archive footer and SHA-256 integrity hash. Must be called after all files are written.
Exceptions:
InvalidOperationException — If already finalizedpublic void Dispose()
Releases resources. Automatically calls Finalize() if not already done.
using var output = File.Create("archive.zar");
using var writer = new ZArchiveWriter(output);
writer.StartNewFile("readme.txt");
writer.AppendData("Hello, World!"u8);
writer.StartNewFile("data/binary.dat");
writer.AppendData(binaryData);
writer.Finalize();
Reads .zar archive files. Faithful port of zarchivereader.h.
public static ZArchiveReader? TryOpen(string path)
public static ZArchiveReader? TryOpen(Stream stream, bool leaveOpen = false)
Opens an archive. Returns null on invalid archives (never throws).
Parameters:
path — Archive file pathstream — Archive streamleaveOpen — Keep stream open after reader disposalReturns: Reader instance, or null if invalid
| Property | Type | Description |
|---|---|---|
InvalidNode |
uint |
Constant 0xFFFFFFFF for path-not-found |
Dictionary |
ZstdDictionary? |
Dictionary for dictionary-packed archives (null = plain; inert for plain blocks; dictionary blocks read without it fail, never mis-decode) |
public bool FileExists(string path)
Checks if a file exists at the given path.
public bool DirectoryExists(string path)
Checks if a directory exists at the given path.
public byte[] ReadFile(string path)
Reads and decompresses an entire file.
Parameters:
path — File path within the archiveReturns: File contents
Exceptions:
FileNotFoundException — If file not foundInvalidOperationException — If archive is corruptpublic byte[] ReadFileRange(string path, long offset, long length)
Reads a range of bytes from a file.
Parameters:
path — File path within the archiveoffset — Byte offset within the filelength — Number of bytes to readReturns: Requested byte range
public IReadOnlyList<ZArchiveReader.DirEntry> ReadDirectory(string path)
Lists directory contents.
Parameters:
path — Directory path within the archiveReturns: List of directory entries
public string GetName(uint nameIndex)
Retrieves a name by its index in the name table.
public readonly struct DirEntry
{
public string Name { get; } // Entry name
public bool IsFile { get; } // True for files
public bool IsDirectory { get; } // True for directories
public ulong Size { get; } // File size (0 for directories)
}
The reader is thread-safe for concurrent reads (single lock, like the C++ mutex).
using var reader = ZArchiveReader.TryOpen("archive.zar");
if (reader == null)
{
Console.WriteLine("Invalid archive");
return;
}
// List root directory
foreach (var entry in reader.ReadDirectory("/"))
{
Console.WriteLine($"{entry.Name}: {(entry.IsFile ? $"{entry.Size} bytes" : "DIR")}");
}
// Read a file
byte[] data = reader.ReadFile("readme.txt");
High-level pack/extract operations. Port of main.cpp CLI behavior.
public static void Pack(
string inputDirectory,
string? outputFile = null,
Action<string>? progress = null,
IZarBlockCompressor? compressor = null,
bool deterministicOrder = true)
Packs a directory into a .zar file.
Parameters:
inputDirectory — Directory to pack (recursively)outputFile — Destination path, or null for <stem>.zarprogress — Optional per-file callback (relative path)compressor — Block compressor, or null for default (zstd level 6)deterministicOrder — true (default) sorts entries ordinallyExceptions:
IOException — On I/O errors or when refusing to overwriteInvalidOperationException — On archive structure errorspublic static void Extract(string inputFile, string outputDirectory)
Extracts an archive to a directory.
Parameters:
inputFile — Archive file pathoutputDirectory — Destination directory (created if needed)Exceptions:
IOException — On I/O errorsInvalidOperationException — On corrupt archivesOptions for ZarPipeline pack/extract work (also honored by the zar
CLI flags --level, --check, --dict).
| Property | Type | Default | Description |
|---|---|---|---|
Level |
int |
6 |
zstd level 1–22 for packing |
Checksum |
bool |
false |
Write per-block content checksums |
Dictionary |
ZstdDictionary? |
null |
Pack dictionary frames / extract them (never stored in the archive; inert for plain frames; ignored when Compressor is set) |
CollisionPolicy |
ZarCollisionPolicy |
Fail |
What to do when the output path already exists |
MaxDegreeOfParallelism |
int |
4 |
Batch parallelism |
DeterministicOrder |
bool |
true |
Sort entries ordinally before packing |
NameOrder |
IReadOnlyList<string>? |
null |
Pre-seeded name-table order; null = pack order (first appearance). Set to a source-walk (discovery) order for byte-parity with packers that write names in discovery order |
Interface for custom block compressors.
public interface IZarBlockCompressor
{
int Compress(ReadOnlySpan<byte> source, Span<byte> destination);
}
Returns: Compressed size, or -1 to store the block raw (uncompressed).
Built-in compressor that stores every block raw (no compression).
public sealed class ZarRawCompressor : IZarBlockCompressor
Options for the zstd compressor.
| Property | Type | Default | Description |
|---|---|---|---|
Level |
int |
6 |
Compression level (1–22) |
ChecksumFlag |
bool |
false |
Write 4-byte XXH64 content checksum |
Dictionary |
ZstdDictionary? |
null |
Dictionary history (null = plain frames) |
public static ZstdCompressionOptions FromLevel(int level)
Creates options for the specified level (1–22).
Pure-C# zstd encoder. Implements IZarBlockCompressor.
public ZstdCompressor(ZstdCompressionOptions? options = null)
| Property | Type | Description |
|---|---|---|
Options |
ZstdCompressionOptions |
Active options |
public int Compress(ReadOnlySpan<byte> source, Span<byte> destination)
Compresses source as a single-shot frame. Returns frame size, or -1 when the frame would not fit or would not be smaller.
public byte[] CompressBlock(ReadOnlySpan<byte> source)
Compresses and returns the frame as a new byte array.
public static int GetCompressBound(int sourceSize)
Returns the maximum possible compressed size for a given input size.
public static byte[] DecompressFrame(ReadOnlySpan<byte> src, int maxSize)
Decompresses a zstd frame.
Parameters:
src — Frame bytesmaxSize — Maximum allowed decompressed sizeReturns: Decompressed data
Write-only zstd compression stream. Buffers everything written and emits one
logical frame with an unknown-size header on Dispose() — byte-identical to
encoding the concatenated input in one shot. Flush() emits the 6-byte frame
header once payload exists. Not seekable; async is thin-over-sync.
public ZstdCompressionStream(Stream destination, int level = 6, bool checksum = false, bool leaveOpen = false)
public ZstdCompressionStream(Stream destination, ZstdCompressionOptions options, bool leaveOpen = false)
Exceptions:
ArgumentNullException — Destination or options is nullArgumentOutOfRangeException — Level outside 1–22ObjectDisposedException — Write after disposeNotSupportedException — Read/Seek/SetLength/Length/Positionusing var dest = File.Create("data.zst");
using (var enc = new ZstdCompressionStream(dest, level: 6, checksum: true))
{
await source.CopyToAsync(enc);
} // frame finalized here
---
## ZstdDecompressionStream (ZArchiveSharp.Zstd)
Read-only zstd decompression stream over concatenated frames (skippable frames
skipped). Decodes incrementally through the shared block path with
`ZstdDecoderOptions` caps enforced per frame. Not seekable; async is
thin-over-sync.
```csharp
public ZstdDecompressionStream(Stream compressed, bool leaveOpen = false)
public ZstdDecompressionStream(Stream compressed, ZstdDecoderOptions options, bool leaveOpen = false)
Exceptions:
ArgumentNullException — Source or options is nullZstdException — Corrupt/truncated input, cap exceeded, checksum mismatchNotSupportedException — Write/Seek/SetLength/Length/Positionusing var src = File.OpenRead("data.zst");
using var dec = new ZstdDecompressionStream(src);
using var outMs = new MemoryStream();
await dec.CopyToAsync(outMs);
Immutable, thread-safe reusable zstd dictionary (use only; training is out
of scope). A supplied dictionary is always active per frame — history plus,
for formatted dictionaries, initial tables; frames carrying a dictionary ID
require it to match DictId.
public static ZstdDictionary FromBytes(byte[] dict) // auto-detect formatted vs raw
public static ZstdDictionary FromRawPrefix(ReadOnlySpan<byte> prefix, uint dictId = 0)
| Property | Type | Description |
|---|---|---|
DictId |
int |
Dictionary ID (0 = no ID field; compared as a 32-bit pattern) |
IsFormatted |
bool |
True when loaded from magic-headed bytes |
ContentSize |
int |
History content size in bytes |
Dictionary-aware entry points (all additive; null = today’s behavior):
// ZstdCompressionOptions
public ZstdDictionary? Dictionary { get; init; }
// ZstdCompressor
public byte[] CompressBlock(ReadOnlySpan<byte> source, ZstdDictionary? dict);
// ZstdDecompressor
public static byte[] Decompress(byte[] src, ZstdDictionary? dict);
public static byte[] Decompress(byte[] src, int offset, int length, ZstdDictionary? dict);
public static byte[] Decompress(byte[] src, int offset, int length, ZstdDictionary? dict, ZstdDecoderOptions options);
// ZstdCompressionStream: options may carry Dictionary (header deferred to Dispose)
// ZstdDecompressionStream
public ZstdDecompressionStream(Stream compressed, ZstdDecoderOptions options, ZstdDictionary? dict, bool leaveOpen = false);
var dict = ZstdDictionary.FromBytes(File.ReadAllBytes("words.dict"));
var options = new ZstdCompressionOptions { Level = 6, Dictionary = dict };
byte[] frame = new ZstdCompressor(options).CompressBlock(data);
byte[] back = ZstdDecompressor.Decompress(frame, dict);
Callable form of the zar zstd contract (single zstd streams, not
archives). Failures map onto the ZarchiveCli exit-code table (no new
codes); cancellation propagates OperationCanceledException.
public static bool TryParse(string[] args, out ZstdJob? job, out string? error,
int defaultLevel = 6, string? defaultDictPath = null,
bool defaultChecksum = false, bool defaultQuiet = false, bool defaultStdout = false);
public static Task<int> RunAsync(ZstdJob job, Stream stdin, Stream stdout,
Action<string>? log, Action<string> error, CancellationToken ct = default);
TryParse takes the tokens after zstd (-c/--compress, -d/--decompress
with exactly one required, -l/--level, --dict, --stdout, --check /
--no-check, -q/--quiet, -h/--help) and never throws. RunAsync opens
file paths (null = the given stdin/stdout streams, flushed but never
closed), deletes a created file output when the run fails, and sends
failures to error.
Callable form of the zar seekable contract (seekable zstd files:
compress, decompress with byte/frame slicing, and seek-table listing).
Same conventions as ZstdCli (exit-code reuse, cancellation, partial-output
cleanup).
public static bool TryParse(string[] args, out SeekableJob? job, out string? error,
int defaultLevel = 3, bool? defaultChecksum = null,
bool defaultQuiet = false, bool defaultStdout = false);
public static Task<int> RunAsync(SeekableJob job, Stream stdin, Stream stdout,
Action<string>? log, Action<string> error, CancellationToken ct = default);
public static bool TryParseByteSize(string? value, out ulong size, out string? error);
TryParse takes the tokens after seekable, verb first (compress|c,
decompress|d, list|l): compress takes -l/--level (1–22, default 3),
-s/--frame-size (TryParseByteSize syntax, default 2M, capped at 1G),
--frame-size-policy, --checksum/--no-checksum (default on),
--seek-table-file; decompress takes --from/--to (end),
--from-frame/--to-frame (last), --seek-table-file; list takes
--from-frame/--to-frame/--num-frames, -d/--detail,
--seek-table-format foot|head. Compress/decompress share -f/--force,
-c/--stdout, -q/--quiet (ignored by list). The verb validates its own
options, so a misplaced flag errors instead of being ignored. Compress with a
file input and no output path derives <input>.zst; decompress defaults to
stdout.
Archive-container stage: finds an external 7z binary and extracts
.zip/.rar/.7z/.tar/.gz through ProcessRunner (ZarManager stage-1 port;
7z stays external by design).
public static string? FindTool(string? preferredPath = null,
IEnumerable<string>? searchDirectories = null, bool probeWellKnownLocations = true);
public static void Extract(string archivePath, string destDir, string? toolPath = null,
IProgress<double>? progress = null, PauseToken pause = default,
CancellationToken cancellationToken = default);
public static string? PickIsoCandidate(IEnumerable<string> extractedFiles);
FindTool checks the explicit path, then the standard Windows install
location, then searchDirectories (default: PATH) for 7z/7zz.
Extract runs x "archive" -o"dest" -y -bsp1 with full paths preserved
(the oracle’s flat e would mangle directory trees). PickIsoCandidate
returns the first .iso in ordinal order (extension case-insensitive) or
null — when several ISOs are present the rest are ignored, like the oracle.
Compression strategy selector. Maps to libzstd’s ZSTD_strategy.
| Value | Name | Typical Levels |
|---|---|---|
1 |
Fast |
1 |
2 |
DoubleFast |
2–3 |
3 |
Greedy |
4–5 |
4 |
Lazy |
6–7 |
5 |
Lazy2 |
8–9 |
6 |
BtLazy2 |
10–12 |
7 |
BtOpt |
13–15 |
8 |
BtUltra |
16–18 |
9 |
BtUltra2 |
19–22 |
Writes seekable zstd files with Foot/Head seek tables.
public SeekableWriter(SeekableOptions? options = null)
| Property | Type | Description |
|---|---|---|
SeekTable |
SeekTable |
Current seek table (frames logged so far) |
public void Write(ReadOnlySpan<byte> data)
public void Write(Stream input)
Appends data, emitting full frames as needed. The Stream overload pumps in
128 KiB takes, so regular files frame exactly like one span write and like the
oracle CLI; short-read streams can shift Compressed-policy boundaries (like
odd oracle reads would) while Uncompressed boundaries never move — every
framing decodes identically.
public byte[] Finish()
Finalizes and returns the complete seekable file bytes.
public byte[] FinishHead()
Returns just the seek table as a standalone Head frame.
Reads seekable zstd files.
// Parse embedded Foot table
public SeekableReader(byte[] data)
// Use external seek table (e.g., standalone Head)
public SeekableReader(byte[] data, SeekTable table)
// Stream-backed: parses the Foot from the tail, reads frames on demand
// (multi-GB files never sit fully in memory; stream not owned)
public SeekableReader(Stream stream)
public SeekableReader(Stream stream, SeekTable table)
The stream must be readable and seekable and stay open for the reader’s lifetime; decode results are identical to the byte-array constructors.
| Property | Type | Description |
|---|---|---|
Table |
SeekTable |
Parsed seek table |
DecompressedLength |
long |
Total decompressed size |
FrameCount |
int |
Number of frames |
public byte[] DecompressAll()
Decompresses the entire payload.
public byte[] DecompressRange(long offset, long length)
Decompresses a byte range, decoding only the frames the range touches.
| Exception | Namespace | When Thrown |
|---|---|---|
ZarArchiveOpenException |
ZArchiveSharp.Pipeline |
Archive fails to open |
ZarInputOpenException |
ZArchiveSharp.Pipeline |
Input file cannot be opened |
ZarEntryCreateException |
ZArchiveSharp.Pipeline |
Archive entry creation fails |
ZstdException |
ZArchiveSharp.Zstd |
zstd decompression error |
IOException |
System |
I/O errors |
InvalidOperationException |
System |
Invalid state (corrupt archive, etc.) |
try
{
ZArchiveTool.Extract("corrupt.zar", "output");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Corrupt archive: {ex.Message}");
}
catch (IOException ex)
{
Console.WriteLine($"I/O error: {ex.Message}");
}
// TryOpen never throws — returns null on invalid archives
using var reader = ZArchiveReader.TryOpen("maybe-valid.zar");
if (reader == null)
{
Console.WriteLine("Invalid or corrupt archive");
return;
}