aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-03-12 00:26:35 +0100
committermetamuffin <metamuffin@disroot.org>2025-03-12 00:26:35 +0100
commitbed5904c0575a96d52f6e7fc3df95d3b772ef196 (patch)
treeee36a53d2ab24de3e363890adfd80e374b0911b0 /src
parent79e341769d04a6daa5c1edae87d6ff8a9adba9c6 (diff)
downloadunity-tools-bed5904c0575a96d52f6e7fc3df95d3b772ef196.tar
unity-tools-bed5904c0575a96d52f6e7fc3df95d3b772ef196.tar.bz2
unity-tools-bed5904c0575a96d52f6e7fc3df95d3b772ef196.tar.zst
texture formats
Diffstat (limited to 'src')
-rw-r--r--src/bin/textures.rs50
-rw-r--r--src/classes/mod.rs3
-rw-r--r--src/classes/texture2d.rs156
-rw-r--r--src/lib.rs9
-rw-r--r--src/object.rs7
5 files changed, 220 insertions, 5 deletions
diff --git a/src/bin/textures.rs b/src/bin/textures.rs
new file mode 100644
index 0000000..5cfaaa7
--- /dev/null
+++ b/src/bin/textures.rs
@@ -0,0 +1,50 @@
+use std::{
+ env::args,
+ fs::{File, create_dir_all},
+ io::{BufReader, Seek, SeekFrom},
+};
+use unity_tools::{
+ classes::{FromValue, texture2d::Texture2d},
+ object::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())?);
+ let mut fs = UnityFS::open(file)?;
+
+ let mut i = 0;
+ create_dir_all("/tmp/tex").unwrap();
+ for node in fs.nodes().to_vec() {
+ if node.name.ends_with(".resource") || node.name.ends_with(".resS") {
+ continue;
+ }
+ let mut cab = fs.read(&node)?;
+ 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 != "Texture2D" {
+ continue;
+ }
+ let value = read_value(typetree, file.endianness, &mut cab)?;
+ let texture = Texture2d::from_value(value).unwrap();
+ if texture.image_data.len() > 0 {
+ let path = format!("/tmp/tex/{i}.png");
+ println!("{path}");
+ texture.to_image().unwrap().save(path).unwrap();
+ i += 1;
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
diff --git a/src/classes/mod.rs b/src/classes/mod.rs
index 8c0db51..be2d5da 100644
--- a/src/classes/mod.rs
+++ b/src/classes/mod.rs
@@ -3,6 +3,7 @@ pub mod gameobject;
pub mod pptr;
pub mod transform;
pub mod vectors;
+pub mod texture2d;
use crate::object::Value;
use anyhow::Result;
@@ -110,7 +111,7 @@ impl HValue {
}
}
-trait FromValue: Sized {
+pub trait FromValue: Sized {
fn from_value(v: Value) -> Result<Self>;
}
diff --git a/src/classes/texture2d.rs b/src/classes/texture2d.rs
new file mode 100644
index 0000000..372aee7
--- /dev/null
+++ b/src/classes/texture2d.rs
@@ -0,0 +1,156 @@
+use super::FromValue;
+use crate::object::Value;
+use anyhow::{Result, bail};
+use image::{DynamicImage, Rgb, Rgba};
+use serde::Serialize;
+use std::mem::transmute;
+
+#[derive(Debug, Serialize)]
+pub struct Texture2d {
+ pub width: i32,
+ pub height: i32,
+ pub mip_count: i32,
+ pub name: String,
+ pub image_data: Vec<u8>,
+ pub texture_format: TextureFormat,
+ pub texture_dimension: i32,
+}
+
+impl FromValue for Texture2d {
+ fn from_value(v: Value) -> Result<Self> {
+ let mut fields = v.as_class("Texture2D").unwrap();
+ Ok(Texture2d {
+ width: fields.remove("m_Width").unwrap().as_i32().unwrap(),
+ height: fields.remove("m_Height").unwrap().as_i32().unwrap(),
+ mip_count: fields.remove("m_MipCount").unwrap().as_i32().unwrap(),
+ texture_dimension: fields
+ .remove("m_TextureDimension")
+ .unwrap()
+ .as_i32()
+ .unwrap(),
+ texture_format: unsafe {
+ transmute::<_, TextureFormat>(
+ fields.remove("m_TextureFormat").unwrap().as_i32().unwrap(),
+ )
+ },
+ name: fields.remove("m_Name").unwrap().as_string().unwrap(),
+ image_data: fields.remove("image data").unwrap().as_typeless().unwrap(),
+ })
+ }
+}
+impl Texture2d {
+ pub fn to_image(&self) -> Result<DynamicImage> {
+ let w = self.width as usize;
+ let h = self.height as usize;
+ use TextureFormat::*;
+ match self.texture_format {
+ DXT5 => {
+ let mut buf = vec![0u8; w * h * 4];
+ texpresso::Format::Bc3.decompress(&self.image_data, w, h, &mut buf);
+ let im = image::ImageBuffer::<Rgba<u8>, Vec<_>>::from_raw(w as u32, h as u32, buf)
+ .unwrap();
+ Ok(im.into())
+ }
+ RGB24 => {
+ let im = image::ImageBuffer::<Rgb<u8>, Vec<_>>::from_raw(
+ w as u32,
+ h as u32,
+ self.image_data.clone(),
+ )
+ .unwrap();
+ Ok(im.into())
+ }
+ ARGB32 => {
+ let mut buf = self.image_data.clone();
+ for pix in buf.array_chunks_mut::<4>() {
+ let a = pix[0];
+ pix[0] = pix[1];
+ pix[1] = pix[2];
+ pix[2] = pix[3];
+ pix[3] = a;
+ }
+ let im = image::ImageBuffer::<Rgba<u8>, Vec<_>>::from_raw(w as u32, h as u32, buf)
+ .unwrap();
+ Ok(im.into())
+ }
+ x => bail!("texture format {x:?} not supported"),
+ }
+ }
+}
+
+#[allow(non_camel_case_types)]
+#[repr(i32)]
+#[derive(Debug, Serialize, PartialEq, Clone, Copy)]
+pub enum TextureFormat {
+ Alpha8 = 1,
+ ARGB4444,
+ RGB24,
+ RGBA32,
+ ARGB32,
+ ARGBFloat,
+ RGB565,
+ BGR24,
+ R16,
+ DXT1, // Bc1
+ DXT3, // Bc2
+ DXT5, // Bc3
+ RGBA4444,
+ BGRA32,
+ RHalf,
+ RGHalf,
+ RGBAHalf,
+ RFloat,
+ RGFloat,
+ RGBAFloat,
+ YUY2,
+ RGB9e5Float,
+ RGBFloat,
+ BC6H,
+ BC7,
+ BC4,
+ BC5,
+ DXT1Crunched,
+ DXT5Crunched,
+ PVRTC_RGB2,
+ PVRTC_RGBA2,
+ PVRTC_RGB4,
+ PVRTC_RGBA4,
+ ETC_RGB4,
+ ATC_RGB4,
+ ATC_RGBA8,
+ EAC_R = 41,
+ EAC_R_SIGNED,
+ EAC_RG,
+ EAC_RG_SIGNED,
+ ETC2_RGB,
+ ETC2_RGBA1,
+ ETC2_RGBA8,
+ ASTC_RGB_4x4,
+ ASTC_RGB_5x5,
+ ASTC_RGB_6x6,
+ ASTC_RGB_8x8,
+ ASTC_RGB_10x10,
+ ASTC_RGB_12x12,
+ ASTC_RGBA_4x4,
+ ASTC_RGBA_5x5,
+ ASTC_RGBA_6x6,
+ ASTC_RGBA_8x8,
+ ASTC_RGBA_10x10,
+ ASTC_RGBA_12x12,
+ ETC_RGB4_3DS,
+ ETC_RGBA8_3DS,
+ RG16,
+ R8,
+ ETC_RGB4Crunched,
+ ETC2_RGBA8Crunched,
+ R16_Alt,
+ ASTC_HDR_4x4,
+ ASTC_HDR_5x5,
+ ASTC_HDR_6x6,
+ ASTC_HDR_8x8,
+ ASTC_HDR_10x10,
+ ASTC_HDR_12x12,
+ RG32,
+ RGB48,
+ RGBA64,
+}
diff --git a/src/lib.rs b/src/lib.rs
index c2416c3..638c3cb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
-pub mod unityfs;
-pub mod helper;
+#![feature(array_chunks)]
+pub mod classes;
pub mod common_strings;
-pub mod serialized_file;
+pub mod helper;
pub mod object;
-pub mod classes;
+pub mod serialized_file;
+pub mod unityfs;
diff --git a/src/object.rs b/src/object.rs
index 9387721..ef677d1 100644
--- a/src/object.rs
+++ b/src/object.rs
@@ -192,6 +192,13 @@ impl Value {
None
}
}
+ pub fn as_typeless(self) -> Option<Vec<u8>> {
+ if let Value::Typeless(s) = self {
+ Some(s)
+ } else {
+ None
+ }
+ }
pub fn to_json(self) -> serde_json::Value {
match self {
Value::Bool(x) => serde_json::Value::Bool(x),