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