diff --git a/Cargo.lock b/Cargo.lock index 031b8a8..0dffca7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,12 +76,37 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "blowfish" +version = "0.9.1" +source = "git+https://github.com/lifegpc/block-ciphers.git?branch=blowfish#2cf4c1413e03cd7f33f752fccbcf5864b6070936" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.38" @@ -137,6 +162,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "csv" version = "1.3.1" @@ -186,6 +221,16 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -204,6 +249,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "int-enum" version = "1.2.0" @@ -261,6 +315,7 @@ name = "msg_tool" version = "0.1.0" dependencies = [ "anyhow", + "blowfish", "clap", "csv", "encoding_rs", @@ -437,6 +492,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/Cargo.toml b/Cargo.toml index a33319d..1b7e3a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] anyhow = "1" +blowfish = { version = "0.9", optional = true } clap = { version = "4.5", features = ["derive"] } csv = "1.3" encoding_rs = "0.8" @@ -18,10 +19,12 @@ serde_json = "1" unicode-segmentation = "1.12" [features] -default = ["bgi", "bgi-arc", "bgi-img", "circus", "escude", "escude-arc", "yaneurao", "yaneurao-itufuru"] +default = ["bgi", "bgi-arc", "bgi-img", "cat-system", "cat-system-arc", "circus", "escude", "escude-arc", "yaneurao", "yaneurao-itufuru"] bgi = [] bgi-arc = ["bgi", "utils-bit-stream"] bgi-img = ["bgi", "image", "utils-bit-stream"] +cat-system = [] +cat-system-arc = ["cat-system", "blowfish", "utils-crc32"] circus = [] escude = ["int-enum"] escude-arc = ["escude", "rand", "utils-bit-stream"] @@ -31,6 +34,10 @@ yaneurao-itufuru = ["yaneurao"] image = ["png"] # utils feature utils-bit-stream = [] +utils-crc32 = [] [target.'cfg(windows)'.dependencies] windows-sys = { version = "0", features = ["Win32_Globalization", "Win32_System_Diagnostics_Debug"] } + +[patch.crates-io] +blowfish = { git = "https://github.com/lifegpc/block-ciphers.git", branch = "blowfish" } diff --git a/src/args.rs b/src/args.rs index 4578b62..f9223a5 100644 --- a/src/args.rs +++ b/src/args.rs @@ -102,6 +102,10 @@ pub struct Arg { /// Whether to create scrambled SysGrp images. When in import mode, the default value depends on the original image. /// When in creation mode, it is not enabled by default. pub bgi_img_scramble: Option, + #[cfg(feature = "cat-system-arc")] + #[arg(long, global = true)] + /// CatSystem2 engine int archive password + pub cat_system_int_encrypt_password: Option, #[command(subcommand)] /// Command pub command: Command, diff --git a/src/main.rs b/src/main.rs index 44fcc29..172fff5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1258,6 +1258,8 @@ fn main() { bgi_is_sysgrp_arc: arg.bgi_is_sysgrp_arc.clone(), #[cfg(feature = "bgi-img")] bgi_img_scramble: arg.bgi_img_scramble.clone(), + #[cfg(feature = "cat-system-arc")] + cat_system_int_encrypt_password: arg.cat_system_int_encrypt_password.clone(), }; match &arg.command { args::Command::Export { input, output } => { diff --git a/src/scripts/cat_system/archive/int.rs b/src/scripts/cat_system/archive/int.rs new file mode 100644 index 0000000..c62f060 --- /dev/null +++ b/src/scripts/cat_system/archive/int.rs @@ -0,0 +1,443 @@ +use super::twister::MersenneTwister; +use crate::ext::io::*; +use crate::scripts::base::*; +use crate::types::*; +use crate::utils::crc32::CRC32NORMAL_TABLE; +use crate::utils::encoding::{decode_to_string, encode_string}; +use anyhow::Result; +use blowfish::Blowfish; +use blowfish::cipher::KeyInit; +use std::io::{Read, Seek, SeekFrom}; +use std::sync::{Arc, Mutex}; + +#[derive(Debug)] +pub struct CSIntArcBuilder {} + +impl CSIntArcBuilder { + pub fn new() -> Self { + CSIntArcBuilder {} + } +} + +impl ScriptBuilder for CSIntArcBuilder { + fn default_encoding(&self) -> Encoding { + Encoding::Cp932 + } + + fn default_archive_encoding(&self) -> Option { + Some(Encoding::Cp932) + } + + fn build_script( + &self, + data: Vec, + filename: &str, + _encoding: Encoding, + archive_encoding: Encoding, + config: &ExtraConfig, + ) -> Result> { + Ok(Box::new(CSIntArc::new( + MemReader::new(data), + archive_encoding, + config, + filename, + )?)) + } + + fn build_script_from_file( + &self, + filename: &str, + _encoding: Encoding, + archive_encoding: Encoding, + config: &ExtraConfig, + ) -> Result> { + if filename == "-" { + let data = crate::utils::files::read_file(filename)?; + Ok(Box::new(CSIntArc::new( + MemReader::new(data), + archive_encoding, + config, + filename, + )?)) + } else { + let f = std::fs::File::open(filename)?; + let reader = std::io::BufReader::new(f); + Ok(Box::new(CSIntArc::new( + reader, + archive_encoding, + config, + filename, + )?)) + } + } + + fn build_script_from_reader( + &self, + reader: Box, + filename: &str, + _encoding: Encoding, + archive_encoding: Encoding, + config: &ExtraConfig, + ) -> Result> { + Ok(Box::new(CSIntArc::new( + reader, + archive_encoding, + config, + filename, + )?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &["int"] + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::CatSystemInt + } + + fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { + if buf_len >= 4 && buf.starts_with(b"KIF\0") { + return Some(10); + } + None + } + + fn is_archive(&self) -> bool { + true + } +} + +#[derive(Clone, Debug)] +struct CSIntFileHeader { + name: String, + offset: u32, + size: u32, +} + +struct Entry { + header: CSIntFileHeader, + reader: Arc>, + pos: usize, +} + +impl ArchiveContent for Entry { + fn name(&self) -> &str { + &self.header.name + } + + fn script_type(&self) -> Option<&ScriptType> { + None + } +} + +impl Read for Entry { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let mut reader = self.reader.lock().map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("Failed to lock mutex: {}", e), + ) + })?; + reader.seek(SeekFrom::Start( + self.header.offset as u64 + self.pos as u64, + ))?; + let bytes_read = buf.len().min(self.header.size as usize - self.pos); + if bytes_read == 0 { + return Ok(0); + } + let bytes_read = reader.read(&mut buf[..bytes_read])?; + self.pos += bytes_read; + Ok(bytes_read) + } +} + +impl Seek for Entry { + fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + let new_pos = match pos { + SeekFrom::Start(offset) => offset as usize, + SeekFrom::End(offset) => { + if offset < 0 { + if (-offset) as usize > self.header.size as usize { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Seek from end exceeds file length", + )); + } + self.header.size as usize - (-offset) as usize + } else { + self.header.size as usize + offset as usize + } + } + SeekFrom::Current(offset) => { + if offset < 0 { + if (-offset) as usize > self.pos { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Seek from current exceeds current position", + )); + } + self.pos.saturating_sub((-offset) as usize) + } else { + self.pos + offset as usize + } + } + }; + self.pos = new_pos; + Ok(self.pos as u64) + } + + fn stream_position(&mut self) -> std::io::Result { + Ok(self.pos as u64) + } +} + +struct MemEntry { + name: String, + data: MemReader, +} + +impl Read for MemEntry { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.data.read(buf) + } +} + +impl ArchiveContent for MemEntry { + fn name(&self) -> &str { + &self.name + } + + fn script_type(&self) -> Option<&ScriptType> { + None + } + + fn data(&mut self) -> Result> { + Ok(self.data.data.clone()) + } + + fn to_data<'a>(&'a mut self) -> Result> { + Ok(Box::new(&mut self.data)) + } +} + +#[derive(Debug)] +pub struct CSIntArc { + reader: Arc>, + encrypt: Option, + entries: Vec, +} + +const NAME_SIZES: [usize; 2] = [0x20, 0x40]; + +impl CSIntArc { + pub fn new( + mut reader: T, + archive_encoding: Encoding, + config: &ExtraConfig, + _filename: &str, + ) -> Result { + let mut magic = [0u8; 4]; + reader.read_exact(&mut magic)?; + if &magic != b"KIF\0" { + return Err(anyhow::anyhow!( + "Invalid magic number for CatSystem2 archive" + )); + } + let entry_count = reader.read_u32()?; + let mut keybuf = [0u8; 12]; + reader.read_exact(&mut keybuf)?; + if &keybuf == b"__key__.dat\0" { + let key = match &config.cat_system_int_encrypt_password { + Some(password) => Self::get_key(password)?, + None => { + return Err(anyhow::anyhow!( + "CatSystem2 archive requires encryption password. Please use --cat-system-int-encrypt-password option." + )); + } + }; + eprintln!("Using CatSystem2 archive encryption key: {key:08X}"); + let seed = reader.peek_u32_at(0x4C)?; + let mut twister = MersenneTwister::new(seed); + let blowfish_key = twister.rand().to_le_bytes(); + let encrypt = match Blowfish::new_from_slice(&blowfish_key) { + Ok(bf) => bf, + Err(e) => { + return Err(anyhow::anyhow!("Failed to create Blowfish cipher: {}", e)); + } + }; + let mut entries = Vec::with_capacity(entry_count as usize - 1); + let mut name_buf = [0u8; 0x40]; + reader.seek(SeekFrom::Start(0x50))?; + for i in 1..entry_count { + reader.read_exact(&mut name_buf)?; + let offset = reader.read_u32()? + i; + let size = reader.read_u32()?; + let decryped = encrypt.decrypt([offset, size]); + twister.s_rand(key + i); + let name_key = twister.rand(); + let name = Self::decrypt_name(&mut name_buf, name_key, archive_encoding)?; + let entry = CSIntFileHeader { + name, + offset: decryped[0], + size: decryped[1], + }; + entries.push(entry); + } + return Ok(CSIntArc { + reader: Arc::new(Mutex::new(reader)), + encrypt: Some(encrypt), + entries: entries, + }); + } + let file_size = reader.seek(SeekFrom::End(0))?; + let mut entries = Vec::with_capacity(entry_count as usize); + for size in NAME_SIZES { + reader.seek(SeekFrom::Start(0x8))?; + for _ in 0..entry_count { + let name = reader.read_fstring(size, archive_encoding, true)?; + if name.is_empty() { + entries.clear(); + break; + } + let current_offset = reader.stream_position()?; + let offset = reader.read_u32()?; + let size = reader.read_u32()?; + if offset as u64 <= current_offset || !((offset as u64) < file_size && size as u64 <= file_size && offset as u64 <= file_size as u64 - size as u64) { + entries.clear(); + break; + } + let entry = CSIntFileHeader { + name, + offset, + size, + }; + entries.push(entry); + } + if !entries.is_empty() { + return Ok(CSIntArc { + reader: Arc::new(Mutex::new(reader)), + encrypt: None, + entries, + }); + } + } + Err(anyhow::anyhow!( + "Failed to parse archives. Maybe another name length is used? (expected 0x20 or 0x40)", + )) + } + + fn decrypt_name(name: &mut [u8; 0x40], key: u32, encoding: Encoding) -> Result { + let mut k = ((key >> 24) + (key >> 16) + (key >> 8) + key) & 0xFF; + let mut i = 0; + while i < 0x40 && name[i] != 0 { + let v = name[i]; + if v.is_ascii_alphabetic() { + let mut j = if v.is_ascii_lowercase() { + b'z' - v + } else { + b'Z' - v + 26 + } as i8; + j -= (k % 0x34) as i8; + if j < 0 { + j += 0x34; + } + j = 0x33 - j; + name[i] = if j < 26 { + b'z' - j as u8 + } else { + b'Z' - (j as u8 - 26) + }; + } + k += 1; + i += 1; + } + decode_to_string(encoding, &name[..i]) + } + + fn get_key(password: &str) -> Result { + let bytes = encode_string(Encoding::Cp932, password, true)?; + let mut key = 0xFFFFFFFF; + for &c in bytes.iter() { + key = !CRC32NORMAL_TABLE[((key >> 24) ^ c as u32) as usize] ^ (key << 8); + } + Ok(key) + } +} + +impl Script for CSIntArc { + fn default_output_script_type(&self) -> OutputScriptType { + OutputScriptType::Json + } + + fn default_format_type(&self) -> FormatOptions { + FormatOptions::None + } + + fn is_archive(&self) -> bool { + true + } + + fn iter_archive<'a>(&'a mut self) -> Result> + 'a>> { + Ok(Box::new( + self.entries.iter().map(|e| Ok(e.name.clone())), + )) + } + + fn iter_archive_mut<'a>( + &'a mut self, + ) -> Result>> + 'a>> { + Ok(Box::new(CSIntArcIter { + entries: self.entries.iter(), + reader: self.reader.clone(), + encrypt: self.encrypt.as_ref(), + })) + } +} + +struct CSIntArcIter<'a, T: Iterator, R: Read + Seek> { + entries: T, + reader: Arc>, + encrypt: Option<&'a Blowfish>, +} + +impl<'a, T: Iterator, R: Read + Seek + 'static> Iterator + for CSIntArcIter<'a, T, R> +{ + type Item = Result>; + + fn next(&mut self) -> Option { + let entry = match self.entries.next() { + Some(e) => e, + None => return None, + }; + let mut entry = Entry { + header: entry.clone(), + reader: self.reader.clone(), + pos: 0, + }; + if let Some(encrypt) = self.encrypt { + let mut data = match entry.data() { + Ok(data) => data, + Err(e) => return Some(Err(e)), + }; + entry.pos = 0; + for i in 0..data.len() / 8 { + let j = i * 8; + let l = data[j] as u32 | (data[j + 1] as u32) << 8 + | (data[j + 2] as u32) << 16 + | (data[j + 3] as u32) << 24; + let r = data[j + 4] as u32 | (data[j + 5] as u32) << 8 + | (data[j + 6] as u32) << 16 + | (data[j + 7] as u32) << 24; + let result = encrypt.decrypt([l, r]); + data[j..j + 4].copy_from_slice(&result[0].to_le_bytes()); + data[j + 4..j + 8].copy_from_slice(&result[1].to_le_bytes()); + } + return Some(Ok(Box::new(MemEntry { + name: entry.header.name.clone(), + data: MemReader::new(data), + }))); + } + Some(Ok(Box::new(entry))) + } +} diff --git a/src/scripts/cat_system/archive/mod.rs b/src/scripts/cat_system/archive/mod.rs new file mode 100644 index 0000000..f1d7407 --- /dev/null +++ b/src/scripts/cat_system/archive/mod.rs @@ -0,0 +1,2 @@ +pub mod int; +mod twister; diff --git a/src/scripts/cat_system/archive/twister.rs b/src/scripts/cat_system/archive/twister.rs new file mode 100644 index 0000000..a25ea5f --- /dev/null +++ b/src/scripts/cat_system/archive/twister.rs @@ -0,0 +1,70 @@ +const STATE_LENGTH: usize = 624; +const STATE_M: usize = 397; +const MATRIX_A: u32 = 0x9908B0DF; +const SIGN_MASK: u32 = 0x80000000; +const LOWER_MASK: u32 = 0x7FFFFFFF; +const TEMPERING_MASK_B: u32 = 0x9D2C5680; +const TEMPERING_MASK_C: u32 = 0xEFC60000; +const DEFAULT_SEED: u32 = 4357; + +pub struct MersenneTwister { + mt: [u32; STATE_LENGTH], + mti: usize, +} + +impl MersenneTwister { + pub fn new(seed: u32) -> Self { + let mut twister = Self { + mt: [0; STATE_LENGTH], + mti: STATE_LENGTH, + }; + twister.s_rand(seed); + twister + } + + pub fn s_rand(&mut self, mut seed: u32) { + for i in 0..STATE_LENGTH { + let upper = seed & 0xffff0000; + seed = seed.wrapping_mul(69069).wrapping_add(1); + self.mt[i] = upper | ((seed & 0xffff0000) >> 16); + seed = seed.wrapping_mul(69069).wrapping_add(1); + } + self.mti = STATE_LENGTH; + } + + pub fn rand(&mut self) -> u32 { + const MAG01: [u32; 2] = [0, MATRIX_A]; + + if self.mti >= STATE_LENGTH { + for kk in 0..(STATE_LENGTH - STATE_M) { + let y = (self.mt[kk] & SIGN_MASK) | (self.mt[kk + 1] & LOWER_MASK); + self.mt[kk] = self.mt[kk + STATE_M] ^ (y >> 1) ^ MAG01[(y & 1) as usize]; + } + for kk in (STATE_LENGTH - STATE_M)..(STATE_LENGTH - 1) { + let y = (self.mt[kk] & SIGN_MASK) | (self.mt[kk + 1] & LOWER_MASK); + self.mt[kk] = + self.mt[kk - (STATE_LENGTH - STATE_M)] ^ (y >> 1) ^ MAG01[(y & 1) as usize]; + } + let y = (self.mt[STATE_LENGTH - 1] & SIGN_MASK) | (self.mt[0] & LOWER_MASK); + self.mt[STATE_LENGTH - 1] = self.mt[STATE_M - 1] ^ (y >> 1) ^ MAG01[(y & 1) as usize]; + + self.mti = 0; + } + + let mut y = self.mt[self.mti]; + self.mti += 1; + + y ^= y >> 11; + y ^= (y << 7) & TEMPERING_MASK_B; + y ^= (y << 15) & TEMPERING_MASK_C; + y ^= y >> 18; + + y + } +} + +impl Default for MersenneTwister { + fn default() -> Self { + Self::new(DEFAULT_SEED) + } +} diff --git a/src/scripts/cat_system/mod.rs b/src/scripts/cat_system/mod.rs new file mode 100644 index 0000000..d8aa5b7 --- /dev/null +++ b/src/scripts/cat_system/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "cat-system-arc")] +pub mod archive; diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index ceee5fc..5e41391 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -1,6 +1,8 @@ pub mod base; #[cfg(feature = "bgi")] pub mod bgi; +#[cfg(feature = "cat-system")] +pub mod cat_system; #[cfg(feature = "circus")] pub mod circus; #[cfg(feature = "escude")] @@ -40,6 +42,8 @@ lazy_static::lazy_static! { Box::new(yaneurao::itufuru::script::ItufuruScriptBuilder::new()), #[cfg(feature = "yaneurao-itufuru")] Box::new(yaneurao::itufuru::archive::ItufuruArchiveBuilder::new()), + #[cfg(feature = "cat-system-arc")] + Box::new(cat_system::archive::int::CSIntArcBuilder::new()), ]; pub static ref ALL_EXTS: Vec = BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect(); diff --git a/src/types.rs b/src/types.rs index 5bac6b8..fd49dc5 100644 --- a/src/types.rs +++ b/src/types.rs @@ -203,14 +203,13 @@ pub struct ExtraConfig { pub bgi_is_sysgrp_arc: Option, #[cfg(feature = "bgi-img")] pub bgi_img_scramble: Option, + #[cfg(feature = "cat-system-arc")] + pub cat_system_int_encrypt_password: Option, } #[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)] /// Script type pub enum ScriptType { - #[cfg(feature = "circus")] - /// Circus MES script - Circus, #[cfg(feature = "bgi")] #[value(alias("ethornell"))] /// Buriko General Interpreter/Ethornell Script @@ -243,6 +242,12 @@ pub enum ScriptType { #[value(alias("ethornell-cbg"))] /// Buriko General Interpreter/Ethornell Compressed Background image (CBG) BGICbg, + #[cfg(feature = "cat-system-arc")] + /// CatSystem2 engine archive + CatSystemInt, + #[cfg(feature = "circus")] + /// Circus MES script + Circus, #[cfg(feature = "escude-arc")] /// Escude bin archive EscudeArc, diff --git a/src/utils/crc32.rs b/src/utils/crc32.rs new file mode 100644 index 0000000..68439bc --- /dev/null +++ b/src/utils/crc32.rs @@ -0,0 +1,48 @@ +use lazy_static::lazy_static; + +fn get_crc32normal_table() -> [u32; 256] { + let mut table = [0; 256]; + for i in 0..256u32 { + let mut c = i << 24; + for _ in 0..8 { + if c & 0x80000000 != 0 { + c = (c << 1) ^ 0x04C11DB7; // Polynomial for CRC-32 + } else { + c <<= 1; + } + } + table[i as usize] = c; + } + table +} + +lazy_static! { + pub static ref CRC32NORMAL_TABLE: [u32; 256] = get_crc32normal_table(); +} + +pub struct Crc32Normal { + crc: u32, +} + +impl Crc32Normal { + pub fn new() -> Self { + Crc32Normal { crc: 0xFFFFFFFF } + } + + pub fn update_crc(init_crc: u32, data: &[u8]) -> u32 { + let mut crc = init_crc; + for &byte in data { + let index = ((crc >> 24) ^ byte as u32) & 0xFF; + crc = (crc << 8) ^ CRC32NORMAL_TABLE[index as usize]; + } + crc ^ 0xFFFFFFFF + } + + pub fn update(&mut self, data: &[u8]) { + self.crc = Self::update_crc(self.crc, data); + } + + pub fn value(&self) -> u32 { + self.crc + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index d28da96..1c115e6 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,8 @@ #[cfg(feature = "utils-bit-stream")] pub mod bit_stream; pub mod counter; +#[cfg(feature = "utils-crc32")] +pub mod crc32; pub mod encoding; #[cfg(windows)] mod encoding_win;