Skip to main content

glacier_texture/
texture_map.rs

1#![allow(unused_variables)]
2
3use crate::atlas::AtlasData;
4use crate::enums::*;
5use crate::mipblock::MipblockData;
6use crate::pack::TexturePackerError;
7use crate::GlacierGame;
8use binrw::helpers::until_eof;
9use binrw::{binread, binrw, BinRead, BinResult, BinWrite, BinWriterExt, Endian};
10use serde::{Deserialize, Serialize};
11use std::fs::File;
12use std::io::{BufWriter, Cursor, Seek, Write};
13use std::path::Path;
14use std::{fs, io};
15
16/// Represents the maximum number of mip levels supported.
17const MAX_MIP_LEVELS: usize = 0xE;
18
19#[derive(Debug, thiserror::Error)]
20pub enum TextureMapError {
21    #[error("Io error")]
22    IoError(#[from] io::Error),
23
24    #[error("Parsing error {0}")]
25    ParsingError(#[from] binrw::Error),
26
27    #[error("Failed on {0}")]
28    UnknownError(String),
29}
30
31/// Arguments used for dynamically constructing texture map headers.
32pub(crate) struct DynamicTextureMapArgs {
33    pub(crate) data_size: u32,
34
35    pub(crate) atlas_data_size: u32,
36
37    pub(crate) text_scale: u8,
38    pub(crate) text_mip_levels: u8,
39}
40
41/// Trait that defines common functionality for texture map headers.
42pub(crate) trait TextureMapHeaderImpl {
43    /// Calculates the texture scaling factor.
44    fn text_scale(&self) -> usize;
45    /// Returns the size of the texture map header.
46    fn size() -> usize;
47    /// Calculates the size of the texture data.
48    fn text_data_size(&self) -> usize;
49    /// Indicates whether the texture has atlas data.
50    fn has_atlas(&self) -> bool;
51    /// Returns the number of mip levels in the texture.
52    fn texd_mip_levels(&self) -> usize;
53
54    fn compressed_mip_sizes(&self) -> [u32; 14];
55}
56
57/// Texture map header for version 1 (HM2016).
58#[binrw]
59#[derive(Serialize, Deserialize, Clone, Debug)]
60#[br(assert(
61    num_textures == 1 && num_textures != 6, "Looks like you tried to export a cubemap texture, those are not supported yet"
62))]
63#[bw(import(args: DynamicTextureMapArgs))]
64pub(crate) struct TextureMapHeaderV1 {
65    #[br(temp)]
66    #[bw(calc(1))]
67    num_textures: u16,
68
69    pub(crate) type_: TextureType,
70
71    pub(crate) texd_identifier: u32,
72
73    #[br(temp)]
74    #[bw(calc(args.data_size - 8))]
75    data_size: u32,
76    pub(crate) flags: TextureFlagsInner,
77    pub(crate) width: u16,
78    pub(crate) height: u16,
79    pub(crate) format: WoaRenderFormat,
80    pub(crate) num_mip_levels: u8,
81    pub(crate) default_mip_level: u8,
82    pub(crate) interpret_as: InterpretAs,
83    pub(crate) dimensions: Dimensions,
84    #[br(temp)]
85    #[bw(calc(0))]
86    mips_interpolation_deprecated: u16,
87
88    pub(crate) mip_sizes: [u32; MAX_MIP_LEVELS],
89    #[br(temp)]
90    #[bw(calc(args.atlas_data_size))]
91    atlas_data_size: u32,
92    #[br(temp)]
93    #[bw(calc(0x54))]
94    atlas_data_offset: u32,
95
96    //additional properties
97    #[br(calc = atlas_data_size > 0)]
98    #[bw(ignore)]
99    pub(crate) has_atlas: bool,
100}
101
102impl TextureMapHeaderImpl for TextureMapHeaderV1 {
103    fn text_scale(&self) -> usize {
104        let texd_mips = self.num_mip_levels as usize;
105
106        if texd_mips == 1 {
107            return 0;
108        }
109
110        if self.interpret_as == InterpretAs::Billboard {
111            return 0;
112        }
113
114        let area = self.width as usize * self.height as usize;
115        ((area as f32).log2() * 0.5 - 6.5).floor() as usize
116    }
117
118    fn size() -> usize {
119        92
120    }
121
122    fn text_data_size(&self) -> usize {
123        let text_mip_levels = self.num_mip_levels as usize - self.text_scale();
124        let blocks_to_skip = self.num_mip_levels as usize - text_mip_levels;
125        let last_mip_size = self.mip_sizes[(self.num_mip_levels - 1) as usize] as usize;
126        if blocks_to_skip == 0 {
127            return last_mip_size;
128        }
129        let texd_mip_size = self.mip_sizes.get(blocks_to_skip - 1).unwrap_or(&0);
130        last_mip_size - *texd_mip_size as usize
131    }
132
133    fn has_atlas(&self) -> bool {
134        self.has_atlas
135    }
136
137    fn texd_mip_levels(&self) -> usize {
138        self.num_mip_levels as usize
139    }
140
141    fn compressed_mip_sizes(&self) -> [u32; 14] {
142        self.mip_sizes
143    }
144}
145
146#[binrw]
147#[derive(Serialize, Deserialize, Clone, Debug)]
148#[br(assert(mip_sizes == compressed_mip_sizes))]
149#[br(assert(num_textures == 1))]
150#[bw(import(args: DynamicTextureMapArgs))]
151pub(crate) struct TextureMapHeaderV2 {
152    #[br(temp)]
153    #[bw(calc(1))]
154    num_textures: u16,
155
156    pub(crate) type_: TextureType,
157
158    #[br(temp)]
159    #[bw(calc(args.data_size))]
160    data_size: u32,
161    pub(crate) flags: TextureFlagsInner,
162    pub(crate) width: u16,
163    pub(crate) height: u16,
164    pub(crate) format: WoaRenderFormat,
165    pub(crate) num_mip_levels: u8,
166    pub(crate) default_mip_level: u8,
167    pub(crate) texd_identifier: u32,
168    pub(crate) mip_sizes: [u32; MAX_MIP_LEVELS],
169    pub(crate) compressed_mip_sizes: [u32; MAX_MIP_LEVELS],
170    #[br(temp)]
171    #[bw(calc(args.atlas_data_size))]
172    atlas_data_size: u32,
173    #[br(temp)]
174    #[bw(calc(0x90))]
175    atlas_data_offset: u32,
176
177    //additional properties
178    #[br(calc = atlas_data_size > 0)]
179    #[bw(ignore)]
180    pub(crate) has_atlas: bool,
181}
182
183impl TextureMapHeaderImpl for TextureMapHeaderV2 {
184    fn text_scale(&self) -> usize {
185        let texd_mips = self.num_mip_levels as usize;
186        if texd_mips == 1 {
187            return 0;
188        }
189
190        if self.type_ == TextureType::Billboard {
191            return 0;
192        }
193
194        if self.format == RenderFormat::BC1 && (self.width as usize * self.height as usize) == 16 {
195            return 1;
196        }
197
198        if self.texd_identifier != 16384 {
199            return 0;
200        }
201
202        let area = self.width as usize * self.height as usize;
203        ((area as f32).log2() * 0.5 - 6.5).floor() as usize
204    }
205
206    fn size() -> usize {
207        144
208    }
209
210    fn text_data_size(&self) -> usize {
211        let text_mip_levels = self.num_mip_levels as usize - self.text_scale();
212        let blocks_to_skip = self.num_mip_levels as usize - text_mip_levels;
213        let last_mip_size = self.compressed_mip_sizes[(self.num_mip_levels - 1) as usize] as usize;
214        if blocks_to_skip == 0 {
215            return last_mip_size;
216        }
217        let texd_mip_size = self
218            .compressed_mip_sizes
219            .get(blocks_to_skip - 1)
220            .unwrap_or(&0);
221        last_mip_size - *texd_mip_size as usize
222    }
223
224    fn has_atlas(&self) -> bool {
225        self.has_atlas
226    }
227
228    fn texd_mip_levels(&self) -> usize {
229        self.num_mip_levels as usize
230    }
231
232    fn compressed_mip_sizes(&self) -> [u32; 14] {
233        self.compressed_mip_sizes
234    }
235}
236
237#[binrw]
238#[derive(Serialize, Deserialize, Clone, Debug)]
239#[br(assert(text_scaling_width == num_mip_levels - text_mip_levels))]
240#[br(assert(text_scaling_height == num_mip_levels - text_mip_levels))]
241#[br(assert(num_textures == 1))]
242#[bw(import(args: DynamicTextureMapArgs))]
243pub(crate) struct TextureMapHeaderV3 {
244    #[br(temp)]
245    #[bw(calc(1))]
246    num_textures: u16,
247
248    pub(crate) type_: TextureType,
249
250    #[br(temp)]
251    #[bw(calc(args.data_size))]
252    data_size: u32,
253    pub(crate) flags: TextureFlagsInner,
254    pub(crate) width: u16,
255    pub(crate) height: u16,
256    pub(crate) format: WoaRenderFormat,
257    pub(crate) num_mip_levels: u8,
258    pub(crate) default_mip_level: u8,
259    pub(crate) interpret_as: InterpretAs,
260    pub(crate) dimensions: Dimensions,
261
262    #[br(temp)]
263    #[bw(calc(0))]
264    mips_interpolation_deprecated: u16,
265    pub(crate) mip_sizes: [u32; MAX_MIP_LEVELS],
266    pub(crate) compressed_mip_sizes: [u32; MAX_MIP_LEVELS],
267    #[br(temp)]
268    #[bw(calc(args.atlas_data_size))]
269    atlas_data_size: u32,
270    #[br(temp)]
271    #[bw(calc(0x98))]
272    atlas_data_offset: u32,
273    #[br(temp)]
274    #[bw(calc(0xFF))]
275    text_scaling_data1: u8,
276    #[br(temp)]
277    #[bw(calc(args.text_scale))]
278    text_scaling_width: u8,
279    #[br(temp)]
280    #[bw(calc(args.text_scale))]
281    text_scaling_height: u8,
282
283    #[br(temp)]
284    #[bw(calc(args.text_mip_levels))]
285    #[brw(pad_after = 0x4)]
286    text_mip_levels: u8,
287
288    //additional properties
289    #[br(calc = atlas_data_size > 0)]
290    #[bw(ignore)]
291    pub(crate) has_atlas: bool,
292}
293
294impl TextureMapHeaderImpl for TextureMapHeaderV3 {
295    fn text_scale(&self) -> usize {
296        let texd_mips = self.num_mip_levels as usize;
297        if texd_mips == 1 {
298            return 0;
299        }
300
301        if self.type_ == TextureType::Billboard || self.interpret_as == InterpretAs::Volume {
302            return 0;
303        }
304
305        if self.type_ == TextureType::Volume {
306            return 0;
307        }
308
309        if self.format == RenderFormat::BC1 && (self.width as usize * self.height as usize) == 16 {
310            return 1;
311        }
312
313        let area = self.width as usize * self.height as usize;
314        ((area as f32).log2() * 0.5 - 6.5).floor() as usize
315    }
316
317    fn size() -> usize {
318        152
319    }
320
321    fn text_data_size(&self) -> usize {
322        let text_mip_levels = self.num_mip_levels as usize - self.text_scale();
323        let blocks_to_skip = self.num_mip_levels as usize - text_mip_levels;
324        let last_mip_size = self.compressed_mip_sizes[(self.num_mip_levels - 1) as usize] as usize;
325        if blocks_to_skip == 0 {
326            return last_mip_size;
327        }
328        let texd_mip_size = self
329            .compressed_mip_sizes
330            .get(blocks_to_skip - 1)
331            .unwrap_or(&0);
332        last_mip_size - *texd_mip_size as usize
333    }
334
335    fn has_atlas(&self) -> bool {
336        self.has_atlas
337    }
338
339    fn texd_mip_levels(&self) -> usize {
340        self.num_mip_levels as usize
341    }
342
343    fn compressed_mip_sizes(&self) -> [u32; 14] {
344        self.compressed_mip_sizes
345    }
346}
347
348#[binrw]
349#[derive(Serialize, Deserialize, Clone, Debug)]
350#[br(assert(text_scaling_width == num_mip_levels - text_mip_levels))]
351#[br(assert(text_scaling_height == num_mip_levels - text_mip_levels))]
352#[br(assert(num_textures == 1))]
353#[bw(import(args: DynamicTextureMapArgs))]
354pub(crate) struct TextureMapHeaderV4 {
355    #[br(temp)]
356    #[bw(calc(1))]
357    num_textures: u16,
358
359    pub(crate) type_: TextureType,
360
361    #[br(temp)]
362    #[bw(calc(args.data_size))]
363    data_size: u32,
364    pub(crate) flags: TextureFlagsInner,
365    pub(crate) width: u16,
366    pub(crate) height: u16,
367    pub(crate) format: BondRenderFormat,
368    pub(crate) num_mip_levels: u8,
369    pub(crate) default_mip_level: u8,
370    pub(crate) interpret_as: InterpretAs,
371    pub(crate) dimensions: Dimensions,
372
373    #[br(temp)]
374    #[bw(calc(0))]
375    mips_interpolation_deprecated: u16,
376    pub(crate) mip_sizes: [u32; MAX_MIP_LEVELS],
377    pub(crate) compressed_mip_sizes: [u32; MAX_MIP_LEVELS],
378    #[br(temp)]
379    #[bw(calc(args.atlas_data_size))]
380    atlas_data_size: u32,
381    #[br(temp)]
382    #[bw(calc(0x98))]
383    atlas_data_offset: u32,
384    #[br(temp)]
385    #[bw(calc(0xFF))]
386    text_scaling_data1: u8,
387    #[br(temp)]
388    #[bw(calc(args.text_scale))]
389    text_scaling_width: u8,
390    #[br(temp)]
391    #[bw(calc(args.text_scale))]
392    text_scaling_height: u8,
393
394    #[br(temp)]
395    #[bw(calc(args.text_mip_levels))]
396    #[brw(pad_after = 0x4)]
397    text_mip_levels: u8,
398
399    //additional properties
400    #[br(calc = atlas_data_size > 0)]
401    #[bw(ignore)]
402    pub(crate) has_atlas: bool,
403}
404
405impl TextureMapHeaderImpl for TextureMapHeaderV4 {
406    fn text_scale(&self) -> usize {
407        let texd_mips = self.num_mip_levels as usize;
408        if texd_mips == 1 {
409            return 0;
410        }
411
412        if self.type_ == TextureType::Billboard || self.interpret_as == InterpretAs::Volume {
413            return 0;
414        }
415
416        if self.type_ == TextureType::Volume {
417            return 0;
418        }
419
420        if self.format == RenderFormat::BC1 && (self.width as usize * self.height as usize) == 16 {
421            return 1;
422        }
423
424        let area = self.width as usize * self.height as usize;
425        ((area as f32).log2() * 0.5 - 6.5).floor() as usize
426    }
427
428    fn size() -> usize {
429        152
430    }
431
432    fn text_data_size(&self) -> usize {
433        let text_mip_levels = self.num_mip_levels as usize - self.text_scale();
434        let blocks_to_skip = self.num_mip_levels as usize - text_mip_levels;
435        let last_mip_size = self.compressed_mip_sizes[(self.num_mip_levels - 1) as usize] as usize;
436        if blocks_to_skip == 0 {
437            return last_mip_size;
438        }
439        let texd_mip_size = self
440            .compressed_mip_sizes
441            .get(blocks_to_skip - 1)
442            .unwrap_or(&0);
443        last_mip_size - *texd_mip_size as usize
444    }
445
446    fn has_atlas(&self) -> bool {
447        self.has_atlas
448    }
449
450    fn texd_mip_levels(&self) -> usize {
451        self.num_mip_levels as usize
452    }
453
454    fn compressed_mip_sizes(&self) -> [u32; 14] {
455        self.compressed_mip_sizes
456    }
457}
458
459#[binrw]
460#[derive(Serialize, Deserialize, Clone, Debug)]
461#[br(import(glacier_game: GlacierGame))]
462pub struct TextureMap {
463    #[br(args(glacier_game))]
464    pub(crate) inner: TextureMapVersion,
465}
466
467#[binrw]
468#[derive(Serialize, Deserialize, Clone, Debug)]
469#[br(import(glacier_game: GlacierGame))]
470pub(crate) enum TextureMapVersion {
471    #[br(pre_assert(glacier_game == GlacierGame::HM2016))]
472    V1(TextureMapInner<TextureMapHeaderV1>),
473
474    #[br(pre_assert(glacier_game == GlacierGame::HM2))]
475    V2(TextureMapInner<TextureMapHeaderV2>),
476
477    #[br(pre_assert(glacier_game == GlacierGame::HM3))]
478    V3(TextureMapInner<TextureMapHeaderV3>),
479
480    #[br(pre_assert(glacier_game == GlacierGame::KNT))]
481    V4(TextureMapInner<TextureMapHeaderV4>),
482}
483
484impl From<TextureMapInner<TextureMapHeaderV1>> for TextureMap {
485    fn from(inner: TextureMapInner<TextureMapHeaderV1>) -> Self {
486        Self {
487            inner: TextureMapVersion::V1(inner),
488        }
489    }
490}
491
492impl From<TextureMapInner<TextureMapHeaderV2>> for TextureMap {
493    fn from(inner: TextureMapInner<TextureMapHeaderV2>) -> Self {
494        Self {
495            inner: TextureMapVersion::V2(inner),
496        }
497    }
498}
499
500impl From<TextureMapInner<TextureMapHeaderV3>> for TextureMap {
501    fn from(inner: TextureMapInner<TextureMapHeaderV3>) -> Self {
502        Self {
503            inner: TextureMapVersion::V3(inner),
504        }
505    }
506}
507
508impl From<TextureMapInner<TextureMapHeaderV4>> for TextureMap {
509    fn from(inner: TextureMapInner<TextureMapHeaderV4>) -> Self {
510        Self {
511            inner: TextureMapVersion::V4(inner),
512        }
513    }
514}
515
516/// Represents the texture data, which can be either raw texture data or a mipblock read from a texd file.
517#[derive(Serialize, Deserialize, Clone, Debug)]
518pub enum TextureData {
519    /// Raw texture data.
520    Tex(Vec<u8>),
521    /// Mipblock data (obtained from a TEXD resource).
522    Mipblock1(MipblockData),
523}
524
525impl BinWrite for TextureData {
526    type Args<'a> = (usize,);
527
528    fn write_options<W: Write + Seek>(
529        &self,
530        writer: &mut W,
531        endian: Endian,
532        args: Self::Args<'_>,
533    ) -> BinResult<()> {
534        match self {
535            TextureData::Tex(data) => writer.write_type(data, endian),
536            TextureData::Mipblock1(mipblock) => {
537                let data = &mipblock.data;
538                let cut_data = &data
539                    .clone()
540                    .into_iter()
541                    .skip(data.len() - args.0)
542                    .collect::<Vec<_>>();
543                writer.write_type(cut_data, endian)
544            }
545        }
546    }
547}
548
549impl TextureData {
550    fn size(&self) -> usize {
551        match self {
552            TextureData::Tex(d) => d.len(),
553            TextureData::Mipblock1(d) => d.data.len(),
554        }
555    }
556}
557
558#[binread]
559#[derive(Serialize, Deserialize, Clone, Debug)]
560pub(crate) struct TextureMapInner<A>
561where
562    A: for<'a> BinRead<Args<'a> = ()>,
563    A: TextureMapHeaderImpl,
564{
565    pub header: A,
566
567    //I would like to seek_before = SeekFrom::Start(TextureMapHeaderArgs::from(header.clone()).atlas_data_offset as u64) here, but H1 and 2 have a -8 offset on the pointer
568    #[br(if (header.has_atlas()))]
569    pub atlas_data: Option<AtlasData>,
570
571    #[br(parse_with = until_eof, map = TextureData::Tex)]
572    #[serde(skip_serializing)]
573    pub data: TextureData,
574}
575
576impl<A> BinWrite for TextureMapInner<A>
577where
578    A: for<'a> BinWrite<Args<'a> = (DynamicTextureMapArgs,)>
579        + Clone
580        + for<'a> binrw::BinRead<Args<'a> = ()>,
581    A: TextureMapHeaderImpl,
582{
583    type Args<'a> = ();
584
585    fn write_options<W: Write + Seek>(
586        &self,
587        writer: &mut W,
588        endian: Endian,
589        _: Self::Args<'_>,
590    ) -> BinResult<()> {
591        let atlas_size = self.atlas_data_size();
592        let total_size = self.data.size() + A::size() + atlas_size;
593
594        let args = DynamicTextureMapArgs {
595            data_size: total_size as u32,
596            atlas_data_size: atlas_size as u32,
597            text_scale: self.header.text_scale() as u8,
598            text_mip_levels: self.header.texd_mip_levels() as u8 - self.header.text_scale() as u8,
599        };
600        self.header.write_options(writer, endian, (args,))?;
601
602        // If atlas_data is present, write it
603        if let Some(atlas_data) = &self.atlas_data {
604            atlas_data.write_options(writer, endian, ())?;
605        }
606
607        // Now write the data
608        let text_data_size = self.header.text_data_size();
609        self.data.write_options(writer, endian, (text_data_size,))?;
610
611        Ok(())
612    }
613}
614
615impl<A> TextureMapInner<A>
616where
617    A: for<'a> BinRead<Args<'a> = ()>,
618    A: Clone,
619    A: for<'a> binrw::BinWrite<Args<'a> = (DynamicTextureMapArgs,)>,
620    A: TextureMapHeaderImpl,
621{
622    pub fn data(&self) -> &Vec<u8> {
623        match &self.data {
624            TextureData::Tex(d) => d,
625            TextureData::Mipblock1(d) => &d.data,
626        }
627    }
628
629    pub fn atlas_data(&self) -> &Option<AtlasData> {
630        &self.atlas_data
631    }
632
633    fn atlas_data_size(&self) -> usize {
634        self.atlas_data
635            .as_ref()
636            .map(|atlas| atlas.size())
637            .unwrap_or(0)
638    }
639
640    pub fn has_mipblock_data(&self) -> bool {
641        match &self.data {
642            TextureData::Tex(_) => false,
643            TextureData::Mipblock1(_) => true,
644        }
645    }
646}
647
648#[derive(Debug, Serialize, Deserialize)]
649pub struct MipLevel {
650    pub format: RenderFormat,
651    pub width: usize,
652    pub height: usize,
653    pub data: Vec<u8>,
654}
655
656macro_rules! match_texture_map {
657    ($value:expr, $binding:ident => $expr:expr) => {
658        match $value {
659            TextureMapVersion::V1($binding) => $expr,
660            TextureMapVersion::V2($binding) => $expr,
661            TextureMapVersion::V3($binding) => $expr,
662            TextureMapVersion::V4($binding) => $expr,
663        }
664    };
665}
666
667impl TextureMap {
668    pub fn default_mip_level(&self) -> u8 {
669        match_texture_map!(&self.inner, tex => {tex.header.default_mip_level})
670    }
671
672    pub fn version(&self) -> GlacierGame {
673        match &self.inner {
674            TextureMapVersion::V1(_) => GlacierGame::HM2016,
675            TextureMapVersion::V2(_) => GlacierGame::HM2,
676            TextureMapVersion::V3(_) => GlacierGame::HM3,
677            TextureMapVersion::V4(_) => GlacierGame::HM3,
678        }
679    }
680
681    pub(crate) fn data(&self) -> &Vec<u8> {
682        match_texture_map!(&self.inner, tex => {tex.data()})
683    }
684
685    pub fn atlas(&self) -> &Option<AtlasData> {
686        match_texture_map!(&self.inner, tex => {tex.atlas_data()})
687    }
688
689    fn set_data(&mut self, data: TextureData) {
690        match_texture_map!(&mut self.inner, tex => {tex.data = data})
691    }
692
693    fn text_mip_levels(&self) -> usize {
694        self.texd_mip_levels() - self.text_scale()
695    }
696
697    fn texd_mip_levels(&self) -> usize {
698        match_texture_map!(&self.inner, tex => {tex.header.num_mip_levels as usize})
699    }
700
701    pub fn num_mip_levels(&self) -> usize {
702        if self.has_mipblock1() || self.independent() {
703            self.texd_mip_levels()
704        } else {
705            self.text_mip_levels()
706        }
707    }
708
709    fn text_scale(&self) -> usize {
710        match_texture_map!(&self.inner, tex => {tex.header.text_scale()})
711    }
712
713    pub(crate) fn mip_sizes(&self) -> Vec<u32> {
714        match &self.inner {
715            TextureMapVersion::V1(tex) => tex
716                .header
717                .mip_sizes
718                .iter()
719                .copied()
720                .filter(|mip| *mip != 0)
721                .collect(),
722            TextureMapVersion::V2(tex) => tex
723                .header
724                .mip_sizes
725                .iter()
726                .copied()
727                .filter(|mip| *mip != 0)
728                .collect(),
729            TextureMapVersion::V3(tex) => tex
730                .header
731                .mip_sizes
732                .iter()
733                .copied()
734                .filter(|mip| *mip != 0)
735                .collect(),
736            TextureMapVersion::V4(tex) => tex
737                .header
738                .mip_sizes
739                .iter()
740                .copied()
741                .filter(|mip| *mip != 0)
742                .collect(),
743        }
744    }
745
746    pub(crate) fn compressed_mip_sizes(&self) -> Vec<u32> {
747        match_texture_map!(&self.inner, tex => {tex.header.compressed_mip_sizes().iter().copied().filter(|mip| *mip != 0).collect()})
748    }
749
750    pub fn video_memory_requirement(&self) -> usize {
751        match self.version() {
752            GlacierGame::HM2016 | GlacierGame::HM2 => {
753                self.mip_sizes()
754                    .get(self.text_scale())
755                    .cloned()
756                    .unwrap_or(0) as usize //The size of the largest TEXT mip
757            }
758            GlacierGame::HM3 | GlacierGame::KNT => {
759                if self.has_mipblock1() {
760                    //if texture has a TEXD
761                    (self.mip_sizes().first().cloned().unwrap_or(0)
762                        + self.mip_sizes().get(1).cloned().unwrap_or(0))
763                        as usize //the size of the largest two TEXD mip
764                } else {
765                    0
766                }
767            }
768        }
769    }
770
771    pub fn mipblock1(&self) -> Option<MipblockData> {
772        self.has_mipblock1()
773            .then(|| {
774                self.texd_header().ok().map(|header| MipblockData {
775                    video_memory_requirement: self.mip_sizes().first().copied().unwrap_or(0x0)
776                        as usize,
777                    header,
778                    data: self.data().clone(),
779                })
780            })
781            .flatten()
782    }
783
784    fn texd_size(&self) -> (usize, usize) {
785        match_texture_map!(&self.inner, tex => {(tex.header.width as usize, tex.header.height as usize)})
786    }
787
788    fn text_size(&self) -> (usize, usize) {
789        let (width, height) = self.texd_size();
790        let scale_factor = 1 << self.text_scale();
791        (width / scale_factor, height / scale_factor)
792    }
793
794    pub fn width(&self) -> usize {
795        if self.has_mipblock1() || self.independent() {
796            self.texd_size().0
797        } else {
798            self.text_size().0
799        }
800    }
801
802    pub fn height(&self) -> usize {
803        if self.has_mipblock1() || self.independent() {
804            self.texd_size().1
805        } else {
806            self.text_size().1
807        }
808    }
809
810    ///If a TEXT is generated without a TEXD it will contain all data, we can call this concept independence.
811    pub(crate) fn independent(&self) -> bool {
812        //Check to see if the TEXT contains ALL mips, this will happen when a TEXT is generated without a TEXD.
813        let mips_size_total = self.compressed_mip_sizes().last().copied().unwrap_or(0) as usize;
814        self.data().len() == mips_size_total //if true no TEXD exists
815    }
816
817    pub fn format(&self) -> RenderFormat {
818        match_texture_map!(&self.inner, tex => {RenderFormat::from(tex.header.format)})
819    }
820
821    pub fn flags(&self) -> TextureFlags {
822        match_texture_map!(&self.inner, tex => TextureFlags {
823            inner: tex.header.flags,
824        })
825    }
826
827    pub fn texture_type(&self) -> TextureType {
828        match_texture_map!(&self.inner, tex => tex.header.type_)
829    }
830
831    pub fn interpret_as(&self) -> Option<InterpretAs> {
832        match &self.inner {
833            TextureMapVersion::V1(tex) => Some(tex.header.interpret_as),
834            TextureMapVersion::V2(_) => None,
835            TextureMapVersion::V3(tex) => Some(tex.header.interpret_as),
836            TextureMapVersion::V4(tex) => Some(tex.header.interpret_as),
837        }
838    }
839
840    pub fn dimensions(&self) -> Dimensions {
841        match &self.inner {
842            TextureMapVersion::V1(tex) => tex.header.dimensions,
843            TextureMapVersion::V2(_) => Dimensions::_2D,
844            TextureMapVersion::V3(tex) => tex.header.dimensions,
845            TextureMapVersion::V4(tex) => tex.header.dimensions,
846        }
847    }
848
849    pub fn has_mipblock1(&self) -> bool {
850        match_texture_map!(&self.inner, tex => tex.has_mipblock_data())
851    }
852
853    pub fn from_file<P: AsRef<Path>>(
854        path: P,
855        glacier_game: GlacierGame,
856    ) -> Result<Self, TextureMapError> {
857        let file = File::open(path).map_err(TextureMapError::IoError)?;
858        let mmap = unsafe { memmap2::Mmap::map(&file).map_err(TextureMapError::IoError)? };
859        let mut reader = Cursor::new(&mmap[..]);
860        TextureMap::read_le_args(&mut reader, (glacier_game,))
861            .map_err(TextureMapError::ParsingError)
862    }
863
864    pub fn from_memory(data: &[u8], glacier_game: GlacierGame) -> Result<Self, TextureMapError> {
865        let mut reader = Cursor::new(data);
866        TextureMap::read_le_args(&mut reader, (glacier_game,))
867            .map_err(TextureMapError::ParsingError)
868    }
869
870    pub fn default_mipmap(&self) -> Result<MipLevel, TextureMapError> {
871        self.mipmap(self.default_mip_level() as usize)
872    }
873
874    pub fn mipmaps(&self) -> impl Iterator<Item = Result<MipLevel, TextureMapError>> + '_ {
875        (0..self.num_mip_levels()).map(move |level| self.mipmap(level))
876    }
877
878    pub fn mipmap(&self, level: usize) -> Result<MipLevel, TextureMapError> {
879        let removed_mip_count = self.texd_mip_levels() - self.text_mip_levels();
880
881        let mut mips_sizes: Vec<u32> = self.mip_sizes();
882        let mut block_sizes: Vec<u32> = self.compressed_mip_sizes();
883
884        //If the TEXT was generated with a TEXD but the TEXD isn’t loaded before reading, we adjust for the missing data.
885        if !self.has_mipblock1() && !self.independent() {
886            let removed_mip = mips_sizes
887                .drain(0..removed_mip_count)
888                .collect::<Vec<u32>>()
889                .pop()
890                .unwrap_or(0);
891            mips_sizes.iter_mut().for_each(|x| {
892                if *x > 0 {
893                    *x -= removed_mip
894                }
895            });
896
897            let removed_block = block_sizes
898                .drain(0..removed_mip_count)
899                .collect::<Vec<u32>>()
900                .pop()
901                .unwrap_or(0);
902            block_sizes.iter_mut().for_each(|x| {
903                if *x > 0 {
904                    *x -= removed_block
905                }
906            });
907        }
908
909        if level > self.texd_mip_levels() {
910            return Err(TextureMapError::UnknownError(
911                "mip level is out of bounds".parse().unwrap(),
912            ));
913        }
914
915        let mip_start = if level > 0 { mips_sizes[level - 1] } else { 0 };
916        let mip_size = mips_sizes.get(level).ok_or(TextureMapError::UnknownError(
917            "mip level is out of bounds".parse().unwrap(),
918        ))? - mip_start;
919
920        let block_start = if level > 0 { block_sizes[level - 1] } else { 0 };
921        let block_size = block_sizes.get(level).ok_or(TextureMapError::UnknownError(
922            "mip level is out of bounds".parse().unwrap(),
923        ))? - block_start;
924
925        let is_compressed = mip_size != block_size;
926        let block = self
927            .data()
928            .clone()
929            .into_iter()
930            .skip(block_start as usize)
931            .take(block_size as usize)
932            .collect::<Vec<u8>>();
933        let data = if is_compressed {
934            let mut dst = vec![0u8; mip_size as usize];
935            match lz4::block::decompress_to_buffer(
936                block.as_slice(),
937                Some(mip_size as i32),
938                &mut dst,
939            ) {
940                Ok(_) => {}
941                Err(e) => {
942                    return Err(TextureMapError::UnknownError(format!(
943                        "Failed to decompress texture data {e}"
944                    )));
945                }
946            };
947            dst
948        } else {
949            block
950        };
951
952        Ok(MipLevel {
953            format: self.format(),
954            width: self.width() >> level,
955            height: self.height() >> level,
956            data,
957        })
958    }
959
960    pub fn has_atlas(&self) -> bool {
961        self.atlas().is_some()
962    }
963
964    pub fn set_mipblock1(&mut self, mipblock: MipblockData) {
965        self.set_data(TextureData::Mipblock1(mipblock))
966    }
967
968    fn texd_header(&self) -> Result<Vec<u8>, TextureMapError> {
969        let mut writer = Cursor::new(Vec::new());
970
971        let data = match_texture_map!(&self.inner, tex => &tex.data);
972
973        let atlas_size = self.atlas().as_ref().map(|atlas| atlas.size()).unwrap_or(0);
974        let total_size = data.size()
975            + match &self.inner {
976                TextureMapVersion::V1(_) => TextureMapHeaderV1::size(),
977                TextureMapVersion::V2(_) => TextureMapHeaderV2::size(),
978                TextureMapVersion::V3(_) => TextureMapHeaderV3::size(),
979                TextureMapVersion::V4(_) => TextureMapHeaderV4::size(),
980            }
981            + atlas_size;
982
983        let args = DynamicTextureMapArgs {
984            data_size: total_size as u32,
985            atlas_data_size: atlas_size as u32,
986
987            //not needed as these are only used in H3, which doesn't use a texd header.
988            text_scale: 0,
989            text_mip_levels: 0,
990        };
991        match_texture_map!(&self.inner, tex => tex.header.write_options(&mut writer, Endian::Little, (args,))?);
992
993        // If atlas_data is present, write it
994        if let Some(atlas_data) = &self.atlas() {
995            atlas_data.write_options(&mut writer, Endian::Little, ())?;
996        }
997
998        Ok(writer.into_inner())
999    }
1000
1001    pub fn pack_to_vec(&self) -> Result<Vec<u8>, TexturePackerError> {
1002        let mut writer = Cursor::new(Vec::new());
1003        self.pack_internal(&mut writer)?;
1004        Ok(writer.into_inner())
1005    }
1006
1007    pub fn pack_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), TexturePackerError> {
1008        let file = fs::File::create(path).map_err(TexturePackerError::IoError)?;
1009        let mut writer = BufWriter::new(file);
1010        self.pack_internal(&mut writer)?;
1011        Ok(())
1012    }
1013
1014    fn pack_internal<W: Write + Seek>(&self, writer: &mut W) -> Result<(), TexturePackerError> {
1015        self.write_le_args(writer, ())
1016            .map_err(TexturePackerError::SerializationError)?;
1017        Ok(())
1018    }
1019}