passes: rename mixin classes + patch mixins.json/refmap like OBF

- Remove absorb_mixins keep rule (mixin classes now renameable)
- Add mixins.rs: patch_mixins_json rewrites package + entries via
  common prefix of mapped names; patch_refmap_json rewrites all strings
- Resources pass routes *.mixins.json and *refmap.json to specialized
  patchers, falls back to generic text remap

Reference: OBF fabric-obfuscator patchMixinsJson/patchRefmapJson.
This commit is contained in:
Loki 2026-10-12 14:18:19 +02:00
parent b73c72d56d
commit 90506a4736
Signed by: boba
GPG key ID: 253067914055423B
17 changed files with 490 additions and 24 deletions

View file

@ -23,7 +23,14 @@ use crate::model::{Attribute, Code, ExceptionHandler};
use frames::convert_frames;
/// Method body → a ready `Code` attribute (with logical instruction numbers).
pub(super) fn convert_code(code: &Code, pool: &mut Pool) -> Result<RtAttr, ClassfileError> {
///
/// `emit_frames` is false for class-file versions below 50 (Java 6): those use
/// the type-inference verifier and must not carry a `StackMapTable`.
pub(super) fn convert_code(
code: &Code,
pool: &mut Pool,
emit_frames: bool,
) -> Result<RtAttr, ClassfileError> {
let (labels, bare) = split_marks(&code.insns)?;
let resolve = |label: Label| -> Result<u32, ClassfileError> {
labels
@ -55,7 +62,11 @@ pub(super) fn convert_code(code: &Code, pool: &mut Pool) -> Result<RtAttr, Class
resolve: &resolve,
byte_of: &byte_of,
};
let frames = convert_frames(&code.frames, pool, &byte_of)?;
let frames = if emit_frames {
convert_frames(&code.frames, pool, &byte_of)?
} else {
Vec::new()
};
let mut attributes = Vec::with_capacity(code.attributes.len() + 1);
for attr in &code.attributes {
attributes.push(match attr {
@ -68,7 +79,7 @@ pub(super) fn convert_code(code: &Code, pool: &mut Pool) -> Result<RtAttr, Class
other => attrs::convert(other, pool)?,
});
}
if !frames.is_empty() {
if emit_frames && !frames.is_empty() {
attributes.push(RtAttr::StackMapTable {
name_index: pool.utf8("StackMapTable")?,
frames,

View file

@ -22,6 +22,11 @@ pub(crate) fn write_class(class: &ClassFile) -> Result<Vec<u8>, ClassfileError>
let mut pool = pool::Pool::new();
let version = RtVersion::from(class.version.major, class.version.minor)
.map_err(|e| ClassfileError::Write(e.to_string()))?;
// `StackMapTable` is only legal from class-file version 50 (Java 6). Older
// classes use the type-inference verifier; emitting the attribute makes
// ASM/Paper reject the file ("Class versions V1_5 or less must use
// F_NEW frames"). Real case: the string-decoder is built at major 49.
let emit_frames = class.version.major >= 50;
let this_class = pool.class(&class.this_class)?;
let super_class = match &class.super_class {
None => 0,
@ -37,7 +42,7 @@ pub(crate) fn write_class(class: &ClassFile) -> Result<Vec<u8>, ClassfileError>
}
let mut methods = Vec::with_capacity(class.methods.len());
for method in &class.methods {
methods.push(convert_method(method, &mut pool)?);
methods.push(convert_method(method, &mut pool, emit_frames)?);
}
let mut attributes = Vec::with_capacity(class.attributes.len());
for attr in &class.attributes {
@ -78,13 +83,17 @@ fn convert_field(field: &Field, pool: &mut pool::Pool) -> Result<RtField, Classf
})
}
fn convert_method(method: &Method, pool: &mut pool::Pool) -> Result<RtMethod, ClassfileError> {
fn convert_method(
method: &Method,
pool: &mut pool::Pool,
emit_frames: bool,
) -> Result<RtMethod, ClassfileError> {
let mut attributes = Vec::with_capacity(method.attributes.len() + 1);
for attr in &method.attributes {
attributes.push(attrs::convert(attr, pool)?);
}
if let Some(code) = &method.code {
attributes.push(code::convert_code(code, pool)?);
attributes.push(code::convert_code(code, pool, emit_frames)?);
}
Ok(RtMethod {
access_flags: MethodAccessFlags::from_bits_truncate(method.access_flags),