From da5f9b62c224c191307b5ee3c95e9313325da012 Mon Sep 17 00:00:00 2001 From: "Rph :3" <11350302+rphsoftware@users.noreply.github.com> Date: Wed, 20 Aug 2025 19:49:59 +0200 Subject: [PATCH] code --- Cargo.lock | 57 +++++++++++++++++++++ Cargo.toml | 2 + src/main.rs | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..303596d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,57 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Excavator" +version = "0.1.0" +dependencies = [ + "anyhow", + "flate2", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] diff --git a/Cargo.toml b/Cargo.toml index 6e7ed9d..80a5ad5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +anyhow = "1.0.99" +flate2 = "1.1.2" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e7a11a9..5d94617 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,138 @@ -fn main() { - println!("Hello, world!"); +use std::{env, fs::{create_dir, File}, io::{BufReader, Cursor, Read, Write}, path::PathBuf}; + +use anyhow::{anyhow, bail, Context}; +use flate2::read::DeflateDecoder; + +const EXPECTED_HEADER: [u8; 12] = [129, 70, 79, 83, 83, 73, 76, 73, 90, 69, 68, 66]; // \x81FOSSILIZEDB + +fn main() -> anyhow::Result<()> { + let args: Vec = env::args().collect(); + + let (input_file, output_path) = { + match args.len() { + 2 => { + let base = PathBuf::from(&args[1]); + + let name = base.to_str().context("Unable to convert path to string")?; + let name = PathBuf::from(format!("{name}.output")); + + if name.exists() { + return Err(anyhow!("Output path {:?} already exists. Remove it.", name)) + } + + if base.exists() { + Ok(( + base, + name + )) + } else { + Err(anyhow!("Unable to find base file.")) + } + }, + 3 => { + let base = PathBuf::from(&args[1]); + let name = PathBuf::from(&args[2]); + + if name.exists() { + return Err(anyhow!("Output path {:?} already exists. Remove it.", name)) + } + + if base.exists() { + Ok(( + base, + name + )) + } else { + Err(anyhow!("Unable to find base file.")) + } + }, + _ => Err(anyhow!("Must provide 1 or 2 parameters. (input file) or (input file, output directory)")) + } + }?; + + create_dir(&output_path)?; + + let fd = File::open(input_file)?; + let mut reader = BufReader::new(fd); + let mut buffer: [u8; 12] = [0; 12]; + + reader.read_exact(&mut buffer)?; + + // magic_number uint8_t[12] "\x81FOSSILIZEDB" + if buffer.as_slice() != EXPECTED_HEADER.as_slice() { + bail!("File header mismatch."); + } + + // unused1, unused2, unused3 - uint8_t + reader.seek_relative(3)?; // Skip 3 unused bytes + + let mut buffer: [u8; 1] = [0; 1]; + + // version uint8_t StreamArchive version: 6 + reader.read_exact(&mut buffer)?; + + if buffer[0] != 6 { + bail!("File version mismatch. Expected 6, got {}", buffer[0]); + } + + // tag and hash, 40 bytes total. Application defined ASCII, used for file names here. + let mut tag_hash_buffer: [u8; 40] = [0; 40]; + + // uint32_t (LE) - Size of the payload + let mut stored_size_buffer: [u8; 4] = [0; 4]; + + // uint32_t (LE) - Flags [0x1 no compression | 0x2 compressio (deflate)] + let mut flags_buffer: [u8; 4] = [0; 4]; + + // uint32_t (LE) - CRC32 of the payload + let mut crc32_buffer: [u8; 4] = [0; 4]; + + // uint32_t (LE) - Payload size after decompression + let mut payload_size_buffer: [u8; 4] = [0; 4]; + + loop { + match reader.read_exact(&mut tag_hash_buffer) { + Ok(_) => {} + Err(_) => { + println!("Finished"); + break; + } + } + + reader.read_exact(&mut stored_size_buffer)?; + reader.read_exact(&mut flags_buffer)?; + reader.read_exact(&mut crc32_buffer)?; + reader.read_exact(&mut payload_size_buffer)?; + + let tag_hash = str::from_utf8(&tag_hash_buffer)?; + let stored_size = u32::from_le_bytes(stored_size_buffer); + let flags = u32::from_le_bytes(flags_buffer); + let crc32 = u32::from_le_bytes(crc32_buffer); + let payload_size = u32::from_le_bytes(payload_size_buffer); + + let mut data_buf: Vec = vec![0; stored_size as usize]; + reader.read_exact(&mut data_buf)?; + + let mutated_data = match flags { + 1 => Ok(data_buf), + 2 => { + let mut decoder = DeflateDecoder::new(Cursor::new(data_buf)); + let mut data_buf = vec![0; payload_size as usize]; + decoder.read_to_end(&mut data_buf)?; + + Ok(data_buf) + }, + _ => Err(anyhow!("Invalid flag: {flags}")) + }?; + + let mut output_path = output_path.clone(); + output_path.push(tag_hash); + + let mut fd = File::create(output_path)?; + fd.write_all(&mutated_data)?; + + println!("tag hash {tag_hash} stored size {stored_size} flags {flags} crc32 {crc32} payload size {payload_size}"); + } + + Ok(()) }