From 0b7c6f0e8b46080420113a7c6fb48c1d958b7b1b Mon Sep 17 00:00:00 2001
From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com>
Date: Sat, 27 Jan 2024 18:45:27 +0100
Subject: [PATCH 1/3] Implement File writeString
---
.../transformer/j10/Java11ToJava10.java | 1 +
.../FilesWriteStringMCR.java | 68 +++++++++++++++++++
2 files changed, 69 insertions(+)
create mode 100644 src/main/java/net/raphimc/javadowngrader/transformer/j10/methodcallreplacer/FilesWriteStringMCR.java
diff --git a/src/main/java/net/raphimc/javadowngrader/transformer/j10/Java11ToJava10.java b/src/main/java/net/raphimc/javadowngrader/transformer/j10/Java11ToJava10.java
index bea9b08..1f3e21a 100644
--- a/src/main/java/net/raphimc/javadowngrader/transformer/j10/Java11ToJava10.java
+++ b/src/main/java/net/raphimc/javadowngrader/transformer/j10/Java11ToJava10.java
@@ -36,6 +36,7 @@ public Java11ToJava10() {
this.addMethodCallReplacer(Opcodes.INVOKEVIRTUAL, "java/lang/String", "stripTrailing", "()Ljava/lang/String;", new StringStripTrailingMCR());
this.addMethodCallReplacer(Opcodes.INVOKESTATIC, "java/nio/file/Files", "readString", new FilesReadStringMCR());
+ this.addMethodCallReplacer(Opcodes.INVOKESTATIC, "java/nio/file/Files", "writeString", new FilesWriteStringMCR());
this.addMethodCallReplacer(Opcodes.INVOKESTATIC, "java/nio/file/Path", "of", new PathOfMCR());
diff --git a/src/main/java/net/raphimc/javadowngrader/transformer/j10/methodcallreplacer/FilesWriteStringMCR.java b/src/main/java/net/raphimc/javadowngrader/transformer/j10/methodcallreplacer/FilesWriteStringMCR.java
new file mode 100644
index 0000000..6985fe2
--- /dev/null
+++ b/src/main/java/net/raphimc/javadowngrader/transformer/j10/methodcallreplacer/FilesWriteStringMCR.java
@@ -0,0 +1,68 @@
+/*
+ * This file is part of JavaDowngrader - https://github.com/RaphiMC/JavaDowngrader
+ * Copyright (C) 2023-2024 RK_01/RaphiMC and contributors
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package net.raphimc.javadowngrader.transformer.j10.methodcallreplacer;
+
+import net.raphimc.javadowngrader.RuntimeDepCollector;
+import net.raphimc.javadowngrader.transformer.DowngradeResult;
+import net.raphimc.javadowngrader.transformer.MethodCallReplacer;
+import net.raphimc.javadowngrader.util.ASMUtil;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.*;
+
+public class FilesWriteStringMCR implements MethodCallReplacer {
+
+ @Override
+ public InsnList getReplacement(ClassNode classNode, MethodNode methodNode, String originalName, String originalDesc, RuntimeDepCollector depCollector, DowngradeResult result) {
+ int freeVarIndex = ASMUtil.getFreeVarIndex(methodNode);
+ final InsnList replacement = new InsnList();
+
+ boolean hasStoredCharset;
+ if (originalDesc.equals("(Ljava/nio/file/Path;Ljava/lang/CharSequence;[Ljava/nio/file/OpenOption;)Ljava/nio/file/Path;")) {
+ hasStoredCharset = false;
+ } else if (originalDesc.equals("(Ljava/nio/file/Path;Ljava/lang/CharSequence;Ljava/nio/charset/Charset;[Ljava/nio/file/OpenOption;)Ljava/nio/file/Path;")) {
+ hasStoredCharset = true;
+ // Path CharSequence Charset OpenOption
+ replacement.add(new InsnNode(Opcodes.SWAP));
+ // Path CharSequence OpenOption Charset
+ replacement.add(new VarInsnNode(Opcodes.ASTORE, freeVarIndex));
+ // Path CharSequence OpenOption
+ } else {
+ throw new RuntimeException("Unsupported method descriptor: " + originalDesc);
+ }
+
+ // Path CharSequence OpenOption
+ replacement.add(new InsnNode(Opcodes.SWAP));
+ // Path OpenOption CharSequence
+ if (hasStoredCharset) {
+ replacement.add(new VarInsnNode(Opcodes.ALOAD, freeVarIndex));
+ // Path OpenOption CharSequence Charset
+ } else {
+ replacement.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/nio/charset/StandardCharsets", "UTF_8", "Ljava/nio/charset/Charset;"));
+ // Path OpenOption CharSequence Charset
+ }
+ replacement.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/CharSequence", "getBytes", "(Ljava/nio/charset/Charset;)[B"));
+ // Path OpenOption byte[]
+ replacement.add(new InsnNode(Opcodes.SWAP));
+ // Path byte[] OpenOption
+ replacement.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/nio/file/Files", "write", "(Ljava/nio/file/Path;[B[Ljava/nio/file/OpenOption;)Ljava/nio/file/Path;"));
+ // Path
+
+ return replacement;
+ }
+
+}
From a3e7b67a2f81bfa2fe46fb7743baa86a12ae2849 Mon Sep 17 00:00:00 2001
From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com>
Date: Sat, 27 Jan 2024 19:11:01 +0100
Subject: [PATCH 2/3] Fix bugs
---
.../j10/methodcallreplacer/FilesWriteStringMCR.java | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/main/java/net/raphimc/javadowngrader/transformer/j10/methodcallreplacer/FilesWriteStringMCR.java b/src/main/java/net/raphimc/javadowngrader/transformer/j10/methodcallreplacer/FilesWriteStringMCR.java
index 6985fe2..d02c384 100644
--- a/src/main/java/net/raphimc/javadowngrader/transformer/j10/methodcallreplacer/FilesWriteStringMCR.java
+++ b/src/main/java/net/raphimc/javadowngrader/transformer/j10/methodcallreplacer/FilesWriteStringMCR.java
@@ -48,14 +48,16 @@ public InsnList getReplacement(ClassNode classNode, MethodNode methodNode, Strin
// Path CharSequence OpenOption
replacement.add(new InsnNode(Opcodes.SWAP));
// Path OpenOption CharSequence
+ replacement.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, "java/lang/CharSequence", "toString", "()Ljava/lang/String;"));
+ // Path OpenOption String
if (hasStoredCharset) {
replacement.add(new VarInsnNode(Opcodes.ALOAD, freeVarIndex));
- // Path OpenOption CharSequence Charset
+ // Path OpenOption String Charset
} else {
replacement.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/nio/charset/StandardCharsets", "UTF_8", "Ljava/nio/charset/Charset;"));
- // Path OpenOption CharSequence Charset
+ // Path OpenOption String Charset
}
- replacement.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/CharSequence", "getBytes", "(Ljava/nio/charset/Charset;)[B"));
+ replacement.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/String", "getBytes", "(Ljava/nio/charset/Charset;)[B"));
// Path OpenOption byte[]
replacement.add(new InsnNode(Opcodes.SWAP));
// Path byte[] OpenOption
From eff1dad775039d55ab5f3fa1008d8b766b6fa38a Mon Sep 17 00:00:00 2001
From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com>
Date: Fri, 16 Feb 2024 21:26:15 +0100
Subject: [PATCH 3/3] Concept for a cheerpj powered web app
---
settings.gradle | 1 +
web/build.gradle | 34 ++++
web/gradle.properties | 1 +
web/public/index.html | 74 ++++++++
.../javadowngrader/web/JavaDowngraderWeb.java | 172 ++++++++++++++++++
5 files changed, 282 insertions(+)
create mode 100644 web/build.gradle
create mode 100644 web/gradle.properties
create mode 100644 web/public/index.html
create mode 100644 web/src/main/java/net/raphimc/javadowngrader/web/JavaDowngraderWeb.java
diff --git a/settings.gradle b/settings.gradle
index e1e5b7b..e4b6f50 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -8,6 +8,7 @@ pluginManagement {
rootProject.name = "JavaDowngrader"
include(":standalone")
+include(":web")
include(":bootstrap")
include(":runtime-dep")
include(":impl-classtransform")
diff --git a/web/build.gradle b/web/build.gradle
new file mode 100644
index 0000000..f884382
--- /dev/null
+++ b/web/build.gradle
@@ -0,0 +1,34 @@
+plugins {
+ id "java"
+}
+
+configurations {
+ include
+
+ implementation.extendsFrom include
+ api.extendsFrom include
+}
+
+dependencies {
+ include project(":impl-classtransform")
+
+ include "org.apache.logging.log4j:log4j-core:2.22.1"
+ include "org.apache.logging.log4j:log4j-slf4j2-impl:2.22.1"
+}
+
+jar {
+ dependsOn configurations.include
+ from {
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ configurations.include.collect {
+ zipTree(it)
+ }
+ } {
+ exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA"
+ }
+}
+
+project.tasks.withType(PublishToMavenRepository).forEach {
+ it.enabled = false
+}
+
diff --git a/web/gradle.properties b/web/gradle.properties
new file mode 100644
index 0000000..96bb5ca
--- /dev/null
+++ b/web/gradle.properties
@@ -0,0 +1 @@
+maven_name=JavaDowngrader-Web
diff --git a/web/public/index.html b/web/public/index.html
new file mode 100644
index 0000000..d5c2812
--- /dev/null
+++ b/web/public/index.html
@@ -0,0 +1,74 @@
+
+
+
+ JavaDowngrader example
+
+
+
+
+
+
+ Loading...
+
+
+
+
diff --git a/web/src/main/java/net/raphimc/javadowngrader/web/JavaDowngraderWeb.java b/web/src/main/java/net/raphimc/javadowngrader/web/JavaDowngraderWeb.java
new file mode 100644
index 0000000..54e4c29
--- /dev/null
+++ b/web/src/main/java/net/raphimc/javadowngrader/web/JavaDowngraderWeb.java
@@ -0,0 +1,172 @@
+/*
+ * This file is part of JavaDowngrader - https://github.com/RaphiMC/JavaDowngrader
+ * Copyright (C) 2023-2024 RK_01/RaphiMC and contributors
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package net.raphimc.javadowngrader.web;
+
+import net.lenni0451.classtransform.TransformerManager;
+import net.lenni0451.classtransform.additionalclassprovider.PathClassProvider;
+import net.lenni0451.classtransform.utils.tree.BasicClassProvider;
+import net.raphimc.javadowngrader.impl.classtransform.JavaDowngraderTransformer;
+import net.raphimc.javadowngrader.impl.classtransform.util.ClassNameUtil;
+import net.raphimc.javadowngrader.runtime.RuntimeRoot;
+import net.raphimc.javadowngrader.util.JavaVersion;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+public class JavaDowngraderWeb {
+ public static String[] supportedVersions() {
+ System.out.println("test2");
+ return Stream.of(JavaVersion.values()).map(JavaVersion::name).toArray(String[]::new);
+ }
+
+ public static String convert(String data, int threadCount, String targetVersion) throws Exception {
+ Path inputFolder = Paths.get("/files/input");
+ Path outputFolder = Paths.get("/files/output");
+ try {
+ Files.createDirectories(inputFolder);
+ Files.createDirectories(outputFolder);
+ ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data)));
+ ZipEntry zipEntry = zis.getNextEntry();
+ while (zipEntry != null) {
+ Path newFile = inputFolder.resolve(zipEntry.getName());
+ if (zipEntry.isDirectory()) {
+ Files.createDirectories(newFile);
+ } else {
+ Files.createDirectories(newFile.getParent());
+ Files.copy(zis, newFile);
+ }
+ zipEntry = zis.getNextEntry();
+ }
+ zis.closeEntry();
+ zis.close();
+
+ System.out.println("Opening source JAR");
+ final Collection runtimeDeps = Collections.newSetFromMap(new ConcurrentHashMap<>());
+ final TransformerManager transformerManager = new TransformerManager(
+ new PathClassProvider(inputFolder, new BasicClassProvider())
+ );
+ transformerManager.addBytecodeTransformer(
+ JavaDowngraderTransformer.builder(transformerManager)
+ .targetVersion(JavaVersion.valueOf(targetVersion).getVersion())
+ .classFilter(c -> Files.isRegularFile(inputFolder.resolve(ClassNameUtil.toClassFilename(c))))
+ .depCollector(runtimeDeps::add)
+ .build()
+ );
+
+ System.out.println("Downgrading classes with " + threadCount + " thread(s)");
+ final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
+ final List> tasks;
+ try (Stream stream = Files.walk(inputFolder)) {
+ tasks = stream.map(path -> (Callable) () -> {
+ final String relative = ClassNameUtil.slashName(inputFolder.relativize(path));
+ final Path dest = outputFolder.resolve(relative);
+ if (Files.isDirectory(path)) {
+ Files.createDirectories(dest);
+ return null;
+ }
+ final Path parent = dest.getParent();
+ if (parent != null) {
+ Files.createDirectories(parent);
+ }
+ if (!relative.endsWith(".class") || relative.contains("META-INF/versions/")) {
+ Files.copy(path, dest);
+ return null;
+ }
+ final String className = ClassNameUtil.toClassName(relative);
+ final byte[] bytecode = Files.readAllBytes(path);
+ byte[] result = null;
+ try {
+ result = transformerManager.transform(className, bytecode);
+ } catch (Exception e) {
+ System.err.println("Failed to transform " + className);
+ e.printStackTrace();
+ }
+ Files.write(dest, result != null ? result : bytecode);
+
+ return null;
+ }).collect(Collectors.toList());
+ }
+ threadPool.invokeAll(tasks);
+ threadPool.shutdown();
+ if (!threadPool.awaitTermination(1, TimeUnit.MINUTES)) {
+ throw new IllegalStateException("Thread pool didn't shutdown correctly");
+ }
+
+ System.out.println("Copying " + runtimeDeps.size() + " runtime class(es)");
+ for (final String runtimeDep : runtimeDeps) {
+ final String classPath = runtimeDep.concat(".class");
+ try (InputStream is = RuntimeRoot.class.getResourceAsStream("/" + classPath)) {
+ if (is == null) {
+ System.out.println("Runtime class '" + runtimeDep + "' not found! Skipping.");
+ continue;
+ }
+ final Path dest = outputFolder.resolve(classPath);
+ final Path parent = dest.getParent();
+ if (parent != null) {
+ Files.createDirectories(parent);
+ }
+ Files.copy(is, dest);
+ }
+ }
+ System.out.println("Writing final JAR");
+
+ // Create a new zip file
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (ZipOutputStream zos = new ZipOutputStream(baos)) {
+ Files.walk(outputFolder)
+ .filter(Files::isRegularFile)
+ .forEach(path -> {
+ ZipEntry zipEntry1 = new ZipEntry(inputFolder.relativize(path).toString());
+ try {
+ zos.putNextEntry(zipEntry1);
+ zos.write(Files.readAllBytes(path));
+ zos.closeEntry();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ return Base64.getEncoder().encodeToString(baos.toByteArray());
+ } catch (Throwable t) {
+ t.printStackTrace();
+ throw t;
+ } finally {
+ Files.walk(inputFolder)
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ Files.walk(outputFolder)
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ }
+ }
+}