Skip to content
This repository was archived by the owner on May 12, 2024. It is now read-only.

Concept for a cheerpj powered web app #54

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pluginManagement {
rootProject.name = "JavaDowngrader"

include(":standalone")
include(":web")
include(":bootstrap")
include(":runtime-dep")
include(":impl-classtransform")
Expand Down
34 changes: 34 additions & 0 deletions web/build.gradle
Original file line number Diff line number Diff line change
@@ -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
}

1 change: 1 addition & 0 deletions web/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
maven_name=JavaDowngrader-Web
74 changes: 74 additions & 0 deletions web/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html>
<head>
<title>JavaDowngrader example</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<script src="https://cjrtnc.leaningtech.com/3.0/cj3loader.js"></script>
</head>
<body>
<form style="display: none" id="downgrader-form">
<label for="file">File:</label>
<input type="file" id="file" name="file" accept=".jar">
<label for="version-select">Target version:</label>
<select id="version-select">
</select>
<input type="button" value="Downgrade" onclick="downgrade()">
</form>
<p id="loading">
Loading...
</p>
<script>
(async function () {
await cheerpjInit();
const cj = await cheerpjRunLibrary("/app/JavaDowngrader-Web-1.1.2-SNAPSHOT.jar");
window.javadowngrader = await cj.net.raphimc.javadowngrader.web.JavaDowngraderWeb

console.log("Creating form")
const versions = await window.javadowngrader.supportedVersions();
const select = document.getElementById("version-select");
for (let i = 0; i < versions.length; i++) {
const version = versions[i];
const option = document.createElement("option");
option.value = version;
option.text = version;
select.appendChild(option);
}

document.getElementById("downgrader-form").style.display = "block";
document.getElementById("loading").style.display = "none";

console.log("Form created")
})()

async function downgrade() {
const file = document.getElementById("file").files[0];
const version = document.getElementById("version-select").value;

const reader = new FileReader();
reader.onload = async function (e) {
const file = e.target.result;
const base64Data = bytesToBase64(new Uint8Array(file));
const downgraded = await window.javadowngrader.convert(base64Data, 20, version);

const blob = new Blob([base64ToBytes(downgraded)], {type: "application/java-archive"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = file.name;
a.click();
};
reader.readAsDataURL(file);
}

function base64ToBytes(base64) {
const binString = atob(base64);
return Uint8Array.from(binString, (m) => m.codePointAt(0));
}

function bytesToBase64(bytes) {
const binString = String.fromCodePoint(...bytes);
return btoa(binString);
}
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> 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<Callable<Void>> tasks;
try (Stream<Path> stream = Files.walk(inputFolder)) {
tasks = stream.map(path -> (Callable<Void>) () -> {
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);
}
}
}