Remove xp3 deps

This commit is contained in:
2026-04-05 23:36:26 +08:00
parent fdf6f7561f
commit a85c67e806
14 changed files with 416 additions and 157 deletions

13
Cargo.lock generated
View File

@@ -1457,7 +1457,6 @@ dependencies = [
"webp",
"windows-sys 0.61.2",
"xml5ever",
"xp3",
"zopfli",
"zstd",
]
@@ -2664,18 +2663,6 @@ dependencies = [
"markup5ever",
]
[[package]]
name = "xp3"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c728da4ef7d98958a2d42fd957e82dd96723ec9c6255ccb3e743142d556ab6"
dependencies = [
"adler32",
"byteorder",
"encoding",
"flate2",
]
[[package]]
name = "yoke"
version = "0.8.2"

View File

@@ -55,7 +55,6 @@ url = { version = "2.5", optional = true }
utf16string = "0.2"
webp = { version = "0.3", default-features = false, optional = true }
xml5ever = { version = "0.38", optional = true }
xp3 = { version = "0.3", optional = true}
zopfli = { version = "0.8", optional = true }
zstd = { version = "0.13", optional = true }
@@ -93,7 +92,7 @@ hexen-haus = ["memchr", "utils-str"]
hexen-haus-arc = ["hexen-haus"]
hexen-haus-img = ["hexen-haus", "image"]
kirikiri = ["emote-psb", "fancy-regex", "flate2", "json", "lz4", "utils-escape"]
kirikiri-arc = ["kirikiri", "adler", "fastcdc", "flate2", "parse-size", "sha2", "xp3", "zopfli", "zstd"]
kirikiri-arc = ["kirikiri", "adler", "fastcdc", "flate2", "parse-size", "sha2", "zopfli", "zstd"]
kirikiri-img = ["kirikiri", "image", "libtlg-rs"]
musica = []
musica-arc = ["musica", "crc32fast", "flate2", "include-flate", "utils-blowfish", "utils-rc4", "utils-serde-base64bytes", "utils-xored-stream"]

View File

@@ -947,6 +947,9 @@ pub trait ReadExt {
/// Reads as much data as possible into the provided buffer.
/// Returns the number of bytes read.
fn read_most(&mut self, buf: &mut [u8]) -> Result<usize>;
/// Skips a specified number of bytes in the reader.
/// Will return an error if readed bytes are less than the specified length.
fn skip(&mut self, len: u64) -> Result<()>;
}
impl<T: Read> ReadExt for T {
@@ -1129,6 +1132,17 @@ impl<T: Read> ReadExt for T {
}
Ok(total_read)
}
fn skip(&mut self, len: u64) -> Result<()> {
let skiped = std::io::copy(&mut self.by_ref().take(len), &mut std::io::sink())?;
if skiped != len {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"Failed to skip the specified number of bytes",
));
}
Ok(())
}
}
/// A trait to help to write data to a writer.

View File

@@ -1,2 +1 @@
pub mod xp3;
mod xp3pack;

View File

