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); + } + } +}