Start parsing CMDL (WIP)

This commit is contained in:
Luke Street
2023-02-16 20:10:20 -05:00
parent e114318988
commit ef9e625d13
4 changed files with 315 additions and 12 deletions
+299
View File
@@ -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<SModelReadBufferInfo>,
#[bw(try_calc = buffer_info.len().try_into())]
pub buffer_count: u32,
#[br(count = buffer_count)]
pub buffer_info: Vec<SModelBufferInfo>,
#[bw(try_calc = idx_buffer_info.len().try_into())]
pub idx_info_count: u32,
#[br(count = idx_info_count)]
pub idx_buffer_info: Vec<SModelBufferInfo>,
}
#[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<EIndexBufferType>,
}
#[binrw]
#[derive(Clone, Debug)]
pub struct SVertexBufferInfoSection {
#[bw(try_calc = info.len().try_into())]
pub count: u32,
#[br(count = count)]
pub info: Vec<SVertexBufferInfo>,
}
#[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<SVertexDataComponent>,
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<Vec<u8>> {
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<SModelMetaData> = 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<SVertexBufferInfoSection> = None;
let mut ibuf: Option<SIndexBufferInfoSection> = 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!()
}
+1 -10
View File
@@ -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),
}
+4
View File
@@ -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)
+11 -2
View File
@@ -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:?}");