Skip to content

Commit 8a68d0b

Browse files
committed
Use smaller names for assets referenced a lot, make rename mappings be deterministic, fix mapping issues when optimizing two resource packs without restarting.
1 parent add6f64 commit 8a68d0b

7 files changed

Lines changed: 177 additions & 89 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packobf/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ image = "0.25.10"
2828
clap = { version = "4.6.1", features = ["derive"] }
2929
strum = "0.28.0"
3030
strum_macros = "0.28.0"
31+
arc-swap = "1"
3132

3233
[build-dependencies]
3334
phf_codegen = "0.13.1"

packobf/src/lib.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
pub mod cache;
2+
mod file_parser;
23
pub mod minecraft;
34
pub mod optimized_zip_writer;
45
pub mod options;
56
pub mod png;
67
pub mod renamer;
78
pub mod resource_pack;
89
pub mod shader_minifier;
9-
pub mod utils;
10-
mod file_parser;
1110
pub mod usage_checker;
11+
pub mod utils;
1212

1313
use crate::cache::Cache;
1414
use crate::optimized_zip_writer::OptimizedZipWriter;
@@ -74,7 +74,12 @@ pub fn process_zip(
7474
let id_usage_counter = IdUsageCounter::default();
7575
mapping::set_id_usage_counter(id_usage_counter);
7676

77-
file_parser::parse_resource_pack_files(logger, &mut entries, progress.clone(), Arc::clone(&pack));
77+
file_parser::parse_resource_pack_files(
78+
logger,
79+
&mut entries,
80+
progress.clone(),
81+
Arc::clone(&pack),
82+
);
7883

7984
usage_checker::check_usage(logger, &pack);
8085

@@ -121,7 +126,7 @@ pub fn process_zip(
121126
if !name.starts_with("assets/") {
122127
let mut parts = name.split('/');
123128
let overlay = parts.next().unwrap().to_string();
124-
if let Some(value) = mapping::GLOBAL_MAPPING.get().expect("Mappings not initialized").overlay_mappings.get(&overlay) {
129+
if let Some(value) = mapping::get_mappings().overlay_mappings.get(&overlay) {
125130
let rest = parts.collect::<Vec<_>>().join("/");
126131
*name = format!("{}/{}", value, rest);
127132
}

packobf/src/renamer.rs

Lines changed: 117 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::resource_pack::files::model::Model;
66
use crate::resource_pack::files::sound::Sound;
77
use crate::resource_pack::files::texture::Texture;
88
use crate::resource_pack::identifier::Identifier;
9-
use crate::resource_pack::mapping::Mapping;
9+
use crate::resource_pack::mapping::{self, Mapping};
1010
use crate::resource_pack::resource_pack::ResourcePack;
1111
use crate::{LogLevel, LogMessage};
1212
use serde_json::json;
@@ -18,10 +18,11 @@ pub fn rename_files(
1818
pack: &ResourcePack,
1919
mapping: &mut Mapping,
2020
) {
21+
let id_counter = &mapping::get_id_usage_counter();
2122
rename_overlays(pack, mapping);
22-
rename_models(pack, mapping);
23-
rename_textures(logger, &pack, mapping);
24-
rename_sounds(pack, mapping);
23+
rename_models(pack, mapping, id_counter);
24+
rename_textures(logger, &pack, mapping, id_counter);
25+
rename_sounds(pack, mapping, id_counter);
2526
}
2627

2728
fn rename_overlays(pack: &ResourcePack, mapping: &mut Mapping) {
@@ -51,31 +52,46 @@ fn rename_overlays(pack: &ResourcePack, mapping: &mut Mapping) {
5152
}
5253
}
5354

54-
fn rename_sounds(pack: &ResourcePack, mapping: &mut Mapping) {
55-
let mut count = 0;
56-
for x in pack.sounds.clone().iter() {
57-
let identifier = x.identifier.to_string();
58-
if x.identifier.namespace == "minecraft" {
59-
// Skip sounds if they are overwriting Minecraft files
60-
if builtin_files::is_in_sounds(identifier.as_str()) {
61-
continue;
62-
}
63-
}
55+
fn rename_sounds(pack: &ResourcePack, mapping: &mut Mapping, id_counter: &mapping::IdUsageCounter) {
56+
let mut sounds: Vec<(String, Sound)> = pack
57+
.sounds
58+
.iter()
59+
.map(|entry| (entry.key().clone(), entry.value().clone()))
60+
.collect();
61+
62+
sounds.retain(|(_, x)| {
63+
!(x.identifier.namespace == "minecraft"
64+
&& builtin_files::is_in_sounds(x.identifier.to_string().as_str()))
65+
});
66+
67+
sounds.sort_by(|(_, a), (_, b)| {
68+
let a_id = a.identifier.to_string();
69+
let b_id = b.identifier.to_string();
70+
71+
let a_usage = id_counter.get_usage_count(&a_id, mapping::IdCategory::Sound);
72+
let b_usage = id_counter.get_usage_count(&b_id, mapping::IdCategory::Sound);
73+
74+
// Higher usage first
75+
b_usage
76+
.cmp(&a_usage)
77+
// Stable deterministic fallback so the same resource pack is generated each time
78+
.then_with(|| a_id.cmp(&b_id))
79+
});
80+
81+
for (count, (key, mut sound)) in sounds.into_iter().enumerate() {
82+
let identifier = sound.identifier.to_string();
6483
if let Some(mapped) = mapping.sound_mappings.get(&identifier) {
65-
let mut new_sound: Sound = x.clone();
66-
new_sound.identifier.path = mapped.clone();
67-
pack.sounds.remove(&x.key().to_string());
68-
pack.sounds.insert(new_sound.path(), new_sound);
84+
sound.identifier.path = mapped.clone();
85+
pack.sounds.remove(&key);
86+
pack.sounds.insert(sound.path(), sound);
6987
} else {
7088
let new_identifier = Identifier::new("_", generate_short_name(count));
7189
mapping
7290
.sound_mappings
7391
.insert(identifier, new_identifier.to_string());
74-
let mut new_sound: Sound = x.clone();
75-
new_sound.identifier = new_identifier;
76-
pack.sounds.remove(&x.key().to_string());
77-
pack.sounds.insert(new_sound.path(), new_sound);
78-
count += 1;
92+
sound.identifier = new_identifier;
93+
pack.sounds.remove(&key);
94+
pack.sounds.insert(sound.path(), sound);
7995
}
8096
}
8197
}
@@ -84,56 +100,72 @@ fn rename_textures(
84100
logger: &UnboundedSender<LogMessage>,
85101
pack: &&ResourcePack,
86102
mapping: &mut Mapping,
103+
id_counter: &mapping::IdUsageCounter,
87104
) {
105+
let mut textures: Vec<(String, Texture)> = pack
106+
.textures
107+
.iter()
108+
.map(|entry| (entry.key().clone(), entry.value().clone()))
109+
.collect();
110+
111+
textures.retain(|(_, x)| {
112+
!(x.identifier.namespace == "minecraft"
113+
&& builtin_files::is_in_textures(x.identifier.to_string().as_str()))
114+
});
115+
116+
textures.sort_by(|(_, a), (_, b)| {
117+
let a_id = a.identifier.to_string();
118+
let b_id = b.identifier.to_string();
119+
120+
let a_usage = id_counter.get_usage_count(&a_id, mapping::IdCategory::Texture);
121+
let b_usage = id_counter.get_usage_count(&b_id, mapping::IdCategory::Texture);
122+
123+
// Higher usage first
124+
b_usage
125+
.cmp(&a_usage)
126+
// Stable deterministic fallback so the same resource pack is generated each time
127+
.then_with(|| a_id.cmp(&b_id))
128+
});
129+
88130
let mut per_folder_count: HashMap<String, usize> = HashMap::new();
89131
let font_textures = get_font_textures(pack);
90-
for x in pack.textures.clone().iter() {
91-
let identifier = x.identifier.to_string();
92-
if x.identifier.namespace == "minecraft" {
93-
// Skip textures if they are overwriting Minecraft files
94-
if builtin_files::is_in_textures(identifier.as_str()) {
95-
continue;
96-
}
97-
}
132+
for (_, (key, mut texture)) in textures.into_iter().enumerate() {
133+
let identifier = texture.identifier.to_string();
98134
if let Some(mapped) = mapping.texture_mappings.get(&identifier) {
99-
let mut new_texture: Texture = x.clone();
100-
new_texture.identifier.path = mapped.clone();
101-
pack.textures.remove(x.key());
102-
let new_path = new_texture.path();
103-
if let Some(mcmeta) = pack
104-
.json_files
105-
.remove(format!("{}.mcmeta", x.key()).as_str())
106-
{
135+
texture.identifier.path = mapped.clone();
136+
pack.textures.remove(&key);
137+
let new_path = texture.path();
138+
if let Some(mcmeta) = pack.json_files.remove(format!("{}.mcmeta", key).as_str()) {
107139
pack.json_files
108140
.insert(format!("{}.mcmeta", new_path), mcmeta.1);
109141
};
110-
pack.textures.insert(new_path, new_texture);
142+
pack.textures.insert(new_path, texture);
111143
} else {
112144
let mut in_items = false;
113145
let mut in_blocks = false;
114-
let in_font = font_textures.contains(&x.identifier.to_string());
146+
let in_font = font_textures.contains(&texture.identifier.to_string());
115147
let mut aliases = Vec::new();
116148
for atlas in &pack.atlases {
117-
if atlas.overlay != x.overlay {
149+
if atlas.overlay != texture.overlay {
118150
continue;
119151
}
120152
match atlas.atlas_type {
121153
AtlasType::Blocks => {
122-
if let Some(id) = atlas.get_identifier(&x.identifier) {
154+
if let Some(id) = atlas.get_identifier(&texture.identifier) {
123155
in_blocks = true;
124156
aliases.push(id);
125157
}
126158
}
127159
AtlasType::Items => {
128-
if let Some(id) = atlas.get_identifier(&x.identifier) {
160+
if let Some(id) = atlas.get_identifier(&texture.identifier) {
129161
in_items = true;
130162
aliases.push(id);
131163
}
132164
}
133165
_ => {}
134166
}
135167
}
136-
match builtin_files::get_atlas(x.identifier.path.as_str()) {
168+
match builtin_files::get_atlas(texture.identifier.path.as_str()) {
137169
Some(AtlasType::Blocks) => {
138170
in_blocks = true;
139171
}
@@ -148,7 +180,7 @@ fn rename_textures(
148180
level: LogLevel::Warning,
149181
message: format!(
150182
"'{}' is both in blocks and items atlas. Using blocks atlas.",
151-
x.path()
183+
texture.path()
152184
),
153185
});
154186
}
@@ -166,18 +198,14 @@ fn rename_textures(
166198
mapping
167199
.texture_mappings
168200
.insert(identifier, new_identifier.to_string());
169-
let mut new_texture: Texture = x.clone();
170-
new_texture.identifier = new_identifier;
171-
pack.textures.remove(x.key());
172-
let new_path = new_texture.path();
173-
if let Some(mcmeta) = pack
174-
.json_files
175-
.remove(format!("{}.mcmeta", x.key()).as_str())
176-
{
201+
texture.identifier = new_identifier;
202+
pack.textures.remove(&key);
203+
let new_path = texture.path();
204+
if let Some(mcmeta) = pack.json_files.remove(format!("{}.mcmeta", key).as_str()) {
177205
pack.json_files
178206
.insert(format!("{}.mcmeta", new_path), mcmeta.1);
179207
};
180-
pack.textures.insert(new_path, new_texture);
208+
pack.textures.insert(new_path, texture);
181209
*count += 1;
182210
}
183211
}
@@ -229,31 +257,46 @@ fn rebuild_atlas(pack: &ResourcePack) {
229257
}
230258
}
231259

232-
fn rename_models(pack: &ResourcePack, mapping: &mut Mapping) {
233-
let mut count = 0;
234-
for x in pack.models.clone().iter() {
235-
let identifier = x.identifier.to_string();
236-
if x.identifier.namespace == "minecraft" {
237-
// Skip models if they are overwriting Minecraft files
238-
if builtin_files::is_in_models(identifier.as_str()) {
239-
continue;
240-
}
241-
}
260+
fn rename_models(pack: &ResourcePack, mapping: &mut Mapping, id_counter: &mapping::IdUsageCounter) {
261+
let mut models: Vec<(String, Model)> = pack
262+
.models
263+
.iter()
264+
.map(|entry| (entry.key().clone(), entry.value().clone()))
265+
.collect();
266+
267+
models.retain(|(_, x)| {
268+
!(x.identifier.namespace == "minecraft"
269+
&& builtin_files::is_in_models(x.identifier.to_string().as_str()))
270+
});
271+
272+
models.sort_by(|(_, a), (_, b)| {
273+
let a_id = a.identifier.to_string();
274+
let b_id = b.identifier.to_string();
275+
276+
let a_usage = id_counter.get_usage_count(&a_id, mapping::IdCategory::Model);
277+
let b_usage = id_counter.get_usage_count(&b_id, mapping::IdCategory::Model);
278+
279+
// Higher usage first
280+
b_usage
281+
.cmp(&a_usage)
282+
// Stable deterministic fallback so the same resource pack is generated each time
283+
.then_with(|| a_id.cmp(&b_id))
284+
});
285+
286+
for (count, (key, mut model)) in models.into_iter().enumerate() {
287+
let identifier = model.identifier.to_string();
242288
if let Some(mapped) = mapping.model_mappings.get(&identifier) {
243-
let mut new_model: Model = x.clone();
244-
new_model.identifier.path = mapped.clone();
245-
pack.models.remove(&x.key().to_string());
246-
pack.models.insert(new_model.path(), new_model);
289+
model.identifier.path = mapped.clone();
290+
pack.models.remove(&key);
291+
pack.models.insert(model.path(), model);
247292
} else {
248293
let new_identifier = Identifier::new("_", generate_short_name(count));
249294
mapping
250295
.model_mappings
251296
.insert(identifier, new_identifier.to_string());
252-
let mut new_model: Model = x.clone();
253-
new_model.identifier = new_identifier;
254-
pack.models.remove(&x.key().to_string());
255-
pack.models.insert(new_model.path(), new_model);
256-
count += 1;
297+
model.identifier = new_identifier;
298+
pack.models.remove(&key);
299+
pack.models.insert(model.path(), model);
257300
}
258301
}
259302
}

packobf/src/resource_pack/identifier.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::resource_pack::mapping::{IdCategory, GLOBAL_MAPPING, GLOBAL_ID_USAGE_COUNTER};
1+
use crate::resource_pack::mapping::{get_id_usage_counter, get_mappings, IdCategory};
22
use serde::{Deserialize, Deserializer, Serialize, Serializer};
33

44
#[derive(Clone, Debug, Default)]
@@ -67,7 +67,7 @@ macro_rules! impl_id_wrapper {
6767
($name:ident, $category:expr $(, $suffix:expr)?) => {
6868
impl std::fmt::Display for $name {
6969
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70-
let mapping = GLOBAL_MAPPING.get().expect("Mappings not initialized");
70+
let mapping = get_mappings();
7171
let id_str = self.0.to_string();
7272
let mapped = mapping.apply_mapping(&id_str, $category);
7373

@@ -101,7 +101,7 @@ macro_rules! impl_id_wrapper {
101101
s
102102
};
103103
let id = Identifier::parse(&path);
104-
let counter = GLOBAL_ID_USAGE_COUNTER.get().expect("Counter not initialized");
104+
let counter = get_id_usage_counter();
105105
counter.increment_counter(id.to_string(), $category);
106106

107107
Ok(Self(id))

0 commit comments

Comments
 (0)