diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/ClassProvider.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/ClassProvider.java new file mode 100644 index 0000000..5d93fb6 --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/ClassProvider.java @@ -0,0 +1,38 @@ +package uwu.narumi.deobfuscator.api.classpath; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.ClassNode; + +import java.util.Collection; + +public interface ClassProvider { + /** + * Gets class bytes by internal name + * + * @param name Internal name of class + * @return Class bytes + */ + byte @Nullable [] getClass(String name); + + /** + * Gets file bytes by name + * + * @param path File path + * @return File bytes + */ + byte @Nullable [] getFile(String path); + + /** + * Gets class node that holds only the class information. It is not guaranteed that the class holds code. + * + * @param name Internal name of class + * @return Class node + */ + @Nullable + ClassNode getClassInfo(String name); + + /** + * Gets all classes in the provider. + */ + Collection getLoadedClasses(); +} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/ClassStorage.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/ClassStorage.java new file mode 100644 index 0000000..30c350b --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/ClassStorage.java @@ -0,0 +1,81 @@ +package uwu.narumi.deobfuscator.api.classpath; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.ClassNode; +import software.coley.cafedude.InvalidClassException; +import uwu.narumi.deobfuscator.api.helper.ClassHelper; +import uwu.narumi.deobfuscator.api.helper.FileHelper; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class ClassStorage implements ClassProvider { + private final Map compiledClasses = new ConcurrentHashMap<>(); + private final Map files = new ConcurrentHashMap<>(); + + private final Map classesInfo = new ConcurrentHashMap<>(); + + /** + * Adds jar to class storage + * + * @param jarPath Jar path + */ + public void addJar(@NotNull Path jarPath) { + FileHelper.loadFilesFromZip(jarPath, (classPath, bytes) -> { + if (!ClassHelper.isClass(classPath, bytes)) { + files.putIfAbsent(classPath, bytes); + return; + } + + addRawClass(bytes); + }); + } + + public void addRawClass(byte[] bytes) { + try { + ClassNode classNode = ClassHelper.loadUnknownClassInfo(bytes); + String className = classNode.name; + + // Add class to class storage + compiledClasses.putIfAbsent(className, bytes); + classesInfo.putIfAbsent(className, classNode); + } catch (InvalidClassException e) { + throw new RuntimeException(e); + } + } + + @Override + public byte @Nullable [] getClass(String name) { + return compiledClasses.get(name); + } + + @Override + public byte @Nullable [] getFile(String path) { + return files.get(path); + } + + @Override + public @Nullable ClassNode getClassInfo(String name) { + return classesInfo.get(name); + } + + @Override + public Collection getLoadedClasses() { + return compiledClasses.keySet(); + } + + public Map compiledClasses() { + return compiledClasses; + } + + public Map files() { + return files; + } + + public Map classesInfo() { + return classesInfo; + } +} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/Classpath.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/Classpath.java deleted file mode 100644 index dcf739d..0000000 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/Classpath.java +++ /dev/null @@ -1,118 +0,0 @@ -package uwu.narumi.deobfuscator.api.classpath; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.objectweb.asm.tree.ClassNode; -import uwu.narumi.deobfuscator.api.context.DeobfuscatorOptions; -import uwu.narumi.deobfuscator.api.helper.ClassHelper; -import uwu.narumi.deobfuscator.api.helper.FileHelper; - -/** - * Immutable classpath - * - * @param classesInfo Class nodes that hold only the class information, not code - */ -public record Classpath( - Map rawClasses, - Map files, - Map classesInfo -) { - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private static final Logger LOGGER = LogManager.getLogger(); - - private final Map rawClasses = new HashMap<>(); - private final Map files = new HashMap<>(); - - private final Map classesInfo = new HashMap<>(); - - private Builder() { - } - - /** - * Adds jar to classpath - * - * @param jarPath Jar path - */ - @Contract("_ -> this") - public Builder addJar(@NotNull Path jarPath) { - FileHelper.loadFilesFromZip(jarPath, (classPath, bytes) -> { - if (!ClassHelper.isClass(classPath, bytes)) { - files.putIfAbsent(classPath, bytes); - return; - } - - try { - ClassNode classNode = ClassHelper.loadUnknownClassInfo(bytes); - String className = classNode.name; - - rawClasses.putIfAbsent(className, bytes); - classesInfo.putIfAbsent(className, classNode); - } catch (Exception e) { - LOGGER.error("Could not load {} class from {} library", classPath, jarPath, e); - } - }); - - return this; - } - - /** - * Adds {@link DeobfuscatorOptions.ExternalFile} to classpath - * - * @param externalFile External class - */ - @Contract("_ -> this") - public Builder addExternalFile(DeobfuscatorOptions.ExternalFile externalFile) { - try { - byte[] bytes = Files.readAllBytes(externalFile.path()); - if (!ClassHelper.isClass(externalFile.pathInJar(), bytes)) { - files.putIfAbsent(externalFile.pathInJar(), bytes); - return this; - } - - ClassNode classNode = ClassHelper.loadUnknownClassInfo(bytes); - String className = classNode.name; - - // Add class to classpath - rawClasses.putIfAbsent(className, bytes); - classesInfo.putIfAbsent(className, classNode); - } catch (Exception e) { - throw new RuntimeException(e); - } - - return this; - } - - /** - * Adds another classpath to this classpath - */ - @Contract("_ -> this") - public Builder addClasspath(Classpath classpath) { - this.rawClasses.putAll(classpath.rawClasses); - this.files.putAll(classpath.files); - this.classesInfo.putAll(classpath.classesInfo); - - return this; - } - - public Classpath build() { - return new Classpath( - Collections.unmodifiableMap(rawClasses), - Collections.unmodifiableMap(files), - Collections.unmodifiableMap(classesInfo) - ); - } - } -} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/CombinedClassProvider.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/CombinedClassProvider.java new file mode 100644 index 0000000..ad3f20c --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/CombinedClassProvider.java @@ -0,0 +1,52 @@ +package uwu.narumi.deobfuscator.api.classpath; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.ClassNode; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class CombinedClassProvider implements ClassProvider { + private final ClassProvider[] classProviders; + + public CombinedClassProvider(ClassProvider... classProviders) { + this.classProviders = classProviders; + } + + @Override + public byte @Nullable [] getClass(String name) { + for (ClassProvider classProvider : this.classProviders) { + byte[] bytes = classProvider.getClass(name); + if (bytes != null) return bytes; + } + return null; + } + + @Override + public byte @Nullable [] getFile(String path) { + for (ClassProvider classProvider : this.classProviders) { + byte[] bytes = classProvider.getFile(path); + if (bytes != null) return bytes; + } + return null; + } + + @Override + public @Nullable ClassNode getClassInfo(String name) { + for (ClassProvider classProvider : this.classProviders) { + ClassNode classInfo = classProvider.getClassInfo(name); + if (classInfo != null) return classInfo; + } + return null; + } + + @Override + public Collection getLoadedClasses() { + Set classes = new HashSet<>(); + for (ClassProvider classProvider : this.classProviders) { + classes.addAll(classProvider.getLoadedClasses()); + } + return classes; + } +} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/JvmClassProvider.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/JvmClassProvider.java new file mode 100644 index 0000000..fd1cc99 --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/JvmClassProvider.java @@ -0,0 +1,82 @@ +package uwu.narumi.deobfuscator.api.classpath; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.ClassNode; +import uwu.narumi.deobfuscator.api.helper.ClassHelper; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Classpath that fetches default JVM classes + */ +public class JvmClassProvider implements ClassProvider { + private static final Logger LOGGER = LogManager.getLogger(); + + public static final JvmClassProvider INSTANCE = new JvmClassProvider(); + + private final Map classesCache = new ConcurrentHashMap<>(); + private final Map classInfoCache = new ConcurrentHashMap<>(); + + private JvmClassProvider() { + } + + @Override + public byte @Nullable [] getClass(String name) { + if (classesCache.containsKey(name)) { + return classesCache.get(name); + } + + // Try to find it in classloader + byte[] value = null; + try (InputStream in = ClassLoader.getSystemResourceAsStream(name + ".class")) { + if (in != null) { + value = in.readAllBytes(); + } + } catch (IOException ex) { + LOGGER.error("Failed to fetch runtime bytecode of class: {}", name, ex); + } + + if (value == null) return null; + + // Cache it! + classesCache.put(name, value); + + return value; + } + + @Override + public byte @Nullable [] getFile(String path) { + // JVM classpath doesn't have files + return null; + } + + @Override + public @Nullable ClassNode getClassInfo(String name) { + if (classInfoCache.containsKey(name)) { + return classInfoCache.get(name); + } + + byte[] bytes = getClass(name); + if (bytes == null) return null; + + ClassNode classNode = ClassHelper.loadClassInfo(bytes); + + // Cache it! + classInfoCache.put(name, classNode); + + return classNode; + } + + @Override + public Collection getLoadedClasses() { + // We cannot determine all classes in JVM classpath + return List.of(); + } +} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/JvmClasspath.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/JvmClasspath.java deleted file mode 100644 index 9ccba83..0000000 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/JvmClasspath.java +++ /dev/null @@ -1,47 +0,0 @@ -package uwu.narumi.deobfuscator.api.classpath; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.tree.ClassNode; -import uwu.narumi.deobfuscator.api.helper.ClassHelper; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Classpath that fetches default JVM classes - */ -public class JvmClasspath { - private static final Logger LOGGER = LogManager.getLogger(); - - private static final Map cache = new ConcurrentHashMap<>(); - - @Nullable - public static ClassNode getClassNode(String name) { - if (cache.containsKey(name)) { - return cache.get(name); - } - - // Try to find it in classloader - byte[] value = null; - try (InputStream in = ClassLoader.getSystemResourceAsStream(name + ".class")) { - if (in != null) { - value = in.readAllBytes(); - } - } catch (IOException ex) { - LOGGER.error("Failed to fetch runtime bytecode of class: {}", name, ex); - } - - if (value == null) return null; - - ClassNode classNode = ClassHelper.loadClassInfo(value); - // Cache it! - cache.put(name, classNode); - - return classNode; - } -} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java index 318291e..5e4f800 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java @@ -2,27 +2,27 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnmodifiableView; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; +import software.coley.cafedude.InvalidClassException; import uwu.narumi.deobfuscator.api.asm.ClassWrapper; +import uwu.narumi.deobfuscator.api.classpath.ClassProvider; +import uwu.narumi.deobfuscator.api.classpath.ClassStorage; import uwu.narumi.deobfuscator.api.execution.SandBox; -import uwu.narumi.deobfuscator.api.classpath.Classpath; +import uwu.narumi.deobfuscator.api.helper.ClassHelper; -public class Context { +public class Context implements ClassProvider { - private static final Logger LOGGER = LogManager.getLogger(Context.class); - - private final Map classes = new ConcurrentHashMap<>(); - private final Map files = new ConcurrentHashMap<>(); + private final Map classesMap = new ConcurrentHashMap<>(); + private final Map filesMap = new ConcurrentHashMap<>(); private final DeobfuscatorOptions options; - private final Classpath primaryClasspath; - private final Classpath libClasspath; - private final Classpath combinedClasspath; + private final ClassStorage compiledClasses; + private final ClassStorage libraries; private SandBox globalSandBox = null; @@ -30,18 +30,14 @@ public class Context { * Creates a new {@link Context} instance from its options * * @param options Deobfuscator options - * @param primaryClasspath Classpath which has only primary jar in it - * @param libClasspath Classpath filled with libs + * @param compiledClasses {@link ClassStorage} that holds the original classes of the primary jar + * @param libraries {@link ClassStorage} that holds the libraries' classes */ - public Context(DeobfuscatorOptions options, Classpath primaryClasspath, Classpath libClasspath) { + public Context(DeobfuscatorOptions options, ClassStorage compiledClasses, ClassStorage libraries) { this.options = options; - this.primaryClasspath = primaryClasspath; - this.libClasspath = libClasspath; - this.combinedClasspath = Classpath.builder() - .addClasspath(primaryClasspath) - .addClasspath(libClasspath) - .build(); + this.compiledClasses = compiledClasses; + this.libraries = libraries; } /** @@ -60,63 +56,73 @@ public DeobfuscatorOptions getOptions() { } /** - * Classpath for primary jar + * Class storage that holds already compiled classes from original jar */ - public Classpath getPrimaryClasspath() { - return primaryClasspath; + public ClassStorage getCompiledClasses() { + return compiledClasses; } /** - * Classpath filled with libs + * Class storage that holds libraries' classes */ - public Classpath getLibClasspath() { - return libClasspath; + public ClassStorage getLibraries() { + return libraries; } - /** - * {@link #getPrimaryClasspath()} and {@link #getLibClasspath()} combined - */ - public Classpath getCombinedClasspath() { - return this.combinedClasspath; + public Collection classes() { + return classesMap.values(); } - public Collection classes() { - return classes.values(); + @UnmodifiableView + public List scopedClasses(ClassWrapper scope) { + return classesMap.values().stream() + .filter(classWrapper -> scope == null || classWrapper.name().equals(scope.name())) + .toList(); } - public Stream stream() { - return classes.values().stream(); + public void addCompiledClass(String pathInJar, byte[] bytes) { + try { + ClassWrapper classWrapper = ClassHelper.loadUnknownClass(pathInJar, bytes, ClassReader.SKIP_FRAMES); + this.classesMap.putIfAbsent(classWrapper.name(), classWrapper); + this.compiledClasses.addRawClass(bytes); + } catch (InvalidClassException e) { + throw new RuntimeException(e); + } } - public Stream stream(ClassWrapper scope) { - return classes.values().stream() - .filter(classWrapper -> scope == null || classWrapper.name().equals(scope.name())); + public void addFile(String path, byte[] bytes) { + this.filesMap.put(path, bytes); + this.compiledClasses.files().put(path, bytes); } - @UnmodifiableView - public List scopedClasses(ClassWrapper scope) { - return classes.values().stream() - .filter(classWrapper -> scope == null || classWrapper.name().equals(scope.name())) - .toList(); + @Override + public byte @Nullable [] getClass(String name) { + // Not implemented because it would need to compile class which is CPU intensive + return null; } - public Optional get(String name) { - return Optional.ofNullable(classes.get(name)); + @Override + public byte @Nullable [] getFile(String path) { + return filesMap.get(path); } - public Optional remove(ClassWrapper classWrapper) { - return remove(classWrapper.name()); + @Override + public @Nullable ClassNode getClassInfo(String name) { + ClassWrapper classWrapper = classesMap.get(name); + if (classWrapper == null) return null; + return classWrapper.classNode(); } - public Optional remove(String name) { - return Optional.ofNullable(classes.remove(name)); + @Override + public Collection getLoadedClasses() { + return this.classesMap.keySet(); } - public Map getClasses() { - return classes; + public Map getClassesMap() { + return classesMap; } - public Map getFiles() { - return files; + public Map getFilesMap() { + return filesMap; } } diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/ClasspathDataSupplier.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/ClasspathDataSupplier.java index 0c1d673..39e7831 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/ClasspathDataSupplier.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/ClasspathDataSupplier.java @@ -1,23 +1,23 @@ package uwu.narumi.deobfuscator.api.execution; import dev.xdark.ssvm.classloading.SupplyingClassLoaderInstaller; -import uwu.narumi.deobfuscator.api.classpath.Classpath; +import uwu.narumi.deobfuscator.api.classpath.ClassProvider; public class ClasspathDataSupplier implements SupplyingClassLoaderInstaller.DataSupplier { - private final Classpath classpath; + private final ClassProvider classpath; - public ClasspathDataSupplier(Classpath classpath) { + public ClasspathDataSupplier(ClassProvider classpath) { this.classpath = classpath; } @Override public byte[] getClass(String className) { - return classpath.rawClasses().get(className.replace('.', '/')); + return classpath.getClass(className.replace('.', '/')); } @Override public byte[] getResource(String resourcePath) { - return classpath.files().get(resourcePath); + return classpath.getFile(resourcePath); } } diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/SandBox.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/SandBox.java index a3b231b..79282c6 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/SandBox.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/SandBox.java @@ -20,6 +20,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import uwu.narumi.deobfuscator.api.classpath.CombinedClassProvider; import uwu.narumi.deobfuscator.api.context.Context; /** @@ -37,7 +38,10 @@ public class SandBox { public SandBox(Context context) { // Install all classes from deobfuscator context - this(new ClasspathDataSupplier(context.getCombinedClasspath())); + this(new ClasspathDataSupplier( + // We need to use compiled classes as they are already compiled + new CombinedClassProvider(context.getCompiledClasses(), context.getLibraries()) + )); } public SandBox(SupplyingClassLoaderInstaller.DataSupplier dataSupplier) { diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/AsmHelper.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/AsmHelper.java index 40797b5..457463a 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/AsmHelper.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/AsmHelper.java @@ -160,7 +160,7 @@ public static void updateMethodDescriptor(Context context, MethodContext methodC private static void tryUpdateMethodDescriptor(Context context, ClassWrapper classWrapper, String name, String oldDesc, String newDesc) { // Search superclass if (classWrapper.classNode().superName != null) { - ClassWrapper superClass = context.getClasses().get(classWrapper.classNode().superName); + ClassWrapper superClass = context.getClassesMap().get(classWrapper.classNode().superName); if (superClass != null) { tryUpdateMethodDescriptor(context, superClass, name, oldDesc, newDesc); } @@ -168,7 +168,7 @@ private static void tryUpdateMethodDescriptor(Context context, ClassWrapper clas // Search interfaces classWrapper.classNode().interfaces.forEach(interfaceName -> { - ClassWrapper interfaceClass = context.getClasses().get(interfaceName); + ClassWrapper interfaceClass = context.getClassesMap().get(interfaceName); if (interfaceClass != null) { tryUpdateMethodDescriptor(context, interfaceClass, name, oldDesc, newDesc); } @@ -213,10 +213,10 @@ public static MethodNode copyMethod(MethodNode methodNode) { } public static void removeField(FieldInsnNode fieldInsnNode, Context context) { - if (!context.getClasses().containsKey(fieldInsnNode.owner)) return; + if (!context.getClassesMap().containsKey(fieldInsnNode.owner)) return; context - .getClasses() + .getClassesMap() .get(fieldInsnNode.owner) .fields() .removeIf( diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/inheritance/InheritanceGraph.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/inheritance/InheritanceGraph.java index 6a1eacd..b2021d5 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/inheritance/InheritanceGraph.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/inheritance/InheritanceGraph.java @@ -6,8 +6,9 @@ import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; -import uwu.narumi.deobfuscator.api.classpath.Classpath; -import uwu.narumi.deobfuscator.api.classpath.JvmClasspath; +import uwu.narumi.deobfuscator.api.classpath.ClassProvider; +import uwu.narumi.deobfuscator.api.classpath.CombinedClassProvider; +import uwu.narumi.deobfuscator.api.classpath.JvmClassProvider; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -31,13 +32,13 @@ public class InheritanceGraph { private final Map vertices = new ConcurrentHashMap<>(); private final Set stubs = ConcurrentHashMap.newKeySet(); private final Function vertexProvider = createVertexProvider(); - private final Classpath classpath; + private final ClassProvider classProvider; /** * Create an inheritance graph. */ - public InheritanceGraph(@NotNull Classpath classpath) { - this.classpath = classpath; + public InheritanceGraph(@NotNull ClassProvider classProvider) { + this.classProvider = new CombinedClassProvider(classProvider, JvmClassProvider.INSTANCE); // Populate downwards (parent --> child) lookup refreshChildLookup(); @@ -51,7 +52,10 @@ private void refreshChildLookup() { parentToChild.clear(); // Repopulate - classpath.classesInfo().values().forEach(this::populateParentToChildLookup); + classProvider.getLoadedClasses().stream() + .map(classProvider::getClassInfo) + .filter(Objects::nonNull) + .forEach(this::populateParentToChildLookup); } /** @@ -297,7 +301,7 @@ private Function createVertexProvider() { return null; // Find class in workspace, if not found yield stub. - ClassNode result = this.getClassNode(name); + ClassNode result = this.classProvider.getClassInfo(name); if (result == null) { return STUB; } @@ -310,15 +314,6 @@ private Function createVertexProvider() { }; } - @Nullable - private ClassNode getClassNode(String name) { - ClassNode result = classpath.classesInfo().get(name); - if (result != null) return result; - - // Try to find it in classloader - return JvmClasspath.getClassNode(name); - } - private static class InheritanceStubVertex extends InheritanceVertex { private InheritanceStubVertex() { super(new ClassNode(), in -> null, in -> null); diff --git a/deobfuscator-impl/src/main/java/uwu/narumi/deobfuscator/Deobfuscator.java b/deobfuscator-impl/src/main/java/uwu/narumi/deobfuscator/Deobfuscator.java index 899d57b..bbf8fc6 100644 --- a/deobfuscator-impl/src/main/java/uwu/narumi/deobfuscator/Deobfuscator.java +++ b/deobfuscator-impl/src/main/java/uwu/narumi/deobfuscator/Deobfuscator.java @@ -13,13 +13,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.objectweb.asm.ClassReader; -import uwu.narumi.deobfuscator.api.asm.ClassWrapper; +import uwu.narumi.deobfuscator.api.classpath.ClassStorage; +import uwu.narumi.deobfuscator.api.classpath.CombinedClassProvider; import uwu.narumi.deobfuscator.api.context.Context; import uwu.narumi.deobfuscator.api.context.DeobfuscatorOptions; import uwu.narumi.deobfuscator.api.helper.ClassHelper; import uwu.narumi.deobfuscator.api.helper.FileHelper; -import uwu.narumi.deobfuscator.api.classpath.Classpath; import uwu.narumi.deobfuscator.api.inheritance.InheritanceGraph; import uwu.narumi.deobfuscator.api.transformer.Transformer; @@ -48,35 +47,21 @@ private Deobfuscator(DeobfuscatorOptions options) { LOGGER.warn("Output file already exist, data will be overwritten"); } - Classpath primaryClasspath = buildPrimaryClasspath(); - LOGGER.info("Loaded {} classes from a primary source", primaryClasspath.rawClasses().size()); + ClassStorage originalClasses = new ClassStorage(); + LOGGER.info("Loaded {} classes from a primary source", originalClasses.compiledClasses().size()); - Classpath libClasspath = buildLibClasspath(); - LOGGER.info("Loaded {} classes from libraries", libClasspath.rawClasses().size()); + ClassStorage libraries = buildLibraries(); + LOGGER.info("Loaded {} classes from libraries", libraries.compiledClasses().size()); - this.context = new Context(options, primaryClasspath, libClasspath); + this.context = new Context(options, originalClasses, libraries); } - public Classpath buildPrimaryClasspath() { - Classpath.Builder builder = Classpath.builder(); - // Add input jar - if (options.inputJar() != null) { - builder.addJar(options.inputJar()); - } - // Add external files - if (!options.externalFiles().isEmpty()) { - options.externalFiles().forEach(builder::addExternalFile); - } - - return builder.build(); - } - - public Classpath buildLibClasspath() { - Classpath.Builder builder = Classpath.builder(); + public ClassStorage buildLibraries() { + ClassStorage classStorage = new ClassStorage(); // Add libraries - options.libraries().forEach(builder::addJar); + options.libraries().forEach(classStorage::addJar); - return builder.build(); + return classStorage; } public void start() { @@ -115,8 +100,7 @@ private void loadClassOrFile(String pathInJar, byte[] bytes) { // Load class if (ClassHelper.isClass(pathInJar, bytes)) { try { - ClassWrapper classWrapper = ClassHelper.loadUnknownClass(pathInJar, bytes, ClassReader.SKIP_FRAMES); - context.getClasses().putIfAbsent(classWrapper.name(), classWrapper); + this.context.addCompiledClass(pathInJar, bytes); return; } catch (Exception e) { LOGGER.error("Could not load class: {}, adding as file", pathInJar, e); @@ -125,8 +109,8 @@ private void loadClassOrFile(String pathInJar, byte[] bytes) { } // Load file - if (!context.getFiles().containsKey(pathInJar)) { - context.getFiles().put(pathInJar, bytes); + if (!context.getFilesMap().containsKey(pathInJar)) { + context.addFile(pathInJar, bytes); } } @@ -201,10 +185,12 @@ private void saveToJar() { * @param saver a consumer that accepts a path and data to save */ private void save(BiConsumer saver) { - InheritanceGraph inheritanceGraph = new InheritanceGraph(this.context.getCombinedClasspath()); + InheritanceGraph inheritanceGraph = new InheritanceGraph( + new CombinedClassProvider(this.context, this.context.getLibraries()) + ); // Save classes - context.getClasses().forEach((ignored, classWrapper) -> { + context.getClassesMap().forEach((ignored, classWrapper) -> { String path = classWrapper.getPathInJar(); try { @@ -216,28 +202,24 @@ private void save(BiConsumer saver) { try { // Save original class as a fallback - byte[] data = context.getPrimaryClasspath().rawClasses().get(classWrapper.name()); + byte[] data = context.getCompiledClasses().getClass(classWrapper.name()); saver.accept(path, data); } catch (Exception e2) { LOGGER.error("Could not save original class: {}", classWrapper.name()); if (this.options.printStacktraces()) LOGGER.throwing(e2); } } - - context.getClasses().remove(classWrapper.name()); }); // Save files if (!this.options.skipFiles()) { - context.getFiles().forEach((path, data) -> { + context.getFilesMap().forEach((path, data) -> { try { saver.accept(path, data); } catch (Exception e) { LOGGER.error("Could not save file: {}", path); if (this.options.printStacktraces()) LOGGER.throwing(e); } - - context.getFiles().remove(path); }); } } diff --git a/deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java b/deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java index f9dbe1c..b5891be 100644 --- a/deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java +++ b/deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java @@ -139,9 +139,9 @@ protected void registerAll() { .input(OutputType.SINGLE_CLASS, InputType.CUSTOM_CLASS, "zkm/EnhancedStringEncManyStrings.class") .register(); - // Example HP888 classes. Without packer. + // Example HP888 classes test("HP888") - .transformers(ComposedHP888Transformer::new) + .transformers(() -> new ComposedHP888Transformer(".mc")) .input(OutputType.MULTIPLE_CLASSES, InputType.CUSTOM_CLASS, "hp888") .register(); diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedHP888Transformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedHP888Transformer.java index ef3303f..251f18f 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedHP888Transformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedHP888Transformer.java @@ -1,12 +1,11 @@ package uwu.narumi.deobfuscator.core.other.composed; +import org.jetbrains.annotations.Nullable; import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer; -import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer; import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralRepairTransformer; -import uwu.narumi.deobfuscator.core.other.impl.clean.peephole.DeadCodeCleanTransformer; -import uwu.narumi.deobfuscator.core.other.impl.exploit.WebExploitRemoveTransformer; import uwu.narumi.deobfuscator.core.other.impl.hp888.HP888PackerTransformer; import uwu.narumi.deobfuscator.core.other.impl.hp888.HP888StringTransformer; +import uwu.narumi.deobfuscator.core.other.impl.universal.RecoverSyntheticsTransformer; import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer; /** @@ -14,27 +13,26 @@ */ public class ComposedHP888Transformer extends ComposedTransformer { - public ComposedHP888Transformer(String packedEndOfFile) { - super( - HP888StringTransformer::new, - () -> new HP888PackerTransformer(packedEndOfFile), - HP888StringTransformer::new, - WebExploitRemoveTransformer::new, - ComposedGeneralRepairTransformer::new, - UniversalNumberTransformer::new, - ComposedGeneralFlowTransformer::new, - DeadCodeCleanTransformer::new - ); + public ComposedHP888Transformer() { + this(null); } - public ComposedHP888Transformer() { + public ComposedHP888Transformer(@Nullable String encryptedClassFilesSuffix) { super( + // Decrypt strings HP888StringTransformer::new, - WebExploitRemoveTransformer::new, - ComposedGeneralRepairTransformer::new, + + () -> encryptedClassFilesSuffix != null ? new ComposedTransformer( + // Unpack encrypted classes + () -> new HP888PackerTransformer(encryptedClassFilesSuffix), + // Decrypt strings in unpacked classes + HP888StringTransformer::new + ) : null, + + // Cleanup UniversalNumberTransformer::new, - ComposedGeneralFlowTransformer::new, - DeadCodeCleanTransformer::new + ComposedGeneralRepairTransformer::new, + RecoverSyntheticsTransformer::new ); } } diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/exploit/WebExploitRemoveTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/exploit/WebExploitRemoveTransformer.java index c8f73ad..827c915 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/exploit/WebExploitRemoveTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/exploit/WebExploitRemoveTransformer.java @@ -8,8 +8,8 @@ public class WebExploitRemoveTransformer extends Transformer { @Override protected void transform() throws Exception { - changed |= context().getClasses().entrySet().removeIf(entry -> entry.getKey().contains("")); - changed |= context().getFiles().entrySet().removeIf(entry -> entry.getKey().contains("")); + changed |= context().getClassesMap().entrySet().removeIf(entry -> entry.getKey().contains("")); + changed |= context().getFilesMap().entrySet().removeIf(entry -> entry.getKey().contains("")); scopedClasses().forEach(classWrapper -> { changed |= classWrapper.methods().removeIf(methodNode -> methodNode.name.contains("")); diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888PackerTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888PackerTransformer.java index a25394c..88d7fa8 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888PackerTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888PackerTransformer.java @@ -51,28 +51,26 @@ protected void transform() throws Exception { // Decrypt encrypted classes Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.getDecoder().decode(key.get()), "AES")); - context().getFiles().forEach((file, bytes) -> { + context().getFilesMap().forEach((file, bytes) -> { if (file.endsWith(encryptedClassFilesSuffix)) { filesToRemove.add(file); - String className = file.replace(encryptedClassFilesSuffix, "").replace(".", "/"); + String path = file.replace(encryptedClassFilesSuffix, ".class").replace(".", "/"); try { // Decrypt! byte[] decrypted = cipher.doFinal(bytes); - // Load class - newClasses.put(className, ClassHelper.loadUnknownClass(className + ".class", decrypted, ClassReader.SKIP_FRAMES)); + // Load and put class + context().addCompiledClass(path, decrypted); + markChange(); } catch (Exception e) { - throw new RuntimeException("Failed to decrypt class: " + className, e); + throw new RuntimeException("Failed to decrypt class: " + path, e); } } }); - // Put all new classes - context().getClasses().putAll(newClasses); - // Cleanup - filesToRemove.forEach(context().getFiles()::remove); + filesToRemove.forEach(context().getFilesMap()::remove); } } diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java index 78452f9..352e00a 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java @@ -11,9 +11,12 @@ import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch; import uwu.narumi.deobfuscator.api.asm.matcher.impl.StringMatch; import uwu.narumi.deobfuscator.api.transformer.Transformer; +import uwu.narumi.deobfuscator.core.other.impl.pool.InlineStaticFieldTransformer; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Strings are encrypted using a constant pool size of a provided class. @@ -33,6 +36,8 @@ public class HP888StringTransformer extends Transformer { @Override protected void transform() throws Exception { + Set classesToRemove = new HashSet<>(); + scopedClasses().forEach(classWrapper -> { List toRemove = new ArrayList<>(); @@ -58,7 +63,7 @@ protected void transform() throws Exception { Type classForConstantPoolType = (Type) constantPoolClassLdc.cst; // Prepare data for decryption - ClassWrapper classForConstantPool = context().getClasses().get(classForConstantPoolType.getInternalName()); + ClassWrapper classForConstantPool = context().getClassesMap().get(classForConstantPoolType.getInternalName()); int constantPoolSize = classForConstantPool.getConstantPool().getSize(); String class0 = classWrapper.name(); String class1 = classWrapper.name(); @@ -70,12 +75,20 @@ protected void transform() throws Exception { methodNode.instructions.set(decryptMethodInsn, new LdcInsnNode(decryptedString)); markChange(); + classesToRemove.add(classWrapper.name()); + toRemove.add(decryptMethod); }); }); }); classWrapper.methods().removeAll(toRemove); }); + + // Inline static fields + Transformer.transform(InlineStaticFieldTransformer::new, scope(), context()); + + // Cleanup + classesToRemove.forEach(className -> context().getClassesMap().remove(className)); } private String decrypt(String string, int constantPoolSize, int className0HashCode, int className1HashCode) { diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionMPCTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionMPCTransformer.java index cb4302b..c53737f 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionMPCTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionMPCTransformer.java @@ -104,8 +104,8 @@ public ZelixLongEncryptionMPCTransformer(Map classInitOrder) { protected void transform() throws Exception { // Firstly, process the manual list of class initialization order for (var entry : classInitOrder.entrySet()) { - ClassWrapper first = context().getClasses().get(entry.getKey()); - ClassWrapper second = context().getClasses().get(entry.getValue()); + ClassWrapper first = context().getClassesMap().get(entry.getKey()); + ClassWrapper second = context().getClassesMap().get(entry.getValue()); decryptEncryptedLongs(context(), first); decryptEncryptedLongs(context(), second); @@ -118,7 +118,7 @@ protected void transform() throws Exception { // Remove decrypter classes if (sandBox != null) { - sandBox.getUsedCustomClasses().forEach(clazz -> context().getClasses().remove(clazz.getInternalName())); + sandBox.getUsedCustomClasses().forEach(clazz -> context().getClassesMap().remove(clazz.getInternalName())); } } @@ -136,7 +136,7 @@ private void decryptEncryptedLongs(Context context, ClassWrapper classWrapper) { // Zelix came up with a great idea to infer class initialization order by the super classes. // So firstly, process encrypted longs in the super class if (classWrapper.classNode().superName != null && !classWrapper.classNode().superName.equals("java/lang/Object")) { - ClassWrapper superClass = context.getClasses().get(classWrapper.classNode().superName); + ClassWrapper superClass = context.getClassesMap().get(classWrapper.classNode().superName); if (superClass != null) { decryptEncryptedLongs(context, superClass); } @@ -161,7 +161,7 @@ private void decryptEncryptedLongs(Context context, ClassWrapper classWrapper) { long key2 = matchContext.captures().get("key-2").insn().asLong(); long decryptKey = matchContext.captures().get("decrypt-key").insn().asLong(); - ClassWrapper longDecrypterCreatorClass = context.getClasses().get(createDecrypterInsn.owner); + ClassWrapper longDecrypterCreatorClass = context.getClassesMap().get(createDecrypterInsn.owner); try { // Create decrypter diff --git a/testData/compiled/custom-classes/hp888/IIIlIIIlllIIIlllIIlIllIIlIIIIIllIlIIlIlIllIIIlIIII.class b/testData/compiled/custom-classes/hp888/IIIlIIIlllIIIlllIIlIllIIlIIIIIllIlIIlIlIllIIIlIIII.class new file mode 100644 index 0000000..72d6fbd Binary files /dev/null and b/testData/compiled/custom-classes/hp888/IIIlIIIlllIIIlllIIlIllIIlIIIIIllIlIIlIlIllIIIlIIII.class differ diff --git a/testData/compiled/custom-classes/hp888/com/bric/colorpicker/ColorPicker$1.mc b/testData/compiled/custom-classes/hp888/com/bric/colorpicker/ColorPicker$1.mc new file mode 100644 index 0000000..a0a1c69 Binary files /dev/null and b/testData/compiled/custom-classes/hp888/com/bric/colorpicker/ColorPicker$1.mc differ diff --git a/testData/compiled/custom-classes/hp888/com/bric/colorpicker/ColorPicker.mc b/testData/compiled/custom-classes/hp888/com/bric/colorpicker/ColorPicker.mc new file mode 100644 index 0000000..bb3f303 Binary files /dev/null and b/testData/compiled/custom-classes/hp888/com/bric/colorpicker/ColorPicker.mc differ diff --git a/testData/compiled/custom-classes/hp888/com/bric/colorpicker/ColorPickerMode.mc b/testData/compiled/custom-classes/hp888/com/bric/colorpicker/ColorPickerMode.mc new file mode 100644 index 0000000..c5f8c03 Binary files /dev/null and b/testData/compiled/custom-classes/hp888/com/bric/colorpicker/ColorPickerMode.mc differ diff --git a/testData/compiled/custom-classes/hp888/lIllIIllllllIlIIIIlIllIIlIIlllIIllIIIllIIlIlIIllIl.class b/testData/compiled/custom-classes/hp888/lIllIIllllllIlIIIIlIllIIlIIlllIIllIIIllIIlIlIIllIl.class deleted file mode 100644 index 93affa9..0000000 Binary files a/testData/compiled/custom-classes/hp888/lIllIIllllllIlIIIIlIllIIlIIlllIIllIIIllIIlIlIIllIl.class and /dev/null differ diff --git a/testData/compiled/custom-classes/hp888/pack/MyClassLoader.class b/testData/compiled/custom-classes/hp888/pack/MyClassLoader.class deleted file mode 100644 index 1e4a27d..0000000 Binary files a/testData/compiled/custom-classes/hp888/pack/MyClassLoader.class and /dev/null differ diff --git a/testData/compiled/custom-classes/hp888/pack/MyFunction2.class b/testData/compiled/custom-classes/hp888/pack/MyFunction2.class new file mode 100644 index 0000000..aac5924 Binary files /dev/null and b/testData/compiled/custom-classes/hp888/pack/MyFunction2.class differ diff --git a/testData/results/custom-classes/hp888/com/bric/colorpicker/ColorPicker$1.dec b/testData/results/custom-classes/hp888/com/bric/colorpicker/ColorPicker$1.dec new file mode 100644 index 0000000..30f70fc --- /dev/null +++ b/testData/results/custom-classes/hp888/com/bric/colorpicker/ColorPicker$1.dec @@ -0,0 +1,36 @@ +package com.bric.colorpicker; + +// $VF: synthetic class +public class ColorPicker$1 { + static { + try { + $SwitchMap$com$bric$colorpicker$ColorPickerMode[ColorPickerMode.HUE.ordinal()] = 1; + } catch (NoSuchFieldError var6) { + } + + try { + $SwitchMap$com$bric$colorpicker$ColorPickerMode[ColorPickerMode.SATURATION.ordinal()] = 2; + } catch (NoSuchFieldError var5) { + } + + try { + $SwitchMap$com$bric$colorpicker$ColorPickerMode[ColorPickerMode.BRIGHTNESS.ordinal()] = 3; + } catch (NoSuchFieldError var4) { + } + + try { + $SwitchMap$com$bric$colorpicker$ColorPickerMode[ColorPickerMode.RED.ordinal()] = 4; + } catch (NoSuchFieldError var3) { + } + + try { + $SwitchMap$com$bric$colorpicker$ColorPickerMode[ColorPickerMode.GREEN.ordinal()] = 5; + } catch (NoSuchFieldError var2) { + } + + try { + $SwitchMap$com$bric$colorpicker$ColorPickerMode[ColorPickerMode.BLUE.ordinal()] = 6; + } catch (NoSuchFieldError var1) { + } + } +} diff --git a/testData/results/custom-classes/hp888/com/bric/colorpicker/ColorPicker.dec b/testData/results/custom-classes/hp888/com/bric/colorpicker/ColorPicker.dec new file mode 100644 index 0000000..7a21b81 --- /dev/null +++ b/testData/results/custom-classes/hp888/com/bric/colorpicker/ColorPicker.dec @@ -0,0 +1,418 @@ +package com.bric.colorpicker; + +import com.bric.colorpicker.colorslider.ColorSlider; +import com.bric.colorpicker.colorslider.ColorSliderUI; +import com.bric.colorpicker.listeners.ColorListener; +import com.bric.colorpicker.listeners.HexFieldListener; +import com.bric.colorpicker.listeners.SelectAllListener; +import com.bric.colorpicker.models.ColorModel; +import com.bric.colorpicker.models.ModeModel; +import com.bric.colorpicker.options.AlphaOption; +import com.bric.colorpicker.options.BlueOption; +import com.bric.colorpicker.options.BrightnessOption; +import com.bric.colorpicker.options.GreenOption; +import com.bric.colorpicker.options.HueOption; +import com.bric.colorpicker.options.Option; +import com.bric.colorpicker.options.RedOption; +import com.bric.colorpicker.options.SaturationOption; +import com.bric.colorpicker.parts.ColorSwatch; +import com.bric.colorpicker.parts.HexField; +import com.bric.colorpicker.parts.OpacitySlider; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.ResourceBundle; +import javax.swing.ButtonGroup; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.JTextField; + +public class ColorPicker extends JPanel { + public static final String MODE_PROPERTY = "mode"; + private static final String SELECTED_COLOR_PROPERTY = "selected color"; + private static final String MODE_CONTROLS_VISIBLE_PROPERTY = "mode controls visible"; + private static ResourceBundle strings = ResourceBundle.getBundle("com.bric.colorpicker.resources.ColorPicker"); + private ColorModel colorModel = new ColorModel(); + private ModeModel modeModel = new ModeModel(); + private ColorSlider slider = new ColorSlider(); + private Option alphaOption = new AlphaOption(); + private Option hueOption = new HueOption(); + private Option saturationOption = new SaturationOption(); + private Option brightnessOption = new BrightnessOption(); + private Option redOption = new RedOption(); + private Option greenOption = new GreenOption(); + private Option blueOption = new BlueOption(); + private ColorSwatch preview = new ColorSwatch(50); + private JLabel hexLabel = new JLabel(strings.getObject("hexLabel").toString()); + private HexField hexField = new HexField(); + private JPanel expertControls = new JPanel(new GridBagLayout()); + private ColorPickerPanel colorPanel = new ColorPickerPanel(); + private OpacitySlider opacitySlider = new OpacitySlider(); + private JLabel opacityLabel = new JLabel(strings.getObject("opacityLabel").toString()); + + public ColorPicker() { + this(true, false); + } + + public ColorPicker(boolean var1, boolean var2) { + super(new GridBagLayout()); + this.initNames(); + GridBagConstraints var3 = new GridBagConstraints(); + Insets var4 = new Insets(3, 3, 3, 3); + JPanel var5 = new JPanel(new GridBagLayout()); + var3.gridx = 0; + var3.gridy = 0; + var3.weightx = 1.0; + var3.weighty = 1.0; + var3.insets = var4; + ButtonGroup var6 = new ButtonGroup(); + Option[] var7 = new Option[]{this.hueOption, this.saturationOption, this.brightnessOption, this.redOption, this.greenOption, this.blueOption}; + + for (int var8 = 0; var8 < var7.length; var8++) { + if (var8 != 3 && var8 != 6) { + var3.insets = var4; + } else { + var3.insets = new Insets(var4.top + 10, var4.left, var4.bottom, var4.right); + } + + var7[var8].addTo(var5, var3, var6); + } + + var3.insets = new Insets(var4.top + 10, var4.left, var4.bottom, var4.right); + var3.anchor = 22; + var3.fill = 0; + var5.add(this.hexLabel, var3); + var3.gridx++; + var3.anchor = 21; + var3.fill = 2; + var5.add(this.hexField, var3); + this.alphaOption.addTo(var5, var3); + var3.gridx = 0; + var3.gridy = 0; + var3.weightx = 1.0; + var3.weighty = 1.0; + var3.fill = 1; + var3.anchor = 10; + var3.insets = var4; + var3.gridwidth = 2; + this.add(this.colorPanel, var3); + var3.gridwidth = 1; + var3.insets = var4; + var3.gridx += 2; + var3.weighty = 1.0; + var3.gridwidth = 1; + var3.fill = 3; + var3.weightx = 0.0; + this.add(this.slider, var3); + var3.gridx++; + var3.fill = 3; + var3.gridheight = 0; + var3.anchor = 10; + var3.insets = new Insets(0, 0, 0, 0); + this.add(this.expertControls, var3); + var3.gridx = 0; + var3.gridheight = 1; + var3.gridy = 1; + var3.weightx = 0.0; + var3.weighty = 0.0; + var3.insets = var4; + var3.anchor = 10; + this.add(this.opacityLabel, var3); + var3.gridx++; + var3.gridwidth = 2; + var3.weightx = 1.0; + var3.fill = 2; + this.add(this.opacitySlider, var3); + var3.gridx = 0; + var3.gridy = 0; + var3.gridheight = 1; + var3.gridwidth = 1; + var3.fill = 1; + var3.weighty = 1.0; + var3.weightx = 1.0; + var3.anchor = 19; + var3.insets = new Insets(var4.top, var4.left + 8, var4.bottom + 10, var4.right + 8); + this.expertControls.add(this.preview, var3); + var3.gridy++; + var3.weighty = 0.0; + var3.anchor = 10; + var3.insets = new Insets(var4.top, var4.left, 0, var4.right); + this.expertControls.add(var5, var3); + this.initializeColorPanel(); + this.initializeSlider(); + this.initializePreview(); + this.initializeHexField(); + this.initialize(this.hueOption); + this.initialize(this.saturationOption); + this.initialize(this.brightnessOption); + this.initialize(this.redOption); + this.initialize(this.greenOption); + this.initialize(this.blueOption); + this.initializeOpacitySlider(); + this.initialize(this.alphaOption); + this.setExpertControlsVisible(var1); + this.setOpacityVisible(var2); + setOpaque(this, false); + this.setColor(Color.BLACK); + this.setMode(ColorPickerMode.BRIGHTNESS); + } + + private static void setOpaque(JComponent var0, boolean var1) { + if (!(var0 instanceof JTextField)) { + var0.setOpaque(false); + if (!(var0 instanceof JSpinner)) { + for (int var2 = 0; var2 < var0.getComponentCount(); var2++) { + JComponent var3 = (JComponent)var0.getComponent(var2); + setOpaque(var3, var1); + } + } + } + } + + private static void requireValidFloat(float var0, String var1) { + if (Float.isInfinite(var0) || Float.isNaN(var0)) { + throw new IllegalArgumentException("The " + var1 + " value '" + var0 + "' is not a valid number."); + } else if (var0 < 0.0F || var0 > 1.0F) { + throw new IllegalArgumentException("The " + var1 + " value '" + var0 + "' must be between [0,1]"); + } + } + + private void initialize(Option var1) { + this.colorModel.addColorListener(var1); + var1.addSpinnerChangeListener(var2 -> { + if (!this.colorModel.isChanging()) { + var1.aboutToChangeColor(); + var1.update(this.colorModel); + } + }); + this.modeModel.addListener(var1); + var1.addRadioActionListener(var2 -> { + if (!this.colorModel.isChanging()) { + var1.aboutToChangeMode(); + var1.update(this.modeModel); + } + }); + var1.addFocusListener(new SelectAllListener()); + } + + private void initializeOpacitySlider() { + this.colorModel.addColorListener(this.opacitySlider); + this.opacitySlider.addChangeListener(var1 -> { + if (!this.opacitySlider.getValueIsAdjusting()) { + if (this.colorModel.isChanging()) { + return; + } + + this.opacitySlider.aboutToChangeColor(); + this.colorModel.setAlpha(this.opacitySlider.getValue()); + } + }); + } + + private void initializeHexField() { + this.colorModel.addColorListener(this.hexField); + HexFieldListener var1 = new HexFieldListener(); + var1.setColorModel(this.colorModel); + var1.setHexField(this.hexField); + this.hexField.getDocument().addDocumentListener(var1); + this.hexField.addFocusListener(new SelectAllListener()); + } + + private void initializePreview() { + this.preview.setOpaque(true); + this.colorModel.addColorListener(this.preview); + } + + private void initializeColorPanel() { + int var1 = this.expertControls.getPreferredSize().height; + this.colorPanel.setPreferredSize(new Dimension(var1, var1)); + this.colorModel.addColorListener(this.colorPanel); + this.modeModel.addListener(this.colorPanel); + this.colorPanel.addChangeListener(var1x -> { + if (!this.colorModel.isChanging()) { + int[] var2 = this.colorPanel.getRGB(); + this.colorPanel.aboutToChangeColor(); + this.colorModel.setColor(new Color(var2[0], var2[1], var2[2])); + } + }); + } + + private void initializeSlider() { + this.colorModel.addColorListener(this.slider); + this.modeModel.addListener(this.slider); + this.slider.addChangeListener(var1 -> { + if (!this.slider.getValueIsAdjusting()) { + if (this.colorModel.isChanging()) { + return; + } + + this.slider.aboutToChangeColor(); + ColorPickerMode var2 = this.modeModel.getMode(); + switch (var2) { + case HUE: + this.colorModel.setHue((float)this.slider.getValue() / (float)var2.getMax()); + break; + case SATURATION: + this.colorModel.setSaturation((float)this.slider.getValue() / (float)var2.getMax()); + break; + case BRIGHTNESS: + this.colorModel.setBrightness((float)this.slider.getValue() / (float)var2.getMax()); + break; + case RED: + this.colorModel.setRed(this.slider.getValue()); + break; + case GREEN: + this.colorModel.setGreen(this.slider.getValue()); + break; + case BLUE: + this.colorModel.setBlue(this.slider.getValue()); + } + } + }); + this.slider.setUI(new ColorSliderUI(this.slider, this)); + } + + public Option getSelectedOption() { + ColorPickerMode var1 = this.getMode(); + switch (var1) { + case HUE: + return this.hueOption; + case SATURATION: + return this.saturationOption; + case BRIGHTNESS: + return this.brightnessOption; + case RED: + return this.redOption; + case GREEN: + return this.greenOption; + case BLUE: + return this.blueOption; + default: + return null; + } + } + + private void initNames() { + this.hexField.setName("Hex"); + this.hueOption.setName("Hue"); + this.saturationOption.setName("Saturation"); + this.brightnessOption.setName("Brightness"); + this.redOption.setName("Red"); + this.greenOption.setName("Green"); + this.blueOption.setName("Blue"); + } + + public void setHexControlsVisible(boolean var1) { + this.hexLabel.setVisible(var1); + this.hexField.setVisible(var1); + } + + public void setPreviewSwatchVisible(boolean var1) { + this.preview.setVisible(var1); + } + + public void setExpertControlsVisible(boolean var1) { + this.expertControls.setVisible(var1); + } + + public void setModeControlsVisible(boolean var1) { + this.hueOption.setRadioButtonVisible(var1 && this.hueOption.isVisible()); + this.saturationOption.setRadioButtonVisible(var1 && this.saturationOption.isVisible()); + this.brightnessOption.setRadioButtonVisible(var1 && this.brightnessOption.isVisible()); + this.redOption.setRadioButtonVisible(var1 && this.redOption.isVisible()); + this.greenOption.setRadioButtonVisible(var1 && this.greenOption.isVisible()); + this.blueOption.setRadioButtonVisible(var1 && this.blueOption.isVisible()); + this.putClientProperty("mode controls visible", var1); + } + + public ColorPickerMode getMode() { + return this.modeModel.getMode(); + } + + public void setMode(ColorPickerMode var1) { + if (var1 == null) { + throw new IllegalArgumentException("mode must not be null"); + } else { + this.modeModel.setMode(var1); + } + } + + public void setRGB(int var1, int var2, int var3) { + this.setColor(new Color(var1, var2, var3)); + } + + public Color getColor() { + return this.colorModel.getColor(); + } + + public void setColor(Color var1) { + Color var2 = this.colorModel.getColor(); + this.colorModel.setColor(var1); + this.firePropertyChange("selected color", var2, var1); + } + + public JPanel getExpertControls() { + return this.expertControls; + } + + public void setRGBControlsVisible(boolean var1) { + boolean var2 = this.areRadioButtonsAllowed(); + this.redOption.setVisible(var1, var2); + this.greenOption.setVisible(var1, var2); + this.blueOption.setVisible(var1, var2); + } + + private boolean areRadioButtonsAllowed() { + Boolean var1 = (Boolean)this.getClientProperty("mode controls visible"); + return var1 != null ? var1 : true; + } + + public void setHSBControlsVisible(boolean var1) { + boolean var2 = this.areRadioButtonsAllowed(); + this.hueOption.setVisible(var1, var2); + this.saturationOption.setVisible(var1, var2); + this.brightnessOption.setVisible(var1, var2); + } + + public final void setOpacityVisible(boolean var1) { + this.opacityLabel.setVisible(var1); + this.opacitySlider.setVisible(var1); + this.alphaOption.setLabelVisible(var1); + this.alphaOption.setSpinnerVisible(var1); + } + + public ColorPickerPanel getColorPanel() { + return this.colorPanel; + } + + public void setHSB(float var1, float var2, float var3) { + requireValidFloat(var1, "hue"); + requireValidFloat(var2, "saturation"); + requireValidFloat(var3, "brightness"); + this.setColor(Color.getHSBColor(var1, var2, var3)); + } + + public float[] getHSB() { + return this.colorModel.getHSB(); + } + + public int[] getRGB() { + return this.colorModel.getRGB(); + } + + public void setOpacity(int var1) { + this.setColor(new Color(this.colorModel.getRed(), this.colorModel.getGreen(), this.colorModel.getBlue(), var1)); + } + + public void addColorListener(ColorListener var1) { + this.colorModel.addColorListener(var1); + } + + public void removeColorListener(ColorListener var1) { + this.colorModel.removeColorListener(var1); + } +} diff --git a/testData/results/custom-classes/hp888/com/bric/colorpicker/ColorPickerMode.dec b/testData/results/custom-classes/hp888/com/bric/colorpicker/ColorPickerMode.dec new file mode 100644 index 0000000..7bc3b4d --- /dev/null +++ b/testData/results/custom-classes/hp888/com/bric/colorpicker/ColorPickerMode.dec @@ -0,0 +1,21 @@ +package com.bric.colorpicker; + +public enum ColorPickerMode { + HUE(360), + BRIGHTNESS(100), + SATURATION(100), + RED(255), + GREEN(255), + BLUE(255), + ALPHA(255); + + private int max; + + private ColorPickerMode(int var3) { + this.max = var3; + } + + public int getMax() { + return this.max; + } +} diff --git a/testData/results/custom-classes/hp888/lIllIIllllllIlIIIIlIllIIlIIlllIIllIIIllIIlIlIIllIl.dec b/testData/results/custom-classes/hp888/lIllIIllllllIlIIIIlIllIIlIIlllIIllIIIllIIlIlIIllIl.dec deleted file mode 100644 index d4c2237..0000000 Binary files a/testData/results/custom-classes/hp888/lIllIIllllllIlIIIIlIllIIlIIlllIIllIIIllIIlIlIIllIl.dec and /dev/null differ diff --git a/testData/results/custom-classes/hp888/pack/MyClassLoader.dec b/testData/results/custom-classes/hp888/pack/MyClassLoader.dec deleted file mode 100644 index 163ca0a..0000000 --- a/testData/results/custom-classes/hp888/pack/MyClassLoader.dec +++ /dev/null @@ -1,32 +0,0 @@ -package pack; - -public class MyClassLoader extends ClassLoader { - private static int[] llIIllIIlIIIIIIlIIIlIllII = new int[2]; - public MyFunction2 function2; - - public Class loadClass(String var1) { - if (this.function2 != null) { - Class var2 = this.function2.apply(var1); - if (var2 != null) { - return var2; - } - } - - return super.loadClass(var1); - } - - static { - llIIllIIlIIIIIIlIIIlIllII[0] = 1; - llIIllIIlIIIIIIlIIIlIllII[1] = 1; - } - - public MyClassLoader(MyFunction2 var1) { - StackTraceElement[] var2 = new Throwable().getStackTrace(); - if (var2[llIIllIIlIIIIIIlIIIlIllII[0]].getClassName().equals("pack.MyLoader") && var2[llIIllIIlIIIIIIlIIIlIllII[1]].getMethodName().equals("main")) { - this.function2 = var1; - this.function2.setClassLoader(this); - } else { - this.function2 = null; - } - } -} diff --git a/testData/results/custom-classes/hp888/pack/MyFunction2.dec b/testData/results/custom-classes/hp888/pack/MyFunction2.dec new file mode 100644 index 0000000..eb4995d Binary files /dev/null and b/testData/results/custom-classes/hp888/pack/MyFunction2.dec differ