diff options
author | metamuffin <metamuffin@disroot.org> | 2025-03-12 00:26:35 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-03-12 00:26:35 +0100 |
commit | bed5904c0575a96d52f6e7fc3df95d3b772ef196 (patch) | |
tree | ee36a53d2ab24de3e363890adfd80e374b0911b0 /src | |
parent | 79e341769d04a6daa5c1edae87d6ff8a9adba9c6 (diff) | |
download | unity-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.rs | 50 | ||||
-rw-r--r-- | src/classes/mod.rs | 3 | ||||
-rw-r--r-- | src/classes/texture2d.rs | 156 | ||||
-rw-r--r-- | src/lib.rs | 9 | ||||
-rw-r--r-- | src/object.rs | 7 |
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, +} @@ -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), |