Skip to content

Commit

Permalink
Support for incremental DCX decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
garyttierney committed Mar 7, 2024
1 parent ce2d597 commit 96e2d2c
Show file tree
Hide file tree
Showing 11 changed files with 280 additions and 160 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
[package]
name = "fstools"
edition = "2021"

[dependencies]
format = { path = "format" }
souls_vfs = { path = "vfs" }

[workspace]
resolver = "2"
default-members = ["cli", "viewer"]
Expand Down
85 changes: 41 additions & 44 deletions cli/src/bin/dcx-extract.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
use std::io::{Read, Write};

use clap::Parser;
use format::{bnd4::BND4, dcx::Dcx};
use memmap2::MmapOptions;

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
Expand All @@ -12,44 +8,45 @@ struct Args {
}

fn main() -> Result<(), std::io::Error> {
let args = Args::parse();
let path = std::path::PathBuf::from(args.file);

let dcx_file = std::fs::File::open(&path)?;
let data = unsafe {
MmapOptions::new()
.populate()
.map_copy_read_only(&dcx_file)?
};

let dcx = Dcx::parse(&data).unwrap();

let mut decoder = dcx.create_decoder().expect("Could not create decoder");

let mut decompressed = Vec::with_capacity(decoder.hint_size());
decoder.read_to_end(&mut decompressed)?;

let mut cursor = std::io::Cursor::new(decompressed);
let bnd4 = BND4::from_reader(&mut cursor)?;

let folder = format!(
"{}/{}/",
path.parent().unwrap().to_str().unwrap(),
path.file_stem().unwrap().to_str().unwrap(),
);

for entry in bnd4.files.iter() {
let trimmed_path = entry.path.replace("N:\\", "").replace('\\', "/");
let output_path = std::path::PathBuf::from(folder.clone()).join(trimmed_path.as_str());

let parent = output_path.parent().unwrap();
std::fs::create_dir_all(parent)?;

let bytes = entry.bytes(&mut cursor)?;

let mut file = std::fs::File::create(&output_path)?;
file.write_all(&bytes)?;
}

Ok(())
// let args = Args::parse();
// let path = std::path::PathBuf::from(args.file);
//
// let dcx_file = std::fs::File::open(&path)?;
// let data = unsafe {
// MmapOptions::new()
// .populate()
// .map_copy_read_only(&dcx_file)?
// };
//
// // let dcx = DcxHeader::parse_no_verify(&data).unwrap();
//
// let mut decoder = dcx.create_decoder().expect("Could not create decoder");
//
// let mut decompressed = Vec::with_capacity(decoder.hint_size());
// decoder.read_to_end(&mut decompressed)?;
//
// let mut cursor = std::io::Cursor::new(decompressed);
// let bnd4 = BND4::from_reader(&mut cursor)?;
//
// let folder = format!(
// "{}/{}/",
// path.parent().unwrap().to_str().unwrap(),
// path.file_stem().unwrap().to_str().unwrap(),
// );
//
// for entry in bnd4.files.iter() {
// let trimmed_path = entry.path.replace("N:\\", "").replace('\\', "/");
// let output_path = std::path::PathBuf::from(folder.clone()).join(trimmed_path.as_str());
//
// let parent = output_path.parent().unwrap();
// std::fs::create_dir_all(parent)?;
//
// let bytes = entry.bytes(&mut cursor)?;
//
// let mut file = std::fs::File::create(&output_path)?;
// file.write_all(&bytes)?;
// }
//
// Ok(())
todo!("FIXME")
}
10 changes: 5 additions & 5 deletions format/src/dcx/deflate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ use std::io::{self, Read};

use flate2::read::ZlibDecoder;

pub struct DcxDecoderDeflate<'a>(ZlibDecoder<&'a [u8]>);
pub struct DcxDecoderDeflate<R: Read>(ZlibDecoder<R>);

