From ef9e625d13763b4cb7d0b4e059f1dd42bd020c30 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 16 Feb 2023 20:10:20 -0500 Subject: [PATCH] Start parsing CMDL (WIP) --- src/cmd/cmdl.rs | 299 ++++++++++++++++++++++++++++++++++++++++++++++++ src/cmd/mod.rs | 11 +- src/cmd/pak.rs | 4 + src/main.rs | 13 ++- 4 files changed, 315 insertions(+), 12 deletions(-) create mode 100644 src/cmd/cmdl.rs diff --git a/src/cmd/cmdl.rs b/src/cmd/cmdl.rs new file mode 100644 index 0000000..9e968cb --- /dev/null +++ b/src/cmd/cmdl.rs @@ -0,0 +1,299 @@ +use std::{fs, io::Cursor, path::PathBuf}; + +use anyhow::{bail, ensure, Result}; +use argh::FromArgs; +use binrw::{binrw, BinReaderExt, Endian}; + +use crate::{ + format::{ + chunk::ChunkDescriptor, + pack::{K_CHUNK_META, K_FORM_FOOT}, + rfrm::FormDescriptor, + FourCC, + }, + util::{file::map_file, lzss::decompress_into}, +}; + +// Cooked model +pub const K_FORM_CMDL: FourCC = FourCC(*b"CMDL"); + +// Model header +// pub const K_CHUNK_HEAD: FourCC = FourCC(*b"HEAD"); +// Material data +// pub const K_CHUNK_MTRL: FourCC = FourCC(*b"MTRL"); +// Mesh data +// pub const K_CHUNK_MESH: FourCC = FourCC(*b"MESH"); +// Vertex buffer +pub const K_CHUNK_VBUF: FourCC = FourCC(*b"VBUF"); +// Index buffer +pub const K_CHUNK_IBUF: FourCC = FourCC(*b"IBUF"); +// GPU data +// pub const K_CHUNK_GPU: FourCC = FourCC(*b"GPU "); + +#[derive(FromArgs, PartialEq, Debug)] +/// process CMDL files +#[argh(subcommand, name = "cmdl")] +pub struct Args { + #[argh(subcommand)] + command: SubCommand, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +enum SubCommand { + Info(InfoArgs), +} + +#[derive(FromArgs, PartialEq, Eq, Debug)] +/// displays model information +#[argh(subcommand, name = "info")] +pub struct InfoArgs { + #[argh(positional)] + /// input CMDL + input: PathBuf, +} + +pub fn run(args: Args) -> Result<()> { + match args.command { + SubCommand::Info(c_args) => info(c_args), + } +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct SModelReadBufferInfo { + pub size: u32, + pub offset: u32, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct SModelBufferInfo { + pub index: u32, + pub offset: u32, + pub size: u32, + pub dest_size: u32, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct SModelMetaData { + pub unk1: u32, + pub gpu_offset: u32, + #[bw(try_calc = read_info.len().try_into())] + pub read_info_count: u32, + #[br(count = read_info_count)] + pub read_info: Vec, + #[bw(try_calc = buffer_info.len().try_into())] + pub buffer_count: u32, + #[br(count = buffer_count)] + pub buffer_info: Vec, + #[bw(try_calc = idx_buffer_info.len().try_into())] + pub idx_info_count: u32, + #[br(count = idx_info_count)] + pub idx_buffer_info: Vec, +} + +#[binrw] +#[repr(u32)] +#[brw(repr(u32))] +#[derive(Copy, Clone, Debug)] +pub enum EIndexBufferType { + U8 = 0, // ? + U16 = 1, + U32 = 2, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct SIndexBufferInfoSection { + #[bw(try_calc = info.len().try_into())] + pub count: u32, + #[br(count = count)] + pub info: Vec, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct SVertexBufferInfoSection { + #[bw(try_calc = info.len().try_into())] + pub count: u32, + #[br(count = count)] + pub info: Vec, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct SVertexBufferInfo { + vertex_count: u32, + #[bw(try_calc = components.len().try_into())] + component_count: u32, + #[br(count = component_count)] + components: Vec, + unk: u8, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct SVertexDataComponent { + buffer_index: u32, + offset: u32, + stride: u32, + format: EVertexDataFormat, + component: EVertexComponent, +} + +#[binrw] +#[repr(u32)] +#[brw(repr(u32))] +#[derive(Copy, Clone, Debug)] +enum EVertexDataFormat { + R8Unorm = 0, + R8Uint = 1, + R8Snorm = 2, + R8Sint = 3, + R16Unorm = 4, + R16Uint = 5, + R16Snorm = 6, + R16Sint = 7, + R16Float = 8, + Rg8Unorm = 9, + Rg8Uint = 10, + Rg8Snorm = 11, + Rg8Sint = 12, + R32Uint = 13, + R32Sint = 14, + R32Float = 15, + Rg16Unorm = 16, + Rg16Uint = 17, + Rg16Snorm = 18, + Rg16Sint = 19, + Rg16Float = 20, + Rgba8Unorm = 21, + Rgba8Uint = 22, + Rgba8Snorm = 23, + Rgba8Sint = 24, + Rgb10a2Unorm = 25, + Rgb10a2Uint = 26, + Rg32Uint = 27, + Rg32Sint = 28, + Rg32Float = 29, + Rgba16Unorm = 30, + Rgba16Uint = 31, + Rgba16Snorm = 32, + Rgba16Sint = 33, + Rgba16Float = 34, + Rgb32Uint = 35, + Rgb32Sint = 36, + Rgb32Float = 37, + Rgba32Uint = 38, + Rgba32Sint = 39, + Rgba32Float = 40, +} + +#[binrw] +#[repr(u32)] +#[brw(repr(u32))] +#[derive(Copy, Clone, Debug)] +enum EVertexComponent { + Position = 0, // in_position + Normal = 1, // in_normal + Tangent0 = 2, // in_tangent[0] + Tangent1 = 3, // in_tangent[1] + Tangent2 = 4, // in_tangent[2] + TexCoord0 = 5, // in_texCoord[0] + TexCoord1 = 6, // in_texCoord[1] + TexCoord2 = 7, // in_texCoord[2] + TexCoord3 = 8, // in_texCoord[3] + Color = 9, // in_color + BoneIndices = 10, // in_boneIndices + BoneWeights = 11, // in_boneWeights + BakedLightingCoord = 12, // in_bakedLightingCoord + BakedLightingTangent = 13, // in_bakedLightingTangent + VertInstanceParams = 14, // in_vertInstanceParams + VertInstanceColor = 15, // in_vertInstanceColor + VertTransform0 = 16, // in_vertTransform[0] + VertTransform1 = 17, // in_vertTransform[1] + VertTransform2 = 18, // in_vertTransform[2] + CurrentPosition = 19, // in_currentPosition + VertInstanceOpacityParams = 20, // in_vertInstanceOpacityParams + VertInstanceColorIndexingParams = 21, // in_vertInstanceColorIndexingParams + VertInstanceOpacityIndexingParams = 22, // in_vertInstanceOpacityIndexingParams + VertInstancePaintParams = 23, // in_vertInstancePaintParams + BakedLightingLookup = 24, // in_bakedLightingLookup + MaterialChoice0 = 25, // in_materialChoice[0] + MaterialChoice1 = 26, // in_materialChoice[1] + MaterialChoice2 = 27, // in_materialChoice[2] + MaterialChoice3 = 28, // in_materialChoice[3] +} + +fn decompress_gpu_buffer( + data: &[u8], + read_info: &[SModelReadBufferInfo], + buffer_info: &[SModelBufferInfo], +) -> Result> { + let buffer_size = buffer_info.iter().map(|b| b.dest_size as usize).sum(); + let mut out = vec![0u8; buffer_size]; + let mut out_cur = 0usize; + for buffer_info in buffer_info { + let read_info = &read_info[buffer_info.index as usize]; + let read_buffer = + &data[read_info.offset as usize..(read_info.offset + read_info.size) as usize]; + let comp_buf = &read_buffer + [buffer_info.offset as usize..(buffer_info.offset + buffer_info.size) as usize]; + decompress_into(comp_buf, &mut out[out_cur..out_cur + buffer_info.dest_size as usize])?; + out_cur += buffer_info.dest_size as usize; + } + Ok(out) +} + +fn info(args: InfoArgs) -> Result<()> { + let data = map_file(&args.input)?; + + let (cmdl_desc, mut cmdl_data, remain) = FormDescriptor::slice(&data, Endian::Little)?; + ensure!(cmdl_desc.id == K_FORM_CMDL); + ensure!(cmdl_desc.version_a == 114); + ensure!(cmdl_desc.version_b == 125); + let (foot_desc, mut foot_data, remain) = FormDescriptor::slice(remain, Endian::Little)?; + ensure!(foot_desc.id == K_FORM_FOOT); + ensure!(foot_desc.version_a == 1); + ensure!(foot_desc.version_b == 1); + ensure!(remain.is_empty()); + + let mut meta: Option = None; + while !foot_data.is_empty() { + let (desc, data, remain) = ChunkDescriptor::slice(foot_data, Endian::Little)?; + if desc.id == K_CHUNK_META { + meta = Some(Cursor::new(data).read_type(Endian::Little)?); + break; + } + foot_data = remain; + } + let Some(meta) = meta else { + bail!("Failed to locate meta chunk"); + }; + + // TODO multiple buffers + let buffer = decompress_gpu_buffer(&data, &meta.read_info, &meta.buffer_info)?; + let idx_buffer = decompress_gpu_buffer(&data, &meta.read_info, &meta.idx_buffer_info)?; + + let mut vbuf: Option = None; + let mut ibuf: Option = None; + while !cmdl_data.is_empty() { + let (chunk_desc, chunk_data, remain) = ChunkDescriptor::slice(cmdl_data, Endian::Little)?; + match chunk_desc.id { + K_CHUNK_VBUF => vbuf = Some(Cursor::new(chunk_data).read_type(Endian::Little)?), + K_CHUNK_IBUF => ibuf = Some(Cursor::new(chunk_data).read_type(Endian::Little)?), + _ => {} + } + cmdl_data = remain; + } + + log::debug!("VBUF: {vbuf:#?}"); + log::debug!("IBUF: {ibuf:#?}"); + + fs::write("vtxbuf", buffer)?; + fs::write("idxbuf", idx_buffer)?; + + todo!() +} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index f358c57..9885284 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,13 +1,4 @@ +pub mod cmdl; pub mod fmv0; pub mod pak; pub mod txtr; - -use argh::FromArgs; - -#[derive(FromArgs, PartialEq, Debug)] -#[argh(subcommand)] -pub enum SubCommand { - Pak(pak::Args), - Txtr(txtr::Args), - Fmv0(fmv0::Args), -} diff --git a/src/cmd/pak.rs b/src/cmd/pak.rs index 96a342e..6a3d345 100644 --- a/src/cmd/pak.rs +++ b/src/cmd/pak.rs @@ -6,6 +6,7 @@ use std::{ io::{BufWriter, Cursor, Write}, path::PathBuf, }; +use std::fs::DirBuilder; use anyhow::{bail, ensure, Context, Result}; use argh::FromArgs; @@ -89,6 +90,9 @@ fn extract(args: ExtractArgs) -> Result<()> { .map(|name| format!("{}.{}", name, asset.kind)) .unwrap_or_else(|| format!("{}.{}", asset.id, asset.kind)); let path = args.output.join(&file_name); + if let Some(parent) = path.parent() { + DirBuilder::new().recursive(true).create(parent)?; + } let mut file = BufWriter::new( File::create(&path) diff --git a/src/main.rs b/src/main.rs index e759e17..e0a4de9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ mod format; mod util; use argh::FromArgs; -use cmd::SubCommand; #[derive(FromArgs, PartialEq, Debug)] /// GameCube/Wii decompilation project tools. @@ -13,6 +12,15 @@ struct TopLevel { command: SubCommand, } +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +pub enum SubCommand { + Cmdl(cmd::cmdl::Args), + Fmv0(cmd::fmv0::Args), + Pak(cmd::pak::Args), + Txtr(cmd::txtr::Args), +} + fn main() { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) .format_timestamp(None) @@ -22,9 +30,10 @@ fn main() { let args: TopLevel = argh_version::from_env(); let result = match args.command { + SubCommand::Cmdl(args) => cmd::cmdl::run(args), + SubCommand::Fmv0(args) => cmd::fmv0::run(args), SubCommand::Pak(args) => cmd::pak::run(args), SubCommand::Txtr(args) => cmd::txtr::run(args), - SubCommand::Fmv0(args) => cmd::fmv0::run(args), }; if let Err(e) = result { eprintln!("Failed: {e:?}");