1mod reader;
38mod writer;
39
40pub use reader::{read_tpc, read_tpc_from_bytes};
41pub use writer::{write_tpc, write_tpc_to_vec};
42
43use std::io::Write;
44use thiserror::Error;
45
46use rakata_core::{encode_text, DecodeTextError, EncodeTextError, TextEncoding};
47
48use crate::binary::{self, write_f32, write_u8, DecodeBinary, EncodeBinary};
49
50const FILE_HEADER_SIZE: usize = 128;
51const RESERVED_SIZE: usize = 114;
52const CUBEMAP_LAYER_COUNT: usize = 6;
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
56pub enum TpcHeaderPixelFormat {
57 Greyscale,
59 Rgb,
61 Rgba,
63 Dxt1,
65 Dxt5,
73}
74
75impl TpcHeaderPixelFormat {
76 fn is_compressed(self) -> bool {
77 matches!(self, Self::Dxt1 | Self::Dxt5)
78 }
79
80 fn bytes_per_pixel(self) -> Option<usize> {
81 match self {
82 Self::Greyscale => Some(1),
83 Self::Rgb => Some(3),
84 Self::Rgba => Some(4),
85 Self::Dxt1 | Self::Dxt5 => None,
86 }
87 }
88
89 fn bytes_per_block(self) -> Option<usize> {
90 match self {
91 Self::Dxt1 => Some(8),
92 Self::Dxt5 => Some(16),
93 _ => None,
94 }
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub struct TpcPixelFormatCode {
101 pub compressed: bool,
103 pub pixel_type: u8,
105}
106
107impl TpcPixelFormatCode {
108 pub const fn known_format(self) -> Option<TpcHeaderPixelFormat> {
110 match (self.compressed, self.pixel_type) {
111 (false, 1) => Some(TpcHeaderPixelFormat::Greyscale),
112 (false, 2) => Some(TpcHeaderPixelFormat::Rgb),
113 (false, 4) => Some(TpcHeaderPixelFormat::Rgba),
114 (true, 2) => Some(TpcHeaderPixelFormat::Dxt1),
115 (true, 4) => Some(TpcHeaderPixelFormat::Dxt5),
116 _ => None,
117 }
118 }
119}
120
121#[derive(Debug, Clone, PartialEq)]
123pub struct TpcHeader {
124 pub data_size: u32,
128 pub alpha_test: f32,
130 pub width: u16,
132 pub height: u16,
134 pub pixel_type: u8,
136 pub mipmap_count: u8,
138 pub reserved: [u8; RESERVED_SIZE],
140}
141
142impl TpcHeader {
143 pub const fn compressed(&self) -> bool {
145 self.data_size != 0
146 }
147
148 pub const fn pixel_format_code(&self) -> TpcPixelFormatCode {
150 TpcPixelFormatCode {
151 compressed: self.compressed(),
152 pixel_type: self.pixel_type,
153 }
154 }
155}
156
157#[derive(Debug, Clone, PartialEq)]
159pub struct Tpc {
160 pub header: TpcHeader,
162 pub payload: Vec<u8>,
164 pub txi_footer: Vec<u8>,
166}
167
168impl Tpc {
169 pub fn new(header: TpcHeader, payload: Vec<u8>, txi_footer: Vec<u8>) -> Self {
171 Self {
172 header,
173 payload,
174 txi_footer,
175 }
176 }
177
178 pub fn known_pixel_format(&self) -> Option<TpcHeaderPixelFormat> {
180 self.header.pixel_format_code().known_format()
181 }
182
183 pub fn is_cube_map(&self) -> bool {
185 let width = usize::from(self.header.width);
186 let height = usize::from(self.header.height);
187 self.header.compressed()
188 && width > 0
189 && height % CUBEMAP_LAYER_COUNT == 0
190 && (height / CUBEMAP_LAYER_COUNT == width)
191 }
192
193 pub fn txi_text(&self) -> String {
195 rakata_core::decode_text(&self.txi_footer, TextEncoding::Windows1252)
196 }
197
198 pub fn txi_text_strict(&self) -> Result<String, DecodeTextError> {
200 rakata_core::decode_text_strict(&self.txi_footer, TextEncoding::Windows1252)
201 }
202
203 pub fn set_txi_text(&mut self, text: &str) -> Result<(), EncodeTextError> {
205 self.txi_footer = encode_text(text, TextEncoding::Windows1252)?;
206 Ok(())
207 }
208}
209
210impl DecodeBinary for Tpc {
211 type Error = TpcBinaryError;
212
213 fn decode_binary(bytes: &[u8]) -> Result<Self, Self::Error> {
214 read_tpc_from_bytes(bytes)
215 }
216}
217
218impl EncodeBinary for Tpc {
219 type Error = TpcBinaryError;
220
221 fn encode_binary(&self) -> Result<Vec<u8>, Self::Error> {
222 write_tpc_to_vec(self)
223 }
224}
225
226#[derive(Debug, Error)]
228pub enum TpcBinaryError {
229 #[error(transparent)]
231 Io(#[from] std::io::Error),
232 #[error("invalid TPC header: {0}")]
234 InvalidHeader(String),
235 #[error("invalid TPC data: {0}")]
237 InvalidData(String),
238 #[error(
240 "unsupported TPC pixel type: compressed={}, pixel_type={}",
241 .0.compressed,
242 .0.pixel_type
243 )]
244 UnsupportedPixelType(TpcPixelFormatCode),
245 #[error("value overflow while handling field `{0}`")]
247 ValueOverflow(&'static str),
248}
249
250impl From<binary::BinaryLayoutError> for TpcBinaryError {
251 fn from(error: binary::BinaryLayoutError) -> Self {
252 Self::InvalidHeader(error.to_string())
253 }
254}
255
256fn read_header(bytes: &[u8]) -> Result<TpcHeader, TpcBinaryError> {
257 let data_size = binary::read_u32(bytes, 0)?;
258 let alpha_test = binary::read_f32(bytes, 4)?;
259 let width = binary::read_u16(bytes, 8)?;
260 let height = binary::read_u16(bytes, 10)?;
261 let pixel_type = bytes[12];
262 let mipmap_count = bytes[13];
263 let mut reserved = [0_u8; RESERVED_SIZE];
264 reserved.copy_from_slice(&bytes[14..FILE_HEADER_SIZE]);
265
266 Ok(TpcHeader {
267 data_size,
268 alpha_test,
269 width,
270 height,
271 pixel_type,
272 mipmap_count,
273 reserved,
274 })
275}
276
277fn write_header<W: Write>(writer: &mut W, header: &TpcHeader) -> Result<(), TpcBinaryError> {
278 binary::write_u32(writer, header.data_size)?;
279 write_f32(writer, header.alpha_test)?;
280 binary::write_u16(writer, header.width)?;
281 binary::write_u16(writer, header.height)?;
282 write_u8(writer, header.pixel_type)?;
283 write_u8(writer, header.mipmap_count)?;
284 writer.write_all(&header.reserved)?;
285 Ok(())
286}
287
288fn expected_payload_size(header: &TpcHeader) -> Result<usize, TpcBinaryError> {
289 let width = usize::from(header.width);
290 let mut height = usize::from(header.height);
291 if width == 0 || height == 0 {
292 return Err(TpcBinaryError::InvalidHeader(
293 "width/height must be non-zero".into(),
294 ));
295 }
296
297 let mip_levels = usize::from(header.mipmap_count);
298 if mip_levels == 0 {
299 return Err(TpcBinaryError::InvalidHeader(
300 "mipmap_count must be at least 1".into(),
301 ));
302 }
303
304 let code = header.pixel_format_code();
305 let format = code
306 .known_format()
307 .ok_or(TpcBinaryError::UnsupportedPixelType(code))?;
308
309 let layer_count = if format.is_compressed()
310 && height % CUBEMAP_LAYER_COUNT == 0
311 && (height / CUBEMAP_LAYER_COUNT == width)
312 {
313 height /= CUBEMAP_LAYER_COUNT;
314 CUBEMAP_LAYER_COUNT
315 } else {
316 1
317 };
318
319 let base_level_size = if format.is_compressed() {
320 binary::checked_to_usize(header.data_size, "data_size")
321 .map_err(|_| TpcBinaryError::ValueOverflow("data_size"))?
322 } else {
323 let bpp = format
324 .bytes_per_pixel()
325 .ok_or_else(|| TpcBinaryError::InvalidHeader("unexpected compressed format".into()))?;
326 checked_uncompressed_level_size(width, height, bpp)?
327 };
328
329 let mut per_layer_size = base_level_size;
330 let mut level_width = width;
331 let mut level_height = height;
332 for _ in 1..mip_levels {
333 level_width >>= 1;
335 level_height >>= 1;
336 per_layer_size = per_layer_size
337 .checked_add(mip_level_size(format, level_width, level_height)?)
338 .ok_or(TpcBinaryError::ValueOverflow("mip level sum"))?;
339 }
340
341 per_layer_size
342 .checked_mul(layer_count)
343 .ok_or(TpcBinaryError::ValueOverflow("layer size sum"))
344}
345
346fn mip_level_size(
347 format: TpcHeaderPixelFormat,
348 width: usize,
349 height: usize,
350) -> Result<usize, TpcBinaryError> {
351 if let Some(bytes_per_pixel) = format.bytes_per_pixel() {
352 return checked_uncompressed_level_size(width, height, bytes_per_pixel);
353 }
354
355 let block_size = format
356 .bytes_per_block()
357 .ok_or_else(|| TpcBinaryError::InvalidHeader("missing block size".into()))?;
358 let block_width = width.div_ceil(4);
359 let block_height = height.div_ceil(4);
360 let block_count = block_width
361 .checked_mul(block_height)
362 .ok_or(TpcBinaryError::ValueOverflow("block count"))?;
363 block_count
364 .checked_mul(block_size)
365 .ok_or(TpcBinaryError::ValueOverflow("block bytes"))
366}
367
368fn checked_uncompressed_level_size(
369 width: usize,
370 height: usize,
371 bytes_per_pixel: usize,
372) -> Result<usize, TpcBinaryError> {
373 width
374 .checked_mul(height)
375 .and_then(|pixels| pixels.checked_mul(bytes_per_pixel))
376 .ok_or(TpcBinaryError::ValueOverflow("uncompressed level size"))
377}