Skip to content

Commit

Permalink
JAR (in JAR) repacker + Manifest minifying
Browse files Browse the repository at this point in the history
  • Loading branch information
szeweq committed Jun 24, 2024
1 parent 2ef99ad commit 28d35e4
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 6 deletions.
3 changes: 2 additions & 1 deletion lib-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ png = ["dep:oxipng"]
toml = ["dep:toml"]
ogg = ["dep:optivorbis"]
nbt = []
jar = []
nbt-zopfli = ["nbt", "dep:zopfli", "_any-zopfli"]
png-zopfli = ["png", "oxipng/zopfli", "_any-zopfli"]
zip-zopfli = ["zip/deflate-zopfli", "dep:zopfli", "_any-zopfli"]
all-zopfli = ["nbt-zopfli", "png-zopfli", "zip-zopfli"]
_any-zopfli = []
serde-cfg = ["dep:serde", "serde/derive"]
default = ["png", "toml", "nbt", "ogg", "serde-cfg"]
default = ["png", "toml", "nbt", "ogg", "jar", "serde-cfg"]

[dependencies]
zip = {workspace = true, features = ["deflate"]}
Expand Down
3 changes: 2 additions & 1 deletion lib-core/src/entry/zip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ impl <W: Write + Seek> EntrySaverSpec for ZipEntrySaver<W> {
}
}

