Pure-C# port of the ZArchive 0.1.2 library — directory-tree archives with per-block zstd compression. Zero native dependencies, BCL only; trimmable and AOT-compatible (net8.0 / net9.0 / net10.0).
- Byte-identical output to the original C++
zarchive.exeand libzstd 1.5.7 - Full RFC 8878 zstd encoder & decoder (levels 1–22, all 9 strategies) — no native dependencies
- Streaming API —
ZstdCompressionStream/ZstdDecompressionStream(chunk-size free, 10 MiB round-trips tested) - Dictionary use — compress/decompress with supplied dicts (formatted + raw prefix);
ZstdDictionary, per-file 4× smaller small entries (training out of scope) - At native speed on the hot path — L6 64 KiB ≈1.0× libzstd 1.5.7, decode ≈1.0× (measured; see Benchmarks)
- Seekable zstd format (Foot + Head) — zeekstd-compatible framing
- Pipeline engine — parallel batch pack/extract with progress, pause, cancellation & collision policies, plus the 7z archive-container stage (
.zip/.7z/.rar→ ISO/dir →.zarin onezar --batchrun) - Name-table order control —
ZarPipelineOptions.NameOrderpre-seeds the writer so archives can match discovery-order packers byte-for-byte - CLI tool —
zarcommand matchingzarchive.exeexit codes and behavior - Trimmable & AOT-compatible — works with Native AOT deployment
- Zero runtime dependencies — BCL only, no
unsafecode in the zstd path
dotnet add package ZArchiveSharpusing ZArchiveSharp;
// Pack a directory (each 64 KiB block zstd level 6 by default)
ZArchiveTool.Pack(@"C:\game", @"C:\game.zar");
// Extract it back
ZArchiveTool.Extract(@"C:\game.zar", @"C:\game_out");using ZArchiveSharp;
using ZArchiveSharp.Zstd;
// Write an archive
using var output = File.Create("game.zar");
using var writer = new ZArchiveWriter(output); // default: ZstdCompressor level 6
writer.StartNewFile("readme.txt");
writer.AppendData("hello"u8);
writer.Finalize();
// Read it back
using var reader = ZArchiveReader.TryOpen("game.zar");using ZArchiveSharp.Zstd;
// Compress (byte-identical to libzstd)
var compressor = new ZstdCompressor(ZstdCompressionOptions.FromLevel(6));
byte[] frame = compressor.CompressBlock(data); // single-shot, any size
// Decompress
byte[] back = ZstdCompressor.DecompressFrame(frame, maxSize: data.Length);using ZArchiveSharp.Zstd;
// Stream a file through zstd (any chunk size; flushed, never closed)
using var input = File.OpenRead("big.bin");
using var output = File.Create("big.zst");
using var enc = new ZstdCompressionStream(output, level: 6);
input.CopyTo(enc);
// Compress small entries with a dictionary (4× smaller here)
var dict = ZstdDictionary.FromRawPrefix(prefixBytes);
var opts = new ZstdCompressionOptions { Level = 6, Dictionary = dict };
byte[] small = new ZstdCompressor(opts).CompressBlock(entry);
byte[] orig = ZstdDecompressor.Decompress(small, dict);| Subsystem | Key types | One line |
|---|---|---|
| Container | ZArchiveWriter, ZArchiveReader, ZArchiveTool |
Directory-tree .zar archives; Pack/Extract one-liners |
| zstd codec | ZstdCompressor, ZstdDecompressor, ZstdCompressionOptions, ZstdDecoderOptions |
Levels 1–22, byte-identical to libzstd 1.5.7 |
| Streams & dicts | ZstdCompressionStream, ZstdDecompressionStream, ZstdDictionary |
Stream wrappers; formatted + raw-prefix dictionary use |
| Seekable | SeekableWriter, SeekableReader, SeekTable, SeekableOptions |
Foot + Head seek tables, subrange decode |
| Pipeline | ZarPipeline, ZarPackEngine, ZarPipelineOptions, ZarBatchRequest, ProcessRunner, SevenZip |
Parallel batches, 7z container stage, collision policies |
| CLI runners | ZarchiveCli, ZstdCli, SeekableCli |
Callable forms of every zar command (same exit codes) |
Full signatures: API Reference.
# Install as a global tool
dotnet tool install -g ZArchiveSharp.Cli
# Pack a directory
zar <directory> [output.zar]
# Extract an archive
zar <archive.zar> [output_dir]
# Convert XISO to .zar (Redump ISOs auto-detected: packs the game partition)
zar --iso <game.iso> [output.zar]
# Raw zstd files (stdin/stdout by default, pipes compose)
zar zstd --compress big.bin big.zst
zar zstd --decompress big.zst big.bin
zar zstd -c big.bin | zar zstd -d > big.bin
# Archives with dictionaries + checksums
zar --dict words.dict --check <directory> [output.zar]
# Seekable zstd files (zeekstd-compatible framing + slicing)
zar seekable compress big.bin big.zst
zar seekable list big.zst
zar seekable decompress --from 1M --to 2M big.zst slice.bin
# Batch: archives through 7z to ISO/dir to .zar, ISOs straight to .zar
zar --batch C:\games C:\archives
zar --batch --mode extract-archive C:\games C:\unpacked
zar --batch --seven-zip "D:\tools\7z.exe" C:\games C:\archives| Project | Description |
|---|---|
| ZArchiveSharp | Core library — archive reader/writer, zstd codec, seekable format, pipeline |
| ZArchiveSharp.Cli | Command-line tool (zar) — pack, extract, convert, batch operations |
| ZArchiveSharp.Benchmarks | BenchmarkDotNet performance suite |
| ZArchiveSharp.Tests | Comprehensive test suite (4171 tests, parity validation) |
- Getting Started — Installation, setup, and first steps
- API Reference — Complete library API documentation
- CLI Reference — Command-line tool usage and options
- Zstd Compression — Compression levels, strategies, and tuning
- ZAR Format — Archive format specification and internals
- Seekable Format — Seekable zstd framing (Foot + Head)
- Pipeline — Batch operations, progress, and collision handling
- Benchmarks — Performance characteristics and tuning
- FAQ — Frequently asked questions
The encoder, archive container and seekable framing are byte-identical to the frozen references (libzstd 1.5.7, zeekstd). The test suite proves it against native tools on thousands of vectors, and ZArchiveSharp.Tests/Goldens/ pins native bytes so CI holds the line with no toolchain installed.
Two known boundaries:
- The shipped
zarchive.exebundles libzstd 1.5.2, whose level 6 can differ from 1.5.7 on multi-transition hetero 64 KiB blocks. Our frames follow the frozen 1.5.7. - The reference C seekable library writes plain zstd frames while zeekstd sets the frame content-checksum flag. Both flavors are valid; our reader decodes both, our writer emits the zeekstd flavor.
- No dictionary training (use only), no legacy frames, no multithreading inside one frame
- Decoder caps (configurable): 512 MiB window, 512 MiB frame content
- Corrupt archives throw documented exceptions; truncations always fail the open
- .NET 8.0, 9.0, or 10.0
- No native dependencies
MIT — see LICENSE.
Contributions are welcome! Please see the issue tracker for known issues and feature requests.