#压缩 #任天堂 #N64 #Huffman编码 #编解码器 #电子阅读器

vpk0

一个用于处理任天堂N64时代vpk0数据压缩的Rust库

2个版本

0.8.2 2022年1月15日
0.8.1 2021年3月2日

#902 in 编码

MIT许可证

84KB
1.5K SLoC

Workflow Status

vpk0

一个用于处理任天堂N64时代vpk0数据压缩的Rust库

vpk0是一种基于LZSS并使用Huffman编码字典偏移量和匹配长度的数据压缩方案。它被HAL实验室用于其三款N64游戏,后来用于在GBA电子阅读器卡上编码数据。

这个包提供了对vpk0文件的解码和编码。由于这是一个旧的方案,解码和编码的设计旨在与任天堂在20世纪90年代的做法相匹配;这个包不专注于压缩比或速度。

使用方法

函数 [decode()] 和 [encode()] 提供了处理 vpk0 数据的快速方法。

use vpk0::{encode, decode};
use std::io::Cursor;

let raw = Cursor::new(data);
let compressed = encode(raw).unwrap();
let decompressed = decode(Cursor::new(&compressed)).unwrap();
assert_eq!(&data, &decompressed);

为了获得更多控制,您可以使用 DecoderEncoder

use vpk0::Encoder;

Encoder::for_bytes(b"ababacdcdeaba")
    .two_sample()
    .encode_to_writer(std::io::stdout())
    .unwrap();

vpk0 背景

vpk0 格式(以魔数字节命名)被认为是在20世纪90年代末由 HAL实验室 开发的。他们的三款N64游戏使用了这种压缩方案

  • 超级炸弹人兄弟
  • 精灵捕捉
  • Shigesato Itoi的No. 1 Bass Fishing: Definitive Edition

这个格式在2000年代中期再次出现,作为GBA电子阅读器的压缩方案。这是这个格式第一次受到互联网大众的关注。Tim Schuerewegen的 nevpk 和 Caitsith的 NVPK Tool and NEDEC Make 是来自电子阅读器的逆向工程的开源实现 vpk0

这个包扩展了他们的工作,为HAL的N64游戏提供匹配的压缩。

格式概述

vpk0 是基于两种基本的编码算法:LSZZ 和 Huffman 编码。这两种技术在 80 年代后期就在日本 BSSes 中流传,并且它们共同构成了许多现代编码方案的基础,例如 Deflate

vpk0 格式相对简单:它是一个可变长度的 LZSS。输入数据通过标准的 LZSS 实现进行压缩。但是,它没有固定长度的字典偏移和长度,而是可变的。可变长度的编码作为 Huffman 代码,并在编码数据前附加必要的 Huffman 树

更多信息,请参阅 格式模块的文档

实现细节

此实现旨在与用于 超级 Smash Bros. 的编码器完全匹配。截至 2021 年 3 月,LZSS 编码器是字节匹配的,但 Huffman 压缩不是。

匹配的 LZSS 编码方案是:在找到匹配后,查看下一个字节以确定是否存在更长的匹配。继续检查下一个字节,直到找到更短或没有匹配为止。

此包中的编码器最多检查下一个十个字节,因为这是在 SSB64 中匹配所有 500 个 vpk0 编码文件所必需的最大数量。将来,此参数可能成为 Encoder 的另一个选项。

高级用法

vpk0 文件中获取信息

use vpk0::vpk_info;
let (header, trees) = vpk_info(vpkfile).unwrap();
println!("Original size: {} bytes", header.size);
println!("VPK encoded with method {}", header.method);
println!("Offsets: {} || Lengths: {}", trees.offsets, trees.lengths);

像标准 LZSS 一样编码

use vpk0::{Encoder, LzssSettings};
// use fixed length compression by setting the offset to 10 and the length to 6.
let compressed = Encoder::for_bytes(b"I am Sam. Sam I am.")
    .one_sample()
    .with_lzss_settings(LzssSettings::new(10, 6, 2))
    .with_offsets("10")
    .with_lengths("6")
    .encode_to_vec();

许可:MIT

依赖关系

~440–680KB
~11K SLoC