szs 0.0.12

szs is a WIP crate for compressing and decompressing SZS formats used in the Nintendo GameCube and Wii games. The library provides C bindings, making it useful in both Rust and C/C++ based projects.
Documentation

szs

szs is a WIP crate for compressing and decompressing SZS formats used in the Nintendo GameCube and Wii games. The library provides C bindings, making it useful in both Rust and C/C++ based projects.

Warning: The library is not currently in a fully functional state. Use at your own risk. 0.1.0 will start the release series. 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

  • Boyer-moore-horspool (1:1 matching source files--relevant for decompilation projects)
  • SP
  • CTGP (1:1 matching)
  • Worst case
  • Haroohie (credit @Gericom)

Stats

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

Method Time Taken Compression Rate
worst-case-encoding 0s 112.50%
ctgp 0.31s 71.41%
Haroohie 0.58s 57.23%
mkw-sp 3.76s 57.23%
nintendo 5.93s 56.87%
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%

In most cases, the Haroohie algorithm gets the best compression the fastest, although wszst ultra is able to get the smallest filesizes if absolutely necessary.

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();

// Calculate the upper bound for encoding.
let max_len = encoded_upper_bound(src_data.len() as u32);

// Allocate a buffer based on the calculated upper bound.
let mut dst_data: Vec<u8> = vec![0; max_len as usize];

// Boyer-Moore-horspool variant
let algo_number: u32 = 0;

match encode_algo_fast(&mut dst_data, &src_data, algo_number) {
    Ok(encoded_len) => {
        println!("Encoded {} bytes", encoded_len);
        // Optionally: shrink the dst_data to the actual size.
        dst_data.truncate(encoded_len as usize);
    }
    Err(EncodeAlgoError::Error(err_msg)) => {
        println!("Encoding failed: {}", err_msg);
    }
}

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

#include `szs.h`

// Boyer-Moore-horspool variant
szs::Algo algorithm = szs::Algo::Nintendo;
auto encoded = szs::encode_algo(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.