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
16const 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
31pub(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
41pub(crate) trait TextureMapHeaderImpl {
43 fn text_scale(&self) -> usize;
45 fn size() -> usize;
47 fn text_data_size(&self) -> usize;
49 fn has_atlas(&self) -> bool;
51 fn texd_mip_levels(&self) -> usize;
53
54 fn compressed_mip_sizes(&self) -> [u32; 14];
55}
56
57#[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 #[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 #[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 #[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 #[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#[derive(Serialize, Deserialize, Clone, Debug)]
518pub enum TextureData {
519 Tex(Vec<u8>),
521 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 #[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 let Some(atlas_data) = &self.atlas_data {
604 atlas_data.write_options(writer, endian, ())?;
605 }
606
607 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 }
758 GlacierGame::HM3 | GlacierGame::KNT => {
759 if self.has_mipblock1() {
760 (self.mip_sizes().first().cloned().unwrap_or(0)
762 + self.mip_sizes().get(1).cloned().unwrap_or(0))
763 as usize } 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 pub(crate) fn independent(&self) -> bool {
812 let mips_size_total = self.compressed_mip_sizes().last().copied().unwrap_or(0) as usize;
814 self.data().len() == mips_size_total }
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 !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 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 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}