physis/
tex.rs

1// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#![allow(clippy::needless_range_loop)]
5
6use std::io::{Cursor, Read, Seek, SeekFrom};
7
8use crate::ByteSpan;
9use crate::bcn::decode_bc1;
10use crate::bcn::decode_bc3;
11use crate::bcn::decode_bc5;
12use binrw::BinRead;
13use binrw::binrw;
14use bitflags::bitflags;
15
16// Attributes and Format are adapted from Lumina (https://github.com/NotAdam/Lumina/blob/master/src/Lumina/Data/Files/TexFile.cs)
17bitflags! {
18    #[binrw]
19    struct TextureAttribute : u32 {
20        const DISCARD_PER_FRAME = 0x1;
21        const DISCARD_PER_MAP = 0x2;
22
23        const MANAGED = 0x4;
24        const USER_MANAGED = 0x8;
25        const CPU_READ = 0x10;
26        const LOCATION_MAIN = 0x20;
27        const NO_GPU_READ = 0x40;
28        const ALIGNED_SIZE = 0x80;
29        const EDGE_CULLING = 0x100;
30        const LOCATION_ONION = 0x200;
31        const READ_WRITE = 0x400;
32        const IMMUTABLE = 0x800;
33
34        const TEXTURE_RENDER_TARGET = 0x100000;
35        const TEXTURE_DEPTH_STENCIL = 0x200000;
36        const TEXTURE_TYPE1_D = 0x400000;
37        const TEXTURE_TYPE2_D = 0x800000;
38        const TEXTURE_TYPE3_D = 0x1000000;
39        const TEXTURE_TYPE_CUBE = 0x2000000;
40        const TEXTURE_TYPE_MASK = 0x3C00000;
41        const TEXTURE_SWIZZLE = 0x4000000;
42        const TEXTURE_NO_TILED = 0x8000000;
43        const TEXTURE_NO_SWIZZLE = 0x80000000;
44    }
45}
46
47#[binrw]
48#[brw(repr = u32)]
49#[derive(Debug)]
50enum TextureFormat {
51    B4G4R4A4 = 0x1440,
52    B8G8R8A8 = 0x1450,
53    BC1 = 0x3420,
54    BC3 = 0x3431,
55    BC5 = 0x6230,
56}
57
58#[binrw]
59#[derive(Debug)]
60#[allow(dead_code)]
61#[brw(little)]
62struct TexHeader {
63    attribute: TextureAttribute,
64    format: TextureFormat,
65
66    width: u16,
67    height: u16,
68    depth: u16,
69    mip_levels: u16,
70
71    lod_offsets: [u32; 3],
72    offset_to_surface: [u32; 13],
73}
74
75#[repr(C)]
76#[derive(Clone, Copy)]
77pub enum TextureType {
78    TwoDimensional,
79    ThreeDimensional,
80}
81
82pub struct Texture {
83    /// Type of texture
84    pub texture_type: TextureType,
85    /// Width of the texture in pixels
86    pub width: u32,
87    /// Height of the texture in pixels
88    pub height: u32,
89    /// Depth of the texture in pixels
90    pub depth: u32,
91    /// Raw RGBA data
92    pub rgba: Vec<u8>,
93}
94
95type DecodeFunction = fn(&[u8], usize, usize, &mut [u32]) -> Result<(), &'static str>;
96
97impl Texture {
98    /// Reads an existing TEX file
99    pub fn from_existing(buffer: ByteSpan) -> Option<Texture> {
100        let mut cursor = Cursor::new(buffer);
101        let header = TexHeader::read(&mut cursor).ok()?;
102
103        cursor
104            .seek(SeekFrom::Start(std::mem::size_of::<TexHeader>() as u64))
105            .ok()?;
106
107        let mut src = vec![0u8; buffer.len() - std::mem::size_of::<TexHeader>()];
108        cursor.read_exact(src.as_mut_slice()).ok()?;
109
110        let mut dst: Vec<u8>;
111
112        match header.format {
113            TextureFormat::B4G4R4A4 => {
114                dst =
115                    vec![
116                        0u8;
117                        header.width as usize * header.height as usize * header.depth as usize * 4
118                    ];
119
120                let mut offset = 0;
121                let mut dst_offset = 0;
122
123                for _ in 0..header.width as usize * header.height as usize {
124                    let short: u16 = ((src[offset] as u16) << 8) | src[offset + 1] as u16;
125
126                    let src_b = short & 0xF;
127                    let src_g = (short >> 4) & 0xF;
128                    let src_r = (short >> 8) & 0xF;
129                    let src_a = (short >> 12) & 0xF;
130
131                    dst[dst_offset] = (17 * src_r) as u8;
132                    dst[dst_offset + 1] = (17 * src_g) as u8;
133                    dst[dst_offset + 2] = (17 * src_b) as u8;
134                    dst[dst_offset + 3] = (17 * src_a) as u8;
135
136                    offset += 2;
137                    dst_offset += 4;
138                }
139            }
140            TextureFormat::B8G8R8A8 => {
141                dst =
142                    vec![
143                        0u8;
144                        header.width as usize * header.height as usize * header.depth as usize * 4
145                    ];
146
147                let mut offset = 0;
148
149                for _ in 0..header.width as usize * header.height as usize * header.depth as usize {
150                    let src_b = src[offset];
151                    let src_g = src[offset + 1];
152                    let src_r = src[offset + 2];
153                    let src_a = src[offset + 3];
154
155                    dst[offset] = src_r;
156                    dst[offset + 1] = src_g;
157                    dst[offset + 2] = src_b;
158                    dst[offset + 3] = src_a;
159
160                    offset += 4;
161                }
162            }
163            TextureFormat::BC1 => {
164                dst = Texture::decode(
165                    &src,
166                    header.width as usize,
167                    header.height as usize * header.depth as usize,
168                    decode_bc1,
169                );
170            }
171            TextureFormat::BC3 => {
172                dst = Texture::decode(
173                    &src,
174                    header.width as usize,
175                    header.height as usize * header.depth as usize,
176                    decode_bc3,
177                );
178            }
179            TextureFormat::BC5 => {
180                dst = Texture::decode(
181                    &src,
182                    header.width as usize,
183                    header.height as usize * header.depth as usize,
184                    decode_bc5,
185                );
186            }
187        }
188
189        Some(Texture {
190            texture_type: if header.attribute.contains(TextureAttribute::TEXTURE_TYPE3_D) {
191                TextureType::ThreeDimensional
192            } else {
193                TextureType::TwoDimensional
194            },
195            width: header.width as u32,
196            height: header.height as u32,
197            depth: header.depth as u32,
198            rgba: dst,
199        })
200    }
201
202    fn decode(src: &[u8], width: usize, height: usize, decode_func: DecodeFunction) -> Vec<u8> {
203        let mut image: Vec<u32> = vec![0; width * height];
204        decode_func(src, width, height, &mut image).unwrap();
205
206        image
207            .iter()
208            .flat_map(|x| {
209                let v = x.to_le_bytes();
210                [v[2], v[1], v[0], v[3]]
211            })
212            .collect::<Vec<u8>>()
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use std::fs::read;
219    use std::path::PathBuf;
220
221    use super::*;
222
223    #[test]
224    fn test_invalid() {
225        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
226        d.push("resources/tests");
227        d.push("random");
228
229        // Feeding it invalid data should not panic
230        Texture::from_existing(&read(d).unwrap());
231    }
232}