Skip to content

Commit

Permalink
speed up jar loading, update cafed00d, some refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
EpicPlayerA10 committed Jan 6, 2025
1 parent cc464fa commit 3b46fb8
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 67 deletions.
2 changes: 1 addition & 1 deletion deobfuscator-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

<log4j.version>2.23.1</log4j.version>
<slf4j.version>2.0.13</slf4j.version>
<cafedude.version>2.1.2</cafedude.version>
<cafedude.version>2.1.3</cafedude.version>
<ssvm.version>ca3c3ab713</ssvm.version>
<jlinker.version>205d8eaa1f</jlinker.version>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public MethodContext methodContext() {
}

/**
* Pops current instruction's stack values by adding POP instructions before this instruction
* Places POP or POP2 instruction before current instruction to remove the value from the stack
*
* @param count Stack values count to pop
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ClassStorage implements ClassProvider {
/**
* Class storage that holds only information about classes, not the bytecode.
*/
public class ClassInfoStorage implements ClassProvider {
private final Map<String, byte[]> compiledClasses = new ConcurrentHashMap<>();
private final Map<String, byte[]> files = new ConcurrentHashMap<>();

// ClassNode without code, only info (like name, superName, interfaces, etc.)
private final Map<String, ClassNode> classesInfo = new ConcurrentHashMap<>();

/**
Expand All @@ -30,21 +34,24 @@ public void addJar(@NotNull Path jarPath) {
return;
}

addRawClass(bytes);
try {
// Fix class bytes
bytes = ClassHelper.fixClass(bytes);

addRawClass(bytes);
} catch (InvalidClassException e) {
throw new RuntimeException(e);
}
});
}

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);
}
ClassNode classNode = ClassHelper.loadClassInfo(bytes);
String className = classNode.name;

// Add class to class storage
compiledClasses.putIfAbsent(className, bytes);
classesInfo.putIfAbsent(className, classNode);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
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.classpath.ClassInfoStorage;
import uwu.narumi.deobfuscator.api.execution.SandBox;
import uwu.narumi.deobfuscator.api.helper.ClassHelper;

Expand All @@ -21,19 +21,19 @@ public class Context implements ClassProvider {

private final DeobfuscatorOptions options;

private final ClassStorage compiledClasses;
private final ClassStorage libraries;
private final ClassInfoStorage compiledClasses;
private final ClassInfoStorage libraries;

private SandBox globalSandBox = null;

/**
* Creates a new {@link Context} instance from its options
*
* @param options Deobfuscator options
* @param compiledClasses {@link ClassStorage} that holds the original classes of the primary jar
* @param libraries {@link ClassStorage} that holds the libraries' classes
* @param compiledClasses {@link ClassInfoStorage} that holds the original classes of the primary jar
* @param libraries {@link ClassInfoStorage} that holds the libraries' classes
*/
public Context(DeobfuscatorOptions options, ClassStorage compiledClasses, ClassStorage libraries) {
public Context(DeobfuscatorOptions options, ClassInfoStorage compiledClasses, ClassInfoStorage libraries) {
this.options = options;

this.compiledClasses = compiledClasses;
Expand All @@ -58,14 +58,14 @@ public DeobfuscatorOptions getOptions() {
/**
* Class storage that holds already compiled classes from original jar
*/
public ClassStorage getCompiledClasses() {
public ClassInfoStorage getCompiledClasses() {
return compiledClasses;
}

/**
* Class storage that holds libraries' classes
*/
public ClassStorage getLibraries() {
public ClassInfoStorage getLibraries() {
return libraries;
}

Expand All @@ -82,8 +82,12 @@ public List<ClassWrapper> scopedClasses(ClassWrapper scope) {

public void addCompiledClass(String pathInJar, byte[] bytes) {
try {
ClassWrapper classWrapper = ClassHelper.loadUnknownClass(pathInJar, bytes, ClassReader.SKIP_FRAMES);
// Fix class bytes
bytes = ClassHelper.fixClass(bytes);

ClassWrapper classWrapper = ClassHelper.loadClass(pathInJar, bytes, ClassReader.SKIP_FRAMES);
this.classesMap.putIfAbsent(classWrapper.name(), classWrapper);

this.compiledClasses.addRawClass(bytes);
} catch (InvalidClassException e) {
throw new RuntimeException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,6 @@ public static ClassWrapper loadClass(
return new ClassWrapper(pathInJar, new ClassReader(bytes), classReaderFlags);
}

/**
* Loads class from unknown sources. Applies fixes to bytecode using CAFED00D.
*
* @param pathInJar Relative path of a class in a jar
* @param bytes Class bytes
* @param classReaderFlags {@link ClassReader} flags
*/
public static ClassWrapper loadUnknownClass(
String pathInJar,
byte[] bytes,
@MagicConstant(flagsFromClass = ClassReader.class) int classReaderFlags
) throws InvalidClassException {
// Fix class
bytes = fixClass(bytes);

return loadClass(pathInJar, bytes, classReaderFlags);
}

/**
* Loads only class info (like class name, superclass, interfaces, etc.) without any code.
*
Expand All @@ -74,18 +56,11 @@ public static ClassNode loadClassInfo(byte[] bytes) {
}

/**
* Loads class info from unknown sources. Applies fixes to bytecode using CAFED00D.
* Fix class using CAFED00D
*
* @param bytes Class bytes
* @return {@link ClassNode} with class info only
* @return Fixed class bytes
*/
public static ClassNode loadUnknownClassInfo(byte[] bytes) throws InvalidClassException {
// Fix class
bytes = fixClass(bytes);

return loadClassInfo(bytes);
}

public static byte[] fixClass(byte[] bytes) throws InvalidClassException {
ClassFileReader classFileReader = new ClassFileReader();
ClassFile classFile = classFileReader.read(bytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.logging.log4j.LogManager;
Expand All @@ -19,20 +26,39 @@ public final class FileHelper {
private FileHelper() {
}

/**
* Load all files from a zip file asynchronously
*
* @param path Path to the zip file
* @param consumer Consumer that accepts the file path and the file bytes. This method is called asynchronously
*/
public static void loadFilesFromZip(Path path, BiConsumer<String, byte[]> consumer) {
try (JarFile zipFile = new JarFile(path.toFile())) {
zipFile.entries()
.asIterator()
.forEachRemaining(zipEntry -> {
if (zipEntry.isDirectory()) return;
ExecutorService executorService = Executors.newFixedThreadPool(5);
List<CompletableFuture<Void>> futures = new ArrayList<>();

try {
consumer.accept(zipEntry.getName(), zipFile.getInputStream(zipEntry).readAllBytes());
} catch (Exception e) {
LOGGER.error("Could not load ZipEntry: {}", zipEntry.getName());
LOGGER.debug("Error", e);
}
});
Iterator<JarEntry> it = zipFile.entries().asIterator();

while (it.hasNext()) {
JarEntry zipEntry = it.next();
if (zipEntry.isDirectory()) continue;

String name = zipEntry.getName();
byte[] bytes = zipFile.getInputStream(zipEntry).readAllBytes();

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
consumer.accept(name, bytes);
} catch (Exception e) {
LOGGER.error("Could not load ZipEntry: {}", zipEntry.getName(), e);
}
}, executorService);
futures.add(future);
}

// Wait for all futures to complete
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
executorService.shutdown();
} catch (Exception e) {
LOGGER.error("Could not load file: {}", path);
throw new RuntimeException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static Map<AbstractInsnNode, Frame<OriginalSourceValue>> analyzeSource(
try {
framesArray = new JumpPredictingAnalyzer(new OriginalSourceInterpreter()).analyze(classNode.name, methodNode);
} catch (AnalyzerException e) {
throw new RuntimeException(e);
throw new RuntimeException("Error analyzing " + classNode.name + "#" + methodNode.name + methodNode.desc, e);
}
for (int i = 0; i < framesArray.length; i++) {
frames.put(methodNode.instructions.get(i), framesArray[i]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import uwu.narumi.deobfuscator.api.classpath.ClassStorage;
import uwu.narumi.deobfuscator.api.classpath.ClassInfoStorage;
import uwu.narumi.deobfuscator.api.classpath.CombinedClassProvider;
import uwu.narumi.deobfuscator.api.context.Context;
import uwu.narumi.deobfuscator.api.context.DeobfuscatorOptions;
Expand Down Expand Up @@ -48,16 +48,16 @@ private Deobfuscator(DeobfuscatorOptions options) {
}

// Those classes will be loaded by Deobfuscator#loadInput
ClassStorage compiledClasses = new ClassStorage();
ClassInfoStorage compiledClasses = new ClassInfoStorage();

ClassStorage libraries = buildLibraries();
ClassInfoStorage libraries = buildLibraries();
LOGGER.info("Loaded {} classes from libraries", libraries.compiledClasses().size());

this.context = new Context(options, compiledClasses, libraries);
}

public ClassStorage buildLibraries() {
ClassStorage classStorage = new ClassStorage();
public ClassInfoStorage buildLibraries() {
ClassInfoStorage classStorage = new ClassInfoStorage();
// Add libraries
options.libraries().forEach(classStorage::addJar);

Expand Down

0 comments on commit 3b46fb8

Please sign in to comment.