Skip to content

Commit

Permalink
feat: start using league toolkit I guess
Browse files Browse the repository at this point in the history
  • Loading branch information
Crauzer committed Jul 17, 2024
1 parent 5dc4e74 commit ba11c7e
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 342 deletions.
185 changes: 135 additions & 50 deletions src-tauri/Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ serde_json = "1.0"
byteorder = "1.4.3"
thiserror = "1.0.49"
num_enum = "0.7.0"
flate2 = "1.0"
flate2 = "1.0.30"
zstd = "0.12.4"
tracing = "0.1.37"
tracing-subscriber = "0.3.17"
Expand All @@ -47,6 +47,7 @@ chrono = { version = "0.4.31", features = ["std", "clock"] }
tracing-appender = "0.2.3"
lazy_static = "1.4.0"
fst = { version = "0.4.7", features = ['levenshtein'] }
league-toolkit = { git = "https://github.com/LeagueToolkit/league-toolkit", branch = "main" }

[features]
# this feature is used for production builds or when `devPath` points to the filesystem
Expand Down
6 changes: 2 additions & 4 deletions src-tauri/src/api/wad/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod extract_wad_items;
mod search_wad;

pub use extract_wad_items::*;
use league_toolkit::core::wad::Wad;
pub use search_wad::*;