fn compress_check(b: &[u8], compress_min: usize) -> bool {
/// Check if data should be compressed. If the compressed size is smaller than original, then the compression should be chosen.
pub fn compress_check(b: &[u8], compress_min: usize) -> bool {
let lb = b.len();
if lb > compress_min {
if calc_entropy(b) < 7.0 { return true }
Expand Down
9 changes: 8 additions & 1 deletion lib-core/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ pub enum KnownFmt {
Js,
/// ZenScript (ZS), format used by CraftTweaker
Zs,
/// JAR archive
Jar,
/// Java Manifest file
Mf,
/// Any type format with maximum length of 3 (unused bytes are marked as zeroes)
Other([u8; 3])
}
impl KnownFmt {
/// Return a KnownFmt based on file extension.
pub fn by_extension(ftype: &str) -> Option<Self> {
Some(match ftype {
Some(match ftype.to_ascii_lowercase().as_str() {
"json" | "mcmeta" => Self::Json,
"toml" => Self::Toml,
"png" => Self::Png,
Expand All @@ -44,6 +48,9 @@ impl KnownFmt {
"fsh" => Self::Fsh,
"vsh" => Self::Vsh,
"js" => Self::Js,
"zs" => Self::Zs,
"jar" => Self::Jar,
"mf" => Self::Mf,
x => match x.as_bytes() {
[a] => Self::Other([*a, 0, 0]),
[a, b] => Self::Other([*a, *b, 0]),
Expand Down
1 change: 0 additions & 1 deletion lib-core/src/fop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ impl FileOp {
if fname.starts_with(".cache/") { return Self::Ignore(FileIgnoreError::Blacklisted) }
if let Some(sub) = fname.strip_prefix("META-INF/") {
match sub {
"MANIFEST.MF" => {return Self::Recompress(64) }
"SIGNFILE.SF" | "SIGNFILE.DSA" => { return Self::Ignore(FileIgnoreError::Signfile) }
x if x.starts_with("SIG-") || [".DSA", ".RSA", ".SF"].into_iter().any(|e| x.ends_with(e)) => {
return Self::Ignore(FileIgnoreError::Signfile)
Expand Down
66 changes: 66 additions & 0 deletions lib-core/src/min/jar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#![cfg(feature = "jar")]

use std::io::{Cursor, Read, Write};

use zip::{write::{FileOptions, SimpleFileOptions}, CompressionMethod, ZipArchive, ZipWriter};

use crate::{cfg::{acfg, ConfigHolder}, entry::zip::compress_check};
use super::Result_;


acfg!(
/// A JAR archive repacker that accepts [`JARConfig`].
MinifierJAR: JARConfig
);

impl ConfigHolder<MinifierJAR> {
pub(super) fn minify(&self, b: &[u8], vout: &mut Vec<u8>) -> Result_ {
let mut zread = ZipArchive::new(Cursor::new(b))?;
let stored: SimpleFileOptions = FileOptions::default().compression_method(CompressionMethod::Stored);
let deflated: SimpleFileOptions = FileOptions::default().compression_method(CompressionMethod::Deflated).compression_level(Some(self.compress_level()));
let mut zwrite = ZipWriter::new(Cursor::new(vout));
let mut v = Vec::new();
for i in 0..zread.len() {
let Some(name) = zread.name_for_index(i).map(|s| s.to_string()) else { continue; };
if !self.keep_dirs && name.ends_with('/') { continue; }

let mut zfile = zread.by_index(i)?;
v.clear();
v.reserve(zfile.size() as usize);
zfile.read_to_end(&mut v)?;
zwrite.start_file(name, if compress_check(&v, 24) {
deflated
} else {
stored
})?;
zwrite.write_all(&v)?;
}

Ok(())
}
}

/// Configuration for JAR repacker
#[derive(Default)]
#[cfg_attr(feature = "serde-cfg", derive(serde::Serialize, serde::Deserialize))]
pub struct JARConfig {
/// Keep directories in the archive
pub keep_dirs: bool,

#[cfg(feature = "zip-zopfli")]
/// Enables Zopfli compression (better, but slower)
pub use_zopfli: crate::cfg::CfgZopfli
}
impl JARConfig {
#[inline]
#[cfg(feature = "zip-zopfli")]
fn compress_level(&self) -> i64 {
self.use_zopfli.iter_count().map_or(9, |ic| 9 + ((u8::from(ic)) as i64))
}

#[inline]
#[cfg(not(feature = "zip-zopfli"))]
fn compress_level(&self) -> i64 {
9
}
}
26 changes: 24 additions & 2 deletions lib-core/src/min/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub mod nbt;
/// Minifier for OGG files
pub mod ogg;

/// Optimizer for JAR archives
pub mod jar;

#[inline]
const fn strip_bom(b: &[u8]) -> &[u8] {
if let [239, 187, 191, x @ ..] = b { x } else { b }
Expand Down Expand Up @@ -44,10 +47,14 @@ pub enum Minifier {
#[cfg(feature = "nbt")] NBT,
/// An OGG minifier using `optivorbis`.
#[cfg(feature = "ogg")] OGG,
/// A simple repacker for embedded JAR archives
#[cfg(feature = "jar")] JAR,
/// A minifier that removes hash (`#`) comment lines (and empty lines)
Hash,
/// A minifier that removes double-slash (`//`) comment lines (and empty lines)
Slash
Slash,
/// A simple Unix line checker
UnixLine
}
impl Minifier {
/// Return a Minifier based on file extension.
Expand All @@ -59,8 +66,10 @@ impl Minifier {
#[cfg(feature = "toml")] "toml" => Self::TOML,
#[cfg(feature = "nbt")] "nbt" | "blueprint" => Self::NBT,
#[cfg(feature = "ogg")] "ogg" => Self::OGG,
#[cfg(feature = "jar")] "jar" => Self::JAR,
"cfg" | "obj" | "mtl" => Self::Hash,
"zs" | "js" | "fsh" | "vsh" => Self::Slash,
"mf" => Self::UnixLine,
_ => return None
})
}
Expand All @@ -73,8 +82,10 @@ impl Minifier {
#[cfg(feature = "toml")] KnownFmt::Toml => Self::TOML,
#[cfg(feature = "nbt")] KnownFmt::Nbt => Self::NBT,
#[cfg(feature = "ogg")] KnownFmt::Ogg => Self::OGG,
#[cfg(feature = "jar")] KnownFmt::Jar => Self::JAR,
KnownFmt::Cfg | KnownFmt::Obj | KnownFmt::Mtl => Self::Hash,
KnownFmt::Fsh | KnownFmt::Vsh | KnownFmt::Js | KnownFmt::Zs => Self::Slash,
KnownFmt::Mf => Self::UnixLine,
_ => return None
})
}
Expand All @@ -89,8 +100,10 @@ impl Minifier {
#[cfg(feature = "toml")] Self::TOML => cfgmap.fetch::<toml::MinifierTOML>().minify(strip_bom(v), vout),
#[cfg(feature = "nbt")] Self::NBT => cfgmap.fetch::<nbt::MinifierNBT>().minify(v, vout),
#[cfg(feature = "ogg")] Self::OGG => cfgmap.fetch::<ogg::MinifierOGG>().minify(v, vout),
#[cfg(feature = "jar")] Self::JAR => cfgmap.fetch::<jar::MinifierJAR>().minify(v, vout),
Self::Hash => remove_line_comments("#", v, vout),
Self::Slash => remove_line_comments("//", v, vout)
Self::Slash => remove_line_comments("//", v, vout),
Self::UnixLine => unixify_lines(v, vout)
}
}

Expand Down Expand Up @@ -128,6 +141,15 @@ fn remove_line_comments(bs: &'static str, v: &[u8], vout: &mut Vec<u8>) -> Resul
Ok(())
}

fn unixify_lines(v: &[u8], vout: &mut Vec<u8>) -> Result_ {
let v = std::str::from_utf8(v)?;
for l in v.lines() {
vout.extend_from_slice(l.trim_end().as_bytes());
vout.push(b'\n');
}
Ok(())
}

/// An error indicating that a file has mismatched pair of brackets
#[derive(Debug)]
pub struct BracketsError;
Expand Down

0 comments on commit 28d35e4

Please sign in to comment.