Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion gui/src/main/java/org/mcphackers/mcp/gui/MenuBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,17 @@ public void reloadSide() {

public void reloadOptions() {
for (Map.Entry<TaskParameter, JMenuItem> entry : optionItems.entrySet()) {
entry.getValue().setSelected(mcp.options.getBooleanParameter(entry.getKey()));
TaskParameter param = entry.getKey();
JMenuItem item = entry.getValue();
if (param.type == String[].class) {
// "Active" for an array-valued option = at least one entry. Lets
// us render it as a toggle in the menu while still keeping the
// underlying value a comma-separated list.
String[] arr = mcp.options.getStringArrayParameter(param);
item.setSelected(arr != null && arr.length > 0);
} else if (param.type == Boolean.class) {
item.setSelected(mcp.options.getBooleanParameter(param));
}
}
}

Expand Down Expand Up @@ -222,6 +232,32 @@ private void initOptions() {
mcp.options.setParameter(param, b.isSelected());
mcp.options.save();
});
} else if (param.type == String[].class) {
// Array-valued option rendered as a checkbox: enabling it
// pops up the value editor; an empty/cancelled value reverts
// to disabled. Disabling clears the array. Lets the user
// toggle expensive opt-in features (like string-literal
// remapping for ASM/transformer code) without leaving the
// menu.
b = new JRadioButtonMenuItem();
translatableComponents.put(b, "task.param." + param.name);
optionItems.put(param, b);
final JRadioButtonMenuItem checkbox = (JRadioButtonMenuItem) b;
b.addActionListener(e -> {
if (checkbox.isSelected()) {
mcp.inputOptionsValue(param);
String[] result = mcp.options.getStringArrayParameter(param);
if (result == null || result.length == 0) {
// User cancelled or supplied an empty value —
// revert the checkbox so its visible state stays
// in sync with the underlying option.
checkbox.setSelected(false);
}
} else {
mcp.options.setParameter(param, new String[0]);
mcp.options.save();
}
});
} else {
b = new JMenuItem(param.getDesc());
b.addActionListener(u -> mcp.inputOptionsValue(param));
Expand Down
20 changes: 18 additions & 2 deletions gui/src/main/java/org/mcphackers/mcp/main/MainGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class MainGUI extends MCP {
public static final String[] TABS = {"task.decompile", "task.recompile", "task.reobfuscate", "task.build", "options.running"};
public static final TaskParameter[][] TAB_PARAMETERS = {
{TaskParameter.PATCHES, TaskParameter.FERNFLOWER_OPTIONS, TaskParameter.IGNORED_PACKAGES, TaskParameter.OUTPUT_SRC, TaskParameter.DECOMPILE_RESOURCES, TaskParameter.GUESS_GENERICS, TaskParameter.STRIP_GENERICS},
{TaskParameter.SOURCE_VERSION, TaskParameter.TARGET_VERSION, TaskParameter.JAVA_HOME, TaskParameter.JAVAC_ARGS}, {TaskParameter.OBFUSCATION, TaskParameter.SRG_OBFUSCATION, TaskParameter.EXCLUDED_CLASSES, TaskParameter.STRIP_SOURCE_FILE},
{TaskParameter.SOURCE_VERSION, TaskParameter.TARGET_VERSION, TaskParameter.JAVA_HOME, TaskParameter.JAVAC_ARGS}, {TaskParameter.OBFUSCATION, TaskParameter.SRG_OBFUSCATION, TaskParameter.EXCLUDED_CLASSES, TaskParameter.STRIP_SOURCE_FILE, TaskParameter.STRING_REMAP_PACKAGES},
{TaskParameter.FULL_BUILD}, {TaskParameter.RUN_BUILD, TaskParameter.RUN_ARGS, TaskParameter.GAME_ARGS}
};
public Theme theme = Theme.THEMES_MAP.get(UIManager.getCrossPlatformLookAndFeelClassName());
Expand Down Expand Up @@ -293,10 +293,26 @@ public void changeWorkingDirectory() {

public void inputOptionsValue(TaskParameter param) {
String s = MCP.TRANSLATOR.translateKey("options.enterValue");
Object initialValue;
if (param.type == String[].class) {
s = MCP.TRANSLATOR.translateKey("options.enterValues") + "\n" + MCP.TRANSLATOR.translateKey("options.enterValues.info");
// Default JOptionPane initial value for an array uses Object.toString
// which prints "[Ljava.lang.String;@<hash>" — meaningless to users
// and worse than an empty box. Render as the same comma-separated
// form the parser accepts so it round-trips cleanly.
String[] current = options.getStringArrayParameter(param);
StringBuilder b = new StringBuilder();
if (current != null) {
for (int i = 0; i < current.length; i++) {
if (i > 0) b.append(",");
b.append(Util.convertToEscapedString(current[i]));
}
}
initialValue = b.toString();
} else {
initialValue = Util.convertToEscapedString(String.valueOf(options.getParameter(param)));
}
String value = (String) JOptionPane.showInputDialog(frame, s, param.getDesc(), JOptionPane.PLAIN_MESSAGE, null, null, Util.convertToEscapedString(String.valueOf(options.getParameter(param))));
String value = (String) JOptionPane.showInputDialog(frame, s, param.getDesc(), JOptionPane.PLAIN_MESSAGE, null, null, initialValue);
safeSetParameter(param, value);
options.save();
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/mcphackers/mcp/tasks/TaskReobfuscate.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.mcphackers.mcp.tasks.mode.TaskParameter;
import org.mcphackers.mcp.tools.FileUtil;
import org.mcphackers.mcp.tools.injector.SourceFileTransformer;
import org.mcphackers.mcp.tools.injector.StringLiteralRemapper;
import org.mcphackers.mcp.tools.mappings.MappingUtil;
import org.mcphackers.rdi.injector.data.ClassStorage;
import org.mcphackers.rdi.injector.data.Mappings;
Expand Down Expand Up @@ -80,6 +81,16 @@ private void reobfuscate() throws IOException {
RDInjector injector = new RDInjector(reobfBin);
Mappings mappings = getMappings(injector.getStorage(), localSide);
if (mappings != null) {
// Rewrite string literals naming vanilla MCP symbols inside the
// configured user packages. Must run BEFORE applyMappings so we
// can still read deobf class names from Type LDCs and look up
// field/method descriptors from the (still-deobf) ClassStorage
// for reflection-pattern context. No-op when the parameter is
// empty, so standard MCP workflows are unaffected.
String[] stringRemapPackages = mcp.getOptions().getStringArrayParameter(TaskParameter.STRING_REMAP_PACKAGES);
if (stringRemapPackages != null && stringRemapPackages.length > 0) {
new StringLiteralRemapper(mappings, stringRemapPackages).transform(injector.getStorage());
}
injector.applyMappings(mappings);
}
if (stripSourceFile) {
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/org/mcphackers/mcp/tasks/mode/TaskParameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,23 @@ public enum TaskParameter {
GUESS_GENERICS("generics", Boolean.class, false),
STRIP_GENERICS("stripgenerics", Boolean.class, false),
OUTPUT_SRC("outputsrc", Boolean.class, true),
STRIP_SOURCE_FILE("stripsourcefile", Boolean.class, true);
STRIP_SOURCE_FILE("stripsourcefile", Boolean.class, true),
/**
* Internal package prefixes (slash form, e.g. {@code me/M41G/nclient/}) whose
* compiled classes should have their string-literal references to vanilla
* MCP names rewritten to the obfuscated equivalents during reobfuscation.
* <p>
* Required for any code that relies on string-keyed access to vanilla
* symbols — ASM transformers (LDC owner/name/desc strings inside
* {@code FieldInsnNode}/{@code MethodInsnNode} construction) and
* reflection ({@code Class.getDeclaredField("name")},
* {@code Class.getDeclaredMethod("name", ...)}). Without this rewrite
* those strings still point at deobf names that no longer exist after
* reobfuscation, and the code becomes a silent no-op at runtime.
* <p>
* Empty by default; standard MCP workflows are unaffected.
*/
STRING_REMAP_PACKAGES("stringremap", String[].class, new String[0]);

public static final TaskParameter[] VALUES = TaskParameter.values();

Expand Down
Loading