szs 0.2.9

Lightweight crate for SZS ("Yaz0") compression/decompression used in the Nintendo GameCube/Wii games. The library provides C, C++ and C# bindings. YAY0 ("SZP") is supported, too.
Documentation

crates.io docs.rs

szs

Lightweight crate for SZS ("Yaz0") compression/decompression used in the Nintendo GameCube/Wii games. The library provides C, C++ and C# bindings. YAY0 ("SZP") is supported, too.

Rust

The following snippet demonstrates how to compress a file as a SZS format using Rust:

// Sample source bytes to be encoded.
let src_data: Vec<u8> = "Hello, World!".as_bytes().to_vec();

match szs::encode(&src_data, szs::EncodeAlgo::Nintendo) {
    Ok(encoded_data) => {
        println!("Encoded into {} bytes", encoded_data.len());
    }
    Err(szs::EncodeAlgoError::Error(err_msg)) => {
        println!("Encoding failed: {}", err_msg);
    }
}

And similarly, to decompress:

match szs::decode(&encoded_data) {
    Ok(decoded_data) => {
        println!("Decoded {} bytes", decoded_data.len());
    }
    Err(szs::EncodeAlgoError::Error(err_msg)) => {
        println!("Decoding failed: {}", err_msg);
    }
}

C# Bindings

The following C# bindings are provided:

using System;

public class SZSExample
{
    public static void Main(string[] args)
    {
        byte[] data = ...; // Initialize your data here

        szs.CompressionAlgorithm algorithm = szs.CompressionAlgorithm.Nintendo;
        byte[] encodedData = szs.Encode(data, algorithm);

        if (encodedData == null || encodedData.Length == 0)
        {
            Console.WriteLine("Failed to compress.");
            return;
        }

        Console.WriteLine($"Encoded {encodedData.Length} bytes.");
    }
}
Warning: These algorithms are currently implemented in the C programming language, and not in Rust. While they have been rigorously validated, please use at your own risk. A Rust rewrite is planned.

Algorithms

Algorithm (rszst compress --algorithm form) Rust form C bindings Desc
nintendo EncodeAlgo::Nintendo RII_SZS_ENCODE_ALGO_NINTENDO Boyer-moore-horspool (Reverse engineered. 1:1 matching source files--relevant for decompilation projects)
mk8 EncodeAlgo::Mk8 RII_SZS_ENCODE_ALGO_MK8 MK8 compressor (Reverse engineered. Credit @aboood40091)
mkw-sp EncodeAlgo::MkwSp RII_SZS_ENCODE_ALGO_MKWSP MKW-SP
ctgp EncodeAlgo::CTGP RII_SZS_ENCODE_ALGO_CTGP CTGP (Reverse engineered. 1:1 matching)
worst-case-encoding EncodeAlgo::WorstCaseEncoding RII_SZS_ENCODE_ALGO_WORST_CASE_ENCODING Worst case
haroohie EncodeAlgo::Haroohie RII_SZS_ENCODE_ALGO_HAROOHIE Haroohie (credit @Gericom, adapted from MarioKartToolbox)
ct-lib EncodeAlgo::CTLib RII_SZS_ENCODE_ALGO_CTLIB CTLib (credit @narahiero, adapted from CTLib)
lib-yaz0 EncodeAlgo::LibYaz0 RII_SZS_ENCODE_ALGO_LIBYAZ0 libyaz0 (Based on wszst. credit @aboood40091)

Generally, the mk8 algorithm gets acceptable compression the fastest. For cases where filesize matters, lib-yaz0 ties wszst ultra for the smallest filesizes, while being ~25% faster.

Large file comparison

NSMBU 8-43 (63.9 MB decompressed)

Method Time (Avg 3 runs) Compression Rate File Size
lib-yaz0 25.97s 29.32% 18.74 MB
mkw 78.26s 29.40% 18.79 MB
mkw-sp 49.28s 29.74% 19.01 MB
haroohie 11.44s 29.74% 19.01 MB
ct-lib 5.32s 29.74% 19.01 MB
mk8 1.46s 30.12% 19.25 MB
ctgp 12.05s 40.91% 26.14 MB
worst-case-encoding 0.07s 112.50% 71.90 MB

* Average of 3 runs; x64 Clang (15, 16) build tested on an Intel i7-9750H on Windows 11

Generally, the mk8 algorithm gets acceptable compression the fastest. For cases where filesize matters, lib-yaz0 ties wszst ultra for the smallest filesizes, while being ~25% faster.

Small file comparison

Task: Compress N64 Bowser Castle (Source filesize: 2,574,368)

Method Time Taken Compression Rate
lib-yaz0 2.03s 56.65%
nintendo 5.93s 56.87%
mkw-sp 3.76s 57.23%
Haroohie 0.58s 57.23%
CTLib 0.32s 57.24%
MK8 0.09s 57.59%
ctgp 0.31s 71.41%
worst-case-encoding 0s 112.50%
Comparison with other libraries:
Haroohie (C#) 0.71s 57.23%
wszst (fast) 0.387s (via shell) 65.78%
wszst (standard) 1.776s (via shell) 57.23%
wszst (ultra) 2.727s (via shell) 56.65%
yaz0-rs 11.34s (via shell) 56.87%

* Average of 3 runs; x64 Clang (15, 16) build tested on an Intel i7-9750H on Windows 11

Example (C Bindings)

#include `szs.h`

// Calculate the upper bound for encoding.
u32 max_size = riiszs_encoded_upper_bound(sizeof(data));

// Allocate a buffer based on the calculated upper bound.
void* encoded_buf = malloc(max_size);
if (!buf) {
	fprintf(stderr, "Failed to allocate %u bytes.\n", max_size);
	return -1;
}

// Boyer-Moore-horspool variant
u32 algorithm = RII_SZS_ENCODE_ALGO_NINTENDO;

u32 actual_len = 0;
const char* ec = riiszs_encode_algo_fast(encoded_buf, max_size, data, sizeof(data), &actual_len, algorithm);
if (ec != NULL) {
	fprintf(stderr, "Failed to compress: %s\n", ec);
	riiszs_free_error_message(ec);
	return -1;
}
printf("Encoded %u bytes.\n", actual_len);
// Optionally: shrink the dst_data to the actual size.
encoded_buf = realloc(encoded_buf, actual_len);

C++ Wrapper on top of C Bindings

A CMake example is provided, too.

#include `szs.h`

// Boyer-Moore-horspool variant
szs::Algo algorithm = szs::Algo::Nintendo;
auto encoded = szs::encode(data, algorithm);
if (!encoded)
	std::println(stderr, "Failed to compress: {}.", encoded.error()); {
	return -1;
}
std::vector<u8> szs_data = *encoded;
std::println("Encoded {} bytes.", szs_data.size());

License

This library is published under the MIT license.