Compare commits

..

No commits in common. "263a990e6ef34a2fcb8ede6a4318a913589ed024" and "4d58df5d154a246aa009cb3064e69187b5a1a0b9" have entirely different histories.

6 changed files with 161 additions and 152 deletions

View file

@ -1,80 +1,70 @@
pub struct BitIo { use std::io::{self, Read, Write};
data: Vec<u8>,
use byteorder::{ReadBytesExt, WriteBytesExt};
/// A simple way to write individual bits to an input implementing [Write].
pub struct BitWriter<'a, O: Write + WriteBytesExt> {
output: &'a mut O,
current_byte: u8,
byte_offset: usize, byte_offset: usize,
bit_offset: usize, bit_offset: usize,
byte_size: usize, byte_size: usize,
} }
impl BitIo { impl<'a, O: Write + WriteBytesExt> BitWriter<'a, O> {
/// Create a new BitIO reader and writer over some data /// Create a new BitWriter wrapper around something which
pub fn new(data: Vec<u8>) -> Self { /// implements [Write].
pub fn new(output: &'a mut O) -> Self {
Self { Self {
data, output,
current_byte: 0,
byte_offset: 0, byte_offset: 0,
bit_offset: 0, bit_offset: 0,
byte_size: 0, byte_size: 0,
} }
} }
/// Get the byte offset of the reader /// Get the number of whole bytes written to the stream.
pub fn byte_offset(&self) -> usize {
self.byte_offset
}
/// Get the byte size of the reader
pub fn byte_size(&self) -> usize { pub fn byte_size(&self) -> usize {
self.byte_size self.byte_size
} }
/// Get the current bytes up to `byte_size` in the reader /// Get the bit offset within the current byte.
pub fn bytes(&self) -> Vec<u8> { pub fn bit_offset(&self) -> u8 {
self.data[..self.byte_size].to_vec() self.bit_offset as u8
} }
/// Read some bits from the buffer /// Check if the stream is aligned to a byte.
pub fn read_bit(&mut self, bit_len: usize) -> u64 { pub fn aligned(&self) -> bool {
if bit_len > 8 * 8 { self.bit_offset() == 0
panic!("Cannot read more than 64 bits")
}
if bit_len % 8 == 0 && self.bit_offset == 0 {
return self.read(bit_len / 8);
}
let mut result = 0;
for i in 0..bit_len {
let bit_value = ((self.data[self.byte_offset] as usize >> self.bit_offset) & 1) as u64;
self.bit_offset += 1;
if self.bit_offset == 8 {
self.byte_offset += 1;
self.bit_offset = 0;
}
result |= bit_value << i;
}
result
} }
/// Read some bytes from the buffer /// Align the writer to the nearest byte by padding with zero bits.
pub fn read(&mut self, byte_len: usize) -> u64 { ///
if byte_len > 8 { /// Returns the number of zero bits
panic!("Cannot read more than 8 bytes") pub fn flush(&mut self) -> Result<usize, io::Error> {
} self.byte_offset += 1;
let mut padded_slice = [0u8; 8]; // Write out the current byte unfinished
padded_slice.copy_from_slice(&self.data[self.byte_offset..self.byte_offset + byte_len]); self.output.write_u8(self.current_byte).unwrap();
self.byte_offset += byte_len; self.current_byte = 0;
self.bit_offset = 0;
u64::from_le_bytes(padded_slice) Ok(8 - self.bit_offset)
} }
/// Write some bits to the buffer /// Write some bits to the output.
pub fn write_bit(&mut self, data: u64, bit_len: usize) { pub fn write_bit(&mut self, data: u64, bit_len: usize) {
if bit_len > 8 * 8 { if bit_len > 64 {
panic!("Cannot write more than 64 bits"); panic!("Cannot write more than 64 bits at once.");
} else if bit_len == 0 {
panic!("Must write 1 or more bits.")
} }
if bit_len % 8 == 0 && self.bit_offset == 0 { if bit_len % 8 == 0 && self.bit_offset == 0 {
@ -85,32 +75,115 @@ impl BitIo {
for i in 0..bit_len { for i in 0..bit_len {
let bit_value = (data >> i) & 1; let bit_value = (data >> i) & 1;
self.data[self.byte_offset] &= !(1 << self.bit_offset); self.current_byte &= !(1 << self.bit_offset);
self.data[self.byte_offset] |= (bit_value << self.bit_offset) as u8; self.current_byte |= (bit_value << self.bit_offset) as u8;
self.bit_offset += 1; self.bit_offset += 1;
if self.bit_offset == 8 { if self.bit_offset >= 8 {
self.byte_offset += 1; self.byte_offset += 1;
self.bit_offset = 0; self.bit_offset = 0;
self.output.write_u8(self.current_byte).unwrap();
self.current_byte = 0;
} }
} }
self.byte_size = self.byte_offset + (self.bit_offset + 7) / 8; self.byte_size = self.byte_offset + (self.bit_offset + 7) / 8;
} }
/// Write some bytes to the output.
pub fn write(&mut self, data: u64, byte_len: usize) { pub fn write(&mut self, data: u64, byte_len: usize) {
if byte_len > 8 { if byte_len > 8 {
panic!("Cannot write more than 8 bytes") panic!("Cannot write more than 8 bytes at once.")
} else if byte_len == 0 {
panic!("Must write 1 or more bytes.")
} }
let mut padded_slice = [0u8; 8]; self.output
padded_slice.copy_from_slice(&data.to_le_bytes()); .write_all(&data.to_le_bytes()[..byte_len])
.unwrap();
self.data[self.byte_offset..self.byte_offset + byte_len]
.copy_from_slice(&padded_slice[..byte_len]);
self.byte_offset += byte_len; self.byte_offset += byte_len;
self.byte_size = self.byte_offset + (self.bit_offset + 7) / 8; self.byte_size = self.byte_offset + (self.bit_offset + 7) / 8;
} }
} }
/// A simple way to read individual bits from an input implementing [Read].
pub struct BitReader<'a, I: Read + ReadBytesExt> {
input: &'a mut I,
current_byte: Option<u8>,
byte_offset: usize,
bit_offset: usize,
}
impl<'a, I: Read + ReadBytesExt> BitReader<'a, I> {
/// Create a new BitReader wrapper around something which
/// implements [Write].
pub fn new(input: &'a mut I) -> Self {
let first = input.read_u8().unwrap();
Self {
input,
current_byte: Some(first),
byte_offset: 0,
bit_offset: 0,
}
}
/// Get the number of whole bytes read from the stream.
pub fn byte_offset(&self) -> usize {
self.byte_offset
}
/// Read some bits from the input.
pub fn read_bit(&mut self, bit_len: usize) -> u64 {
if bit_len > 64 {
panic!("Cannot read more than 64 bits at once.")
} else if bit_len == 0 {
panic!("Must read 1 or more bits.")
}
if bit_len % 8 == 0 && self.bit_offset == 0 {
return self.read(bit_len / 8);
}
let mut result = 0;
for i in 0..bit_len {
let bit_value = ((self.current_byte.unwrap() as usize >> self.bit_offset) & 1) as u64;
self.bit_offset += 1;
if self.bit_offset == 8 {
self.byte_offset += 1;
self.bit_offset = 0;
self.current_byte = Some(self.input.read_u8().unwrap());
}
result |= bit_value << i;
}
result
}
/// Read some bytes from the input.
pub fn read(&mut self, byte_len: usize) -> u64 {
if byte_len > 8 {
panic!("Cannot read more than 8 bytes at once.")
} else if byte_len == 0 {
panic!("Must read 1 or more bytes")
}
let mut padded_slice = vec![0u8; byte_len];
self.input.read_exact(&mut padded_slice).unwrap();
self.byte_offset += byte_len;
let extra_length = padded_slice.len() - byte_len;
padded_slice.extend_from_slice(&vec![0u8; extra_length]);
u64::from_le_bytes(padded_slice.try_into().unwrap())
}
}

View file

@ -1,10 +1,10 @@
use byteorder::{ReadBytesExt, WriteBytesExt, LE}; use byteorder::{ReadBytesExt, WriteBytesExt, LE};
use std::{ use std::{
collections::HashMap, collections::HashMap,
io::{Read, Seek, Write}, io::{Cursor, Read, Seek, Write},
}; };
use crate::binio::BitIo; use crate::binio::{BitReader, BitWriter};
use crate::common::CzError; use crate::common::CzError;
/// The size of compressed data in each chunk /// The size of compressed data in each chunk
@ -163,7 +163,7 @@ pub fn decompress2<T: Seek + ReadBytesExt + Read>(
} }
fn decompress_lzw2(input_data: &[u8], size: usize) -> Vec<u8> { fn decompress_lzw2(input_data: &[u8], size: usize) -> Vec<u8> {
let data = input_data; let mut data = Cursor::new(input_data);
let mut dictionary = HashMap::new(); let mut dictionary = HashMap::new();
for i in 0..256 { for i in 0..256 {
dictionary.insert(i as u64, vec![i as u8]); dictionary.insert(i as u64, vec![i as u8]);
@ -172,7 +172,7 @@ fn decompress_lzw2(input_data: &[u8], size: usize) -> Vec<u8> {
let mut result = Vec::with_capacity(size); let mut result = Vec::with_capacity(size);
let data_size = input_data.len(); let data_size = input_data.len();
let mut bit_io = BitIo::new(data.to_vec()); let mut bit_io = BitReader::new(&mut data);
let mut w = dictionary.get(&0).unwrap().clone(); let mut w = dictionary.get(&0).unwrap().clone();
let mut element; let mut element;
@ -362,8 +362,9 @@ fn compress_lzw2(data: &[u8], last: Vec<u8>) -> (usize, Vec<u8>, Vec<u8>) {
element = last element = last
} }
let mut bit_io = BitIo::new(vec![0u8; 0xF0000]); let mut output_buf = Vec::new();
let write_bit = |bit_io: &mut BitIo, code: u64| { let mut bit_io = BitWriter::new(&mut output_buf);
let write_bit = |bit_io: &mut BitWriter<Vec<u8>>, code: u64| {
if code > 0x7FFF { if code > 0x7FFF {
bit_io.write_bit(1, 1); bit_io.write_bit(1, 1);
bit_io.write_bit(code, 18); bit_io.write_bit(code, 18);
@ -402,14 +403,17 @@ fn compress_lzw2(data: &[u8], last: Vec<u8>) -> (usize, Vec<u8>, Vec<u8>) {
} }
} }
return (count, bit_io.bytes(), Vec::new()); bit_io.flush().unwrap();
return (count, output_buf, Vec::new());
} else if bit_io.byte_size() < 0x87BDF { } else if bit_io.byte_size() < 0x87BDF {
if !last_element.is_empty() { if !last_element.is_empty() {
write_bit(&mut bit_io, *dictionary.get(&last_element).unwrap()); write_bit(&mut bit_io, *dictionary.get(&last_element).unwrap());
} }
return (count, bit_io.bytes(), Vec::new()); bit_io.flush().unwrap();
return (count, output_buf, Vec::new());
} }
(count, bit_io.bytes(), last_element) bit_io.flush().unwrap();
(count, output_buf, last_element)
} }

View file

@ -1,10 +1,9 @@
[package] [package]
name = "utils" name = "utils"
version = "0.2.0" version = "0.1.2"
edition = "2021" edition = "2021"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
authors.workspace = true authors.workspace = true
build = "build.rs"
[[bin]] [[bin]]
name = "czutil" name = "czutil"
@ -17,10 +16,7 @@ cz = { path = "../cz/", features = ["png"] }
luca_pak = { path = "../luca_pak/" } luca_pak = { path = "../luca_pak/" }
image = { version = "0.25", default-features = false, features = ["png"] } image = { version = "0.25", default-features = false, features = ["png"] }
clap = { version = "4.5", features = ["derive", "error-context"] } clap = { version = "4.5.9", features = ["derive"] }
[build-dependencies]
vergen-git2 = { version = "1.0", features = ["build", "cargo", "rustc", "si"] }
[lints] [lints]
workspace = true workspace = true

View file

@ -1,17 +0,0 @@
use vergen_git2::{BuildBuilder, CargoBuilder, Emitter, Git2Builder, RustcBuilder, SysinfoBuilder};
fn main() {
let build = BuildBuilder::all_build().unwrap();
let cargo = CargoBuilder::all_cargo().unwrap();
let git2 = Git2Builder::all_git().unwrap();
let rustc = RustcBuilder::all_rustc().unwrap();
let si = SysinfoBuilder::all_sysinfo().unwrap();
Emitter::default()
.add_instructions(&build).unwrap()
.add_instructions(&cargo).unwrap()
.add_instructions(&git2).unwrap()
.add_instructions(&rustc).unwrap()
.add_instructions(&si).unwrap()
.emit().unwrap();
}

View file

@ -1,22 +1,17 @@
use clap::{error::ErrorKind, Command, Error, Parser, Subcommand}; use clap::{error::ErrorKind, Error, Parser, Subcommand};
use std::{ use std::{
fs, fs,
path::{Path, PathBuf}, process::exit, path::{Path, PathBuf},
}; };
/// Utility to maniuplate CZ image files from the LUCA System game engine by /// Utility to maniuplate CZ image files from the LUCA System game engine by
/// Prototype Ltd. /// Prototype Ltd.
#[derive(Parser)] #[derive(Parser)]
#[command(name = "CZ Utility")] #[command(name = "CZ Utility")]
#[command(author, version, about, long_about = None, disable_version_flag = true)] #[command(version, about, long_about = None)]
#[command(arg_required_else_help(true))]
struct Cli { struct Cli {
/// Show program version information
#[arg(short('V'), long)]
version: bool,
#[command(subcommand)] #[command(subcommand)]
command: Option<Commands>, command: Commands,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -68,24 +63,8 @@ enum Commands {
fn main() { fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
if cli.version {
println!(
"{}, {} v{}-{}",
env!("CARGO_BIN_NAME"),
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
&env!("VERGEN_GIT_SHA")[0..=6]
);
exit(0);
}
let command = match cli.command {
Some(c) => c,
None => exit(0),
};
// Check what subcommand was run // Check what subcommand was run
match &command { match &cli.command {
Commands::Decode { Commands::Decode {
input, input,
output, output,

View file

@ -3,23 +3,19 @@ use clap::{
Parser, Subcommand, Parser, Subcommand,
}; };
use luca_pak::Pak; use luca_pak::Pak;
use std::{fs, path::PathBuf, process::exit}; use std::{fs, path::PathBuf};
/// Utility to maniuplate PAK archive files from the LUCA System game engine by /// Utility to maniuplate PAK archive files from the LUCA System game engine by
/// Prototype Ltd. /// Prototype Ltd.
#[derive(Parser)] #[derive(Parser)]
#[command(name = "PAK Utility")] #[command(name = "PAK Utility")]
#[command(author, version, about, long_about = None, disable_version_flag = true)] #[command(version, about, long_about = None)]
struct Cli { struct Cli {
/// Show program version information #[arg(value_name = "PAK FILE")]
#[arg(short('V'), long)] input: PathBuf,
version: bool,
#[arg(value_name = "PAK FILE", required_unless_present("version"))]
input: Option<PathBuf>,
#[command(subcommand)] #[command(subcommand)]
command: Option<Commands>, command: Commands,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -63,30 +59,12 @@ enum Commands {
fn main() { fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
if cli.version { let mut pak = match Pak::open(&cli.input) {
println!(
"{}, {} v{}-{}",
env!("CARGO_BIN_NAME"),
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
&env!("VERGEN_GIT_SHA")[0..=6]
);
exit(0);
}
let mut pak = match Pak::open(&cli.input.unwrap()) {
Ok(pak) => pak, Ok(pak) => pak,
Err(err) => fmt_error(&format!("Could not open PAK file: {}", err)).exit(), Err(err) => fmt_error(&format!("Could not open PAK file: {}", err)).exit(),
}; };
let command = match cli.command { match cli.command {
Some(c) => c,
None => {
exit(0);
},
};
match command {
Commands::Extract { output } => { Commands::Extract { output } => {
if output.exists() && !output.is_dir() { if output.exists() && !output.is_dir() {
fmt_error("The output given was not a directory").exit() fmt_error("The output given was not a directory").exit()
@ -96,11 +74,7 @@ fn main() {
for entry in pak.entries() { for entry in pak.entries() {
let mut outpath = output.clone(); let mut outpath = output.clone();
if let Some(n) = entry.name() { outpath.push(entry.display_name());
outpath.push(n);
} else {
outpath.push(entry.index().to_string())
}
entry.save(&outpath).unwrap(); entry.save(&outpath).unwrap();
} }
} }