From 58dbe405d1c9df170e799a00aaa4cd84a5d3f4b2 Mon Sep 17 00:00:00 2001 From: endernon Date: Fri, 6 Dec 2024 22:58:45 +0000 Subject: [PATCH] the better update --- Cargo.lock | 9 ++++ Cargo.toml | 3 ++ src/catzou/mod.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 59 ++++++-------------- 4 files changed, 163 insertions(+), 42 deletions(-) create mode 100644 src/catzou/mod.rs diff --git a/Cargo.lock b/Cargo.lock index a2ccb64..c6512f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "adler2" version = "2.0.0" @@ -965,9 +971,12 @@ dependencies = [ name = "respack-decrypter" version = "0.1.0" dependencies = [ + "adler", "clap", + "crc32fast", "glob", "image", + "miniz_oxide", "oxipng", "zune-png", ] diff --git a/Cargo.toml b/Cargo.toml index 1605d92..1330dd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,6 @@ glob = "0.3.1" image = "0.25.5" oxipng = "9.1.3" zune-png = "0.4.10" +adler = "1.0.2" +crc32fast = "1.4.2" +miniz_oxide = "0.8.0" diff --git a/src/catzou/mod.rs b/src/catzou/mod.rs new file mode 100644 index 0000000..f076555 --- /dev/null +++ b/src/catzou/mod.rs @@ -0,0 +1,134 @@ +use std::{ + fmt::Debug, + fs::File, + io::{BufReader, BufWriter, Read, Write}, + usize, +}; +use std::path::PathBuf; +use adler::Adler32; +use crc32fast::Hasher; +use miniz_oxide::inflate::core::{ + decompress, + inflate_flags::{ + TINFL_FLAG_IGNORE_ADLER32, TINFL_FLAG_PARSE_ZLIB_HEADER, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF, + }, + DecompressorOxide, +}; + +struct Chunk { + kind: [u8; 4], + data: Vec, + crc: [u8; 4], +} + +impl Debug for Chunk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Chunk") + .field("kind", &self.kind) + .field("data len", &self.data.len()) + .field("crc", &self.crc) + .finish() + } +} + +impl Chunk { + fn kind_to_string(&self) -> String { + String::from_utf8_lossy(&self.kind).to_string() + } +} + +pub fn encoder(inpath: PathBuf, outpath: PathBuf) { + let file = File::open(inpath).unwrap(); + let mut bufread = BufReader::new(file); + + // read the png header + let mut header = [0; 8]; + bufread.read_exact(&mut header).unwrap(); + + let mut chunks = Vec::new(); + + let mut lenbuf = [0; 4]; + while let Ok(_) = bufread.read_exact(&mut lenbuf) { + let len = u32::from_be_bytes(lenbuf); + + let mut kind = [0; 4]; + bufread.read_exact(&mut kind).unwrap(); + + let data = { + let mut data = vec![0; len as usize]; + bufread.read_exact(&mut data).unwrap(); + data + }; + + let mut crc = [0; 4]; + bufread.read_exact(&mut crc).unwrap(); + + let mut chunk = Chunk { kind, data, crc }; + println!("{:?}", chunk); + + // recode the compressed image data + if chunk.kind == *b"IDAT" { + println!("Decompressing IDAT chunk"); + let mut decompressor = DecompressorOxide::new(); + decompressor.init(); + let mut buf = vec![0; 1024 * 1024 * 1024]; // this could probably be smaller + let data = decompress( + &mut decompressor, + &chunk.data, + &mut buf, + 0, + TINFL_FLAG_IGNORE_ADLER32 + | TINFL_FLAG_PARSE_ZLIB_HEADER + | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF, + ); + + println!( + "Decompressed IDAT chunk status {:?}, bytes read {}, bytes outputted {}", + data.0, data.1, data.2 + ); + + let _ = buf.split_off(data.2); + + let mut adler = Adler32::new(); + adler.write_slice(&buf); + + let csum = adler.checksum().to_be_bytes(); + + // replace the last 4 bytes of the data with the new checksum + let data_len = chunk.data.len(); + chunk.data[data_len - 4..].copy_from_slice(&csum); + println!("Corrected Adler32 checksum"); + } + + let mut hasher = Hasher::new(); + hasher.update(&chunk.kind); + hasher.update(&chunk.data); + let checksum = hasher.finalize(); + + if checksum != u32::from_be_bytes(chunk.crc) { + println!("CRC error in chunk {:?}", chunk.kind_to_string()); + chunk.crc = checksum.to_be_bytes(); + println!("Corrected CRC"); + } + + chunks.push(chunk); + } + + let ofile = File::create(outpath).unwrap(); + let mut writer = BufWriter::new(ofile); + + // write a new header + writer + .write_all(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) + .unwrap(); + + for chunk in chunks { + writer + .write_all(&u32::to_be_bytes(chunk.data.len() as u32)) + .unwrap(); + writer.write_all(&chunk.kind).unwrap(); + writer.write_all(&chunk.data).unwrap(); + writer.write_all(&chunk.crc).unwrap(); + } +} diff --git a/src/main.rs b/src/main.rs index a5ded83..c963f98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod catzou; use clap::Parser; use glob::glob; use image::ImageBuffer; @@ -14,6 +15,7 @@ struct Args { /// Input path /// /// Include a file path or a directory path. Do not use any asterisks or wildcards. + /// Do not use multiple paths in one command. /// If the path includes spaces you may find it useful to use speechmarks (" ") around the path. #[arg(short, long)] path: PathBuf, @@ -21,9 +23,9 @@ struct Args { // /// Output path // #[arg(short, long,default_value="")] // out: PathBuf, - /// Debug mode + /// Optional Debug mode /// - /// Either "true" or "false" + /// Either "true" or "false" in lowercase. #[arg(short, long, default_value_t = false)] debug: bool, } @@ -31,7 +33,7 @@ struct Args { fn main() { // argument parsing yep let Args: Args = Args::parse(); - let pathfr = Args.path.clone(); + let mut pathfr = Args.path.clone(); let debugmode = Args.debug; let mut filelist: Vec = Vec::new(); @@ -39,9 +41,12 @@ fn main() { let pathtype = metadata(&pathfr).unwrap(); if pathtype.is_file() { filelist.push(PathBuf::from(format!("{:?}", pathtype))); - println!("input path FILE"); + println!("input path type is FILE"); } else if pathtype.is_dir() { - println!("input path DIR"); + println!("input path type is DIR"); + if pathfr.ends_with("/") { + pathfr.pop(); + } // parse file list if it's a dir for entry in glob(&format!("{}/**/*.png", pathfr.clone().display())) .expect("Failed to read glob pattern (you should panic)") @@ -49,15 +54,17 @@ fn main() { match entry { Ok(path) => { // println!("{:?}", path.display()); - filelist.push(path.display().to_string().parse().unwrap()); + filelist.push( + path.display().to_string().parse().unwrap() + ); } Err(e) => { - println!("Shell globbing error"); + eprintln!("Globbing error... uh oh..."); } } } } else { - panic!("that input is neither file nor dir and thats scary...") + eprint!("that input is neither file nor dir and thats scary... aaa") } // if debugmode { @@ -67,38 +74,6 @@ fn main() { // } for thatpath in filelist { - encoder(thatpath, debugmode) + catzou::encoder(thatpath.to_owned(), thatpath) } -} - -fn encoder(filepath: PathBuf, modeDebug: bool) { - let file = File::open(&filepath).unwrap(); - let mut data = BufReader::new(file); - let mut buf = Vec::new(); - data.read_to_end(&mut buf).unwrap(); - - let mut decoder = PngDecoder::new_with_options(buf, DecoderOptions::new_cmd()); - decoder.decode_headers().unwrap(); - let dim = decoder.get_dimensions().unwrap(); - //dbg!(dim); - let col = decoder.get_colorspace().unwrap(); - //dbg!(col); - let data = decoder.decode_raw().unwrap(); - //dbg!(&data); - let depth = decoder.get_depth().unwrap(); - - if modeDebug { - println!("Filepath: {:?}", filepath); - println!("Dimensions: {:?}", dim); - println!("Colorspace: {:?}", col); - println!("BitDepth: {:?}", depth); - } - - // TODO: figure out propper color checks rn we assume data is RGBA - - let out: ImageBuffer, Vec> = - ImageBuffer::from_raw(dim.0 as u32, dim.1 as u32, data).unwrap(); - - out.save(filepath.clone()).unwrap(); - println!("wrote file {:?}\n",filepath) -} +} \ No newline at end of file