@@ -0,0 +1,61 @@
use super::crypt::Crypt;
use crate::scripts::base::ReadSeek;
use std::sync::{Arc, Mutex};
/// Represents a single data segment for a file.
/// A file can be split into multiple segments, which can be compressed independently.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Segment {
pub is_compressed: bool,
/// The offset of the segment's data within the archive file.
pub start: u64,
/// The offset of this segment within the original, uncompressed file.
pub offset_in_file: u64,
/// The size of the segment after decompression.
pub original_size: u64,
/// The size of the segment in the archive (potentially compressed).
pub archived_size: u64,
}
/// Represents a single file entry within the XP3 archive.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ArchiveItem {
pub name: String,
pub file_hash: u32,
pub original_size: u64,
pub archived_size: u64,
pub segments: Vec<Segment>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Xp3Entry {
pub name: String,
pub flags: u32,
pub file_hash: u32,
pub original_size: u64,
pub archived_size: u64,
pub segments: Vec<Segment>,
pub extras: Vec<ExtraProp>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExtraProp {
pub tag: [u8; 4],
pub data: Vec<u8>,
}
/// Represents the entire XP3 archive
#[derive(Debug)]
#[allow(dead_code)]
pub struct Xp3Archive {
pub inner: Arc<Mutex<Box<dyn ReadSeek>>>,
pub crypt: Arc<Box<dyn Crypt>>,
/// The offset which the archive file start. If the archive is embedded in another file (such as exe), this is the offset of the archive data within the larger file.
pub base_offset: u64,
/// The offset which index start. Releatived to whole file not just xp3 archive.
pub index_offset: u64,
/// Minor version
pub minor_version: u32,
pub entries: Vec<Xp3Entry>,
pub extras: Vec<ExtraProp>,
}

View File

@@ -20,3 +20,7 @@ pub const TVP_XP3_FILE_PROTECTED: u32 = 1 << 31;
pub const TVP_XP3_SEGM_ENCODE_METHOD_MASK: u32 = 0x07;
pub const TVP_XP3_SEGM_ENCODE_RAW: u32 = 0;
pub const TVP_XP3_SEGM_ENCODE_ZLIB: u32 = 1;
pub const TVP_XP3_CURRENT_HEADER_VERSION: u64 = 0x17;
pub const ZSTD_SIGNATURE: &[u8; 4] = b"\x28\xb5\x2f\xfd";

View File

@@ -0,0 +1,34 @@
use super::archive::*;
use crate::ext::io::*;
use crate::types::*;
use crate::utils::encoding::*;
use anyhow::Result;
use std::io::Read;
pub trait Crypt: std::fmt::Debug {
/// Initializes the cryptographic context for the archive.
fn init(&self, _archive: &mut Xp3Archive) -> Result<()> {
Ok(())
}
/// Read a entry name from archive index
fn read_name<'a>(&self, reader: &mut Box<dyn Read + 'a>) -> Result<(String, u64)> {
let name_length = reader.read_u16()?;
let name = reader.read_exact_vec(name_length as usize * 2)?;
Ok((
decode_to_string(Encoding::Utf16LE, &name, true)?,
name_length as u64 * 2 + 2,
))
}
}
#[derive(Debug)]
pub struct NoCrypt {}
impl NoCrypt {
pub fn new() -> Self {
Self {}
}
}
impl Crypt for NoCrypt {}

View File