impl<'a> DcxDecoderDeflate<'a> {
pub fn from_buffer(buf: &'a [u8]) -> Self {
Self(ZlibDecoder::new(buf))
impl<R: Read> DcxDecoderDeflate<R> {
pub fn new(reader: R) -> Self {
Self(ZlibDecoder::new(reader))
}
}

impl<'a> Read for DcxDecoderDeflate<'a> {
impl<R: Read> Read for DcxDecoderDeflate<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
Expand Down
167 changes: 124 additions & 43 deletions format/src/dcx/kraken.rs
Original file line number Diff line number Diff line change
@@ -1,65 +1,146 @@
use std::io::{Cursor, Error, ErrorKind, Read, Result};
use std::{
cmp::min,
io::{BufRead, BufReader, Error, Read, Result, Take},
ptr::null_mut,
};

use byteorder::BE;
use oodle_sys::{
OodleLZ_Decode_ThreadPhase_OodleLZ_Decode_ThreadPhaseAll, OodleLZ_Decompress, OODLELZ_FAILED,
OodleLZDecoder, OodleLZDecoder_Create, OodleLZDecoder_DecodeSome,
OodleLZ_CheckCRC_OodleLZ_CheckCRC_Yes, OodleLZ_Compressor_OodleLZ_Compressor_Kraken,
OodleLZ_DecodeSome_Out, OodleLZ_Decode_ThreadPhase_OodleLZ_Decode_Unthreaded,
OodleLZ_FuzzSafe_OodleLZ_FuzzSafe_Yes, OodleLZ_Verbosity_OodleLZ_Verbosity_Lots,
OODLELZ_BLOCK_LEN,
};
use zerocopy::U32;

pub struct DcxDecoderKraken<'a> {
compressed: &'a [u8],
uncompressed_size: U32<BE>,
inner_cursor: Option<Cursor<Vec<u8>>>,
pub struct DcxDecoderKraken<R: Read> {
reader: BufReader<Take<R>>,

/// The total size of the raw data expected to be read from the underlying stream.
uncompressed_size: u32,

/// The Oodle decoder instance created for this buffer.
decoder: *mut OodleLZDecoder,

/// A sliding window of bytes decoded by the compressor, large enough to keep the past block in
/// memory while the next block is decoded.
sliding_window: Box<[u8]>,

/// The decoders position into the sliding window.
sliding_window_pos: usize,

/// The number of bytes that the consuming reader is lagging behind the decoder.
sliding_window_lag: usize,
}

impl<'a> DcxDecoderKraken<'a> {
pub fn from_buffer(buf: &'a [u8], uncompressed_size: U32<BE>) -> Self {
impl<R: Read> DcxDecoderKraken<R> {
// TODO: fix vfs reader so it isn't producing padding
pub fn new(reader: Take<R>, uncompressed_size: u32) -> Self {
let compressor = OodleLZ_Compressor_OodleLZ_Compressor_Kraken;
let decoder = unsafe {
OodleLZDecoder_Create(compressor, uncompressed_size as i64, null_mut(), 0isize)
};

if decoder.is_null() {
panic!("return error here: failed to create decoder, check oodle error");
}

let sliding_window = Box::new([0u8; (OODLELZ_BLOCK_LEN * 2) as usize]);

Self {
compressed: buf,
decoder,
reader: BufReader::with_capacity(OODLELZ_BLOCK_LEN as usize, reader),
sliding_window,
sliding_window_pos: 0,
sliding_window_lag: 0,
uncompressed_size,
inner_cursor: None,
}
}
}
impl<'a> Read for DcxDecoderKraken<'a> {
// TODO: implement somewhat incremental reading by working with oodle's
// blocks per example in docs.
// It currently just decompresses the entire input one go and then
// operates a Cursor wrapping the decompressed bytes.

impl<R: Read> Read for DcxDecoderKraken<R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if self.inner_cursor.is_none() {
let mut inner_buffer = vec![0u8; self.uncompressed_size.get() as usize];
let compressed_len =
isize::try_from(inner_buffer.len()).map_err(|e| Error::new(ErrorKind::Other, e))?;
let inner_buffer_len =
isize::try_from(inner_buffer.len()).map_err(|e| Error::new(ErrorKind::Other, e))?;
if buf.is_empty() {
return Ok(0);
}

let mut total_written = 0usize;
while total_written < buf.len() {
let wpos = self.sliding_window_pos;

// Check if there's data to be written from the sliding window first
if self.sliding_window_lag > 0 {
let bytes_to_copy = min(self.sliding_window_lag, buf.len() - total_written);
let start = self.sliding_window_pos - self.sliding_window_lag;
let end = start + bytes_to_copy;

let src = &self.sliding_window[start..end];
let dest = &mut buf[total_written..total_written + bytes_to_copy];

dest.copy_from_slice(src);

self.sliding_window_lag -= bytes_to_copy;
total_written += bytes_to_copy;

continue;
}

// Read and decode new data
let input_data = self.reader.fill_buf()?;
if input_data.is_empty() {
break; // EOF reached
}

let mut out: OodleLZ_DecodeSome_Out = unsafe { std::mem::zeroed() };
let result = unsafe {
OodleLZ_Decompress(
self.compressed.as_ptr() as *const _,
compressed_len,
inner_buffer.as_mut_ptr() as *mut _,
inner_buffer_len,
oodle_sys::OodleLZ_FuzzSafe_OodleLZ_FuzzSafe_Yes,
0,
0,
std::ptr::null_mut(),
0,
None,
std::ptr::null_mut(),
std::ptr::null_mut(),
0,
OodleLZ_Decode_ThreadPhase_OodleLZ_Decode_ThreadPhaseAll,
) as usize
// EXTREMELY unlikely, however unsound otherwise.
let input_data_len = isize::try_from(input_data.len()).unwrap_or(isize::MAX);

// SAFETY:
// - Signedness conversions of offsets are all valid, given that
// `sliding_window.len() <= i32::MAX` and `self.uncompressed_size < isize::MAX`.
// - Consumed `input_data_len` is caped at i32::MAX
OodleLZDecoder_DecodeSome(
self.decoder,
&mut out as *mut _,
self.sliding_window.as_mut_ptr() as *mut _,
wpos as isize,
self.uncompressed_size as _,
(self.sliding_window.len() - wpos) as isize,
input_data.as_ptr() as *const _,
input_data_len,
OodleLZ_FuzzSafe_OodleLZ_FuzzSafe_Yes,
OodleLZ_CheckCRC_OodleLZ_CheckCRC_Yes,
OodleLZ_Verbosity_OodleLZ_Verbosity_Lots,
OodleLZ_Decode_ThreadPhase_OodleLZ_Decode_Unthreaded,
)
};

if result == OODLELZ_FAILED as usize {
return Err(Error::from(ErrorKind::Other));
if result == 0 {
return Err(Error::other("Oodle decoder failed"));
}

self.inner_cursor = Some(Cursor::new(inner_buffer));
let decoded_bytes = out.decodedCount as usize;
let consumed_bytes = out.compBufUsed as usize;

self.reader.consume(consumed_bytes);

let bytes_to_copy = min(decoded_bytes, buf.len() - total_written);
let dest = &mut buf[total_written..total_written + bytes_to_copy];
let src = &self.sliding_window[wpos..wpos + bytes_to_copy];

dest.copy_from_slice(src);

self.sliding_window_pos += decoded_bytes;
self.sliding_window_lag = decoded_bytes - bytes_to_copy;
total_written += bytes_to_copy;

// Manage sliding window
if self.sliding_window_pos >= self.sliding_window.len() {
self.sliding_window.rotate_left(self.sliding_window_pos);
self.sliding_window_pos = 0;
}
}

self.inner_cursor.as_mut().unwrap().read(buf)
Ok(total_written)
}
}
Loading

0 comments on commit 96e2d2c

Please sign in to comment.