rw_parser_rs/renderware/txd/
txd_parser.rs

1//! # TXD Parser
2//!
3//! A parser for RenderWare TXD (Texture Dictionary) files, used for storing
4//! textures in Grand Theft Auto 3, Vice City, and San Andreas.
5//!
6//! ## Features
7//!
8//! - Parses texture metadata, including name, dimensions, and format.
9//! - Supports decompression of DXT1, DXT3, and DXT5 (BC1, BC2, BC3) textures.
10//! - Extracts mipmap levels for supported formats.
11//! - Deserializes texture data into a structured `RwTxd` format.
12//!
13//! ## Example
14//!
15//! ```no_run
16//! use rw_parser_rs::renderware::txd::txd_parser::TxdParser;
17//! use std::fs;
18//!
19//! let file_data = fs::read("path/to/your/textures.txd").unwrap();
20//! let mut parser = TxdParser::new(&file_data);
21//! let txd_data = parser.parse().unwrap();
22//!
23//! println!("Texture count: {}", txd_data.texture_dictionary.texture_count);
24//! ```
25
26use crate::renderware::rw_file::RwFile;
27use crate::renderware::utils::image_format_enums::{PaletteType, PlatformType};
28use std::io::Result;
29use texpresso;
30
31use serde::Serialize;
32
33/// Represents the top-level structure of a parsed TXD file.
34///
35/// This struct contains the texture dictionary, which holds all the
36/// individual textures in the file.
37#[derive(Debug, Clone, PartialEq, Serialize)]
38pub struct RwTxd {
39    /// The texture dictionary containing all texture data.
40    pub texture_dictionary: RwTextureDictionary,
41}
42
43#[derive(Debug, Clone, PartialEq, Serialize)]
44pub struct RwTextureDictionary {
45    pub texture_count: u16,
46    pub texture_natives: Vec<RwTextureNative>,
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize)]
50pub struct RwTextureNative {
51    pub platform_id: u32,
52    pub filter_mode: u8,
53    pub u_addressing: u8,
54    pub v_addressing: u8,
55    pub texture_name: String,
56    pub mask_name: String,
57    pub raster_format: u32,
58    pub d3d_format: String,
59    pub width: u16,
60    pub height: u16,
61    pub depth: u8,
62    pub mipmap_count: u8,
63    pub raster_type: u8,
64    pub alpha: bool,
65    pub cube_texture: bool,
66    pub auto_mip_maps: bool,
67    pub compressed: bool,
68    pub mipmaps: Vec<Vec<u8>>,
69}
70
71/// The main parser for TXD files.
72///
73/// This struct holds the file buffer and provides the `parse` method to
74/// deserialize the TXD texture data.
75pub struct TxdParser<'a> {
76    file: RwFile<'a>,
77}
78
79impl<'a> TxdParser<'a> {
80    /// Creates a new `TxdParser` instance with the given file buffer.
81    ///
82    /// # Arguments
83    ///
84    /// * `buffer` - A byte slice containing the raw TXD file data.
85    pub fn new(buffer: &'a [u8]) -> Self {
86        TxdParser {
87            file: RwFile::new(buffer),
88        }
89    }
90
91    /// Parses the entire TXD file buffer.
92    ///
93    // This method reads the root `TextureDictionary` section and all the
94    // `TextureNative` sections within it.
95    ///
96    /// # Returns
97    ///
98    /// A `Result` containing the parsed `RwTxd` data or an `std::io::Error`
99    /// if a parsing error occurs.
100    pub fn parse(&mut self) -> Result<RwTxd> {
101        Ok(RwTxd {
102            texture_dictionary: self.read_texture_dictionary()?,
103        })
104    }
105
106    fn read_texture_dictionary(&mut self) -> Result<RwTextureDictionary> {
107        self.file.read_section_header()?; // Struct
108        self.file.read_section_header()?; // TextureDictionary
109
110        let texture_count = self.file.get_stream().read_u16()?;
111        self.file.get_stream().skip(2)?;
112
113        let mut texture_natives = Vec::with_capacity(texture_count as usize);
114        for _ in 0..texture_count {
115            texture_natives.push(self.read_texture_native()?);
116        }
117        
118        let size = self.file.read_section_header()?.section_size;
119        self.file.get_stream().skip(size as u64)?;
120
121        Ok(RwTextureDictionary {
122            texture_count,
123            texture_natives,
124        })
125    }
126
127    fn read_texture_native(&mut self) -> Result<RwTextureNative> {
128        self.file.read_section_header()?; // Struct
129        self.file.read_section_header()?; // TextureNative
130
131        let platform_id = self.file.get_stream().read_u32()?;
132        let flags = self.file.get_stream().read_u32()?;
133
134        let filter_mode = (flags & 0xFF) as u8;
135        let u_addressing = ((flags & 0xF00) >> 8) as u8;
136        let v_addressing = ((flags & 0xF000) >> 12) as u8;
137
138        let texture_name = self.file.get_stream().read_string(32)?;
139        let mask_name = self.file.get_stream().read_string(32)?;
140
141        let raster_format = self.file.get_stream().read_u32()?;
142        let d3d_format = self.file.get_stream().read_string(4)?;
143        let width = self.file.get_stream().read_u16()?;
144        let height = self.file.get_stream().read_u16()?;
145        let depth = self.file.get_stream().read_u8()?;
146        let mipmap_count = self.file.get_stream().read_u8()?;
147        let raster_type = self.file.get_stream().read_u8()?;
148        let compression_flags = self.file.get_stream().read_u8()?;
149
150        let alpha = (compression_flags & (1 << 0)) != 0;
151        let cube_texture = (compression_flags & (1 << 1)) != 0;
152        let auto_mip_maps = (compression_flags & (1 << 2)) != 0;
153        let compressed = (compression_flags & (1 << 3)) != 0;
154
155        let palette_type = (raster_format >> 13) & 0b11;
156
157        let mut mipmaps = Vec::new();
158        let palette = if palette_type != PaletteType::PaletteNone as u32 {
159            self.read_palette(palette_type, depth)?
160        } else {
161            Vec::new()
162        };
163
164        for i in 0..mipmap_count {
165            let raster_size = self.file.get_stream().read_u32()?;
166            let raster = self.file.get_stream().read(raster_size as usize)?;
167            
168            if i == 0 {
169                 let bitmap = if !palette.is_empty() {
170                    // Palette decoding is not implemented yet
171                    vec![]
172                } else if platform_id == PlatformType::D3d8 as u32 && compression_flags != 0 {
173                    self.get_bitmap_with_dxt(&format!("DXT{}", compression_flags), &raster, width, height)?
174                } else if platform_id == PlatformType::D3d9 as u32 && compressed {
175                    self.get_bitmap_with_dxt(&d3d_format, &raster, width, height)?
176                } else {
177                    // Raw RGBA decoding is not implemented yet
178                    vec![]
179                };
180                mipmaps.push(bitmap);
181            }
182        }
183        
184        let size = self.file.read_section_header()?.section_size;
185        self.file.get_stream().skip(size as u64)?;
186
187        Ok(RwTextureNative {
188            platform_id,
189            filter_mode,
190            u_addressing,
191            v_addressing,
192            texture_name,
193            mask_name,
194            raster_format,
195            d3d_format,
196            width,
197            height,
198            depth,
199            mipmap_count,
200            raster_type,
201            alpha,
202            cube_texture,
203            auto_mip_maps,
204            compressed,
205            mipmaps,
206        })
207    }
208
209    fn read_palette(&mut self, palette_type: u32, depth: u8) -> Result<Vec<u8>> {
210        let size = if palette_type == PaletteType::Palette8 as u32 { 1024 } else if depth == 4 { 64 } else { 128 };
211        self.file.get_stream().read(size)
212    }
213
214    fn get_bitmap_with_dxt(&self, dxt_type: &str, raster: &[u8], width: u16, height: u16) -> Result<Vec<u8>> {
215        let format = match dxt_type {
216            "DXT1" => texpresso::Format::Bc1,
217            "DXT2" => texpresso::Format::Bc2,
218            "DXT3" => texpresso::Format::Bc2,
219            "DXT4" => texpresso::Format::Bc3,
220            "DXT5" => texpresso::Format::Bc3,
221            _ => return Ok(Vec::new())
222        };
223
224        let mut decoded = vec![0; width as usize * height as usize * 4];
225        format.decompress(raster, width as usize, height as usize, &mut decoded);
226        
227        Ok(decoded)
228    }
229}