Add simple crypt support when unpack xp3 archive

This commit is contained in:
2025-10-10 11:16:59 +08:00
parent f12a66d3d4
commit fdbfab18ab
2 changed files with 325 additions and 7 deletions

View File

@@ -1719,6 +1719,7 @@ impl CPeek for MemWriter {
}
/// A region of a stream that can be read/write and seeked within a specified range.
#[derive(Debug)]
pub struct StreamRegion<T: Seek> {
stream: T,
start_pos: u64,
@@ -2001,7 +2002,7 @@ impl<R: Read + Seek, W: Write + Seek, A: Fn(u64) -> Result<u64>, O: Fn(u64) -> R
}
/// A thread-safe wrapper around a Mutex-protected writer/reader.
#[derive(Clone)]
#[derive(Debug)]
pub struct MutexWrapper<T> {
inner: Arc<Mutex<T>>,
pos: u64,
@@ -2078,3 +2079,90 @@ impl Write for EmptyWriter {
Ok(())
}
}
#[derive(Debug)]
/// A readable stream that starts with a given prefix before the actual data.
pub struct PrefixStream<T> {
prefix: Vec<u8>,
pos: usize,
inner: T,
}
impl<T> PrefixStream<T> {
/// Creates a new `PrefixStream` with the given prefix and inner stream.
pub fn new(prefix: Vec<u8>, inner: T) -> Self {
PrefixStream {
prefix,
pos: 0,
inner,
}
}
}
impl<T: Read> Read for PrefixStream<T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if self.pos < self.prefix.len() {
let bytes_to_read = std::cmp::min(buf.len(), self.prefix.len() - self.pos);
buf[..bytes_to_read].copy_from_slice(&self.prefix[self.pos..self.pos + bytes_to_read]);
self.pos += bytes_to_read;
Ok(bytes_to_read)
} else {
self.inner.read(buf)
}
}
}
impl<T: Seek> Seek for PrefixStream<T> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
let prefix_len = self.prefix.len() as u64;
let new_pos = match pos {
SeekFrom::Start(offset) => offset,
SeekFrom::End(offset) => {
let inner_len = self.inner.stream_length()?;
if offset < 0 {
if (-offset) as u64 > inner_len + prefix_len {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek position is before the start of the stream",
));
}
inner_len + prefix_len - (-offset) as u64
} else {
inner_len + prefix_len + offset as u64
}
}
SeekFrom::Current(offset) => {
let current_pos = self.stream_position()?;
if offset < 0 {
if (-offset) as u64 > current_pos + prefix_len {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek position is before the start of the stream",
));
}
prefix_len + current_pos - (-offset) as u64
} else {
prefix_len + current_pos + offset as u64
}
}
};
if new_pos < prefix_len {
self.pos = new_pos as usize;
self.inner.rewind()?;
} else {
self.pos = self.prefix.len();
self.inner.seek(SeekFrom::Start(new_pos - prefix_len))?;
}
Ok(new_pos)
}
fn stream_position(&mut self) -> Result<u64> {
Ok(self.pos as u64 + self.inner.stream_position()?)
}
fn rewind(&mut self) -> Result<()> {
self.pos = 0;
self.inner.rewind()?;
Ok(())
}
}

View File

