diff --git a/build-logic/src/main/kotlin/poke.base-conventions.gradle.kts b/build-logic/src/main/kotlin/poke.base-conventions.gradle.kts index 6d7a1e9..e71506d 100644 --- a/build-logic/src/main/kotlin/poke.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/poke.base-conventions.gradle.kts @@ -3,7 +3,7 @@ plugins { } group = "run.slicer" -version = "1.0.0-SNAPSHOT" +version = "1.0.0" description = "A Java library for performing bytecode normalization and generic deobfuscation." repositories { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c8ffc8b..e924b51 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,15 @@ [versions] picocli = "4.7.6" +teavm = "0.10.0" [libraries] jspecify = { group = "org.jspecify", name = "jspecify", version = "0.3.0" } picocli = { group = "info.picocli", name = "picocli", version.ref = "picocli" } picocli-codegen = { group = "info.picocli", name = "picocli-codegen", version.ref = "picocli" } proguard = { group = "com.github.run-slicer", name = "proguard", version = "82ace0a065" } +teavm-core = { group = "org.teavm", name = "teavm-core", version.ref = "teavm" } [plugins] shadow = { id = "com.gradleup.shadow", version = "8.3.0" } blossom = { id = "net.kyori.blossom", version = "2.1.0" } +teavm = { id = "org.teavm", version.ref = "teavm" } diff --git a/js/.gitignore b/js/.gitignore new file mode 100644 index 0000000..7773828 --- /dev/null +++ b/js/.gitignore @@ -0,0 +1 @@ +dist/ \ No newline at end of file diff --git a/js/build.gradle.kts b/js/build.gradle.kts new file mode 100644 index 0000000..07ba360 --- /dev/null +++ b/js/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + id("poke.base-conventions") + alias(libs.plugins.teavm) +} + +dependencies { + implementation(project(":${rootProject.name}-core")) + compileOnly(libs.teavm.core) +} + +teavm.js { + mainClass = "run.slicer.poke.js.Main" + moduleType = org.teavm.gradle.api.JSModuleType.ES2015 + // obfuscated = false + // optimization = org.teavm.gradle.api.OptimizationLevel.NONE +} + +tasks { + register("copyDist") { + group = "build" + + from("../README.md", "../LICENSE", generateJavaScript, "poke.d.ts") + into("dist") + + doLast { + file("dist/package.json").writeText( + """ + { + "name": "@run-slicer/poke", + "version": "${project.version}", + "description": "A library for performing Java bytecode normalization and generic deobfuscation.", + "main": "poke-js.js", + "types": "poke.d.ts", + "keywords": [ + "deobfuscation", + "java", + "bytecode", + "optimization" + ], + "author": "run-slicer", + "license": "GPL-2.0-only" + } + """.trimIndent() + ) + } + } + + build { + dependsOn("copyDist") + } +} diff --git a/js/poke.d.ts b/js/poke.d.ts new file mode 100644 index 0000000..8c6ce6b --- /dev/null +++ b/js/poke.d.ts @@ -0,0 +1,9 @@ +declare module "@run-slicer/poke" { + export interface Config { + passes?: number; + optimize?: boolean; + verify?: boolean; + } + + export function analyze(data: Uint8Array, config?: Config): Uint8Array; +} diff --git a/js/src/main/java/run/slicer/poke/js/Main.java b/js/src/main/java/run/slicer/poke/js/Main.java new file mode 100644 index 0000000..2deaebe --- /dev/null +++ b/js/src/main/java/run/slicer/poke/js/Main.java @@ -0,0 +1,23 @@ +package run.slicer.poke.js; + +import org.teavm.jso.JSByRef; +import org.teavm.jso.JSExport; +import org.teavm.jso.core.JSObjects; +import run.slicer.poke.Analyzer; + +public class Main { + @JSExport + public static @JSByRef byte[] analyze(@JSByRef byte[] data, Options options) { + return analyze0(data, options == null || JSObjects.isUndefined(options) ? JSObjects.create() : options); + } + + private static byte[] analyze0(byte[] data, Options options) { + final Analyzer analyzer = Analyzer.builder() + .passes(options.passes()) + .optimize(options.optimize()) + .verify(options.verify()) + .build(); + + return analyzer.analyze(data); + } +} diff --git a/js/src/main/java/run/slicer/poke/js/Options.java b/js/src/main/java/run/slicer/poke/js/Options.java new file mode 100644 index 0000000..a142655 --- /dev/null +++ b/js/src/main/java/run/slicer/poke/js/Options.java @@ -0,0 +1,15 @@ +package run.slicer.poke.js; + +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; + +public interface Options extends JSObject { + @JSBody(script = "return this.passes || 0;") + int passes(); + + @JSBody(script = "return Boolean(this.optimize);") + boolean optimize(); + + @JSBody(script = "return Boolean(this.verify);") + boolean verify(); +} diff --git a/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/DummyExecutorService.java b/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/DummyExecutorService.java new file mode 100644 index 0000000..a062ff2 --- /dev/null +++ b/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/DummyExecutorService.java @@ -0,0 +1,122 @@ +package run.slicer.poke.js.teavm.classlib.java.util.concurrent; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.*; + +final class DummyExecutorService implements ExecutorService { + private boolean shutdown = false; + + @Override + public void shutdown() { + this.shutdown = true; + } + + @Override + public List shutdownNow() { + this.shutdown = true; + return List.of(); + } + + @Override + public boolean isShutdown() { + return this.shutdown; + } + + @Override + public boolean isTerminated() { + return this.shutdown; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) { + return true; + } + + @Override + public Future submit(Callable task) { + try { + return new Task<>(task.call(), null); + } catch (Throwable t) { + return new Task<>(null, t); + } + } + + @Override + public Future submit(Runnable task, T result) { + try { + task.run(); + } catch (Throwable t) { + return new Task<>(null, t); + } + + return new Task<>(result, null); + } + + @Override + public Future submit(Runnable task) { + try { + task.run(); + } catch (Throwable t) { + return new Task<>(null, t); + } + + return new Task<>(null, null); + } + + @Override + public List> invokeAll(Collection> tasks) { + throw new UnsupportedOperationException(); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public T invokeAny(Collection> tasks) { + throw new UnsupportedOperationException(); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public void execute(Runnable command) { + command.run(); + } + + private record Task(T value, Throwable error) implements Future { + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public T get() throws ExecutionException { + if (this.error != null) { + throw new ExecutionException(this.error); + } + + return this.value; + } + + @Override + public T get(long timeout, TimeUnit unit) throws ExecutionException { + return this.get(); + } + } +} diff --git a/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/TExecutorService.java b/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/TExecutorService.java new file mode 100644 index 0000000..e0bafb1 --- /dev/null +++ b/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/TExecutorService.java @@ -0,0 +1,9 @@ +package run.slicer.poke.js.teavm.classlib.java.util.concurrent; + +import java.util.concurrent.Executor; + +public interface TExecutorService extends Executor { + TFuture submit(Runnable task); + + void shutdown(); +} diff --git a/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/TExecutors.java b/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/TExecutors.java new file mode 100644 index 0000000..dda083e --- /dev/null +++ b/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/TExecutors.java @@ -0,0 +1,10 @@ +package run.slicer.poke.js.teavm.classlib.java.util.concurrent; + +import java.util.concurrent.ThreadFactory; + +public class TExecutors { + @SuppressWarnings("DataFlowIssue") + public static TExecutorService newFixedThreadPool(int ignored0, ThreadFactory ignored1) { + return (TExecutorService) ((Object) new DummyExecutorService()); + } +} diff --git a/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/TFuture.java b/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/TFuture.java new file mode 100644 index 0000000..bdbca4d --- /dev/null +++ b/js/src/teavm/java/run/slicer/poke/js/teavm/classlib/java/util/concurrent/TFuture.java @@ -0,0 +1,7 @@ +package run.slicer.poke.js.teavm.classlib.java.util.concurrent; + +import java.util.concurrent.ExecutionException; + +public interface TFuture { + V get() throws InterruptedException, ExecutionException; +} diff --git a/js/src/teavm/resources/META-INF/teavm.properties b/js/src/teavm/resources/META-INF/teavm.properties new file mode 100644 index 0000000..a33c9f0 --- /dev/null +++ b/js/src/teavm/resources/META-INF/teavm.properties @@ -0,0 +1,3 @@ +mapClass|run.slicer.poke.js.teavm.classlib.java.util.concurrent.TExecutors=java.util.concurrent.Executors +mapClass|run.slicer.poke.js.teavm.classlib.java.util.concurrent.TExecutorService=java.util.concurrent.ExecutorService +mapClass|run.slicer.poke.js.teavm.classlib.java.util.concurrent.TFuture=java.util.concurrent.Future \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index e4d2558..9524940 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,4 +17,4 @@ fun includePrefixed(vararg modules: String) { } } -includePrefixed("core", "cli") +includePrefixed("core", "cli", "js")