mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-06 04:48:54 +08:00
Remove xp3 deps
This commit is contained in:
13
Cargo.lock
generated
13
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
pub mod xp3;
|
||||
mod xp3pack;
|
||||
|
||||
61
src/scripts/kirikiri/archive/xp3/archive.rs
Normal file
61
src/scripts/kirikiri/archive/xp3/archive.rs
Normal 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>,
|
||||
}
|
||||
@@ -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";
|
||||
34
src/scripts/kirikiri/archive/xp3/crypt.rs
Normal file
34
src/scripts/kirikiri/archive/xp3/crypt.rs
Normal 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 {}
|
||||
@@ -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)
|
||||
}
|
||||
181
src/scripts/kirikiri/archive/xp3/read.rs
Normal file
181
src/scripts/kirikiri/archive/xp3/read.rs
Normal 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);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user