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 |
| Area | Change |
|---|---|
SeekableReader.DecompressRange / DecompressFrames |
Range errors now throw ArgumentOutOfRangeException (was ZstdException); ranges larger than int.MaxValue are rejected before allocating |
ZArchiveReader.TryOpen(Stream, leaveOpen: false) |
Disposes the stream when the open fails (ownership transfers only on success) |
ZarPackEngine.PackEntries |
Returns the path actually written and takes an optional ZarCollisionPolicy |
ZarPackEngine |
New MoveIntoPlace, OutputExistsMessage, and MaxExtractDepth members |
ZstdDecoderOptions |
New MaxTotalOutputSize cumulative cap (default 1 GiB) for concatenated frames |
PauseTokenSource |
Now implements IDisposable; dispose after workers stop |
ZstdDecompressor.Decompress / DecompressExact |
Validate src/dst/options and both ranges up front |
| Extraction | Rejects unsafe entry names (zip-slip, device names), builds through scratch files, and caps nesting at MaxExtractDepth |
DirectoryPackSource |
Never descends directory symlinks/junctions (the link stays as an empty directory entry) |
Writes .zar archive files. Faithful port of zarchivewriter.cpp.
// Stream-based (recommended)
public ZArchiveWriter(
Stream output,
IZarBlockCompressor? compressor = null,
IEnumerable<string>? nameOrder = null,
int maxDegreeOfParallelism = 1,
Func<IZarBlockCompressor>? compressorFactory = null)
// Callback-based (for advanced scenarios)
public ZArchiveWriter(
Action<int> newOutputFile,
Action<byte[], int, int> writeOutputData,
IZarBlockCompressor? compressor = null,
IEnumerable<string>? nameOrder = null,
int maxDegreeOfParallelism = 1,
Func<IZarBlockCompressor>? compressorFactory = null)maxDegreeOfParallelism fans 64 KiB block compression across workers
(byte-identical, v1.1.0); it takes effect only with compressorFactory
(one compressor per worker — explicit IZarBlockCompressor instances
stay sequential).
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 invalid
public 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 append
Exceptions:
InvalidOperationException— If no file is being written
public void Finalize()Writes the archive footer and SHA-256 integrity hash. Must be called after all files are written.
Exceptions:
InvalidOperationException— If already finalized
public 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.
// File
public static ZArchiveReader? TryOpen(string path)
public static ZArchiveReader? TryOpen(string path, ZArchiveReaderOptions options)
public static ZArchiveReader? TryOpen(string path, out ZArchiveOpenFailure failure)
public static ZArchiveReader? TryOpen(string path, ZArchiveReaderOptions options, out ZArchiveOpenFailure failure)
// Seekable stream
public static ZArchiveReader? TryOpen(Stream stream, bool leaveOpen = false)
public static ZArchiveReader? TryOpen(Stream stream, bool leaveOpen, ZArchiveReaderOptions options)
public static ZArchiveReader? TryOpen(Stream stream, bool leaveOpen, out ZArchiveOpenFailure failure)
public static ZArchiveReader? TryOpen(Stream stream, bool leaveOpen, ZArchiveReaderOptions options,
out ZArchiveOpenFailure failure)
// Byte array
public static ZArchiveReader? TryOpen(byte[] data)
public static ZArchiveReader? TryOpen(byte[] data, ZArchiveReaderOptions options)
public static ZArchiveReader? TryOpen(byte[] data, out ZArchiveOpenFailure failure)
public static ZArchiveReader? TryOpen(byte[] data, ZArchiveReaderOptions options, out ZArchiveOpenFailure failure)Opens an archive. Returns null on invalid archives (never throws; a null
options argument is a programming error and throws ArgumentNullException).
Parameters:
path— Archive file pathstream— Seekable archive streamleaveOpen— Keep the stream open after a failed openoptions— Cache size, extended-name decoding, path share modefailure— ReceivesNoneon success, or aZArchiveOpenFailurereason:FileNotFound,AccessDenied,InvalidStream,ReadError,TooSmall,BadMagic,UnsupportedVersion,LengthMismatch,SectionOutOfRange,BadOffsetRecords,BadNameTable,BadFileTree,InvalidPath
Ownership: with leaveOpen: false (the default), a failed open disposes
the stream — ownership is only transferred to the returned reader on success.
Pass leaveOpen: true to keep the stream alive after a failed open.
| Property | Type | Description |
|---|---|---|
InvalidNode |
uint |
Constant 0xFFFFFFFF for path-not-found |
RootNode |
uint |
Node handle of the root directory (always 0) |
EntryCount |
uint |
File-tree entry count (files and directories), computed at open |
TotalUncompressedSize |
ulong |
Sum of every file's uncompressed size, computed at open (no archive I/O); saturates at ulong.MaxValue for crafted sizes that overflow |
Dictionary |
ZstdDictionary? |
Dictionary for dictionary-packed archives (null = plain; inert for plain blocks; dictionary blocks read without it fail, never mis-decode) |
public uint LookUp(string path, bool allowFile = true, bool allowDirectory = true)Resolves a path (case-insensitive, / or \ separators) to a node handle,
or InvalidNode. allowFile/allowDirectory are accepted for C++
compatibility and ignored.
public bool IsFile(uint node)
public bool IsDirectory(uint node)Reports the type of a node handle (false for invalid handles).
public uint GetDirEntryCount(uint node)
public bool GetDirEntry(uint node, uint index, out ZArchiveReader.DirEntry entry)
public bool TryGetDirEntry(uint node, uint index, out uint childNode, out ZArchiveReader.DirEntry entry)Enumerates a directory. GetDirEntryCount is clamped to the file-tree bounds,
so a crafted directory entry can never make callers iterate past the table.
TryGetDirEntry additionally returns the child's node handle, so mount and
extraction hosts can descend without rebuilding a path and looking it up again.
With the default name decoding (DecodeExtendedNames unset), an entry whose
stored name is ≥ 0x80 characters decodes to "": it is included in
GetDirEntryCount but GetDirEntry/TryGetDirEntry return false for it.
Open with DecodeExtendedNames = true to list and resolve such entries.
public bool TryGetNodeName(uint node, out string name)Returns the canonical (stored) name of a node handle, preserving the archive's
casing. The root's name is "". Returns false for out-of-range handles.
public ulong GetFileSize(uint node)File size in bytes (0 for directories and invalid handles).
public ulong ReadFromFile(uint node, ulong offset, Span<byte> buffer)
public byte[] ReadFile(uint node)ReadFromFile reads at most buffer.Length bytes, clamped to the file size.
A block failure mid-read returns the partial count (short read), never a
silent EOF. ReadFile reads the whole file into a new array and throws
IOException on a short read (or InvalidOperationException when the file
exceeds int.MaxValue).
public Stream OpenRead(uint node)
public Stream? TryOpenRead(string path)Opens a seekable, read-only Stream over a file's uncompressed contents,
reading through the block cache. OpenRead throws ArgumentException for an
invalid or directory handle; TryOpenRead returns null when the path is
missing or not a file. Disposing the stream does not dispose the archive.
public static string GetName(byte[] nameTable, uint nameOffset)
public static string GetName(byte[] nameTable, uint nameOffset, bool decodeExtendedLengths)
public static byte[]? GetNameRaw(byte[] nameTable, uint nameOffset, out int length)
public static byte[]? GetNameRaw(byte[] nameTable, uint nameOffset, out int length, bool decodeExtendedLengths)Decodes a name-table entry. The default preserves the 0.1.2 extended-length
quirk (names of ≥ 0x80 characters decode to ""); pass
decodeExtendedLengths: true for the corrected decode. A whole reader can opt
in through ZArchiveReaderOptions.DecodeExtendedNames.
public sealed class ZArchiveReaderOptions
{
public static ZArchiveReaderOptions Default { get; }
public int CacheBlockCount { get; init; } // default 64 (4 MiB); must be >= 1
public bool DecodeExtendedNames { get; init; } // default false (0.1.2 quirk)
public FileShare FileShare { get; init; } // default FileShare.Read
}public enum ZArchiveOpenFailure
{
None, FileNotFound, AccessDenied, InvalidStream, ReadError, TooSmall,
BadMagic, UnsupportedVersion, LengthMismatch, SectionOutOfRange,
BadOffsetRecords, BadNameTable, BadFileTree, InvalidPath,
}public readonly struct DirEntry
{
public string Name { get; } // Entry name (Windows-1252 decoded)
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. Cache bookkeeping and copies
are taken under a single lock; block decompression happens outside it, so
distinct blocks decode in parallel (tune the working set with
ZArchiveReaderOptions.CacheBlockCount).
using var reader = ZArchiveReader.TryOpen("archive.zar",
new ZArchiveReaderOptions { FileShare = FileShare.ReadWrite },
out var failure);
if (reader == null)
{
Console.WriteLine($"Invalid archive: {failure}");
return;
}
// Walk a directory with node handles (no path rebuilds).
for (uint i = 0; i < reader.GetDirEntryCount(ZArchiveReader.RootNode); i++)
{
if (!reader.TryGetDirEntry(ZArchiveReader.RootNode, i, out var node, out var entry))
{
continue;
}
Console.WriteLine($"{entry.Name}: {(entry.IsFile ? $"{entry.Size} bytes" : "DIR")}");
if (entry.IsFile)
{
using var stream = reader.OpenRead(node);
// stream.CopyTo(destination);
}
}
// Or resolve by path and stream the file.
using var readme = reader.TryOpenRead("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, ornullfor<stem>.zarprogress— Optional per-file callback (relative path)compressor— Block compressor, ornullfor default (zstd level 6)deterministicOrder—true(default) sorts entries ordinally
Exceptions:
IOException— On I/O errors or when refusing to overwriteInvalidOperationException— On archive structure errors
public 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 archives
Options 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) |
Compressor |
IZarBlockCompressor? |
null |
Explicit block compressor; null builds one from Level/Checksum/Dictionary (explicit instances keep the sequential path) |
CollisionPolicy |
ZarCollisionPolicy |
Fail |
What to do when the output path already exists |
MaxDegreeOfParallelism |
int |
4 |
Batch parallelism, plus the 64 KiB block fan-out inside a single pack/extract |
DeterministicOrder |
bool |
true |
Sort entries ordinally before packing |
DeleteSourceOnSuccess |
bool |
false |
Delete the pack source directory after a successful pack (single and batch) |
Pause |
PauseToken |
default | Pause gate checked alongside the cancellation token |
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 |
ZarPipeline.Pack validates and collects the source before resolving the
output, so an Overwrite policy never deletes a previous archive when the
pack cannot start; ZarPipeline.PackSource honors the collision policy and
creates the output directory.
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 : IZarBlockCompressorEngine behind ZarPipeline and the callable CLI runners: collision
resolution, pack, and the hardened extraction path.
public static class ZarPackEngine
{
// Prefix of the IOException thrown when the Fail policy refuses.
// Batch callers match it to separate -11 refusals from other faults.
public const string OutputExistsMessage = "The output file already exists:";
// Maximum accepted archive directory nesting during extraction.
public const int MaxExtractDepth = 1024;
public static string? ResolveOutputPath(string wantedPath, ZarCollisionPolicy policy);
public static string? MoveIntoPlace(
string source, string wantedPath, ZarCollisionPolicy policy, bool isDirectory);
public static string PackEntries(
IReadOnlyList<ZarPackEntry> entries, string displayPath, string zarPath,
ZarPipelineOptions? options = null, IProgress<ZarProgress>? progress = null,
CancellationToken cancellationToken = default,
ZarCollisionPolicy collisionPolicy = ZarCollisionPolicy.Fail);
public static IReadOnlyList<string> ExtractEntries(
string zarPath, string destDir, string? displayPath = null,
ZarPipelineOptions? options = null, IProgress<ZarProgress>? progress = null,
CancellationToken cancellationToken = default, Action<string>? log = null);
public static IReadOnlyList<string> ExtractOpen(
ZArchiveReader reader, string displayPath, string destDir,
ZarPipelineOptions? options = null, IProgress<ZarProgress>? progress = null,
CancellationToken cancellationToken = default, Action<string>? log = null);
}Collision resolution. ResolveOutputPath returns the path to use, or
null for Skip; Fail throws IOException prefixed with
OutputExistsMessage. MoveIntoPlace moves a staged file/directory into
place and re-resolves when the free name is claimed between resolve and move,
keeping AutoRename suffixes canonical (game, game_1, …).
PackEntries returns the path actually written (which can differ from
zarPath after an AutoRename race) and deletes its partial output on
failure.
Extraction safety. Entry names are validated (no .., separators,
rooted/drive-qualified paths, or Windows device names), the resolved path is
re-checked against the destination root, nesting deeper than
MaxExtractDepth fails catchably, and files are written through unique
.part scratch files and moved into place only after the size check.
Pause gate shared by pipeline workers. PauseToken is a snapshot struct; a
default value never pauses. PauseTokenSource implements IDisposable since
v1.2.0 — dispose it after the workers stop, while no PauseToken from it can
still be waited on.
public sealed class PauseTokenSource : IDisposable
{
public bool IsPaused { get; }
public PauseToken Token { get; }
public void Pause();
public void Resume();
public void Dispose();
}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. Since v1.2.0 the cap is enforced during decoding
(the decoder's MaxFrameContentSize is set to maxSize), so an oversized
frame is rejected before a large buffer is materialized. maxSize: 0 decodes
under a one-byte cap, so only an empty frame passes.
Parameters:
src— Frame bytesmaxSize— Maximum allowed decompressed size
Returns: Decompressed data
Stateless decoder entry points over concatenated frames, with optional dictionary and explicit resource limits.
public static class ZstdDecompressor
{
public static byte[] Decompress(byte[] src);
public static byte[] Decompress(byte[] src, ZstdDecoderOptions options);
public static byte[] Decompress(byte[] src, int offset, int length);
public static byte[] Decompress(byte[] src, int offset, int length, ZstdDecoderOptions options);
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);
public static void DecompressExact(
byte[] src, int srcOffset, int srcLength,
byte[] dst, int dstOffset, int dstLength); // + options / dict overloads
}Since v1.2.0 every overload validates src/dst/options and both ranges
up front (ArgumentNullException / ArgumentOutOfRangeException) and
enforces a cumulative output cap across concatenated frames (see
MaxTotalOutputSize). Corruption, dictionary mismatch, and cap violations
throw ZstdException.
Decoder resource limits (all configurable; defaults accept foreign frames up
to 512 MiB and cap one Decompress call at 1 GiB total):
| Property | Type | Default | Description |
|---|---|---|---|
MaxWindowSize |
ulong |
512 MiB | Reject frames declaring a larger window |
MaxFrameContentSize |
ulong |
512 MiB | Bound allocation for frames without a declared content size |
MaxTotalOutputSize |
ulong |
1 GiB | Cumulative output cap across all frames of one Decompress call; set to ulong.MaxValue to disable |
var options = new ZstdDecoderOptions { MaxTotalOutputSize = 64UL * 1024 * 1024 };
byte[] data = ZstdDecompressor.Decompress(frame, options);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 - except with a dictionary, where the header carries
the final content size and nothing is emitted before Dispose(). 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/Position
using 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/Position
using 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. A frame never exceeds the format's 1 GiB
uncompressed cap: the Compressed policy ends a frame when the compressed
threshold is reached or when the uncompressed cap is hit.
public byte[] Finish()Finalizes and returns the complete seekable file bytes.
public (byte[] Data, byte[] SeekTable) FinishHead()Returns the bare frame data plus the standalone Head seek table: Data is
the frames without an appended Foot, SeekTable is the serialized table.
Reads seekable zstd files.
// Parse embedded Foot table
public SeekableReader(byte[] data)
// Use external seek table (e.g., standalone Head; table must not be null)
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.
Exceptions:
ArgumentOutOfRangeException— negativeoffset/length, a range pastDecompressedLength, or a range larger thanint.MaxValue(it cannot be materialized as one array). Since v1.2.0 these areArgumentOutOfRangeException(previouslyZstdException).
public byte[] DecompressFrames(int first, int lastInclusive)Decompresses frames first through lastInclusive concatenated
(set_lower_frame / set_upper_frame).
Exceptions:
ArgumentOutOfRangeException— negative indices,lastInclusive < first, orlastInclusive >= FrameCount(since v1.2.0; previouslyZstdException).
| 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;
}