/* * Nocturne * Copyright (c) 2015-2016, Lapis <https://github.com/LapisBlue> * * The MIT License * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package blue.lapis.nocturne.jar.model; import static blue.lapis.nocturne.processor.index.model.IndexedClass.INDEXED_CLASSES; import static blue.lapis.nocturne.util.Constants.FF_OPTIONS; import static blue.lapis.nocturne.util.Constants.INNER_CLASS_SEPARATOR_CHAR; import static com.google.common.base.Preconditions.checkArgument; import blue.lapis.nocturne.Main; import blue.lapis.nocturne.decompile.NoopResultSaver; import blue.lapis.nocturne.decompile.SimpleBytecodeProvider; import blue.lapis.nocturne.decompile.SimpleFernflowerLogger; import blue.lapis.nocturne.processor.index.ClassIndexer; import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import blue.lapis.nocturne.processor.transform.ClassTransformer; import blue.lapis.nocturne.util.MemberType; import blue.lapis.nocturne.util.helper.StringHelper; import javafx.scene.control.Dialog; import org.jetbrains.java.decompiler.main.Fernflower; import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.lazy.LazyLoader; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; /** * Represents an class entry within a JAR file. */ public class JarClassEntry { private static Dialog<Boolean> decompileDialog; private final String name; private byte[] content; private boolean deobfuscated; private final Map<String, String> classNames = new HashMap<>(); private final Map<FieldSignature, FieldSignature> fields = new HashMap<>(); private final Map<MethodSignature, MethodSignature> methods = new HashMap<>(); static { if (!Main.getInstance().testingEnv) { decompileDialog = new Dialog<>(); decompileDialog.setTitle(Main.getResourceBundle().getString("dialog.decompile.title")); decompileDialog.setHeaderText(null); decompileDialog.setContentText(Main.getResourceBundle().getString("dialog.decompile.content")); decompileDialog.setResult(false); } else { decompileDialog = null; } } /** * Constructs a new {@link JarClassEntry} with the given name and byte * content. * * @param name The name of the {@link JarClassEntry}. * @param content A byte array representing the raw content of the class */ public JarClassEntry(String name, byte[] content) { this.name = name; this.content = new byte[content.length]; System.arraycopy(content, 0, this.content, 0, content.length); } public void index() { INDEXED_CLASSES.put(getName(), new ClassIndexer(this).index()); } public void process() { try { content = new ClassTransformer(getName(), getContent()).process(); } catch (IOException ex) { Main.getLogger().severe("Failed to process class " + getName()); ex.printStackTrace(); } } /** * Returns the name of this {@link JarClassEntry}. * * @return The name of this {@link JarClassEntry} */ public String getName() { return name; } public String getDeobfuscatedName() { checkArgument(isDeobfuscated(), "Cannot get deobfuscated name from non-deobfuscated class entry"); return Main.getMappingContext().getMappings().get(name).getDeobfuscatedName(); } /** * Returns the raw byte content of this {@link JarClassEntry}. * * @return The raw byte content of this {@link JarClassEntry}. */ public byte[] getContent() { return content; } /** * Returns whether this {@link JarClassEntry} is marked as deobfuscated. * * @return Whether this {@link JarClassEntry} is marked as deobfuscated */ public boolean isDeobfuscated() { return deobfuscated; } /** * Sets whether this {@link JarClassEntry} is marked as deobfuscated. * * @param deobfuscated Whether this {@link JarClassEntry} is marked as * deobfuscated */ public void setDeobfuscated(boolean deobfuscated) { this.deobfuscated = deobfuscated; } public String decompile() { showDecompileDialog(); Fernflower ff = new Fernflower( SimpleBytecodeProvider.getInstance(), NoopResultSaver.getInstance(), FF_OPTIONS, SimpleFernflowerLogger.getInstance() ); try { LazyLoader ll = new LazyLoader(SimpleBytecodeProvider.getInstance()); String procName = StringHelper.getProcessedName(getName(), null, MemberType.CLASS); ll.addClassLink(procName, new LazyLoader.Link(LazyLoader.Link.CLASS, null, procName)); StructClass sc = new StructClass( SimpleBytecodeProvider.getInstance().getBytecode(null, procName), true, ll ); ff.getStructContext().getClasses().put(procName, sc); // provide inner classes for (JarClassEntry jce : Main.getLoadedJar().getClasses().stream() .filter(entry -> entry.getName().startsWith(getName() + INNER_CLASS_SEPARATOR_CHAR)) .collect(Collectors.toList())) { String innerProcName = StringHelper.getProcessedName(jce.getName(), null, MemberType.CLASS); ll.addClassLink(innerProcName, new LazyLoader.Link(LazyLoader.Link.CLASS, null, innerProcName)); StructClass innerSc = new StructClass( SimpleBytecodeProvider.getInstance().getBytecode(null, innerProcName), true, ll ); ff.getStructContext().getClasses().put(innerProcName, innerSc); } ff.decompileContext(); return ff.getClassContent(sc); } catch (IOException ex) { throw new RuntimeException(ex); } finally { closeDecompileDialog(); ff.clearContext(); } } public Map<String, String> getCurrentInnerClassNames() { return classNames; } public Map<FieldSignature, FieldSignature> getCurrentFields() { return fields; } public Map<MethodSignature, MethodSignature> getCurrentMethods() { return methods; } @Override public boolean equals(Object otherObject) { return otherObject instanceof JarClassEntry && hashCode() == otherObject.hashCode(); } @Override public int hashCode() { return Objects.hash(getName()); } private static void showDecompileDialog() { decompileDialog.show(); } private static void closeDecompileDialog() { decompileDialog.close(); } }