diff options
author | metamuffin <metamuffin@disroot.org> | 2025-03-13 14:09:36 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-03-13 14:09:36 +0100 |
commit | 664d54952323a52132377ce9ed3c4a552c0993fa (patch) | |
tree | db6af4ac220e6fcfcdf7954b861b0a0762cf8579 | |
parent | e4d1504b1d7e575702895d93781f3650ff190bb3 (diff) | |
download | unity-tools-664d54952323a52132377ce9ed3c4a552c0993fa.tar unity-tools-664d54952323a52132377ce9ed3c4a552c0993fa.tar.bz2 unity-tools-664d54952323a52132377ce9ed3c4a552c0993fa.tar.zst |
can extract meshes
-rw-r--r-- | src/bin/meshes.rs | 81 | ||||
-rw-r--r-- | src/bin/textures.rs | 4 | ||||
-rw-r--r-- | src/classes/mesh.rs | 148 | ||||
-rw-r--r-- | src/classes/mod.rs | 35 |
4 files changed, 211 insertions, 57 deletions
diff --git a/src/bin/meshes.rs b/src/bin/meshes.rs new file mode 100644 index 0000000..64fe91c --- /dev/null +++ b/src/bin/meshes.rs @@ -0,0 +1,81 @@ +#![feature(array_chunks)] +use anyhow::anyhow; +use std::{ + env::args, + fs::{File, create_dir_all}, + io::{BufReader, BufWriter, Seek, SeekFrom, Write}, +}; +use unity_tools::{ + classes::mesh::{Mesh, VertexDataChannel}, + object::{parser::FromValue, read::read_value}, + serialized_file::read_serialized_file, + unityfs::UnityFS, +}; + +fn main() -> anyhow::Result<()> { + env_logger::init_from_env("LOG"); + let file = || BufReader::new(File::open(args().nth(1).unwrap()).unwrap()); + let mut fs = UnityFS::open(file())?; + // let mut fs2 = UnityFS::open(file())?; + + let mut i = 0; + create_dir_all("/tmp/a").unwrap(); + + let cabfile = fs + .nodes() + .iter() + .find(|n| !n.name.ends_with(".resource") && !n.name.ends_with(".resS")) + .ok_or(anyhow!("no CAB file found"))? + .to_owned(); + // let ressfile = fs2 + // .nodes() + // .iter() + // .find(|n| n.name.ends_with(".resS")) + // .cloned(); + + let mut cab = fs.read(&cabfile)?; + // let mut ress = ressfile.map(|p| fs2.read(&p)).transpose()?; + let file = read_serialized_file(&mut cab)?; + for ob in file.objects { + cab.seek(SeekFrom::Start(ob.data_offset))?; + let typetree = if ob.type_id < 0 { + unimplemented!() + } else { + &file.types[ob.type_id as usize] + }; + if let Some(typetree) = &typetree.type_tree { + if typetree.type_string != "Mesh" { + continue; + } + let value = read_value(typetree, file.endianness, &mut cab)?; + let mesh = Mesh::from_value(value).unwrap(); + // if mesh.stream_data.len() == 0 { + // let ress = ress.as_mut().unwrap(); + // ress.seek(SeekFrom::Start(mesh.stream_data.offset))?; + // ress.by_ref() + // .take(mesh.stream_data.size as u64) + // .read_to_end(&mut mesh.)?; + // } + let mut obj = BufWriter::new(File::create(format!( + "/tmp/a/{}_{i}.obj", + mesh.name.replace("/", "-").replace(".", "-") + ))?); + + let (pos_dims, positions) = mesh + .vertex_data + .read_channel(VertexDataChannel::Position) + .unwrap(); + assert_eq!(pos_dims, 3); + + for [x, y, z] in positions.array_chunks() { + writeln!(obj, "v {x} {y} {z}")?; + } + for [a, b, c] in mesh.read_indecies() { + writeln!(obj, "f {a} {b} {c}")?; + } + i += 1; + } + } + + Ok(()) +} diff --git a/src/bin/textures.rs b/src/bin/textures.rs index d85f494..00f3596 100644 --- a/src/bin/textures.rs +++ b/src/bin/textures.rs @@ -19,7 +19,7 @@ fn main() -> anyhow::Result<()> { let mut fs2 = UnityFS::open(file())?; let mut i = 0; - create_dir_all("/tmp/tex").unwrap(); + create_dir_all("/tmp/a").unwrap(); let cabfile = fs .nodes() @@ -57,7 +57,7 @@ fn main() -> anyhow::Result<()> { .read_to_end(&mut texture.image_data)?; } let path = format!( - "/tmp/tex/{}_{i}.png", + "/tmp/a/{}_{i}.png", texture.name.replace("/", "-").replace(".", "-") ); match texture.to_image() { diff --git a/src/classes/mesh.rs b/src/classes/mesh.rs index 7df7f8d..3883535 100644 --- a/src/classes/mesh.rs +++ b/src/classes/mesh.rs @@ -1,8 +1,9 @@ use super::streaminginfo::StreamingInfo; use crate::object::{Value, parser::FromValue}; -use anyhow::Result; +use anyhow::{Result, anyhow, bail}; use glam::Mat4; use serde::Serialize; +use std::mem::transmute; #[derive(Debug, Serialize)] pub struct Mesh { @@ -13,6 +14,7 @@ pub struct Mesh { pub index_buffer: Vec<u8>, pub sub_meshes: Vec<SubMesh>, pub stream_data: StreamingInfo, + pub vertex_data: VertexData, } #[derive(Debug, Serialize)] @@ -28,14 +30,14 @@ pub struct SubMesh { #[derive(Debug, Serialize)] pub struct VertexData { pub channels: Vec<ChannelInfo>, - pub data_size: Vec<u8>, + pub data: Vec<u8>, pub vertex_count: u32, } #[derive(Debug, Serialize)] pub struct ChannelInfo { pub dimension: u8, - pub format: u8, + pub format: VertexFormat, pub offset: u8, pub stream: u8, } @@ -44,8 +46,10 @@ impl FromValue for Mesh { fn from_value(v: Value) -> Result<Self> { let mut fields = v.as_class("Mesh").unwrap(); Ok(Mesh { - name: fields.remove("m_Name").unwrap().parse().unwrap(), - index_format: fields.remove("m_IndexFormat").unwrap().parse().unwrap(), + name: fields.field("m_Name")?, + index_format: fields.field("m_IndexFormat")?, + vertex_data: fields.field("m_VertexData")?, + stream_data: fields.field("m_StreamData")?, index_buffer: fields .remove("m_IndexBuffer") .unwrap() @@ -62,7 +66,6 @@ impl FromValue for Mesh { .into_iter() .map(|e| e.parse().unwrap()) .collect(), - stream_data: fields.remove("m_StreamData").unwrap().parse().unwrap(), bind_pose: fields .remove("m_BindPose") .unwrap() @@ -82,25 +85,86 @@ impl FromValue for Mesh { }) } } +impl Mesh { + pub fn read_indecies(&self) -> Vec<[u32; 3]> { + if self.index_format == 0 { + self.index_buffer + .array_chunks::<2>() + .map(|x| u16::from_le_bytes(*x) as u32) + .array_chunks() + .collect() + } else { + self.index_buffer + .array_chunks::<4>() + .map(|x| u32::from_le_bytes(*x)) + .array_chunks() + .collect() + } + } +} impl FromValue for VertexData { fn from_value(v: Value) -> Result<Self> { let mut fields = v.as_class("VertexData").unwrap(); Ok(VertexData { - vertex_count: fields.remove("vertexCount").unwrap().parse().unwrap(), + vertex_count: fields.field("m_VertexCount")?, + data: fields.remove("m_DataSize").unwrap().as_typeless().unwrap(), channels: fields .remove("m_Channels") .unwrap() - .as_array() + .as_vector() .unwrap() .into_iter() .map(|e| e.parse().unwrap()) .collect(), - data_size: fields.remove("m_DataSize").unwrap().as_typeless().unwrap(), }) } } +impl VertexData { + /// Returns (offset, stride) for each stream. + pub fn stream_layout(&self) -> Vec<(usize, usize)> { + let stream_count = self.channels.iter().map(|c| c.stream).max().unwrap() + 1; + let mut streams = Vec::new(); + let mut offset = 0; + for si in 0..stream_count { + let stride = self + .channels + .iter() + .filter(|c| c.stream == si) + .map(|c| c.dimension as usize * c.format.component_size()) + .sum(); + streams.push((offset, stride)); + offset += stride * self.vertex_count as usize + } + streams + } + /// Reads a vertex channel and returns dimension count and data converted to floats + pub fn read_channel(&self, channel: VertexDataChannel) -> Option<(usize, Vec<f32>)> { + let channel = &self.channels[channel as u8 as usize]; + if channel.dimension == 0 { + return None; + } + let component_offset = channel.offset as usize; + let component_size = channel.format.component_size(); + let (offset, stride) = self.stream_layout()[channel.stream as usize]; + + let mut out = Vec::new(); + for vi in 0..self.vertex_count as usize { + for di in 0..channel.dimension as usize { + let off = offset + vi * stride + component_offset + component_size * di; + let e = &self.data[off..]; + out.push(match channel.format { + VertexFormat::Float => f32::from_le_bytes([e[0], e[1], e[2], e[3]]), + VertexFormat::Float16 => f16::from_le_bytes([e[0], e[1]]) as f32, + x => todo!("vertex format {x:?}"), + }) + } + } + Some((channel.dimension as usize, out)) + } +} + impl FromValue for ChannelInfo { fn from_value(v: Value) -> Result<Self> { let mut fields = v.as_class("ChannelInfo").unwrap(); @@ -130,18 +194,56 @@ impl FromValue for SubMesh { #[repr(u8)] #[derive(Debug, Serialize, Clone, Copy, PartialEq)] pub enum VertexDataChannel { - Position = 0, - Normal = 1, - Tangent = 2, - Color = 3, - TexCoord0 = 4, - TexCoord1 = 5, - TexCoord2 = 6, - TexCoord3 = 7, - TexCoord4 = 8, - TexCoord5 = 9, - TexCoord6 = 10, - TexCoord7 = 11, - BlendWeight = 12, - BlendIndices = 13, + Position, + Normal, + Tangent, + Color, + TexCoord0, + TexCoord1, + TexCoord2, + TexCoord3, + TexCoord4, + TexCoord5, + TexCoord6, + TexCoord7, + BlendWeight, + BlendIndices, +} + +#[repr(u8)] +#[derive(Debug, Serialize, Clone, Copy, PartialEq)] +pub enum VertexFormat { + Float, + Float16, + UNorm8, + SNorm8, + UNorm16, + SNorm16, + UInt8, + SInt8, + UInt16, + SInt16, + UInt32, + SInt32, +} + +impl FromValue for VertexFormat { + fn from_value(v: Value) -> Result<Self> { + let x = v.as_u8().ok_or(anyhow!("expected u8 vertex format"))?; + if x < 12 { + Ok(unsafe { transmute(x) }) + } else { + bail!("unknown vertex format") + } + } +} +impl VertexFormat { + pub fn component_size(&self) -> usize { + use VertexFormat::*; + match self { + Float | UInt32 | SInt32 => 4, + Float16 | UInt16 | SInt16 | UNorm16 | SNorm16 => 2, + UInt8 | SInt8 | UNorm8 | SNorm8 => 1, + } + } } diff --git a/src/classes/mod.rs b/src/classes/mod.rs index 501fdf9..038e701 100644 --- a/src/classes/mod.rs +++ b/src/classes/mod.rs @@ -53,41 +53,12 @@ impl HValue { "GameObject" => Self::GameObject(GameObject::from_value(value)?), "Transform" => Self::Transform(Transform::from_value(value)?), "Texture2D" => Self::Texture2D(Texture2D::from_value(value)?), - "StreamingInfo" => Self::StreamingInfo(StreamingInfo::from_value(value)?), + // "StreamingInfo" => Self::StreamingInfo(StreamingInfo::from_value(value)?), // "SubMesh" => Self::SubMesh(SubMesh::from_value(value)?), - "Mesh" => Self::Mesh(Mesh::from_value(value)?), + // "Mesh" => Self::Mesh(Mesh::from_value(value)?), // "VertexData" => Self::VertexData(VertexData::from_value(value)?), // "ChannelInfo" => Self::ChannelInfo(ChannelInfo::from_value(value)?), - _ => { - let Value::Object { class, fields } = value else { - unreachable!() - }; - let mut fields = fields - .into_iter() - .map(|(k, v)| Ok((k, HValue::from_value(v)?))) - .collect::<Result<BTreeMap<_, _>>>()?; - match class.as_str() { - "map" => { - let Self::Array(a) = fields.remove("Array").unwrap() else { - unreachable!() - }; - Self::Map( - a.into_iter() - .map(|e| { - let Self::Pair(k, v) = e else { unreachable!() }; - (k.as_value().unwrap().clone().as_string().unwrap(), *v) - }) - .collect(), - ) - } - "pair" => Self::Pair( - Box::new(fields.remove("first").unwrap()), - Box::new(fields.remove("second").unwrap()), - ), - "vector" => fields.remove("Array").unwrap(), - _ => Self::Object { class, fields }, - } - } + _ => Self::Value([value]), } } Value::Array(a) => Self::Array( |