aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bin/meshes.rs81
-rw-r--r--src/bin/textures.rs4
-rw-r--r--src/classes/mesh.rs148
-rw-r--r--src/classes/mod.rs35
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(