decrunch_unity/
lib.rs

1// Copyright (c) Istvan Fehervari
2
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21//! # Decoder for crunch-compressed texture data
22//!
23//! This crate provides a Rust wrapper around [crunch's](https://github.com/BinomialLLC/crunch) decompressor.
24//!
25//! # Example
26//!
27//! ```
28//! use decrunch::*;
29//! use std::fs::File;
30//! use std::io::Read;
31//!
32//! # use std::io;
33//! # fn foo() -> io::Result<()> {
34//! let mut compressed_file = File::open("testdata/copyright_2048_compressed.dat")?;
35//! let mut compressed_data = Vec::new();
36//!
37//! compressed_file.read_to_end(&mut compressed_data)?;
38//!
39//! let c_data = CrunchedData::new(&compressed_data);
40//! let decompressed_data = match c_data.decode_level(0) {
41//!     None => {
42//!         panic!("Failed to decompress texture data");
43//!     }
44//!     Some(res) => res,
45//! };
46//!
47//! assert!(decompressed_data.len() > 0);
48//!
49//! # Ok(())
50//! # }
51//! ```
52
53extern crate libc;
54
55mod crunch;
56
57use libc::c_void;
58use std::mem;
59
60// Rust with repr(C) actually does 8 bytes here, as does the c compiler on linux
61// For a windows build (at least with cargo xwin), the c compiler will actually
62// use 4 bytes as intended, so we use repr(i32) to match. This is probably
63// the wrong way to fix this, but works and doesn't require changing the c code.
64#[cfg_attr(target_os = "windows", repr(i32))]
65#[cfg_attr(target_os = "linux", repr(C))]
66#[derive(Debug, Clone, Copy, PartialEq)]
67#[derive(Default)]
68pub enum CrnFormat {
69    FirstValid = -2,
70    #[default]
71    Invalid = -1,
72    Dxt1 = 0,
73    /// cCRNFmtDXT3 is not currently supported when writing to CRN - only DDS.
74    Dxt3,
75    Dxt5,
76    // Various DXT5 derivatives
77    /// Luma-chroma
78    Dxt5cCxY,
79    /// Swizzled 2-component
80    Dxt5xGxR,
81    /// Swizzled 3-component
82    Dxt5xGBR,
83    /// Swizzled 4-component
84    Dxt5Agbr,
85
86    /// ATI 3DC and X360 DXN
87    DxNXy,
88    DxNYx,
89
90    /// DXT5 alpha blocks only
91    Dxt5A,
92    Etc1,
93    Total,
94    #[cfg(target_os = "linux")] // this doesn't work with repr(i32)
95    ForceDWORD = 0xFFFFFFFF,
96}
97
98
99#[repr(C)]
100#[derive(Debug, Clone, Copy)]
101pub struct LevelInfo {
102    pub struct_size: u32,
103    pub width: u32,
104    pub height: u32,
105    pub faces: u32,
106    pub blocks_x: u32,
107    pub blocks_y: u32,
108    pub bytes_per_block: u32,
109    pub format: CrnFormat,
110}
111
112impl Default for LevelInfo {
113    fn default() -> LevelInfo {
114        LevelInfo {
115            struct_size: mem::size_of::<LevelInfo>() as u32,
116            width: 0,
117            height: 0,
118            faces: 0,
119            blocks_x: 0,
120            blocks_y: 0,
121            bytes_per_block: 0,
122            format: CrnFormat::Invalid,
123        }
124    }
125}
126
127#[repr(C)]
128#[derive(Debug, PartialEq)]
129pub struct TextureInfo {
130    pub struct_size: u32,
131    pub width: u32,
132    pub height: u32,
133    pub levels: u32,
134    pub faces: u32,
135    pub bytes_per_block: u32,
136    pub userdata0: u32,
137    pub userdata1: u32,
138    pub format: CrnFormat,
139}
140
141impl Default for TextureInfo {
142    fn default() -> TextureInfo {
143        TextureInfo {
144            struct_size: mem::size_of::<TextureInfo>() as u32,
145            width: 0,
146            height: 0,
147            levels: 0,
148            faces: 0,
149            bytes_per_block: 0,
150            userdata0: 0,
151            userdata1: 0,
152            format: CrnFormat::Invalid,
153        }
154    }
155}
156
157pub struct CrunchedData<'a> {
158    pub buffer: &'a [u8],
159    ctx: *const c_void,
160}
161
162impl<'a> CrunchedData<'a> {
163    pub fn new(buffer: &'a [u8]) -> Self {
164        CrunchedData {
165            buffer,
166            ctx: crunch::unpack_begin(buffer),
167        }
168    }
169
170    /// Retrieves mipmap level specific information from the CRN data.
171    pub fn level_info(&self, level: u32) -> LevelInfo {
172        crunch::get_level_info(self, level)
173    }
174
175    /// Retrieves texture information from the CRN data.
176    pub fn texture_info(&self) -> TextureInfo {
177        crunch::get_texture_info(self)
178    }
179
180    /// Transcodes the specified mipmap level to a destination buffer.
181    pub fn decode_level(&self, level: u32) -> Option<Vec<u8>> {
182        let info = self.level_info(level);
183        let mut dst: Vec<u8> =
184            vec![0; (info.blocks_x * info.blocks_y * info.bytes_per_block) as usize];
185        if !crunch::unpack_level(
186            self.ctx,
187            &mut dst,
188            info.blocks_x * info.bytes_per_block,
189            level,
190        ) {
191            return None;
192        }
193        Some(dst)
194    }
195}
196
197impl Drop for CrunchedData<'_> {
198    fn drop(&mut self) {
199        crunch::unpack_end(self.ctx);
200    }
201}
202
203#[cfg(test)]
204mod tests;