From c280157d06bf35f5b3b9c1b4deb85bd61f76d2aa Mon Sep 17 00:00:00 2001 From: misieur Date: Thu, 11 Jun 2026 23:09:59 +0200 Subject: [PATCH 1/7] Enhance multithreading and add num_threads option --- java/rust/src/lib.rs | 13 +- .../main/java/dev/misieur/packobf/Native.java | 4 +- .../java/dev/misieur/packobf/PackOBF.java | 3 +- .../dev/misieur/packobf/options/Options.java | 4 +- .../packobf/progress/OptimizingProgress.java | 33 ++ .../misieur/packobf/progress/Progress.java | 7 +- packobf/src/file_parser.rs | 8 +- packobf/src/lib.rs | 284 +++++++++++------- packobf/src/options.rs | 5 + packobf_cli/src/main.rs | 51 +++- packobf_gui/src/main.rs | 9 + 11 files changed, 294 insertions(+), 127 deletions(-) create mode 100644 java/src/main/java/dev/misieur/packobf/progress/OptimizingProgress.java diff --git a/java/rust/src/lib.rs b/java/rust/src/lib.rs index 60625b9..023ee15 100644 --- a/java/rust/src/lib.rs +++ b/java/rust/src/lib.rs @@ -59,6 +59,10 @@ pub extern "system" fn Java_dev_misieur_packobf_Native_optimizeZip<'caller>( corrupt_png_files: env .get_field(&options, jni_str!("corruptPngFiles"), jni_sig!("Z"))? .z()?, + num_threads: Some( + env.get_field(&options, jni_str!("numThreads"), jni_sig!("I"))? + .i()? as usize, + ), } }; @@ -119,12 +123,17 @@ pub extern "system" fn Java_dev_misieur_packobf_Native_optimizeZip<'caller>( total: t, } => (1, c as i32, t as i32, None), Progress::Parsing { current: s } => (2, 0, 0, Some(s)), - Progress::Building { + Progress::Optimizing { current: s, index: i, total: t, } => (3, i as i32, t as i32, Some(s)), - Progress::Done => (4, 0, 0, None), + Progress::Building { + current: s, + index: i, + total: t, + } => (4, i as i32, t as i32, Some(s)), + Progress::Done => (5, 0, 0, None), }; let _ = env.with_local_frame(16, |env| { diff --git a/java/src/main/java/dev/misieur/packobf/Native.java b/java/src/main/java/dev/misieur/packobf/Native.java index adc421b..2d8479a 100644 --- a/java/src/main/java/dev/misieur/packobf/Native.java +++ b/java/src/main/java/dev/misieur/packobf/Native.java @@ -14,12 +14,13 @@ private Native() { static class Options { - public Options(int compression, int shaderCompression, boolean renameFiles, boolean blockUnzipping, boolean corruptPngFiles) { + public Options(int compression, int shaderCompression, boolean renameFiles, boolean blockUnzipping, boolean corruptPngFiles, int numThreads) { this.compression = compression; this.shaderCompression = shaderCompression; this.renameFiles = renameFiles; this.blockUnzipping = blockUnzipping; this.corruptPngFiles = corruptPngFiles; + this.numThreads = numThreads; } public int compression; @@ -27,6 +28,7 @@ public Options(int compression, int shaderCompression, boolean renameFiles, bool public boolean renameFiles; public boolean blockUnzipping; public boolean corruptPngFiles; + public int numThreads; } interface LogCallback { diff --git a/java/src/main/java/dev/misieur/packobf/PackOBF.java b/java/src/main/java/dev/misieur/packobf/PackOBF.java index 1aebf9b..cee4a19 100644 --- a/java/src/main/java/dev/misieur/packobf/PackOBF.java +++ b/java/src/main/java/dev/misieur/packobf/PackOBF.java @@ -42,7 +42,8 @@ public static byte[] optimizeZip( options.shaderCompression().value, options.renameFiles(), options.blockUnzipping(), - options.corruptPngFiles() + options.corruptPngFiles(), + options.numThreads() != null ? options.numThreads() : 0 ), (level, message) -> logCallback.onLog(switch (level) { case 0 -> LogLevel.INFO; diff --git a/java/src/main/java/dev/misieur/packobf/options/Options.java b/java/src/main/java/dev/misieur/packobf/options/Options.java index e01c400..fbab4b3 100644 --- a/java/src/main/java/dev/misieur/packobf/options/Options.java +++ b/java/src/main/java/dev/misieur/packobf/options/Options.java @@ -1,6 +1,8 @@ package dev.misieur.packobf.options; -public record Options(Compression compression, ShaderCompression shaderCompression, boolean renameFiles, boolean blockUnzipping, boolean corruptPngFiles) { +import dev.misieur.packobf.annotations.Nullable; + +public record Options(Compression compression, ShaderCompression shaderCompression, boolean renameFiles, boolean blockUnzipping, boolean corruptPngFiles, @Nullable Integer numThreads) { public static Options simplest() { return new Options( Compression.SIMPLEST, diff --git a/java/src/main/java/dev/misieur/packobf/progress/OptimizingProgress.java b/java/src/main/java/dev/misieur/packobf/progress/OptimizingProgress.java new file mode 100644 index 0000000..224c5d2 --- /dev/null +++ b/java/src/main/java/dev/misieur/packobf/progress/OptimizingProgress.java @@ -0,0 +1,33 @@ +package dev.misieur.packobf.progress; + +public final class OptimizingProgress extends Progress { + /** + * The total number of files to optimize + */ + private final int total; + /** + * The current file that PackOBF started to optimize + */ + private final Current current; + + public OptimizingProgress(int total, Current current) { + this.total = total; + this.current = current; + } + + public int total() { + return total; + } + + public Current current() { + return current; + } + + @Override + public State state() { + return State.OPTIMIZING; + } + + public record Current(String name, int index) { + } +} diff --git a/java/src/main/java/dev/misieur/packobf/progress/Progress.java b/java/src/main/java/dev/misieur/packobf/progress/Progress.java index 369fbeb..a394751 100644 --- a/java/src/main/java/dev/misieur/packobf/progress/Progress.java +++ b/java/src/main/java/dev/misieur/packobf/progress/Progress.java @@ -1,6 +1,6 @@ package dev.misieur.packobf.progress; -public abstract sealed class Progress permits BuildingProgress, DoneProgress, IdleProgress, ParsingProgress, ReadingZipProgress { +public abstract sealed class Progress permits IdleProgress, ReadingZipProgress, ParsingProgress, OptimizingProgress, BuildingProgress, DoneProgress { private State state; public abstract State state(); @@ -9,8 +9,9 @@ public enum State { IDLE(0), READING_ZIP(1), PARSING(2), - BUILDING(3), - DONE(4); + OPTIMIZING(3), + BUILDING(4), + DONE(5); private final int value; diff --git a/packobf/src/file_parser.rs b/packobf/src/file_parser.rs index d1b6c19..0865651 100644 --- a/packobf/src/file_parser.rs +++ b/packobf/src/file_parser.rs @@ -16,6 +16,7 @@ use crate::{get_type, parse_path, LogMessage, Progress}; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use std::str::FromStr; use std::sync::Arc; +use rayon::ThreadPool; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::watch::Sender; use crate::resource_pack::files::unknowntexture::UnknownTexture; @@ -25,9 +26,12 @@ pub fn parse_resource_pack_files( entries: &mut Vec<(String, Vec)>, progress: Sender, pack: Arc, + thread_pool: &ThreadPool ) { - entries.par_iter_mut().for_each(move |(name, content)| { - parse_resource_pack_file(logger, &progress, &pack, name, content); + thread_pool.install(|| { + entries.par_iter_mut().for_each(move |(name, content)| { + parse_resource_pack_file(logger, &progress, &pack, name, content); + }); }); } diff --git a/packobf/src/lib.rs b/packobf/src/lib.rs index 61c1156..89d948d 100644 --- a/packobf/src/lib.rs +++ b/packobf/src/lib.rs @@ -13,7 +13,7 @@ pub mod utils; use crate::cache::Cache; use crate::optimized_zip_writer::OptimizedZipWriter; -use crate::options::Options; +use crate::options::{Options, ShaderCompression}; use crate::resource_pack::files::atlas::Atlas; use crate::resource_pack::files::blockstate::Blockstate; use crate::resource_pack::files::font::Font; @@ -25,19 +25,21 @@ use crate::resource_pack::files::shader::Shader; use crate::resource_pack::files::sound::Sound; use crate::resource_pack::files::sound_definitions::SoundDefinitions; use crate::resource_pack::files::texture::Texture; +use crate::resource_pack::files::unknowntexture::UnknownTexture; use crate::resource_pack::identifier::Identifier; use crate::resource_pack::mapping; use crate::resource_pack::mapping::{IdUsageCounter, Mapping}; use crate::resource_pack::pack::ResourcePack; use crate::LogLevel::Info; use rayon::prelude::*; -use std::io::{Cursor, Error, Read}; +use rayon::{ThreadPool, ThreadPoolBuilder}; +use std::error::Error; +use std::io::{Cursor, Read}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::watch::Sender; use zip::ZipArchive; -use crate::resource_pack::files::unknowntexture::UnknownTexture; pub fn process_zip( input_bytes: Vec, @@ -45,7 +47,7 @@ pub fn process_zip( progress: Sender, logger: &UnboundedSender, cache_file: &Option, -) -> zip::result::ZipResult> { +) -> Result, Box> { let _ = progress.send(Progress::Idle); #[cfg(feature = "profiling")] profiler::profiler::PROFILER.store(Arc::new(profiler::profiler::Profiler::new())); @@ -80,11 +82,16 @@ pub fn process_zip( let id_usage_counter = IdUsageCounter::default(); mapping::set_id_usage_counter(id_usage_counter); + let pool = ThreadPoolBuilder::new() + .num_threads(options.num_threads.unwrap_or(0)) + .build()?; + file_parser::parse_resource_pack_files( logger, &mut entries, progress.clone(), Arc::clone(&pack), + &pool, ); usage_checker::check_usage(logger, &pack); @@ -95,7 +102,7 @@ pub fn process_zip( } mapping::set_mappings(mapping); - let mut items = collect_files(pack); + let mut items = collect_files(pack, &pool); let total = items.len(); let mut output = Cursor::new(Vec::new()); @@ -123,18 +130,71 @@ pub fn process_zip( None }; - items.par_iter_mut().for_each(|(name, item)| { - match add_item_to_archive( - options, &progress, logger, total, &writer, &counter, &cache, name, item, - ) { - Ok(_) => {} - Err(e) => { - let _ = logger.send(LogMessage { - level: LogLevel::Error, - message: format!("Failed to add item to archive: {}", e), - }); + pool.install(|| { + let total_to_optimize = AtomicUsize::new(0); + let to_optimize: Vec<_> = items + .par_iter_mut() + .filter(|(_, item)| match item { + ResourcePackItem::Texture(_) => { + total_to_optimize.fetch_add(1, Ordering::Relaxed); + true + } + ResourcePackItem::UnknownTexture(_) => { + total_to_optimize.fetch_add(1, Ordering::Relaxed); + true + } + ResourcePackItem::Shader(_) => { + if options.shader_compression != ShaderCompression::None { + total_to_optimize.fetch_add(1, Ordering::Relaxed); + true + } else { + false + } + } + ResourcePackItem::Sound(_) => { + total_to_optimize.fetch_add(1, Ordering::Relaxed); + true + } + _ => false, + }) + .collect(); + + let total_to_optimize = to_optimize.len(); + to_optimize.into_par_iter().for_each(|(name, item)| { + let _ = progress.send(Progress::Optimizing { + current: name.to_string(), + index: counter.fetch_add(1, Ordering::Relaxed), + total: total_to_optimize, + }); + match item { + ResourcePackItem::Texture(x) => { + x.unknown_texture.optimize(options, logger, &cache); + } + ResourcePackItem::UnknownTexture(x) => { + x.optimize(options, logger, &cache); + } + ResourcePackItem::Shader(x) => { + x.optimize(options, logger); + } + ResourcePackItem::Sound(x) => { + x.optimize(logger, &cache); + } + _ => {} } - } + }); + items.par_iter_mut().for_each(|(name, item)| { + match add_item_to_archive( + options, &progress, logger, total, &writer, &counter, &cache, name, item, + ) { + Ok(_) => {} + Err(e) => { + let _ = logger.send(LogMessage { + level: LogLevel::Error, + message: format!("Failed to add item to archive: {}", e), + }); + } + } + }); }); writer.finish()?; @@ -161,7 +221,7 @@ fn add_item_to_archive( cache: &Option, name: &mut String, item: &mut ResourcePackItem, -) -> Result<(), Error> { +) -> Result<(), Box> { let _ = progress.send(Progress::Building { current: name.to_string(), index: counter.fetch_add(1, Ordering::Relaxed), @@ -185,10 +245,12 @@ fn add_item_to_archive( } } match item { - ResourcePackItem::Texture(o) => { - o.optimize(options, logger, cache); - writer.add_file(name.as_str(), o.unknown_texture.bytes.as_slice(), options, cache) - } + ResourcePackItem::Texture(o) => writer.add_file( + name.as_str(), + o.unknown_texture.bytes.as_slice(), + options, + cache, + ), ResourcePackItem::Shader(o) => { o.optimize(options, logger); writer.add_file(name.as_str(), o.content.as_bytes(), options, cache) @@ -215,7 +277,6 @@ fn add_item_to_archive( writer.add_file(name.as_str(), o.to_string().as_bytes(), options, cache) } ResourcePackItem::Sound(o) => { - o.optimize(logger, cache); writer.add_file(name.as_str(), o.bytes.as_slice(), options, cache) } ResourcePackItem::SoundDefinitions(o) => { @@ -225,99 +286,103 @@ fn add_item_to_archive( writer.add_file(name.as_str(), o.to_string().as_bytes(), options, cache) } ResourcePackItem::UnknownTexture(o) => { - o.optimize(options, logger, cache); writer.add_file(name.as_str(), o.bytes.as_slice(), options, cache) } }?; Ok(()) } -fn collect_files(pack: Arc) -> Vec<(String, ResourcePackItem)> { +fn collect_files( + pack: Arc, + thread_pool: &ThreadPool, +) -> Vec<(String, ResourcePackItem)> { profile_scope!("collect_files"); - let texture_iter = pack.textures.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::Texture(kv.value().clone()), - ) - }); - let unknown_texture_iter = pack.unknown_textures.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::UnknownTexture(kv.value().clone()), - ) - }); - let shader_iter = pack.shaders.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::Shader(kv.value().clone()), - ) - }); - let model_iter = pack.models.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::Model(kv.value().clone()), - ) - }); - let json_iter = pack - .json_files - .par_iter() - .map(|kv| (kv.key().clone(), ResourcePackItem::Json(kv.value().clone()))); - let unknown_iter = pack.unknown_files.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::Unknown(kv.value().clone()), - ) - }); - let blockstate_iter = pack.blockstates.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::BlockStateDefinition(kv.value().clone()), - ) - }); - let font_iter = pack.fonts.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::FontDefinition(kv.value().clone()), - ) - }); - let item_iter = pack.items.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::ItemDefinition(kv.value().clone()), - ) - }); - let sound_iter = pack.sounds.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::Sound(kv.value().clone()), - ) - }); - let sound_definitions_iter = pack.sound_definitions.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::SoundDefinitions(kv.value().clone()), - ) - }); - let atlas_iter = pack.atlases.par_iter().map(|kv| { - ( - kv.key().clone(), - ResourcePackItem::Atlas(kv.value().clone()), - ) - }); + thread_pool.install(|| { + let texture_iter = pack.textures.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::Texture(kv.value().clone()), + ) + }); + let unknown_texture_iter = pack.unknown_textures.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::UnknownTexture(kv.value().clone()), + ) + }); + let shader_iter = pack.shaders.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::Shader(kv.value().clone()), + ) + }); + let model_iter = pack.models.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::Model(kv.value().clone()), + ) + }); + let json_iter = pack + .json_files + .par_iter() + .map(|kv| (kv.key().clone(), ResourcePackItem::Json(kv.value().clone()))); + let unknown_iter = pack.unknown_files.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::Unknown(kv.value().clone()), + ) + }); + let blockstate_iter = pack.blockstates.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::BlockStateDefinition(kv.value().clone()), + ) + }); + let font_iter = pack.fonts.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::FontDefinition(kv.value().clone()), + ) + }); + let item_iter = pack.items.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::ItemDefinition(kv.value().clone()), + ) + }); + let sound_iter = pack.sounds.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::Sound(kv.value().clone()), + ) + }); + let sound_definitions_iter = pack.sound_definitions.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::SoundDefinitions(kv.value().clone()), + ) + }); + let atlas_iter = pack.atlases.par_iter().map(|kv| { + ( + kv.key().clone(), + ResourcePackItem::Atlas(kv.value().clone()), + ) + }); - texture_iter - .chain(unknown_texture_iter) - .chain(shader_iter) - .chain(model_iter) - .chain(json_iter) - .chain(unknown_iter) - .chain(blockstate_iter) - .chain(font_iter) - .chain(item_iter) - .chain(sound_iter) - .chain(sound_definitions_iter) - .chain(atlas_iter) - .collect() + texture_iter + .chain(unknown_texture_iter) + .chain(shader_iter) + .chain(model_iter) + .chain(json_iter) + .chain(unknown_iter) + .chain(blockstate_iter) + .chain(font_iter) + .chain(item_iter) + .chain(sound_iter) + .chain(sound_definitions_iter) + .chain(atlas_iter) + .collect() + }) } #[derive(Clone, Debug)] @@ -330,6 +395,11 @@ pub enum Progress { Parsing { current: String, }, + Optimizing { + current: String, + index: usize, + total: usize, + }, Building { current: String, index: usize, diff --git a/packobf/src/options.rs b/packobf/src/options.rs index f5687cc..ea61f99 100644 --- a/packobf/src/options.rs +++ b/packobf/src/options.rs @@ -15,6 +15,8 @@ pub struct Options { pub block_unzipping: bool, #[arg(long)] pub corrupt_png_files: bool, + #[arg(long)] + pub num_threads: Option, } #[derive(ValueEnum, Clone, Debug)] @@ -32,6 +34,7 @@ impl Options { rename_files: false, block_unzipping: false, corrupt_png_files: false, + num_threads: None, } } @@ -42,6 +45,7 @@ impl Options { rename_files: false, block_unzipping: false, corrupt_png_files: false, + num_threads: None, } } @@ -52,6 +56,7 @@ impl Options { rename_files: true, block_unzipping: true, corrupt_png_files: true, + num_threads: None, } } diff --git a/packobf_cli/src/main.rs b/packobf_cli/src/main.rs index fc66dd9..ee8e0a7 100644 --- a/packobf_cli/src/main.rs +++ b/packobf_cli/src/main.rs @@ -32,6 +32,7 @@ struct Args { static LOOKING_GLASS: Emoji<'_, '_> = Emoji("🔍 ", ""); static TRUCK: Emoji<'_, '_> = Emoji("🚚 ", ""); static CLIP: Emoji<'_, '_> = Emoji("🔗 ", ""); +static OPTIMIZING: Emoji<'_, '_> = Emoji("🚀 ", ""); static BUILDING: Emoji<'_, '_> = Emoji("⚒️ ", ""); static SPARKLE: Emoji<'_, '_> = Emoji("✨ ", ":-)"); static CHECK: Emoji<'_, '_> = Emoji("✅ ", "OK "); @@ -77,7 +78,7 @@ pub async fn run_progress_loop( let global_started = Instant::now(); let mut stage_started = Instant::now(); let mut current_pb: Option = None; - // 0: Idle, 1: Reading, 2: Parsing, 4: Building + // 1: Idle, 2: Reading, 3: Parsing, 4: Optimizing, 5: Building let mut current_stage: u8 = 0; let clear_current = |pb: &mut Option| { @@ -136,7 +137,7 @@ pub async fn run_progress_loop( if current_stage != 1 { println!( "{} {} Initializing...", - style("[1/4]").bold().dim(), + style("[1/5]").bold().dim(), LOOKING_GLASS ); current_stage = 1; @@ -150,7 +151,7 @@ pub async fn run_progress_loop( clear_current(&mut current_pb); println!( "{} {} Reading archive...", - style("[2/4]").bold().dim(), + style("[2/5]").bold().dim(), TRUCK ); current_stage = 2; @@ -160,7 +161,7 @@ pub async fn run_progress_loop( let pb = current_pb.get_or_insert_with(|| { let p = ProgressBar::new(total as u64); p.set_style(bar_style.clone()); - p.set_prefix("[2/4]"); + p.set_prefix("[2/5]"); p }); if pb.position() < current as u64 { @@ -176,12 +177,12 @@ pub async fn run_progress_loop( clear_current(&mut current_pb); println!( "{} {} Parsing resource files...", - style("[3/4]").bold().dim(), + style("[3/5]").bold().dim(), CLIP ); let pb = ProgressBar::new_spinner(); pb.set_style(spinner_style.clone()); - pb.set_prefix("[3/4]"); + pb.set_prefix("[3/5]"); current_pb = Some(pb); current_stage = 3; stage_started = Instant::now(); @@ -193,7 +194,7 @@ pub async fn run_progress_loop( } } - Progress::Building { + Progress::Optimizing { current, index, total, @@ -201,20 +202,50 @@ pub async fn run_progress_loop( if current_stage != 4 { print_finished_stage(&mut current_pb, "Parsing Complete", stage_started); + clear_current(&mut current_pb); + println!( + "{} {} Optimizing resource pack...", + style("[4/5]").bold().dim(), + OPTIMIZING + ); + current_stage = 4; + stage_started = Instant::now(); + } + + let pb = current_pb.get_or_insert_with(|| { + let p = ProgressBar::new(total as u64); + p.set_style(bar_style.clone()); + p.set_prefix("[4/5]"); + p + }); + if pb.position() < index as u64 { + pb.set_position(index as u64); + } + pb.set_message(format!("File: {}", current)); + } + + Progress::Building { + current, + index, + total, + } => { + if current_stage != 5 { + print_finished_stage(&mut current_pb, "Optimizing Complete", stage_started); + clear_current(&mut current_pb); println!( "{} {} Building resource pack...", - style("[4/4]").bold().dim(), + style("[5/5]").bold().dim(), BUILDING ); - current_stage = 4; + current_stage = 5; stage_started = Instant::now(); } let pb = current_pb.get_or_insert_with(|| { let p = ProgressBar::new(total as u64); p.set_style(bar_style.clone()); - p.set_prefix("[4/4]"); + p.set_prefix("[5/5]"); p }); if pb.position() < index as u64 { diff --git a/packobf_gui/src/main.rs b/packobf_gui/src/main.rs index 6a6ddb0..d4729e0 100644 --- a/packobf_gui/src/main.rs +++ b/packobf_gui/src/main.rs @@ -521,6 +521,7 @@ async fn run_packobf(path: String, cache: Option, state: Arc, state: Arc { + format!("Optimizing ({}/{}) {}", index, total, current) + } + Progress::Building { current, index, From d97d0737e048998374a987e0b653fdc8f8b3e55a Mon Sep 17 00:00:00 2001 From: misieur Date: Mon, 15 Jun 2026 22:35:30 +0200 Subject: [PATCH 2/7] Fix errors --- .../main/java/dev/misieur/packobf/options/Options.java | 9 ++++++--- packobf_gui/src/cxxqt_object.rs | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/java/src/main/java/dev/misieur/packobf/options/Options.java b/java/src/main/java/dev/misieur/packobf/options/Options.java index fbab4b3..8d7ddf1 100644 --- a/java/src/main/java/dev/misieur/packobf/options/Options.java +++ b/java/src/main/java/dev/misieur/packobf/options/Options.java @@ -9,7 +9,8 @@ public static Options simplest() { ShaderCompression.NONE, false, false, - false + false, + null ); } @@ -19,7 +20,8 @@ public static Options normal() { ShaderCompression.NONE, false, false, - false + false, + null ); } @@ -30,7 +32,8 @@ public static Options max() { ShaderCompression.MINIFY_AND_OBFUSCATE, true, true, - true + true, + null ); } } diff --git a/packobf_gui/src/cxxqt_object.rs b/packobf_gui/src/cxxqt_object.rs index 3d798d0..471c53e 100644 --- a/packobf_gui/src/cxxqt_object.rs +++ b/packobf_gui/src/cxxqt_object.rs @@ -203,6 +203,7 @@ impl qobject::AppController { rename_files: *self.rename_files(), block_unzipping: *self.block_unzipping(), corrupt_png_files: *self.corrupt_png_files(), + num_threads: None, }; let qt_thread = self.qt_thread(); @@ -241,6 +242,7 @@ impl qobject::AppController { Progress::Idle => "Idle".to_string(), Progress::ReadingZip { current, total } => format!("Reading ZIP ({}/{})", current, total), Progress::Parsing { current } => format!("Parsing {}", current), + Progress::Optimizing { current, index, total } => format!("Optimizing ({}/{}) {}", index, total, current), Progress::Building { current, index, total } => format!("Building ({}/{}) {}", index, total, current), Progress::Done => "Done".to_string(), }; From d02a904681bb286292668eb1fbd05b4828c0c89b Mon Sep 17 00:00:00 2001 From: misieur Date: Fri, 31 Jul 2026 11:03:57 +0200 Subject: [PATCH 3/7] Optimize PNG handling: add dynamic Zopfli presets for Normal/Best compression, refactor compression options, introduce Ultra preset, and enhance cache versioning. --- java/rust/src/lib.rs | 9 +- packobf/src/cache.rs | 55 ++---- packobf/src/file_parser.rs | 6 +- packobf/src/lib.rs | 129 +++++++++---- packobf/src/optimized_zip_writer.rs | 132 +++++++++---- packobf/src/options.rs | 167 +++++++++++++++-- packobf/src/profiler.rs | 20 +- .../src/resource_pack/files/unknowntexture.rs | 176 ++++++++++++++++-- packobf_cli/Cargo.toml | 1 + packobf_cli/src/main.rs | 2 +- packobf_gui/src/cxxqt_object.rs | 7 +- 11 files changed, 551 insertions(+), 153 deletions(-) diff --git a/java/rust/src/lib.rs b/java/rust/src/lib.rs index 023ee15..1abd0df 100644 --- a/java/rust/src/lib.rs +++ b/java/rust/src/lib.rs @@ -30,7 +30,7 @@ pub extern "system" fn Java_dev_misieur_packobf_Native_optimizeZip<'caller>( }; let options = if options.is_null() { - Options::simplest() + Options::fastest() } else { let comp_val = env .get_field(&options, jni_str!("compression"), jni_sig!("I"))? @@ -41,9 +41,10 @@ pub extern "system" fn Java_dev_misieur_packobf_Native_optimizeZip<'caller>( Options { compression: match comp_val { - 0 => Compression::Simplest, - 1 => Compression::Normal, - _ => Compression::Max, + 0 => Compression::Fastest, + 1 => Compression::Fast, + 2 => Compression::Normal, + _ => Compression::Best, }, shader_compression: match shader_comp_val { 0 => ShaderCompression::None, diff --git a/packobf/src/cache.rs b/packobf/src/cache.rs index 5192f57..c9935b1 100644 --- a/packobf/src/cache.rs +++ b/packobf/src/cache.rs @@ -6,12 +6,10 @@ use std::io::{BufReader, BufWriter, Read, Write}; use crate::profile_scope; -const MAGIC_NUMBER: [u8; 8] = *b"PACKOBF1"; -pub const VERSION: u16 = 1; +const MAGIC_NUMBER: [u8; 10] = *b"PACKOBF001"; // Increase version number each time compression is changed (hex number) pub struct CachedItem { pub compression: Compression, - pub version: u16, pub data: Vec, } @@ -19,16 +17,20 @@ pub struct CachedItem { #[derive(Debug, Clone, Copy)] pub enum Compression { Fastest = 0, - Normal = 1, - Best = 2, + Fast = 1, + Normal = 2, + Best = 3, + Ultra = 4, } impl Compression { fn from_u8(value: u8) -> Self { match value { 0 => Compression::Fastest, - 2 => Compression::Best, - _ => Compression::Normal, + 1 => Compression::Fast, + 2 => Compression::Normal, + 3 => Compression::Best, + _ => Compression::Ultra, } } } @@ -85,9 +87,6 @@ impl Cache { // Write Compression (1 byte) writer.write_all(&[item.compression as u8])?; - // Write Version (2 bytes) - writer.write_all(&item.version.to_le_bytes())?; - // Write Data Length (u64) and Data writer.write_all(&(item.data.len() as u64).to_le_bytes())?; writer.write_all(&item.data)?; @@ -101,25 +100,19 @@ impl Cache { profile_scope!("load_from_file::cache"); let file = File::open(path); if let Err(e) = file { - return if e.kind() == io::ErrorKind::NotFound { - Ok(Cache { - items: DashMap::new(), - }) - } else { - Err(e) - }; + return Err(e) } let file = file?; let mut reader = BufReader::new(file); let items = DashMap::new(); - let mut magic = [0u8; 8]; + let mut magic = [0u8; 10]; reader.read_exact(&mut magic)?; if magic != MAGIC_NUMBER { return Err(io::Error::new( io::ErrorKind::InvalidData, - "Not a valid cache file: Magic number mismatch", + "Not a valid cache file: Magic number mismatch (may be from a different version of packobf)", )); } @@ -143,11 +136,6 @@ impl Cache { reader.read_exact(&mut comp_byte)?; let compression = Compression::from_u8(comp_byte[0]); - // Read Version - let mut ver_bytes = [0u8; 2]; - reader.read_exact(&mut ver_bytes)?; - let version = u16::from_le_bytes(ver_bytes); - // Read Data Length and then the Data let mut data_len_bytes = [0u8; 8]; reader.read_exact(&mut data_len_bytes)?; @@ -156,16 +144,13 @@ impl Cache { let mut data = vec![0u8; data_len]; reader.read_exact(&mut data)?; - if version == VERSION { - items.insert( - CachedItemKey { hash, item_type }, - CachedItem { - compression, - version, - data, - }, - ); - } + items.insert( + CachedItemKey { hash, item_type }, + CachedItem { + compression, + data, + }, + ); } Ok(Cache { items }) @@ -198,7 +183,6 @@ impl Cache { CachedItemKey { hash, item_type }, CachedItem { compression: Compression::from_u8(compression), - version: VERSION, data: data.into(), }, ); @@ -218,7 +202,6 @@ impl Cache { }, CachedItem { compression: Compression::from_u8(compression), - version: VERSION, data: data.into(), }, ); diff --git a/packobf/src/file_parser.rs b/packobf/src/file_parser.rs index 0865651..1bfac68 100644 --- a/packobf/src/file_parser.rs +++ b/packobf/src/file_parser.rs @@ -46,14 +46,14 @@ fn parse_resource_pack_file( current: name.to_string(), }); #[cfg(feature = "profiling")] - let _ = crate::profiler::profiler::ScopeTimer::new( + let _ = crate::profiler::ScopeTimer::new( if name.ends_with(".json") || name.ends_with(".mcmeta") { "parse_resource_pack_files::json" - } else if name.ends_with(".png") && crate::get_type(&name) == Some("textures") { + } else if name.ends_with(".png") && get_type(&name) == Some("textures") { "parse_resource_pack_files::texture" } else if name.ends_with(".vsh") || name.ends_with(".fsh") || name.ends_with(".glsl") { "parse_resource_pack_files::shader" - } else if name.ends_with(".ogg") && crate::get_type(&name) == Some("sounds") { + } else if name.ends_with(".ogg") && get_type(&name) == Some("sounds") { "parse_resource_pack_files::sound" } else { "parse_resource_pack_files::unknown" diff --git a/packobf/src/lib.rs b/packobf/src/lib.rs index 89d948d..068bbcb 100644 --- a/packobf/src/lib.rs +++ b/packobf/src/lib.rs @@ -30,7 +30,8 @@ use crate::resource_pack::identifier::Identifier; use crate::resource_pack::mapping; use crate::resource_pack::mapping::{IdUsageCounter, Mapping}; use crate::resource_pack::pack::ResourcePack; -use crate::LogLevel::Info; +use crate::LogLevel::{Info, Warning}; +use dashmap::DashMap; use rayon::prelude::*; use rayon::{ThreadPool, ThreadPoolBuilder}; use std::error::Error; @@ -50,7 +51,7 @@ pub fn process_zip( ) -> Result, Box> { let _ = progress.send(Progress::Idle); #[cfg(feature = "profiling")] - profiler::profiler::PROFILER.store(Arc::new(profiler::profiler::Profiler::new())); + profiler::PROFILER.store(Arc::new(profiler::Profiler::new())); let progress_clone = progress.clone(); let reader = Cursor::new(&input_bytes); @@ -107,11 +108,15 @@ pub fn process_zip( let total = items.len(); let mut output = Cursor::new(Vec::new()); let writer = OptimizedZipWriter::new(&mut output); - let counter = AtomicUsize::new(0); if options.block_unzipping { // Add this file first to make tools crash before they can read the data - writer.add_file("assets\0", Vec::new().as_slice(), options, &None)?; + writer.add_file( + "assets\0", + Vec::new().as_slice(), + options, + &None, + )?; // `\0` (null) is universally disallowed inside filenames, but Minecraft doesn't care } @@ -120,11 +125,24 @@ pub fn process_zip( level: Info, message: format!("Loading cache from {}", cache), }); - let cache = Cache::load_from_file(cache)?; - let _ = logger.send(LogMessage { - level: Info, - message: format!("Cache loaded: {} items", cache.items.len()), - }); + let cache = match Cache::load_from_file(cache) { + Ok(cache) => { + let _ = logger.send(LogMessage { + level: Info, + message: format!("Cache loaded: {} items", cache.items.len()), + }); + cache + } + Err(e) => { + let _ = logger.send(LogMessage { + level: Warning, + message: format!("Invalid cache file, creating a new one. Error: {}", e), + }); + Cache { + items: DashMap::new(), + } + } + }; Some(cache) } else { None @@ -160,6 +178,7 @@ pub fn process_zip( .collect(); let total_to_optimize = to_optimize.len(); + let counter = AtomicUsize::new(0); to_optimize.into_par_iter().for_each(|(name, item)| { let _ = progress.send(Progress::Optimizing { current: name.to_string(), @@ -182,6 +201,8 @@ pub fn process_zip( _ => {} } }); + + let counter = AtomicUsize::new(0); items.par_iter_mut().for_each(|(name, item)| { match add_item_to_archive( options, &progress, logger, total, &writer, &counter, &cache, name, item, @@ -206,7 +227,7 @@ pub fn process_zip( let _ = progress.send(Progress::Done); #[cfg(feature = "profiling")] - profiler::profiler::PROFILER.load().print(); + profiler::PROFILER.load().print(); Ok(output.into_inner()) } @@ -253,7 +274,12 @@ fn add_item_to_archive( ), ResourcePackItem::Shader(o) => { o.optimize(options, logger); - writer.add_file(name.as_str(), o.content.as_bytes(), options, cache) + writer.add_file( + name.as_str(), + o.content.as_bytes(), + options, + cache, + ) } ResourcePackItem::Json(o) => writer.add_file( name.as_str(), @@ -261,33 +287,60 @@ fn add_item_to_archive( options, cache, ), - ResourcePackItem::Model(o) => { - writer.add_file(name.as_str(), o.to_string().as_bytes(), options, cache) - } - ResourcePackItem::Unknown(o) => { - writer.add_file(name.as_str(), o.bytes.as_slice(), options, cache) - } - ResourcePackItem::BlockStateDefinition(o) => { - writer.add_file(name.as_str(), o.to_string().as_bytes(), options, cache) - } - ResourcePackItem::FontDefinition(o) => { - writer.add_file(name.as_str(), o.to_string().as_bytes(), options, cache) - } - ResourcePackItem::ItemDefinition(o) => { - writer.add_file(name.as_str(), o.to_string().as_bytes(), options, cache) - } - ResourcePackItem::Sound(o) => { - writer.add_file(name.as_str(), o.bytes.as_slice(), options, cache) - } - ResourcePackItem::SoundDefinitions(o) => { - writer.add_file(name.as_str(), o.to_string().as_bytes(), options, cache) - } - ResourcePackItem::Atlas(o) => { - writer.add_file(name.as_str(), o.to_string().as_bytes(), options, cache) - } - ResourcePackItem::UnknownTexture(o) => { - writer.add_file(name.as_str(), o.bytes.as_slice(), options, cache) - } + ResourcePackItem::Model(o) => writer.add_file( + name.as_str(), + o.to_string().as_bytes(), + options, + cache, + ), + ResourcePackItem::Unknown(o) => writer.add_file( + name.as_str(), + o.bytes.as_slice(), + options, + cache, + ), + ResourcePackItem::BlockStateDefinition(o) => writer.add_file( + name.as_str(), + o.to_string().as_bytes(), + options, + cache, + ), + ResourcePackItem::FontDefinition(o) => writer.add_file( + name.as_str(), + o.to_string().as_bytes(), + options, + cache, + ), + ResourcePackItem::ItemDefinition(o) => writer.add_file( + name.as_str(), + o.to_string().as_bytes(), + options, + cache, + ), + ResourcePackItem::Sound(o) => writer.add_file( + name.as_str(), + o.bytes.as_slice(), + options, + cache, + ), + ResourcePackItem::SoundDefinitions(o) => writer.add_file( + name.as_str(), + o.to_string().as_bytes(), + options, + cache, + ), + ResourcePackItem::Atlas(o) => writer.add_file( + name.as_str(), + o.to_string().as_bytes(), + options, + cache, + ), + ResourcePackItem::UnknownTexture(o) => writer.add_file( + name.as_str(), + o.bytes.as_slice(), + options, + cache, + ), }?; Ok(()) } diff --git a/packobf/src/optimized_zip_writer.rs b/packobf/src/optimized_zip_writer.rs index 2f304e5..f5b5c9e 100644 --- a/packobf/src/optimized_zip_writer.rs +++ b/packobf/src/optimized_zip_writer.rs @@ -1,28 +1,25 @@ use crate::cache::{Cache, ItemType}; -pub(crate) use crate::options::ZOPFLI_OPTIONS; -use crate::options::{Compression, Options}; +use crate::options::{analyze_and_get_zopfli_config_best, analyze_and_get_zopfli_config_normal, Compression, Options, PreCheckResult, ULTRA_ZOPFLI_OPTIONS}; use crate::profile_scope; use byteorder::{LittleEndian, WriteBytesExt}; -use crc32fast::Hasher as Crc32Hasher; use dashmap::DashMap; use libdeflater::CompressionLvl; use sha2::{Digest, Sha256}; use std::io::{self, Error, Seek, Write}; use std::sync::{Arc, Mutex}; +use crate::options::PreCheckResult::{CompressWithZopfli, Skip}; #[derive(Clone, Debug)] pub struct CachedFileData { pub header_offset: u32, - pub crc32: u32, + pub compression_method: u16, pub compressed_size: u32, - pub uncompressed_size: u32, } struct CentralDirectoryEntry { filename: String, - crc32: u32, + compression_method: u16, compressed_size: u32, - uncompressed_size: u32, header_offset: u32, } @@ -65,16 +62,20 @@ impl OptimizedZipWriter { return self.record_entry(&mut inner, filename, cached_data); } - - // Calculate CRC32 (Required for Central Directory) - let mut crc32_hasher = Crc32Hasher::new(); - crc32_hasher.update(data); - let crc32 = crc32_hasher.finalize(); let uncompressed_size = data.len() as u32; // Compress the data using DEFLATE - let compressed_data: Vec = Self::compress(data, options, &hash, cache)?; - let compressed_size = compressed_data.len() as u32; + let compressed_data = Self::compress(data, options, &hash, cache)?; + let mut compressed_size = compressed_data.len() as u32; + let using_store = compressed_size == 0; // Skip compression if it's larger when compressed + if using_store { + compressed_size = uncompressed_size; + } + let compression_method = if using_store { + 0 // Store + } else { + 8 // Deflate + }; let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner()); @@ -90,9 +91,9 @@ impl OptimizedZipWriter { // Write the Minimized Local File Header (LFH) // We zero out the metadata, filename length, and omit the filename string to save space. inner.writer.write_u32::(0x04034b50)?; // LFH Signature - inner.writer.write_u16::(10)?; // Version needed to extract (1.0) + inner.writer.write_u16::(0)?; // Version needed to extract (Zeroed) inner.writer.write_u16::(0)?; // General purpose bit flag - inner.writer.write_u16::(8)?; // Compression method (8 = Deflate) + inner.writer.write_u16::(0)?; // Compression method (Zeroed) inner.writer.write_u32::(0)?; // Last mod file time and date (Zeroed) inner.writer.write_u32::(0)?; // CRC-32 (Zeroed) inner.writer.write_u32::(0)?; // Compressed size (Zeroed) @@ -101,22 +102,24 @@ impl OptimizedZipWriter { inner.writer.write_u16::(0)?; // Extra field length (Zeroed) // Write the actual compressed payload - inner.writer.write_all(&compressed_data)?; + if using_store { + inner.writer.write_all(data)?; // When using Store + } else { + inner.writer.write_all(&compressed_data)?; // When using Deflate + } let new_cache = CachedFileData { header_offset, - crc32, - compressed_size, - uncompressed_size, + compression_method, + compressed_size }; // Insert into the hashmap so future identical files point here self.content_cache.insert(hash, new_cache.clone()); inner.cd_entries.push(CentralDirectoryEntry { filename: filename.to_string(), - crc32: new_cache.crc32, + compression_method: new_cache.compression_method, compressed_size: new_cache.compressed_size, - uncompressed_size: new_cache.uncompressed_size, header_offset: new_cache.header_offset, }); @@ -140,14 +143,19 @@ impl OptimizedZipWriter { return Ok(bytes); } } + let input_size = data.len(); Ok(match options.compression { - Compression::Simplest => { + Compression::Fastest => { let mut compressor = libdeflater::Compressor::default(); let mut out = vec![0u8; compressor.deflate_compress_bound(data.len())]; let size = compressor .deflate_compress(data, &mut out) .map_err(|_| Error::other("Compression failed"))?; out.truncate(size); + let out_size = out.len(); + if out_size > input_size { + out = vec![]; + } if let Some(cache) = cache { cache.add_item_hash( hash, @@ -158,31 +166,86 @@ impl OptimizedZipWriter { } out } - Compression::Normal => { + Compression::Fast => { let mut compressor = libdeflater::Compressor::new(CompressionLvl::best()); let mut out = vec![0u8; compressor.deflate_compress_bound(data.len())]; let size = compressor .deflate_compress(data, &mut out) .map_err(|_| Error::other("Compression failed"))?; out.truncate(size); + let out_size = out.len(); + if out_size > input_size { + out = vec![]; + } if let Some(cache) = cache { cache.add_item_hash( hash, &*out, - crate::cache::Compression::Normal as u8, + crate::cache::Compression::Fast as u8, ItemType::Generic, ) } out } - Compression::Max => { + Compression::Normal => { + let pre_check_result = analyze_and_get_zopfli_config_normal(&data); + Self::compress_with_pre_check(data, hash, cache, input_size, pre_check_result)? + } + Compression::Best => { + let pre_check_result = analyze_and_get_zopfli_config_best(&data); + Self::compress_with_pre_check(data, hash, cache, input_size, pre_check_result)? + } + Compression::Ultra => { let mut encoder = zopfli::DeflateEncoder::new( - ZOPFLI_OPTIONS.to_owned(), + ULTRA_ZOPFLI_OPTIONS.to_owned(), zopfli::BlockType::Dynamic, Vec::new(), ); encoder.write_all(data)?; - let out = encoder.finish()?; + let mut out = encoder.finish()?; + let out_size = out.len(); + if out_size > input_size { + out = vec![]; + } + if let Some(cache) = cache { + cache.add_item_hash( + hash, + &*out, + crate::cache::Compression::Ultra as u8, + ItemType::Generic, + ) + } + out + } + }) + } + + fn compress_with_pre_check(data: &[u8], hash: &[u8; 32], cache: &Option, input_size: usize, pre_check_result: PreCheckResult) -> Result, Error> { + Ok(match pre_check_result { + CompressWithZopfli(options) => { + let mut encoder = zopfli::DeflateEncoder::new( + options, + zopfli::BlockType::Dynamic, + Vec::new(), + ); + encoder.write_all(data)?; + let mut out = encoder.finish()?; + let out_size = out.len(); + if out_size > input_size { + out = vec![]; + } + if let Some(cache) = cache { + cache.add_item_hash( + hash, + &*out, + crate::cache::Compression::Best as u8, + ItemType::Generic, + ) + } + out + } + Skip => { + let out = vec![]; if let Some(cache) = cache { cache.add_item_hash( hash, @@ -204,9 +267,8 @@ impl OptimizedZipWriter { ) -> io::Result<()> { inner.cd_entries.push(CentralDirectoryEntry { filename: filename.to_string(), - crc32: data.crc32, + compression_method: data.compression_method, compressed_size: data.compressed_size, - uncompressed_size: data.uncompressed_size, header_offset: data.header_offset, }); Ok(()) @@ -229,14 +291,14 @@ impl OptimizedZipWriter { let filename_bytes = entry.filename.as_bytes(); writer.write_u32::(0x02014b50)?; // CD Signature - writer.write_u16::(10)?; // Version made by - writer.write_u16::(10)?; // Version needed to extract + writer.write_u16::(0)?; // Version made by + writer.write_u16::(0)?; // Version needed to extract writer.write_u16::(0)?; // General purpose bit flag - writer.write_u16::(8)?; // Compression method (8 = Deflate) + writer.write_u16::(entry.compression_method)?; // Compression method writer.write_u32::(0)?; // Last mod file time and date - writer.write_u32::(entry.crc32)?; // Actual CRC-32 + writer.write_u32::(0)?; // Actual CRC-32 writer.write_u32::(entry.compressed_size)?; // Actual Compressed size - writer.write_u32::(entry.uncompressed_size)?; // Actual Uncompressed size + writer.write_u32::(0)?; // Actual Uncompressed size writer.write_u16::(filename_bytes.len() as u16)?; // File name length writer.write_u16::(0)?; // Extra field length writer.write_u16::(0)?; // File comment length diff --git a/packobf/src/options.rs b/packobf/src/options.rs index ea61f99..400473f 100644 --- a/packobf/src/options.rs +++ b/packobf/src/options.rs @@ -1,6 +1,7 @@ use once_cell::sync::Lazy; use std::num::NonZeroU64; use clap::{Parser, ValueEnum}; +use libdeflater::{CompressionLvl, Compressor}; #[derive(Parser, Clone, Debug)] #[group(id = "options")] @@ -21,15 +22,27 @@ pub struct Options { #[derive(ValueEnum, Clone, Debug)] pub enum Preset { - Simplest, + Fastest, + Fast, Normal, - Max, + Best, } impl Options { - pub fn simplest() -> Self { + pub fn fastest() -> Self { Self { - compression: Compression::Simplest, + compression: Compression::Fastest, + shader_compression: ShaderCompression::None, + rename_files: false, + block_unzipping: false, + corrupt_png_files: false, + num_threads: None, + } + } + + pub fn fast() -> Self { + Self { + compression: Compression::Fast, shader_compression: ShaderCompression::None, rename_files: false, block_unzipping: false, @@ -49,9 +62,9 @@ impl Options { } } - pub fn max() -> Self { + pub fn best() -> Self { Self { - compression: Compression::Max, + compression: Compression::Best, shader_compression: ShaderCompression::MinifyAndObfuscate, rename_files: true, block_unzipping: true, @@ -62,9 +75,10 @@ impl Options { pub fn from_preset(preset: Preset) -> Self { match preset { - Preset::Simplest => Self::simplest(), + Preset::Fastest => Self::fastest(), + Preset::Fast => Self::fast(), Preset::Normal => Self::normal(), - Preset::Max => Self::max(), + Preset::Best => Self::best(), } } } @@ -72,9 +86,11 @@ impl Options { #[repr(u8)] #[derive(ValueEnum, Clone, Debug)] pub enum Compression { - Simplest = 0, - Normal = 1, - Max = 2, + Fastest = 0, + Fast = 1, + Normal = 2, + Best = 3, + Ultra = 4, } #[repr(u8)] @@ -89,6 +105,131 @@ pub enum ShaderCompression { #[allow(clippy::unwrap_used)] pub static ZOPFLI_OPTIONS: Lazy = Lazy::new(|| zopfli::Options { iteration_count: NonZeroU64::new(25).unwrap(), - iterations_without_improvement: NonZeroU64::new(7).unwrap(), - maximum_block_splits: 50, + iterations_without_improvement: NonZeroU64::new(3).unwrap(), + maximum_block_splits: 15, +}); + +#[allow(clippy::unwrap_used)] +pub static FASTEST_ZOPFLI_OPTIONS: Lazy = Lazy::new(|| zopfli::Options { + iteration_count: NonZeroU64::new(3).unwrap(), + iterations_without_improvement: NonZeroU64::new(1).unwrap(), + maximum_block_splits: 2, +}); + +#[allow(clippy::unwrap_used)] +pub static FAST_ZOPFLI_OPTIONS: Lazy = Lazy::new(|| zopfli::Options { + iteration_count: NonZeroU64::new(5).unwrap(), + iterations_without_improvement: NonZeroU64::new(2).unwrap(), + maximum_block_splits: 5, +}); + +#[allow(clippy::unwrap_used)] +pub static NORMAL_ZOPFLI_OPTIONS: Lazy = Lazy::new(|| zopfli::Options { + iteration_count: NonZeroU64::new(12).unwrap(), + iterations_without_improvement: NonZeroU64::new(2).unwrap(), + maximum_block_splits: 10, +}); + +#[allow(clippy::unwrap_used)] +pub static SLOW_ZOPFLI_OPTIONS: Lazy = Lazy::new(|| zopfli::Options { + iteration_count: NonZeroU64::new(20).unwrap(), + iterations_without_improvement: NonZeroU64::new(3).unwrap(), + maximum_block_splits: 15, +}); + +#[allow(clippy::unwrap_used)] +pub static SLOWEST_ZOPFLI_OPTIONS: Lazy = Lazy::new(|| zopfli::Options { + iteration_count: NonZeroU64::new(25).unwrap(), + iterations_without_improvement: NonZeroU64::new(3).unwrap(), + maximum_block_splits: 15, +}); + +#[allow(clippy::unwrap_used)] +pub static ULTRA_ZOPFLI_OPTIONS: Lazy = Lazy::new(|| zopfli::Options { + iteration_count: NonZeroU64::new(40).unwrap(), + iterations_without_improvement: NonZeroU64::new(40).unwrap(), + maximum_block_splits: 25, }); + +pub enum PreCheckResult { + /// Skip Zopfli entirely + Skip, + /// Use Zopfli with dynamically assigned options + CompressWithZopfli(zopfli::Options), +} + +/// Pre-checks data compressibility using libdeflater (Level 9) +/// and dynamically calculates the Zopfli config for 'normal' preset. +pub fn analyze_and_get_zopfli_config_normal(data: &[u8]) -> PreCheckResult { + let (original_size, savings_ratio) = match analyze(data) { + Ok(value) => value, + Err(value) => return value, + }; + + // Less than 1% savings + if savings_ratio < 0.01 { + return PreCheckResult::Skip; // Don't waste CPU time on Zopfli + } + + // 1% to 8% savings + if savings_ratio < 0.08 { + return PreCheckResult::CompressWithZopfli(FASTEST_ZOPFLI_OPTIONS.to_owned()); + } + + // > 8% savings + PreCheckResult::CompressWithZopfli(match original_size { + 0..=51_200 => SLOW_ZOPFLI_OPTIONS.to_owned(), + + 51_201..=512_000 => NORMAL_ZOPFLI_OPTIONS.to_owned(), + + _ => FAST_ZOPFLI_OPTIONS.to_owned(), + }) +} + +/// Pre-checks data compressibility using libdeflater (Level 9) +/// and dynamically calculates the Zopfli config for 'best' preset. +pub fn analyze_and_get_zopfli_config_best(data: &[u8]) -> PreCheckResult { + let (original_size, savings_ratio) = match analyze(data) { + Ok(value) => value, + Err(value) => return value, + }; + + // Less than 1% savings + if savings_ratio < 0.01 { + return PreCheckResult::Skip; // Don't waste CPU time on Zopfli + } + + // 1% to 8% savings + if savings_ratio < 0.08 { + return PreCheckResult::CompressWithZopfli(FAST_ZOPFLI_OPTIONS.to_owned()); + } + + // > 8% savings + PreCheckResult::CompressWithZopfli(match original_size { + 0..=51_200 => SLOWEST_ZOPFLI_OPTIONS.to_owned(), + + 51_201..=512_000 => SLOW_ZOPFLI_OPTIONS.to_owned(), + + _ => NORMAL_ZOPFLI_OPTIONS.to_owned(), + }) +} + +fn analyze(data: &[u8]) -> Result<(usize, f64), PreCheckResult> { + let original_size = data.len(); + if original_size == 0 { + return Err(PreCheckResult::Skip); + } + + let mut compressor = Compressor::new(CompressionLvl::new(9).unwrap()); + let max_buf_len = compressor.deflate_compress_bound(original_size); + let mut compressed_buf = vec![0u8; max_buf_len]; + + let fast_compressed_size = match compressor.deflate_compress(data, &mut compressed_buf) { + Ok(sz) => sz, + Err(_) => return Err(PreCheckResult::Skip), + }; + + let bytes_saved = original_size.saturating_sub(fast_compressed_size); + let savings_ratio = bytes_saved as f64 / original_size as f64; + Ok((original_size, savings_ratio)) +} diff --git a/packobf/src/profiler.rs b/packobf/src/profiler.rs index 4003549..33a0fa8 100644 --- a/packobf/src/profiler.rs +++ b/packobf/src/profiler.rs @@ -15,6 +15,8 @@ pub static PROFILER: LazyLock> = LazyLock::new(|| ArcSwap::fro #[cfg(feature = "profiling")] pub struct Stat { pub total_ns: AtomicU64, + pub min_ns: AtomicU64, + pub max_ns: AtomicU64, pub calls: AtomicU64, } @@ -36,10 +38,14 @@ impl Profiler { let entry = self.stats.entry(name).or_insert_with(|| Stat { total_ns: AtomicU64::new(0), + min_ns: AtomicU64::new(nanos), + max_ns: AtomicU64::new(nanos), calls: AtomicU64::new(0), }); entry.total_ns.fetch_add(nanos, Ordering::Relaxed); + entry.min_ns.fetch_min(nanos, Ordering::Relaxed); + entry.max_ns.fetch_max(nanos, Ordering::Relaxed); entry.calls.fetch_add(1, Ordering::Relaxed); } @@ -49,9 +55,11 @@ impl Profiler { .iter() .map(|e| { let total = e.total_ns.load(Ordering::Relaxed); + let min = e.min_ns.load(Ordering::Relaxed); + let max = e.max_ns.load(Ordering::Relaxed); let calls = e.calls.load(Ordering::Relaxed); - (*e.key(), total, calls, total as f64 / calls.max(1) as f64) + (*e.key(), total, calls, total as f64 / calls.max(1) as f64, min, max) }) .collect::>(); @@ -59,13 +67,15 @@ impl Profiler { println!("==== PROFILING ===="); - for (name, total, calls, avg) in entries { + for (name, total, calls, avg, min, max) in entries { println!( - "{:<40} total={:>10.2}ms calls={:>8.2} avg={:>10.2}µs", + "{:<40} total={:>10.2}ms calls={:>8.2} avg={:>10.5}ms min={:>10.5}ms max={:>10.5}ms", name, total as f64 / 1_000_000.0, calls, - avg / 1_000.0 + avg / 1_000_000.0, + min as f64 / 1_000_000.0, + max as f64 / 1_000_000.0, ); } } @@ -98,6 +108,6 @@ impl Drop for ScopeTimer { macro_rules! profile_scope { ($name:expr) => { #[cfg(feature = "profiling")] - let _profiler_scope = $crate::profiler::profiler::ScopeTimer::new($name); + let _profiler_scope = $crate::profiler::ScopeTimer::new($name); }; } diff --git a/packobf/src/resource_pack/files/unknowntexture.rs b/packobf/src/resource_pack/files/unknowntexture.rs index 316c5a2..79755ab 100644 --- a/packobf/src/resource_pack/files/unknowntexture.rs +++ b/packobf/src/resource_pack/files/unknowntexture.rs @@ -1,3 +1,4 @@ +use std::num::NonZeroU64; use std::time::Duration; use once_cell::sync::Lazy; use oxipng::{indexset, optimize_from_memory, Deflater, FilterStrategy, StripChunks}; @@ -14,6 +15,74 @@ pub struct UnknownTexture { pub bytes: Vec, } +/// Decodes PNG dimensions and estimates uncompressed IDAT size instantly from raw headers. +fn get_raw_uncompressed_size(bytes: &[u8]) -> Option { + if bytes.len() < 26 { + return None; + } + if bytes[0..8] != [137, 80, 78, 71, 13, 10, 26, 10] { + return None; + } + if &bytes[12..16] != b"IHDR" { + return None; + } + + let width = u32::from_be_bytes(bytes[16..20].try_into().ok()?) as usize; + let height = u32::from_be_bytes(bytes[20..24].try_into().ok()?) as usize; + let bit_depth = bytes[24] as usize; + let color_type = bytes[25]; + + let channels = match color_type { + 0 => 1, // Grayscale + 2 => 3, // RGB + 3 => 1, // Palette (indexed) + 4 => 2, // Grayscale + Alpha + 6 => 4, // RGBA + _ => 4, // Fallback + }; + + let bits_per_pixel = channels * bit_depth; + let row_bytes = (width * bits_per_pixel).div_ceil(8) + 1; + Some(row_bytes * height) +} + +/// Formula generated using a script +fn get_zopfli_options_normal(raw_size: usize) -> oxipng::ZopfliOptions { + let (iterations, without_improvement, splits) = if raw_size < 10_000 { + (10, 6, 1) + } else if raw_size < 100_000 { + (9, 3, 1) + } else if raw_size < 1_000_000 { + (9, 2, 1) + } else { + (9, 1, 2) + }; + + oxipng::ZopfliOptions { + iteration_count: NonZeroU64::new(iterations).unwrap(), + iterations_without_improvement: NonZeroU64::new(without_improvement).unwrap(), + maximum_block_splits: splits, + } +} + +fn get_zopfli_options_best(raw_size: usize) -> oxipng::ZopfliOptions { + let (iterations, without_improvement, splits) = if raw_size < 10_000 { + (10, 6, 1) + } else if raw_size < 100_000 { + (16, 6, 1) + } else if raw_size < 1_000_000 { + (20, 6, 2) + } else { + (22, 7, 2) + }; + + oxipng::ZopfliOptions { + iteration_count: NonZeroU64::new(iterations).unwrap(), + iterations_without_improvement: NonZeroU64::new(without_improvement).unwrap(), + maximum_block_splits: splits, + } +} + impl UnknownTexture { pub fn new(path: impl Into, bytes: Vec) -> Self { Self { @@ -71,12 +140,28 @@ impl UnknownTexture { return bytes; } } + let oxipng_options = match options.compression { - Compression::Simplest => &DEFAULT_OPTIONS, - Compression::Normal => &NORMAL_OPTIONS, - Compression::Max => &MAX_OPTIONS, + Compression::Fastest => FASTEST_OPTIONS.clone(), + Compression::Fast => FAST_OPTIONS.clone(), + Compression::Normal => { + let mut opts = NORMAL_OPTIONS.clone(); + let raw_size = get_raw_uncompressed_size(bytes).unwrap_or(bytes.len()); + opts.deflater = Deflater::Zopfli(get_zopfli_options_normal(raw_size)); + opts + } + Compression::Best => { + let mut opts = BEST_OPTIONS.clone(); + let raw_size = get_raw_uncompressed_size(bytes).unwrap_or(bytes.len()); + opts.deflater = Deflater::Zopfli(get_zopfli_options_best(raw_size)); + opts + } + Compression::Ultra => { + ULTRA_OPTIONS.clone() + } }; - match optimize_from_memory(bytes, oxipng_options) { + + match optimize_from_memory(bytes, &oxipng_options) { Ok(value) => { if let Some(cache) = cache { cache.add_item( @@ -102,7 +187,7 @@ impl UnknownTexture { level: Info, message: format!("Image '{}' was recovered successfully.", path), }); - match optimize_from_memory(value.as_slice(), oxipng_options) { + match optimize_from_memory(value.as_slice(), &oxipng_options) { Ok(value) => { if let Some(cache) = cache { cache.add_item( @@ -140,14 +225,10 @@ impl UnknownTexture { } } } - } -/** -Currently, only the `deflater` option changes, but some other options could be modified based on the compression wanted. -*/ -// -static DEFAULT_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { +// +static FASTEST_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { fix_errors: true, force: false, filters: indexset! { @@ -175,6 +256,39 @@ static DEFAULT_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { scale_16: false, strip: StripChunks::All, deflater: Deflater::Libdeflater { compression: 6 }, // 6: default compression level + fast_evaluation: true, + timeout: Some(Duration::from_secs(3)), + max_decompressed_size: None, +}); + +static FAST_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { + fix_errors: true, + force: false, + filters: indexset! { + FilterStrategy::NONE, + FilterStrategy::SUB, + FilterStrategy::UP, + FilterStrategy::AVERAGE, + FilterStrategy::PAETH, + FilterStrategy::MinSum, + FilterStrategy::Entropy, + FilterStrategy::Bigrams, + FilterStrategy::BigEnt, + FilterStrategy::Brute { + num_lines: 8, + level: 12, + }, + }, + interlace: Some(false), + optimize_alpha: true, + bit_depth_reduction: true, + color_type_reduction: true, + palette_reduction: true, + grayscale_reduction: true, + idat_recoding: true, + scale_16: false, + strip: StripChunks::All, + deflater: Deflater::Libdeflater { compression: 12 }, // 12: max compression level for libdeflater fast_evaluation: false, timeout: Some(Duration::from_secs(3)), max_decompressed_size: None, @@ -207,13 +321,46 @@ static NORMAL_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { idat_recoding: true, scale_16: false, strip: StripChunks::All, - deflater: Deflater::Libdeflater { compression: 12 }, // 12: max compression level for libdeflater + deflater: Deflater::Zopfli(options::NORMAL_ZOPFLI_OPTIONS.to_owned()), fast_evaluation: false, timeout: Some(Duration::from_secs(3)), max_decompressed_size: None, }); -static MAX_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { +static BEST_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { + fix_errors: true, + force: false, + filters: indexset! { + FilterStrategy::NONE, + FilterStrategy::SUB, + FilterStrategy::UP, + FilterStrategy::AVERAGE, + FilterStrategy::PAETH, + FilterStrategy::MinSum, + FilterStrategy::Entropy, + FilterStrategy::Bigrams, + FilterStrategy::BigEnt, + FilterStrategy::Brute { + num_lines: 8, + level: 12, + }, + }, + interlace: Some(false), + optimize_alpha: true, + bit_depth_reduction: true, + color_type_reduction: true, + palette_reduction: true, + grayscale_reduction: true, + idat_recoding: true, + scale_16: false, + strip: StripChunks::All, + deflater: Deflater::Zopfli(options::SLOW_ZOPFLI_OPTIONS.to_owned()), + fast_evaluation: false, + timeout: None, + max_decompressed_size: None, +}); + +static ULTRA_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { fix_errors: true, force: false, filters: indexset! { @@ -240,10 +387,9 @@ static MAX_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { idat_recoding: true, scale_16: false, strip: StripChunks::All, - deflater: Deflater::Zopfli(options::ZOPFLI_OPTIONS.to_owned()), // zopfli: best compression + deflater: Deflater::Zopfli(options::ULTRA_ZOPFLI_OPTIONS.to_owned()), fast_evaluation: false, timeout: Some(Duration::from_secs(3)), max_decompressed_size: None, }); // - diff --git a/packobf_cli/Cargo.toml b/packobf_cli/Cargo.toml index 86bcced..31290b4 100644 --- a/packobf_cli/Cargo.toml +++ b/packobf_cli/Cargo.toml @@ -3,6 +3,7 @@ name = "packobf_cli" version = "0.2.1" edition = "2024" license = "MIT" +default-run = "packobf_cli" [dependencies] packobf = { path = "../packobf" } diff --git a/packobf_cli/src/main.rs b/packobf_cli/src/main.rs index ee8e0a7..04cd183 100644 --- a/packobf_cli/src/main.rs +++ b/packobf_cli/src/main.rs @@ -60,7 +60,7 @@ async fn main() { DecimalBytes(bytes.len() as u64), DecimalBytes(input_size as u64), DecimalBytes(bytes.len().abs_diff(input_size) as u64), - 100.0 - ratio * 100.0, + (100.0 - ratio * 100.0).abs(), if bytes.len() < input_size { "smaller" } else { diff --git a/packobf_gui/src/cxxqt_object.rs b/packobf_gui/src/cxxqt_object.rs index 471c53e..aec1dcd 100644 --- a/packobf_gui/src/cxxqt_object.rs +++ b/packobf_gui/src/cxxqt_object.rs @@ -191,9 +191,10 @@ impl qobject::AppController { let options = Options { compression: match self.compression() { - 0 => Compression::Simplest, - 1 => Compression::Normal, - _ => Compression::Max, + 0 => Compression::Fastest, + 1 => Compression::Fast, + 2 => Compression::Normal, + _ => Compression::Best, }, shader_compression: match self.shader_compression() { 0 => ShaderCompression::None, From 725faada8812a039846db1363bb29e0c644a20a1 Mon Sep 17 00:00:00 2001 From: misieur Date: Fri, 31 Jul 2026 11:11:25 +0200 Subject: [PATCH 4/7] Clean up code --- packobf/src/lib.rs | 2 +- packobf/src/optimized_zip_writer.rs | 4 +- packobf/src/options.rs | 1 + packobf/src/renamer.rs | 4 +- packobf/src/resource_pack/files/texture.rs | 4 +- .../src/resource_pack/files/unknowntexture.rs | 79 +------------------ packobf/src/shader_minifier/minifier.rs | 11 +-- 7 files changed, 16 insertions(+), 89 deletions(-) diff --git a/packobf/src/lib.rs b/packobf/src/lib.rs index 068bbcb..bf5a45b 100644 --- a/packobf/src/lib.rs +++ b/packobf/src/lib.rs @@ -498,7 +498,7 @@ fn parse_path(path: &str) -> (String, Identifier) { let overlay = if path.starts_with("assets/") { "".to_string() } else { - parts.next().unwrap().to_string() + parts.next().unwrap_or("").to_string() }; parts.next(); // skip assets diff --git a/packobf/src/optimized_zip_writer.rs b/packobf/src/optimized_zip_writer.rs index f5b5c9e..20369f2 100644 --- a/packobf/src/optimized_zip_writer.rs +++ b/packobf/src/optimized_zip_writer.rs @@ -188,11 +188,11 @@ impl OptimizedZipWriter { out } Compression::Normal => { - let pre_check_result = analyze_and_get_zopfli_config_normal(&data); + let pre_check_result = analyze_and_get_zopfli_config_normal(data); Self::compress_with_pre_check(data, hash, cache, input_size, pre_check_result)? } Compression::Best => { - let pre_check_result = analyze_and_get_zopfli_config_best(&data); + let pre_check_result = analyze_and_get_zopfli_config_best(data); Self::compress_with_pre_check(data, hash, cache, input_size, pre_check_result)? } Compression::Ultra => { diff --git a/packobf/src/options.rs b/packobf/src/options.rs index 400473f..6d1cd2b 100644 --- a/packobf/src/options.rs +++ b/packobf/src/options.rs @@ -220,6 +220,7 @@ fn analyze(data: &[u8]) -> Result<(usize, f64), PreCheckResult> { return Err(PreCheckResult::Skip); } + #[allow(clippy::unwrap_used)] let mut compressor = Compressor::new(CompressionLvl::new(9).unwrap()); let max_buf_len = compressor.deflate_compress_bound(original_size); let mut compressed_buf = vec![0u8; max_buf_len]; diff --git a/packobf/src/renamer.rs b/packobf/src/renamer.rs index 65e3b8d..6c2f27d 100644 --- a/packobf/src/renamer.rs +++ b/packobf/src/renamer.rs @@ -239,7 +239,7 @@ fn rebuild_atlas(pack: &ResourcePack) { atlas.sources.retain(|source| !matches!(source, Source::Directory { .. } | Source::Single { .. })); match atlas.atlas_type { AtlasType::Blocks => { - if atlas.overlay == "" { + if atlas.overlay.is_empty() { block_atlas_exists = true; } atlas.sources.push(Source::Directory { @@ -248,7 +248,7 @@ fn rebuild_atlas(pack: &ResourcePack) { }); } AtlasType::Items => { - if atlas.overlay == "" { + if atlas.overlay.is_empty() { item_atlas_exists = true; } atlas.sources.push(Source::Directory { diff --git a/packobf/src/resource_pack/files/texture.rs b/packobf/src/resource_pack/files/texture.rs index 29d2d52..cb84e04 100644 --- a/packobf/src/resource_pack/files/texture.rs +++ b/packobf/src/resource_pack/files/texture.rs @@ -31,8 +31,8 @@ impl Texture { ); let unknown_texture = UnknownTexture::new(path, bytes); Self { - overlay: overlay.into(), - identifier: identifier.into(), + overlay, + identifier, unknown_texture, } } diff --git a/packobf/src/resource_pack/files/unknowntexture.rs b/packobf/src/resource_pack/files/unknowntexture.rs index 79755ab..df87201 100644 --- a/packobf/src/resource_pack/files/unknowntexture.rs +++ b/packobf/src/resource_pack/files/unknowntexture.rs @@ -1,4 +1,3 @@ -use std::num::NonZeroU64; use std::time::Duration; use once_cell::sync::Lazy; use oxipng::{indexset, optimize_from_memory, Deflater, FilterStrategy, StripChunks}; @@ -15,74 +14,6 @@ pub struct UnknownTexture { pub bytes: Vec, } -/// Decodes PNG dimensions and estimates uncompressed IDAT size instantly from raw headers. -fn get_raw_uncompressed_size(bytes: &[u8]) -> Option { - if bytes.len() < 26 { - return None; - } - if bytes[0..8] != [137, 80, 78, 71, 13, 10, 26, 10] { - return None; - } - if &bytes[12..16] != b"IHDR" { - return None; - } - - let width = u32::from_be_bytes(bytes[16..20].try_into().ok()?) as usize; - let height = u32::from_be_bytes(bytes[20..24].try_into().ok()?) as usize; - let bit_depth = bytes[24] as usize; - let color_type = bytes[25]; - - let channels = match color_type { - 0 => 1, // Grayscale - 2 => 3, // RGB - 3 => 1, // Palette (indexed) - 4 => 2, // Grayscale + Alpha - 6 => 4, // RGBA - _ => 4, // Fallback - }; - - let bits_per_pixel = channels * bit_depth; - let row_bytes = (width * bits_per_pixel).div_ceil(8) + 1; - Some(row_bytes * height) -} - -/// Formula generated using a script -fn get_zopfli_options_normal(raw_size: usize) -> oxipng::ZopfliOptions { - let (iterations, without_improvement, splits) = if raw_size < 10_000 { - (10, 6, 1) - } else if raw_size < 100_000 { - (9, 3, 1) - } else if raw_size < 1_000_000 { - (9, 2, 1) - } else { - (9, 1, 2) - }; - - oxipng::ZopfliOptions { - iteration_count: NonZeroU64::new(iterations).unwrap(), - iterations_without_improvement: NonZeroU64::new(without_improvement).unwrap(), - maximum_block_splits: splits, - } -} - -fn get_zopfli_options_best(raw_size: usize) -> oxipng::ZopfliOptions { - let (iterations, without_improvement, splits) = if raw_size < 10_000 { - (10, 6, 1) - } else if raw_size < 100_000 { - (16, 6, 1) - } else if raw_size < 1_000_000 { - (20, 6, 2) - } else { - (22, 7, 2) - }; - - oxipng::ZopfliOptions { - iteration_count: NonZeroU64::new(iterations).unwrap(), - iterations_without_improvement: NonZeroU64::new(without_improvement).unwrap(), - maximum_block_splits: splits, - } -} - impl UnknownTexture { pub fn new(path: impl Into, bytes: Vec) -> Self { Self { @@ -145,16 +76,10 @@ impl UnknownTexture { Compression::Fastest => FASTEST_OPTIONS.clone(), Compression::Fast => FAST_OPTIONS.clone(), Compression::Normal => { - let mut opts = NORMAL_OPTIONS.clone(); - let raw_size = get_raw_uncompressed_size(bytes).unwrap_or(bytes.len()); - opts.deflater = Deflater::Zopfli(get_zopfli_options_normal(raw_size)); - opts + NORMAL_OPTIONS.clone() } Compression::Best => { - let mut opts = BEST_OPTIONS.clone(); - let raw_size = get_raw_uncompressed_size(bytes).unwrap_or(bytes.len()); - opts.deflater = Deflater::Zopfli(get_zopfli_options_best(raw_size)); - opts + BEST_OPTIONS.clone() } Compression::Ultra => { ULTRA_OPTIONS.clone() diff --git a/packobf/src/shader_minifier/minifier.rs b/packobf/src/shader_minifier/minifier.rs index 06cf928..6eed1f4 100644 --- a/packobf/src/shader_minifier/minifier.rs +++ b/packobf/src/shader_minifier/minifier.rs @@ -238,11 +238,12 @@ impl VisitorMut for Minifier { false }; if !has_forbidden_qualifier { - let name = list.head.name.clone().unwrap().0.to_string(); - - if !name.starts_with("gl_") { - if let Some(short) = self.local_mapping.get(&name) { - list.head.name = Some(IdentifierData::from(short.as_str()).into()); + if let Some(identifier) = list.head.name.clone() { + let name = identifier.0.to_string(); + if !name.starts_with("gl_") { + if let Some(short) = self.local_mapping.get(&name) { + list.head.name = Some(IdentifierData::from(short.as_str()).into()); + } } } From d94324bf4baa736fcb53c13474db51453c4b463d Mon Sep 17 00:00:00 2001 From: misieur Date: Fri, 31 Jul 2026 11:13:23 +0200 Subject: [PATCH 5/7] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 8d73cb7..e1c0e17 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ target # keep your zip files! **/*.zip + +packobf_gui/.qmlls.ini From deaecb65839b68710e4868e52ae9db53c20789e1 Mon Sep 17 00:00:00 2001 From: misieur Date: Fri, 31 Jul 2026 21:57:22 +0200 Subject: [PATCH 6/7] Rewrite IDAT with Zopfli for PNG files when using Normal/Best presets --- Cargo.lock | 703 ++++++++---------- packobf/Cargo.toml | 5 +- packobf/src/cache.rs | 28 +- packobf/src/lib.rs | 2 +- packobf/src/optimized_zip_writer.rs | 37 +- packobf/src/options.rs | 90 ++- packobf/src/png/crc.rs | 4 +- packobf/src/png/mod.rs | 3 +- packobf/src/png/recoverer.rs | 2 +- packobf/src/png/zopfli_png_idat_rewriter.rs | 204 +++++ packobf/src/renamer.rs | 10 +- packobf/src/resource_pack/files/sound.rs | 2 +- .../src/resource_pack/files/unknowntexture.rs | 104 ++- packobf/src/shader_minifier/minifier.rs | 2 +- packobf/src/usage_checker.rs | 2 +- 15 files changed, 654 insertions(+), 544 deletions(-) create mode 100644 packobf/src/png/zopfli_png_idat_rewriter.rs diff --git a/Cargo.lock b/Cargo.lock index e5134ca..2928743 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,9 +10,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" dependencies = [ "cipher", "cpubits", @@ -113,9 +113,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arbitrary" @@ -145,9 +145,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -160,14 +160,14 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "as-slice" @@ -208,7 +208,7 @@ dependencies = [ "num-traits", "pastey", "rayon", - "thiserror 2.0.18", + "thiserror 2.0.19", "v_frame", "y4m", ] @@ -259,9 +259,9 @@ checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bitstream-io" @@ -274,9 +274,9 @@ dependencies = [ [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -295,9 +295,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", "zeroize", @@ -305,9 +305,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" dependencies = [ "bytes", "cfg_aliases", @@ -327,9 +327,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" -version = "1.25.0" +version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" [[package]] name = "byteorder" @@ -345,9 +345,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bzip2" @@ -360,9 +360,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.63" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -378,15 +378,15 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -416,9 +416,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" dependencies = [ "clap_builder", "clap_derive", @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" dependencies = [ "anstream", "anstyle", @@ -439,14 +439,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -515,9 +515,9 @@ dependencies = [ [[package]] name = "console" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" dependencies = [ "encode_unicode", "libc", @@ -596,9 +596,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -606,18 +606,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crunchy" @@ -655,24 +655,24 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.194" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d8437319e3a2f43d93b341c137927ca70c0f5dabeea7a005a73665e247c7e" +checksum = "6fe442a792c7c736eea18b32a7f8a3b63cf8aafabda6760042dc2fdeda456291" dependencies = [ "cc", "cxx-build", "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", - "foldhash 0.2.0", + "foldhash", "link-cplusplus", ] [[package]] name = "cxx-build" -version = "1.0.194" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f4697d190a142477b16aef7da8a99bfdc41e7e8b1687583c0d23a79c7afc1e" +checksum = "e3184a94384c663718698311a78a51ac00c484c10b4eeac06fb0a068c5f64fa2" dependencies = [ "cc", "codespan-reporting 0.13.1", @@ -680,20 +680,20 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 3.0.3", ] [[package]] name = "cxx-gen" -version = "0.7.194" +version = "0.7.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "035b6c61a944483e8a4b2ad4fb8b13830d63491bd004943716ad16d85dcc64bc" +checksum = "fb0f24fd8cafa20f043e24372ca5d8273ab1fdce055ee977b989f82c1bcd89b5" dependencies = [ "codespan-reporting 0.13.1", "indexmap", "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -739,7 +739,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -773,39 +773,39 @@ dependencies = [ "cxx-qt-gen", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "cxxbridge-cmd" -version = "1.0.194" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0956799fa8678d4c50eed028f2de1c0552ae183c76e976cf7ca8c4e36a7c328" +checksum = "0148d8fd1199329ddf1d157a5e134e51ceff37c6a7ddd38615c399d81cb05d8d" dependencies = [ "clap", "codespan-reporting 0.13.1", "indexmap", "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "cxxbridge-flags" -version = "1.0.194" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23384a836ab4f0ad98ace7e3955ad2de39de42378ab487dc28d3990392cb283a" +checksum = "52850339faed2eaadd24e286dc1d8268cc6f8a7bd9524d713adc9099566b4c89" [[package]] name = "cxxbridge-macro" -version = "1.0.194" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf" +checksum = "2c77c856545d886c9bd5215409ebb63b925e262135248b50c79e5a5f194ee47c" dependencies = [ "indexmap", "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -829,7 +829,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.119", ] [[package]] @@ -840,7 +840,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -869,9 +869,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "derive_more" @@ -892,7 +889,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.119", "unicode-xid", ] @@ -912,7 +909,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid", "crypto-common 0.2.2", "ctutils", @@ -931,20 +928,20 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "ena" @@ -963,18 +960,19 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", + "regex", ] [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -999,7 +997,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1026,14 +1024,16 @@ checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "exr" -version = "1.74.0" +version = "1.74.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" dependencies = [ "bit_field", "half", "lebe", "miniz_oxide", + "num-complex", + "pulp", "rayon-core", "smallvec", "zune-inflate", @@ -1041,9 +1041,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fax" @@ -1089,12 +1089,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -1118,21 +1112,21 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-core", "futures-task", @@ -1185,16 +1179,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", "wasm-bindgen", ] @@ -1210,9 +1202,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "glsl-lang" @@ -1225,7 +1217,7 @@ dependencies = [ "lalrpop-util", "lang-util", "once_cell", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -1237,7 +1229,7 @@ dependencies = [ "glsl-lang-types", "lalrpop-util", "lang-util", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -1250,7 +1242,7 @@ dependencies = [ "lang-util", "string_cache", "string_cache_codegen", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -1259,7 +1251,7 @@ version = "0.8.1" source = "git+https://github.com/LostEngine/patched-libraries?branch=alixinne%2Fglsl-lang#71c58558796b74b62c3997924aba712c7dfeb5c8" dependencies = [ "lang-util", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] @@ -1279,15 +1271,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - [[package]] name = "hashbrown" version = "0.17.1" @@ -1317,9 +1300,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -1327,9 +1310,9 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "typenum", ] @@ -1440,12 +1423,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -1521,9 +1498,9 @@ dependencies = [ [[package]] name = "imgref" -version = "1.12.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" +checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" [[package]] name = "indexmap" @@ -1534,15 +1511,13 @@ dependencies = [ "equivalent", "hashbrown 0.17.1", "rayon", - "serde", - "serde_core", ] [[package]] name = "indicatif" -version = "0.18.4" +version = "0.18.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb" +checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" dependencies = [ "console", "portable-atomic", @@ -1577,7 +1552,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1613,7 +1588,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.18", + "thiserror 2.0.19", "walkdir", "windows-link", ] @@ -1628,7 +1603,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", + "syn 2.0.119", ] [[package]] @@ -1647,28 +1622,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1736,15 +1710,9 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "lebe" version = "0.5.3" @@ -1759,9 +1727,9 @@ checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libdeflate-sys" @@ -1783,14 +1751,20 @@ dependencies = [ [[package]] name = "libfuzzer-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" dependencies = [ "arbitrary", "cc", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "line-span" version = "0.1.5" @@ -1829,9 +1803,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.30" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "loop9" @@ -1844,9 +1818,9 @@ dependencies = [ [[package]] name = "lzma-rust2" -version = "0.16.3" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9ceaec84b54518262de7cf06b8b43e83c808349960f1610b21b0bfc9640f20" +checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b" dependencies = [ "sha2", ] @@ -1863,9 +1837,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "miniz_oxide" @@ -1879,9 +1853,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -1930,14 +1904,24 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -1952,7 +1936,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2092,7 +2076,7 @@ dependencies = [ "ouroboros", "rand_xoshiro", "slice-group-by", - "thiserror 2.0.18", + "thiserror 2.0.19", "tinyvec", "vorbis_bitpack", ] @@ -2118,7 +2102,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2150,6 +2134,7 @@ dependencies = [ "clap", "crc32fast", "dashmap", + "flate2", "glsl-lang", "image 0.25.10", "libdeflater", @@ -2291,7 +2276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.6", + "rand 0.8.7", ] [[package]] @@ -2314,7 +2299,7 @@ dependencies = [ "phf_shared 0.13.1", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2368,9 +2353,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "potential_utf" @@ -2408,21 +2393,11 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -2435,7 +2410,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "version_check", "yansi", ] @@ -2456,14 +2431,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + [[package]] name = "pxfm" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" [[package]] name = "qoi" @@ -2495,9 +2493,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -2522,18 +2520,18 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "rand_core 0.6.4", ] [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha", "rand_core 0.9.5", @@ -2600,10 +2598,10 @@ dependencies = [ "num-traits", "paste", "profiling", - "rand 0.9.4", + "rand 0.9.5", "rand_chacha", "simd_helpers", - "thiserror 2.0.18", + "thiserror 2.0.19", "v_frame", "wasm-bindgen", ] @@ -2623,6 +2621,15 @@ dependencies = [ "rgb", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + [[package]] name = "rayon" version = "1.12.0" @@ -2643,6 +2650,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2654,9 +2667,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -2666,9 +2679,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -2677,9 +2690,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rgb" @@ -2701,9 +2714,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -2729,9 +2742,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "same-file" @@ -2762,9 +2775,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2772,29 +2785,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -2853,15 +2866,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -2902,9 +2915,9 @@ checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "smol_str" @@ -2918,9 +2931,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -2994,14 +3007,25 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -3016,7 +3040,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3070,11 +3094,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -3085,18 +3109,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -3115,9 +3139,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", "js-sys", @@ -3129,9 +3153,9 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "tinystr" @@ -3145,9 +3169,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -3160,9 +3184,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -3177,13 +3201,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -3206,9 +3230,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -3260,9 +3284,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ "js-sys", "wasm-bindgen", @@ -3309,27 +3333,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -3340,9 +3355,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3350,60 +3365,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "web-time" version = "1.1.0" @@ -3450,7 +3431,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3461,7 +3442,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3571,100 +3552,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "writeable" version = "0.6.3" @@ -3728,28 +3621,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3769,15 +3662,15 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" @@ -3809,7 +3702,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3824,7 +3717,7 @@ dependencies = [ "crc32fast", "deflate64", "flate2", - "getrandom 0.4.2", + "getrandom 0.4.3", "hmac", "indexmap", "lzma-rust2", @@ -3841,15 +3734,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zopfli" diff --git a/packobf/Cargo.toml b/packobf/Cargo.toml index 37f9378..8e3b8e1 100644 --- a/packobf/Cargo.toml +++ b/packobf/Cargo.toml @@ -9,9 +9,9 @@ zip = "8.5.1" serde_json = "1.0.149" serde = { version = "1.0.228", features = ["derive"] } once_cell = "1.21.4" -oxipng = { version = "10.1.0", features = ["zopfli"] } +oxipng = { version = "10.1.1", features = ["zopfli"] } dashmap = { version = "6.1.0", features = ["rayon"] } -tokio = { version = "1.52.1", features = ["full"] } +tokio = { version = "1.53.1", features = ["full"] } glsl-lang = "0.8.1" smol_str = "0.3.6" byteorder = "1.5.0" @@ -29,6 +29,7 @@ clap = { version = "4.6.1", features = ["derive"] } strum = "0.28.0" strum_macros = "0.28.0" arc-swap = "1" +flate2 = { version = "1", default-features = false, features = ["rust_backend"] } [build-dependencies] phf_codegen = "0.13.1" diff --git a/packobf/src/cache.rs b/packobf/src/cache.rs index c9935b1..817b877 100644 --- a/packobf/src/cache.rs +++ b/packobf/src/cache.rs @@ -3,7 +3,7 @@ use sha2::{Digest, Sha256}; use std::fs::File; use std::io; use std::io::{BufReader, BufWriter, Read, Write}; - +use crate::options::Compression; use crate::profile_scope; const MAGIC_NUMBER: [u8; 10] = *b"PACKOBF001"; // Increase version number each time compression is changed (hex number) @@ -13,28 +13,6 @@ pub struct CachedItem { pub data: Vec, } -#[repr(u8)] -#[derive(Debug, Clone, Copy)] -pub enum Compression { - Fastest = 0, - Fast = 1, - Normal = 2, - Best = 3, - Ultra = 4, -} - -impl Compression { - fn from_u8(value: u8) -> Self { - match value { - 0 => Compression::Fastest, - 1 => Compression::Fast, - 2 => Compression::Normal, - 3 => Compression::Best, - _ => Compression::Ultra, - } - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct CachedItemKey { hash: [u8; 32], @@ -65,7 +43,7 @@ pub struct Cache { impl Cache { pub fn save_to_file(&self, path: &str) -> io::Result<()> { - profile_scope!("save_to_file::cache"); + profile_scope!(std::any::type_name_of_val(&Cache::save_to_file)); let file = File::create(path)?; let mut writer = BufWriter::new(file); @@ -97,7 +75,7 @@ impl Cache { } pub fn load_from_file(path: &str) -> io::Result { - profile_scope!("load_from_file::cache"); + profile_scope!(std::any::type_name_of_val(&Cache::load_from_file)); let file = File::open(path); if let Err(e) = file { return Err(e) diff --git a/packobf/src/lib.rs b/packobf/src/lib.rs index bf5a45b..88873f9 100644 --- a/packobf/src/lib.rs +++ b/packobf/src/lib.rs @@ -349,7 +349,7 @@ fn collect_files( pack: Arc, thread_pool: &ThreadPool, ) -> Vec<(String, ResourcePackItem)> { - profile_scope!("collect_files"); + profile_scope!(std::any::type_name_of_val(&collect_files)); thread_pool.install(|| { let texture_iter = pack.textures.par_iter().map(|kv| { ( diff --git a/packobf/src/optimized_zip_writer.rs b/packobf/src/optimized_zip_writer.rs index 20369f2..2d8c78b 100644 --- a/packobf/src/optimized_zip_writer.rs +++ b/packobf/src/optimized_zip_writer.rs @@ -51,7 +51,7 @@ impl OptimizedZipWriter { options: &Options, cache: &Option, ) -> io::Result<()> { - profile_scope!("add_file::zip"); + profile_scope!(std::any::type_name_of_val(&OptimizedZipWriter::::add_file)); let mut sha256 = Sha256::new(); sha256.update(data); let hash: [u8; 32] = sha256.finalize().into(); @@ -160,7 +160,7 @@ impl OptimizedZipWriter { cache.add_item_hash( hash, &*out, - crate::cache::Compression::Fastest as u8, + Compression::Fastest as u8, ItemType::Generic, ) } @@ -181,7 +181,7 @@ impl OptimizedZipWriter { cache.add_item_hash( hash, &*out, - crate::cache::Compression::Fast as u8, + Compression::Fast as u8, ItemType::Generic, ) } @@ -211,7 +211,7 @@ impl OptimizedZipWriter { cache.add_item_hash( hash, &*out, - crate::cache::Compression::Ultra as u8, + Compression::Ultra as u8, ItemType::Generic, ) } @@ -238,24 +238,45 @@ impl OptimizedZipWriter { cache.add_item_hash( hash, &*out, - crate::cache::Compression::Best as u8, + Compression::Best as u8, ItemType::Generic, ) } out } + PreCheckResult::LibDeflater => { + let mut compressor = libdeflater::Compressor::new(CompressionLvl::best()); + let mut out = vec![0u8; compressor.deflate_compress_bound(data.len())]; + let size = compressor + .deflate_compress(data, &mut out) + .map_err(|_| Error::other("Compression failed"))?; + out.truncate(size); + let out_size = out.len(); + if out_size > input_size { + out = vec![]; + } + if let Some(cache) = cache { + cache.add_item_hash( + hash, + &*out, + Compression::Fast as u8, + ItemType::Generic, + ) + } + out + }, Skip => { let out = vec![]; if let Some(cache) = cache { cache.add_item_hash( hash, &*out, - crate::cache::Compression::Best as u8, + Compression::Best as u8, ItemType::Generic, ) } out - } + }, }) } @@ -277,7 +298,7 @@ impl OptimizedZipWriter { /// Writes the Central Directory and End of Central Directory (EOCD) records. /// This finalizes the ZIP file, making it valid for CD-parsing tools. pub fn finish(&self) -> io::Result<()> { - profile_scope!("finish::zip"); + profile_scope!(std::any::type_name_of_val(&OptimizedZipWriter::::finish)); let mut inner_guard = self.inner.lock().unwrap_or_else(|e| e.into_inner()); let Inner { ref mut writer, diff --git a/packobf/src/options.rs b/packobf/src/options.rs index 6d1cd2b..afa7870 100644 --- a/packobf/src/options.rs +++ b/packobf/src/options.rs @@ -84,7 +84,7 @@ impl Options { } #[repr(u8)] -#[derive(ValueEnum, Clone, Debug)] +#[derive(ValueEnum, Clone, Debug, Copy)] pub enum Compression { Fastest = 0, Fast = 1, @@ -93,6 +93,18 @@ pub enum Compression { Ultra = 4, } +impl Compression { + pub fn from_u8(value: u8) -> Self { + match value { + 0 => Compression::Fastest, + 1 => Compression::Fast, + 2 => Compression::Normal, + 3 => Compression::Best, + _ => Compression::Ultra, + } + } +} + #[repr(u8)] #[derive(ValueEnum, Clone, Debug)] #[derive(PartialEq)] @@ -156,6 +168,8 @@ pub enum PreCheckResult { Skip, /// Use Zopfli with dynamically assigned options CompressWithZopfli(zopfli::Options), + /// Use Libdeflater Level 12 + LibDeflater, } /// Pre-checks data compressibility using libdeflater (Level 9) @@ -166,24 +180,7 @@ pub fn analyze_and_get_zopfli_config_normal(data: &[u8]) -> PreCheckResult { Err(value) => return value, }; - // Less than 1% savings - if savings_ratio < 0.01 { - return PreCheckResult::Skip; // Don't waste CPU time on Zopfli - } - - // 1% to 8% savings - if savings_ratio < 0.08 { - return PreCheckResult::CompressWithZopfli(FASTEST_ZOPFLI_OPTIONS.to_owned()); - } - - // > 8% savings - PreCheckResult::CompressWithZopfli(match original_size { - 0..=51_200 => SLOW_ZOPFLI_OPTIONS.to_owned(), - - 51_201..=512_000 => NORMAL_ZOPFLI_OPTIONS.to_owned(), - - _ => FAST_ZOPFLI_OPTIONS.to_owned(), - }) + get_normal_precheck_result(savings_ratio, original_size) } /// Pre-checks data compressibility using libdeflater (Level 9) @@ -194,6 +191,31 @@ pub fn analyze_and_get_zopfli_config_best(data: &[u8]) -> PreCheckResult { Err(value) => return value, }; + get_best_pre_check_result(savings_ratio, original_size) +} + +fn analyze(data: &[u8]) -> Result<(usize, f64), PreCheckResult> { + let original_size = data.len(); + if original_size == 0 { + return Err(PreCheckResult::Skip); + } + + #[allow(clippy::unwrap_used)] + let mut compressor = Compressor::new(CompressionLvl::new(9).unwrap()); + let max_buf_len = compressor.deflate_compress_bound(original_size); + let mut compressed_buf = vec![0u8; max_buf_len]; + + let fast_compressed_size = match compressor.deflate_compress(data, &mut compressed_buf) { + Ok(sz) => sz, + Err(_) => return Err(PreCheckResult::Skip), + }; + + let bytes_saved = original_size.saturating_sub(fast_compressed_size); + let savings_ratio = bytes_saved as f64 / original_size as f64; + Ok((original_size, savings_ratio)) +} + +pub fn get_best_pre_check_result(savings_ratio: f64, original_size: usize) -> PreCheckResult { // Less than 1% savings if savings_ratio < 0.01 { return PreCheckResult::Skip; // Don't waste CPU time on Zopfli @@ -214,23 +236,23 @@ pub fn analyze_and_get_zopfli_config_best(data: &[u8]) -> PreCheckResult { }) } -fn analyze(data: &[u8]) -> Result<(usize, f64), PreCheckResult> { - let original_size = data.len(); - if original_size == 0 { - return Err(PreCheckResult::Skip); +pub fn get_normal_precheck_result(savings_ratio: f64, original_size: usize) -> PreCheckResult { + // Less than 1% savings + if savings_ratio < 0.01 { + return PreCheckResult::Skip; // Don't waste CPU time on Zopfli } - #[allow(clippy::unwrap_used)] - let mut compressor = Compressor::new(CompressionLvl::new(9).unwrap()); - let max_buf_len = compressor.deflate_compress_bound(original_size); - let mut compressed_buf = vec![0u8; max_buf_len]; + // 1% to 8% savings + if savings_ratio < 0.08 { + return PreCheckResult::LibDeflater; + } - let fast_compressed_size = match compressor.deflate_compress(data, &mut compressed_buf) { - Ok(sz) => sz, - Err(_) => return Err(PreCheckResult::Skip), - }; + // > 8% savings + PreCheckResult::CompressWithZopfli(match original_size { + 0..=51_200 => NORMAL_ZOPFLI_OPTIONS.to_owned(), - let bytes_saved = original_size.saturating_sub(fast_compressed_size); - let savings_ratio = bytes_saved as f64 / original_size as f64; - Ok((original_size, savings_ratio)) + 51_201..=512_000 => FAST_ZOPFLI_OPTIONS.to_owned(), + + _ => FASTEST_ZOPFLI_OPTIONS.to_owned(), + }) } diff --git a/packobf/src/png/crc.rs b/packobf/src/png/crc.rs index 2c04133..6bfae55 100644 --- a/packobf/src/png/crc.rs +++ b/packobf/src/png/crc.rs @@ -1,9 +1,11 @@ use crate::profile_scope; +/// [PNG Specification, version 3.0](https://www.w3.org/TR/png-3/) + /// This function modifies the PNG CRCs to make them invalid and removes the IEND CRC. /// Doing this will break most PNG file readers, but Minecraft doesn't care about CRC. pub fn modify_png_crcs(input: &[u8]) -> Result, &'static str> { - profile_scope!("modify_png_crcs"); + profile_scope!(std::any::type_name_of_val(&modify_png_crcs)); const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; if input.len() < 8 || input[0..8] != PNG_SIGNATURE { diff --git a/packobf/src/png/mod.rs b/packobf/src/png/mod.rs index 0d338f6..3f971ec 100644 --- a/packobf/src/png/mod.rs +++ b/packobf/src/png/mod.rs @@ -1,2 +1,3 @@ pub mod crc; -pub mod recoverer; \ No newline at end of file +pub mod recoverer; +pub mod zopfli_png_idat_rewriter; \ No newline at end of file diff --git a/packobf/src/png/recoverer.rs b/packobf/src/png/recoverer.rs index ccc7506..9df9cfa 100644 --- a/packobf/src/png/recoverer.rs +++ b/packobf/src/png/recoverer.rs @@ -4,7 +4,7 @@ use image::{ExtendedColorType, ImageEncoder}; use crate::profile_scope; pub fn recover_png(input: &[u8]) -> Result, String> { - profile_scope!("recover_png"); + profile_scope!(std::any::type_name_of_val(&recover_png)); unsafe { let mut width: i32 = 0; let mut height: i32 = 0; diff --git a/packobf/src/png/zopfli_png_idat_rewriter.rs b/packobf/src/png/zopfli_png_idat_rewriter.rs new file mode 100644 index 0000000..7719e9e --- /dev/null +++ b/packobf/src/png/zopfli_png_idat_rewriter.rs @@ -0,0 +1,204 @@ +use crate::options::{Compression, PreCheckResult}; +use crate::{options, profile_scope, LogLevel, LogMessage}; +use byteorder::{BigEndian, WriteBytesExt}; +use libdeflater::{crc32, CompressionLvl}; +use std::io::{Error, Write, Read}; +use tokio::sync::mpsc::UnboundedSender; +use zopfli::{Options, ZlibEncoder}; +use flate2::read::DeflateDecoder; + +/// [PNG Specification, version 3.0](https://www.w3.org/TR/png-3/) + +/// Re-compresses deflate idat using zopfli with dynamic options +/// See [options::analyze](crate::options::analyze). +/// The input is only meant to be generated by oxipng as it does +/// not fully support the format but what oxipng generates. +pub fn rewrite_idat_with_zopfli( + input: &[u8], + compression: &Compression, + logger: &UnboundedSender, +) -> Vec { + profile_scope!(std::any::type_name_of_val(&rewrite_idat_with_zopfli)); + const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; + + if input.len() < 8 || input[0..8] != PNG_SIGNATURE { + return input.into(); + } + + let mut output = Vec::with_capacity(input.len()); + output.extend_from_slice(&PNG_SIGNATURE); + + let mut offset = 8; + + while offset < input.len() { + if offset + 8 > input.len() { + return input.into(); + } + + let length = u32::from_be_bytes([ + input[offset], + input[offset + 1], + input[offset + 2], + input[offset + 3], + ]) as usize; + + let chunk_type = &input[offset + 4..offset + 8]; + + let chunk_end = offset + 8 + length; + let crc_end = chunk_end + 4; + if crc_end > input.len() { + return input.into(); + } + + if chunk_type == b"IDAT" { + let chunk_start = offset + 8; + // See [zlib data format](https://en.wikipedia.org/wiki/Zlib#Data_format). + // DICTID is not supported here, but it should never be used. + let data = &input[ + chunk_start + 2 // removes the zlib header + .. + chunk_end - 4 // removes the zlib checksum + ]; + let mut decoder = DeflateDecoder::new(data); + + let mut original_data = Vec::new(); + match decoder.read_to_end(&mut original_data) { + Ok(original_size) => { + let data_size = data.len(); + let bytes_saved = original_size.saturating_sub(data_size); + let savings_ratio = bytes_saved as f64 / original_size as f64; + + match compression { + Compression::Normal => { + let result = + options::get_normal_precheck_result(savings_ratio, original_size); + compress_data_and_write_idat(input, logger, &mut output, original_data.as_slice(), offset, crc_end, data_size, result); + } + Compression::Best => { + let result = + options::get_best_pre_check_result(savings_ratio, original_size); + compress_data_and_write_idat(input, logger, &mut output, original_data.as_slice(), offset, crc_end, data_size, result); + } + compression => { + let _ = logger.send(LogMessage { + level: LogLevel::Error, + message: format!( + "{} called with {:?}", + std::any::type_name_of_val(&rewrite_idat_with_zopfli), + compression + ), + }); + return input.into(); + } + } + } + Err(e) => { + let _ = logger.send(LogMessage { + level: LogLevel::Error, + message: format!("Failed to decompress PNG IDAT data: {:?}", e), + }); + return input.into(); + } + } + } else { + output.extend_from_slice(&input[offset..crc_end]); + } + + offset = crc_end; + } + + output +} + +fn compress_data_and_write_idat(input: &[u8], logger: &UnboundedSender, mut output: &mut Vec, original_data: &[u8], offset: usize, crc_end: usize, data_size: usize, result: PreCheckResult) { + let out = match result { + PreCheckResult::CompressWithZopfli(options) => { + match compress(original_data, options) { + Ok(out) => out, + Err(e) => { + let _ = logger.send(LogMessage { + level: LogLevel::Error, + message: format!( + "Failed to compress PNG data with zopfli: {:?}", + e + ), + }); + output.extend_from_slice(&input[offset..crc_end]); + return; + } + } + }, + PreCheckResult::LibDeflater => { + let mut compressor = libdeflater::Compressor::new(CompressionLvl::best()); + let mut out = vec![0u8; compressor.zlib_compress_bound(data_size)]; + let size = match compressor + .zlib_compress(original_data, &mut out) { + Ok(out) => out, + Err(e) => { + let _ = logger.send(LogMessage { + level: LogLevel::Error, + message: format!( + "Failed to compress PNG data with libdeflate: {:?}", + e + ), + }); + output.extend_from_slice(&input[offset..crc_end]); + return; + } + }; + out.truncate(size); + out + } + PreCheckResult::Skip => { + output.extend_from_slice(&input[offset..crc_end]); + return; + }, + }; + let out_size = out.len(); + if out_size > data_size + 6 + // Add 6 bytes for header and checksum + { + output.extend_from_slice(&input[offset..crc_end]); + return; + } + match write_idat(&mut output, out_size, out) { + Ok(_) => {} + Err(e) => { + let _ = logger.send(LogMessage { + level: LogLevel::Error, + message: format!( + "Failed to write PNG IDAT: {:?}", + e + ), + }); + output.extend_from_slice(&input[offset..crc_end]); + } + } +} + +fn write_idat(output: &mut Vec, out_size: usize, data: Vec) -> std::io::Result<()> { + output.write_u32::(out_size as u32)?; + output.extend_from_slice(b"IDAT"); + output.extend_from_slice(data.as_slice()); + output.write_u32::(crc32(&output[output.len() - out_size - b"IDAT".len()..]))?; + Ok(()) +} + +fn compress(original_data: &[u8], options: Options) -> Result, Error> { + let mut encoder = ZlibEncoder::new(options, zopfli::BlockType::Dynamic, Vec::new())?; + encoder.write_all(original_data)?; + let out = encoder.finish()?; + Ok(out) +} + +#[macro_export] +macro_rules! compress { + ($options:expr) => {{}}; +} + +#[macro_export] +macro_rules! stop { + ($output:expr, $input:expr, $offset:expr) => {{ + input + }}; +} diff --git a/packobf/src/renamer.rs b/packobf/src/renamer.rs index 6c2f27d..a2f2c10 100644 --- a/packobf/src/renamer.rs +++ b/packobf/src/renamer.rs @@ -18,7 +18,7 @@ pub fn rename_files( pack: &ResourcePack, mapping: &mut Mapping, ) { - profile_scope!("rename_files"); + profile_scope!(std::any::type_name_of_val(&rename_files)); let id_counter = &mapping::get_id_usage_counter(); rayon::scope(|s| { s.spawn(|_| rename_overlays(pack, &mut mapping.overlay_mappings)); @@ -29,7 +29,7 @@ pub fn rename_files( } fn rename_overlays(pack: &ResourcePack, mapping: &mut HashMap) { - profile_scope!("rename_files::overlays"); + profile_scope!(std::any::type_name_of_val(&rename_overlays)); let mut count = 0; if let Some(mut mcmeta) = pack.json_files.get_mut("pack.mcmeta") { if let Some(entries) = mcmeta @@ -58,7 +58,7 @@ fn rename_sounds( mapping: &mut HashMap, id_counter: &mapping::IdUsageCounter, ) { - profile_scope!("rename_files::sounds"); + profile_scope!(std::any::type_name_of_val(&rename_sounds)); let mut sounds: Vec<(String, Sound)> = pack .sounds .iter() @@ -106,7 +106,7 @@ fn rename_textures( mapping: &mut HashMap, id_counter: &mapping::IdUsageCounter, ) { - profile_scope!("rename_files::textures"); + profile_scope!(std::any::type_name_of_val(&rename_textures)); let mut textures: Vec<(String, Texture)> = pack .textures .iter() @@ -288,7 +288,7 @@ fn rename_models( mapping: &mut HashMap, id_counter: &mapping::IdUsageCounter, ) { - profile_scope!("rename_files::models"); + profile_scope!(std::any::type_name_of_val(&rename_models)); let mut models: Vec<(String, Model)> = pack .models .iter() diff --git a/packobf/src/resource_pack/files/sound.rs b/packobf/src/resource_pack/files/sound.rs index cbc03cd..104b999 100644 --- a/packobf/src/resource_pack/files/sound.rs +++ b/packobf/src/resource_pack/files/sound.rs @@ -34,7 +34,7 @@ impl Sound { logger: &tokio::sync::mpsc::UnboundedSender, cache: &Option, ) { - profile_scope!("optimize::sound"); + profile_scope!(std::any::type_name_of_val(&Sound::optimize)); if let Some(cache) = cache { let mut sha256 = Sha256::new(); sha256.update(self.bytes.as_slice()); diff --git a/packobf/src/resource_pack/files/unknowntexture.rs b/packobf/src/resource_pack/files/unknowntexture.rs index df87201..9dacacd 100644 --- a/packobf/src/resource_pack/files/unknowntexture.rs +++ b/packobf/src/resource_pack/files/unknowntexture.rs @@ -1,12 +1,15 @@ -use std::time::Duration; -use once_cell::sync::Lazy; -use oxipng::{indexset, optimize_from_memory, Deflater, FilterStrategy, StripChunks}; -use sha2::{Digest, Sha256}; use crate::cache::{Cache, ItemType}; -use crate::LogLevel::{Info, Warning}; -use crate::{options, profile_scope, LogMessage}; -use crate::options::{Compression, Options}; +use crate::options::{Compression, Options, ULTRA_ZOPFLI_OPTIONS}; use crate::png::{crc, recoverer}; +use crate::LogLevel::{Info, Warning}; +use crate::{profile_scope, LogMessage}; +use once_cell::sync::Lazy; +use oxipng::{indexset, optimize_from_memory, Deflater, FilterStrategy, PngError, StripChunks}; +use sha2::{Digest, Sha256}; +use std::borrow::ToOwned; +use std::time::Duration; +use tokio::sync::mpsc::UnboundedSender; +use crate::png::zopfli_png_idat_rewriter::rewrite_idat_with_zopfli; #[derive(Clone, Debug)] pub struct UnknownTexture { @@ -28,7 +31,8 @@ impl UnknownTexture { logger: &tokio::sync::mpsc::UnboundedSender, cache: &Option, ) { - self.bytes = Self::cache_or_optimize(&self.bytes, options, logger, cache, self.path.as_str()); + self.bytes = + Self::cache_or_optimize(&self.bytes, options, logger, cache, self.path.as_str()); if options.corrupt_png_files { match crc::modify_png_crcs(&self.bytes) { Ok(bytes) => { @@ -37,7 +41,11 @@ impl UnknownTexture { Err(e) => { let _ = logger.send(LogMessage { level: Warning, - message: format!("Could not corrupt image '{}'. Error: {}", self.path.as_str(), e), + message: format!( + "Could not corrupt image '{}'. Error: {}", + self.path.as_str(), + e + ), }); } } @@ -47,11 +55,11 @@ impl UnknownTexture { fn cache_or_optimize( bytes: &[u8], options: &Options, - logger: &tokio::sync::mpsc::UnboundedSender, + logger: &UnboundedSender, cache: &Option, path: &str, ) -> Vec { - profile_scope!("cache_or_optimize::texture"); + profile_scope!(std::any::type_name_of_val(&Self::cache_or_optimize)); if let Some(cache) = cache { let mut sha256 = Sha256::new(); sha256.update(bytes); @@ -75,19 +83,19 @@ impl UnknownTexture { let oxipng_options = match options.compression { Compression::Fastest => FASTEST_OPTIONS.clone(), Compression::Fast => FAST_OPTIONS.clone(), - Compression::Normal => { - NORMAL_OPTIONS.clone() - } - Compression::Best => { - BEST_OPTIONS.clone() - } - Compression::Ultra => { - ULTRA_OPTIONS.clone() - } + Compression::Normal => ANALYZE_OPTIONS.clone(), + Compression::Best => ANALYZE_OPTIONS.clone(), + Compression::Ultra => ULTRA_OPTIONS.clone(), }; - match optimize_from_memory(bytes, &oxipng_options) { + match optimize(bytes, &oxipng_options, &options.compression, logger) { Ok(value) => { + match options.compression { + Compression::Normal | Compression::Best => { + + } + _ => {} + } if let Some(cache) = cache { cache.add_item( bytes, @@ -112,7 +120,7 @@ impl UnknownTexture { level: Info, message: format!("Image '{}' was recovered successfully.", path), }); - match optimize_from_memory(value.as_slice(), &oxipng_options) { + match optimize(value.as_slice(), &oxipng_options, &options.compression, logger) { Ok(value) => { if let Some(cache) = cache { cache.add_item( @@ -152,6 +160,17 @@ impl UnknownTexture { } } +fn optimize(data: &[u8], opts: &oxipng::Options, compression: &Compression, logger: &UnboundedSender) -> Result, PngError> { + let mut data = optimize_from_memory(data, &opts)?; + match compression { + Compression::Normal | Compression::Best => { + data = rewrite_idat_with_zopfli(data.as_slice(), compression, logger); + } + _ => {} + } + Ok(data) +} + // static FASTEST_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { fix_errors: true, @@ -219,7 +238,9 @@ static FAST_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { max_decompressed_size: None, }); -static NORMAL_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { +/// Libdeflater (Level 9) is used to determine which zopfli options are going to be used. +/// See [options::analyze](crate::options::analyze). +static ANALYZE_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { fix_errors: true, force: false, filters: indexset! { @@ -246,45 +267,12 @@ static NORMAL_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { idat_recoding: true, scale_16: false, strip: StripChunks::All, - deflater: Deflater::Zopfli(options::NORMAL_ZOPFLI_OPTIONS.to_owned()), + deflater: Deflater::Libdeflater { compression: 9 }, fast_evaluation: false, timeout: Some(Duration::from_secs(3)), max_decompressed_size: None, }); -static BEST_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { - fix_errors: true, - force: false, - filters: indexset! { - FilterStrategy::NONE, - FilterStrategy::SUB, - FilterStrategy::UP, - FilterStrategy::AVERAGE, - FilterStrategy::PAETH, - FilterStrategy::MinSum, - FilterStrategy::Entropy, - FilterStrategy::Bigrams, - FilterStrategy::BigEnt, - FilterStrategy::Brute { - num_lines: 8, - level: 12, - }, - }, - interlace: Some(false), - optimize_alpha: true, - bit_depth_reduction: true, - color_type_reduction: true, - palette_reduction: true, - grayscale_reduction: true, - idat_recoding: true, - scale_16: false, - strip: StripChunks::All, - deflater: Deflater::Zopfli(options::SLOW_ZOPFLI_OPTIONS.to_owned()), - fast_evaluation: false, - timeout: None, - max_decompressed_size: None, -}); - static ULTRA_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { fix_errors: true, force: false, @@ -312,7 +300,7 @@ static ULTRA_OPTIONS: Lazy = Lazy::new(|| oxipng::Options { idat_recoding: true, scale_16: false, strip: StripChunks::All, - deflater: Deflater::Zopfli(options::ULTRA_ZOPFLI_OPTIONS.to_owned()), + deflater: Deflater::Zopfli(ULTRA_ZOPFLI_OPTIONS.to_owned()), fast_evaluation: false, timeout: Some(Duration::from_secs(3)), max_decompressed_size: None, diff --git a/packobf/src/shader_minifier/minifier.rs b/packobf/src/shader_minifier/minifier.rs index 6eed1f4..56ff7bb 100644 --- a/packobf/src/shader_minifier/minifier.rs +++ b/packobf/src/shader_minifier/minifier.rs @@ -55,7 +55,7 @@ impl Minifier { source: &str, rename: bool, ) -> Result> { - profile_scope!("minify_shader"); + profile_scope!(std::any::type_name_of_val(&Minifier::minify)); let mut ast = TranslationUnit::parse(source).map_err(|e| format!("GLSL Parse Error: {}", e))?; diff --git a/packobf/src/usage_checker.rs b/packobf/src/usage_checker.rs index 4e51586..e3e5143 100644 --- a/packobf/src/usage_checker.rs +++ b/packobf/src/usage_checker.rs @@ -9,7 +9,7 @@ use std::sync::atomic::AtomicUsize; use tokio::sync::mpsc::UnboundedSender; pub fn check_usage(logger: &UnboundedSender, pack: &ResourcePack) { - profile_scope!("check_usage"); + profile_scope!(std::any::type_name_of_val(&check_usage)); let counter = mapping::get_id_usage_counter(); rayon::scope(|s| { From 7cfef66830ca21241a03d65080a40ac9d1eab47f Mon Sep 17 00:00:00 2001 From: misieur Date: Fri, 31 Jul 2026 22:03:23 +0200 Subject: [PATCH 7/7] [ci skip] Add TODO --- packobf/src/png/zopfli_png_idat_rewriter.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packobf/src/png/zopfli_png_idat_rewriter.rs b/packobf/src/png/zopfli_png_idat_rewriter.rs index 7719e9e..f63abe9 100644 --- a/packobf/src/png/zopfli_png_idat_rewriter.rs +++ b/packobf/src/png/zopfli_png_idat_rewriter.rs @@ -59,6 +59,7 @@ pub fn rewrite_idat_with_zopfli( .. chunk_end - 4 // removes the zlib checksum ]; + // TODO: Use IHDR to calculate the decompressed size so we don't need flat2 anymore and we can use libdeflate let mut decoder = DeflateDecoder::new(data); let mut original_data = Vec::new();