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