Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
ad98236e06
|
|||
9d703833e4
|
|||
40f9ea4572
|
|||
3009d73726 | |||
|
108d8bce92 | ||
527354a3af
|
|||
f46a06948c
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
|
*.exe
|
||||||
|
*.zip
|
||||||
|
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -60,6 +60,15 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.15.11"
|
version = "0.15.11"
|
||||||
@@ -295,8 +304,9 @@ checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mhws-tex-decompressor"
|
name = "mhws-tex-decompressor"
|
||||||
version = "0.1.1"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"colored",
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
"eyre",
|
"eyre",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
@@ -494,7 +504,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "re-tex"
|
name = "re-tex"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/eigeen/re-tex.git?branch=main#8d0278fa1064791e2e58049c55f9c9b784b53d62"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"better_default",
|
"better_default",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@@ -514,8 +523,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ree-pak-core"
|
name = "ree-pak-core"
|
||||||
version = "0.3.1"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/eigeen/ree-pak-rs.git?branch=main#0f8e027f7dc60d3be950d403c8ae8c74c87b9798"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
13
Cargo.toml
13
Cargo.toml
@@ -1,18 +1,19 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mhws-tex-decompressor"
|
name = "mhws-tex-decompressor"
|
||||||
version = "0.1.1"
|
version = "0.1.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# # local development
|
# local development
|
||||||
# re-tex = { path = "../re-tex" }
|
re-tex = { path = "../re-tex" }
|
||||||
# ree-pak-core = { path = "../../ree-pak-rs/ree-pak-core" }
|
ree-pak-core = { path = "../../ree-pak-rs/ree-pak-core" }
|
||||||
|
|
||||||
re-tex = { git = "https://github.com/eigeen/re-tex.git", branch = "main" }
|
# re-tex = { git = "https://github.com/eigeen/re-tex.git", branch = "main" }
|
||||||
ree-pak-core = { git = "https://github.com/eigeen/ree-pak-rs.git", branch = "main" }
|
# ree-pak-core = { git = "https://github.com/eigeen/ree-pak-rs.git", branch = "main" }
|
||||||
|
|
||||||
dialoguer = "0.11"
|
dialoguer = "0.11"
|
||||||
eyre = "0.6"
|
eyre = "0.6"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
|
colored = "3.0"
|
||||||
|
143
src/main.rs
143
src/main.rs
@@ -9,23 +9,38 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use dialoguer::{Input, theme::ColorfulTheme};
|
use colored::Colorize;
|
||||||
|
use dialoguer::{Input, Select, theme::ColorfulTheme};
|
||||||
use indicatif::{HumanBytes, ProgressBar, ProgressStyle};
|
use indicatif::{HumanBytes, ProgressBar, ProgressStyle};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use re_tex::tex::Tex;
|
use re_tex::tex::Tex;
|
||||||
use ree_pak_core::{filename::FileNameTable, read::archive::PakArchiveReader, write::FileOptions};
|
use ree_pak_core::{
|
||||||
|
filename::{FileNameExt, FileNameTable},
|
||||||
|
pak::PakEntry,
|
||||||
|
read::archive::PakArchiveReader,
|
||||||
|
write::FileOptions,
|
||||||
|
};
|
||||||
|
|
||||||
const FILE_NAME_LIST: &[u8] = include_bytes!("../assets/MHWs_STM_Release.list.zst");
|
const FILE_NAME_LIST: &[u8] = include_bytes!("../assets/MHWs_STM_Release.list.zst");
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Version {}. Tool by @Eigeen", env!("CARGO_PKG_VERSION"));
|
std::panic::set_hook(Box::new(panic_hook));
|
||||||
|
|
||||||
|
println!("Version v{} - Tool by @Eigeen", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
if let Err(e) = main_entry() {
|
if let Err(e) = main_entry() {
|
||||||
eprintln!("Error: {e}");
|
eprintln!("{}: {}", "Error".red().bold(), e);
|
||||||
wait_for_exit();
|
wait_for_exit();
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
wait_for_exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn panic_hook(info: &std::panic::PanicHookInfo) {
|
||||||
|
eprintln!("{}: {}", "Panic".red().bold(), info);
|
||||||
|
wait_for_exit();
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_entry() -> eyre::Result<()> {
|
fn main_entry() -> eyre::Result<()> {
|
||||||
@@ -34,14 +49,33 @@ fn main_entry() -> eyre::Result<()> {
|
|||||||
.default("re_chunk_000.pak.sub_000.pak".to_string())
|
.default("re_chunk_000.pak.sub_000.pak".to_string())
|
||||||
.with_prompt("Input .pak file path")
|
.with_prompt("Input .pak file path")
|
||||||
.interact_text()
|
.interact_text()
|
||||||
.unwrap();
|
.unwrap()
|
||||||
|
.trim_matches(|c| c == '\"' || c == '\'')
|
||||||
|
.to_string();
|
||||||
|
|
||||||
println!("Input file: {}", input);
|
|
||||||
let input_path = Path::new(&input);
|
let input_path = Path::new(&input);
|
||||||
if !input_path.is_file() {
|
if !input_path.is_file() {
|
||||||
eyre::bail!("input file not exists.");
|
eyre::bail!("input file not exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FALSE_TRUE_SELECTION: [&str; 2] = ["False", "True"];
|
||||||
|
|
||||||
|
let use_full_package_mode = Select::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Package all files, including non-tex files (for replacing original files)")
|
||||||
|
.default(0)
|
||||||
|
.items(&FALSE_TRUE_SELECTION)
|
||||||
|
.interact()
|
||||||
|
.unwrap();
|
||||||
|
let use_full_package_mode = use_full_package_mode == 1;
|
||||||
|
|
||||||
|
let use_feature_clone = Select::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Clone feature flags from original file?")
|
||||||
|
.default(1)
|
||||||
|
.items(&FALSE_TRUE_SELECTION)
|
||||||
|
.interact()
|
||||||
|
.unwrap();
|
||||||
|
let use_feature_clone = use_feature_clone == 1;
|
||||||
|
|
||||||
println!("Loading embedded file name table...");
|
println!("Loading embedded file name table...");
|
||||||
let filename_table = FileNameTable::from_bytes(FILE_NAME_LIST)?;
|
let filename_table = FileNameTable::from_bytes(FILE_NAME_LIST)?;
|
||||||
|
|
||||||
@@ -54,17 +88,16 @@ fn main_entry() -> eyre::Result<()> {
|
|||||||
let archive_reader_mtx = Mutex::new(archive_reader);
|
let archive_reader_mtx = Mutex::new(archive_reader);
|
||||||
|
|
||||||
// filtered entries
|
// filtered entries
|
||||||
println!("Filtering entries...");
|
let entries = if use_full_package_mode {
|
||||||
let entries = pak_archive
|
pak_archive.entries().iter().collect::<Vec<_>>()
|
||||||
.entries()
|
} else {
|
||||||
.iter()
|
println!("Filtering entries...");
|
||||||
.filter(|entry| {
|
pak_archive
|
||||||
let Some(file_name) = filename_table.get_file_name(entry.hash()) else {
|
.entries()
|
||||||
return false;
|
.iter()
|
||||||
};
|
.filter(|entry| is_tex_file(entry.hash(), &filename_table))
|
||||||
file_name.get_name().ends_with(".tex.241106027")
|
.collect::<Vec<_>>()
|
||||||
})
|
};
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// new pak archive
|
// new pak archive
|
||||||
let output_path = input_path.with_extension("uncompressed.pak");
|
let output_path = input_path.with_extension("uncompressed.pak");
|
||||||
@@ -97,20 +130,37 @@ fn main_entry() -> eyre::Result<()> {
|
|||||||
let mut archive_reader = archive_reader_mtx.lock();
|
let mut archive_reader = archive_reader_mtx.lock();
|
||||||
archive_reader.owned_entry_reader(entry.clone())?
|
archive_reader.owned_entry_reader(entry.clone())?
|
||||||
};
|
};
|
||||||
let mut tex = Tex::from_reader(&mut entry_reader)?;
|
|
||||||
// decompress mipmaps
|
|
||||||
tex.batch_decompress()?;
|
|
||||||
|
|
||||||
let tex_bytes = tex.as_bytes()?;
|
if !is_tex_file(entry.hash(), &filename_table) {
|
||||||
bytes_written.fetch_add(tex_bytes.len() as usize, Ordering::SeqCst);
|
// plain file, just copy
|
||||||
|
let mut buf = vec![];
|
||||||
// save file
|
std::io::copy(&mut entry_reader, &mut buf)?;
|
||||||
let file_name = filename_table.get_file_name(entry.hash()).unwrap().clone();
|
|
||||||
{
|
|
||||||
let mut pak_writer = pak_writer_mtx.lock();
|
let mut pak_writer = pak_writer_mtx.lock();
|
||||||
pak_writer.start_file(file_name, FileOptions::default())?;
|
let write_bytes = write_to_pak(
|
||||||
pak_writer.write_all(&tex_bytes)?;
|
&mut pak_writer,
|
||||||
|
entry,
|
||||||
|
entry.hash(),
|
||||||
|
&buf,
|
||||||
|
use_feature_clone,
|
||||||
|
)?;
|
||||||
|
bytes_written.fetch_add(write_bytes, Ordering::SeqCst);
|
||||||
|
} else {
|
||||||
|
let mut tex = Tex::from_reader(&mut entry_reader)?;
|
||||||
|
// decompress mipmaps
|
||||||
|
tex.batch_decompress()?;
|
||||||
|
|
||||||
|
let tex_bytes = tex.as_bytes()?;
|
||||||
|
let mut pak_writer = pak_writer_mtx.lock();
|
||||||
|
let write_bytes = write_to_pak(
|
||||||
|
&mut pak_writer,
|
||||||
|
entry,
|
||||||
|
entry.hash(),
|
||||||
|
&tex_bytes,
|
||||||
|
use_feature_clone,
|
||||||
|
)?;
|
||||||
|
bytes_written.fetch_add(write_bytes, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
bar.inc(1);
|
bar.inc(1);
|
||||||
if bar.position() % 100 == 0 {
|
if bar.position() % 100 == 0 {
|
||||||
bar.set_message(
|
bar.set_message(
|
||||||
@@ -133,16 +183,45 @@ fn main_entry() -> eyre::Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bar.finish();
|
bar.finish();
|
||||||
println!("Done!");
|
println!("{}", "Done!".cyan().bold());
|
||||||
println!(
|
if !use_full_package_mode {
|
||||||
"You should rename the output file like `re_chunk_000.pak.sub_000.pak.patch_xxx.pak`, or manage it by your favorite mod manager."
|
println!(
|
||||||
);
|
"You should rename the output file like `re_chunk_000.pak.sub_000.pak.patch_xxx.pak`, or manage it by your favorite mod manager."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_tex_file(hash: u64, file_name_table: &FileNameTable) -> bool {
|
||||||
|
let Some(file_name) = file_name_table.get_file_name(hash) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
file_name.get_name().ends_with(".tex.241106027")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_pak<W>(
|
||||||
|
writer: &mut ree_pak_core::write::PakWriter<W>,
|
||||||
|
entry: &PakEntry,
|
||||||
|
file_name: impl FileNameExt,
|
||||||
|
data: &[u8],
|
||||||
|
use_feature_clone: bool,
|
||||||
|
) -> eyre::Result<usize>
|
||||||
|
where
|
||||||
|
W: io::Write + io::Seek,
|
||||||
|
{
|
||||||
|
let mut file_options = FileOptions::default();
|
||||||
|
if use_feature_clone {
|
||||||
|
file_options = file_options.with_unk_attr(*entry.unk_attr())
|
||||||
|
}
|
||||||
|
writer.start_file(file_name, file_options)?;
|
||||||
|
writer.write_all(data)?;
|
||||||
|
Ok(data.len())
|
||||||
|
}
|
||||||
|
|
||||||
fn wait_for_exit() {
|
fn wait_for_exit() {
|
||||||
let _: String = Input::with_theme(&ColorfulTheme::default())
|
let _: String = Input::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Press Enter to exit")
|
||||||
.allow_empty(true)
|
.allow_empty(true)
|
||||||
.interact_text()
|
.interact_text()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
Reference in New Issue
Block a user