2023-08-06 08:25:04 -04:00
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
2023-09-22 19:17:24 -04:00
#![ allow(clippy::needless_range_loop) ]
2022-08-16 11:52:07 -04:00
use std ::io ::{ Cursor , Read , Seek , SeekFrom } ;
2023-08-06 08:25:04 -04:00
2025-03-11 16:25:59 -04:00
use crate ::ByteSpan ;
2025-03-10 17:15:14 -04:00
use crate ::bcn ::decode_bc1 ;
use crate ::bcn ::decode_bc3 ;
use crate ::bcn ::decode_bc5 ;
2025-05-17 12:32:23 -04:00
use crate ::bcn ::decode_bc7 ;
2024-04-20 13:18:03 -04:00
use binrw ::BinRead ;
2025-03-06 18:52:26 -05:00
use binrw ::binrw ;
2023-08-06 08:25:04 -04:00
use bitflags ::bitflags ;
2025-05-09 15:16:23 -04:00
#[ binrw ]
#[ derive(Debug) ]
struct TextureAttribute ( u32 ) ;
2022-08-11 09:23:15 -04:00
// Attributes and Format are adapted from Lumina (https://github.com/NotAdam/Lumina/blob/master/src/Lumina/Data/Files/TexFile.cs)
bitflags! {
2025-05-09 15:16:23 -04:00
impl TextureAttribute : u32 {
2022-09-15 16:31:56 -04:00
const DISCARD_PER_FRAME = 0x1 ;
const DISCARD_PER_MAP = 0x2 ;
2022-08-11 09:23:15 -04:00
2022-09-15 16:31:56 -04:00
const MANAGED = 0x4 ;
const USER_MANAGED = 0x8 ;
const CPU_READ = 0x10 ;
const LOCATION_MAIN = 0x20 ;
const NO_GPU_READ = 0x40 ;
const ALIGNED_SIZE = 0x80 ;
const EDGE_CULLING = 0x100 ;
const LOCATION_ONION = 0x200 ;
const READ_WRITE = 0x400 ;
const IMMUTABLE = 0x800 ;
2022-08-11 09:23:15 -04:00
2022-09-15 16:31:56 -04:00
const TEXTURE_RENDER_TARGET = 0x100000 ;
const TEXTURE_DEPTH_STENCIL = 0x200000 ;
const TEXTURE_TYPE1_D = 0x400000 ;
const TEXTURE_TYPE2_D = 0x800000 ;
const TEXTURE_TYPE3_D = 0x1000000 ;
2025-05-17 12:32:23 -04:00
const TEXTURE_TYPE2_D_ARRAY = 0x10000000 ;
2022-09-15 16:31:56 -04:00
const TEXTURE_TYPE_CUBE = 0x2000000 ;
const TEXTURE_TYPE_MASK = 0x3C00000 ;
const TEXTURE_SWIZZLE = 0x4000000 ;
const TEXTURE_NO_TILED = 0x8000000 ;
const TEXTURE_NO_SWIZZLE = 0x80000000 ;
2022-08-11 09:23:15 -04:00
}
}
2025-05-17 12:32:23 -04:00
// From https://github.com/aers/FFXIVClientStructs/blob/344f5d488197e9c9d5fd78e92439e7104f25e2e0/FFXIVClientStructs/FFXIV/Client/Graphics/Kernel/Texture.cs#L97
2022-10-13 17:11:03 -04:00
#[ binrw ]
#[ brw(repr = u32) ]
2022-08-11 15:03:12 -04:00
#[ derive(Debug) ]
enum TextureFormat {
2025-05-17 12:32:23 -04:00
L8_UNORM = 0x1130 ,
A8_UNORM = 0x1131 ,
R8_UNORM = 0x1132 ,
R8_UINT = 0x1133 ,
R16_UINT = 0x1140 ,
R32_UINT = 0x1150 ,
R8G8_UNORM = 0x1240 ,
B4G4R4A4_UNORM = 0x1440 ,
B5G5R5A1_UNORM = 0x1441 ,
B8G8R8A8_UNORM = 0x1450 ,
B8G8R8X8_UNORM = 0x1451 ,
R16_FLOAT = 0x2140 ,
R32_FLOAT = 0x2150 ,
R16G16_FLOAT = 0x2250 ,
R32G32_FLOAT = 0x2260 ,
R11G11B10_FLOAT = 0x2350 ,
R16G16B16A16_FLOAT = 0x2460 ,
R32G32B32A32_FLOAT = 0x2470 ,
BC1_UNORM = 0x3420 ,
BC2_UNORM = 0x3430 ,
BC3_UNORM = 0x3431 ,
D16_UNORM = 0x4140 ,
D24_UNORM_S8_UINT = 0x4250 ,
D16_UNORM_2 = 0x5140 ,
D24_UNORM_S8_UINT_2 = 0x5150 ,
BC4_UNORM = 0x6120 ,
BC5_UNORM = 0x6230 ,
BC6H_SF16 = 0x6330 ,
BC7_UNORM = 0x6432 ,
R16_UNORM = 0x7140 ,
R16G16_UNORM = 0x7250 ,
R10G10B10A2_UNORM_2 = 0x7350 ,
R10G10B10A2_UNORM = 0x7450 ,
D24_UNORM_S8_UINT_3 = 0x8250 ,
2022-08-11 09:23:15 -04:00
}
2022-08-09 19:31:32 -04:00
2022-10-13 17:11:03 -04:00
#[ binrw ]
2022-08-09 19:31:32 -04:00
#[ derive(Debug) ]
2022-09-15 16:26:31 -04:00
#[ allow(dead_code) ]
2022-10-13 17:11:03 -04:00
#[ brw(little) ]
2022-08-09 19:31:32 -04:00
struct TexHeader {
2022-08-16 11:52:07 -04:00
attribute : TextureAttribute ,
2022-08-11 09:23:15 -04:00
format : TextureFormat ,
2022-08-09 19:31:32 -04:00
2022-08-16 11:52:07 -04:00
width : u16 ,
height : u16 ,
depth : u16 ,
mip_levels : u16 ,
2022-08-09 19:31:32 -04:00
2022-08-16 11:52:07 -04:00
lod_offsets : [ u32 ; 3 ] ,
offset_to_surface : [ u32 ; 13 ] ,
2022-08-09 19:31:32 -04:00
}
2024-04-30 18:01:09 -04:00
#[ repr(C) ]
#[ derive(Clone, Copy) ]
pub enum TextureType {
TwoDimensional ,
2024-05-18 09:42:07 -04:00
ThreeDimensional ,
2024-04-30 18:01:09 -04:00
}
2022-08-09 19:31:32 -04:00
pub struct Texture {
2024-04-30 18:01:09 -04:00
/// Type of texture
pub texture_type : TextureType ,
2023-12-02 19:55:50 -05:00
/// Width of the texture in pixels
2022-08-11 15:48:28 -04:00
pub width : u32 ,
2023-12-02 19:55:50 -05:00
/// Height of the texture in pixels
2022-08-11 15:48:28 -04:00
pub height : u32 ,
2024-04-30 18:01:09 -04:00
/// Depth of the texture in pixels
pub depth : u32 ,
2023-12-02 19:55:50 -05:00
/// Raw RGBA data
2022-08-16 11:52:07 -04:00
pub rgba : Vec < u8 > ,
2022-08-09 19:31:32 -04:00
}
2024-04-20 10:46:59 -04:00
type DecodeFunction = fn ( & [ u8 ] , usize , usize , & mut [ u32 ] ) -> Result < ( ) , & 'static str > ;
2022-08-09 19:31:32 -04:00
impl Texture {
2023-12-02 19:55:50 -05:00
/// Reads an existing TEX file
2023-10-13 16:16:04 -04:00
pub fn from_existing ( buffer : ByteSpan ) -> Option < Texture > {
2022-08-09 19:31:32 -04:00
let mut cursor = Cursor ::new ( buffer ) ;
2025-05-17 12:32:23 -04:00
let header = TexHeader ::read ( & mut cursor ) . unwrap ( ) ;
2022-08-09 19:31:32 -04:00
2022-08-16 11:52:07 -04:00
cursor
2024-04-17 20:26:45 -04:00
. seek ( SeekFrom ::Start ( std ::mem ::size_of ::< TexHeader > ( ) as u64 ) )
2022-08-16 11:52:07 -04:00
. ok ( ) ? ;
2022-08-11 15:03:12 -04:00
2024-04-18 22:07:57 -04:00
let mut src = vec! [ 0 u8 ; buffer . len ( ) - std ::mem ::size_of ::< TexHeader > ( ) ] ;
2022-08-11 15:03:12 -04:00
cursor . read_exact ( src . as_mut_slice ( ) ) . ok ( ) ? ;
2024-04-20 13:18:03 -04:00
let mut dst : Vec < u8 > ;
2022-08-11 15:03:12 -04:00
match header . format {
2025-05-17 12:32:23 -04:00
TextureFormat ::B4G4R4A4_UNORM = > {
2024-05-18 09:42:07 -04:00
dst =
vec! [
0 u8 ;
header . width as usize * header . height as usize * header . depth as usize * 4
] ;
2024-04-20 10:00:13 -04:00
let mut offset = 0 ;
let mut dst_offset = 0 ;
2024-06-26 23:45:50 -04:00
for _ in 0 .. header . width as usize * header . height as usize {
2024-04-20 10:00:13 -04:00
let short : u16 = ( ( src [ offset ] as u16 ) < < 8 ) | src [ offset + 1 ] as u16 ;
let src_b = short & 0xF ;
2024-04-20 13:18:03 -04:00
let src_g = ( short > > 4 ) & 0xF ;
2024-04-20 10:00:13 -04:00
let src_r = ( short > > 8 ) & 0xF ;
let src_a = ( short > > 12 ) & 0xF ;
dst [ dst_offset ] = ( 17 * src_r ) as u8 ;
dst [ dst_offset + 1 ] = ( 17 * src_g ) as u8 ;
dst [ dst_offset + 2 ] = ( 17 * src_b ) as u8 ;
dst [ dst_offset + 3 ] = ( 17 * src_a ) as u8 ;
offset + = 2 ;
dst_offset + = 4 ;
}
}
2025-05-17 12:32:23 -04:00
TextureFormat ::B8G8R8A8_UNORM = > {
2024-05-18 09:42:07 -04:00
dst =
vec! [
0 u8 ;
header . width as usize * header . height as usize * header . depth as usize * 4
] ;
2024-04-27 21:16:01 -04:00
let mut offset = 0 ;
2024-06-26 23:45:50 -04:00
for _ in 0 .. header . width as usize * header . height as usize * header . depth as usize {
2024-04-27 21:16:01 -04:00
let src_b = src [ offset ] ;
let src_g = src [ offset + 1 ] ;
let src_r = src [ offset + 2 ] ;
let src_a = src [ offset + 3 ] ;
dst [ offset ] = src_r ;
dst [ offset + 1 ] = src_g ;
dst [ offset + 2 ] = src_b ;
dst [ offset + 3 ] = src_a ;
offset + = 4 ;
}
2022-08-11 15:03:12 -04:00
}
2025-05-17 12:32:23 -04:00
TextureFormat ::BC1_UNORM = > {
2024-04-20 13:18:03 -04:00
dst = Texture ::decode (
& src ,
header . width as usize ,
2024-04-30 18:01:09 -04:00
header . height as usize * header . depth as usize ,
2024-04-20 13:18:03 -04:00
decode_bc1 ,
) ;
2022-08-11 15:03:12 -04:00
}
2025-05-17 12:32:23 -04:00
TextureFormat ::BC3_UNORM = > {
2024-04-20 13:18:03 -04:00
dst = Texture ::decode (
& src ,
header . width as usize ,
2024-04-30 18:01:09 -04:00
header . height as usize * header . depth as usize ,
2024-04-20 13:18:03 -04:00
decode_bc3 ,
) ;
2022-08-11 15:03:12 -04:00
}
2025-05-17 12:32:23 -04:00
TextureFormat ::BC5_UNORM = > {
2024-04-20 13:18:03 -04:00
dst = Texture ::decode (
& src ,
header . width as usize ,
2024-04-30 18:01:09 -04:00
header . height as usize * header . depth as usize ,
2024-04-20 13:18:03 -04:00
decode_bc5 ,
) ;
2024-04-17 20:26:45 -04:00
}
2025-05-17 12:32:23 -04:00
TextureFormat ::BC7_UNORM = > {
dst = Texture ::decode (
& src ,
header . width as usize ,
header . height as usize * header . depth as usize ,
decode_bc7 ,
) ;
}
_ = > panic! ( " Unsupported texture format {:?} ! " , header . format ) ,
2022-08-11 15:03:12 -04:00
}
Some ( Texture {
2024-05-18 09:42:07 -04:00
texture_type : if header . attribute . contains ( TextureAttribute ::TEXTURE_TYPE3_D ) {
TextureType ::ThreeDimensional
} else {
TextureType ::TwoDimensional
} ,
2022-08-11 15:47:02 -04:00
width : header . width as u32 ,
height : header . height as u32 ,
2024-04-30 18:01:09 -04:00
depth : header . depth as u32 ,
2022-08-16 11:52:07 -04:00
rgba : dst ,
2022-08-11 15:03:12 -04:00
} )
2022-08-09 19:31:32 -04:00
}
2024-04-20 10:46:59 -04:00
fn decode ( src : & [ u8 ] , width : usize , height : usize , decode_func : DecodeFunction ) -> Vec < u8 > {
2024-04-20 13:17:11 -04:00
let mut image : Vec < u32 > = vec! [ 0 ; width * height ] ;
2024-04-20 13:18:03 -04:00
decode_func ( src , width , height , & mut image ) . unwrap ( ) ;
2024-04-20 10:46:59 -04:00
image
. iter ( )
. flat_map ( | x | {
let v = x . to_le_bytes ( ) ;
[ v [ 2 ] , v [ 1 ] , v [ 0 ] , v [ 3 ] ]
} )
. collect ::< Vec < u8 > > ( )
}
2022-08-16 11:52:07 -04:00
}
2024-04-16 22:08:22 -04:00
#[ cfg(test) ]
mod tests {
use std ::fs ::read ;
use std ::path ::PathBuf ;
use super ::* ;
#[ test ]
fn test_invalid ( ) {
let mut d = PathBuf ::from ( env! ( " CARGO_MANIFEST_DIR " ) ) ;
d . push ( " resources/tests " ) ;
d . push ( " random " ) ;
// Feeding it invalid data should not panic
Texture ::from_existing ( & read ( d ) . unwrap ( ) ) ;
}
}