use super::{
Expand All @@ -11,10 +12,7 @@ use crate::core::wad;
use crate::core::wad::tree::{WadTree, WadTreeItem};
use crate::{
api::error::ApiError,
core::wad::{
tree::{WadTreeParent, WadTreePathable, WadTreeSelectable},
Wad,
},
core::wad::tree::{WadTreeParent, WadTreePathable, WadTreeSelectable},
state::{MountedWadsState, SettingsState, WadHashtableState},
utils::actions::emit_action_progress,
};
Expand Down
10 changes: 4 additions & 6 deletions src-tauri/src/api/wad/commands/extract_wad_items.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use std::iter;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::str::FromStr;

use color_eyre::eyre::{eyre, Context, ContextCompat};
use color_eyre::eyre::{Context, ContextCompat};
use itertools::Itertools;
use tauri::Manager;
use uuid::Uuid;

use crate::api::wad::commands::ApiError;
use crate::core::wad::tree::{WadTreeItem, WadTreeParent, WadTreePathable};
use crate::core::wad::{self, WadChunk};
use crate::core::wad::tree::{WadTreeItem, WadTreePathable};
use crate::core::wad::{self};
use crate::state::SettingsState;
use crate::utils::actions::emit_action_progress;
use crate::{MountedWadsState, WadHashtableState};
Expand Down
6 changes: 2 additions & 4 deletions src-tauri/src/api/wad/commands/search_wad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@ use std::{collections::HashMap, str, sync::Arc};
use color_eyre::eyre::{Context, ContextCompat};
use fst::{self, IntoStreamer, Streamer};
use itertools::Itertools;
use league_toolkit::core::wad::WadChunk;
use uuid::Uuid;

use crate::{
api::{
error::ApiError,
wad::{guess_file_kind, SearchWadResponse, SearchWadResponseItem},
},
core::wad::{
tree::{WadTreeItem, WadTreeParent, WadTreePathable},
WadChunk,
},
core::wad::tree::{WadTreeItem, WadTreePathable},
state::{MountedWadsState, WadHashtableState},
};

Expand Down
6 changes: 2 additions & 4 deletions src-tauri/src/api/wad/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod commands;

pub use commands::*;
use league_toolkit::core::wad::WadChunkCompression;

use std::path::Path;

Expand All @@ -9,10 +10,7 @@ use uuid::Uuid;

use crate::core::{
league_file::{get_league_file_kind_from_extension, LeagueFileKind},
wad::{
tree::{WadTreeDirectory, WadTreeFile, WadTreeItem, WadTreePathable},
WadChunk, WadChunkCompression,
},
wad::tree::{WadTreeDirectory, WadTreeFile, WadTreeItem, WadTreePathable},
};

#[derive(Serialize, Deserialize)]
Expand Down
27 changes: 0 additions & 27 deletions src-tauri/src/core/wad/error.rs

This file was deleted.

6 changes: 3 additions & 3 deletions src-tauri/src/core/wad/extractor.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::{
core::{
league_file::{get_extension_from_league_file_kind, identify_league_file, LeagueFileKind},
wad::{WadChunk, WadDecoder},
core::league_file::{
get_extension_from_league_file_kind, identify_league_file, LeagueFileKind,
},
state::WadHashtable,
};
use color_eyre::eyre::{self, Ok};
use eyre::Context;
use league_toolkit::core::wad::{WadChunk, WadDecoder};
use std::{
collections::HashMap,
ffi::OsStr,
Expand Down
234 changes: 1 addition & 233 deletions src-tauri/src/core/wad/mod.rs
Original file line number Diff line number Diff line change
@@ -1,237 +1,5 @@
use byteorder::{ReadBytesExt, LE};
use flate2::read::GzDecoder;
use memchr::memmem;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::{
collections::HashMap,
io::{BufReader, Read, Seek, SeekFrom},
vec,
};

mod error;
mod extractor;

pub mod tree;

pub use error::*;
pub use extractor::*;

#[derive(Debug)]
pub struct Wad<TSource: Read + Seek> {
chunks: HashMap<u64, WadChunk>,
source: TSource,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WadChunk {
path_hash: u64,
data_offset: usize,
compressed_size: usize,
uncompressed_size: usize,
compression_type: WadChunkCompression,
is_duplicated: bool,
frame_count: u8,
start_frame: u16,
checksum: u64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum WadChunkCompression {
None = 0,
GZip = 1,
Satellite = 2,
Zstd = 3,
ZstdMulti = 4,
}

const ZSTD_MAGIC: [u8; 4] = [0x28, 0xB5, 0x2F, 0xFD];

impl<TSource: Read + Seek> Wad<TSource> {
pub fn chunks(&self) -> &HashMap<u64, WadChunk> {
&self.chunks
}

pub fn mount(mut source: TSource) -> Result<Wad<TSource>, WadError> {
let mut reader = BufReader::new(&mut source);

// 0x5752 = "RW"
let magic = reader.read_u16::<LE>()?;
if magic != 0x5752 {
return Err(WadError::InvalidHeader {
expected: String::from("RW"),
actual: format!("0x{:x}", magic),
});
}

let major = reader.read_u8()?;
let minor = reader.read_u8()?;
if major > 3 {
return Err(WadError::InvalidVersion { major, minor });
}

if major == 2 {
let _ecdsa_length = reader.seek(SeekFrom::Current(1))?;
let _ecdsa_signature = reader.seek(SeekFrom::Current(83))?;
let _data_checksum = reader.seek(SeekFrom::Current(8))?;
} else if major == 3 {
let _ecdsa_signature = reader.seek(SeekFrom::Current(256))?;
let _data_checksum = reader.seek(SeekFrom::Current(8))?;
}

if major == 1 || major == 2 {
let _toc_start_offset = reader.seek(SeekFrom::Current(2))?;
let _toc_chunk_size = reader.seek(SeekFrom::Current(2))?;
}

let chunk_count = reader.read_i32::<LE>()? as usize;
let mut chunks = HashMap::<u64, WadChunk>::with_capacity(chunk_count);
for _ in 0..chunk_count {
let chunk = WadChunk::read(&mut reader).expect("failed to read chunk");
chunks
.insert(chunk.path_hash(), chunk)
.map_or(Ok(()), |chunk| {
Err(WadError::DuplicateChunk {
path_hash: chunk.path_hash(),
})
})?;
}

Ok(Wad { chunks, source })
}

pub fn decode<'wad>(&'wad mut self) -> (WadDecoder<'wad, TSource>, &HashMap<u64, WadChunk>) {
(
WadDecoder {
source: &mut self.source,
},
&self.chunks,
)
}
}

impl WadChunk {
fn read<R: Read>(reader: &mut BufReader<R>) -> Result<WadChunk, WadError> {
let path_hash = reader.read_u64::<LE>()?;
let data_offset = reader.read_u32::<LE>()? as usize;
let compressed_size = reader.read_i32::<LE>()? as usize;
let uncompressed_size = reader.read_i32::<LE>()? as usize;

let type_frame_count = reader.read_u8()?;
let frame_count = type_frame_count >> 4;
let compression_type = WadChunkCompression::try_from_primitive(type_frame_count & 0xF)
.expect("failed to read chunk compression");

let is_duplicated = reader.read_u8()? == 1;
let start_frame = reader.read_u16::<LE>()?;
let checksum = reader.read_u64::<LE>()?;

Ok(WadChunk {
path_hash,
data_offset,
compressed_size,
uncompressed_size,
compression_type,
is_duplicated,
frame_count,
start_frame,
checksum,
})
}

pub fn path_hash(&self) -> u64 {
self.path_hash
}
pub fn data_offset(&self) -> usize {
self.data_offset
}
pub fn compressed_size(&self) -> usize {
self.compressed_size
}
pub fn uncompressed_size(&self) -> usize {
self.uncompressed_size
}
pub fn compression_type(&self) -> WadChunkCompression {
self.compression_type
}
pub fn checksum(&self) -> u64 {
self.checksum
}
}

pub struct WadDecoder<'wad, TSource: Read + Seek> {
source: &'wad mut TSource,
}

impl<'wad, TSource> WadDecoder<'wad, TSource>
where
TSource: Read + Seek,
{
pub fn load_chunk_raw(&mut self, chunk: &WadChunk) -> Result<Box<[u8]>, WadError> {
let mut data = vec![0; chunk.compressed_size];

self.source
.seek(SeekFrom::Start(chunk.data_offset as u64))?;
self.source.read_exact(&mut data)?;

Ok(data.into_boxed_slice())
}
pub fn load_chunk_decompressed(&mut self, chunk: &WadChunk) -> Result<Box<[u8]>, WadError> {
match chunk.compression_type {
WadChunkCompression::None => self.load_chunk_raw(chunk),
WadChunkCompression::GZip => self.decode_gzip_chunk(chunk),
WadChunkCompression::Satellite => Err(WadError::Other(String::from(
"satellite chunks are not supported",
))),
WadChunkCompression::Zstd => self.decode_zstd_chunk(chunk),
WadChunkCompression::ZstdMulti => self.decode_zstd_multi_chunk(chunk),
}
}

fn decode_gzip_chunk(&mut self, chunk: &WadChunk) -> Result<Box<[u8]>, WadError> {
self.source
.seek(SeekFrom::Start(chunk.data_offset as u64))?;

let mut data = vec![0; chunk.uncompressed_size];
GzDecoder::new(&mut self.source).read_exact(&mut data)?;

Ok(data.into_boxed_slice())
}
fn decode_zstd_chunk(&mut self, chunk: &WadChunk) -> Result<Box<[u8]>, WadError> {
self.source
.seek(SeekFrom::Start(chunk.data_offset as u64))?;

let mut data: Vec<u8> = vec![0; chunk.uncompressed_size];
zstd::Decoder::new(&mut self.source)
.expect("failed to create zstd decoder")
.read_exact(&mut data)?;

Ok(data.into_boxed_slice())
}
fn decode_zstd_multi_chunk(&mut self, chunk: &WadChunk) -> Result<Box<[u8]>, WadError> {
let raw_data = self.load_chunk_raw(chunk)?;
let mut data: Vec<u8> = vec![0; chunk.uncompressed_size];

let zstd_magic_offset =
memmem::find(&raw_data, &ZSTD_MAGIC).ok_or(WadError::DecompressionFailure {
path_hash: chunk.path_hash,
reason: String::from("failed to find zstd magic"),
})?;

// copy raw uncompressed data which exists before first zstd frame
for (i, value) in raw_data[0..zstd_magic_offset].iter().enumerate() {
data[i] = *value;
}

// seek to start of first zstd frame
self.source.seek(SeekFrom::Start(
(chunk.data_offset + zstd_magic_offset) as u64,
))?;

// decode zstd data
zstd::Decoder::new(&mut self.source)
.expect("failed to create zstd decoder")
.read_exact(&mut data[zstd_magic_offset..])?;

Ok(data.into_boxed_slice())
}
}
Loading

0 comments on commit ba11c7e

Please sign in to comment.