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;