mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-06-23 07:02:53 -05:00
Compare commits
12 commits
f6374eecfc
...
7dfef2a2dc
Author | SHA1 | Date | |
---|---|---|---|
7dfef2a2dc | |||
78fceae000 | |||
f75654fa18 | |||
5399a7dbb4 | |||
4e0df5412d | |||
e1a6a80659 | |||
df3f3b9373 | |||
17a29fef8e | |||
f88202221e | |||
7b9140ae22 | |||
3bd0980313 | |||
002eda0612 |
13 changed files with 190 additions and 72 deletions
|
@ -2,8 +2,8 @@
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"cz",
|
"cz",
|
||||||
"experimental",
|
"pak_explorer",
|
||||||
"luca_pak",
|
"luca_pak", "utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|
70
README.md
70
README.md
|
@ -6,15 +6,18 @@ Tested on the following games:
|
||||||
- Little Busters! English Edition (2017)
|
- Little Busters! English Edition (2017)
|
||||||
- LOOPERS (2023)
|
- LOOPERS (2023)
|
||||||
- Harmonia Full HD Edition (2024)
|
- Harmonia Full HD Edition (2024)
|
||||||
|
- Kanon (2024)
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
The implementation for decompression of CZ1, CZ3, and CZ4 was originally derived from
|
The implementation for decompression of CZ1, CZ3, and CZ4 was originally
|
||||||
[GARbro](https://github.com/morkt/GARbro/). The implementation of compresssion
|
derived from [GARbro](https://github.com/morkt/GARbro/). The implementation of
|
||||||
and decompression of CZ1, CZ2, CZ3, and CZ4 was derived from [LuckSystem](https://github.com/wetor/LuckSystem).
|
compresssion and decompression of CZ1, CZ2, CZ3, and CZ4 was derived from
|
||||||
This project would not have been possible without their amazing work.
|
[LuckSystem](https://github.com/wetor/LuckSystem). This project would not have
|
||||||
|
been possible without their amazing work.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
These decoders and encoders are structured as libraries first and tools second. It's possible to use them as a base to build other applications.
|
These decoders and encoders are structured as libraries first and tools second.
|
||||||
|
It's possible to use them as a base to build other applications.
|
||||||
|
|
||||||
### CZ Images
|
### CZ Images
|
||||||
Completely accurate CZ# file decoding and encoding. Read more about that here:
|
Completely accurate CZ# file decoding and encoding. Read more about that here:
|
||||||
|
@ -22,4 +25,59 @@ Completely accurate CZ# file decoding and encoding. Read more about that here:
|
||||||
https://g2games.dev/blog/2024/06/28/the-cz-image-formats/
|
https://g2games.dev/blog/2024/06/28/the-cz-image-formats/
|
||||||
|
|
||||||
### PAK Archives
|
### PAK Archives
|
||||||
Partial implementation of PAK files, enough to extract data from most I've encountered, and replace data as long as decoding is successful. Any extra metadata can't be changed as of yet, however.
|
Partial implementation of PAK files, enough to extract data from most I've
|
||||||
|
encountered, and replace data as long as decoding is successful. Any extra
|
||||||
|
metadata can't be changed as of yet, however.
|
||||||
|
|
||||||
|
## Programs
|
||||||
|
|
||||||
|
### [lbee-utils](https://github.com/G2-Games/lbee-utils/releases/tag/utils-0.1.0)
|
||||||
|
Small command line tools for modifying CZ images and PAK archives. Usage for each
|
||||||
|
is as follows:
|
||||||
|
|
||||||
|
#### pakutil
|
||||||
|
```
|
||||||
|
Utility to maniuplate PAK archive files from the LUCA System game engine by Prototype Ltd
|
||||||
|
|
||||||
|
Usage: pakutil <PAK FILE> <COMMAND>
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
extract Extracts the contents of a PAK file into a folder
|
||||||
|
replace Replace the entries in a PAK file
|
||||||
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<PAK FILE>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Print help
|
||||||
|
-V, --version Print version
|
||||||
|
```
|
||||||
|
|
||||||
|
#### czutil
|
||||||
|
```
|
||||||
|
Utility to maniuplate CZ image files from the LUCA System game engine by Prototype Ltd
|
||||||
|
|
||||||
|
Usage: czutil <COMMAND>
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
decode Converts a CZ file to a PNG
|
||||||
|
replace Replace a CZ file's image data
|
||||||
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Print help
|
||||||
|
-V, --version Print version
|
||||||
|
```
|
||||||
|
------
|
||||||
|
|
||||||
|
### [PAK Explorer](https://github.com/G2-Games/lbee-utils/releases/tag/explorer-0.1.1)
|
||||||
|
This is a basic explorer application for PAK files which allows you to see
|
||||||
|
their contents, replace the contents, extract files, and save them again.
|
||||||
|
|
||||||
|
While this is a useful tool for just viewing and extracting the contents of
|
||||||
|
a PAK file, it is recommended to use the command line tools for doing
|
||||||
|
anything important as they offer many more options and allow for batch
|
||||||
|
operations on many files at once.
|
||||||
|
|
||||||
|

|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cz"
|
name = "cz"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
description="""
|
description="""
|
||||||
A encoder/decoder for CZ# image files used in the LUCA System Engine.
|
An encoder/decoder for CZ# image files used in the LUCA System engine by
|
||||||
|
Prototype Ltd.
|
||||||
"""
|
"""
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
png = ["dep:image"]
|
png = ["dep:image"]
|
||||||
binary = ["dep:clap"]
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "czutil"
|
|
||||||
required-features = ["binary", "png"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.5"
|
byteorder = "1.5"
|
||||||
|
@ -22,11 +18,8 @@ thiserror = "1.0"
|
||||||
imagequant = "4.3"
|
imagequant = "4.3"
|
||||||
rgb = "0.8"
|
rgb = "0.8"
|
||||||
|
|
||||||
# Only active on features "png" and "binary"
|
# Only active on PNG feature
|
||||||
image = { version = "0.25", optional = true }
|
image = { version = "0.25", optional = true }
|
||||||
|
|
||||||
# Only active on feature "binary"
|
|
||||||
clap = { version = "4.5.8", features = ["derive"], optional = true }
|
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
@ -54,7 +54,7 @@ impl DynamicCz {
|
||||||
CzVersion::CZ2 => cz2::decode(input)?,
|
CzVersion::CZ2 => cz2::decode(input)?,
|
||||||
CzVersion::CZ3 => cz3::decode(input, &header_common)?,
|
CzVersion::CZ3 => cz3::decode(input, &header_common)?,
|
||||||
CzVersion::CZ4 => cz4::decode(input, &header_common)?,
|
CzVersion::CZ4 => cz4::decode(input, &header_common)?,
|
||||||
CzVersion::CZ5 => unimplemented!(),
|
CzVersion::CZ5 => unimplemented!("CZ5 files are not implemented! Please contact the application developers about this file."),
|
||||||
};
|
};
|
||||||
|
|
||||||
let image_size = header_common.width() as usize * header_common.height() as usize;
|
let image_size = header_common.width() as usize * header_common.height() as usize;
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
[package]
|
[package]
|
||||||
name = "luca_pak"
|
name = "luca_pak"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
description = """
|
description = """
|
||||||
A crate for parsing and modifying PAK files from the LUCA System engine by
|
An encoder/decoder for PAK archive files used in the LUCA System engine by
|
||||||
Prototype Ltd.
|
Prototype Ltd.
|
||||||
"""
|
"""
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "pakutil"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.5.0"
|
||||||
clap = { version = "4.5.8", features = ["derive"] }
|
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
thiserror = "1.0.61"
|
thiserror = "1.0.61"
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
error::Error, fmt, fs::File, io::{BufWriter, Write}, path::Path
|
error::Error, fs::File, io::{BufWriter, Write}, path::Path
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A single file entry in a PAK file
|
/// A single file entry in a PAK file
|
||||||
|
@ -33,6 +33,10 @@ impl Entry {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn index(&self) -> usize {
|
||||||
|
self.index
|
||||||
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> u32 {
|
pub fn id(&self) -> u32 {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ pub struct Header {
|
||||||
pub(super) id_start: u32,
|
pub(super) id_start: u32,
|
||||||
pub(super) block_size: u32,
|
pub(super) block_size: u32,
|
||||||
|
|
||||||
pub(super) unknown1: u32,
|
/// The offset of the subdirectory name within the PAK
|
||||||
|
pub(super) subdir_offset: u32,
|
||||||
pub(super) unknown2: u32,
|
pub(super) unknown2: u32,
|
||||||
pub(super) unknown3: u32,
|
pub(super) unknown3: u32,
|
||||||
pub(super) unknown4: u32,
|
pub(super) unknown4: u32,
|
||||||
|
@ -28,7 +29,7 @@ impl Header {
|
||||||
output.write_u32::<LE>(self.entry_count)?;
|
output.write_u32::<LE>(self.entry_count)?;
|
||||||
output.write_u32::<LE>(self.id_start)?;
|
output.write_u32::<LE>(self.id_start)?;
|
||||||
output.write_u32::<LE>(self.block_size)?;
|
output.write_u32::<LE>(self.block_size)?;
|
||||||
output.write_u32::<LE>(self.unknown1)?;
|
output.write_u32::<LE>(self.subdir_offset)?;
|
||||||
output.write_u32::<LE>(self.unknown2)?;
|
output.write_u32::<LE>(self.unknown2)?;
|
||||||
output.write_u32::<LE>(self.unknown3)?;
|
output.write_u32::<LE>(self.unknown3)?;
|
||||||
output.write_u32::<LE>(self.unknown4)?;
|
output.write_u32::<LE>(self.unknown4)?;
|
||||||
|
|
|
@ -33,6 +33,8 @@ pub enum PakError {
|
||||||
/// A full PAK file with a header and its contents
|
/// A full PAK file with a header and its contents
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Pak {
|
pub struct Pak {
|
||||||
|
subdirectory: Option<String>,
|
||||||
|
|
||||||
/// The path of the PAK file, can serve as an identifier or name as the
|
/// The path of the PAK file, can serve as an identifier or name as the
|
||||||
/// header has no name for the file.
|
/// header has no name for the file.
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -72,7 +74,7 @@ impl Pak {
|
||||||
entry_count: input.read_u32::<LE>()?,
|
entry_count: input.read_u32::<LE>()?,
|
||||||
id_start: input.read_u32::<LE>()?,
|
id_start: input.read_u32::<LE>()?,
|
||||||
block_size: input.read_u32::<LE>()?,
|
block_size: input.read_u32::<LE>()?,
|
||||||
unknown1: input.read_u32::<LE>()?,
|
subdir_offset: input.read_u32::<LE>()?,
|
||||||
unknown2: input.read_u32::<LE>()?,
|
unknown2: input.read_u32::<LE>()?,
|
||||||
unknown3: input.read_u32::<LE>()?,
|
unknown3: input.read_u32::<LE>()?,
|
||||||
unknown4: input.read_u32::<LE>()?,
|
unknown4: input.read_u32::<LE>()?,
|
||||||
|
@ -130,16 +132,15 @@ impl Pak {
|
||||||
|
|
||||||
// Read all the file names
|
// Read all the file names
|
||||||
let mut file_names = None;
|
let mut file_names = None;
|
||||||
|
let mut subdirectory = None;
|
||||||
if header.flags.has_names() {
|
if header.flags.has_names() {
|
||||||
debug!("READING: file_names");
|
debug!("READING: file_names");
|
||||||
let mut string_buf = Vec::new();
|
if header.subdir_offset != 0 {
|
||||||
|
subdirectory = Some(read_cstring(&mut input)?);
|
||||||
|
}
|
||||||
file_names = Some(Vec::new());
|
file_names = Some(Vec::new());
|
||||||
for _ in 0..header.entry_count() {
|
for _ in 0..header.entry_count() {
|
||||||
string_buf.clear();
|
let strbuf = read_cstring(&mut input)?;
|
||||||
input.read_until(0x00, &mut string_buf)?;
|
|
||||||
string_buf.pop();
|
|
||||||
|
|
||||||
let strbuf = String::from_utf8_lossy(&string_buf).to_string();
|
|
||||||
file_names.as_mut().unwrap().push(strbuf.clone());
|
file_names.as_mut().unwrap().push(strbuf.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,6 +190,7 @@ impl Pak {
|
||||||
debug!("Entry list contains {} entries", entries.len());
|
debug!("Entry list contains {} entries", entries.len());
|
||||||
|
|
||||||
Ok(Pak {
|
Ok(Pak {
|
||||||
|
subdirectory,
|
||||||
header,
|
header,
|
||||||
unknown_pre_data,
|
unknown_pre_data,
|
||||||
entries,
|
entries,
|
||||||
|
@ -211,7 +213,6 @@ impl Pak {
|
||||||
&self,
|
&self,
|
||||||
mut output: &mut T
|
mut output: &mut T
|
||||||
) -> Result<(), PakError> {
|
) -> Result<(), PakError> {
|
||||||
let mut block_offset = 0;
|
|
||||||
self.header.write_into(&mut output)?;
|
self.header.write_into(&mut output)?;
|
||||||
|
|
||||||
// Write unknown data
|
// Write unknown data
|
||||||
|
@ -237,6 +238,11 @@ impl Pak {
|
||||||
|
|
||||||
// Write names if the flags indicate it should have them
|
// Write names if the flags indicate it should have them
|
||||||
if self.header.flags().has_names() {
|
if self.header.flags().has_names() {
|
||||||
|
if let Some(subdir) = &self.subdirectory {
|
||||||
|
output.write_all(
|
||||||
|
CString::new(subdir.as_bytes()).unwrap().to_bytes_with_nul()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
for entry in self.entries() {
|
for entry in self.entries() {
|
||||||
let name = entry.name.as_ref().unwrap();
|
let name = entry.name.as_ref().unwrap();
|
||||||
output.write_all(
|
output.write_all(
|
||||||
|
@ -247,18 +253,22 @@ impl Pak {
|
||||||
|
|
||||||
output.write_all(&self.unknown_post_header)?;
|
output.write_all(&self.unknown_post_header)?;
|
||||||
|
|
||||||
block_offset += self.header().data_offset / self.header().block_size;
|
//let mut block_offset = self.header().data_offset / self.header().block_size;
|
||||||
|
|
||||||
for entry in self.entries() {
|
for entry in self.entries() {
|
||||||
let block_size = entry.data.len().div_ceil(self.header().block_size as usize);
|
//let block_size = entry.data.len().div_ceil(self.header().block_size as usize);
|
||||||
let remainder = 2048 - entry.data.len().rem_euclid(self.header().block_size as usize);
|
let mut remainder = 2048 - entry.data.len().rem_euclid(self.header().block_size as usize);
|
||||||
|
if remainder == 2048 {
|
||||||
debug!("entry {:?} len {}", entry.name(), entry.data.len());
|
remainder = 0;
|
||||||
debug!("remainder {}", remainder);
|
}
|
||||||
debug!("block_offset {} - expected offset {}", block_offset, entry.offset);
|
|
||||||
output.write_all(&entry.data)?;
|
output.write_all(&entry.data)?;
|
||||||
output.write_all(&vec![0u8; remainder])?;
|
output.write_all(&vec![0u8; remainder])?;
|
||||||
block_offset += block_size as u32;
|
|
||||||
|
//println!("entry len {}", entry.data.len());
|
||||||
|
//println!("remainder {}", remainder);
|
||||||
|
//println!("block_offset {} - expected offset {}", block_offset, entry.offset);
|
||||||
|
|
||||||
|
//block_offset += block_size as u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -384,3 +394,11 @@ impl Pak {
|
||||||
.is_some_and(|n| n == name))
|
.is_some_and(|n| n == name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_cstring<T: Seek + Read + BufRead>(input: &mut T) -> Result<String, io::Error> {
|
||||||
|
let mut string_buf = vec![];
|
||||||
|
input.read_until(0x00, &mut string_buf)?;
|
||||||
|
string_buf.pop();
|
||||||
|
|
||||||
|
Ok(String::from_utf8_lossy(&string_buf).to_string())
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "experimental"
|
name = "pak_explorer"
|
||||||
description = """
|
|
||||||
A package for experimentation, not for publishing
|
|
||||||
"""
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
version = "0.1.1"
|
||||||
|
description = """
|
||||||
|
A simple GUI for exploring and making modifications to LUCA System PAK files.
|
||||||
|
"""
|
||||||
|
license = "AGPL-3.0"
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
publish = false
|
publish = false
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::fs;
|
||||||
|
|
||||||
use eframe::{egui::{self, text::{LayoutJob, TextWrapping}, ColorImage, Image, Rgba, TextBuffer, TextureFilter, TextureHandle, TextureOptions}, epaint::Fonts};
|
use eframe::egui::{self, ColorImage, Image, TextureFilter, TextureHandle, TextureOptions};
|
||||||
use luca_pak::{entry::EntryType, header, Pak};
|
use luca_pak::{entry::EntryType, Pak};
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
|
@ -47,6 +47,7 @@ impl eframe::App for PakExplorer {
|
||||||
ctx.set_pixels_per_point(1.5);
|
ctx.set_pixels_per_point(1.5);
|
||||||
ui.heading("PAK File Explorer");
|
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() {
|
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
||||||
let pak = Pak::open(&path).unwrap();
|
let pak = Pak::open(&path).unwrap();
|
||||||
|
@ -56,6 +57,17 @@ impl eframe::App for PakExplorer {
|
||||||
self.hex_string = None;
|
self.hex_string = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(pak) = &self.open_file {
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
pak.save(&path).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
@ -92,6 +104,7 @@ impl eframe::App for PakExplorer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(entry) = &self.selected_entry {
|
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()
|
if let Some(path) = rfd::FileDialog::new()
|
||||||
.set_file_name(entry.display_name())
|
.set_file_name(entry.display_name())
|
||||||
|
@ -100,6 +113,16 @@ impl eframe::App for PakExplorer {
|
||||||
entry.save(&path).unwrap();
|
entry.save(&path).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(pak) = &mut self.open_file.as_mut() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
match entry.file_type() {
|
match entry.file_type() {
|
||||||
EntryType::CZ0 | EntryType::CZ1
|
EntryType::CZ0 | EntryType::CZ1
|
||||||
| EntryType::CZ2 | EntryType::CZ3
|
| EntryType::CZ2 | EntryType::CZ3
|
||||||
|
@ -147,7 +170,7 @@ impl eframe::App for PakExplorer {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else if self.open_file.is_some() {
|
||||||
ui.centered_and_justified(|ui|
|
ui.centered_and_justified(|ui|
|
||||||
ui.label("Select an Entry")
|
ui.label("Select an Entry")
|
||||||
);
|
);
|
21
utils/Cargo.toml
Normal file
21
utils/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "czutil"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "pakutil"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cz = { path = "../cz/", features = ["png"] }
|
||||||
|
luca_pak = { path = "../luca_pak/" }
|
||||||
|
|
||||||
|
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||||
|
clap = { version = "4.5.9", features = ["derive"] }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
|
@ -1,8 +1,10 @@
|
||||||
use clap::{error::ErrorKind, Error, Parser, Subcommand};
|
use clap::{error::ErrorKind, Error, Parser, Subcommand};
|
||||||
use std::{fs, path::{Path, PathBuf}};
|
use std::{fs, path::{Path, PathBuf}};
|
||||||
|
|
||||||
|
/// Utility to maniuplate CZ image files from the LUCA System game engine by
|
||||||
|
/// Prototype Ltd.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "CZ Utils")]
|
#[command(name = "CZ Utility")]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
|
@ -2,8 +2,10 @@ use std::{fs, path::PathBuf};
|
||||||
use clap::{error::{Error, ErrorKind}, Parser, Subcommand};
|
use clap::{error::{Error, ErrorKind}, Parser, Subcommand};
|
||||||
use luca_pak::Pak;
|
use luca_pak::Pak;
|
||||||
|
|
||||||
|
/// Utility to maniuplate PAK archive files from the LUCA System game engine by
|
||||||
|
/// Prototype Ltd.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "CZ Utils")]
|
#[command(name = "PAK Utility")]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
#[arg(value_name = "PAK FILE")]
|
#[arg(value_name = "PAK FILE")]
|
Loading…
Reference in a new issue