1use libloading::Library;
2use std::ffi::{OsStr, c_void};
3use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[repr(i32)]
11pub enum OodleFuzzSafe {
12 No = 0,
13 Yes = 1,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17#[repr(i32)]
18pub enum OodleCheckCrc {
19 No = 0,
20 Yes = 1,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24#[repr(i32)]
25pub enum OodleVerbosity {
26 None = 0,
27 Minimal = 1,
28 Some = 2,
29 Lots = 3,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33#[repr(i32)]
34pub enum OodleDecodeThreadPhase {
35 Phase1 = 1,
36 Phase2 = 2,
37 All = 3,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41#[repr(i32)]
42pub enum OodleCompressor {
43 Invalid = -1,
44 None = 3,
45 Kraken = 8,
46 Mermaid = 9,
47 Selkie = 11,
48 Hydra = 12,
49 Leviathan = 13,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53#[repr(i32)]
54pub enum OodleCompressionLevel {
55 HyperFast4 = -4,
56 HyperFast3 = -3,
57 HyperFast2 = -2,
58 HyperFast1 = -1,
59 None = 0,
60 SuperFast = 1,
61 VeryFast = 2,
62 Fast = 3,
63 Normal = 4,
64 Optimal1 = 5,
65 Optimal2 = 6,
66 Optimal3 = 7,
67 Optimal4 = 8,
68 Optimal5 = 9,
69}
70
71type CompressFn = unsafe extern "C" fn(
76 compressor: OodleCompressor,
77 raw_buf: *const c_void,
78 raw_len: isize,
79 comp_buf: *mut c_void,
80 level: OodleCompressionLevel,
81 p_options: *const c_void,
82 dictionary_base: *const c_void,
83 lrm: *const c_void,
84 scratch_mem: *mut c_void,
85 scratch_size: isize,
86) -> isize;
87
88type DecompressFn = unsafe extern "C" fn(
89 comp_buf: *const c_void,
90 comp_buf_size: isize,
91 raw_buf: *mut c_void,
92 raw_len: isize,
93 fuzz_safe: OodleFuzzSafe,
94 check_crc: OodleCheckCrc,
95 verbosity: OodleVerbosity,
96 dec_buf_base: *mut c_void,
97 dec_buf_size: isize,
98 fp_callback: *mut c_void,
99 callback_user_data: *mut c_void,
100 decoder_memory: *mut c_void,
101 decoder_memory_size: isize,
102 thread_phase: OodleDecodeThreadPhase,
103) -> isize;
104
105type GetCompressedBufferSizeNeededFn =
106 unsafe extern "C" fn(compressor: OodleCompressor, raw_size: isize) -> isize;
107
108type GetDecodeBufferSizeFn = unsafe extern "C" fn(
109 compressor: OodleCompressor,
110 raw_size: isize,
111 corruption_possible: i32,
112) -> isize;
113
114type GetCompressScratchMemBoundFn = unsafe extern "C" fn(
115 compressor: OodleCompressor,
116 level: OodleCompressionLevel,
117 raw_len: isize,
118 p_options: *const c_void,
119) -> isize;
120
121#[derive(Debug)]
126pub enum Error {
127 LibLoadError(libloading::Error),
128 FunctionLoadError(libloading::Error),
129 CompressFailed,
130 DecompressFailed,
131}
132
133impl fmt::Display for Error {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 match self {
136 Error::LibLoadError(e) => write!(f, "failed to load library: {e}"),
137 Error::FunctionLoadError(e) => write!(f, "failed to load function: {e}"),
138 Error::CompressFailed => write!(f, "compression failed"),
139 Error::DecompressFailed => write!(f, "decompression failed"),
140 }
141 }
142}
143
144impl std::error::Error for Error {
145 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
146 match self {
147 Error::LibLoadError(e) | Error::FunctionLoadError(e) => Some(e),
148 Error::CompressFailed | Error::DecompressFailed => None,
149 }
150 }
151}
152
153pub struct Oodle {
159 compress_fn: CompressFn,
160 decompress_fn: DecompressFn,
161 get_compressed_buffer_size_needed_fn: GetCompressedBufferSizeNeededFn,
162 get_decode_buffer_size_fn: GetDecodeBufferSizeFn,
163 get_compress_scratch_mem_bound_fn: GetCompressScratchMemBoundFn,
164 _lib: Library,
165}
166
167impl fmt::Debug for Oodle {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 f.debug_struct("Oodle").finish_non_exhaustive()
170 }
171}
172
173const _: () = {
178 const fn assert_send_sync<T: Send + Sync>() {}
179 assert_send_sync::<Oodle>();
180};
181
182impl Oodle {
183 pub fn load(path: impl AsRef<OsStr>) -> Result<Self, Error> {
185 unsafe {
186 let lib = Library::new(path.as_ref()).map_err(Error::LibLoadError)?;
187
188 let compress_fn = *lib
189 .get::<CompressFn>(b"OodleLZ_Compress")
190 .map_err(Error::FunctionLoadError)?;
191 let decompress_fn = *lib
192 .get::<DecompressFn>(b"OodleLZ_Decompress")
193 .map_err(Error::FunctionLoadError)?;
194 let get_compressed_buffer_size_needed_fn = *lib
195 .get::<GetCompressedBufferSizeNeededFn>(b"OodleLZ_GetCompressedBufferSizeNeeded")
196 .map_err(Error::FunctionLoadError)?;
197 let get_decode_buffer_size_fn = *lib
198 .get::<GetDecodeBufferSizeFn>(b"OodleLZ_GetDecodeBufferSize")
199 .map_err(Error::FunctionLoadError)?;
200 let get_compress_scratch_mem_bound_fn = *lib
201 .get::<GetCompressScratchMemBoundFn>(b"OodleLZ_GetCompressScratchMemBound")
202 .map_err(Error::FunctionLoadError)?;
203
204 Ok(Self {
205 compress_fn,
206 decompress_fn,
207 get_compressed_buffer_size_needed_fn,
208 get_decode_buffer_size_fn,
209 get_compress_scratch_mem_bound_fn,
210 _lib: lib,
211 })
212 }
213 }
214
215 pub fn compress(
219 &self,
220 compressor: OodleCompressor,
221 level: OodleCompressionLevel,
222 input: &[u8],
223 output: &mut [u8],
224 ) -> Result<usize, Error> {
225 let result = unsafe {
226 (self.compress_fn)(
227 compressor,
228 input.as_ptr() as *const c_void,
229 input.len() as isize,
230 output.as_mut_ptr() as *mut c_void,
231 level,
232 std::ptr::null(),
233 std::ptr::null(),
234 std::ptr::null(),
235 std::ptr::null_mut(),
236 0,
237 )
238 };
239 if result == 0 {
240 Err(Error::CompressFailed)
241 } else {
242 Ok(result as usize)
243 }
244 }
245
246 pub fn decompress(&self, source: &[u8], dest: &mut [u8]) -> Result<usize, Error> {
250 self.decompress_with_options(
251 source,
252 dest,
253 OodleFuzzSafe::Yes,
254 OodleCheckCrc::No,
255 OodleVerbosity::None,
256 OodleDecodeThreadPhase::All,
257 )
258 }
259
260 pub fn decompress_with_options(
264 &self,
265 source: &[u8],
266 dest: &mut [u8],
267 fuzz_safe: OodleFuzzSafe,
268 check_crc: OodleCheckCrc,
269 verbosity: OodleVerbosity,
270 thread_phase: OodleDecodeThreadPhase,
271 ) -> Result<usize, Error> {
272 let result = unsafe {
273 (self.decompress_fn)(
274 source.as_ptr() as *const c_void,
275 source.len() as isize,
276 dest.as_mut_ptr() as *mut c_void,
277 dest.len() as isize,
278 fuzz_safe,
279 check_crc,
280 verbosity,
281 std::ptr::null_mut(),
282 0,
283 std::ptr::null_mut(),
284 std::ptr::null_mut(),
285 std::ptr::null_mut(),
286 0,
287 thread_phase,
288 )
289 };
290 if result == 0 {
291 Err(Error::DecompressFailed)
292 } else {
293 Ok(result as usize)
294 }
295 }
296
297 pub fn get_compressed_buffer_size_needed(
300 &self,
301 compressor: OodleCompressor,
302 raw_size: usize,
303 ) -> usize {
304 unsafe {
305 (self.get_compressed_buffer_size_needed_fn)(compressor, raw_size as isize) as usize
306 }
307 }
308
309 pub fn get_decode_buffer_size(
312 &self,
313 compressor: OodleCompressor,
314 raw_size: usize,
315 corruption_possible: bool,
316 ) -> usize {
317 unsafe {
318 (self.get_decode_buffer_size_fn)(
319 compressor,
320 raw_size as isize,
321 corruption_possible as i32,
322 ) as usize
323 }
324 }
325
326 pub fn get_compress_scratch_mem_bound(
328 &self,
329 compressor: OodleCompressor,
330 level: OodleCompressionLevel,
331 raw_len: usize,
332 ) -> usize {
333 unsafe {
334 (self.get_compress_scratch_mem_bound_fn)(
335 compressor,
336 level,
337 raw_len as isize,
338 std::ptr::null(),
339 ) as usize
340 }
341 }
342}
343
344#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn enum_compressor_discriminants() {
354 assert_eq!(OodleCompressor::Invalid as i32, -1);
355 assert_eq!(OodleCompressor::None as i32, 3);
356 assert_eq!(OodleCompressor::Kraken as i32, 8);
357 assert_eq!(OodleCompressor::Mermaid as i32, 9);
358 assert_eq!(OodleCompressor::Selkie as i32, 11);
359 assert_eq!(OodleCompressor::Hydra as i32, 12);
360 assert_eq!(OodleCompressor::Leviathan as i32, 13);
361 }
362
363 #[test]
364 fn enum_compression_level_discriminants() {
365 assert_eq!(OodleCompressionLevel::HyperFast4 as i32, -4);
366 assert_eq!(OodleCompressionLevel::HyperFast3 as i32, -3);
367 assert_eq!(OodleCompressionLevel::HyperFast2 as i32, -2);
368 assert_eq!(OodleCompressionLevel::HyperFast1 as i32, -1);
369 assert_eq!(OodleCompressionLevel::None as i32, 0);
370 assert_eq!(OodleCompressionLevel::SuperFast as i32, 1);
371 assert_eq!(OodleCompressionLevel::VeryFast as i32, 2);
372 assert_eq!(OodleCompressionLevel::Fast as i32, 3);
373 assert_eq!(OodleCompressionLevel::Normal as i32, 4);
374 assert_eq!(OodleCompressionLevel::Optimal1 as i32, 5);
375 assert_eq!(OodleCompressionLevel::Optimal2 as i32, 6);
376 assert_eq!(OodleCompressionLevel::Optimal3 as i32, 7);
377 assert_eq!(OodleCompressionLevel::Optimal4 as i32, 8);
378 assert_eq!(OodleCompressionLevel::Optimal5 as i32, 9);
379 }
380
381 #[test]
382 fn enum_copy_and_eq() {
383 let a = OodleCompressor::Kraken;
384 let b = a;
385 assert_eq!(a, b);
386 }
387
388 #[test]
389 fn enum_hash() {
390 use std::collections::HashSet;
391 let mut set = HashSet::new();
392 set.insert(OodleCompressor::Kraken);
393 set.insert(OodleCompressor::Mermaid);
394 assert_eq!(set.len(), 2);
395 assert!(set.contains(&OodleCompressor::Kraken));
396 }
397
398 #[test]
399 fn error_display() {
400 assert_eq!(Error::CompressFailed.to_string(), "compression failed");
401 assert_eq!(Error::DecompressFailed.to_string(), "decompression failed");
402 }
403
404 #[test]
405 fn error_is_std_error() {
406 fn assert_error<T: std::error::Error>() {}
407 assert_error::<Error>();
408 }
409
410 #[test]
411 fn load_nonexistent_library() {
412 let result = Oodle::load("nonexistent_library.so");
413 assert!(result.is_err());
414 assert!(matches!(result.unwrap_err(), Error::LibLoadError(_)));
415 }
416
417 #[test]
418 fn oodle_is_send_and_sync() {
419 fn assert_send_sync<T: Send + Sync>() {}
420 assert_send_sync::<Oodle>();
421 }
422}