diff --git a/Cargo.toml b/Cargo.toml index 89a90ed..b5fdd03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ resolver = "2" members = [ - "crates/dtacheck", + "crates/dtacheck", "crates/milo", "crates/swap_art_bytes", ] diff --git a/crates/milo/Cargo.toml b/crates/milo/Cargo.toml new file mode 100644 index 0000000..ea01bf4 --- /dev/null +++ b/crates/milo/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "milo" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +byteorder = "1.5.0" +clap = { version = "4.4.16", features = ["derive"] } +flate2 = "1.0.28" +tempfile = "3.9.0" diff --git a/crates/milo/src/ark.rs b/crates/milo/src/ark.rs new file mode 100644 index 0000000..10c57fa --- /dev/null +++ b/crates/milo/src/ark.rs @@ -0,0 +1,34 @@ +use std::error::Error; +use std::fs::File; + +use crate::fio::read_u32; +use crate::traits::Load; +pub mod amp; +pub mod freq; + +pub enum ArkTypes { + FreqArk(freq::FreqArchive), + AmpArk(amp::AmpArchive), +} + +pub fn load_ark_file(f: &mut File) -> Result> { + let vercheck = read_u32(f, true)?; + let ark: ArkTypes; + match vercheck { + 0x004B5241 => { + let mut freq = freq::FreqArchive::new(); + freq.load(f, 0)?; + ark = ArkTypes::FreqArk(freq); + } + 0..=2 => { + let mut amp = amp::AmpArchive::new(); + amp.load(f, 0)?; + ark = ArkTypes::AmpArk(amp); + } + _ => { + println!("unrecognized ark version. if gh1 or later, use the .hdr and not the .ark"); + return Err::<_, Box>("unkver".into()); + } + } + Ok(ark) +} diff --git a/crates/milo/src/ark/amp.rs b/crates/milo/src/ark/amp.rs new file mode 100644 index 0000000..ebdfa2b --- /dev/null +++ b/crates/milo/src/ark/amp.rs @@ -0,0 +1,116 @@ +use std::fmt::{Formatter, Display}; +use std::error::Error; + +use crate::traits::Load; +use crate::fio; + +#[derive(Clone, Copy)] +struct AmpFileEntry { + offset: u32, + file_name_idx: u32, + folder_name_idx: u32, + size: u32, + inflated_size: u32, +} + +impl AmpFileEntry { + pub fn new() -> Self { + Self { + offset: 0, + file_name_idx: 0, + folder_name_idx: 0, + size: 0, + inflated_size: 0, + } + } +} + +impl Load for AmpFileEntry { + fn load(&mut self, f: &mut std::fs::File, ver: u32) -> Result<(), Box> { + if ver != 1 {self.offset = fio::read_u32(f, true)?;} + self.file_name_idx = fio::read_u32(f, true)?; + self.folder_name_idx = fio::read_u32(f, true)?; + if ver == 1 {self.offset = fio::read_u32(f, true)?;} + self.size = fio::read_u32(f, true)?; + self.inflated_size = fio::read_u32(f, true)?; + Ok(()) + } +} + +impl Display for AmpFileEntry { + fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { + fmt.write_fmt(format_args!("Offset: {}", self.offset))?; + fmt.write_fmt(format_args!("File name index: {}", self.file_name_idx))?; + fmt.write_fmt(format_args!("Folder name index: {}", self.folder_name_idx))?; + fmt.write_fmt(format_args!("Size: {}", self.size))?; + fmt.write_fmt(format_args!("Inflated size: {}", self.inflated_size))?; + Ok(()) + } +} + +pub struct AmpArchive { + version: u32, + entry_ct: u32, + entries: Vec, + str_table_size: u32, + string_table: Vec, + string_idx_count: u32, + string_idx_entries: Vec +} + +impl AmpArchive { + pub fn new() -> Self { + Self { + version: 0, + entry_ct: 0, + entries: vec![], + str_table_size: 0, + string_table: vec![], + string_idx_count: 0, + string_idx_entries: vec![] + } + } +} + +impl Load for AmpArchive { + fn load(&mut self, f: &mut std::fs::File, _: u32) -> Result<(), Box> { + self.version = fio::read_u32(f, true)?; + self.entry_ct = fio::read_u32(f, true)?; + for _ in 0..self.entry_ct { + let mut ent = AmpFileEntry::new(); + ent.load(f, self.version)?; + self.entries.push(ent); + } + self.str_table_size = fio::read_u32(f, true)?; + for _ in 0..self.str_table_size { + let st = fio::readstr(f)?; + self.string_table.push(st); + } + self.string_idx_count = fio::read_u32(f, true)?; + for _ in 0..self.string_idx_count { + let idx = fio::read_u32(f, true)?; + self.string_idx_entries.push(idx); + } + Ok(()) + } +} + +impl Display for AmpArchive { + fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { + fmt.write_fmt(format_args!("Version: {}", self.version))?; + fmt.write_str("BEGIN ENTRIES")?; + for ent in self.entries.clone() { + ent.fmt(fmt)?; + } + fmt.write_str("END ENTRIES")?; + fmt.write_fmt(format_args!("String table size: {}", self.str_table_size))?; + for i in 0..self.str_table_size { + fmt.write_fmt(format_args!("Entry {i}: {}", self.string_table[i as usize]))?; + } + fmt.write_fmt(format_args!("String index count: {}", self.string_idx_count))?; + for i in 0..self.string_idx_entries.len() { + fmt.write_fmt(format_args!("Index {i}: {}", self.string_idx_entries[i]))?; + } + Ok(()) + } +} diff --git a/crates/milo/src/ark/freq.rs b/crates/milo/src/ark/freq.rs new file mode 100644 index 0000000..c00df46 --- /dev/null +++ b/crates/milo/src/ark/freq.rs @@ -0,0 +1,109 @@ +use crate::traits::Load; +use crate::fio; +use std::fmt::Display; +use std::fs::File; +use std::error::Error; + +#[derive(Clone, Copy)] +struct FreqFileEntry { // 24 bytes + unknown: u32, // Path name hash? + file_name_offset: u32, + folder_name_index: u16, + block_offset: u16, + block: u32, // Use block * block_size + block_offset to get file position + file_size: u32, + inflated_size: u32, // Same as file size if not compressed + fake_file_offset: u32 // = (block * 2048) + block_offset; +} + +impl Load for FreqFileEntry { + fn load(&mut self, f: &mut File, _: u32) -> Result<(), Box> { + self.unknown = fio::read_u32(f, true)?; + self.file_name_offset = fio::read_u32(f, true)?; + self.folder_name_index = fio::read_u16(f, true)?; + self.block_offset = fio::read_u16(f, true)?; + self.block = fio::read_u32(f, true)?; + self.file_size = fio::read_u32(f, true)?; + self.inflated_size = fio::read_u32(f, true)?; + self.fake_file_offset = (self.block * 2048) + self.block_offset as u32; + Ok(()) + } +} + +impl Display for FreqFileEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("Unknown value (possibly a pathname hash?): {}", self.unknown))?; + f.write_fmt(format_args!("File name offset: {}", self.file_name_offset))?; + f.write_fmt(format_args!("Folder name index: {}", self.folder_name_index))?; + f.write_fmt(format_args!("Block offset: {}", self.block_offset))?; + f.write_fmt(format_args!("Block #: {}", self.block))?; + f.write_fmt(format_args!("File offset (calculated): {}", self.fake_file_offset))?; + f.write_fmt(format_args!("File size: {}", self.file_size))?; + f.write_fmt(format_args!("Inflated filesize: {}", self.inflated_size))?; + Ok(()) + } +} + +#[derive(Clone, Copy)] +pub struct FreqArchive { + magic: u32, // technically a char[4] but 0x204B5241 is easier to check + version: u32, + file_entry_offset: u32, // Always 256 + file_entry_count: u32, + folder_entry_offset: u32, + folder_entry_count: u32, + string_table_offset: u32, + string_count: u32, + total_hdr_size: u32, // Size of header + string offsets + string table + block_size: u32, // Used for padding, always 2048? +} + +impl FreqArchive { + pub fn new() -> Self { + Self { + magic: 0x204B5241, + version: 0, + file_entry_offset: 256, + file_entry_count: 0, + folder_entry_offset: 0, + folder_entry_count: 0, + string_table_offset: 0, + string_count: 0, + total_hdr_size: 40, + block_size: 2048 + } + } +} + +impl Load for FreqArchive { + fn load(&mut self, f: &mut File, _: u32) -> Result<(), Box> { + self.magic = fio::read_u32(f, true)?; + self.version = fio::read_u32(f, true)?; + self.file_entry_offset = fio::read_u32(f, true)?; + self.file_entry_count = fio::read_u32(f, true)?; + self.folder_entry_offset = fio::read_u32(f, true)?; + self.folder_entry_count = fio::read_u32(f, true)?; + self.string_table_offset = fio::read_u32(f, true)?; + self.string_count = fio::read_u32(f, true)?; + self.total_hdr_size = fio::read_u32(f, true)?; + self.block_size = fio::read_u32(f, true)?; + Ok(()) + } +} + +impl Display for FreqArchive { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("Magic value: {:#010X} \n", self.magic))?; + f.write_fmt(format_args!("Should be 0x004B5241.\n"))?; + f.write_fmt(format_args!("Version: {}\n", self.version))?; + f.write_fmt(format_args!("File entry offset: {}\n", self.file_entry_offset))?; + f.write_fmt(format_args!("File entry count: {}\n", self.file_entry_count))?; + f.write_fmt(format_args!("Folder entry offset: {}\n", self.folder_entry_offset))?; + f.write_fmt(format_args!("Folder entry count: {}\n", self.folder_entry_count))?; + f.write_fmt(format_args!("String table offset: {}\n", self.string_table_offset))?; + f.write_fmt(format_args!("String count: {}\n", self.string_count))?; + f.write_fmt(format_args!("Total header size (includes string table): {}\n", self.total_hdr_size))?; + f.write_fmt(format_args!("Block size: {}\n", self.block_size))?; + Ok(()) + } +} diff --git a/crates/milo/src/fio.rs b/crates/milo/src/fio.rs new file mode 100644 index 0000000..77966a6 --- /dev/null +++ b/crates/milo/src/fio.rs @@ -0,0 +1,52 @@ +use std::fs::File; +use std::error::Error; +use std::io::Read; +use byteorder::{LittleEndian, BigEndian, ReadBytesExt}; + +fn readlen(f: &mut File, len: usize) -> Result, Box> { + let mut buf = vec![0u8; len]; + f.read_exact(&mut buf)?; + Ok(buf) +} + +pub fn readstr(src: &mut File) -> Result> { + let mut ret: String = String::with_capacity(256); + for _ in 0..256 { // 256 is a good length limit, right + let asciidiot = readlen(src, 1)?[0]; + if asciidiot == 0 { + ret.shrink_to_fit(); + break + } + let test = char::from_u32(asciidiot as u32); + ret.push(test.expect("found eof")); + } + Ok(ret) +} + +pub fn read_u32(f: &mut File, little_endian: bool) -> Result> { + if little_endian { Ok(f.read_u32::()?) } + else { Ok(f.read_u32::()?) } +} + +pub fn read_i32(f: &mut File, little_endian: bool) -> Result> { + if little_endian { Ok(f.read_i32::()?) } + else { Ok(f.read_i32::()?) } +} + +pub fn read_u16(f: &mut File, little_endian: bool) -> Result> { + if little_endian { Ok(f.read_u16::()?) } + else { Ok(f.read_u16::()?) } +} + +pub fn read_i16(f: &mut File, little_endian: bool) -> Result> { + if little_endian { Ok(f.read_i16::()?) } + else { Ok(f.read_i16::()?) } +} + +pub fn read_u8(f: &mut File) -> Result> { Ok(f.read_u8()?) } +pub fn read_i8(f: &mut File) -> Result> { Ok(f.read_i8()?) } + +pub fn read_f32(f: &mut File, little_endian: bool) -> Result> { + if little_endian { Ok(f.read_f32::()?) } + else { Ok(f.read_f32::()?) } +} diff --git a/crates/milo/src/lib.rs b/crates/milo/src/lib.rs new file mode 100644 index 0000000..c929915 --- /dev/null +++ b/crates/milo/src/lib.rs @@ -0,0 +1,3 @@ +pub mod ark; +pub mod traits; +mod fio; diff --git a/crates/milo/src/main.rs b/crates/milo/src/main.rs new file mode 100644 index 0000000..2167d67 --- /dev/null +++ b/crates/milo/src/main.rs @@ -0,0 +1,21 @@ +use std::fs::File; +use std::error::Error; +use std::path::PathBuf; +use clap::Parser; +use milo::ark; +use milo::traits::Load; + +#[derive(clap::Parser)] +struct Args { + input: PathBuf, +} + +fn main() -> Result<(), Box> { + println!("Hello, world!"); + let args = Args::parse(); + let mut infile = File::open(args.input)?; + let mut freqindasheets = ark::freq::FreqArchive::new(); + freqindasheets.load(&mut infile, 0)?; + println!("{}", freqindasheets); + Ok(()) +} diff --git a/crates/milo/src/traits.rs b/crates/milo/src/traits.rs new file mode 100644 index 0000000..46e7a11 --- /dev/null +++ b/crates/milo/src/traits.rs @@ -0,0 +1,15 @@ +use std::error::Error; +use std::fs::File; + +pub trait Load { + fn load(&mut self, f: &mut File, ver: u32) -> Result<(), Box>; +} + +pub trait Save { + fn save(&mut self, f: &mut File) -> Result<(), Box>; +} + +pub trait Port { + fn import(&mut self, f: &mut File) -> Result<(), Box>; + fn export(&mut self, f: &mut File) -> Result<(), Box>; +}