1#![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
16bitflags! {
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 pub texture_type: TextureType,
85 pub width: u32,
87 pub height: u32,
89 pub depth: u32,
91 pub rgba: Vec<u8>,
93}
94
95type DecodeFunction = fn(&[u8], usize, usize, &mut [u32]) -> Result<(), &'static str>;
96
97impl Texture {
98 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 Texture::from_existing(&read(d).unwrap());
231 }
232}