feat: обновить методы обработки данных и улучшить обработку ошибок в библиотеке
This commit is contained in:
@@ -31,6 +31,7 @@ impl AsRef<[u8]> for ResourceData<'_> {
|
||||
|
||||
/// Output sink used by `read_into`/`load_into` APIs.
|
||||
pub trait OutputBuffer {
|
||||
/// Writes the full payload to the sink, replacing any previous content.
|
||||
fn write_exact(&mut self, data: &[u8]) -> io::Result<()>;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,4 +100,11 @@ impl fmt::Display for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Io(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
use crate::error::Error;
|
||||
use crate::Result;
|
||||
use flate2::read::{DeflateDecoder, ZlibDecoder};
|
||||
use flate2::read::DeflateDecoder;
|
||||
use std::io::Read;
|
||||
|
||||
/// Decode Deflate or Zlib compressed data
|
||||
/// Decode raw Deflate (RFC 1951) payload.
|
||||
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)
|
||||
decoder
|
||||
.read_to_end(&mut out)
|
||||
.map_err(|_| Error::DecompressionFailed("deflate"))?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
@@ -52,14 +52,14 @@ impl<'a> LzhDecoder<'a> {
|
||||
let mut out = Vec::with_capacity(expected_size);
|
||||
|
||||
while out.len() < expected_size {
|
||||
let c = self.decode_char();
|
||||
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();
|
||||
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);
|
||||
|
||||
@@ -131,29 +131,29 @@ impl<'a> LzhDecoder<'a> {
|
||||
self.parent[LZH_R] = 0;
|
||||
}
|
||||
|
||||
fn decode_char(&mut self) -> usize {
|
||||
fn decode_char(&mut self) -> Result<usize> {
|
||||
let mut node = self.son[LZH_R];
|
||||
while node < LZH_T {
|
||||
let bit = usize::from(self.bit_reader.read_bit_or_zero());
|
||||
let bit = usize::from(self.bit_reader.read_bit()?);
|
||||
node = self.son[node + bit];
|
||||
}
|
||||
|
||||
let c = node - LZH_T;
|
||||
self.update(c);
|
||||
c
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
fn decode_position(&mut self) -> usize {
|
||||
let i = self.bit_reader.read_bits_or_zero(8) as usize;
|
||||
fn decode_position(&mut self) -> Result<usize> {
|
||||
let i = self.bit_reader.read_bits(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 |= usize::from(self.bit_reader.read_bit()?) << j;
|
||||
}
|
||||
|
||||
c | (i & 0x3F)
|
||||
Ok(c | (i & 0x3F))
|
||||
}
|
||||
|
||||
fn update(&mut self, c: usize) {
|
||||
@@ -264,10 +264,10 @@ impl<'a> BitReader<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_bit_or_zero(&mut self) -> u8 {
|
||||
fn read_bit(&mut self) -> Result<u8> {
|
||||
if self.bit_mask == 0x80 {
|
||||
let Some(mut byte) = self.data.get(self.byte_pos).copied() else {
|
||||
return 0;
|
||||
return Err(Error::DecompressionFailed("lzss-huffman: unexpected EOF"));
|
||||
};
|
||||
if let Some(state) = &mut self.xor_state {
|
||||
byte = state.decrypt_byte(byte);
|
||||
@@ -285,14 +285,14 @@ impl<'a> BitReader<'a> {
|
||||
self.bit_mask = 0x80;
|
||||
self.byte_pos = self.byte_pos.saturating_add(1);
|
||||
}
|
||||
bit
|
||||
Ok(bit)
|
||||
}
|
||||
|
||||
fn read_bits_or_zero(&mut self, bits: usize) -> u32 {
|
||||
fn read_bits(&mut self, bits: usize) -> Result<u32> {
|
||||
let mut value = 0u32;
|
||||
for _ in 0..bits {
|
||||
value = (value << 1) | u32::from(self.read_bit_or_zero());
|
||||
value = (value << 1) | u32::from(self.read_bit()?);
|
||||
}
|
||||
value
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,4 +130,11 @@ impl fmt::Display for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Io(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ impl Library {
|
||||
|
||||
pub fn load(&self, id: EntryId) -> Result<Vec<u8>> {
|
||||
let entry = self.entry_by_id(id)?;
|
||||
let packed = self.packed_slice(entry)?;
|
||||
let packed = self.packed_slice(id, entry)?;
|
||||
decode_payload(
|
||||
packed,
|
||||
entry.meta.method,
|
||||
@@ -208,7 +208,7 @@ impl Library {
|
||||
|
||||
pub fn load_packed(&self, id: EntryId) -> Result<PackedResource> {
|
||||
let entry = self.entry_by_id(id)?;
|
||||
let packed = self.packed_slice(entry)?.to_vec();
|
||||
let packed = self.packed_slice(id, entry)?.to_vec();
|
||||
Ok(PackedResource {
|
||||
meta: entry.meta.clone(),
|
||||
packed,
|
||||
@@ -231,7 +231,7 @@ impl Library {
|
||||
pub fn load_fast(&self, id: EntryId) -> Result<ResourceData<'_>> {
|
||||
let entry = self.entry_by_id(id)?;
|
||||
if entry.meta.method == PackMethod::None {
|
||||
let packed = self.packed_slice(entry)?;
|
||||
let packed = self.packed_slice(id, entry)?;
|
||||
let size =
|
||||
usize::try_from(entry.meta.unpacked_size).map_err(|_| Error::IntegerOverflow)?;
|
||||
if packed.len() < size {
|
||||
@@ -255,7 +255,7 @@ impl Library {
|
||||
})
|
||||
}
|
||||
|
||||
fn packed_slice<'a>(&'a self, entry: &EntryRecord) -> Result<&'a [u8]> {
|
||||
fn packed_slice<'a>(&'a self, id: EntryId, entry: &EntryRecord) -> Result<&'a [u8]> {
|
||||
let start = entry.effective_offset;
|
||||
let end = start
|
||||
.checked_add(entry.packed_size_available)
|
||||
@@ -263,7 +263,7 @@ impl Library {
|
||||
self.bytes
|
||||
.get(start..end)
|
||||
.ok_or(Error::EntryDataOutOfBounds {
|
||||
id: 0,
|
||||
id: id.0,
|
||||
offset: u64::try_from(start).unwrap_or(u64::MAX),
|
||||
size: entry.packed_size_declared,
|
||||
file_len: u64::try_from(self.bytes.len()).unwrap_or(u64::MAX),
|
||||
|
||||
@@ -2,6 +2,7 @@ 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::ZlibEncoder;
|
||||
use flate2::Compression;
|
||||
use std::any::Any;
|
||||
use std::fs;
|
||||
@@ -103,6 +104,12 @@ fn deflate_raw(data: &[u8]) -> Vec<u8> {
|
||||
encoder.finish().expect("deflate encoder finish failed")
|
||||
}
|
||||
|
||||
fn deflate_zlib(data: &[u8]) -> Vec<u8> {
|
||||
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
|
||||
encoder.write_all(data).expect("zlib encoder write failed");
|
||||
encoder.finish().expect("zlib encoder finish failed")
|
||||
}
|
||||
|
||||
fn lzss_pack_literals(data: &[u8]) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
for chunk in data.chunks(8) {
|
||||
@@ -795,6 +802,131 @@ fn rsli_synthetic_all_methods_roundtrip() {
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rsli_find_falls_back_when_sort_table_corrupted_in_memory() {
|
||||
let entries = vec![
|
||||
SyntheticRsliEntry {
|
||||
name: "AAA".to_string(),
|
||||
method_raw: 0x000,
|
||||
plain: b"a".to_vec(),
|
||||
declared_packed_size: None,
|
||||
},
|
||||
SyntheticRsliEntry {
|
||||
name: "BBB".to_string(),
|
||||
method_raw: 0x000,
|
||||
plain: b"b".to_vec(),
|
||||
declared_packed_size: None,
|
||||
},
|
||||
SyntheticRsliEntry {
|
||||
name: "CCC".to_string(),
|
||||
method_raw: 0x000,
|
||||
plain: b"c".to_vec(),
|
||||
declared_packed_size: None,
|
||||
},
|
||||
];
|
||||
let bytes = build_rsli_bytes(
|
||||
&entries,
|
||||
&RsliBuildOptions {
|
||||
presorted: true,
|
||||
..RsliBuildOptions::default()
|
||||
},
|
||||
);
|
||||
let path = write_temp_file("rsli-find-fallback", &bytes);
|
||||
|
||||
let mut library = Library::open_path(&path).expect("open synthetic rsli failed");
|
||||
library.entries[1].sort_to_original = -1;
|
||||
|
||||
assert_eq!(library.find("AAA"), Some(EntryId(0)));
|
||||
assert_eq!(library.find("bbb"), Some(EntryId(1)));
|
||||
assert_eq!(library.find("CcC"), Some(EntryId(2)));
|
||||
assert_eq!(library.find("missing"), None);
|
||||
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rsli_deflate_method_rejects_zlib_wrapped_stream() {
|
||||
let plain = b"payload".to_vec();
|
||||
let zlib_payload = deflate_zlib(&plain);
|
||||
let entries = vec![SyntheticRsliEntry {
|
||||
name: "ZLIB".to_string(),
|
||||
method_raw: 0x100,
|
||||
plain,
|
||||
declared_packed_size: Some(
|
||||
u32::try_from(zlib_payload.len()).expect("zlib payload size overflow"),
|
||||
),
|
||||
}];
|
||||
let mut bytes = build_rsli_bytes(
|
||||
&entries,
|
||||
&RsliBuildOptions {
|
||||
presorted: true,
|
||||
..RsliBuildOptions::default()
|
||||
},
|
||||
);
|
||||
|
||||
let table_end = 32 + entries.len() * 32;
|
||||
let data_offset = table_end;
|
||||
let data_end = data_offset + zlib_payload.len();
|
||||
if bytes.len() < data_end {
|
||||
bytes.resize(data_end, 0);
|
||||
}
|
||||
bytes[data_offset..data_end].copy_from_slice(&zlib_payload);
|
||||
|
||||
let path = write_temp_file("rsli-zlib-reject", &bytes);
|
||||
let library = Library::open_path(&path).expect("open zlib-wrapped rsli failed");
|
||||
match library.load(EntryId(0)) {
|
||||
Err(Error::DecompressionFailed(reason)) => {
|
||||
assert_eq!(reason, "deflate");
|
||||
}
|
||||
other => panic!("expected deflate decompression error, got {other:?}"),
|
||||
}
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rsli_lzss_huffman_reports_unexpected_eof() {
|
||||
let entries = vec![SyntheticRsliEntry {
|
||||
name: "TRUNC".to_string(),
|
||||
method_raw: 0x080,
|
||||
plain: b"this payload is long enough".to_vec(),
|
||||
declared_packed_size: None,
|
||||
}];
|
||||
let mut bytes = build_rsli_bytes(
|
||||
&entries,
|
||||
&RsliBuildOptions {
|
||||
presorted: true,
|
||||
..RsliBuildOptions::default()
|
||||
},
|
||||
);
|
||||
|
||||
let seed = read_u32_le(&bytes, 20);
|
||||
let mut table_plain = xor_stream(&bytes[32..64], (seed & 0xFFFF) as u16);
|
||||
let original_packed_size = u32::from_le_bytes([
|
||||
table_plain[28],
|
||||
table_plain[29],
|
||||
table_plain[30],
|
||||
table_plain[31],
|
||||
]);
|
||||
assert!(
|
||||
original_packed_size > 4,
|
||||
"packed payload too small for truncation"
|
||||
);
|
||||
let truncated_size = original_packed_size - 3;
|
||||
table_plain[28..32].copy_from_slice(&truncated_size.to_le_bytes());
|
||||
let encrypted_table = xor_stream(&table_plain, (seed & 0xFFFF) as u16);
|
||||
bytes[32..64].copy_from_slice(&encrypted_table);
|
||||
|
||||
let path = write_temp_file("rsli-lzh-truncated", &bytes);
|
||||
let library = Library::open_path(&path).expect("open truncated lzh rsli failed");
|
||||
match library.load(EntryId(0)) {
|
||||
Err(Error::DecompressionFailed(reason)) => {
|
||||
assert_eq!(reason, "lzss-huffman: unexpected EOF");
|
||||
}
|
||||
other => panic!("expected lzss-huffman EOF error, got {other:?}"),
|
||||
}
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rsli_presorted_flag_requires_permutation() {
|
||||
let entries = vec![
|
||||
@@ -842,6 +974,48 @@ fn rsli_presorted_flag_requires_permutation() {
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rsli_load_reports_correct_entry_id_on_range_failure() {
|
||||
let entries = vec![
|
||||
SyntheticRsliEntry {
|
||||
name: "ONE".to_string(),
|
||||
method_raw: 0x000,
|
||||
plain: b"one".to_vec(),
|
||||
declared_packed_size: None,
|
||||
},
|
||||
SyntheticRsliEntry {
|
||||
name: "TWO".to_string(),
|
||||
method_raw: 0x000,
|
||||
plain: b"two".to_vec(),
|
||||
declared_packed_size: None,
|
||||
},
|
||||
];
|
||||
let bytes = build_rsli_bytes(
|
||||
&entries,
|
||||
&RsliBuildOptions {
|
||||
presorted: true,
|
||||
..RsliBuildOptions::default()
|
||||
},
|
||||
);
|
||||
let path = write_temp_file("rsli-entry-id-error", &bytes);
|
||||
|
||||
let mut library = Library::open_path(&path).expect("open synthetic rsli failed");
|
||||
library.entries[1].packed_size_available = usize::MAX;
|
||||
|
||||
match library.load(EntryId(1)) {
|
||||
Err(Error::IntegerOverflow) => {}
|
||||
other => panic!("expected IntegerOverflow, got {other:?}"),
|
||||
}
|
||||
|
||||
library.entries[1].packed_size_available = library.bytes.len();
|
||||
match library.load(EntryId(1)) {
|
||||
Err(Error::EntryDataOutOfBounds { id, .. }) => assert_eq!(id, 1),
|
||||
other => panic!("expected EntryDataOutOfBounds with id=1, got {other:?}"),
|
||||
}
|
||||
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rsli_xorlzss_huffman_on_the_fly_roundtrip() {
|
||||
let plain: Vec<u8> = (0..512u16).map(|i| b'A' + (i % 26) as u8).collect();
|
||||
|
||||
Reference in New Issue
Block a user