Implement LZSS decompression with optional XOR decryption
- Added `lzss_decompress_simple` function for LZSS decompression in `lzss.rs`. - Introduced `XorState` struct and `xor_stream` function for XOR decryption in `xor.rs`. - Updated `mod.rs` to include new LZSS and XOR modules. - Refactored `parse_library` function in `parse.rs` to utilize the new XOR decryption functionality. - Cleaned up and organized code in `lib.rs` by removing redundant functions and structures. - Added tests for new functionality in `tests.rs`.
This commit is contained in:
@@ -232,23 +232,6 @@ impl EditableEntry {
|
|||||||
EntryData::Modified(vec) => vec.as_slice(),
|
EntryData::Modified(vec) => vec.as_slice(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_mut(&mut self, source: &Arc<[u8]>) -> &mut Vec<u8> {
|
|
||||||
// Check if we need to copy-on-write
|
|
||||||
if matches!(&self.data, EntryData::Borrowed(_)) {
|
|
||||||
let range = match &self.data {
|
|
||||||
EntryData::Borrowed(r) => r.clone(),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let copied = source[range].to_vec();
|
|
||||||
self.data = EntryData::Modified(copied);
|
|
||||||
}
|
|
||||||
// Now we have Modified variant, return mutable reference
|
|
||||||
match &mut self.data {
|
|
||||||
EntryData::Modified(vec) => vec,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|||||||
19
crates/rsli/src/compress/deflate.rs
Normal file
19
crates/rsli/src/compress/deflate.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use crate::error::Error;
|
||||||
|
use crate::Result;
|
||||||
|
use flate2::read::{DeflateDecoder, ZlibDecoder};
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
/// Decode Deflate or Zlib compressed data
|
||||||
|
pub fn decode_deflate(packed: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut decoder = DeflateDecoder::new(packed);
|
||||||
|
if decoder.read_to_end(&mut out).is_ok() {
|
||||||
|
return Ok(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.clear();
|
||||||
|
let mut zlib = ZlibDecoder::new(packed);
|
||||||
|
zlib.read_to_end(&mut out)
|
||||||
|
.map_err(|_| Error::DecompressionFailed("deflate"))?;
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
292
crates/rsli/src/compress/lzh.rs
Normal file
292
crates/rsli/src/compress/lzh.rs
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
use super::xor::xor_stream;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
pub(crate) const LZH_N: usize = 4096;
|
||||||
|
pub(crate) const LZH_F: usize = 60;
|
||||||
|
pub(crate) const LZH_THRESHOLD: usize = 2;
|
||||||
|
pub(crate) const LZH_N_CHAR: usize = 256 - LZH_THRESHOLD + LZH_F;
|
||||||
|
pub(crate) const LZH_T: usize = LZH_N_CHAR * 2 - 1;
|
||||||
|
pub(crate) const LZH_R: usize = LZH_T - 1;
|
||||||
|
pub(crate) const LZH_MAX_FREQ: u16 = 0x8000;
|
||||||
|
|
||||||
|
/// LZSS-Huffman decompression with optional XOR pre-decryption
|
||||||
|
pub fn lzss_huffman_decompress(
|
||||||
|
data: &[u8],
|
||||||
|
expected_size: usize,
|
||||||
|
xor_key: Option<u16>,
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
// TODO: Full optimization for Huffman variant (rare in practice)
|
||||||
|
// For now, fallback to separate XOR step for Huffman
|
||||||
|
if let Some(key) = xor_key {
|
||||||
|
let decrypted = xor_stream(data, key);
|
||||||
|
let mut decoder = LzhDecoder::new(&decrypted);
|
||||||
|
decoder.decode(expected_size)
|
||||||
|
} else {
|
||||||
|
let mut decoder = LzhDecoder::new(data);
|
||||||
|
decoder.decode(expected_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LzhDecoder<'a> {
|
||||||
|
bit_reader: BitReader<'a>,
|
||||||
|
text: [u8; LZH_N],
|
||||||
|
freq: [u16; LZH_T + 1],
|
||||||
|
parent: [usize; LZH_T + LZH_N_CHAR],
|
||||||
|
son: [usize; LZH_T],
|
||||||
|
d_code: [u8; 256],
|
||||||
|
d_len: [u8; 256],
|
||||||
|
ring_pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> LzhDecoder<'a> {
|
||||||
|
fn new(data: &'a [u8]) -> Self {
|
||||||
|
let mut decoder = Self {
|
||||||
|
bit_reader: BitReader::new(data),
|
||||||
|
text: [0x20u8; LZH_N],
|
||||||
|
freq: [0u16; LZH_T + 1],
|
||||||
|
parent: [0usize; LZH_T + LZH_N_CHAR],
|
||||||
|
son: [0usize; LZH_T],
|
||||||
|
d_code: [0u8; 256],
|
||||||
|
d_len: [0u8; 256],
|
||||||
|
ring_pos: LZH_N - LZH_F,
|
||||||
|
};
|
||||||
|
decoder.init_tables();
|
||||||
|
decoder.start_huff();
|
||||||
|
decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(&mut self, expected_size: usize) -> Result<Vec<u8>> {
|
||||||
|
let mut out = Vec::with_capacity(expected_size);
|
||||||
|
|
||||||
|
while out.len() < expected_size {
|
||||||
|
let c = self.decode_char();
|
||||||
|
if c < 256 {
|
||||||
|
let byte = c as u8;
|
||||||
|
out.push(byte);
|
||||||
|
self.text[self.ring_pos] = byte;
|
||||||
|
self.ring_pos = (self.ring_pos + 1) & (LZH_N - 1);
|
||||||
|
} else {
|
||||||
|
let mut offset = self.decode_position();
|
||||||
|
offset = (self.ring_pos.wrapping_sub(offset).wrapping_sub(1)) & (LZH_N - 1);
|
||||||
|
let mut length = c.saturating_sub(253);
|
||||||
|
|
||||||
|
while length > 0 && out.len() < expected_size {
|
||||||
|
let byte = self.text[offset];
|
||||||
|
out.push(byte);
|
||||||
|
self.text[self.ring_pos] = byte;
|
||||||
|
self.ring_pos = (self.ring_pos + 1) & (LZH_N - 1);
|
||||||
|
offset = (offset + 1) & (LZH_N - 1);
|
||||||
|
length -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.len() != expected_size {
|
||||||
|
return Err(Error::DecompressionFailed("lzss-huffman"));
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_tables(&mut self) {
|
||||||
|
let d_code_group_counts = [1usize, 3, 8, 12, 24, 16];
|
||||||
|
let d_len_group_counts = [32usize, 48, 64, 48, 48, 16];
|
||||||
|
|
||||||
|
let mut group_index = 0u8;
|
||||||
|
let mut idx = 0usize;
|
||||||
|
let mut run = 32usize;
|
||||||
|
for count in d_code_group_counts {
|
||||||
|
for _ in 0..count {
|
||||||
|
for _ in 0..run {
|
||||||
|
self.d_code[idx] = group_index;
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
group_index = group_index.wrapping_add(1);
|
||||||
|
}
|
||||||
|
run >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut len = 3u8;
|
||||||
|
idx = 0;
|
||||||
|
for count in d_len_group_counts {
|
||||||
|
for _ in 0..count {
|
||||||
|
self.d_len[idx] = len;
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
len = len.saturating_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_huff(&mut self) {
|
||||||
|
for i in 0..LZH_N_CHAR {
|
||||||
|
self.freq[i] = 1;
|
||||||
|
self.son[i] = i + LZH_T;
|
||||||
|
self.parent[i + LZH_T] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut i = 0usize;
|
||||||
|
let mut j = LZH_N_CHAR;
|
||||||
|
while j <= LZH_R {
|
||||||
|
self.freq[j] = self.freq[i].saturating_add(self.freq[i + 1]);
|
||||||
|
self.son[j] = i;
|
||||||
|
self.parent[i] = j;
|
||||||
|
self.parent[i + 1] = j;
|
||||||
|
i += 2;
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.freq[LZH_T] = u16::MAX;
|
||||||
|
self.parent[LZH_R] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_char(&mut self) -> usize {
|
||||||
|
let mut node = self.son[LZH_R];
|
||||||
|
while node < LZH_T {
|
||||||
|
let bit = usize::from(self.bit_reader.read_bit_or_zero());
|
||||||
|
node = self.son[node + bit];
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = node - LZH_T;
|
||||||
|
self.update(c);
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_position(&mut self) -> usize {
|
||||||
|
let i = self.bit_reader.read_bits_or_zero(8) as usize;
|
||||||
|
let mut c = usize::from(self.d_code[i]) << 6;
|
||||||
|
let mut j = usize::from(self.d_len[i]).saturating_sub(2);
|
||||||
|
|
||||||
|
while j > 0 {
|
||||||
|
j -= 1;
|
||||||
|
c |= usize::from(self.bit_reader.read_bit_or_zero()) << j;
|
||||||
|
}
|
||||||
|
|
||||||
|
c | (i & 0x3F)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, c: usize) {
|
||||||
|
if self.freq[LZH_R] == LZH_MAX_FREQ {
|
||||||
|
self.reconstruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current = self.parent[c + LZH_T];
|
||||||
|
loop {
|
||||||
|
self.freq[current] = self.freq[current].saturating_add(1);
|
||||||
|
let freq = self.freq[current];
|
||||||
|
|
||||||
|
if current + 1 < self.freq.len() && freq > self.freq[current + 1] {
|
||||||
|
let mut swap_idx = current + 1;
|
||||||
|
while swap_idx + 1 < self.freq.len() && freq > self.freq[swap_idx + 1] {
|
||||||
|
swap_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.freq.swap(current, swap_idx);
|
||||||
|
|
||||||
|
let left = self.son[current];
|
||||||
|
let right = self.son[swap_idx];
|
||||||
|
self.son[current] = right;
|
||||||
|
self.son[swap_idx] = left;
|
||||||
|
|
||||||
|
self.parent[left] = swap_idx;
|
||||||
|
if left < LZH_T {
|
||||||
|
self.parent[left + 1] = swap_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.parent[right] = current;
|
||||||
|
if right < LZH_T {
|
||||||
|
self.parent[right + 1] = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = swap_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = self.parent[current];
|
||||||
|
if current == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reconstruct(&mut self) {
|
||||||
|
let mut j = 0usize;
|
||||||
|
for i in 0..LZH_T {
|
||||||
|
if self.son[i] >= LZH_T {
|
||||||
|
self.freq[j] = (self.freq[i].saturating_add(1)) / 2;
|
||||||
|
self.son[j] = self.son[i];
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut i = 0usize;
|
||||||
|
let mut current = LZH_N_CHAR;
|
||||||
|
while current < LZH_T {
|
||||||
|
let sum = self.freq[i].saturating_add(self.freq[i + 1]);
|
||||||
|
self.freq[current] = sum;
|
||||||
|
|
||||||
|
let mut insert_at = current;
|
||||||
|
while insert_at > 0 && sum < self.freq[insert_at - 1] {
|
||||||
|
insert_at -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for move_idx in (insert_at..current).rev() {
|
||||||
|
self.freq[move_idx + 1] = self.freq[move_idx];
|
||||||
|
self.son[move_idx + 1] = self.son[move_idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.freq[insert_at] = sum;
|
||||||
|
self.son[insert_at] = i;
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
current += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx in 0..LZH_T {
|
||||||
|
let node = self.son[idx];
|
||||||
|
self.parent[node] = idx;
|
||||||
|
if node < LZH_T {
|
||||||
|
self.parent[node + 1] = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.freq[LZH_T] = u16::MAX;
|
||||||
|
self.parent[LZH_R] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BitReader<'a> {
|
||||||
|
data: &'a [u8],
|
||||||
|
byte_pos: usize,
|
||||||
|
bit_mask: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BitReader<'a> {
|
||||||
|
fn new(data: &'a [u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
data,
|
||||||
|
byte_pos: 0,
|
||||||
|
bit_mask: 0x80,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_bit_or_zero(&mut self) -> u8 {
|
||||||
|
let Some(byte) = self.data.get(self.byte_pos).copied() else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
let bit = if (byte & self.bit_mask) != 0 { 1 } else { 0 };
|
||||||
|
self.bit_mask >>= 1;
|
||||||
|
if self.bit_mask == 0 {
|
||||||
|
self.bit_mask = 0x80;
|
||||||
|
self.byte_pos = self.byte_pos.saturating_add(1);
|
||||||
|
}
|
||||||
|
bit
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_bits_or_zero(&mut self, bits: usize) -> u32 {
|
||||||
|
let mut value = 0u32;
|
||||||
|
for _ in 0..bits {
|
||||||
|
value = (value << 1) | u32::from(self.read_bit_or_zero());
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
79
crates/rsli/src/compress/lzss.rs
Normal file
79
crates/rsli/src/compress/lzss.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use super::xor::XorState;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
/// Simple LZSS decompression with optional on-the-fly XOR decryption
|
||||||
|
pub fn lzss_decompress_simple(
|
||||||
|
data: &[u8],
|
||||||
|
expected_size: usize,
|
||||||
|
xor_key: Option<u16>,
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let mut ring = [0x20u8; 0x1000];
|
||||||
|
let mut ring_pos = 0xFEEusize;
|
||||||
|
let mut out = Vec::with_capacity(expected_size);
|
||||||
|
let mut in_pos = 0usize;
|
||||||
|
|
||||||
|
let mut control = 0u8;
|
||||||
|
let mut bits_left = 0u8;
|
||||||
|
|
||||||
|
// XOR state for on-the-fly decryption
|
||||||
|
let mut xor_state = xor_key.map(XorState::new);
|
||||||
|
|
||||||
|
// Helper to read byte with optional XOR decryption
|
||||||
|
let read_byte = |pos: usize, state: &mut Option<XorState>| -> Option<u8> {
|
||||||
|
let encrypted = data.get(pos).copied()?;
|
||||||
|
Some(if let Some(ref mut s) = state {
|
||||||
|
s.decrypt_byte(encrypted)
|
||||||
|
} else {
|
||||||
|
encrypted
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
while out.len() < expected_size {
|
||||||
|
if bits_left == 0 {
|
||||||
|
let byte = read_byte(in_pos, &mut xor_state)
|
||||||
|
.ok_or(Error::DecompressionFailed("lzss-simple: unexpected EOF"))?;
|
||||||
|
control = byte;
|
||||||
|
in_pos += 1;
|
||||||
|
bits_left = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (control & 1) != 0 {
|
||||||
|
let byte = read_byte(in_pos, &mut xor_state)
|
||||||
|
.ok_or(Error::DecompressionFailed("lzss-simple: unexpected EOF"))?;
|
||||||
|
in_pos += 1;
|
||||||
|
|
||||||
|
out.push(byte);
|
||||||
|
ring[ring_pos] = byte;
|
||||||
|
ring_pos = (ring_pos + 1) & 0x0FFF;
|
||||||
|
} else {
|
||||||
|
let low = read_byte(in_pos, &mut xor_state)
|
||||||
|
.ok_or(Error::DecompressionFailed("lzss-simple: unexpected EOF"))?;
|
||||||
|
let high = read_byte(in_pos + 1, &mut xor_state)
|
||||||
|
.ok_or(Error::DecompressionFailed("lzss-simple: unexpected EOF"))?;
|
||||||
|
in_pos += 2;
|
||||||
|
|
||||||
|
let offset = usize::from(low) | (usize::from(high & 0xF0) << 4);
|
||||||
|
let length = usize::from((high & 0x0F) + 3);
|
||||||
|
|
||||||
|
for step in 0..length {
|
||||||
|
let byte = ring[(offset + step) & 0x0FFF];
|
||||||
|
out.push(byte);
|
||||||
|
ring[ring_pos] = byte;
|
||||||
|
ring_pos = (ring_pos + 1) & 0x0FFF;
|
||||||
|
if out.len() >= expected_size {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
control >>= 1;
|
||||||
|
bits_left -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.len() != expected_size {
|
||||||
|
return Err(Error::DecompressionFailed("lzss-simple"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
9
crates/rsli/src/compress/mod.rs
Normal file
9
crates/rsli/src/compress/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
pub mod deflate;
|
||||||
|
pub mod lzh;
|
||||||
|
pub mod lzss;
|
||||||
|
pub mod xor;
|
||||||
|
|
||||||
|
pub use deflate::decode_deflate;
|
||||||
|
pub use lzh::lzss_huffman_decompress;
|
||||||
|
pub use lzss::lzss_decompress_simple;
|
||||||
|
pub use xor::{xor_stream, XorState};
|
||||||
29
crates/rsli/src/compress/xor.rs
Normal file
29
crates/rsli/src/compress/xor.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/// XOR cipher state for RsLi format
|
||||||
|
pub struct XorState {
|
||||||
|
lo: u8,
|
||||||
|
hi: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XorState {
|
||||||
|
/// Create new XOR state from 16-bit key
|
||||||
|
pub fn new(key16: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
lo: (key16 & 0xFF) as u8,
|
||||||
|
hi: ((key16 >> 8) & 0xFF) as u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt a single byte and update state
|
||||||
|
pub fn decrypt_byte(&mut self, encrypted: u8) -> u8 {
|
||||||
|
self.lo = self.hi ^ self.lo.wrapping_shl(1);
|
||||||
|
let decrypted = encrypted ^ self.lo;
|
||||||
|
self.hi = self.lo ^ (self.hi >> 1);
|
||||||
|
decrypted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt entire buffer with XOR stream cipher
|
||||||
|
pub fn xor_stream(data: &[u8], key16: u16) -> Vec<u8> {
|
||||||
|
let mut state = XorState::new(key16);
|
||||||
|
data.iter().map(|&b| state.decrypt_byte(b)).collect()
|
||||||
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
|
pub mod compress;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod parse;
|
||||||
|
|
||||||
|
use crate::compress::{
|
||||||
|
decode_deflate, lzss_decompress_simple, lzss_huffman_decompress, xor_stream,
|
||||||
|
};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
use crate::parse::{c_name_bytes, cmp_c_string, parse_library};
|
||||||
use common::{OutputBuffer, ResourceData};
|
use common::{OutputBuffer, ResourceData};
|
||||||
use flate2::read::{DeflateDecoder, ZlibDecoder};
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Read;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -31,15 +35,15 @@ pub struct Library {
|
|||||||
bytes: Arc<[u8]>,
|
bytes: Arc<[u8]>,
|
||||||
entries: Vec<EntryRecord>,
|
entries: Vec<EntryRecord>,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
header_raw: [u8; 32],
|
pub(crate) header_raw: [u8; 32],
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
table_plain_original: Vec<u8>,
|
pub(crate) table_plain_original: Vec<u8>,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
xor_seed: u32,
|
pub(crate) xor_seed: u32,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
source_size: usize,
|
pub(crate) source_size: usize,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
trailer_raw: Option<[u8; 6]>,
|
pub(crate) trailer_raw: Option<[u8; 6]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
@@ -79,16 +83,16 @@ pub struct PackedResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct EntryRecord {
|
pub(crate) struct EntryRecord {
|
||||||
meta: EntryMeta,
|
pub(crate) meta: EntryMeta,
|
||||||
name_raw: [u8; 12],
|
pub(crate) name_raw: [u8; 12],
|
||||||
sort_to_original: i16,
|
pub(crate) sort_to_original: i16,
|
||||||
key16: u16,
|
pub(crate) key16: u16,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
data_offset_raw: u32,
|
pub(crate) data_offset_raw: u32,
|
||||||
packed_size_declared: u32,
|
pub(crate) packed_size_declared: u32,
|
||||||
packed_size_available: usize,
|
pub(crate) packed_size_available: usize,
|
||||||
effective_offset: usize,
|
pub(crate) effective_offset: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Library {
|
impl Library {
|
||||||
@@ -281,7 +285,7 @@ impl Library {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn rebuild_from_parsed_metadata(&self) -> Result<Vec<u8>> {
|
pub(crate) fn rebuild_from_parsed_metadata(&self) -> Result<Vec<u8>> {
|
||||||
let trailer_len = usize::from(self.trailer_raw.is_some()) * 6;
|
let trailer_len = usize::from(self.trailer_raw.is_some()) * 6;
|
||||||
let pre_trailer_size = self
|
let pre_trailer_size = self
|
||||||
.source_size
|
.source_size
|
||||||
@@ -345,229 +349,6 @@ impl Library {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_library(bytes: Arc<[u8]>, opts: OpenOptions) -> Result<Library> {
|
|
||||||
if bytes.len() < 32 {
|
|
||||||
return Err(Error::EntryTableOutOfBounds {
|
|
||||||
table_offset: 32,
|
|
||||||
table_len: 0,
|
|
||||||
file_len: u64::try_from(bytes.len()).map_err(|_| Error::IntegerOverflow)?,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut header_raw = [0u8; 32];
|
|
||||||
header_raw.copy_from_slice(&bytes[0..32]);
|
|
||||||
|
|
||||||
if &bytes[0..2] != b"NL" {
|
|
||||||
let mut got = [0u8; 2];
|
|
||||||
got.copy_from_slice(&bytes[0..2]);
|
|
||||||
return Err(Error::InvalidMagic { got });
|
|
||||||
}
|
|
||||||
if bytes[3] != 0x01 {
|
|
||||||
return Err(Error::UnsupportedVersion { got: bytes[3] });
|
|
||||||
}
|
|
||||||
|
|
||||||
let entry_count = i16::from_le_bytes([bytes[4], bytes[5]]);
|
|
||||||
if entry_count < 0 {
|
|
||||||
return Err(Error::InvalidEntryCount { got: entry_count });
|
|
||||||
}
|
|
||||||
let count = usize::try_from(entry_count).map_err(|_| Error::IntegerOverflow)?;
|
|
||||||
|
|
||||||
// Validate entry_count fits in u32 (required for EntryId)
|
|
||||||
if count > u32::MAX as usize {
|
|
||||||
return Err(Error::TooManyEntries { got: count });
|
|
||||||
}
|
|
||||||
|
|
||||||
let xor_seed = u32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]);
|
|
||||||
|
|
||||||
let table_len = count.checked_mul(32).ok_or(Error::IntegerOverflow)?;
|
|
||||||
let table_offset = 32usize;
|
|
||||||
let table_end = table_offset
|
|
||||||
.checked_add(table_len)
|
|
||||||
.ok_or(Error::IntegerOverflow)?;
|
|
||||||
if table_end > bytes.len() {
|
|
||||||
return Err(Error::EntryTableOutOfBounds {
|
|
||||||
table_offset: u64::try_from(table_offset).map_err(|_| Error::IntegerOverflow)?,
|
|
||||||
table_len: u64::try_from(table_len).map_err(|_| Error::IntegerOverflow)?,
|
|
||||||
file_len: u64::try_from(bytes.len()).map_err(|_| Error::IntegerOverflow)?,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let table_enc = &bytes[table_offset..table_end];
|
|
||||||
let table_plain_original = xor_stream(table_enc, (xor_seed & 0xFFFF) as u16);
|
|
||||||
if table_plain_original.len() != table_len {
|
|
||||||
return Err(Error::EntryTableDecryptFailed);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (overlay, trailer_raw) = parse_ao_trailer(&bytes, opts.allow_ao_trailer)?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let _ = trailer_raw;
|
|
||||||
|
|
||||||
let mut entries = Vec::with_capacity(count);
|
|
||||||
for idx in 0..count {
|
|
||||||
let row = &table_plain_original[idx * 32..(idx + 1) * 32];
|
|
||||||
|
|
||||||
let mut name_raw = [0u8; 12];
|
|
||||||
name_raw.copy_from_slice(&row[0..12]);
|
|
||||||
|
|
||||||
let flags_signed = i16::from_le_bytes([row[16], row[17]]);
|
|
||||||
let sort_to_original = i16::from_le_bytes([row[18], row[19]]);
|
|
||||||
let unpacked_size = u32::from_le_bytes([row[20], row[21], row[22], row[23]]);
|
|
||||||
let data_offset_raw = u32::from_le_bytes([row[24], row[25], row[26], row[27]]);
|
|
||||||
let packed_size_declared = u32::from_le_bytes([row[28], row[29], row[30], row[31]]);
|
|
||||||
|
|
||||||
let method_raw = (flags_signed as u16 as u32) & 0x1E0;
|
|
||||||
let method = parse_method(method_raw);
|
|
||||||
|
|
||||||
let effective_offset_u64 = u64::from(data_offset_raw)
|
|
||||||
.checked_add(u64::from(overlay))
|
|
||||||
.ok_or(Error::IntegerOverflow)?;
|
|
||||||
let effective_offset =
|
|
||||||
usize::try_from(effective_offset_u64).map_err(|_| Error::IntegerOverflow)?;
|
|
||||||
|
|
||||||
let packed_size_usize =
|
|
||||||
usize::try_from(packed_size_declared).map_err(|_| Error::IntegerOverflow)?;
|
|
||||||
let mut packed_size_available = packed_size_usize;
|
|
||||||
|
|
||||||
let end = effective_offset_u64
|
|
||||||
.checked_add(u64::from(packed_size_declared))
|
|
||||||
.ok_or(Error::IntegerOverflow)?;
|
|
||||||
let file_len_u64 = u64::try_from(bytes.len()).map_err(|_| Error::IntegerOverflow)?;
|
|
||||||
|
|
||||||
if end > file_len_u64 {
|
|
||||||
if method_raw == 0x100 && end == file_len_u64 + 1 {
|
|
||||||
if opts.allow_deflate_eof_plus_one {
|
|
||||||
packed_size_available = packed_size_available
|
|
||||||
.checked_sub(1)
|
|
||||||
.ok_or(Error::IntegerOverflow)?;
|
|
||||||
} else {
|
|
||||||
return Err(Error::DeflateEofPlusOneQuirkRejected {
|
|
||||||
id: u32::try_from(idx).expect("entry count validated at parse"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::PackedSizePastEof {
|
|
||||||
id: u32::try_from(idx).expect("entry count validated at parse"),
|
|
||||||
offset: effective_offset_u64,
|
|
||||||
packed_size: packed_size_declared,
|
|
||||||
file_len: file_len_u64,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let available_end = effective_offset
|
|
||||||
.checked_add(packed_size_available)
|
|
||||||
.ok_or(Error::IntegerOverflow)?;
|
|
||||||
if available_end > bytes.len() {
|
|
||||||
return Err(Error::EntryDataOutOfBounds {
|
|
||||||
id: u32::try_from(idx).expect("entry count validated at parse"),
|
|
||||||
offset: effective_offset_u64,
|
|
||||||
size: packed_size_declared,
|
|
||||||
file_len: file_len_u64,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = decode_name(c_name_bytes(&name_raw));
|
|
||||||
|
|
||||||
entries.push(EntryRecord {
|
|
||||||
meta: EntryMeta {
|
|
||||||
name,
|
|
||||||
flags: i32::from(flags_signed),
|
|
||||||
method,
|
|
||||||
data_offset: effective_offset_u64,
|
|
||||||
packed_size: packed_size_declared,
|
|
||||||
unpacked_size,
|
|
||||||
},
|
|
||||||
name_raw,
|
|
||||||
sort_to_original,
|
|
||||||
key16: sort_to_original as u16,
|
|
||||||
#[cfg(test)]
|
|
||||||
data_offset_raw,
|
|
||||||
packed_size_declared,
|
|
||||||
packed_size_available,
|
|
||||||
effective_offset,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let presorted_flag = u16::from_le_bytes([bytes[14], bytes[15]]);
|
|
||||||
if presorted_flag == 0xABBA {
|
|
||||||
for entry in &entries {
|
|
||||||
let idx = i32::from(entry.sort_to_original);
|
|
||||||
if idx < 0 || usize::try_from(idx).map_err(|_| Error::IntegerOverflow)? >= count {
|
|
||||||
return Err(Error::CorruptEntryTable(
|
|
||||||
"sort_to_original is not a valid permutation index",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut sorted: Vec<usize> = (0..count).collect();
|
|
||||||
sorted.sort_by(|a, b| {
|
|
||||||
cmp_c_string(
|
|
||||||
c_name_bytes(&entries[*a].name_raw),
|
|
||||||
c_name_bytes(&entries[*b].name_raw),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
for (idx, entry) in entries.iter_mut().enumerate() {
|
|
||||||
entry.sort_to_original =
|
|
||||||
i16::try_from(sorted[idx]).map_err(|_| Error::IntegerOverflow)?;
|
|
||||||
entry.key16 = entry.sort_to_original as u16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
let source_size = bytes.len();
|
|
||||||
|
|
||||||
Ok(Library {
|
|
||||||
bytes,
|
|
||||||
entries,
|
|
||||||
#[cfg(test)]
|
|
||||||
header_raw,
|
|
||||||
#[cfg(test)]
|
|
||||||
table_plain_original,
|
|
||||||
#[cfg(test)]
|
|
||||||
xor_seed,
|
|
||||||
#[cfg(test)]
|
|
||||||
source_size,
|
|
||||||
#[cfg(test)]
|
|
||||||
trailer_raw,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_ao_trailer(bytes: &[u8], allow: bool) -> Result<(u32, Option<[u8; 6]>)> {
|
|
||||||
if !allow || bytes.len() < 6 {
|
|
||||||
return Ok((0, None));
|
|
||||||
}
|
|
||||||
|
|
||||||
if &bytes[bytes.len() - 6..bytes.len() - 4] != b"AO" {
|
|
||||||
return Ok((0, None));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut trailer = [0u8; 6];
|
|
||||||
trailer.copy_from_slice(&bytes[bytes.len() - 6..]);
|
|
||||||
let overlay = u32::from_le_bytes([trailer[2], trailer[3], trailer[4], trailer[5]]);
|
|
||||||
|
|
||||||
if u64::from(overlay) > u64::try_from(bytes.len()).map_err(|_| Error::IntegerOverflow)? {
|
|
||||||
return Err(Error::MediaOverlayOutOfBounds {
|
|
||||||
overlay,
|
|
||||||
file_len: u64::try_from(bytes.len()).map_err(|_| Error::IntegerOverflow)?,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((overlay, Some(trailer)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_method(raw: u32) -> PackMethod {
|
|
||||||
match raw {
|
|
||||||
0x000 => PackMethod::None,
|
|
||||||
0x020 => PackMethod::XorOnly,
|
|
||||||
0x040 => PackMethod::Lzss,
|
|
||||||
0x060 => PackMethod::XorLzss,
|
|
||||||
0x080 => PackMethod::LzssHuffman,
|
|
||||||
0x0A0 => PackMethod::XorLzssHuffman,
|
|
||||||
0x100 => PackMethod::Deflate,
|
|
||||||
other => PackMethod::Unknown(other),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_payload(
|
fn decode_payload(
|
||||||
packed: &[u8],
|
packed: &[u8],
|
||||||
method: PackMethod,
|
method: PackMethod,
|
||||||
@@ -619,430 +400,6 @@ fn decode_payload(
|
|||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_deflate(packed: &[u8]) -> Result<Vec<u8>> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
let mut decoder = DeflateDecoder::new(packed);
|
|
||||||
if decoder.read_to_end(&mut out).is_ok() {
|
|
||||||
return Ok(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
out.clear();
|
|
||||||
let mut zlib = ZlibDecoder::new(packed);
|
|
||||||
zlib.read_to_end(&mut out)
|
|
||||||
.map_err(|_| Error::DecompressionFailed("deflate"))?;
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct XorState {
|
|
||||||
lo: u8,
|
|
||||||
hi: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XorState {
|
|
||||||
fn new(key16: u16) -> Self {
|
|
||||||
Self {
|
|
||||||
lo: (key16 & 0xFF) as u8,
|
|
||||||
hi: ((key16 >> 8) & 0xFF) as u8,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decrypt_byte(&mut self, encrypted: u8) -> u8 {
|
|
||||||
self.lo = self.hi ^ self.lo.wrapping_shl(1);
|
|
||||||
let decrypted = encrypted ^ self.lo;
|
|
||||||
self.hi = self.lo ^ (self.hi >> 1);
|
|
||||||
decrypted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xor_stream(data: &[u8], key16: u16) -> Vec<u8> {
|
|
||||||
let mut state = XorState::new(key16);
|
|
||||||
data.iter().map(|&b| state.decrypt_byte(b)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lzss_decompress_simple(
|
|
||||||
data: &[u8],
|
|
||||||
expected_size: usize,
|
|
||||||
xor_key: Option<u16>,
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
let mut ring = [0x20u8; 0x1000];
|
|
||||||
let mut ring_pos = 0xFEEusize;
|
|
||||||
let mut out = Vec::with_capacity(expected_size);
|
|
||||||
let mut in_pos = 0usize;
|
|
||||||
|
|
||||||
let mut control = 0u8;
|
|
||||||
let mut bits_left = 0u8;
|
|
||||||
|
|
||||||
// XOR state for on-the-fly decryption
|
|
||||||
let mut xor_state = xor_key.map(XorState::new);
|
|
||||||
|
|
||||||
// Helper to read byte with optional XOR decryption
|
|
||||||
let read_byte = |pos: usize, state: &mut Option<XorState>| -> Option<u8> {
|
|
||||||
let encrypted = data.get(pos).copied()?;
|
|
||||||
Some(if let Some(ref mut s) = state {
|
|
||||||
s.decrypt_byte(encrypted)
|
|
||||||
} else {
|
|
||||||
encrypted
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
while out.len() < expected_size {
|
|
||||||
if bits_left == 0 {
|
|
||||||
let byte = read_byte(in_pos, &mut xor_state)
|
|
||||||
.ok_or(Error::DecompressionFailed("lzss-simple: unexpected EOF"))?;
|
|
||||||
control = byte;
|
|
||||||
in_pos += 1;
|
|
||||||
bits_left = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (control & 1) != 0 {
|
|
||||||
let byte = read_byte(in_pos, &mut xor_state)
|
|
||||||
.ok_or(Error::DecompressionFailed("lzss-simple: unexpected EOF"))?;
|
|
||||||
in_pos += 1;
|
|
||||||
|
|
||||||
out.push(byte);
|
|
||||||
ring[ring_pos] = byte;
|
|
||||||
ring_pos = (ring_pos + 1) & 0x0FFF;
|
|
||||||
} else {
|
|
||||||
let low = read_byte(in_pos, &mut xor_state)
|
|
||||||
.ok_or(Error::DecompressionFailed("lzss-simple: unexpected EOF"))?;
|
|
||||||
let high = read_byte(in_pos + 1, &mut xor_state)
|
|
||||||
.ok_or(Error::DecompressionFailed("lzss-simple: unexpected EOF"))?;
|
|
||||||
in_pos += 2;
|
|
||||||
|
|
||||||
let offset = usize::from(low) | (usize::from(high & 0xF0) << 4);
|
|
||||||
let length = usize::from((high & 0x0F) + 3);
|
|
||||||
|
|
||||||
for step in 0..length {
|
|
||||||
let byte = ring[(offset + step) & 0x0FFF];
|
|
||||||
out.push(byte);
|
|
||||||
ring[ring_pos] = byte;
|
|
||||||
ring_pos = (ring_pos + 1) & 0x0FFF;
|
|
||||||
if out.len() >= expected_size {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
control >>= 1;
|
|
||||||
bits_left -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.len() != expected_size {
|
|
||||||
return Err(Error::DecompressionFailed("lzss-simple"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
const LZH_N: usize = 4096;
|
|
||||||
const LZH_F: usize = 60;
|
|
||||||
const LZH_THRESHOLD: usize = 2;
|
|
||||||
const LZH_N_CHAR: usize = 256 - LZH_THRESHOLD + LZH_F;
|
|
||||||
const LZH_T: usize = LZH_N_CHAR * 2 - 1;
|
|
||||||
const LZH_R: usize = LZH_T - 1;
|
|
||||||
const LZH_MAX_FREQ: u16 = 0x8000;
|
|
||||||
|
|
||||||
fn lzss_huffman_decompress(
|
|
||||||
data: &[u8],
|
|
||||||
expected_size: usize,
|
|
||||||
xor_key: Option<u16>,
|
|
||||||
) -> Result<Vec<u8>> {
|
|
||||||
// TODO: Full optimization for Huffman variant (rare in practice)
|
|
||||||
// For now, fallback to separate XOR step for Huffman
|
|
||||||
if let Some(key) = xor_key {
|
|
||||||
let decrypted = xor_stream(data, key);
|
|
||||||
let mut decoder = LzhDecoder::new(&decrypted);
|
|
||||||
decoder.decode(expected_size)
|
|
||||||
} else {
|
|
||||||
let mut decoder = LzhDecoder::new(data);
|
|
||||||
decoder.decode(expected_size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LzhDecoder<'a> {
|
|
||||||
bit_reader: BitReader<'a>,
|
|
||||||
text: [u8; LZH_N],
|
|
||||||
freq: [u16; LZH_T + 1],
|
|
||||||
parent: [usize; LZH_T + LZH_N_CHAR],
|
|
||||||
son: [usize; LZH_T],
|
|
||||||
d_code: [u8; 256],
|
|
||||||
d_len: [u8; 256],
|
|
||||||
ring_pos: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> LzhDecoder<'a> {
|
|
||||||
fn new(data: &'a [u8]) -> Self {
|
|
||||||
let mut decoder = Self {
|
|
||||||
bit_reader: BitReader::new(data),
|
|
||||||
text: [0x20u8; LZH_N],
|
|
||||||
freq: [0u16; LZH_T + 1],
|
|
||||||
parent: [0usize; LZH_T + LZH_N_CHAR],
|
|
||||||
son: [0usize; LZH_T],
|
|
||||||
d_code: [0u8; 256],
|
|
||||||
d_len: [0u8; 256],
|
|
||||||
ring_pos: LZH_N - LZH_F,
|
|
||||||
};
|
|
||||||
decoder.init_tables();
|
|
||||||
decoder.start_huff();
|
|
||||||
decoder
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode(&mut self, expected_size: usize) -> Result<Vec<u8>> {
|
|
||||||
let mut out = Vec::with_capacity(expected_size);
|
|
||||||
|
|
||||||
while out.len() < expected_size {
|
|
||||||
let c = self.decode_char();
|
|
||||||
if c < 256 {
|
|
||||||
let byte = c as u8;
|
|
||||||
out.push(byte);
|
|
||||||
self.text[self.ring_pos] = byte;
|
|
||||||
self.ring_pos = (self.ring_pos + 1) & (LZH_N - 1);
|
|
||||||
} else {
|
|
||||||
let mut offset = self.decode_position();
|
|
||||||
offset = (self.ring_pos.wrapping_sub(offset).wrapping_sub(1)) & (LZH_N - 1);
|
|
||||||
let mut length = c.saturating_sub(253);
|
|
||||||
|
|
||||||
while length > 0 && out.len() < expected_size {
|
|
||||||
let byte = self.text[offset];
|
|
||||||
out.push(byte);
|
|
||||||
self.text[self.ring_pos] = byte;
|
|
||||||
self.ring_pos = (self.ring_pos + 1) & (LZH_N - 1);
|
|
||||||
offset = (offset + 1) & (LZH_N - 1);
|
|
||||||
length -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.len() != expected_size {
|
|
||||||
return Err(Error::DecompressionFailed("lzss-huffman"));
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_tables(&mut self) {
|
|
||||||
let d_code_group_counts = [1usize, 3, 8, 12, 24, 16];
|
|
||||||
let d_len_group_counts = [32usize, 48, 64, 48, 48, 16];
|
|
||||||
|
|
||||||
let mut group_index = 0u8;
|
|
||||||
let mut idx = 0usize;
|
|
||||||
let mut run = 32usize;
|
|
||||||
for count in d_code_group_counts {
|
|
||||||
for _ in 0..count {
|
|
||||||
for _ in 0..run {
|
|
||||||
self.d_code[idx] = group_index;
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
group_index = group_index.wrapping_add(1);
|
|
||||||
}
|
|
||||||
run >>= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut len = 3u8;
|
|
||||||
idx = 0;
|
|
||||||
for count in d_len_group_counts {
|
|
||||||
for _ in 0..count {
|
|
||||||
self.d_len[idx] = len;
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
len = len.saturating_add(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_huff(&mut self) {
|
|
||||||
for i in 0..LZH_N_CHAR {
|
|
||||||
self.freq[i] = 1;
|
|
||||||
self.son[i] = i + LZH_T;
|
|
||||||
self.parent[i + LZH_T] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut i = 0usize;
|
|
||||||
let mut j = LZH_N_CHAR;
|
|
||||||
while j <= LZH_R {
|
|
||||||
self.freq[j] = self.freq[i].saturating_add(self.freq[i + 1]);
|
|
||||||
self.son[j] = i;
|
|
||||||
self.parent[i] = j;
|
|
||||||
self.parent[i + 1] = j;
|
|
||||||
i += 2;
|
|
||||||
j += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.freq[LZH_T] = u16::MAX;
|
|
||||||
self.parent[LZH_R] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_char(&mut self) -> usize {
|
|
||||||
let mut node = self.son[LZH_R];
|
|
||||||
while node < LZH_T {
|
|
||||||
let bit = usize::from(self.bit_reader.read_bit_or_zero());
|
|
||||||
node = self.son[node + bit];
|
|
||||||
}
|
|
||||||
|
|
||||||
let c = node - LZH_T;
|
|
||||||
self.update(c);
|
|
||||||
c
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_position(&mut self) -> usize {
|
|
||||||
let i = self.bit_reader.read_bits_or_zero(8) as usize;
|
|
||||||
let mut c = usize::from(self.d_code[i]) << 6;
|
|
||||||
let mut j = usize::from(self.d_len[i]).saturating_sub(2);
|
|
||||||
|
|
||||||
while j > 0 {
|
|
||||||
j -= 1;
|
|
||||||
c |= usize::from(self.bit_reader.read_bit_or_zero()) << j;
|
|
||||||
}
|
|
||||||
|
|
||||||
c | (i & 0x3F)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, c: usize) {
|
|
||||||
if self.freq[LZH_R] == LZH_MAX_FREQ {
|
|
||||||
self.reconstruct();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut current = self.parent[c + LZH_T];
|
|
||||||
loop {
|
|
||||||
self.freq[current] = self.freq[current].saturating_add(1);
|
|
||||||
let freq = self.freq[current];
|
|
||||||
|
|
||||||
if current + 1 < self.freq.len() && freq > self.freq[current + 1] {
|
|
||||||
let mut swap_idx = current + 1;
|
|
||||||
while swap_idx + 1 < self.freq.len() && freq > self.freq[swap_idx + 1] {
|
|
||||||
swap_idx += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.freq.swap(current, swap_idx);
|
|
||||||
|
|
||||||
let left = self.son[current];
|
|
||||||
let right = self.son[swap_idx];
|
|
||||||
self.son[current] = right;
|
|
||||||
self.son[swap_idx] = left;
|
|
||||||
|
|
||||||
self.parent[left] = swap_idx;
|
|
||||||
if left < LZH_T {
|
|
||||||
self.parent[left + 1] = swap_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.parent[right] = current;
|
|
||||||
if right < LZH_T {
|
|
||||||
self.parent[right + 1] = current;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = swap_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = self.parent[current];
|
|
||||||
if current == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reconstruct(&mut self) {
|
|
||||||
let mut j = 0usize;
|
|
||||||
for i in 0..LZH_T {
|
|
||||||
if self.son[i] >= LZH_T {
|
|
||||||
self.freq[j] = (self.freq[i].saturating_add(1)) / 2;
|
|
||||||
self.son[j] = self.son[i];
|
|
||||||
j += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut i = 0usize;
|
|
||||||
let mut current = LZH_N_CHAR;
|
|
||||||
while current < LZH_T {
|
|
||||||
let sum = self.freq[i].saturating_add(self.freq[i + 1]);
|
|
||||||
self.freq[current] = sum;
|
|
||||||
|
|
||||||
let mut insert_at = current;
|
|
||||||
while insert_at > 0 && sum < self.freq[insert_at - 1] {
|
|
||||||
insert_at -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for move_idx in (insert_at..current).rev() {
|
|
||||||
self.freq[move_idx + 1] = self.freq[move_idx];
|
|
||||||
self.son[move_idx + 1] = self.son[move_idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
self.freq[insert_at] = sum;
|
|
||||||
self.son[insert_at] = i;
|
|
||||||
|
|
||||||
i += 2;
|
|
||||||
current += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx in 0..LZH_T {
|
|
||||||
let node = self.son[idx];
|
|
||||||
self.parent[node] = idx;
|
|
||||||
if node < LZH_T {
|
|
||||||
self.parent[node + 1] = idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.freq[LZH_T] = u16::MAX;
|
|
||||||
self.parent[LZH_R] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BitReader<'a> {
|
|
||||||
data: &'a [u8],
|
|
||||||
byte_pos: usize,
|
|
||||||
bit_mask: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> BitReader<'a> {
|
|
||||||
fn new(data: &'a [u8]) -> Self {
|
|
||||||
Self {
|
|
||||||
data,
|
|
||||||
byte_pos: 0,
|
|
||||||
bit_mask: 0x80,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_bit_or_zero(&mut self) -> u8 {
|
|
||||||
let Some(byte) = self.data.get(self.byte_pos).copied() else {
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
let bit = if (byte & self.bit_mask) != 0 { 1 } else { 0 };
|
|
||||||
self.bit_mask >>= 1;
|
|
||||||
if self.bit_mask == 0 {
|
|
||||||
self.bit_mask = 0x80;
|
|
||||||
self.byte_pos = self.byte_pos.saturating_add(1);
|
|
||||||
}
|
|
||||||
bit
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_bits_or_zero(&mut self, bits: usize) -> u32 {
|
|
||||||
let mut value = 0u32;
|
|
||||||
for _ in 0..bits {
|
|
||||||
value = (value << 1) | u32::from(self.read_bit_or_zero());
|
|
||||||
}
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_name(name: &[u8]) -> String {
|
|
||||||
name.iter().map(|b| char::from(*b)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn c_name_bytes(raw: &[u8; 12]) -> &[u8] {
|
|
||||||
let len = raw.iter().position(|&b| b == 0).unwrap_or(raw.len());
|
|
||||||
&raw[..len]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cmp_c_string(a: &[u8], b: &[u8]) -> Ordering {
|
|
||||||
let min_len = a.len().min(b.len());
|
|
||||||
let mut idx = 0usize;
|
|
||||||
while idx < min_len {
|
|
||||||
if a[idx] != b[idx] {
|
|
||||||
return a[idx].cmp(&b[idx]);
|
|
||||||
}
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
a.len().cmp(&b.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_xor_key(method: PackMethod) -> bool {
|
fn needs_xor_key(method: PackMethod) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
method,
|
method,
|
||||||
|
|||||||
249
crates/rsli/src/parse.rs
Normal file
249
crates/rsli/src/parse.rs
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
use crate::compress::xor::xor_stream;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::{EntryMeta, EntryRecord, Library, OpenOptions, PackMethod, Result};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub fn parse_library(bytes: Arc<[u8]>, opts: OpenOptions) -> Result<Library> {
|
||||||
|
if bytes.len() < 32 {
|
||||||
|
return Err(Error::EntryTableOutOfBounds {
|
||||||
|
table_offset: 32,
|
||||||
|
table_len: 0,
|
||||||
|
file_len: u64::try_from(bytes.len()).map_err(|_| Error::IntegerOverflow)?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut header_raw = [0u8; 32];
|
||||||
|
header_raw.copy_from_slice(&bytes[0..32]);
|
||||||
|
|
||||||
|
if &bytes[0..2] != b"NL" {
|
||||||
|
let mut got = [0u8; 2];
|
||||||
|
got.copy_from_slice(&bytes[0..2]);
|
||||||
|
return Err(Error::InvalidMagic { got });
|
||||||
|
}
|
||||||
|
if bytes[3] != 0x01 {
|
||||||
|
return Err(Error::UnsupportedVersion { got: bytes[3] });
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry_count = i16::from_le_bytes([bytes[4], bytes[5]]);
|
||||||
|
if entry_count < 0 {
|
||||||
|
return Err(Error::InvalidEntryCount { got: entry_count });
|
||||||
|
}
|
||||||
|
let count = usize::try_from(entry_count).map_err(|_| Error::IntegerOverflow)?;
|
||||||
|
|
||||||
|
// Validate entry_count fits in u32 (required for EntryId)
|
||||||
|
if count > u32::MAX as usize {
|
||||||
|
return Err(Error::TooManyEntries { got: count });
|
||||||
|
}
|
||||||
|
|
||||||
|
let xor_seed = u32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]);
|
||||||
|
|
||||||
|
let table_len = count.checked_mul(32).ok_or(Error::IntegerOverflow)?;
|
||||||
|
let table_offset = 32usize;
|
||||||
|
let table_end = table_offset
|
||||||
|
.checked_add(table_len)
|
||||||
|
.ok_or(Error::IntegerOverflow)?;
|
||||||
|
if table_end > bytes.len() {
|
||||||
|
return Err(Error::EntryTableOutOfBounds {
|
||||||
|
table_offset: u64::try_from(table_offset).map_err(|_| Error::IntegerOverflow)?,
|
||||||
|
table_len: u64::try_from(table_len).map_err(|_| Error::IntegerOverflow)?,
|
||||||
|
file_len: u64::try_from(bytes.len()).map_err(|_| Error::IntegerOverflow)?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let table_enc = &bytes[table_offset..table_end];
|
||||||
|
let table_plain_original = xor_stream(table_enc, (xor_seed & 0xFFFF) as u16);
|
||||||
|
if table_plain_original.len() != table_len {
|
||||||
|
return Err(Error::EntryTableDecryptFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (overlay, trailer_raw) = parse_ao_trailer(&bytes, opts.allow_ao_trailer)?;
|
||||||
|
#[cfg(not(test))]
|
||||||
|
let _ = trailer_raw;
|
||||||
|
|
||||||
|
let mut entries = Vec::with_capacity(count);
|
||||||
|
for idx in 0..count {
|
||||||
|
let row = &table_plain_original[idx * 32..(idx + 1) * 32];
|
||||||
|
|
||||||
|
let mut name_raw = [0u8; 12];
|
||||||
|
name_raw.copy_from_slice(&row[0..12]);
|
||||||
|
|
||||||
|
let flags_signed = i16::from_le_bytes([row[16], row[17]]);
|
||||||
|
let sort_to_original = i16::from_le_bytes([row[18], row[19]]);
|
||||||
|
let unpacked_size = u32::from_le_bytes([row[20], row[21], row[22], row[23]]);
|
||||||
|
let data_offset_raw = u32::from_le_bytes([row[24], row[25], row[26], row[27]]);
|
||||||
|
let packed_size_declared = u32::from_le_bytes([row[28], row[29], row[30], row[31]]);
|
||||||
|
|
||||||
|
let method_raw = (flags_signed as u16 as u32) & 0x1E0;
|
||||||
|
let method = parse_method(method_raw);
|
||||||
|
|
||||||
|
let effective_offset_u64 = u64::from(data_offset_raw)
|
||||||
|
.checked_add(u64::from(overlay))
|
||||||
|
.ok_or(Error::IntegerOverflow)?;
|
||||||
|
let effective_offset =
|
||||||
|
usize::try_from(effective_offset_u64).map_err(|_| Error::IntegerOverflow)?;
|
||||||
|
|
||||||
|
let packed_size_usize =
|
||||||
|
usize::try_from(packed_size_declared).map_err(|_| Error::IntegerOverflow)?;
|
||||||
|
let mut packed_size_available = packed_size_usize;
|
||||||
|
|
||||||
|
let end = effective_offset_u64
|
||||||
|
.checked_add(u64::from(packed_size_declared))
|
||||||
|
.ok_or(Error::IntegerOverflow)?;
|
||||||
|
let file_len_u64 = u64::try_from(bytes.len()).map_err(|_| Error::IntegerOverflow)?;
|
||||||
|
|
||||||
|
if end > file_len_u64 {
|
||||||
|
if method_raw == 0x100 && end == file_len_u64 + 1 {
|
||||||
|
if opts.allow_deflate_eof_plus_one {
|
||||||
|
packed_size_available = packed_size_available
|
||||||
|
.checked_sub(1)
|
||||||
|
.ok_or(Error::IntegerOverflow)?;
|
||||||
|
} else {
|
||||||
|
return Err(Error::DeflateEofPlusOneQuirkRejected {
|
||||||
|
id: u32::try_from(idx).expect("entry count validated at parse"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::PackedSizePastEof {
|
||||||
|
id: u32::try_from(idx).expect("entry count validated at parse"),
|
||||||
|
offset: effective_offset_u64,
|
||||||
|
packed_size: packed_size_declared,
|
||||||
|
file_len: file_len_u64,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let available_end = effective_offset
|
||||||
|
.checked_add(packed_size_available)
|
||||||
|
.ok_or(Error::IntegerOverflow)?;
|
||||||
|
if available_end > bytes.len() {
|
||||||
|
return Err(Error::EntryDataOutOfBounds {
|
||||||
|
id: u32::try_from(idx).expect("entry count validated at parse"),
|
||||||
|
offset: effective_offset_u64,
|
||||||
|
size: packed_size_declared,
|
||||||
|
file_len: file_len_u64,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = decode_name(c_name_bytes(&name_raw));
|
||||||
|
|
||||||
|
entries.push(EntryRecord {
|
||||||
|
meta: EntryMeta {
|
||||||
|
name,
|
||||||
|
flags: i32::from(flags_signed),
|
||||||
|
method,
|
||||||
|
data_offset: effective_offset_u64,
|
||||||
|
packed_size: packed_size_declared,
|
||||||
|
unpacked_size,
|
||||||
|
},
|
||||||
|
name_raw,
|
||||||
|
sort_to_original,
|
||||||
|
key16: sort_to_original as u16,
|
||||||
|
#[cfg(test)]
|
||||||
|
data_offset_raw,
|
||||||
|
packed_size_declared,
|
||||||
|
packed_size_available,
|
||||||
|
effective_offset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let presorted_flag = u16::from_le_bytes([bytes[14], bytes[15]]);
|
||||||
|
if presorted_flag == 0xABBA {
|
||||||
|
for entry in &entries {
|
||||||
|
let idx = i32::from(entry.sort_to_original);
|
||||||
|
if idx < 0 || usize::try_from(idx).map_err(|_| Error::IntegerOverflow)? >= count {
|
||||||
|
return Err(Error::CorruptEntryTable(
|
||||||
|
"sort_to_original is not a valid permutation index",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut sorted: Vec<usize> = (0..count).collect();
|
||||||
|
sorted.sort_by(|a, b| {
|
||||||
|
cmp_c_string(
|
||||||
|
c_name_bytes(&entries[*a].name_raw),
|
||||||
|
c_name_bytes(&entries[*b].name_raw),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
for (idx, entry) in entries.iter_mut().enumerate() {
|
||||||
|
entry.sort_to_original =
|
||||||
|
i16::try_from(sorted[idx]).map_err(|_| Error::IntegerOverflow)?;
|
||||||
|
entry.key16 = entry.sort_to_original as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
let source_size = bytes.len();
|
||||||
|
|
||||||
|
Ok(Library {
|
||||||
|
bytes,
|
||||||
|
entries,
|
||||||
|
#[cfg(test)]
|
||||||
|
header_raw,
|
||||||
|
#[cfg(test)]
|
||||||
|
table_plain_original,
|
||||||
|
#[cfg(test)]
|
||||||
|
xor_seed,
|
||||||
|
#[cfg(test)]
|
||||||
|
source_size,
|
||||||
|
#[cfg(test)]
|
||||||
|
trailer_raw,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ao_trailer(bytes: &[u8], allow: bool) -> Result<(u32, Option<[u8; 6]>)> {
|
||||||
|
if !allow || bytes.len() < 6 {
|
||||||
|
return Ok((0, None));
|
||||||
|
}
|
||||||
|
|
||||||
|
if &bytes[bytes.len() - 6..bytes.len() - 4] != b"AO" {
|
||||||
|
return Ok((0, None));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut trailer = [0u8; 6];
|
||||||
|
trailer.copy_from_slice(&bytes[bytes.len() - 6..]);
|
||||||
|
let overlay = u32::from_le_bytes([trailer[2], trailer[3], trailer[4], trailer[5]]);
|
||||||
|
|
||||||
|
if u64::from(overlay) > u64::try_from(bytes.len()).map_err(|_| Error::IntegerOverflow)? {
|
||||||
|
return Err(Error::MediaOverlayOutOfBounds {
|
||||||
|
overlay,
|
||||||
|
file_len: u64::try_from(bytes.len()).map_err(|_| Error::IntegerOverflow)?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((overlay, Some(trailer)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_method(raw: u32) -> PackMethod {
|
||||||
|
match raw {
|
||||||
|
0x000 => PackMethod::None,
|
||||||
|
0x020 => PackMethod::XorOnly,
|
||||||
|
0x040 => PackMethod::Lzss,
|
||||||
|
0x060 => PackMethod::XorLzss,
|
||||||
|
0x080 => PackMethod::LzssHuffman,
|
||||||
|
0x0A0 => PackMethod::XorLzssHuffman,
|
||||||
|
0x100 => PackMethod::Deflate,
|
||||||
|
other => PackMethod::Unknown(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_name(name: &[u8]) -> String {
|
||||||
|
name.iter().map(|b| char::from(*b)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn c_name_bytes(raw: &[u8; 12]) -> &[u8] {
|
||||||
|
let len = raw.iter().position(|&b| b == 0).unwrap_or(raw.len());
|
||||||
|
&raw[..len]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cmp_c_string(a: &[u8], b: &[u8]) -> Ordering {
|
||||||
|
let min_len = a.len().min(b.len());
|
||||||
|
let mut idx = 0usize;
|
||||||
|
while idx < min_len {
|
||||||
|
if a[idx] != b[idx] {
|
||||||
|
return a[idx].cmp(&b[idx]);
|
||||||
|
}
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
a.len().cmp(&b.len())
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::compress::lzh::{LZH_MAX_FREQ, LZH_N_CHAR, LZH_R, LZH_T};
|
||||||
|
use crate::compress::xor::xor_stream;
|
||||||
use flate2::write::DeflateEncoder;
|
use flate2::write::DeflateEncoder;
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|||||||
Reference in New Issue
Block a user