@@ -1,16 +1,24 @@
use super::xp3pack::*;
mod archive;
#[allow(dead_code)]
mod consts;
mod crypt;
mod read;
mod reader;
mod segmenter;
mod writer;
use crate::ext::io::*;
use crate::scripts::base::*;
use crate::types::*;
use anyhow::Result;
use consts::ZSTD_SIGNATURE;
use flate2::read::ZlibDecoder;
use overf::wrapping;
use std::io::{Read, Seek, SeekFrom, Take};
pub use segmenter::SegmenterConfig;
use std::io::{Read, Seek, SeekFrom};
use std::sync::{Arc, Mutex};
use xp3::XP3Reader;
use xp3::index::file::{IndexSegmentFlag, XP3FileIndex};
pub use super::xp3pack::SegmenterConfig;
use writer::Xp3ArchiveWriter;
use zstd::stream::read::Decoder as ZstdDecoder;
pub fn parse_segmenter_config(str: &str) -> Result<SegmenterConfig> {
let parts: Vec<&str> = str.split(':').collect();
@@ -143,41 +151,32 @@ impl ScriptBuilder for Xp3ArchiveBuilder {
#[derive(Debug)]
/// Kirikiri XP3 Archive
pub struct Xp3Archive<T: Read + Seek + std::fmt::Debug> {
reader: Arc<Mutex<T>>,
entries: Vec<(String, XP3FileIndex)>,
pub struct Xp3Archive {
archive: archive::Xp3Archive,
decrypt_simple_crypt: bool,
decompress_mdf: bool,
}
impl<T: Read + Seek + std::fmt::Debug> Xp3Archive<T> {
/// Create a new Kirikiri XP3 Archive
pub fn new(reader: T, config: &ExtraConfig) -> Result<Self> {
let xp3_reader = XP3Reader::open_archive(reader)
.map_err(|e| anyhow::anyhow!("Failed to open XP3 archive: {:?}", e))?;
let entries = xp3_reader
.entries()
.filter_map(|(i, d)| {
// Skip garbage files
if i.find("$$$ This is a protected archive. $$$").is_some()
|| (i.to_lowercase().ends_with(".nene") && d.info().file_size() == 0)
{
None
} else {
Some((i.clone(), d.clone()))
}
})
.collect();
impl Xp3Archive {
pub fn new<T: Read + Seek + std::fmt::Debug + 'static>(
stream: T,
config: &ExtraConfig,
) -> Result<Self> {
let mut archive = archive::Xp3Archive::new(stream, config)?;
archive.entries.retain(|entry| {
let i = &entry.name;
!(i.find("$$$ This is a protected archive. $$$").is_some()
|| (i.to_lowercase().ends_with(".nene") && entry.original_size == 0))
});
Ok(Self {
reader: Arc::new(Mutex::new(xp3_reader.close().1)),
entries,
archive,
decrypt_simple_crypt: config.xp3_simple_crypt,
decompress_mdf: config.xp3_mdf_decompress,
})
}
}
impl<T: Read + Seek + std::fmt::Debug + 'static> Script for Xp3Archive<T> {
impl Script for Xp3Archive {
fn default_output_script_type(&self) -> OutputScriptType {
OutputScriptType::Json
}
@@ -194,23 +193,26 @@ impl<T: Read + Seek + std::fmt::Debug + 'static> Script for Xp3Archive<T> {
&'a self,
) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
Ok(Box::new(
self.entries.iter().map(|entry| Ok(entry.0.clone())),
self.archive
.entries
.iter()
.map(|entry| Ok(entry.name.clone())),
))
}
fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + 'a>> {
let index = self
.archive
.entries
.iter()
.nth(index)
.ok_or(anyhow::anyhow!("Index out of bounds: {}", index))?
.1
.clone();
let mut entry = Entry::new(self.reader.clone(), index);
let mut entry = Entry::new(self.archive.inner.clone(), index);
let mut header = [0u8; 16];
let header_len = entry.read(&mut header)?;
entry.rewind()?;
entry.script_type = detect_script_type(entry.index.info().name(), &header, header_len);
entry.script_type = detect_script_type(&entry.index.name, &header, header_len);
if self.decrypt_simple_crypt
&& header_len >= 5
&& header[0] == 0xFE
@@ -231,7 +233,7 @@ impl<T: Read + Seek + std::fmt::Debug + 'static> Script for Xp3Archive<T> {
if self.decompress_mdf
&& header_len >= 4
&& &header[0..4] == b"mdf\0"
&& entry.index.info().file_size() > 8
&& entry.index.original_size > 8
{
let index = entry.index.clone();
return Ok(Box::new(MdfEntry::new(entry, index)?));
@@ -267,25 +269,36 @@ fn detect_script_type(filename: &str, buf: &[u8], buf_len: usize) -> Option<Scri
}
}
#[derive(Debug)]
struct Entry<T: Read + Seek + std::fmt::Debug> {
reader: Arc<Mutex<T>>,
index: XP3FileIndex,
cache: Option<ZlibDecoder<Take<MutexWrapper<T>>>>,
struct Entry {
reader: Arc<Mutex<Box<dyn ReadSeek>>>,
index: archive::Xp3Entry,
cache: Option<Box<dyn Read>>,
pos: u64,
entries_pos: Vec<u64>,
script_type: Option<ScriptType>,
}
impl<T: Read + Seek + std::fmt::Debug> Entry<T> {
fn new(reader: Arc<Mutex<T>>, index: XP3FileIndex) -> Self {
impl std::fmt::Debug for Entry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Entry")
.field("name", &self.index.name)
.field("flags", &self.index.flags)
.field("file_hash", &self.index.file_hash)
.field("original_size", &self.index.original_size)
.field("archived_size", &self.index.archived_size)
.finish()
}
}
impl Entry {
fn new(reader: Arc<Mutex<Box<dyn ReadSeek>>>, index: archive::Xp3Entry) -> Self {
let mut pos = 0;
let entries_pos = index
.segments()
.segments
.iter()
.map(|seg| {
let p = pos;
pos += seg.original_size();
pos += seg.original_size;
p
})
.collect();
@@ -300,9 +313,9 @@ impl<T: Read + Seek + std::fmt::Debug> Entry<T> {
}
}
impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for Entry<T> {
impl ArchiveContent for Entry {
fn name(&self) -> &str {
&self.index.info().name()
&self.index.name
}
fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + 'a>> {
@@ -314,9 +327,9 @@ impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for Entry<T> {
}
}
impl<T: Read + Seek + std::fmt::Debug> Read for Entry<T> {
impl Read for Entry {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.pos >= self.index.info().file_size() {
if self.pos >= self.index.original_size {
self.cache.take();
return Ok(0);
}
@@ -338,50 +351,50 @@ impl<T: Read + Seek + std::fmt::Debug> Read for Entry<T> {
}
}
};
let seg = &self.index.segments()[seg_index];
let start_pos = seg.data_offset();
let seg = &self.index.segments[seg_index];
let start_pos = seg.start;
let seg_pos = self.entries_pos[seg_index];
let skip_pos = self.pos - seg_pos;
let read_size = seg.saved_size();
match seg.flag() {
IndexSegmentFlag::UnCompressed => {
let mut lock = MutexWrapper::new(self.reader.clone(), start_pos + skip_pos);
let readed = (&mut lock).take(read_size - skip_pos).read(buf)?;
self.pos += readed as u64;
Ok(readed)
}
IndexSegmentFlag::Compressed => {
let mut cache = ZlibDecoder::new(
MutexWrapper::new(self.reader.clone(), start_pos).take(read_size),
);
if skip_pos != 0 {
let mut e = EmptyWriter::new();
std::io::copy(&mut (&mut cache).take(skip_pos), &mut e)?; // skip
}
let readed = cache.read(buf)?;
self.pos += readed as u64;
self.cache = Some(cache);
Ok(readed)
let read_size = seg.archived_size;
if seg.is_compressed {
let mut inner = MutexWrapper::new(self.reader.clone(), start_pos).take(read_size);
let mut cache = if inner.peek_and_equal(ZSTD_SIGNATURE).is_ok() {
Box::new(ZstdDecoder::new(inner)?) as Box<dyn Read>
} else {
Box::new(ZlibDecoder::new(inner)) as Box<dyn Read>
};
if skip_pos != 0 {
let mut e = EmptyWriter::new();
std::io::copy(&mut (&mut cache).take(skip_pos), &mut e)?; // skip
}
let readed = cache.read(buf)?;
self.pos += readed as u64;
self.cache = Some(cache);
Ok(readed)
} else {
let mut lock = MutexWrapper::new(self.reader.clone(), start_pos + skip_pos);
let readed = (&mut lock).take(read_size - skip_pos).read(buf)?;
self.pos += readed as u64;
Ok(readed)
}
}
}
impl<T: Read + Seek + std::fmt::Debug> Seek for Entry<T> {
impl Seek for Entry {
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() {
if (-offset) as u64 > self.index.original_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
self.index.original_size - (-offset) as u64
} else {
self.index.info().file_size() + offset as u64
self.index.original_size + offset as u64
}
}
SeekFrom::Current(offset) => {
@@ -446,42 +459,42 @@ impl<T: Read + Seek + std::fmt::Debug> Seek for Entry<T> {
}
}
struct SimpleCryptZlib<T: Read + Seek + std::fmt::Debug> {
inner: PrefixStream<ZlibDecoder<StreamRegion<Entry<T>>>>,
index: XP3FileIndex,
struct SimpleCryptZlib {
inner: PrefixStream<ZlibDecoder<StreamRegion<Entry>>>,
index: archive::Xp3Entry,
}
impl<T: Read + Seek + std::fmt::Debug> SimpleCryptZlib<T> {
fn new(mut entry: Entry<T>, index: XP3FileIndex) -> Result<Self> {
impl SimpleCryptZlib {
fn new(mut entry: Entry, index: archive::Xp3Entry) -> Result<Self> {
entry.seek(SeekFrom::Start(0x15))?;
let entry = StreamRegion::new(entry, 0x15, index.info().file_size())?;
let entry = StreamRegion::new(entry, 0x15, index.original_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> {
impl ArchiveContent for SimpleCryptZlib {
fn name(&self) -> &str {
&self.index.info().name()
&self.index.name
}
}
impl<T: Read + Seek + std::fmt::Debug> Read for SimpleCryptZlib<T> {
impl Read for SimpleCryptZlib {
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>>,
struct SimpleCryptInner {
inner: StreamRegion<Entry>,
crypt: u8,
}
impl<T: Read + Seek + std::fmt::Debug> SimpleCryptInner<T> {
fn new(mut entry: Entry<T>, crypt: u8) -> Result<Self> {
impl SimpleCryptInner {
fn new(mut entry: Entry, crypt: u8) -> Result<Self> {
entry.seek(SeekFrom::Start(5))?;
let size = entry.index.info().file_size();
let size = entry.index.original_size;
let entry = StreamRegion::new(entry, 5, size)?;
Ok(Self {
inner: entry,
@@ -490,7 +503,7 @@ impl<T: Read + Seek + std::fmt::Debug> SimpleCryptInner<T> {
}
}
impl<T: Read + Seek + std::fmt::Debug> Read for SimpleCryptInner<T> {
impl Read for SimpleCryptInner {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let readed = self.inner.read(buf)?;
match self.crypt {
@@ -515,7 +528,7 @@ impl<T: Read + Seek + std::fmt::Debug> Read for SimpleCryptInner<T> {
}
}
impl<T: Read + Seek + std::fmt::Debug> Seek for SimpleCryptInner<T> {
impl Seek for SimpleCryptInner {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
@@ -530,21 +543,21 @@ impl<T: Read + Seek + std::fmt::Debug> Seek for SimpleCryptInner<T> {
}
#[derive(Debug)]
struct SimpleCrypt<T: Read + Seek + std::fmt::Debug> {
inner: PrefixStream<SimpleCryptInner<T>>,
index: XP3FileIndex,
struct SimpleCrypt {
inner: PrefixStream<SimpleCryptInner>,
index: archive::Xp3Entry,
}
impl<T: Read + Seek + std::fmt::Debug> SimpleCrypt<T> {
fn new(entry: Entry<T>, index: XP3FileIndex, crypt: u8) -> Result<Self> {
impl SimpleCrypt {
fn new(entry: Entry, index: archive::Xp3Entry, 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> {
impl ArchiveContent for SimpleCrypt {
fn name(&self) -> &str {
&self.index.info().name()
&self.index.name
}
fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + 'a>> {
@@ -552,13 +565,13 @@ impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for SimpleCrypt<T> {
}
}
impl<T: Read + Seek + std::fmt::Debug> Read for SimpleCrypt<T> {
impl Read for SimpleCrypt {
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> {
impl Seek for SimpleCrypt {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
@@ -573,27 +586,27 @@ impl<T: Read + Seek + std::fmt::Debug> Seek for SimpleCrypt<T> {
}
#[derive(Debug)]
struct MdfEntry<T: Read + Seek + std::fmt::Debug> {
inner: ZlibDecoder<StreamRegion<Entry<T>>>,
index: XP3FileIndex,
struct MdfEntry {
inner: ZlibDecoder<StreamRegion<Entry>>,
index: archive::Xp3Entry,
}
impl<T: Read + Seek + std::fmt::Debug> MdfEntry<T> {
fn new(mut entry: Entry<T>, index: XP3FileIndex) -> Result<Self> {
impl MdfEntry {
fn new(mut entry: Entry, index: archive::Xp3Entry) -> Result<Self> {
entry.seek(SeekFrom::Start(8))?;
let entry = StreamRegion::new(entry, 8, index.info().file_size())?;
let entry = StreamRegion::new(entry, 8, index.original_size)?;
let inner = ZlibDecoder::new(entry);
Ok(Self { inner, index })
}
}
impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for MdfEntry<T> {
impl ArchiveContent for MdfEntry {
fn name(&self) -> &str {
&self.index.info().name()
&self.index.name
}
}
impl<T: Read + Seek + std::fmt::Debug> Read for MdfEntry<T> {
impl Read for MdfEntry {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inner.read(buf)
}

View File

@@ -0,0 +1,181 @@
use super::archive::*;
use super::consts::*;
use super::crypt::*;
use crate::ext::io::*;
use crate::types::*;
use anyhow::Result;
use std::io::{Read, Seek, SeekFrom};
use std::sync::{Arc, Mutex};
impl Xp3Archive {
pub fn new<T: Read + Seek + std::fmt::Debug + 'static>(
stream: T,
_config: &ExtraConfig,
) -> Result<Self> {
let crypt: Box<dyn Crypt> = Box::new(NoCrypt::new());
let crypt = Arc::new(crypt);
let mut stream = Box::new(stream);
let base_offset = 0;
if base_offset != 0 {
stream.seek(SeekFrom::Start(base_offset))?;
}
stream
.read_and_equal(XP3_MAGIC)
.map_err(|e| anyhow::anyhow!("Invalid xp3 signature: {}", e))?;
let mut index_offset = stream.read_u64()?;
let mut minor_version = 0;
if index_offset == TVP_XP3_CURRENT_HEADER_VERSION {
minor_version = stream.read_u32()?;
let sig = stream.read_u8()?;
if sig != TVP_XP3_INDEX_CONTINUE {
anyhow::bail!("Unsupported XP3 index format: {} is not continue flag", sig);
}
let index_offset_offset = stream.read_i64()?;
if index_offset_offset != 0 {
stream.seek_relative(index_offset_offset)?;
}
index_offset = stream.read_u64()?;
}
index_offset += base_offset;
stream.seek(SeekFrom::Start(index_offset))?;
let mut entries = Vec::new();
let mut extras = Vec::new();
{
let mut index_stream = Self::get_index_stream(&mut stream)?;
let mut sig = [0u8; 4];
loop {
let readed = index_stream.read_most(&mut sig)?;
if readed == 0 {
break;
}
if readed < 4 {
anyhow::bail!("Invalid chunk signature in index");
}
let mut size = index_stream.read_u64()?;
if &sig == CHUNK_FILE {
let mut name = None;
let mut flags = None;
let mut file_hash = None;
let mut original_size = None;
let mut archived_size = None;
let mut segments = Vec::new();
let mut seg_offset = 0;
let mut entry_extras = Vec::new();
while size > 0 {
if size < 12 {
anyhow::bail!("Invalid chunk size in index");
}
let mut chunk_sig = [0u8; 4];
index_stream.read_exact(&mut chunk_sig)?;
let mut chunk_size = index_stream.read_u64()?;
size -= 12;
if size < chunk_size {
anyhow::bail!("Invalid chunk size in index");
}
size -= chunk_size;
if &chunk_sig == CHUNK_INFO {
if chunk_size < 20 {
anyhow::bail!("Invalid info chunk size in index");
}
flags = Some(index_stream.read_u32()?);
original_size = Some(index_stream.read_u64()?);
archived_size = Some(index_stream.read_u64()?);
chunk_size -= 20;
let (n, s) = crypt.read_name(&mut index_stream)?;
name = Some(n);
chunk_size -= s;
} else if &chunk_sig == CHUNK_ADLR {
if chunk_size == 4 {
file_hash = Some(index_stream.read_u32()?);
chunk_size -= 4;
}
} else if &chunk_sig == CHUNK_SEGM {
while chunk_size > 0 {
if chunk_size < 0x1C {
anyhow::bail!("Invalid segm chunk size in index");
}
let seg_flags = index_stream.read_u32()?;
let start = index_stream.read_u64()?;
let original_size = index_stream.read_u64()?;
let archived_size = index_stream.read_u64()?;
chunk_size -= 0x1C;
segments.push(Segment {
is_compressed: seg_flags != 0,
start,
offset_in_file: seg_offset,
original_size,
archived_size,
});
seg_offset += original_size;
}
} else {
let data = index_stream.read_exact_vec(chunk_size as usize)?;
chunk_size = 0;
entry_extras.push(ExtraProp {
tag: chunk_sig,
data,
});
}
if chunk_size > 0 {
index_stream.skip(chunk_size)?;
}
}
entries.push(Xp3Entry {
name: name
.ok_or_else(|| anyhow::anyhow!("Missing name chunk in file entry"))?,
flags: flags
.ok_or_else(|| anyhow::anyhow!("Missing flags chunk in file entry"))?,
file_hash: file_hash.unwrap_or(0),
original_size: original_size.ok_or_else(|| {
anyhow::anyhow!("Missing original size chunk in file entry")
})?,
archived_size: archived_size.ok_or_else(|| {
anyhow::anyhow!("Missing archived size chunk in file entry")
})?,
segments,
extras: entry_extras,
});
} else {
let data = index_stream.read_exact_vec(size as usize)?;
extras.push(ExtraProp { tag: sig, data });
}
}
}
let mut archive = Self {
inner: Arc::new(Mutex::new(stream)),
crypt: crypt.clone(),
base_offset,
index_offset,
minor_version,
entries,
extras,
};
crypt.init(&mut archive)?;
Ok(archive)
}
fn get_index_stream<'a, T: Read + Seek + std::fmt::Debug + 'static>(
stream: &'a mut Box<T>,
) -> Result<Box<dyn Read + 'a>> {
let index_type = stream.read_u8()?;
Ok(match index_type {
TVP_XP3_INDEX_ENCODE_RAW => {
let index_size = stream.read_u64()?;
Box::new(StreamRegion::with_size(stream, index_size)?)
}
TVP_XP3_INDEX_ENCODE_ZLIB => {
let packed_size = stream.read_u64()?;
let _original_size = stream.read_u64()?;
let mut compressed_data = StreamRegion::with_size(stream, packed_size)?;
if compressed_data.peek_and_equal(ZSTD_SIGNATURE).is_ok() {
Box::new(zstd::stream::read::Decoder::new(compressed_data)?)
} else {
Box::new(flate2::read::ZlibDecoder::new(compressed_data))
}
}
_ => {
anyhow::bail!("Unsupported index type: {}", index_type);
}
})
}
}

View File

@@ -1,24 +0,0 @@
/// Represents a single data segment for a file.
/// A file can be split into multiple segments, which can be compressed independently.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Segment {
pub is_compressed: bool,
/// The offset of the segment's data within the archive file.
pub start: u64,
/// The offset of this segment within the original, uncompressed file.
pub offset_in_file: u64,
/// The size of the segment after decompression.
pub original_size: u64,
/// The size of the segment in the archive (potentially compressed).
pub archived_size: u64,
}
/// Represents a single file entry within the XP3 archive.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ArchiveItem {
pub name: String,
pub file_hash: u32,
pub original_size: u64,
pub archived_size: u64,
pub segments: Vec<Segment>,
}

View File

@@ -1,9 +0,0 @@
mod archive;
#[allow(dead_code)]
mod consts;
mod reader;
mod segmenter;
mod writer;
pub use segmenter::SegmenterConfig;
pub use writer::Xp3ArchiveWriter;