lotus_utils_texture/
utils.rs

1use std::fs::File;
2use std::io::{Seek, SeekFrom};
3
4use anyhow::{Error, Result};
5use bytebuffer::ByteBuffer;
6use log::debug;
7use lotus_lib::cache_pair::{CachePair, CachePairReader};
8use lotus_lib::compression::{decompress_post_ensmallening, get_block_lengths};
9use lotus_lib::package::{Package, PackageType};
10use lotus_lib::toc::{FileNode, Node};
11
12use crate::header::TextureHeader;
13use crate::raw_header::RawTextureHeader;
14use crate::kind::TextureKind;
15
16pub trait Texture {
17    /// Check if the given node is a texture.
18    ///
19    /// # Errors
20    ///
21    /// Returns an error if the H cache is not found.
22    fn is_texture(&self, node: &Node) -> Result<bool>;
23
24    /// Decompresses the texture file data for the given node.
25    ///
26    /// # Returns
27    ///
28    /// A tuple containing the decompressed audio file data and the name of the audio file.
29    fn decompress_texture(&self, node: &Node) -> Result<(Vec<u8>, String)>;
30}
31
32impl Texture for Package<CachePairReader> {
33    fn is_texture(&self, node: &Node) -> Result<bool> {
34        if !node.name().ends_with(".png") {
35            return Ok(false);
36        }
37
38        let h_cache = self
39            .borrow(PackageType::H)
40            .ok_or(Error::msg("No header file found"))?;
41
42        let header_file_data = h_cache.decompress_data(node.clone())?;
43        let header = match RawTextureHeader::try_from(header_file_data.as_slice()) {
44            Ok(header) => header,
45            Err(_) => return Ok(false),
46        };
47
48        match TextureKind::try_from(header.file_type) {
49            Ok(_) => Ok(true),
50            Err(_) => Ok(false),
51        }
52    }
53
54    fn decompress_texture(&self, node: &Node) -> Result<(Vec<u8>, String)> {
55        let h_cache = self.borrow(PackageType::H);
56        let f_cache = self.borrow(PackageType::F);
57        let b_cache = self.borrow(PackageType::B);
58
59        // Unwrap the H cache as there should always be a header file
60        let h_cache = match h_cache {
61            Some(h_cache) => h_cache,
62            None => return Err(Error::msg("No header file found")),
63        };
64
65        // Get the decompressed header file data
66        let header_file_data = h_cache.decompress_data(node.clone())?;
67
68        // Parse the header file
69        let header = TextureHeader::try_from(header_file_data.as_slice())?;
70        debug!("Header: {:?}", header);
71
72        let mut buffer = ByteBuffer::new();
73        buffer.write_bytes(b"DDS ");
74        header.header.write(&mut buffer)?;
75        if let Some(header10) = &header.header10 {
76            header10.write(&mut buffer)?;
77        }
78
79        if header.f_cache_image_count > 0 {
80            let f_cache = match f_cache {
81                Some(f_cache) => f_cache,
82                None => return Err(Error::msg("No F cache found")),
83            };
84            let file_node = f_cache.get_file_node(node.path()).unwrap();
85
86            debug!("Cache offset: {}", file_node.cache_offset() as u64);
87            debug!("Cache image size: {}", file_node.comp_len() as u64);
88            debug!("Real image size: {}", header.size() as u64);
89            debug!("Decompressed image size: {}", file_node.len() as u64);
90
91            let f_cache_offsets = &header.f_cache_image_offsets;
92
93            if f_cache_offsets.len() != 0 {
94                let cache_image_sub_offset = f_cache_offsets.last().unwrap_or(&0).to_owned();
95
96                let mut f_cache_reader = File::open(f_cache.cache_path()).unwrap();
97                let real_cache_image_sub_offset = get_real_cache_image_offset(
98                    &mut f_cache_reader,
99                    file_node.cache_offset() as usize,
100                    cache_image_sub_offset as usize,
101                )?;
102
103                debug!("Cache image offset: {}", cache_image_sub_offset);
104                debug!("Real cache image offset: {}", real_cache_image_sub_offset);
105
106                f_cache_reader.seek(SeekFrom::Current(real_cache_image_sub_offset as i64))?;
107
108                let file_data = decompress_post_ensmallening(
109                    file_node.comp_len() as usize,
110                    header.size() as usize,
111                    &mut f_cache_reader,
112                )?;
113                buffer.write_bytes(&file_data);
114            } else {
115                // Fall back to the old method if the cache image offsets are not present
116                let file_data = f_cache.decompress_data(file_node)?;
117                buffer.write_bytes(&file_data[file_data.len() - header.size()..]);
118            }
119        } else {
120            let b_cache = match b_cache {
121                Some(b_cache) => b_cache,
122                None => return Err(Error::msg("No B cache found")),
123            };
124            let file_node = b_cache.get_file_node(node.path()).unwrap();
125
126            debug!("Cache offset: {}", file_node.cache_offset() as u64);
127            debug!("Cache image size: {}", file_node.comp_len() as u64);
128            debug!("Real image size: {}", header.size() as u64);
129            debug!("Decompressed image size: {}", file_node.len() as u64);
130
131            let file_data = b_cache.decompress_data(file_node)?;
132            buffer.write_bytes(&file_data[file_data.len() - header.size()..]);
133        }
134
135        Ok((buffer.into_vec(), get_texture_file_name(node)))
136    }
137}
138
139fn get_real_cache_image_offset(
140    cache_reader: &mut File,
141    cache_image_offset: usize,
142    cache_image_sub_offset: usize,
143) -> Result<usize> {
144    cache_reader.seek(SeekFrom::Start(cache_image_offset as u64))?;
145
146    const BLOCK_HEADER_LEN: usize = 8;
147
148    let mut cache_offset_top: usize = 0;
149    let mut cache_offset_bottom: usize = 0;
150
151    loop {
152        let (block_compressed_len, _) = get_block_lengths(cache_reader)?.unwrap_or((0, 0));
153        cache_offset_top += block_compressed_len as usize + BLOCK_HEADER_LEN;
154
155        if cache_offset_top >= cache_image_sub_offset {
156            break;
157        }
158
159        cache_offset_bottom = cache_offset_top;
160        cache_reader.seek(SeekFrom::Current(block_compressed_len as i64))?;
161    }
162
163    // Seek back to the start of the block
164    cache_reader.seek(SeekFrom::Start(cache_image_offset as u64))?;
165
166    let diff_top = cache_offset_top - cache_image_sub_offset;
167    let diff_bottom = cache_image_sub_offset - cache_offset_bottom;
168
169    if diff_top > diff_bottom {
170        return Ok(cache_offset_bottom);
171    } else {
172        return Ok(cache_offset_top);
173    }
174}
175
176fn get_texture_file_name(node: &Node) -> String {
177    let mut file_name = node.name();
178    if file_name.ends_with(".png") {
179        file_name.truncate(file_name.len() - 4);
180    }
181    file_name.push_str(".dds");
182    file_name
183}