diff --git a/Cargo.toml b/Cargo.toml index fb4c4d1..be15ac2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [workspace] #resolver = "2" members = [ - "crates/jmmt", - "crates/jmrenamer" -# "crates/paktool" + "crates/jmmt", + "crates/jmrenamer", + "crates/textool" +# "crates/paktool" ] diff --git a/crates/jmmt/src/format/mod.rs b/crates/jmmt/src/format/mod.rs index b1975b5..3a3de8d 100644 --- a/crates/jmmt/src/format/mod.rs +++ b/crates/jmmt/src/format/mod.rs @@ -1,10 +1,37 @@ -//! Low-level structure definitions +//! Low-level file format definitions. These are suitable for usage with the [binext] crate, +//! which just so happens to be a dependency of this crate! Funny how things work. pub mod package; pub mod package_toc; +pub mod ps2_texture; +pub mod ps2_palette; /// A trait validatable format objects should implement. +/// TODO: integrate this with some FourCC crate, or re-invent the wheel. pub trait Validatable { /// Returns true if the object is valid, false otherwise. fn valid(&self) -> bool; } + +/// Make a Rust [String] from a byte slice that came from a C string/structure. +/// +/// # Usage +/// +/// The byte slice has to be a valid UTF-8 string. +/// (Note that in most cases, ASCII strings are valid UTF-8, so this isn't something you'll particularly +/// have to worry about). +/// +/// # Safety +/// +/// This function does not directly make use of any unsafe Rust code. +pub fn make_c_string(bytes: &[u8]) -> Option { + let bytes_without_null = match bytes.iter().position(|&b| b == 0) { + Some(ix) => &bytes[..ix], + None => bytes, + }; + + match std::str::from_utf8(bytes_without_null).ok() { + Some(string) => Some(String::from(string)), + None => None + } +} diff --git a/crates/jmmt/src/format/package.rs b/crates/jmmt/src/format/package.rs index 469e03c..1e4a1f1 100644 --- a/crates/jmmt/src/format/package.rs +++ b/crates/jmmt/src/format/package.rs @@ -18,6 +18,7 @@ pub struct PackageEofHeader { /// A Package Group. I have no idea what this is yet #[repr(C)] +#[derive(Debug, Default)] pub struct PackageGroup { pub fourcc: u32, @@ -44,6 +45,7 @@ impl Validatable for PackageGroup { /// A package file chunk. #[repr(C)] +#[derive(Debug, Default)] pub struct PackageFileChunk { pub fourcc: u32, diff --git a/crates/jmmt/src/format/package_toc.rs b/crates/jmmt/src/format/package_toc.rs index b2ad43a..cbc238b 100644 --- a/crates/jmmt/src/format/package_toc.rs +++ b/crates/jmmt/src/format/package_toc.rs @@ -1,5 +1,7 @@ //! Package.toc structures +use super::make_c_string; + /// An entry inside the `package.toc` file #[derive(Debug)] #[repr(C)] @@ -15,7 +17,7 @@ pub struct PackageTocEntry { impl PackageTocEntry { fn file_name(&self) -> Option { - String::from_utf8(self.file_name.to_vec()).ok() + make_c_string(&self.file_name) } fn file_name_hash(&self) -> u32 { diff --git a/crates/jmmt/src/format/ps2_palette.rs b/crates/jmmt/src/format/ps2_palette.rs new file mode 100644 index 0000000..b501eae --- /dev/null +++ b/crates/jmmt/src/format/ps2_palette.rs @@ -0,0 +1,32 @@ +//! .ps2_palette structures + +use super::Validatable; + +/// .ps2_palette header +#[repr(C)] +#[derive(Debug, Default)] +pub struct Ps2PaletteHeader { + pub fourcc: u32, + pub unk: u32, + pub unk2: u16, + + pub color_count: u16, + pub palette_bpp: u16, + pub unk3: u16, + + pub data_start: u32, + pub header_size: u32, + + pub pad: [u32; 6] // reserved for game code, like .ps2_texture? +} + +impl Ps2PaletteHeader { + /// 'PAL1' + pub const VALID_FOURCC : u32 = 0x314c4150; +} + +impl Validatable for Ps2PaletteHeader { + fn valid(&self) -> bool { + self.fourcc == Self::VALID_FOURCC && self.header_size == 0x18 + } +} diff --git a/crates/jmmt/src/format/ps2_texture.rs b/crates/jmmt/src/format/ps2_texture.rs new file mode 100644 index 0000000..377a3e0 --- /dev/null +++ b/crates/jmmt/src/format/ps2_texture.rs @@ -0,0 +1,44 @@ +//! .ps2_texture structures + +use super::Validatable; + +/// .ps2_texture header. +#[repr(C)] +#[derive(Debug, Default)] +pub struct Ps2TextureHeader { + pub magic: u32, + pub unk: u32, + pub unk2: u16, + pub width: u16, + pub height: u16, + + /// bits-per-pixel of the texture data. Anything above 8 + /// will not have an associated .ps2_palette file, + /// since the texture data will not be indirect color. + pub bpp: u16, + + /// Data start offset. + pub data_start_offset: u32, + + /// Possibly the size of this header. + pub header_end_offset: u32, + + pub unk6: [u32; 8], // mostly unrelated values, this is probably padding space for the game code to put stuff +} + +impl Ps2TextureHeader { + /// 'TEX1' + pub const VALID_FOURCC: u32 = 0x31584554; + + fn has_palette(&self) -> bool { + // if the BPP is less than or equal to 8 (I've only seen 8bpp and 16bpp), + // then the texture will be palettized. + self.bpp >= 8 + } +} + +impl Validatable for Ps2TextureHeader { + fn valid(&self) -> bool { + self.magic == Self::VALID_FOURCC && self.header_end_offset == 0x38 + } +} diff --git a/crates/jmmt/src/lib.rs b/crates/jmmt/src/lib.rs index 461f0c0..bbb6a41 100644 --- a/crates/jmmt/src/lib.rs +++ b/crates/jmmt/src/lib.rs @@ -10,3 +10,6 @@ pub mod lzss; // higher level I/O? // pub mod read; // pub mod write; + +// Maybe, using package.toc? +// pub mod pakfs; diff --git a/crates/jmmt/src/lzss/header.rs b/crates/jmmt/src/lzss/header.rs index 5e284f9..b494f37 100644 --- a/crates/jmmt/src/lzss/header.rs +++ b/crates/jmmt/src/lzss/header.rs @@ -2,6 +2,7 @@ use std::mem::size_of; use crate::format::Validatable; #[repr(C)] +#[derive(Debug, Default)] pub struct LzssHeader { pub next: u32, // ps2 ptr. usually 0 cause theres no next header pub byte_id: u8, @@ -25,4 +26,4 @@ impl Validatable for LzssHeader { fn valid(&self) -> bool { self.byte_id == 0x91 && self.header_size as usize == size_of::() } -} \ No newline at end of file +} diff --git a/crates/textool/Cargo.toml b/crates/textool/Cargo.toml new file mode 100644 index 0000000..b3d9a47 --- /dev/null +++ b/crates/textool/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "textool" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = { version = "4.3.8", features = ["derive"] } +image = "0.24.6" +jmmt = { path = "../jmmt" } + +# Temporary, until the reader code is thrown into jmmt crate +binext = "1.0.0" diff --git a/crates/textool/src/main.rs b/crates/textool/src/main.rs new file mode 100644 index 0000000..998ce74 --- /dev/null +++ b/crates/textool/src/main.rs @@ -0,0 +1,65 @@ +use clap::{Parser, Subcommand}; + +use std::default; +use std::path::Path; +use std::fs::{ + File +}; + +use jmmt::format::{ + ps2_palette::*, + ps2_texture::* +}; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +#[command(propagate_version = true)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Exports the texture to a .png file + Export { path: String }, + + //Import +} + +struct ImageReader { + file: File, + header: Ps2TextureHeader, + pal_header: Ps2PaletteHeader, + + image_data: Vec, + palette_data: Vec +} + +impl ImageReader { + fn new(file: &mut File) -> Self { + ImageReader { + file: file.try_clone().unwrap(), + header: Ps2TextureHeader::default(), + pal_header: Ps2PaletteHeader::default(), + image_data: Vec::default(), + palette_data: Vec::default() + } + } + + fn read(&mut self) -> std::io::Result<()> { + + Ok(()) + } +} + +fn main() { + let cli = Cli::parse(); + + match &cli.command { + Commands::Export { path } => { + println!("exporting {}", path); + let path = Path::new(path); + } + } +}