@@ -3,7 +3,8 @@ use crate::scripts::base::*;
use crate::types::*;
use anyhow::Result;
use flate2::read::ZlibDecoder;
use std::io::{Read, Seek, Take};
use overf::wrapping;
use std::io::{Read, Seek, SeekFrom, Take};
use std::sync::{Arc, Mutex};
use xp3::XP3Reader;
use xp3::index::file::{IndexSegmentFlag, XP3FileIndex};
@@ -138,12 +139,32 @@ impl<T: Read + Seek + std::fmt::Debug + 'static> Script for Xp3Archive<T> {
.ok_or(anyhow::anyhow!("Index out of bounds: {}", index))?
.1
.clone();
let entry = Entry::new(self.reader.clone(), index);
let mut entry = Entry::new(self.reader.clone(), index);
let mut header = [0u8; 16];
let header_len = entry.read(&mut header)?;
entry.rewind()?;
if header_len >= 5
&& header[0] == 0xFE
&& header[1] == 0xFE
&& header[3] == 0xFF
&& header[4] == 0xFE
{
let crypt = header[2];
if crypt == 2 {
let index = entry.index.clone();
return Ok(Box::new(SimpleCryptZlib::new(entry, index)?));
}
if matches!(crypt, 0 | 1) {
let index = entry.index.clone();
return Ok(Box::new(SimpleCrypt::new(entry, index, crypt)?));
}
}
Ok(Box::new(entry))
}
}
struct Entry<T: Read + Seek> {
#[derive(Debug)]
struct Entry<T: Read + Seek + std::fmt::Debug> {
reader: Arc<Mutex<T>>,
index: XP3FileIndex,
cache: Option<ZlibDecoder<Take<MutexWrapper<T>>>>,
@@ -151,7 +172,7 @@ struct Entry<T: Read + Seek> {
entries_pos: Vec<u64>,
}
impl<T: Read + Seek> Entry<T> {
impl<T: Read + Seek + std::fmt::Debug> Entry<T> {
fn new(reader: Arc<Mutex<T>>, index: XP3FileIndex) -> Self {
let mut pos = 0;
let entries_pos = index
@@ -173,13 +194,17 @@ impl<T: Read + Seek> Entry<T> {
}
}
impl<T: Read + Seek> ArchiveContent for Entry<T> {
impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for Entry<T> {
fn name(&self) -> &str {
&self.index.info().name()
}
fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + 'a>> {
Ok(Box::new(self))
}
}
impl<T: Read + Seek> Read for Entry<T> {
impl<T: Read + Seek + std::fmt::Debug> Read for Entry<T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.pos >= self.index.info().file_size() {
self.cache.take();
@@ -231,3 +256,208 @@ impl<T: Read + Seek> Read for Entry<T> {
}
}
}
impl<T: Read + Seek + std::fmt::Debug> Seek for Entry<T> {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
let new_pos = match pos {
SeekFrom::Start(p) => p,
SeekFrom::End(offset) => {
if offset < 0 {
if (-offset) as u64 > self.index.info().file_size() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek from end exceeds file length",
));
}
self.index.info().file_size() - (-offset) as u64
} else {
self.index.info().file_size() + offset as u64
}
}
SeekFrom::Current(offset) => {
if offset < 0 {
if (-offset) as u64 > self.pos {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek from current exceeds file start",
));
}
self.pos - (-offset) as u64
} else {
self.pos + offset as u64
}
}
};
if let Some(cache) = self.cache.as_mut() {
let old_seg_index = match self.entries_pos.binary_search(&self.pos) {
Ok(i) => i,
Err(i) => {
if i == 0 {
0
} else {
i - 1
}
}
};
let new_seg_index = match self.entries_pos.binary_search(&new_pos) {
Ok(i) => i,
Err(i) => {
if i == 0 {
0
} else {
i - 1
}
}
};
if old_seg_index != new_seg_index {
self.cache.take();
} else {
if new_pos >= self.pos {
let skip_pos = new_pos - self.pos;
let mut e = EmptyWriter::new();
std::io::copy(&mut cache.take(skip_pos), &mut e)?; // skip
} else {
self.cache.take();
}
}
}
self.pos = new_pos;
Ok(self.pos)
}
fn rewind(&mut self) -> std::io::Result<()> {
self.pos = 0;
self.cache.take();
Ok(())
}
fn stream_position(&mut self) -> std::io::Result<u64> {
Ok(self.pos)
}
}
struct SimpleCryptZlib<T: Read + Seek + std::fmt::Debug> {
inner: PrefixStream<ZlibDecoder<StreamRegion<Entry<T>>>>,
index: XP3FileIndex,
}
impl<T: Read + Seek + std::fmt::Debug> SimpleCryptZlib<T> {
fn new(mut entry: Entry<T>, index: XP3FileIndex) -> Result<Self> {
entry.seek(SeekFrom::Start(0x15))?;
let entry = StreamRegion::new(entry, 0x15, index.info().file_size())?;
let inner = PrefixStream::new(vec![0xFF, 0xFE], ZlibDecoder::new(entry));
Ok(Self { inner, index })
}
}
impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for SimpleCryptZlib<T> {
fn name(&self) -> &str {
&self.index.info().name()
}
}
impl<T: Read + Seek + std::fmt::Debug> Read for SimpleCryptZlib<T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inner.read(buf)
}
}
#[derive(Debug)]
struct SimpleCryptInner<T: Read + Seek + std::fmt::Debug> {
inner: StreamRegion<Entry<T>>,
crypt: u8,
}
impl<T: Read + Seek + std::fmt::Debug> SimpleCryptInner<T> {
fn new(mut entry: Entry<T>, crypt: u8) -> Result<Self> {
entry.seek(SeekFrom::Start(5))?;
let size = entry.index.info().file_size();
let entry = StreamRegion::new(entry, 5, size)?;
Ok(Self {
inner: entry,
crypt,
})
}
}
impl<T: Read + Seek + std::fmt::Debug> Read for SimpleCryptInner<T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let readed = self.inner.read(buf)?;
match self.crypt {
0 => {
for b in &mut buf[..readed] {
let ch = *b as u16;
if ch >= 20 {
*b = wrapping! {ch ^ (((ch & 0xfe) << 8) ^ 1)} as u8;
}
}
}
1 => {
for b in &mut buf[..readed] {
let mut ch = *b as u32;
ch = wrapping! {((ch & 0xaaaaaaaa) >> 1) | ((ch & 0x55555555) << 1)};
*b = ch as u8;
}
}
_ => {}
}
Ok(readed)
}
}
impl<T: Read + Seek + std::fmt::Debug> Seek for SimpleCryptInner<T> {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
fn rewind(&mut self) -> std::io::Result<()> {
self.inner.rewind()
}
fn stream_position(&mut self) -> std::io::Result<u64> {
self.inner.stream_position()
}
}
#[derive(Debug)]
struct SimpleCrypt<T: Read + Seek + std::fmt::Debug> {
inner: PrefixStream<SimpleCryptInner<T>>,
index: XP3FileIndex,
}
impl<T: Read + Seek + std::fmt::Debug> SimpleCrypt<T> {
fn new(entry: Entry<T>, index: XP3FileIndex, crypt: u8) -> Result<Self> {
let inner = PrefixStream::new(vec![0xFF, 0xFE], SimpleCryptInner::new(entry, crypt)?);
Ok(Self { inner, index })
}
}
impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for SimpleCrypt<T> {
fn name(&self) -> &str {
&self.index.info().name()
}
fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + 'a>> {
Ok(Box::new(self))
}
}
impl<T: Read + Seek + std::fmt::Debug> Read for SimpleCrypt<T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inner.read(buf)
}
}
impl<T: Read + Seek + std::fmt::Debug> Seek for SimpleCrypt<T> {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
fn rewind(&mut self) -> std::io::Result<()> {
self.inner.rewind()
}
fn stream_position(&mut self) -> std::io::Result<u64> {
self.inner.stream_position()
}
}