diff --git a/luca_pak/src/lib.rs b/luca_pak/src/lib.rs index 0ae15d0..e176375 100644 --- a/luca_pak/src/lib.rs +++ b/luca_pak/src/lib.rs @@ -1,14 +1,13 @@ pub mod entry; pub mod header; -use byteorder::{LittleEndian, ReadBytesExt}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use header::Header; use log::{debug, info}; use std::{ ffi::CString, fs::File, io::{self, BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write}, path::{Path, PathBuf} }; use thiserror::Error; -use byteorder::WriteBytesExt; type LE = LittleEndian; @@ -20,8 +19,11 @@ pub enum PakError { #[error("Could not read/write file")] IoError(#[from] io::Error), - #[error("Expected {} files, got {} in {}", 0, 1, 2)] - FileCountMismatch(usize, usize, &'static str), + #[error("Expected {0} files, got {1} in {2}")] + EntryCountMismatch(usize, usize, &'static str), + + #[error("Number of entries in header ({0}) exceeds limit of {1}")] + EntryLimit(u32, usize), #[error("Malformed header information")] HeaderError, @@ -35,9 +37,11 @@ pub enum PakError { pub struct Pak { subdirectory: Option, - /// The path of the PAK file, can serve as an identifier or name as the + /// The path to the PAK file, can serve as an identifier or name as the /// header has no name for the file. path: PathBuf, + + /// Header information header: Header, unknown_pre_data: Vec, @@ -46,23 +50,42 @@ pub struct Pak { entries: Vec, } -struct FileLocation { +struct EntryLocation { offset: u32, length: u32, } +pub struct PakLimits { + pub entry_limit: usize, + pub size_limit: usize, +} + +impl Default for PakLimits { + fn default() -> Self { + Self { + entry_limit: 10_000, // 10,000 entries + size_limit: 10_000_000_000, // 10 gb + } + } +} + impl Pak { /// Convenience method to open a PAK file from a path and decode it pub fn open>(path: &P) -> Result { let mut file = File::open(path)?; - Pak::decode(&mut file, path.as_ref().to_path_buf()) + Pak::decode( + &mut file, + path.as_ref().to_path_buf(), + PakLimits::default() + ) } /// Decode a PAK file from a byte stream. pub fn decode( input: &mut T, path: PathBuf, + limits: PakLimits, ) -> Result { info!("Reading pak from {:?}", path); let mut input = BufReader::new(input); @@ -80,6 +103,10 @@ impl Pak { unknown4: input.read_u32::()?, flags: PakFlags(input.read_u32::()?), }; + + if header.entry_count >= limits.entry_limit as u32 { + return Err(PakError::EntryLimit(header.entry_count, limits.entry_limit)) + } info!("{} entries detected", header.entry_count); debug!("Block size is {} bytes", header.block_size); debug!("Flag bits {:#032b}", header.flags().0); @@ -87,6 +114,7 @@ impl Pak { let first_offset = header.data_offset() / header.block_size(); // Read some unknown data before the data we want + // TODO: This *must* be done differently for real, figure it out! let mut unknown_pre_data = Vec::new(); while input.stream_position()? < header.data_offset() as u64 { let unknown = input.read_u32::()?; @@ -111,7 +139,7 @@ impl Pak { for _ in 0..header.entry_count() { let offset = input.read_u32::().unwrap(); let length = input.read_u32::().unwrap(); - offsets.push(FileLocation { + offsets.push(EntryLocation { offset, length, }); diff --git a/pak_explorer/Cargo.toml b/pak_explorer/Cargo.toml index 740daaa..e14ed88 100644 --- a/pak_explorer/Cargo.toml +++ b/pak_explorer/Cargo.toml @@ -10,9 +10,11 @@ authors.workspace = true publish = false [dependencies] +colog = "1.3.0" cz = { path = "../cz/", features = ["png"] } eframe = { version = "0.28.1", default-features = false, features = ["wayland", "x11", "accesskit", "default_fonts", "wgpu"] } egui_extras = "0.28.1" +log = "0.4.22" luca_pak = { path = "../luca_pak/" } rfd = "0.14.1" diff --git a/pak_explorer/src/main.rs b/pak_explorer/src/main.rs index 2387026..aa96477 100644 --- a/pak_explorer/src/main.rs +++ b/pak_explorer/src/main.rs @@ -1,22 +1,47 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release use std::fs; - +use colog; use eframe::egui::{self, ColorImage, Image, TextureFilter, TextureHandle, TextureOptions}; +use log::error; use luca_pak::{entry::EntryType, Pak}; fn main() -> eframe::Result { + colog::default_builder() + .filter(None, log::LevelFilter::Warn) + .init(); + let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([1024.0, 800.0]), follow_system_theme: true, ..Default::default() }; + + let mut fonts = egui::FontDefinitions::default(); + fonts.font_data.insert( + "Noto Sans".to_owned(), + egui::FontData::from_static(include_bytes!("/home/g2/Downloads/Noto_Sans/static/NotoSans-Regular.ttf")), + ); + fonts.font_data.insert( + "Noto Sans Japanese".to_owned(), + egui::FontData::from_static(include_bytes!("/home/g2/Downloads/Noto_Sans_JP/static/NotoSansJP-Regular.ttf")), + ); + fonts + .families + .entry(egui::FontFamily::Proportional) + .or_default() + .insert(0, "Noto Sans".to_owned()); + fonts + .families + .entry(egui::FontFamily::Proportional) + .or_default() + .insert(1, "Noto Sans Japanese".to_owned()); + eframe::run_native( "LUCA PAK Explorer", options, Box::new(|cc| { - // This gives us image support: - egui_extras::install_image_loaders(&cc.egui_ctx); + cc.egui_ctx.set_fonts(fonts); Ok(Box::::default()) }), @@ -48,17 +73,23 @@ impl eframe::App for PakExplorer { ui.heading("PAK File Explorer"); ui.horizontal(|ui| { - if ui.button("Open file…").clicked() { + if ui.button("Open file").clicked() { if let Some(path) = rfd::FileDialog::new().pick_file() { - let pak = Pak::open(&path).unwrap(); - self.open_file = Some(pak); + let pak = match Pak::open(&path) { + Ok(pak) => Some(pak), + Err(e) => { + error!("Unable to read selected file as PAK: {}", e); + None + }, + }; + self.open_file = pak; self.selected_entry = None; self.image_texture = None; self.hex_string = None; } } if let Some(pak) = &self.open_file { - if ui.button("Save PAK…").clicked() { + if ui.button("Save PAK").clicked() { if let Some(path) = rfd::FileDialog::new() .set_file_name(pak.path().file_name().unwrap().to_string_lossy()) .save_file() @@ -105,7 +136,7 @@ impl eframe::App for PakExplorer { if let Some(entry) = &self.selected_entry { ui.horizontal(|ui| { - if ui.button("Save entry…").clicked() { + if ui.button("Save entry").clicked() { if let Some(path) = rfd::FileDialog::new() .set_file_name(entry.display_name()) .save_file() @@ -115,7 +146,7 @@ impl eframe::App for PakExplorer { } if let Some(pak) = &mut self.open_file.as_mut() { - if ui.button("Replace entry…").clicked() { + if ui.button("Replace entry").clicked() { if let Some(path) = rfd::FileDialog::new().pick_file() { let file_bytes = fs::read(path).unwrap(); pak.replace(entry.index(), &file_bytes).unwrap(); @@ -128,7 +159,7 @@ impl eframe::App for PakExplorer { | EntryType::CZ2 | EntryType::CZ3 | EntryType::CZ4 | EntryType::CZ5 => { - if ui.button("Save as PNG…").clicked() { + if ui.button("Save as PNG").clicked() { let mut display_name = entry.display_name(); display_name.push_str(".png"); if let Some(path) = rfd::FileDialog::new()