mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-04-19 15:22:53 -05:00
Made experimental UI
This commit is contained in:
parent
3cb07bc74d
commit
f6374eecfc
5 changed files with 223 additions and 26 deletions
|
@ -10,6 +10,10 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cz = { path = "../cz/", features = ["png"] }
|
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"
|
||||||
|
luca_pak = { path = "../luca_pak/" }
|
||||||
|
rfd = "0.14.1"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
@ -1,10 +1,157 @@
|
||||||
use std::time::Instant;
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
fn main() {
|
use std::path::PathBuf;
|
||||||
let mut cz_file = cz::open("test_file.cz3").unwrap();
|
|
||||||
//cz_file.save_as_png("test.png").unwrap();
|
|
||||||
|
|
||||||
cz_file.header_mut().set_version(4).unwrap();
|
use eframe::{egui::{self, text::{LayoutJob, TextWrapping}, ColorImage, Image, Rgba, TextBuffer, TextureFilter, TextureHandle, TextureOptions}, epaint::Fonts};
|
||||||
|
use luca_pak::{entry::EntryType, header, Pak};
|
||||||
|
|
||||||
cz_file.save_as_cz("test_file.cz4").unwrap();
|
fn main() -> eframe::Result {
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
viewport: egui::ViewportBuilder::default().with_inner_size([1024.0, 800.0]),
|
||||||
|
follow_system_theme: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
eframe::run_native(
|
||||||
|
"LUCA PAK Explorer",
|
||||||
|
options,
|
||||||
|
Box::new(|cc| {
|
||||||
|
// This gives us image support:
|
||||||
|
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||||
|
|
||||||
|
Ok(Box::<PakExplorer>::default())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PakExplorer {
|
||||||
|
open_file: Option<Pak>,
|
||||||
|
selected_entry: Option<luca_pak::entry::Entry>,
|
||||||
|
image_texture: Option<egui::TextureHandle>,
|
||||||
|
hex_string: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PakExplorer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
open_file: None,
|
||||||
|
selected_entry: None,
|
||||||
|
image_texture: None,
|
||||||
|
hex_string: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for PakExplorer {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ctx.set_pixels_per_point(1.5);
|
||||||
|
ui.heading("PAK File Explorer");
|
||||||
|
|
||||||
|
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);
|
||||||
|
self.selected_entry = None;
|
||||||
|
self.image_texture = None;
|
||||||
|
self.hex_string = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
if let Some(pak) = &self.open_file {
|
||||||
|
ui.label(format!("Opened {}", pak.path().file_name().unwrap().to_string_lossy()));
|
||||||
|
ui.label(format!("Contains {} Entries", pak.entries().len()));
|
||||||
|
|
||||||
|
let selection = if let Some(entry) = &self.selected_entry {
|
||||||
|
entry.display_name()
|
||||||
|
} else {
|
||||||
|
"None".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
egui::ComboBox::from_id_source("my-combobox")
|
||||||
|
.selected_text(selection)
|
||||||
|
.truncate()
|
||||||
|
.show_ui(ui, |ui|
|
||||||
|
{
|
||||||
|
ui.selectable_value(&mut self.selected_entry, None, "");
|
||||||
|
for entry in pak.entries() {
|
||||||
|
if ui.selectable_value(
|
||||||
|
&mut self.selected_entry,
|
||||||
|
Some(entry.clone()),
|
||||||
|
entry.display_name(),
|
||||||
|
).clicked() {
|
||||||
|
self.image_texture = None;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ui.centered_and_justified(|ui|
|
||||||
|
ui.label("No File Opened")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(entry) = &self.selected_entry {
|
||||||
|
if ui.button("Save entry…").clicked() {
|
||||||
|
if let Some(path) = rfd::FileDialog::new()
|
||||||
|
.set_file_name(entry.display_name())
|
||||||
|
.save_file()
|
||||||
|
{
|
||||||
|
entry.save(&path).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match entry.file_type() {
|
||||||
|
EntryType::CZ0 | EntryType::CZ1
|
||||||
|
| EntryType::CZ2 | EntryType::CZ3
|
||||||
|
| EntryType::CZ4 | EntryType::CZ5 =>
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
.set_file_name(display_name)
|
||||||
|
.save_file()
|
||||||
|
{
|
||||||
|
let cz = cz::DynamicCz::decode(&mut std::io::Cursor::new(entry.as_bytes())).unwrap();
|
||||||
|
cz.save_as_png(&path).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
let texture: &TextureHandle = self.image_texture.get_or_insert_with(|| {
|
||||||
|
let cz = cz::DynamicCz::decode(&mut std::io::Cursor::new(entry.as_bytes())).unwrap();
|
||||||
|
let image = ColorImage::from_rgba_unmultiplied(
|
||||||
|
[cz.header().width() as usize, cz.header().height() as usize],
|
||||||
|
cz.as_raw()
|
||||||
|
);
|
||||||
|
ui.ctx().load_texture("eventframe", image, TextureOptions {
|
||||||
|
magnification: TextureFilter::Nearest,
|
||||||
|
minification: TextureFilter::Linear,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.centered_and_justified(|ui|
|
||||||
|
ui.add(
|
||||||
|
Image::from_texture(texture)
|
||||||
|
.show_loading_spinner(true)
|
||||||
|
.shrink_to_fit()
|
||||||
|
.rounding(2.0)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
ui.centered_and_justified(|ui|
|
||||||
|
ui.label("No Preview Available")
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.centered_and_justified(|ui|
|
||||||
|
ui.label("Select an Entry")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error, fmt, fs::File, io::{BufWriter, Write}, path::Path
|
||||||
fs::File,
|
|
||||||
io::{BufWriter, Write},
|
|
||||||
path::Path,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A single file entry in a PAK file
|
/// A single file entry in a PAK file
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Entry {
|
pub struct Entry {
|
||||||
pub(super) index: usize,
|
pub(super) index: usize,
|
||||||
|
|
||||||
|
@ -42,18 +39,6 @@ impl Entry {
|
||||||
|
|
||||||
/// Save an [`Entry`] as its underlying data to a file
|
/// Save an [`Entry`] as its underlying data to a file
|
||||||
pub fn save<P: ?Sized + AsRef<Path>>(&self, path: &P) -> Result<(), Box<dyn Error>> {
|
pub fn save<P: ?Sized + AsRef<Path>>(&self, path: &P) -> Result<(), Box<dyn Error>> {
|
||||||
let mut path = path.as_ref().to_path_buf();
|
|
||||||
if !path.is_dir() {
|
|
||||||
return Err("Path must be a directory".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the file to <folder> + <file name>
|
|
||||||
if let Some(name) = &self.name {
|
|
||||||
path.push(name);
|
|
||||||
} else {
|
|
||||||
path.push(&self.id.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out_file = BufWriter::new(File::create(path)?);
|
let mut out_file = BufWriter::new(File::create(path)?);
|
||||||
|
|
||||||
out_file.write_all(&self.data)?;
|
out_file.write_all(&self.data)?;
|
||||||
|
@ -70,4 +55,63 @@ impl Entry {
|
||||||
pub fn as_bytes(&self) -> &Vec<u8> {
|
pub fn as_bytes(&self) -> &Vec<u8> {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display_name(&self) -> String {
|
||||||
|
let mut name = self.name().clone().unwrap_or(self.id().to_string());
|
||||||
|
let entry_type = self.file_type();
|
||||||
|
name.push_str(&entry_type.extension());
|
||||||
|
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_type(&self) -> EntryType {
|
||||||
|
if self.data[0..2] == [b'C', b'Z'] {
|
||||||
|
match self.data[2] {
|
||||||
|
b'0' => EntryType::CZ0,
|
||||||
|
b'1' => EntryType::CZ1,
|
||||||
|
b'2' => EntryType::CZ2,
|
||||||
|
b'3' => EntryType::CZ3,
|
||||||
|
b'4' => EntryType::CZ4,
|
||||||
|
b'5' => EntryType::CZ5,
|
||||||
|
_ => EntryType::Unknown,
|
||||||
|
}
|
||||||
|
} else if self.data[0..3] == [b'M', b'V', b'T'] {
|
||||||
|
EntryType::MVT
|
||||||
|
} else {
|
||||||
|
EntryType::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum EntryType {
|
||||||
|
CZ0,
|
||||||
|
CZ1,
|
||||||
|
CZ2,
|
||||||
|
CZ3,
|
||||||
|
CZ4,
|
||||||
|
CZ5,
|
||||||
|
|
||||||
|
/// An MVT video file
|
||||||
|
MVT,
|
||||||
|
|
||||||
|
/// Who knows!
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntryType {
|
||||||
|
/// Get the file extension for the file
|
||||||
|
pub fn extension(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::CZ0 => ".cz0",
|
||||||
|
Self::CZ1 => ".cz1",
|
||||||
|
Self::CZ2 => ".cz2",
|
||||||
|
Self::CZ3 => ".cz3",
|
||||||
|
Self::CZ4 => ".cz4",
|
||||||
|
Self::CZ5 => ".cz5",
|
||||||
|
Self::MVT => ".mvt",
|
||||||
|
Self::Unknown => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod entry;
|
pub mod entry;
|
||||||
mod header;
|
pub mod header;
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use header::Header;
|
use header::Header;
|
||||||
|
|
|
@ -68,7 +68,9 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry in pak.entries() {
|
for entry in pak.entries() {
|
||||||
entry.save(&output).unwrap();
|
let mut outpath = output.clone();
|
||||||
|
outpath.push(entry.display_name());
|
||||||
|
entry.save(&outpath).unwrap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Commands::Replace { batch, name, id, replacement, output } => {
|
Commands::Replace { batch, name, id, replacement, output } => {
|
||||||
|
|
Loading…
Reference in a new issue