mirror of
https://github.com/xavo95/repak.git
synced 2025-01-18 19:04:07 +00:00
Implement v8b PakWriter
This commit is contained in:
parent
be58d245e8
commit
ed653969a7
7 changed files with 369 additions and 75 deletions
|
@ -18,6 +18,7 @@ aes = "0.8"
|
||||||
flate2 = "1.0"
|
flate2 = "1.0"
|
||||||
hashbrown = "0.13"
|
hashbrown = "0.13"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
sha1 = "0.10.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub fn unpack(path: String, key: Option<String>) -> Result<(), unpak::Error> {
|
||||||
let mut pak = super::load_pak(path.clone(), key)?;
|
let mut pak = super::load_pak(path.clone(), key)?;
|
||||||
for file in pak.files() {
|
for file in pak.files() {
|
||||||
std::fs::create_dir_all(folder.join(&file).parent().expect("will be a file"))?;
|
std::fs::create_dir_all(folder.join(&file).parent().expect("will be a file"))?;
|
||||||
match pak.read(&file, &mut std::fs::File::create(folder.join(&file))?) {
|
match pak.read_file(&file, &mut std::fs::File::create(folder.join(&file))?) {
|
||||||
Ok(_) => println!("{file}"),
|
Ok(_) => println!("{file}"),
|
||||||
Err(e) => eprintln!("{e}"),
|
Err(e) => eprintln!("{e}"),
|
||||||
}
|
}
|
||||||
|
|
92
src/entry.rs
92
src/entry.rs
|
@ -1,7 +1,13 @@
|
||||||
use super::{ext::ReadExt, Compression, Version, VersionMajor};
|
use super::{ext::ReadExt, ext::WriteExt, Compression, Version, VersionMajor};
|
||||||
use byteorder::{ReadBytesExt, LE};
|
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EntryLocation {
|
||||||
|
Data,
|
||||||
|
Index,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
pub start: u64,
|
pub start: u64,
|
||||||
|
@ -9,12 +15,18 @@ pub struct Block {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Block {
|
impl Block {
|
||||||
pub fn new<R: io::Read>(reader: &mut R) -> Result<Self, super::Error> {
|
pub fn read<R: io::Read>(reader: &mut R) -> Result<Self, super::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
start: reader.read_u64::<LE>()?,
|
start: reader.read_u64::<LE>()?,
|
||||||
end: reader.read_u64::<LE>()?,
|
end: reader.read_u64::<LE>()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write<W: io::Write>(&self, writer: &mut W) -> Result<(), super::Error> {
|
||||||
|
writer.write_u64::<LE>(self.start)?;
|
||||||
|
writer.write_u64::<LE>(self.end)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn align(offset: u64) -> u64 {
|
fn align(offset: u64) -> u64 {
|
||||||
|
@ -66,7 +78,10 @@ impl Entry {
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new<R: io::Read>(reader: &mut R, version: super::Version) -> Result<Self, super::Error> {
|
pub fn read<R: io::Read>(
|
||||||
|
reader: &mut R,
|
||||||
|
version: super::Version,
|
||||||
|
) -> Result<Self, super::Error> {
|
||||||
// since i need the compression flags, i have to store these as variables which is mildly annoying
|
// since i need the compression flags, i have to store these as variables which is mildly annoying
|
||||||
let offset = reader.read_u64::<LE>()?;
|
let offset = reader.read_u64::<LE>()?;
|
||||||
let compressed = reader.read_u64::<LE>()?;
|
let compressed = reader.read_u64::<LE>()?;
|
||||||
|
@ -92,7 +107,7 @@ impl Entry {
|
||||||
blocks: match version.version_major() >= VersionMajor::CompressionEncryption
|
blocks: match version.version_major() >= VersionMajor::CompressionEncryption
|
||||||
&& compression != Compression::None
|
&& compression != Compression::None
|
||||||
{
|
{
|
||||||
true => Some(reader.read_array(Block::new)?),
|
true => Some(reader.read_array(Block::read)?),
|
||||||
false => None,
|
false => None,
|
||||||
},
|
},
|
||||||
encrypted: version.version_major() >= VersionMajor::CompressionEncryption
|
encrypted: version.version_major() >= VersionMajor::CompressionEncryption
|
||||||
|
@ -104,8 +119,50 @@ impl Entry {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub fn write<W: io::Write>(
|
||||||
|
&self,
|
||||||
|
writer: &mut W,
|
||||||
|
version: super::Version,
|
||||||
|
location: EntryLocation,
|
||||||
|
) -> Result<(), super::Error> {
|
||||||
|
writer.write_u64::<LE>(match location {
|
||||||
|
EntryLocation::Data => 0,
|
||||||
|
EntryLocation::Index => self.offset,
|
||||||
|
})?;
|
||||||
|
writer.write_u64::<LE>(self.compressed)?;
|
||||||
|
writer.write_u64::<LE>(self.uncompressed)?;
|
||||||
|
let compression: u8 = match self.compression {
|
||||||
|
Compression::None => 0,
|
||||||
|
Compression::Zlib => 1,
|
||||||
|
Compression::Gzip => todo!(),
|
||||||
|
Compression::Oodle => todo!(),
|
||||||
|
};
|
||||||
|
match version {
|
||||||
|
Version::V8A => writer.write_u8(compression)?,
|
||||||
|
_ => writer.write_u32::<LE>(compression.into())?,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_encoded<R: io::Read>(
|
if version.version_major() == VersionMajor::Initial {
|
||||||
|
writer.write_u64::<LE>(self.timestamp.unwrap_or_default())?;
|
||||||
|
}
|
||||||
|
if let Some(hash) = self.hash {
|
||||||
|
writer.write_all(&hash)?;
|
||||||
|
} else {
|
||||||
|
panic!("hash missing");
|
||||||
|
}
|
||||||
|
if version.version_major() >= VersionMajor::CompressionEncryption {
|
||||||
|
if let Some(blocks) = &self.blocks {
|
||||||
|
for block in blocks {
|
||||||
|
block.write(writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.write_bool(self.encrypted)?;
|
||||||
|
writer.write_u32::<LE>(self.block_uncompressed.unwrap_or_default())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_encoded<R: io::Read>(
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
version: super::Version,
|
version: super::Version,
|
||||||
) -> Result<Self, super::Error> {
|
) -> Result<Self, super::Error> {
|
||||||
|
@ -194,7 +251,7 @@ impl Entry {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R: io::Read + io::Seek, W: io::Write>(
|
pub fn read_file<R: io::Read + io::Seek, W: io::Write>(
|
||||||
&self,
|
&self,
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
version: Version,
|
version: Version,
|
||||||
|
@ -202,7 +259,7 @@ impl Entry {
|
||||||
buf: &mut W,
|
buf: &mut W,
|
||||||
) -> Result<(), super::Error> {
|
) -> Result<(), super::Error> {
|
||||||
reader.seek(io::SeekFrom::Start(self.offset))?;
|
reader.seek(io::SeekFrom::Start(self.offset))?;
|
||||||
Entry::new(reader, version)?;
|
Entry::read(reader, version)?;
|
||||||
let data_offset = reader.stream_position()?;
|
let data_offset = reader.stream_position()?;
|
||||||
let mut data = reader.read_len(match self.encrypted {
|
let mut data = reader.read_len(match self.encrypted {
|
||||||
true => align(self.compressed),
|
true => align(self.compressed),
|
||||||
|
@ -258,3 +315,22 @@ impl Entry {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_entry() {
|
||||||
|
let data = vec![
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x54, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0xDD, 0x94, 0xFD, 0xC3, 0x5F, 0xF5, 0x91, 0xA9, 0x9A, 0x5E, 0x14, 0xDC, 0x9B,
|
||||||
|
0xD3, 0x58, 0x89, 0x78, 0xA6, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
let mut out = vec![];
|
||||||
|
let entry = super::Entry::read(&mut std::io::Cursor::new(data.clone()), super::Version::V5)
|
||||||
|
.unwrap();
|
||||||
|
entry
|
||||||
|
.write(&mut out, super::Version::V5, super::EntryLocation::Data)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(&data, &out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
26
src/ext.rs
26
src/ext.rs
|
@ -1,4 +1,4 @@
|
||||||
use byteorder::{ReadBytesExt, LE};
|
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||||
|
|
||||||
pub trait ReadExt {
|
pub trait ReadExt {
|
||||||
fn read_bool(&mut self) -> Result<bool, super::Error>;
|
fn read_bool(&mut self) -> Result<bool, super::Error>;
|
||||||
|
@ -11,6 +11,11 @@ pub trait ReadExt {
|
||||||
fn read_len(&mut self, len: usize) -> Result<Vec<u8>, super::Error>;
|
fn read_len(&mut self, len: usize) -> Result<Vec<u8>, super::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait WriteExt {
|
||||||
|
fn write_bool(&mut self, value: bool) -> Result<(), super::Error>;
|
||||||
|
fn write_string(&mut self, value: &str) -> Result<(), super::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
impl<R: std::io::Read> ReadExt for R {
|
impl<R: std::io::Read> ReadExt for R {
|
||||||
fn read_bool(&mut self) -> Result<bool, super::Error> {
|
fn read_bool(&mut self) -> Result<bool, super::Error> {
|
||||||
match self.read_u8()? {
|
match self.read_u8()? {
|
||||||
|
@ -37,7 +42,7 @@ impl<R: std::io::Read> ReadExt for R {
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_string(&mut self) -> Result<String, crate::Error> {
|
fn read_string(&mut self) -> Result<String, super::Error> {
|
||||||
let mut buf = match self.read_i32::<LE>()? {
|
let mut buf = match self.read_i32::<LE>()? {
|
||||||
size if size.is_negative() => {
|
size if size.is_negative() => {
|
||||||
let mut buf = Vec::with_capacity(-size as usize);
|
let mut buf = Vec::with_capacity(-size as usize);
|
||||||
|
@ -59,3 +64,20 @@ impl<R: std::io::Read> ReadExt for R {
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<W: std::io::Write> WriteExt for W {
|
||||||
|
fn write_bool(&mut self, value: bool) -> Result<(), super::Error> {
|
||||||
|
self.write_u8(match value {
|
||||||
|
true => 1,
|
||||||
|
false => 0,
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn write_string(&mut self, value: &str) -> Result<(), super::Error> {
|
||||||
|
let bytes = value.as_bytes();
|
||||||
|
self.write_u32::<LE>(bytes.len() as u32 + 1)?;
|
||||||
|
self.write_all(bytes)?;
|
||||||
|
self.write_u8(0)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
use crate::ext::WriteExt;
|
||||||
|
|
||||||
use super::{ext::ReadExt, Compression, Version, VersionMajor};
|
use super::{ext::ReadExt, Compression, Version, VersionMajor};
|
||||||
use byteorder::{ReadBytesExt, LE};
|
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -17,7 +19,7 @@ pub struct Footer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Footer {
|
impl Footer {
|
||||||
pub fn new<R: std::io::Read>(reader: &mut R, version: Version) -> Result<Self, super::Error> {
|
pub fn read<R: std::io::Read>(reader: &mut R, version: Version) -> Result<Self, super::Error> {
|
||||||
let footer = Self {
|
let footer = Self {
|
||||||
encryption_uuid: match version.version_major() >= VersionMajor::EncryptionKeyGuid {
|
encryption_uuid: match version.version_major() >= VersionMajor::EncryptionKeyGuid {
|
||||||
true => Some(reader.read_u128::<LE>()?),
|
true => Some(reader.read_u128::<LE>()?),
|
||||||
|
@ -66,4 +68,27 @@ impl Footer {
|
||||||
}
|
}
|
||||||
Ok(footer)
|
Ok(footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write<W: std::io::Write>(&self, writer: &mut W) -> Result<(), super::Error> {
|
||||||
|
if self.version_major >= VersionMajor::EncryptionKeyGuid {
|
||||||
|
writer.write_u128::<LE>(0)?;
|
||||||
|
}
|
||||||
|
if self.version_major >= VersionMajor::IndexEncryption {
|
||||||
|
writer.write_bool(self.encrypted)?;
|
||||||
|
}
|
||||||
|
writer.write_u32::<LE>(self.magic)?;
|
||||||
|
writer.write_u32::<LE>(self.version_major as u32)?;
|
||||||
|
writer.write_u64::<LE>(self.index_offset)?;
|
||||||
|
writer.write_u64::<LE>(self.index_size)?;
|
||||||
|
writer.write_all(&self.hash)?;
|
||||||
|
let algo_size = match self.version {
|
||||||
|
ver if ver < Version::V8A => 0,
|
||||||
|
ver if ver < Version::V8B => 4,
|
||||||
|
_ => 5,
|
||||||
|
};
|
||||||
|
for _ in 0..algo_size {
|
||||||
|
writer.write_all(&[0; 32])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
292
src/pak.rs
292
src/pak.rs
|
@ -1,20 +1,38 @@
|
||||||
|
use super::ext::{ReadExt, WriteExt};
|
||||||
use super::{Version, VersionMajor};
|
use super::{Version, VersionMajor};
|
||||||
use hashbrown::HashMap;
|
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||||
use std::io::{self, Seek};
|
use std::collections::BTreeMap;
|
||||||
|
use std::io::{self, Read, Seek, Write};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PakReader<R: io::Read + io::Seek> {
|
pub struct PakReader<R: Read + Seek> {
|
||||||
pak: Pak,
|
pak: Pak,
|
||||||
reader: R,
|
reader: R,
|
||||||
|
key: Option<aes::Aes256Dec>,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PakWriter<W: Write + Seek> {
|
||||||
|
pak: Pak,
|
||||||
|
writer: W,
|
||||||
|
key: Option<aes::Aes256Enc>,
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Pak {
|
pub struct Pak {
|
||||||
version: Version,
|
version: Version,
|
||||||
mount_point: String,
|
mount_point: String,
|
||||||
key: Option<aes::Aes256Dec>,
|
|
||||||
index: Index,
|
index: Index,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pak {
|
||||||
|
fn new(version: Version, mount_point: String) -> Self {
|
||||||
|
Pak {
|
||||||
|
version,
|
||||||
|
mount_point,
|
||||||
|
index: Index::new(version),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Index {
|
pub enum Index {
|
||||||
V1(IndexV1),
|
V1(IndexV1),
|
||||||
|
@ -22,26 +40,41 @@ pub enum Index {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index {
|
impl Index {
|
||||||
fn entries(&self) -> &HashMap<String, super::entry::Entry> {
|
fn new(version: Version) -> Self {
|
||||||
|
if version < Version::V10 {
|
||||||
|
Self::V1(IndexV1::default())
|
||||||
|
} else {
|
||||||
|
Self::V2(IndexV2::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entries(&self) -> &BTreeMap<String, super::entry::Entry> {
|
||||||
match self {
|
match self {
|
||||||
Index::V1(index) => &index.entries,
|
Index::V1(index) => &index.entries,
|
||||||
Index::V2(index) => &index.entries_by_path,
|
Index::V2(index) => &index.entries_by_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_entry(&mut self, path: &str, entry: super::entry::Entry) {
|
||||||
|
match self {
|
||||||
|
Index::V1(index) => index.entries.insert(path.to_string(), entry),
|
||||||
|
Index::V2(_index) => todo!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct IndexV1 {
|
pub struct IndexV1 {
|
||||||
entries: HashMap<String, super::entry::Entry>,
|
entries: BTreeMap<String, super::entry::Entry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct IndexV2 {
|
pub struct IndexV2 {
|
||||||
path_hash_seed: u64,
|
path_hash_seed: u64,
|
||||||
path_hash_index: Option<Vec<u8>>,
|
path_hash_index: Option<Vec<u8>>,
|
||||||
full_directory_index: Option<HashMap<String, HashMap<String, u32>>>,
|
full_directory_index: Option<BTreeMap<String, BTreeMap<String, u32>>>,
|
||||||
encoded_entries: Vec<u8>,
|
encoded_entries: Vec<u8>,
|
||||||
entries_by_path: HashMap<String, super::entry::Entry>,
|
entries_by_path: BTreeMap<String, super::entry::Entry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt(key: &Option<aes::Aes256Dec>, bytes: &mut [u8]) -> Result<(), super::Error> {
|
fn decrypt(key: &Option<aes::Aes256Dec>, bytes: &mut [u8]) -> Result<(), super::Error> {
|
||||||
|
@ -56,18 +89,12 @@ fn decrypt(key: &Option<aes::Aes256Dec>, bytes: &mut [u8]) -> Result<(), super::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: io::Seek + io::Read> PakReader<R> {
|
impl<R: Read + Seek> PakReader<R> {
|
||||||
pub fn into_reader(self) -> R {
|
|
||||||
self.reader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: io::Read + io::Seek> PakReader<R> {
|
|
||||||
pub fn new_any(mut reader: R, key: Option<aes::Aes256Dec>) -> Result<Self, super::Error> {
|
pub fn new_any(mut reader: R, key: Option<aes::Aes256Dec>) -> Result<Self, super::Error> {
|
||||||
for ver in Version::iter() {
|
for ver in Version::iter() {
|
||||||
match PakReader::new(&mut reader, ver, key.clone()) {
|
match Pak::read(&mut reader, ver, key.clone()) {
|
||||||
Ok(pak) => {
|
Ok(pak) => {
|
||||||
return Ok(PakReader { pak, reader });
|
return Ok(PakReader { pak, reader, key });
|
||||||
}
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
|
@ -75,16 +102,119 @@ impl<R: io::Read + io::Seek> PakReader<R> {
|
||||||
Err(super::Error::Other("version unsupported"))
|
Err(super::Error::Other("version unsupported"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_reader(self) -> R {
|
||||||
|
self.reader
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version(&self) -> super::Version {
|
||||||
|
self.pak.version
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mount_point(&self) -> &str {
|
||||||
|
&self.pak.mount_point
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&mut self, path: &str) -> Result<Vec<u8>, super::Error> {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
self.read_file(path, &mut data)?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_file<W: io::Write>(
|
||||||
|
&mut self,
|
||||||
|
path: &str,
|
||||||
|
writer: &mut W,
|
||||||
|
) -> Result<(), super::Error> {
|
||||||
|
match self.pak.index.entries().get(path) {
|
||||||
|
Some(entry) => entry.read_file(
|
||||||
|
&mut self.reader,
|
||||||
|
self.pak.version,
|
||||||
|
self.key.as_ref(),
|
||||||
|
writer,
|
||||||
|
),
|
||||||
|
None => Err(super::Error::Other("no file found at given path")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn files(&self) -> std::vec::IntoIter<String> {
|
||||||
|
self.pak
|
||||||
|
.index
|
||||||
|
.entries()
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write + io::Seek> PakWriter<W> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
writer: W,
|
||||||
|
key: Option<aes::Aes256Enc>,
|
||||||
|
version: Version,
|
||||||
|
mount_point: String,
|
||||||
|
) -> Self {
|
||||||
|
PakWriter {
|
||||||
|
pak: Pak::new(version, mount_point),
|
||||||
|
writer,
|
||||||
|
key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_writer(self) -> W {
|
||||||
|
self.writer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_file<R: Read>(&mut self, path: &str, reader: &mut R) -> Result<(), super::Error> {
|
||||||
|
let mut data = vec![];
|
||||||
|
reader.read_to_end(&mut data)?;
|
||||||
|
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
|
let mut hasher = Sha1::new();
|
||||||
|
hasher.update(&data);
|
||||||
|
|
||||||
|
let offset = self.writer.stream_position()?;
|
||||||
|
let len = data.len() as u64;
|
||||||
|
|
||||||
|
let entry = super::entry::Entry {
|
||||||
|
offset,
|
||||||
|
compressed: len,
|
||||||
|
uncompressed: len,
|
||||||
|
compression: super::Compression::None,
|
||||||
|
timestamp: None,
|
||||||
|
hash: Some(hasher.finalize().into()),
|
||||||
|
blocks: None,
|
||||||
|
encrypted: false,
|
||||||
|
block_uncompressed: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
entry.write(
|
||||||
|
&mut self.writer,
|
||||||
|
self.pak.version,
|
||||||
|
super::entry::EntryLocation::Data,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.pak.index.add_entry(path, entry);
|
||||||
|
|
||||||
|
self.writer.write_all(&data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_index(mut self) -> Result<W, super::Error> {
|
||||||
|
self.pak.write(&mut self.writer, self.key)?;
|
||||||
|
Ok(self.writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pak {
|
||||||
|
fn read<R: Read + Seek>(
|
||||||
mut reader: R,
|
mut reader: R,
|
||||||
version: super::Version,
|
version: super::Version,
|
||||||
key: Option<aes::Aes256Dec>,
|
key: Option<aes::Aes256Dec>,
|
||||||
) -> Result<Pak, super::Error> {
|
) -> Result<Self, super::Error> {
|
||||||
use super::ext::ReadExt;
|
|
||||||
use byteorder::{ReadBytesExt, LE};
|
|
||||||
// read footer to get index, encryption & compression info
|
// read footer to get index, encryption & compression info
|
||||||
reader.seek(io::SeekFrom::End(-version.size()))?;
|
reader.seek(io::SeekFrom::End(-version.size()))?;
|
||||||
let footer = super::footer::Footer::new(&mut reader, version)?;
|
let footer = super::footer::Footer::read(&mut reader, version)?;
|
||||||
// read index to get all the entry info
|
// read index to get all the entry info
|
||||||
reader.seek(io::SeekFrom::Start(footer.index_offset))?;
|
reader.seek(io::SeekFrom::Start(footer.index_offset))?;
|
||||||
let mut index = reader.read_len(footer.index_size as usize)?;
|
let mut index = reader.read_len(footer.index_size as usize)?;
|
||||||
|
@ -134,11 +264,11 @@ impl<R: io::Read + io::Seek> PakReader<R> {
|
||||||
let mut fdi = io::Cursor::new(full_directory_index);
|
let mut fdi = io::Cursor::new(full_directory_index);
|
||||||
|
|
||||||
let dir_count = fdi.read_u32::<LE>()? as usize;
|
let dir_count = fdi.read_u32::<LE>()? as usize;
|
||||||
let mut directories = HashMap::with_capacity(dir_count);
|
let mut directories = BTreeMap::new();
|
||||||
for _ in 0..dir_count {
|
for _ in 0..dir_count {
|
||||||
let dir_name = fdi.read_string()?;
|
let dir_name = fdi.read_string()?;
|
||||||
let file_count = fdi.read_u32::<LE>()? as usize;
|
let file_count = fdi.read_u32::<LE>()? as usize;
|
||||||
let mut files = HashMap::with_capacity(file_count);
|
let mut files = BTreeMap::new();
|
||||||
for _ in 0..file_count {
|
for _ in 0..file_count {
|
||||||
let file_name = fdi.read_string()?;
|
let file_name = fdi.read_string()?;
|
||||||
files.insert(file_name, fdi.read_u32::<LE>()?);
|
files.insert(file_name, fdi.read_u32::<LE>()?);
|
||||||
|
@ -152,14 +282,14 @@ impl<R: io::Read + io::Seek> PakReader<R> {
|
||||||
let size = index.read_u32::<LE>()? as usize;
|
let size = index.read_u32::<LE>()? as usize;
|
||||||
let encoded_entries = index.read_len(size)?;
|
let encoded_entries = index.read_len(size)?;
|
||||||
|
|
||||||
let mut entries_by_path = HashMap::new();
|
let mut entries_by_path = BTreeMap::new();
|
||||||
if let Some(fdi) = &full_directory_index {
|
if let Some(fdi) = &full_directory_index {
|
||||||
let mut encoded_entries = io::Cursor::new(&encoded_entries);
|
let mut encoded_entries = io::Cursor::new(&encoded_entries);
|
||||||
for (dir_name, dir) in fdi {
|
for (dir_name, dir) in fdi {
|
||||||
for (file_name, encoded_offset) in dir {
|
for (file_name, encoded_offset) in dir {
|
||||||
encoded_entries.seek(io::SeekFrom::Start(*encoded_offset as u64))?;
|
encoded_entries.seek(io::SeekFrom::Start(*encoded_offset as u64))?;
|
||||||
let entry =
|
let entry =
|
||||||
super::entry::Entry::new_encoded(&mut encoded_entries, version)?;
|
super::entry::Entry::read_encoded(&mut encoded_entries, version)?;
|
||||||
|
|
||||||
// entry next to file contains full metadata
|
// entry next to file contains full metadata
|
||||||
//reader.seek(io::SeekFrom::Start(entry.offset))?;
|
//reader.seek(io::SeekFrom::Start(entry.offset))?;
|
||||||
|
@ -186,11 +316,11 @@ impl<R: io::Read + io::Seek> PakReader<R> {
|
||||||
entries_by_path,
|
entries_by_path,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let mut entries = HashMap::with_capacity(len);
|
let mut entries = BTreeMap::new();
|
||||||
for _ in 0..len {
|
for _ in 0..len {
|
||||||
entries.insert(
|
entries.insert(
|
||||||
index.read_string()?,
|
index.read_string()?,
|
||||||
super::entry::Entry::new(&mut index, version)?,
|
super::entry::Entry::read(&mut index, version)?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Index::V1(IndexV1 { entries })
|
Index::V1(IndexV1 { entries })
|
||||||
|
@ -199,44 +329,84 @@ impl<R: io::Read + io::Seek> PakReader<R> {
|
||||||
Ok(Pak {
|
Ok(Pak {
|
||||||
version,
|
version,
|
||||||
mount_point,
|
mount_point,
|
||||||
key,
|
|
||||||
index,
|
index,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
fn write<W: Write + Seek>(
|
||||||
|
&self,
|
||||||
|
writer: &mut W,
|
||||||
|
_key: Option<aes::Aes256Enc>,
|
||||||
|
) -> Result<(), super::Error> {
|
||||||
|
let index_offset = writer.stream_position()?;
|
||||||
|
|
||||||
pub fn version(&self) -> super::Version {
|
let mut index_cur = std::io::Cursor::new(vec![]);
|
||||||
self.pak.version
|
index_cur.write_string(&self.mount_point)?;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mount_point(&self) -> &str {
|
match &self.index {
|
||||||
&self.pak.mount_point
|
Index::V1(index) => {
|
||||||
}
|
index_cur.write_u32::<LE>(index.entries.len() as u32)?;
|
||||||
|
for (path, entry) in &index.entries {
|
||||||
pub fn get(&mut self, path: &str) -> Result<Vec<u8>, super::Error> {
|
index_cur.write_string(path)?;
|
||||||
let mut data = Vec::new();
|
entry.write(
|
||||||
self.read(path, &mut data)?;
|
&mut index_cur,
|
||||||
Ok(data)
|
self.version,
|
||||||
}
|
super::entry::EntryLocation::Index,
|
||||||
|
)?;
|
||||||
pub fn read<W: io::Write>(&mut self, path: &str, writer: &mut W) -> Result<(), super::Error> {
|
}
|
||||||
match self.pak.index.entries().get(path) {
|
}
|
||||||
Some(entry) => entry.read(
|
Index::V2(_index) => todo!(),
|
||||||
&mut self.reader,
|
|
||||||
self.pak.version,
|
|
||||||
self.pak.key.as_ref(),
|
|
||||||
writer,
|
|
||||||
),
|
|
||||||
None => Err(super::Error::Other("no file found at given path")),
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn files(&self) -> std::vec::IntoIter<String> {
|
let index_data = index_cur.into_inner();
|
||||||
self.pak
|
|
||||||
.index
|
use sha1::{Digest, Sha1};
|
||||||
.entries()
|
let mut hasher = Sha1::new();
|
||||||
.keys()
|
hasher.update(&index_data);
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<String>>()
|
let footer = super::footer::Footer {
|
||||||
.into_iter()
|
encryption_uuid: None,
|
||||||
|
encrypted: false,
|
||||||
|
magic: super::MAGIC,
|
||||||
|
version: self.version,
|
||||||
|
version_major: self.version.version_major(),
|
||||||
|
index_offset,
|
||||||
|
index_size: index_data.len() as u64,
|
||||||
|
hash: hasher.finalize().into(),
|
||||||
|
frozen: false,
|
||||||
|
compression: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
writer.write_all(&index_data)?;
|
||||||
|
|
||||||
|
footer.write(writer)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn test_rewrite_pak() {
|
||||||
|
use std::io::Cursor;
|
||||||
|
let bytes = include_bytes!("../tests/packs/pack_v8b.pak");
|
||||||
|
|
||||||
|
let mut reader = super::PakReader::new_any(Cursor::new(bytes), None).unwrap();
|
||||||
|
let writer = Cursor::new(vec![]);
|
||||||
|
let mut pak_writer = super::PakWriter::new(
|
||||||
|
writer,
|
||||||
|
None,
|
||||||
|
super::Version::V8B,
|
||||||
|
reader.mount_point().to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
|
for path in reader.files() {
|
||||||
|
let data = reader.get(&path).unwrap();
|
||||||
|
pak_writer
|
||||||
|
.write_file(&path, &mut std::io::Cursor::new(data))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let out_bytes = pak_writer.write_index().unwrap().into_inner();
|
||||||
|
assert_eq!(bytes.to_vec(), out_bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ macro_rules! encryptindex {
|
||||||
for file in files {
|
for file in files {
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
let mut writer = std::io::Cursor::new(&mut buf);
|
let mut writer = std::io::Cursor::new(&mut buf);
|
||||||
pak.read(&file, &mut writer).unwrap();
|
pak.read_file(&file, &mut writer).unwrap();
|
||||||
match file.as_str() {
|
match file.as_str() {
|
||||||
"test.txt" => assert_eq!(buf, include_bytes!("pack/root/test.txt"), "test.txt incorrect contents"),
|
"test.txt" => assert_eq!(buf, include_bytes!("pack/root/test.txt"), "test.txt incorrect contents"),
|
||||||
"test.png" => assert_eq!(buf, include_bytes!("pack/root/test.png"), "test.png incorrect contents"),
|
"test.png" => assert_eq!(buf, include_bytes!("pack/root/test.png"), "test.png incorrect contents"),
|
||||||
|
|
Loading…
Reference in a new issue