mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-04-19 07:12:55 -05:00
Add logging to pak explorer, fix some error messages in pak library
This commit is contained in:
parent
d9bd35f075
commit
fddcf2f055
3 changed files with 79 additions and 18 deletions
|
@ -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<String>,
|
||||
|
||||
/// 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<u32>,
|
||||
|
@ -46,23 +50,42 @@ pub struct Pak {
|
|||
entries: Vec<Entry>,
|
||||
}
|
||||
|
||||
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<P: ?Sized + AsRef<Path>>(path: &P) -> Result<Self, PakError> {
|
||||
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<T: Seek + Read>(
|
||||
input: &mut T,
|
||||
path: PathBuf,
|
||||
limits: PakLimits,
|
||||
) -> Result<Self, PakError> {
|
||||
info!("Reading pak from {:?}", path);
|
||||
let mut input = BufReader::new(input);
|
||||
|
@ -80,6 +103,10 @@ impl Pak {
|
|||
unknown4: input.read_u32::<LE>()?,
|
||||
flags: PakFlags(input.read_u32::<LE>()?),
|
||||
};
|
||||
|
||||
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::<LE>()?;
|
||||
|
@ -111,7 +139,7 @@ impl Pak {
|
|||
for _ in 0..header.entry_count() {
|
||||
let offset = input.read_u32::<LE>().unwrap();
|
||||
let length = input.read_u32::<LE>().unwrap();
|
||||
offsets.push(FileLocation {
|
||||
offsets.push(EntryLocation {
|
||||
offset,
|
||||
length,
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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::<PakExplorer>::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()
|
||||
|
|
Loading…
Reference in a new issue