From 2cef65b051b26f84a3cab2c492efec94d925992b Mon Sep 17 00:00:00 2001 From: Ethan Roseman Date: Tue, 5 Dec 2023 01:31:57 +0900 Subject: [PATCH] cod --- .github/workflows/release.yml | 37 ++++++ .github/workflows/rust.yml | 38 ++++++ .gitignore | 1 + .vscode/settings.json | 3 + Cargo.lock | 244 ++++++++++++++++++++++++++++++++++ Cargo.toml | 11 ++ src/lib.rs | 37 ++++++ src/main.rs | 67 ++++++++++ src/yay0.rs | 64 +++++++++ 9 files changed, 502 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/rust.yml create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/yay0.rs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e07e56a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,37 @@ +on: + release: + types: [created] +jobs: + release: + name: release ${{ matrix.target }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-pc-windows-gnu + archive: zip + - target: x86_64-unknown-linux-musl + archive: tar.gz + - target: x86_64-apple-darwin + archive: zip + steps: + - uses: actions/checkout@master + - name: Compile and release + uses: rust-build/rust-build.action@v1.4.4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + RUSTTARGET: ${{ matrix.target }} + ARCHIVE_TYPES: ${{ matrix.archive }} + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: katyo/publish-crates@v2 + with: + registry-token: ${{ secrets.CRATE_AUTH_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..4969144 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,38 @@ +name: Rust + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + rustfmt-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Run cargo fmt + run: cargo fmt --all -- --check + - name: Run cargo clippy + run: cargo clippy --all -- -D warnings + macos-check: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Test + run: cargo test --all-features + ubuntu-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Test + run: cargo test --all-features + windows-check: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: Test + run: cargo test --all-features diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..23fd35f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..34f1106 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,244 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "clap" +version = "4.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "crunch64" +version = "0.1.0" +dependencies = [ + "clap", + "thiserror", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4a87344 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "crunch64" +version = "0.1.0" +edition = "2021" +description = "A library for handling common compression formats for N64 games" +repository = "https://github.com/ethteck/crunch64" +license = "MIT" + +[dependencies] +clap = { version = "4.4.10", features = ["derive"] } +thiserror = "1.0" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6236cf4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,37 @@ +pub mod yay0; + +use thiserror::Error; +use yay0::{compress_yay0, decompress_yay0}; + +#[derive(Copy, Clone, Debug, Error, PartialEq, Eq, Hash)] +pub enum Crunch64Error { + #[error("Failed to open file")] + OpenFile, + #[error("Failed to read file")] + ReadFile, + #[error("Failed to write file")] + WriteFile, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum CompressionType { + Yay0, + Yaz0, +} + +impl CompressionType { + pub fn decompress(self: CompressionType, bytes: Vec) -> Vec { + match self { + CompressionType::Yay0 => decompress_yay0(bytes), + _ => panic!("Unsupported compression type: {:?}", self), + } + } + + pub fn compress(self: CompressionType, bytes: Vec) -> Vec { + match self { + CompressionType::Yay0 => compress_yay0(bytes), + _ => panic!("Unsupported compression type: {:?}", self), + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..46bf855 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,67 @@ +use clap::Parser; +use crunch64::Crunch64Error; +use std::{ + fs::File, + io::{BufReader, BufWriter, Read, Write}, + path::PathBuf, +}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + #[arg()] + in_path: String, + #[arg()] + out_path: String, +} + +fn main() { + let args = Args::parse(); + + let file_bytes = match read_file_bytes(args.in_path) { + Ok(bytes) => bytes, + Err(error) => { + println!("{:?}", error); + return; + } + }; + + let file_magic: &[u8] = &file_bytes[0..4]; + + let out_bytes = match file_magic { + b"Yay0" => crunch64::CompressionType::Yay0.decompress(file_bytes), + _ => { + panic!("File format not recognized - magic: {:?}", file_magic) + } + }; + + let mut buf_writer = match File::create(args.out_path) { + Ok(file) => BufWriter::new(file), + Err(_error) => { + println!("Failed to create file"); + return; + } + }; + + let _ = buf_writer + .write_all(&out_bytes) + .or(Err(Crunch64Error::WriteFile)); +} + +pub fn read_file_bytes>(path: P) -> Result, Crunch64Error> { + let file = match File::open(path.into()) { + Ok(file) => file, + Err(_error) => { + return Err(Crunch64Error::OpenFile); + } + }; + + let mut buf_reader = BufReader::new(file); + let mut buffer = Vec::new(); + + let _ = buf_reader + .read_to_end(&mut buffer) + .or(Err(Crunch64Error::ReadFile)); + + Ok(buffer) +} diff --git a/src/yay0.rs b/src/yay0.rs new file mode 100644 index 0000000..d7a5e60 --- /dev/null +++ b/src/yay0.rs @@ -0,0 +1,64 @@ +pub fn decompress_yay0(bytes: Vec) -> Vec { + let decompressed_size = u32::from_be_bytes(bytes[4..8].try_into().unwrap()); + let link_table_offset = u32::from_be_bytes(bytes[8..12].try_into().unwrap()); + let chunk_offset = u32::from_be_bytes(bytes[12..16].try_into().unwrap()); + + let mut link_table_idx = link_table_offset as usize; + let mut chunk_idx = chunk_offset as usize; + let mut other_idx = 16; + + let mut mask_bit_counter = 0; + let mut current_mask = 0; + + // Preallocate result and index into it + let mut idx: usize = 0; + let mut ret = vec![0u8; decompressed_size as usize]; + + while idx < decompressed_size as usize { + // If we're out of bits, get the next mask + if mask_bit_counter == 0 { + current_mask = u32::from_be_bytes(bytes[other_idx..other_idx + 4].try_into().unwrap()); + other_idx += 4; + mask_bit_counter = 32; + } + + if current_mask & 0x80000000 != 0 { + ret[idx] = bytes[chunk_idx]; + idx += 1; + chunk_idx += 1; + } else { + let link = u16::from_be_bytes( + bytes[link_table_idx..link_table_idx + 2] + .try_into() + .unwrap(), + ); + link_table_idx += 2; + + let offset = idx as isize - (link as isize & 0xFFF); + + let mut count = (link >> 12) as usize; + + if count == 0 { + let count_modifier = bytes[chunk_idx]; + chunk_idx += 1; + count = count_modifier as usize + 18; + } else { + count = count + 2; + } + + for i in 0..count { + ret[idx] = ret[(offset + i as isize - 1) as usize]; + idx += 1; + } + } + + current_mask <<= 1; + mask_bit_counter -= 1; + } + + ret +} + +pub fn compress_yay0(_bytes: Vec) -> Vec { + panic!("Not implemented") +}