Compare commits
57 commits
TheKodeToa
...
main
Author | SHA1 | Date | |
---|---|---|---|
moehreag | f5f4cb1d4f | ||
moehreag | e08ed69730 | ||
TheKodeToad | 5ce38092c6 | ||
moehreag | bc47d9cc1a | ||
moehreag | f74fbc5248 | ||
moehreag | d68f4abea0 | ||
moehreag | 1fff5c75f6 | ||
moehreag | 811b4e3611 | ||
moehreag | f90baaef1b | ||
moehreag | 2d524c0eea | ||
moehreag | 69b62e7d0e | ||
moehreag | 25fefae245 | ||
Ecorous | 068928ad3d | ||
moehreag | e84f01e117 | ||
moehreag | 9a1f48345f | ||
moehreag | 84fd29a7f4 | ||
moehreag | a77f361527 | ||
moehreag | 367a77b9a2 | ||
moehreag | 9431528c47 | ||
moehreag | 5eaeaba14f | ||
moehreag | ba6c5ea32c | ||
moehreag | 422fa6aa3a | ||
moehreag | 98b52d394f | ||
moehreag | fbba141ba9 | ||
moehreag | dfcad64308 | ||
moehreag | 1187039f90 | ||
moehreag | a0a5f6cf2f | ||
owlsys | 9728a83d3e | ||
TheKodeToad | e7bde3bab1 | ||
moehreag | 087234d3f9 | ||
moehreag | 8ce3afa7d6 | ||
moehreag | 7005d47d1e | ||
moehreag | 2a3c244d0c | ||
moehreag | 0c213ceff4 | ||
moehreag | c89e190c0b | ||
moehreag | ebb333bd02 | ||
moehreag | e78696e425 | ||
moehreag | 9c81fb61cc | ||
moehreag | 39528a7468 | ||
moehreag | b1795595b8 | ||
moehreag | 80d2ef41e0 | ||
TheKodeToad | af4af1ba29 | ||
TheKodeToad | b1a30431f3 | ||
moehreag | 8653453644 | ||
moehreag | adad073065 | ||
moehreag | 7fbc4ecdaf | ||
moehreag | 7e5962a1f2 | ||
moehreag | 021aba65aa | ||
moehreag | 47ea5135d3 | ||
moehreag | ff6ce3210b | ||
moehreag | 70117f8873 | ||
moehreag | aa187beaba | ||
TheKodeToad | dcf69d2e34 | ||
TheKodeToad | 4ae8f8c80f | ||
TheKodeToad | 3c0a637e7c | ||
TheKodeToad | 361b9d62f3 | ||
TheKodeToad | 29abf83418 |
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
tab_width = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = tab
|
23
.forgejo/workflows/publish.yml
Normal file
23
.forgejo/workflows/publish.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
name: Publish to snapshot maven
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: https://github.com/actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: 21
|
||||||
|
- uses: https://github.com/gradle/actions/setup-gradle@v3
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
chmod +x ./gradlew
|
||||||
|
./gradlew publishMavenJavaPublicationToFrogMCSnapshotsMavenRepository \
|
||||||
|
-PFrogMCSnapshotsMavenUsername=${{ secrets.MAVEN_PUSH_USER }} \
|
||||||
|
-PFrogMCSnapshotsMavenPassword=${{ secrets.MAVEN_PUSH_TOKEN }}
|
|
@ -1,21 +1,63 @@
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("java")
|
java
|
||||||
|
`java-library`
|
||||||
id("io.freefair.lombok").version("8.6+")
|
id("io.freefair.lombok").version("8.6+")
|
||||||
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "org.example"
|
group = "dev.frogmc"
|
||||||
version = "1.0-SNAPSHOT"
|
version = "0.0.1-alpha.18"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.google.code.gson:gson:2.10.1")
|
implementation("com.electronwill.night-config:json:3.8.1")
|
||||||
implementation("org.ow2.asm:asm-tree:9.7")
|
implementation("org.ow2.asm:asm:9.7")
|
||||||
implementation("org.ow2.asm:asm-commons:9.7")
|
implementation("org.ow2.asm:asm-commons:9.7")
|
||||||
implementation("org.ow2.asm:asm-util:9.7")
|
}
|
||||||
|
|
||||||
// Annotation Processor
|
java {
|
||||||
annotationProcessor("org.ow2.asm:asm-tree:9.7")
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.jar {
|
||||||
|
manifest {
|
||||||
|
attributes("Implementation-Version" to version,
|
||||||
|
"Implementation-Name" to project.name,
|
||||||
|
"Implementation-Date" to Date(),
|
||||||
|
"Main-Class" to "dev.frogmc.thyroxine.Thyroxine")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
create<MavenPublication>("mavenJava") {
|
||||||
|
from(components["java"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = "FrogMCSnapshotsMaven"
|
||||||
|
url = uri("https://maven.frogmc.dev/snapshots")
|
||||||
|
credentials(PasswordCredentials::class)
|
||||||
|
authentication {
|
||||||
|
create<BasicAuthentication>("basic")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maven {
|
||||||
|
name = "FrogMCReleasesMaven"
|
||||||
|
url = uri("https://maven.frogmc.dev/releases")
|
||||||
|
credentials(PasswordCredentials::class)
|
||||||
|
authentication {
|
||||||
|
create<BasicAuthentication>("basic")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,5 @@
|
||||||
#Sat Apr 27 20:33:54 CEST 2024
|
#Sat Apr 27 20:33:54 CEST 2024
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
rootProject.name = "mojmap-patcher"
|
rootProject.name = "thyroxine"
|
||||||
|
|
||||||
|
|
15
src/main/java/dev/frogmc/thyroxine/Constants.java
Normal file
15
src/main/java/dev/frogmc/thyroxine/Constants.java
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package dev.frogmc.thyroxine;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.json.JsonParser;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
public final class Constants {
|
||||||
|
public static final String USER_AGENT = "FrogMC Thyroxine/" +
|
||||||
|
Objects.requireNonNullElse(Constants.class.getPackage().getImplementationVersion(), "development") +
|
||||||
|
" <frogmc.dev>";
|
||||||
|
public static final String VERSION_MANIFEST = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
|
||||||
|
public static final JsonParser JSON_PARSER = new JsonParser();
|
||||||
|
public static final int ASM_VERSION = Opcodes.ASM9;
|
||||||
|
}
|
44
src/main/java/dev/frogmc/thyroxine/HttpHelper.java
Normal file
44
src/main/java/dev/frogmc/thyroxine/HttpHelper.java
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package dev.frogmc.thyroxine;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.net.http.HttpClient.Redirect;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import static dev.frogmc.thyroxine.Constants.JSON_PARSER;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class HttpHelper {
|
||||||
|
|
||||||
|
public static UnmodifiableConfig getJson(String url) throws IOException {
|
||||||
|
return JSON_PARSER.parse(getString(url)).unmodifiable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getString(String url) throws IOException {
|
||||||
|
return request(url, HttpResponse.BodyHandlers.ofString()).body();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void download(String url, Path file) throws IOException {
|
||||||
|
request(url, HttpResponse.BodyHandlers.ofFile(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> HttpResponse<T> request(String url, HttpResponse.BodyHandler<T> handler) throws IOException {
|
||||||
|
try (HttpClient client = HttpClient.newBuilder().followRedirects(Redirect.NORMAL).build()) {
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.header("User-Agent", Constants.USER_AGENT)
|
||||||
|
.header("Accept", "*/*")
|
||||||
|
.GET()
|
||||||
|
.uri(URI.create(url))
|
||||||
|
.build();
|
||||||
|
return client.send(request, handler);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/main/java/dev/frogmc/thyroxine/RemappingStep.java
Normal file
8
src/main/java/dev/frogmc/thyroxine/RemappingStep.java
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.frogmc.thyroxine;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
public interface RemappingStep {
|
||||||
|
ClassVisitor run(ClassVisitor previous, Remapper remapper);
|
||||||
|
}
|
228
src/main/java/dev/frogmc/thyroxine/Thyroxine.java
Normal file
228
src/main/java/dev/frogmc/thyroxine/Thyroxine.java
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
package dev.frogmc.thyroxine;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.api.Mapper;
|
||||||
|
import dev.frogmc.thyroxine.api.ParameterClassRemapper;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingBundle;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
import dev.frogmc.thyroxine.parser.ProguardParser;
|
||||||
|
import dev.frogmc.thyroxine.parser.tiny.TinyV1Parser;
|
||||||
|
import dev.frogmc.thyroxine.parser.tiny.TinyV2Parser;
|
||||||
|
import dev.frogmc.thyroxine.provider.MojmapProvider;
|
||||||
|
import dev.frogmc.thyroxine.provider.ParchmentProvider;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.commons.ClassRemapper;
|
||||||
|
import org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
public class Thyroxine {
|
||||||
|
|
||||||
|
private static boolean SYSOUT = false;
|
||||||
|
|
||||||
|
public static void run(String minecraftVersion, Path inputJar, Path outputJar, boolean skipMetaInf, boolean renameParameters) throws IOException, InterruptedException {
|
||||||
|
Path out = outputJar.toAbsolutePath();
|
||||||
|
MappingBundle data = MojmapProvider.get(minecraftVersion,
|
||||||
|
out.resolveSibling("client-" + minecraftVersion + ".txt"),
|
||||||
|
out.resolveSibling("server-" + minecraftVersion + ".txt")).reverse();
|
||||||
|
|
||||||
|
MappingBundle parchment = null;
|
||||||
|
if (renameParameters) {
|
||||||
|
parchment = ParchmentProvider.getParchment(minecraftVersion, out.getParent());
|
||||||
|
}
|
||||||
|
|
||||||
|
MappingBundle result;
|
||||||
|
if (parchment != null) {
|
||||||
|
result = MappingBundle.merge(data, parchment);
|
||||||
|
} else {
|
||||||
|
result = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
SYSOUT = true;
|
||||||
|
remap(result, inputJar, out, skipMetaInf, renameParameters, "official", "named");
|
||||||
|
SYSOUT = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remap(MappingData data, Path inputJar, Path outputJar, boolean skipMetaInf, Path... context) throws IOException, InterruptedException {
|
||||||
|
remap(data, inputJar, outputJar, skipMetaInf, true, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remap(MappingBundle bundle, Path inputJar, Path outputJar, boolean skipMetaInf, boolean renameParameters, String srcNamespace, String dstNamespace, Path... context) throws IOException, InterruptedException {
|
||||||
|
remap(bundle.forNamespaces(srcNamespace, dstNamespace), inputJar, outputJar, skipMetaInf, renameParameters, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remap(MappingData data, Path inputJar, Path outputJar, boolean skipMetaInf, boolean renameParameters, Path... context) throws IOException, InterruptedException {
|
||||||
|
List<RemappingStep> steps = List.of(
|
||||||
|
ClassRemapper::new,
|
||||||
|
(cv, mapper) -> renameParameters ? new ParameterClassRemapper(cv, mapper, data) : cv
|
||||||
|
);
|
||||||
|
remap(data, inputJar, outputJar, skipMetaInf, steps, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remap(MappingData data, Path inputJar, Path outputJar, boolean skipMetaInf, List<RemappingStep> steps, Path... context) throws IOException, InterruptedException {
|
||||||
|
Files.deleteIfExists(outputJar);
|
||||||
|
|
||||||
|
if (SYSOUT) {
|
||||||
|
System.out.println("Remapping...");
|
||||||
|
}
|
||||||
|
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
try (ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||||
|
FileSystem inFs = FileSystems.newFileSystem(inputJar);
|
||||||
|
FileSystem outFs = FileSystems.newFileSystem(outputJar, Map.of("create", "true"))) {
|
||||||
|
List<Callable<Void>> tasks = new ArrayList<>();
|
||||||
|
Map<Path, FileSystem> contexts = new HashMap<>(context.length + 1);
|
||||||
|
contexts.put(inputJar, inFs);
|
||||||
|
for (Path p : context) {
|
||||||
|
if (!contexts.containsKey(p)) {
|
||||||
|
contexts.put(p, FileSystems.newFileSystem(p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Remapper mapper = createMapper(data, contexts.values());
|
||||||
|
|
||||||
|
Files.walkFileTree(inFs.getPath("/"), new SimpleFileVisitor<>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes) {
|
||||||
|
if (skipMetaInf) {
|
||||||
|
if (path.startsWith("/META-INF")) {
|
||||||
|
var name = path.getFileName().toString();
|
||||||
|
if (name.endsWith(".MF") || name.endsWith(".SF") || name.endsWith(".RSA")) {
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.add(() -> {
|
||||||
|
try {
|
||||||
|
if (path.getFileName().toString().endsWith(".class")) {
|
||||||
|
String className = path.toString().substring(1, path.toString().lastIndexOf("."));
|
||||||
|
Path result = outFs.getPath(mapper.map(className) + ".class");
|
||||||
|
Path newClass = result.toAbsolutePath();
|
||||||
|
byte[] bytes = Files.readAllBytes(path);
|
||||||
|
ClassReader reader = new ClassReader(bytes);
|
||||||
|
ClassWriter writer = new ClassWriter(0);
|
||||||
|
ClassVisitor visitor = writer;
|
||||||
|
for (RemappingStep pass : steps) {
|
||||||
|
visitor = pass.run(visitor, mapper);
|
||||||
|
}
|
||||||
|
reader.accept(visitor, 0);
|
||||||
|
|
||||||
|
byte[] output = writer.toByteArray();
|
||||||
|
Files.createDirectories(result.getParent());
|
||||||
|
Files.write(newClass, output);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Path result = outFs.getPath(path.toString()).toAbsolutePath();
|
||||||
|
Files.createDirectories(result.getParent());
|
||||||
|
Files.copy(path, result);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new Error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exec.invokeAll(tasks);
|
||||||
|
|
||||||
|
for (FileSystem fs : contexts.values()) {
|
||||||
|
fs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SYSOUT) {
|
||||||
|
System.out.printf("Finished remapping (%.2fs)%n", (System.currentTimeMillis() - startTime) / 1000F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Remapper createMapper(MappingData data, Collection<FileSystem> contexts) {
|
||||||
|
Map<String, List<String>> lazyParents = new ConcurrentHashMap<>();
|
||||||
|
MappingData reverseData = data.reverse();
|
||||||
|
return new Mapper(data, className -> lazyParents.computeIfAbsent(className, ignored ->
|
||||||
|
computeInheritances(className, reverseData, contexts)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MappingBundle parse(Path mappings) throws IOException {
|
||||||
|
String data = Files.readString(mappings);
|
||||||
|
String header = data.split("\n", 2)[0];
|
||||||
|
if (header.matches("tiny\t+v2\t+.*")) {
|
||||||
|
return TinyV2Parser.parse(data);
|
||||||
|
} else if (TinyV1Parser.HEADER.matcher(header).matches()) {
|
||||||
|
return TinyV1Parser.parse(data);
|
||||||
|
} else {
|
||||||
|
return new MappingBundle(ProguardParser.read(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException, InterruptedException {
|
||||||
|
// temporary
|
||||||
|
if (args.length < 3 || args.length > 5) {
|
||||||
|
System.err.println("Usage: <minecraft-version> <minecraft.jar> <out.jar> [--remove-meta-inf] [--remap-parameters]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String minecraftVersion = args[0];
|
||||||
|
String minecraftJar = args[1];
|
||||||
|
String outJar = args[2];
|
||||||
|
boolean removeMetaInf = args.length > 3 && hasArg(args, "--remove-meta-inf");
|
||||||
|
boolean remapParams = args.length > 3 && hasArg(args, "--remap-parameters");
|
||||||
|
|
||||||
|
run(minecraftVersion, Paths.get(minecraftJar), Paths.get(outJar), removeMetaInf, remapParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasArg(String[] arguments, String arg) {
|
||||||
|
for (String s : arguments) {
|
||||||
|
if (arg.equals(s)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> computeInheritances(String className, MappingData reverse, Collection<FileSystem> jars) {
|
||||||
|
try {
|
||||||
|
if (reverse.classes().containsKey(className)) {
|
||||||
|
className = reverse.classes().get(className);
|
||||||
|
}
|
||||||
|
List<String> superTypes = new ArrayList<>();
|
||||||
|
for (FileSystem fs : jars) {
|
||||||
|
Path path = fs.getPath("/" + className + ".class");
|
||||||
|
if (!Files.isRegularFile(path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = Files.readAllBytes(path);
|
||||||
|
ClassReader reader = new ClassReader(bytes);
|
||||||
|
reader.accept(new ClassVisitor(Constants.ASM_VERSION) {
|
||||||
|
@Override
|
||||||
|
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||||
|
if (interfaces != null) {
|
||||||
|
superTypes.addAll(Arrays.asList(interfaces));
|
||||||
|
}
|
||||||
|
if (!"java/lang/Object".equals(superName)) {
|
||||||
|
superTypes.add(superName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||||
|
}
|
||||||
|
return superTypes;
|
||||||
|
} catch (IOException error) {
|
||||||
|
throw new Error(error); // not our problem :^) for now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
src/main/java/dev/frogmc/thyroxine/api/Mapper.java
Normal file
63
src/main/java/dev/frogmc/thyroxine/api/Mapper.java
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package dev.frogmc.thyroxine.api;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.Member;
|
||||||
|
import org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class Mapper extends Remapper {
|
||||||
|
|
||||||
|
private final MappingData data;
|
||||||
|
private final Function<String, List<String>> parentProvider;
|
||||||
|
|
||||||
|
public Mapper(MappingData data, Function<String, List<String>> parentProvider) {
|
||||||
|
this.data = data;
|
||||||
|
this.parentProvider = parentProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String map(String internalName) {
|
||||||
|
return data.classes().getOrDefault(internalName, internalName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String mapMethodName(String owner, String name, String descriptor) {
|
||||||
|
String result = map(0, data.methods(), owner, name, descriptor);
|
||||||
|
if (result != null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String mapFieldName(String owner, String name, String descriptor) {
|
||||||
|
String result = map(0, data.fields(), owner, name, descriptor);
|
||||||
|
if (result != null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String map(int depth, Map<Member, String> map, String owner, String name, String descriptor) {
|
||||||
|
if (owner.startsWith("["))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String result = map.get(new Member(owner, name, descriptor));
|
||||||
|
if (result != null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (depth >= 64)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (String parent : parentProvider.apply(owner)) {
|
||||||
|
result = map(depth + 1, map, parent, name, descriptor);
|
||||||
|
if (result != null)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package dev.frogmc.thyroxine.api;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.Constants;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.Member;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
public class ParameterClassRemapper extends ClassVisitor {
|
||||||
|
private final MappingData data;
|
||||||
|
private final Remapper remapper;
|
||||||
|
|
||||||
|
private String className;
|
||||||
|
public ParameterClassRemapper(ClassVisitor classVisitor, Remapper remapper, MappingData data) {
|
||||||
|
super(Constants.ASM_VERSION, classVisitor);
|
||||||
|
this.remapper = remapper;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||||
|
this.className = name;
|
||||||
|
super.visit(version, access, name, signature, superName, interfaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||||
|
String remappedDescriptor = remapper.mapMethodDesc(descriptor);
|
||||||
|
MethodVisitor methodVisitor = cv.visitMethod(access, remapper.mapMethodName(className, name, descriptor), remappedDescriptor,
|
||||||
|
remapper.mapSignature(signature, false), exceptions == null ? null : remapper.mapTypes(exceptions));
|
||||||
|
|
||||||
|
Member member = new Member(remapper.map(className), remapper.mapMethodName(className, name, descriptor), remappedDescriptor);
|
||||||
|
Map<Integer, String> parameters = data != null ? data.parameters().getOrDefault(member, Collections.emptyMap()) : Collections.emptyMap();
|
||||||
|
return new ParameterMethodRemapper(methodVisitor, remapper, parameters);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package dev.frogmc.thyroxine.api;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.Constants;
|
||||||
|
import org.objectweb.asm.Label;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.commons.Remapper;
|
||||||
|
|
||||||
|
public class ParameterMethodRemapper extends MethodVisitor {
|
||||||
|
private final Remapper remapper;
|
||||||
|
private final Map<Integer, String> parameters;
|
||||||
|
private final Set<String> usedLocalNames = new HashSet<>();
|
||||||
|
|
||||||
|
public ParameterMethodRemapper(MethodVisitor methodVisitor, Remapper remapper, Map<Integer, String> parameters) {
|
||||||
|
super(Constants.ASM_VERSION, methodVisitor);
|
||||||
|
this.remapper = remapper;
|
||||||
|
this.parameters = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
||||||
|
if (!name.equals("this")) {
|
||||||
|
if (parameters.containsKey(index)) {
|
||||||
|
name = parameters.get(index);
|
||||||
|
} else {
|
||||||
|
name = getNewName(descriptor, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.visitLocalVariable(name, descriptor, signature, start, end, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNewName(String descriptor, String signature) {
|
||||||
|
descriptor = remapper.mapDesc(descriptor);
|
||||||
|
signature = remapper.mapSignature(signature, false);
|
||||||
|
|
||||||
|
String newName = convertDescriptor(descriptor);
|
||||||
|
|
||||||
|
int count = 1;
|
||||||
|
String nameCopy = newName;
|
||||||
|
while (usedLocalNames.contains(newName)) {
|
||||||
|
if (signature != null) {
|
||||||
|
String type = cleanType(signature);
|
||||||
|
if (!newName.toLowerCase(Locale.ROOT).contains(type.toLowerCase(Locale.ROOT))) {
|
||||||
|
newName = Character.toLowerCase(type.charAt(0)) + type.substring(1) +
|
||||||
|
Character.toUpperCase(newName.charAt(0)) + newName.substring(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newName = nameCopy + ++count;
|
||||||
|
}
|
||||||
|
usedLocalNames.add(newName);
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String convertDescriptor(String descriptor) {
|
||||||
|
return switch (descriptor.charAt(0)) {
|
||||||
|
case 'I' -> "i";
|
||||||
|
case 'B' -> "b";
|
||||||
|
case 'C' -> "c";
|
||||||
|
case 'S' -> "s";
|
||||||
|
case 'D' -> "d";
|
||||||
|
case 'F' -> "f";
|
||||||
|
case 'J' -> "l";
|
||||||
|
case 'Z' -> "bl";
|
||||||
|
case '[' -> {
|
||||||
|
Type type = Type.getType(descriptor).getElementType();
|
||||||
|
if (type.getSort() == Type.OBJECT) {
|
||||||
|
yield convertDescriptor(type.getInternalName()) + "s";
|
||||||
|
}
|
||||||
|
yield type.getClassName() + "s";
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
String name = cleanType(descriptor);
|
||||||
|
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
|
||||||
|
yield name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cleanType(String type) {
|
||||||
|
var cleaned = type;
|
||||||
|
if (cleaned.indexOf('<') != -1) {
|
||||||
|
cleaned = cleaned.substring(0, cleaned.indexOf('<'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleaned.indexOf('/') != -1) {
|
||||||
|
cleaned = cleaned.substring(cleaned.lastIndexOf('/') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleaned = cleaned.replaceAll("\\$\\d+;", "");
|
||||||
|
|
||||||
|
if (cleaned.indexOf('$') != -1) {
|
||||||
|
cleaned = cleaned.substring(cleaned.lastIndexOf('$') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleaned = cleaned.replace("[", "").replace("]", "").replaceAll("(.*)\\d", "$1").replace(";", "");
|
||||||
|
|
||||||
|
return cleaned;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
package dev.frogmc.thyroxine.api.data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public record DocumentationData(String namespace, List<Package> packages, List<Class> classes) {
|
||||||
|
|
||||||
|
public DocumentationData(String namespace) {
|
||||||
|
this(namespace, new ArrayList<>(), new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DocumentationData insert(DocumentationData other) {
|
||||||
|
other.classes().forEach(c -> {
|
||||||
|
Optional<Class> opt = getClass(c.name);
|
||||||
|
if (opt.isPresent()) {
|
||||||
|
opt.get().merge(c);
|
||||||
|
} else {
|
||||||
|
classes.add(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
other.packages().forEach(p -> {
|
||||||
|
Optional<Package> opt = getPackage(p.name);
|
||||||
|
if (opt.isPresent()) {
|
||||||
|
opt.get().merge(p);
|
||||||
|
} else {
|
||||||
|
packages.add(p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Class> getClass(String name) {
|
||||||
|
if (classes == null){
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return classes.stream().filter(c -> c.name().equals(name)).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Package> getPackage(String name) {
|
||||||
|
if (packages == null){
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return packages().stream().filter(p -> p.name().equals(name)).findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Package(String name, List<String> javadoc) {
|
||||||
|
|
||||||
|
public void merge(Package other) {
|
||||||
|
if (other.javadoc() != null && !String.join("\n", other.javadoc()).equals(String.join("\n", javadoc()))) {
|
||||||
|
javadoc().addAll(other.javadoc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Class(String name, List<String> javadoc, List<Field> fields, List<Method> methods) {
|
||||||
|
public Optional<Field> getField(String name, String descriptor) {
|
||||||
|
if (fields == null){
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return fields.stream()
|
||||||
|
.filter(f -> f.name.equals(name) &&
|
||||||
|
f.descriptor.equals(descriptor))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Method> getMethod(String name, String descriptor) {
|
||||||
|
if (methods == null){
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return methods.stream()
|
||||||
|
.filter(method -> method.name.equals(name) &&
|
||||||
|
method.descriptor.equals(descriptor))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void merge(Class other) {
|
||||||
|
if (other.javadoc() != null && !String.join("\n", other.javadoc()).equals(String.join("\n", javadoc()))) {
|
||||||
|
if (!other.javadoc().isEmpty()) {
|
||||||
|
javadoc().addAll(other.javadoc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
other.fields.forEach(f -> {
|
||||||
|
Optional<Field> opt = getField(f.name, f.descriptor);
|
||||||
|
if (opt.isPresent()) {
|
||||||
|
opt.get().merge(f);
|
||||||
|
} else {
|
||||||
|
fields.add(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
other.methods.forEach(m -> {
|
||||||
|
Optional<Method> opt = getMethod(m.name, m.descriptor);
|
||||||
|
if (opt.isPresent()) {
|
||||||
|
opt.get().merge(m);
|
||||||
|
} else {
|
||||||
|
methods.add(m);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Field(String name, String descriptor, List<String> javadoc) {
|
||||||
|
public void merge(Field other) {
|
||||||
|
if (other.javadoc() != null && !String.join("\n", other.javadoc()).equals(String.join("\n", javadoc()))) {
|
||||||
|
javadoc().addAll(other.javadoc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Method(String name, String descriptor, List<String> javadoc) {
|
||||||
|
public void merge(Method other) {
|
||||||
|
if (other.javadoc() != null && !String.join("\n", other.javadoc()).equals(String.join("\n", javadoc()))) {
|
||||||
|
javadoc().addAll(other.javadoc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
358
src/main/java/dev/frogmc/thyroxine/api/data/MappingBundle.java
Normal file
358
src/main/java/dev/frogmc/thyroxine/api/data/MappingBundle.java
Normal file
|
@ -0,0 +1,358 @@
|
||||||
|
package dev.frogmc.thyroxine.api.data;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.api.Mapper;
|
||||||
|
|
||||||
|
public record MappingBundle(List<MappingData> data, List<DocumentationData> documentation) {
|
||||||
|
|
||||||
|
public MappingBundle() {
|
||||||
|
this((MappingData) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle(MappingData data) {
|
||||||
|
this(data, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle(DocumentationData data) {
|
||||||
|
this(null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle(MappingData data, DocumentationData documentation) {
|
||||||
|
this(list(data), list(documentation));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> List<T> list(T element) { // create a mutable list populated with a single element
|
||||||
|
List<T> list = new ArrayList<>();
|
||||||
|
if (element != null) {
|
||||||
|
list.add(element);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MappingBundle merge(MappingBundle... bundles) {
|
||||||
|
List<MappingData> mergedData = new ArrayList<>();
|
||||||
|
List<DocumentationData> mergedDocs = new ArrayList<>();
|
||||||
|
|
||||||
|
for (MappingBundle bundle : bundles) {
|
||||||
|
mergedData.addAll(bundle.data());
|
||||||
|
mergedDocs.addAll(bundle.documentation());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MappingBundle(mergedData, mergedDocs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle insert(MappingData data) {
|
||||||
|
this.data.add(data);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle insert(DocumentationData data) {
|
||||||
|
for (DocumentationData d : documentation) {
|
||||||
|
if (d.namespace().equals(data.namespace())) {
|
||||||
|
d.insert(data);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
documentation.add(data);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> srcNamespaces() {
|
||||||
|
return data.stream().map(MappingData::srcNamespace).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> dstNamespaces() {
|
||||||
|
return data.stream().map(MappingData::dstNamespace).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingData forNamespaces(String src, String dst) {
|
||||||
|
return flattenInternal(src, dst, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingData forNamespaces(String src, String dst, boolean allowIncomplete) {
|
||||||
|
return flattenInternal(src, dst, allowIncomplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle flatten(String src, String dst) {
|
||||||
|
return flatten(src, dst, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle flatten(String src, String dst, boolean allowIncomplete) {
|
||||||
|
MappingData data = flattenInternal(src, dst, allowIncomplete);
|
||||||
|
return new MappingBundle(data, get(data.dstNamespace()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public DocumentationData docsForNamespace(String ns) {
|
||||||
|
DocumentationData result = new DocumentationData(ns);
|
||||||
|
for (DocumentationData d : documentation) {
|
||||||
|
if (d.namespace().equals(ns)) {
|
||||||
|
result.insert(d);
|
||||||
|
} else {
|
||||||
|
Mapper mappings = new Mapper(forNamespaces(d.namespace(), ns, false), a -> Collections.emptyList());
|
||||||
|
DocumentationData remapped = new DocumentationData(ns);
|
||||||
|
// TODO is there something we can do about the packages...? (don't think so)
|
||||||
|
d.classes().forEach(c -> {
|
||||||
|
String name = mappings.map(c.name());
|
||||||
|
List<DocumentationData.Method> methods = c.methods().stream().map(m ->
|
||||||
|
new DocumentationData.Method(mappings.mapMethodName(c.name(), m.name(), m.descriptor()),
|
||||||
|
mappings.mapMethodDesc(m.descriptor()), m.javadoc())).toList();
|
||||||
|
List<DocumentationData.Field> fields = c.fields().stream().map(f ->
|
||||||
|
new DocumentationData.Field(mappings.mapFieldName(c.name(), f.name(), f.descriptor()),
|
||||||
|
mappings.mapDesc(f.descriptor()), f.javadoc())).collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
remapped.classes().add(new DocumentationData.Class(name, c.javadoc(), fields, methods));
|
||||||
|
});
|
||||||
|
result.insert(remapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle flatten() {
|
||||||
|
return flatten(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle flatten(boolean allowIncomplete) {
|
||||||
|
MappingData data = flattenData(allowIncomplete);
|
||||||
|
return new MappingBundle(data, get(data.dstNamespace()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingData flattenData(boolean allowIncomplete) {
|
||||||
|
Set<String> overlayNs = new HashSet<>();
|
||||||
|
for (MappingData d : data()) {
|
||||||
|
if (d.srcNamespace().equals(d.dstNamespace())) {
|
||||||
|
overlayNs.add(d.srcNamespace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> srcNs = srcNamespaces();
|
||||||
|
List<String> dstNs = dstNamespaces();
|
||||||
|
List<String> srcNsCandidates = srcNs.stream().distinct().filter(s -> !dstNs.contains(s) && !overlayNs.contains(s)).toList();
|
||||||
|
List<String> dstNsCandidates = dstNs.stream().distinct().filter(s -> !srcNs.contains(s) || overlayNs.contains(s)).toList();
|
||||||
|
if (srcNsCandidates.size() > 1) {
|
||||||
|
throw new IllegalStateException("Not a linear graph, cannot flatten! Got possible roots: " + String.join(", ", srcNsCandidates));
|
||||||
|
} else if (srcNsCandidates.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Not a linear graph, cannot flatten! Got no possible roots! SrcNamespaces: " + String.join(", ", srcNs) + " Overlays: " + String.join(", ", overlayNs));
|
||||||
|
}
|
||||||
|
if (dstNsCandidates.size() > 1) {
|
||||||
|
throw new IllegalStateException("Not a linear graph, cannot flatten! Got possible ends: " + String.join(", ", dstNsCandidates));
|
||||||
|
} else if (dstNsCandidates.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Not a linear graph, cannot flatten! Got no possible roots! DstNamespaces: " + String.join(", ", dstNs));
|
||||||
|
}
|
||||||
|
String srcRoot = srcNsCandidates.getFirst();
|
||||||
|
String dstRoot = dstNsCandidates.getFirst();
|
||||||
|
return flattenInternal(srcRoot, dstRoot, allowIncomplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MappingData flattenInternal(String srcRoot, String dstRoot, boolean allowIncomplete) {
|
||||||
|
Deque<MappingData> path = computePath(srcRoot, dstRoot).reversed();
|
||||||
|
|
||||||
|
MappingData result = new MappingData(srcRoot, dstRoot);
|
||||||
|
|
||||||
|
MappingData first = path.pop();
|
||||||
|
|
||||||
|
Map<String, Map<String, Mapper>> mappers = new HashMap<>();
|
||||||
|
Mapper start = mappers.computeIfAbsent(first.srcNamespace(),
|
||||||
|
a -> new HashMap<>()).computeIfAbsent(first.dstNamespace(),
|
||||||
|
a -> new Mapper(first, b -> Collections.emptyList()));
|
||||||
|
|
||||||
|
first.classes().forEach((s, s2) -> {
|
||||||
|
String res = s2;
|
||||||
|
for (MappingData d : path) {
|
||||||
|
String w = res;
|
||||||
|
res = d.classes().get(res);
|
||||||
|
if (res == null) {
|
||||||
|
int dollar = w.indexOf("$");
|
||||||
|
if (dollar != -1) {
|
||||||
|
String prefix = d.classes().get(w.substring(0, dollar));
|
||||||
|
if (prefix != null) {
|
||||||
|
res = prefix + w.substring(dollar);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!allowIncomplete) {
|
||||||
|
/*
|
||||||
|
* The mapping set is incomplete at this point.
|
||||||
|
* We're using the last available value to prevent incompleteness
|
||||||
|
* in the resulting set.
|
||||||
|
*/
|
||||||
|
res = w;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (res != null) {
|
||||||
|
result.classes().put(s, res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
first.methods().forEach((member, s) -> {
|
||||||
|
String res = s;
|
||||||
|
String owner = start.map(member.owner());
|
||||||
|
String desc = start.mapMethodDesc(member.descriptor());
|
||||||
|
for (MappingData d : path) {
|
||||||
|
Mapper mapper = mappers.computeIfAbsent(d.srcNamespace(),
|
||||||
|
a -> new HashMap<>()).computeIfAbsent(d.dstNamespace(),
|
||||||
|
a -> new Mapper(d, b -> Collections.emptyList()));
|
||||||
|
String w = res;
|
||||||
|
res = d.methods().get(new Member(owner, res, desc));
|
||||||
|
owner = mapper.map(owner);
|
||||||
|
desc = mapper.mapMethodDesc(desc);
|
||||||
|
if (res == null) {
|
||||||
|
if (!allowIncomplete) {
|
||||||
|
/*
|
||||||
|
* The mapping set is incomplete at this point.
|
||||||
|
* We're using the last available value to prevent incompleteness
|
||||||
|
* in the resulting set.
|
||||||
|
*/
|
||||||
|
res = w;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (res != null) {
|
||||||
|
result.methods().put(member, res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
first.fields().forEach((member, s) -> {
|
||||||
|
String res = s;
|
||||||
|
String owner = start.map(member.owner());
|
||||||
|
String desc = start.mapDesc(member.descriptor());
|
||||||
|
for (MappingData d : path) {
|
||||||
|
Mapper mapper = mappers.computeIfAbsent(d.srcNamespace(),
|
||||||
|
a -> new HashMap<>()).computeIfAbsent(d.dstNamespace(),
|
||||||
|
a -> new Mapper(d, b -> Collections.emptyList()));
|
||||||
|
String w = res;
|
||||||
|
res = d.fields().get(new Member(owner, res, desc));
|
||||||
|
owner = mapper.map(owner);
|
||||||
|
desc = mapper.mapDesc(desc);
|
||||||
|
if (res == null) {
|
||||||
|
if (!allowIncomplete) {
|
||||||
|
/*
|
||||||
|
* The mapping set is incomplete at this point.
|
||||||
|
* We're using the last available value to prevent incompleteness
|
||||||
|
* in the resulting set.
|
||||||
|
*/
|
||||||
|
res = w;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (res != null) {
|
||||||
|
result.fields().put(member, res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
List<MappingData> all = new ArrayList<>(path);
|
||||||
|
all.addFirst(first);
|
||||||
|
Map<Member, Map<Integer, String>> queue = new HashMap<>();
|
||||||
|
for (MappingData d : all) {
|
||||||
|
queue.putAll(d.parameters());
|
||||||
|
Mapper mapper = mappers.computeIfAbsent(d.srcNamespace(),
|
||||||
|
a -> new HashMap<>()).computeIfAbsent(d.dstNamespace(),
|
||||||
|
a -> new Mapper(d, b -> Collections.emptyList()));
|
||||||
|
Map<Member, Map<Integer, String>> map = new HashMap<>(queue);
|
||||||
|
queue.clear();
|
||||||
|
map.forEach((member, param) -> queue.put(new Member(mapper.map(member.owner()),
|
||||||
|
mapper.mapMethodName(member.owner(), member.name(), member.descriptor()),
|
||||||
|
mapper.mapMethodDesc(member.descriptor())),
|
||||||
|
param));
|
||||||
|
}
|
||||||
|
result.parameters().putAll(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Deque<MappingData> computePath(String srcRoot, String dstRoot) {
|
||||||
|
|
||||||
|
Deque<MappingData> path = new ArrayDeque<>();
|
||||||
|
List<MappingData> available = new ArrayList<>(data);
|
||||||
|
for (MappingData d : data) {
|
||||||
|
if (d.srcNamespace().equals(srcRoot)) {
|
||||||
|
path.push(d);
|
||||||
|
available.remove(d);
|
||||||
|
break;
|
||||||
|
} else if (d.dstNamespace().equals(srcRoot)) {
|
||||||
|
path.push(d.reverse());
|
||||||
|
available.remove(d);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!path.getFirst().dstNamespace().equals(dstRoot)) {
|
||||||
|
MappingData last = path.getFirst();
|
||||||
|
|
||||||
|
String needed = last.dstNamespace();
|
||||||
|
for (MappingData d : new ArrayList<>(available)) {
|
||||||
|
if (d.srcNamespace().equals(needed)) {
|
||||||
|
available.remove(d);
|
||||||
|
path.push(d);
|
||||||
|
} else if (d.dstNamespace().equals(needed)) {
|
||||||
|
available.remove(d);
|
||||||
|
path.push(d.reverse());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MappingData d : available) {
|
||||||
|
if (d.srcNamespace().equals(d.dstNamespace()) && d.srcNamespace().equals(dstRoot)) {
|
||||||
|
path.push(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle insert(MappingData data, DocumentationData documentation) {
|
||||||
|
return insert(data).insert(documentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle insert(MappingBundle other) {
|
||||||
|
return insert(other.data, other.documentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle insert(List<MappingData> data, List<DocumentationData> documentation) {
|
||||||
|
data.forEach(this::insert);
|
||||||
|
documentation.forEach(this::insert);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DocumentationData get(String namespace) {
|
||||||
|
DocumentationData result = null;
|
||||||
|
for (DocumentationData d : documentation) {
|
||||||
|
if (d.namespace().equals(namespace)) {
|
||||||
|
if (result == null) {
|
||||||
|
result = new DocumentationData(namespace);
|
||||||
|
}
|
||||||
|
result.insert(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingData getWithSrcNamespace(String namespace) {
|
||||||
|
return data.stream().filter(d -> d.srcNamespace().equals(namespace)).findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingData getWithDstNamespace(String namespace) {
|
||||||
|
return data.stream().filter(d -> d.dstNamespace().equals(namespace)).findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingBundle reverse() {
|
||||||
|
MappingBundle reversed = new MappingBundle();
|
||||||
|
for (MappingData d : data) {
|
||||||
|
reversed.data().addFirst(d.reverse());
|
||||||
|
}
|
||||||
|
|
||||||
|
reversed.documentation.addAll(documentation);
|
||||||
|
return reversed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingData flattenData() {
|
||||||
|
return flattenData(false);
|
||||||
|
}
|
||||||
|
}
|
73
src/main/java/dev/frogmc/thyroxine/api/data/MappingData.java
Normal file
73
src/main/java/dev/frogmc/thyroxine/api/data/MappingData.java
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package dev.frogmc.thyroxine.api.data;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
|
public record MappingData(String srcNamespace, String dstNamespace, Map<String, String> classes, Map<Member, String> methods,
|
||||||
|
Map<Member, String> fields, Map<Member, Map<Integer, String>> parameters) {
|
||||||
|
|
||||||
|
public MappingData(String srcNs, String dstNs) {
|
||||||
|
this(srcNs, dstNs, new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingData reverse() {
|
||||||
|
Map<String, String> classesReversed = new HashMap<>();
|
||||||
|
Map<Member, String> methodsReversed = new HashMap<>();
|
||||||
|
Map<Member, String> fieldsReversed = new HashMap<>();
|
||||||
|
Map<Member, Map<Integer, String>> parametersReversed = new HashMap<>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> clazz : classes.entrySet())
|
||||||
|
classesReversed.put(clazz.getValue(), clazz.getKey());
|
||||||
|
|
||||||
|
for (Map.Entry<Member, String> method : methods.entrySet()) {
|
||||||
|
methodsReversed.put(
|
||||||
|
new Member(
|
||||||
|
classes.getOrDefault(method.getKey().owner(), method.getKey().owner()),
|
||||||
|
method.getValue(),
|
||||||
|
remapType(Type.getType(method.getKey().descriptor())).getDescriptor()
|
||||||
|
),
|
||||||
|
method.getKey().name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<Member, String> field : fields.entrySet()) {
|
||||||
|
fieldsReversed.put(
|
||||||
|
new Member(
|
||||||
|
classes.getOrDefault(field.getKey().owner(), field.getKey().owner()),
|
||||||
|
field.getValue(),
|
||||||
|
remapType(Type.getType(field.getKey().descriptor())).getDescriptor()
|
||||||
|
),
|
||||||
|
field.getKey().name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<Member, Map<Integer, String>> param : parameters.entrySet()) {
|
||||||
|
parametersReversed.put(new Member(
|
||||||
|
classes.getOrDefault(param.getKey().owner(), param.getKey().owner()),
|
||||||
|
fields.getOrDefault(param.getKey(), param.getKey().name()),
|
||||||
|
remapType(Type.getType(param.getKey().descriptor())).getDescriptor()
|
||||||
|
), param.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MappingData(dstNamespace, srcNamespace, classesReversed, methodsReversed, fieldsReversed, parametersReversed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type remapType(Type type) {
|
||||||
|
return switch (type.getSort()) {
|
||||||
|
case Type.OBJECT ->
|
||||||
|
Type.getObjectType(classes.getOrDefault(type.getInternalName(), type.getInternalName()));
|
||||||
|
case Type.ARRAY -> Type.getObjectType(
|
||||||
|
"[".repeat(type.getDimensions()) + remapType(type.getElementType()).getDescriptor()
|
||||||
|
);
|
||||||
|
case Type.METHOD -> Type.getMethodType(
|
||||||
|
remapType(type.getReturnType()),
|
||||||
|
Arrays.stream(type.getArgumentTypes()).map(this::remapType).toArray(Type[]::new)
|
||||||
|
);
|
||||||
|
default -> type;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
4
src/main/java/dev/frogmc/thyroxine/api/data/Member.java
Normal file
4
src/main/java/dev/frogmc/thyroxine/api/data/Member.java
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package dev.frogmc.thyroxine.api.data;
|
||||||
|
|
||||||
|
public record Member(String owner, String name, String descriptor) {
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package dev.frogmc.thyroxine.parser;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.Member;
|
||||||
|
|
||||||
|
public class ProguardParser {
|
||||||
|
|
||||||
|
public static MappingData read(String mappings) {
|
||||||
|
String[] lines = mappings.split("\n");
|
||||||
|
|
||||||
|
MappingData data = new MappingData("named", "official");
|
||||||
|
String currentClass = null;
|
||||||
|
for (String line : lines) {
|
||||||
|
if (line.contains("#")) {
|
||||||
|
line = line.substring(0, line.indexOf('#'));
|
||||||
|
}
|
||||||
|
|
||||||
|
line = line.trim();
|
||||||
|
|
||||||
|
if (line.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int arrowIndex = line.indexOf("->");
|
||||||
|
if (arrowIndex == -1) {
|
||||||
|
throw new IllegalStateException("Missing arrow in mapping");
|
||||||
|
}
|
||||||
|
|
||||||
|
String obf = line.substring(0, arrowIndex).trim();
|
||||||
|
String deobf = line.substring(arrowIndex + 2).trim();
|
||||||
|
|
||||||
|
if (line.endsWith(":")) {
|
||||||
|
deobf = deobf.substring(0, deobf.length() - 1);
|
||||||
|
currentClass = obf.replace('.', '/');
|
||||||
|
data.classes().put(currentClass, deobf.replace('.', '/'));
|
||||||
|
} else {
|
||||||
|
if (currentClass == null) {
|
||||||
|
throw new IllegalStateException("Member mapping specified before a class");
|
||||||
|
}
|
||||||
|
|
||||||
|
int spaceIndex = obf.indexOf(' ');
|
||||||
|
if (spaceIndex == -1) {
|
||||||
|
throw new IllegalStateException("Missing member signature");
|
||||||
|
}
|
||||||
|
|
||||||
|
String type = obf.substring(0, spaceIndex);
|
||||||
|
String signature = obf.substring(spaceIndex + 1);
|
||||||
|
|
||||||
|
int colonIndex = type.lastIndexOf(':');
|
||||||
|
if (colonIndex != -1)
|
||||||
|
type = type.substring(colonIndex + 1);
|
||||||
|
|
||||||
|
if (signature.contains("(") && signature.endsWith(")")) {
|
||||||
|
// method
|
||||||
|
String name = signature.substring(0, signature.indexOf('('));
|
||||||
|
String args = signature.substring(signature.indexOf('(') + 1, signature.length() - 1);
|
||||||
|
StringBuilder descriptor = new StringBuilder();
|
||||||
|
descriptor.append('(');
|
||||||
|
if (!args.isEmpty()) {
|
||||||
|
for (String arg : args.split(",")) {
|
||||||
|
descriptor.append(convertType(arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
descriptor.append(')');
|
||||||
|
descriptor.append(convertType(type));
|
||||||
|
|
||||||
|
data.methods().put(new Member(currentClass, name, descriptor.toString()), deobf);
|
||||||
|
} else
|
||||||
|
data.fields().put(new Member(currentClass, signature, convertType(type)), deobf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String convertType(String type) {
|
||||||
|
int dimensions = 0;
|
||||||
|
while (type.endsWith("[]")) {
|
||||||
|
type = type.substring(0, type.length() - 2);
|
||||||
|
++dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = switch (type) {
|
||||||
|
case "void" -> "V";
|
||||||
|
case "boolean" -> "Z";
|
||||||
|
case "byte" -> "B";
|
||||||
|
case "short" -> "S";
|
||||||
|
case "int" -> "I";
|
||||||
|
case "long" -> "J";
|
||||||
|
case "float" -> "F";
|
||||||
|
case "double" -> "D";
|
||||||
|
case "char" -> "C";
|
||||||
|
default -> 'L' + type.replace('.', '/') + ';';
|
||||||
|
};
|
||||||
|
|
||||||
|
return "[".repeat(dimensions) + result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package dev.frogmc.thyroxine.parser.tiny;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingBundle;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.Member;
|
||||||
|
|
||||||
|
public class TinyV1Parser {
|
||||||
|
|
||||||
|
public static final Pattern HEADER = Pattern.compile("v(?<majorVer>[12]+)\\s\\w*\\s?(?<srcns>\\S+)\\s(?<dstns>\\S+)");
|
||||||
|
|
||||||
|
public static MappingBundle parse(String mappings) {
|
||||||
|
String[] lines = mappings.split("\n");
|
||||||
|
int length = lines.length;
|
||||||
|
|
||||||
|
Matcher header = HEADER.matcher(lines[0]);
|
||||||
|
if (!header.matches()) {
|
||||||
|
throw new IllegalStateException("Not a valid tiny file");
|
||||||
|
}
|
||||||
|
|
||||||
|
MappingData data = new MappingData(header.group("srcns"), header.group("dstns"));
|
||||||
|
//DocumentationData documentation = new DocumentationData();
|
||||||
|
for (int i = 1; i < length; i++) {
|
||||||
|
String[] line = lines[i].split("\\s");
|
||||||
|
|
||||||
|
String type = line[0];
|
||||||
|
|
||||||
|
if ("CLASS".equals(type)) {
|
||||||
|
data.classes().put(line[1], line[2]);
|
||||||
|
//documentation.classes().add(new DocumentationData.Class(line[1], new ArrayList<>(0), new ArrayList<>(), new ArrayList<>()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ("FIELD".equals(type)) {
|
||||||
|
String desc = line[2];
|
||||||
|
String obf = line[line.length - 2]; // aaa these files can contain more than two namespaces, we *should* be fine by just using the last two
|
||||||
|
String deobf = line[line.length - 1];
|
||||||
|
data.fields().put(new Member(line[1], obf, desc), deobf);
|
||||||
|
//documentation.getClass(line[1]).ifPresent(c -> c.fields().add(new DocumentationData.Field(obf, desc, new ArrayList<>())));
|
||||||
|
//continue;
|
||||||
|
} else if ("METHOD".equals(type)) {
|
||||||
|
String desc = line[2];
|
||||||
|
String obf = line[line.length - 2]; // aaa these files can contain more than two namespaces, we *should* be fine by just using the last two
|
||||||
|
String deobf = line[line.length - 1];
|
||||||
|
|
||||||
|
data.methods().put(new Member(line[1], obf, desc), deobf);
|
||||||
|
//documentation.getClass(line[1]).ifPresent(c -> c.methods().add(new DocumentationData.Method(obf, desc, new ArrayList<>(), new ArrayList<>())));
|
||||||
|
//continue;
|
||||||
|
}
|
||||||
|
// TODO Observe if there are any tinyv1 files with javadocs and/or parameter mappings
|
||||||
|
/* else if ("c".equals(type)) {
|
||||||
|
currentC.javadoc().addAll(Arrays.asList(line[2].split("\n")));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (currentF == null && currentM == null) {
|
||||||
|
throw new IllegalStateException("Field javadoc/Method parameter mapping/javadoc specified before a declaration");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (currentM != null) {
|
||||||
|
if ("p".equals(type)) {
|
||||||
|
currentM.parameters().add(new DocumentationData.Parameter(Integer.parseInt(line[3]), line[5]));
|
||||||
|
} else if ("c".equals(type)) {
|
||||||
|
currentM.javadoc().addAll(Arrays.asList(line[2].split("\n")));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ("c".equals(type)) {
|
||||||
|
currentF.javadoc().addAll(Arrays.asList(line[2].split("\n")));
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MappingBundle(data);
|
||||||
|
}
|
||||||
|
}
|
135
src/main/java/dev/frogmc/thyroxine/parser/tiny/TinyV2Parser.java
Normal file
135
src/main/java/dev/frogmc/thyroxine/parser/tiny/TinyV2Parser.java
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package dev.frogmc.thyroxine.parser.tiny;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.api.Mapper;
|
||||||
|
import dev.frogmc.thyroxine.api.data.DocumentationData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingBundle;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.Member;
|
||||||
|
|
||||||
|
public class TinyV2Parser {
|
||||||
|
|
||||||
|
private static final Pattern HEADER = Pattern.compile("tiny\t(?<majorVer>\\d+)\t(?<minorVer>\\d+)\t(?<namespaces>(?:\t?[^\t]+){2,})");
|
||||||
|
|
||||||
|
public static MappingBundle parse(String mappings) {
|
||||||
|
String[] lines = mappings.split("\n");
|
||||||
|
int length = lines.length;
|
||||||
|
|
||||||
|
Matcher header = HEADER.matcher(lines[0]);
|
||||||
|
if (!header.matches()) {
|
||||||
|
throw new IllegalStateException("Not a valid tiny file! (header: "+lines[0]+")");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> namespaces = Arrays.asList(header.group("namespaces").split("\t"));
|
||||||
|
int count = namespaces.size();
|
||||||
|
MappingBundle bundle = new MappingBundle();
|
||||||
|
List<Mapper> mappers = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int ns = 1;ns<count;ns++) {
|
||||||
|
String srcNs = namespaces.get(ns-1);
|
||||||
|
String dstNs = namespaces.get(ns);
|
||||||
|
|
||||||
|
MappingData data = new MappingData(srcNs, dstNs);
|
||||||
|
String currentClass = null;
|
||||||
|
DocumentationData.Class currentC = null;
|
||||||
|
DocumentationData.Method currentM = null;
|
||||||
|
DocumentationData.Field currentF = null;
|
||||||
|
DocumentationData documentation = new DocumentationData(dstNs);
|
||||||
|
for (int i = 1; i < length; i++) {
|
||||||
|
String[] line = lines[i].split("\t");
|
||||||
|
|
||||||
|
if ("c".equals(line[0])) {
|
||||||
|
if (1+ns >= line.length) {
|
||||||
|
while (lines[i + 1].startsWith("\t")) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
currentClass = line[ns];
|
||||||
|
data.classes().put(currentClass, line[1+ns]);
|
||||||
|
documentation.classes().add(currentC = new DocumentationData.Class(line[1+ns], new ArrayList<>(0), new ArrayList<>(), new ArrayList<>()));
|
||||||
|
currentM = null;
|
||||||
|
currentF = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentClass == null) {
|
||||||
|
throw new IllegalStateException("Member mapping specified before a class");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
String type = line[1];
|
||||||
|
if ("f".equals(type)) {
|
||||||
|
if (3+ns >= line.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String desc = line[2];
|
||||||
|
String obf = line[2+ns];
|
||||||
|
String deobf = line[3+ns];
|
||||||
|
|
||||||
|
if (ns > 1) {
|
||||||
|
for (Mapper mapper : mappers) {
|
||||||
|
desc = mapper.mapDesc(desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.fields().put(new Member(currentClass, obf, desc), deobf);
|
||||||
|
currentC.fields().add(currentF = new DocumentationData.Field(deobf, desc, new ArrayList<>()));
|
||||||
|
continue;
|
||||||
|
} else if ("m".equals(type)) {
|
||||||
|
if (3+ns >= line.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String desc = line[2];
|
||||||
|
String obf = line[2+ns];
|
||||||
|
String deobf = line[3+ns];
|
||||||
|
|
||||||
|
if (ns > 1) {
|
||||||
|
for (Mapper mapper : mappers) {
|
||||||
|
desc = mapper.mapMethodDesc(desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.methods().put(new Member(currentClass, obf, desc), deobf);
|
||||||
|
currentC.methods().add(currentM = new DocumentationData.Method(deobf, desc, new ArrayList<>()));
|
||||||
|
continue;
|
||||||
|
} else if ("c".equals(type)) {
|
||||||
|
currentC.javadoc().addAll(Arrays.asList(line[2].split("\n")));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentF == null && currentM == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
String type = line[2];
|
||||||
|
if (currentM != null) {
|
||||||
|
if ("p".equals(type)) {
|
||||||
|
if (4+ns >= line.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
data.parameters().computeIfAbsent(new Member(currentClass, currentM.name(), currentM.descriptor()), a -> new HashMap<>())
|
||||||
|
.put(Integer.parseInt(line[3]), line[4+ns]);
|
||||||
|
} else if ("c".equals(type)) {
|
||||||
|
currentM.javadoc().addAll(Arrays.asList(line[3].split("\n")));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ("c".equals(type)) {
|
||||||
|
currentF.javadoc().addAll(Arrays.asList(line[3].split("\n")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mappers.add(new Mapper(data, s -> Collections.emptyList()));
|
||||||
|
bundle.insert(data, documentation);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package dev.frogmc.thyroxine.provider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||||
|
import dev.frogmc.thyroxine.Constants;
|
||||||
|
import dev.frogmc.thyroxine.HttpHelper;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingBundle;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
import dev.frogmc.thyroxine.parser.ProguardParser;
|
||||||
|
|
||||||
|
public class MojmapProvider {
|
||||||
|
|
||||||
|
public static MappingBundle get(String gameVersion, Path clientCacheFile, Path serverCacheFile) throws IOException {
|
||||||
|
MappingData client = ProguardParser.read(getMappings(gameVersion, "client", clientCacheFile));
|
||||||
|
MappingData server = ProguardParser.read(getMappings(gameVersion, "server", serverCacheFile));
|
||||||
|
client.classes().putAll(server.classes());
|
||||||
|
client.fields().putAll(server.fields());
|
||||||
|
client.methods().putAll(server.methods());
|
||||||
|
client.parameters().putAll(server.parameters());
|
||||||
|
return new MappingBundle(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MappingBundle get(String gameVersion, String env, Path cacheFile) throws IOException {
|
||||||
|
return new MappingBundle(ProguardParser.read(getMappings(gameVersion, env, cacheFile)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static String getMappings(String gameVersion, String env, Path cacheFile) throws IOException {
|
||||||
|
if (Files.exists(cacheFile)) {
|
||||||
|
return Files.readString(cacheFile, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
UnmodifiableConfig manifest = HttpHelper.getJson(Constants.VERSION_MANIFEST);
|
||||||
|
String versionName;
|
||||||
|
if (gameVersion.startsWith("latest-")) {
|
||||||
|
versionName = (String) ((Map<?, ?>) manifest.get("latest")).get(gameVersion.split("-")[1]);
|
||||||
|
} else {
|
||||||
|
versionName = gameVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Loading version: " + versionName);
|
||||||
|
|
||||||
|
for (UnmodifiableConfig version : (List<UnmodifiableConfig>) manifest.get("versions")) {
|
||||||
|
if (version.get("id").equals(versionName)) {
|
||||||
|
|
||||||
|
UnmodifiableConfig versionManifest = HttpHelper.getJson(version.get("url"));
|
||||||
|
String mappingsUrl = ((UnmodifiableConfig) ((UnmodifiableConfig) versionManifest
|
||||||
|
.get("downloads")).get(env+"_mappings")).get("url");
|
||||||
|
|
||||||
|
String s = HttpHelper.getString(mappingsUrl);
|
||||||
|
Files.createDirectories(cacheFile.getParent());
|
||||||
|
Files.writeString(cacheFile, s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Could not find mojmap for the specified version: " + gameVersion + "/"+env+"!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
package dev.frogmc.thyroxine.provider;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||||
|
import dev.frogmc.thyroxine.Constants;
|
||||||
|
import dev.frogmc.thyroxine.HttpHelper;
|
||||||
|
import dev.frogmc.thyroxine.api.data.DocumentationData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingBundle;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.Member;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
public class ParchmentProvider {
|
||||||
|
|
||||||
|
private static final String PARCHMENT_URL = "https://maven.parchmentmc.org/org/parchmentmc/data";
|
||||||
|
|
||||||
|
private static String getParchmentUrl(String gameVersion) {
|
||||||
|
return PARCHMENT_URL + "/parchment-" + gameVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String findParchmentVersion(String gameVersion) throws IOException {
|
||||||
|
String url = getParchmentUrl(gameVersion) + "/maven-metadata.xml";
|
||||||
|
try (InputStream
|
||||||
|
stream = URI.create(url).toURL().openStream()) {
|
||||||
|
Document document = DocumentBuilderFactory.newInstance()
|
||||||
|
.newDocumentBuilder().parse(stream);
|
||||||
|
return document.getElementsByTagName("release").item(0).getTextContent();
|
||||||
|
} catch (IOException | SAXException | ParserConfigurationException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MappingBundle getParchment(String gameVersion, Path cacheDir) throws IOException {
|
||||||
|
return getParchment(gameVersion, findParchmentVersion(gameVersion), cacheDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MappingBundle getParchment(String gameVersion, String parchmentVer, Path cacheDir) throws IOException {
|
||||||
|
return getParchment(gameVersion, parchmentVer, cacheDir, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MappingBundle getParchment(String gameVersion, String parchmentVer, Path cacheDir, boolean forceDownload) throws IOException {
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
System.out.println("Loading Parchment mappings..");
|
||||||
|
var cachePath = cacheDir.resolve("parchment-" + gameVersion + "-" + parchmentVer + ".zip");
|
||||||
|
Files.createDirectories(cachePath.getParent());
|
||||||
|
|
||||||
|
var url = "%s/%s/parchment-%s-%s.zip".formatted(getParchmentUrl(gameVersion), parchmentVer, gameVersion, parchmentVer);
|
||||||
|
if (forceDownload || Files.notExists(cachePath)) {
|
||||||
|
HttpHelper.download(url, cachePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
var hash = HttpHelper.getString(url + ".sha512").split("\n", 2)[0];
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-512");
|
||||||
|
byte[] out = digest.digest(Files.readAllBytes(cachePath));
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : out) {
|
||||||
|
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
|
||||||
|
}
|
||||||
|
String local = sb.toString();
|
||||||
|
if (!hash.equals(local)) {
|
||||||
|
System.out.println("Hashes do not match for " + cachePath + ": " + local + " != " + hash + "; redownloading!");
|
||||||
|
return getParchment(gameVersion, parchmentVer, cacheDir, true);
|
||||||
|
}
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MappingBundle parchment = new MappingBundle();
|
||||||
|
MappingData parameters = new MappingData("named", "named");
|
||||||
|
try (FileSystem fs = FileSystems.newFileSystem(cachePath)) {
|
||||||
|
var mappings = Files.readString(fs.getPath("parchment.json"));
|
||||||
|
UnmodifiableConfig c = Constants.JSON_PARSER.parse(mappings);
|
||||||
|
// String version = c.get("version"); // currently unused
|
||||||
|
List<UnmodifiableConfig> packagesList = Objects.requireNonNullElse(c.get("packages"), Collections.emptyList());
|
||||||
|
List<DocumentationData.Package> packages = new ArrayList<>();
|
||||||
|
packagesList.forEach(config ->
|
||||||
|
packages.add(new DocumentationData.Package(config.get("name"), new ArrayList<>(Objects.requireNonNullElse(c.get("javadoc"),
|
||||||
|
Collections.emptyList())))));
|
||||||
|
|
||||||
|
List<DocumentationData.Class> classes = new ArrayList<>();
|
||||||
|
List<UnmodifiableConfig> classesList = Objects.requireNonNullElse(c.get("classes"), Collections.emptyList());
|
||||||
|
classesList.forEach(config -> {
|
||||||
|
List<DocumentationData.Method> methods = new ArrayList<>();
|
||||||
|
List<DocumentationData.Field> fields = new ArrayList<>();
|
||||||
|
|
||||||
|
List<UnmodifiableConfig> methodsList = Objects.requireNonNullElse(config.get("methods"), Collections.emptyList());
|
||||||
|
methodsList.forEach(uc -> {
|
||||||
|
List<String> javadoc = uc.get("javadoc");
|
||||||
|
List<UnmodifiableConfig> parametersList = Objects.requireNonNullElse(uc.get("parameters"), Collections.emptyList());
|
||||||
|
for (UnmodifiableConfig puC : parametersList) {
|
||||||
|
parameters.parameters().computeIfAbsent(new Member(config.get("name"), uc.get("name"), uc.get("descriptor")),
|
||||||
|
a -> new HashMap<>()).put(puC.get("index"), puC.get("name"));
|
||||||
|
String doc = puC.get("javadoc");
|
||||||
|
if (doc != null) {
|
||||||
|
if (javadoc == null) {
|
||||||
|
javadoc = new ArrayList<>();
|
||||||
|
}
|
||||||
|
javadoc.add("@parameter " + puC.get("name") + " " + doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
methods.add(new DocumentationData.Method(uc.get("name"), uc.get("descriptor"), Objects.requireNonNullElse(javadoc, new ArrayList<>())));
|
||||||
|
});
|
||||||
|
|
||||||
|
List<UnmodifiableConfig> fieldsList = Objects.requireNonNullElse(config.get("fields"), Collections.emptyList());
|
||||||
|
fieldsList.forEach(uc -> {
|
||||||
|
List<String> javadoc = new ArrayList<>(uc.get("javadoc"));
|
||||||
|
fields.add(new DocumentationData.Field(uc.get("name"), uc.get("descriptor"), javadoc));
|
||||||
|
});
|
||||||
|
|
||||||
|
classes.add(new DocumentationData.Class(config.get("name"), new ArrayList<>(Objects.requireNonNullElse(config.get("javadoc"),
|
||||||
|
Collections.emptyList())), fields, methods));
|
||||||
|
});
|
||||||
|
parchment.insert(parameters, new DocumentationData("named", packages, classes));
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.printf("Finished loading (%.2fs)\n", (System.currentTimeMillis() - time) / 1000F);
|
||||||
|
return parchment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String findForMinecraftVersion(String minecraftVersion) throws IOException {
|
||||||
|
return findParchmentVersion(minecraftVersion);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package dev.frogmc.thyroxine.writer;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingBundle;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.Member;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
|
public class ProguardWriter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING: This format should probably not be used.
|
||||||
|
* <strong>The Proguard format does not support parameter mappings or javadocs.
|
||||||
|
* This means that this process is likely to be lossy!</strong>
|
||||||
|
*
|
||||||
|
* @param bundle the data
|
||||||
|
* @param writer a writer for the mappings to be written to
|
||||||
|
*/
|
||||||
|
public static void write(MappingBundle bundle, Writer writer) {
|
||||||
|
MappingData data = bundle.flattenData(); // we can only have 2 namespaces.
|
||||||
|
Map<String, Map<Member, String>> methods = new HashMap<>();
|
||||||
|
Map<String, Map<Member, String>> fields = new HashMap<>();
|
||||||
|
|
||||||
|
data.methods().forEach((member, s) -> methods.computeIfAbsent(member.owner(), a -> new HashMap<>()).put(member, s));
|
||||||
|
data.fields().forEach((member, s) -> fields.computeIfAbsent(member.owner(), a -> new HashMap<>()).put(member, s));
|
||||||
|
|
||||||
|
PrintWriter print = new PrintWriter(writer, true);
|
||||||
|
print.println("# Generated by FrogMC/Thyroxine.");
|
||||||
|
print.println("# src-ns: " + data.srcNamespace() + " dst-ns: " + data.dstNamespace());
|
||||||
|
|
||||||
|
data.classes().keySet().stream().sorted().toList().forEach(s -> {
|
||||||
|
String s2 = data.classes().get(s);
|
||||||
|
print.println(s.replace("/", ".") + " -> " + s2.replace("/", "."));
|
||||||
|
fields.getOrDefault(s, Collections.emptyMap()).forEach((member, s1) ->
|
||||||
|
print.println(" " + Type.getType(member.descriptor()).getClassName() +
|
||||||
|
" " + member.name() + " -> " + s1));
|
||||||
|
methods.getOrDefault(s, Collections.emptyMap()).forEach((member, s1) -> {
|
||||||
|
Type type = Type.getMethodType(member.descriptor());
|
||||||
|
print.println(" ::" + type.getReturnType().getClassName() + " " +
|
||||||
|
member.name() + "(" + String.join(",", Arrays.stream(type.getArgumentTypes())
|
||||||
|
.map(Type::getClassName).toList()) + ") -> " + s1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package dev.frogmc.thyroxine.writer.tiny;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingBundle;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
|
||||||
|
public class TinyV1Writer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING: This format should probably not be used.
|
||||||
|
* <strong>Tiny V1 does not support parameter mappings or javadocs.
|
||||||
|
* This means that this process is likely to be lossy!</strong>
|
||||||
|
*
|
||||||
|
* @param bundle the data
|
||||||
|
* @param writer a writer for the mappings to be written to
|
||||||
|
*/
|
||||||
|
public static void write(MappingBundle bundle, Writer writer) {
|
||||||
|
MappingData data;
|
||||||
|
if (bundle.srcNamespaces().size() > 1 || bundle.dstNamespaces().size() > 1) {
|
||||||
|
data = bundle.flattenData(); // we can only have 2 namespaces.
|
||||||
|
} else {
|
||||||
|
data = bundle.data().getFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintWriter print = new PrintWriter(writer, true);
|
||||||
|
print.println("v1\t" + data.srcNamespace() + "\t" + data.dstNamespace());
|
||||||
|
|
||||||
|
data.classes().forEach((s, s2) -> print.println("CLASS\t" + s + "\t" + s2));
|
||||||
|
data.fields().forEach((member, s) -> print.println("FIELD\t" + member.owner() + "\t" + member.descriptor() + "\t" + member.name() + "\t" + s));
|
||||||
|
data.methods().forEach((member, s) -> print.println("METHOD\t" + member.owner() + "\t" + member.descriptor() + "\t" + member.name() + "\t" + s));
|
||||||
|
}
|
||||||
|
}
|
188
src/main/java/dev/frogmc/thyroxine/writer/tiny/TinyV2Writer.java
Normal file
188
src/main/java/dev/frogmc/thyroxine/writer/tiny/TinyV2Writer.java
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
package dev.frogmc.thyroxine.writer.tiny;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import dev.frogmc.thyroxine.api.Mapper;
|
||||||
|
import dev.frogmc.thyroxine.api.data.DocumentationData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingBundle;
|
||||||
|
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||||
|
import dev.frogmc.thyroxine.api.data.Member;
|
||||||
|
|
||||||
|
public class TinyV2Writer {
|
||||||
|
|
||||||
|
public static void write(MappingBundle bundle, Writer writer) {
|
||||||
|
|
||||||
|
Set<String> overlayNs = new HashSet<>();
|
||||||
|
for (MappingData d : bundle.data()) {
|
||||||
|
if (d.srcNamespace().equals(d.dstNamespace())) {
|
||||||
|
overlayNs.add(d.srcNamespace());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> srcNs = bundle.srcNamespaces();
|
||||||
|
List<String> dstNs = bundle.dstNamespaces();
|
||||||
|
List<String> srcNamespaces = srcNs.stream().distinct().filter(s -> !dstNs.contains(s) && !overlayNs.contains(s)).toList();
|
||||||
|
if (srcNamespaces.size() > 1) {
|
||||||
|
throw new IllegalStateException("Mapping Data cannot be reduced to a single source namespace, " +
|
||||||
|
"please use .flatten(...) to obtain a mappings set with defined namespaces");
|
||||||
|
}
|
||||||
|
String srcNamespace = srcNamespaces.getFirst();
|
||||||
|
|
||||||
|
Map<String, Map<Integer, String>> classes = new HashMap<>();
|
||||||
|
Map<String, Map<Member, Map<Integer, String>>> methods = new HashMap<>();
|
||||||
|
Map<String, Map<Member, Map<Integer, String>>> fields = new HashMap<>();
|
||||||
|
Map<Member, Map<Integer, Map<Integer, String>>> parameters = new HashMap<>();
|
||||||
|
Map<String, String> javadocs = new HashMap<>();
|
||||||
|
|
||||||
|
List<Mapper> reverseMappers = new ArrayList<>();
|
||||||
|
|
||||||
|
String src = srcNamespace;
|
||||||
|
|
||||||
|
MappingData data = bundle.getWithSrcNamespace(src);
|
||||||
|
DocumentationData docs;
|
||||||
|
int indent = 0;
|
||||||
|
while (data != null) {
|
||||||
|
reverseMappers.addFirst(new Mapper(data.reverse(), s -> Collections.emptyList()));
|
||||||
|
docs = bundle.get(data.dstNamespace());
|
||||||
|
Map<String, DocumentationData.Class> docsClasses = null;
|
||||||
|
Mapper selfMap = new Mapper(data, s -> Collections.emptyList());
|
||||||
|
if (docs != null) {
|
||||||
|
docsClasses = docs.classes().stream().collect(Collectors.toMap(DocumentationData.Class::name, c -> c));
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, String> e : data.classes().entrySet()) {
|
||||||
|
String k = e.getKey();
|
||||||
|
String s2 = e.getValue();
|
||||||
|
String root = k;
|
||||||
|
for (Mapper mapper : reverseMappers) {
|
||||||
|
root = mapper.map(root);
|
||||||
|
}
|
||||||
|
classes.computeIfAbsent(root, a -> new HashMap<>()).put(indent, s2);
|
||||||
|
if (docs != null) {
|
||||||
|
DocumentationData.Class c = docsClasses.get(k);
|
||||||
|
if (c != null) {
|
||||||
|
if (c.javadoc() != null && !c.javadoc().isEmpty()) {
|
||||||
|
javadocs.put(root, String.join("\\n", c.javadoc()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Map.Entry<Member, String> entry : data.methods().entrySet()) {
|
||||||
|
Member key = entry.getKey();
|
||||||
|
String value = entry.getValue();
|
||||||
|
Member root = key;
|
||||||
|
for (Mapper mapper : reverseMappers) {
|
||||||
|
root = new Member(mapper.map(root.owner()),
|
||||||
|
mapper.mapMethodName(root.owner(), root.name(), root.descriptor()),
|
||||||
|
mapper.mapMethodDesc(root.descriptor()));
|
||||||
|
}
|
||||||
|
methods.computeIfAbsent(root.owner(), a -> new HashMap<>()).computeIfAbsent(root, a -> new HashMap<>()).put(indent, value);
|
||||||
|
if (docs != null) {
|
||||||
|
Member rt = root;
|
||||||
|
DocumentationData.Class c = docsClasses.get(data.classes().get(key.owner()));
|
||||||
|
if (c != null) {
|
||||||
|
c.getMethod(data.methods().get(key), selfMap.mapMethodDesc(key.descriptor()))
|
||||||
|
.ifPresent(m -> {
|
||||||
|
if (m.javadoc() != null && !m.javadoc().isEmpty()) {
|
||||||
|
javadocs.put(rt.owner() + rt.descriptor() + rt.name(), String.join("\\n", m.javadoc()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Map.Entry<Member, String> entry : data.fields().entrySet()) {
|
||||||
|
Member member = entry.getKey();
|
||||||
|
String value = entry.getValue();
|
||||||
|
Member root = member;
|
||||||
|
for (Mapper mapper : reverseMappers) {
|
||||||
|
root = new Member(mapper.map(root.owner()),
|
||||||
|
mapper.mapFieldName(root.owner(), root.name(), root.descriptor()),
|
||||||
|
mapper.mapDesc(root.descriptor()));
|
||||||
|
}
|
||||||
|
fields.computeIfAbsent(root.owner(), a -> new HashMap<>()).computeIfAbsent(root, a -> new HashMap<>()).put(indent, value);
|
||||||
|
if (docs != null) {
|
||||||
|
String id = root.owner() + root.descriptor() + root.name();
|
||||||
|
DocumentationData.Class c = docsClasses.get(data.classes().get(member.owner()));
|
||||||
|
if (c != null) {
|
||||||
|
c.getField(data.fields().get(member), selfMap.mapDesc(member.descriptor()))
|
||||||
|
.ifPresent(f -> {
|
||||||
|
if (f.javadoc() != null && !f.javadoc().isEmpty()) {
|
||||||
|
javadocs.put(id, String.join("\\n", f.javadoc()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Map.Entry<Member, Map<Integer, String>> entry : data.parameters().entrySet()) {
|
||||||
|
Member member = entry.getKey();
|
||||||
|
Map<Integer, String> integerStringMap = entry.getValue();
|
||||||
|
Member root = member;
|
||||||
|
for (Mapper mapper : reverseMappers) {
|
||||||
|
root = new Member(mapper.map(root.owner()),
|
||||||
|
mapper.mapFieldName(root.owner(), root.name(), root.descriptor()),
|
||||||
|
mapper.mapDesc(root.descriptor()));
|
||||||
|
}
|
||||||
|
Map<Integer, Map<Integer, String>> map = parameters.computeIfAbsent(root, a -> new HashMap<>());
|
||||||
|
for (Map.Entry<Integer, String> e : integerStringMap.entrySet()) {
|
||||||
|
Integer integer = e.getKey();
|
||||||
|
String s = e.getValue();
|
||||||
|
map.computeIfAbsent(integer, a -> new HashMap<>()).put(indent, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indent++;
|
||||||
|
src = data.dstNamespace();
|
||||||
|
data = bundle.getWithSrcNamespace(src);
|
||||||
|
}
|
||||||
|
PrintWriter print = new PrintWriter(writer, true);
|
||||||
|
print.println("tiny\t2\t0\t" + srcNamespace + "\t" + String.join("\t", dstNs));
|
||||||
|
|
||||||
|
classes.keySet().stream().sorted().forEach(s -> {
|
||||||
|
Map<Integer, String> strings = classes.get(s);
|
||||||
|
print.println("c\t" + s + "\t" + printWithIndent(strings));
|
||||||
|
if (javadocs.containsKey(s)) {
|
||||||
|
print.println("\tc\t" + javadocs.get(s));
|
||||||
|
}
|
||||||
|
Map<Member, Map<Integer, String>> fmap = fields.getOrDefault(s, Collections.emptyMap());
|
||||||
|
fmap.keySet().stream().sorted(Comparator.comparing(Member::name)).forEach(m -> {
|
||||||
|
Map<Integer, String> map = fmap.get(m);
|
||||||
|
print.println("\tf\t" + m.descriptor() + "\t" + m.name() + "\t" + printWithIndent(map));
|
||||||
|
String id = m.owner() + m.descriptor() + m.name();
|
||||||
|
if (javadocs.containsKey(id)) {
|
||||||
|
print.println("\t\tc\t" + javadocs.get(id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Map<Member, Map<Integer, String>> mmap = methods.getOrDefault(s, Collections.emptyMap());
|
||||||
|
mmap.keySet().stream().sorted(Comparator.comparing(Member::name)).forEach(m -> {
|
||||||
|
Map<Integer, String> map = mmap.get(m);
|
||||||
|
print.println("\tm\t" + m.descriptor() + "\t" + m.name() + "\t" + printWithIndent(map));
|
||||||
|
String id = m.owner() + m.descriptor() + m.name();
|
||||||
|
if (javadocs.containsKey(id) && !javadocs.get(id).isEmpty()) {
|
||||||
|
print.println("\t\tc\t" + javadocs.get(id));
|
||||||
|
}
|
||||||
|
if (parameters.containsKey(m)) {
|
||||||
|
parameters.get(m).forEach((integer, ps) -> {
|
||||||
|
if (ps.values().stream().anyMatch(pname -> !pname.isEmpty())) {
|
||||||
|
print.println("\t\tp\t" + integer + "\t\t" + printWithIndent(ps));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String printWithIndent(Map<Integer, String> map) {
|
||||||
|
int max = map.keySet().stream().max(Comparator.comparingInt(i -> i)).orElse(0);
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (int current = 0; current <= max; current++) {
|
||||||
|
if (current > 0) {
|
||||||
|
builder.append("\t");
|
||||||
|
}
|
||||||
|
builder.append(map.getOrDefault(current, ""));
|
||||||
|
}
|
||||||
|
String line = builder.toString();
|
||||||
|
while (line.endsWith("\t")) {
|
||||||
|
line = line.substring(0, line.length()-1);
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
package io.github.moehreag.mojmap_patcher;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public final class Constants {
|
|
||||||
public static final String VERSION_MANIFEST = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
|
|
||||||
public static final Gson GSON = new GsonBuilder().create();
|
|
||||||
public static final Pattern METHOD_PATTERN = Pattern.compile("\\d+:\\d+:.*");
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package io.github.moehreag.mojmap_patcher;
|
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import lombok.experimental.UtilityClass;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static io.github.moehreag.mojmap_patcher.Constants.GSON;
|
|
||||||
|
|
||||||
@UtilityClass
|
|
||||||
public class HttpHelper {
|
|
||||||
|
|
||||||
private static final Map<String, String> requestCache = new HashMap<>();
|
|
||||||
|
|
||||||
public static Optional<JsonObject> getJson(String url) {
|
|
||||||
return getString(url).map(s -> GSON.fromJson(s, JsonObject.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<String> getString(String url) {
|
|
||||||
return Optional.ofNullable(requestCache.computeIfAbsent(url, s -> {
|
|
||||||
try (InputStream in = URI.create(url).parseServerAuthority().toURL().openStream()) {
|
|
||||||
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
|
||||||
} catch (IOException | URISyntaxException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package io.github.moehreag.mojmap_patcher;
|
|
||||||
|
|
||||||
import io.github.moehreag.mojmap_patcher.provider.MojmapProvider;
|
|
||||||
import io.github.moehreag.mojmap_patcher.parser.ProguardParser;
|
|
||||||
|
|
||||||
public class MojMapPatcher {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
String version;
|
|
||||||
if (args.length == 0) {
|
|
||||||
version = "latest-release";
|
|
||||||
} else {
|
|
||||||
version = args[0];
|
|
||||||
}
|
|
||||||
MojmapProvider.get(version).map(ProguardParser::read).ifPresent(s -> {
|
|
||||||
//s.getClassFields().keySet().forEach(System.out::println);
|
|
||||||
s.classFields().get("net.minecraft.client.Minecraft").forEach((s1, s2) -> System.out.println(s1 + " -> " + s2));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
package io.github.moehreag.mojmap_patcher.api;
|
|
||||||
|
|
||||||
import io.github.moehreag.mojmap_patcher.api.data.MappingData;
|
|
||||||
import io.github.moehreag.mojmap_patcher.api.data.Field;
|
|
||||||
import io.github.moehreag.mojmap_patcher.api.data.Method;
|
|
||||||
import org.objectweb.asm.commons.Remapper;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class Mapper extends Remapper {
|
|
||||||
|
|
||||||
private MappingData data;
|
|
||||||
|
|
||||||
public static Mapper load() {
|
|
||||||
throw new UnsupportedOperationException("TODO");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String mapClass(String name) {
|
|
||||||
return data.classes().getOrDefault(name, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Method> mapMethodInternal(String owner, String name, String descriptor) {
|
|
||||||
for (Map.Entry<Method, Method> entry : data.classMethods().getOrDefault(owner, Collections.emptyMap()).entrySet()) {
|
|
||||||
Method from = entry.getKey();
|
|
||||||
Method to = entry.getValue();
|
|
||||||
|
|
||||||
if (from.name().equals(name) && from.descriptor().equals(descriptor)) {
|
|
||||||
return Optional.of(to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Method mapMethod(String owner, String name, String descriptor) {
|
|
||||||
return mapMethodInternal(owner, name, descriptor).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Field> mapFieldInternal(String owner, String name, String descriptor) {
|
|
||||||
for (Map.Entry<Field, Field> entry : data.classFields().getOrDefault(owner, Collections.emptyMap()).entrySet()) {
|
|
||||||
Field from = entry.getKey();
|
|
||||||
Field to = entry.getValue();
|
|
||||||
|
|
||||||
if (from.name().equals(name) && from.descriptor().equals(descriptor)) {
|
|
||||||
return Optional.of(to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Field mapField(String owner, String name, String descriptor) {
|
|
||||||
return mapFieldInternal(owner, name, descriptor).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String map(String internalName) {
|
|
||||||
return mapClass(internalName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String mapMethodName(String owner, String name, String descriptor) {
|
|
||||||
return mapMethodInternal(owner, name, descriptor).map(Method::name).orElse(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String mapFieldName(String owner, String name, String descriptor) {
|
|
||||||
return mapFieldInternal(owner, name, descriptor).map(Field::name).orElse(name);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package io.github.moehreag.mojmap_patcher.api;
|
|
||||||
|
|
||||||
public class Remapper {
|
|
||||||
|
|
||||||
private final Mapper mapper;
|
|
||||||
|
|
||||||
public Remapper(Mapper mapper) {
|
|
||||||
this.mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Remapper of(Mapper mapper) {
|
|
||||||
return new Remapper(mapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void apply() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
package io.github.moehreag.mojmap_patcher.api.data;
|
|
||||||
|
|
||||||
public record Field(String name, String descriptor) {
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package io.github.moehreag.mojmap_patcher.api.data;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public record MappingData(
|
|
||||||
// IntelliJank, stop putting 8 spaces >:(
|
|
||||||
Map<String, String> classes,
|
|
||||||
Map<String, Map<Method, Method>> classMethods,
|
|
||||||
Map<String, Map<Field, Field>> classFields
|
|
||||||
) {
|
|
||||||
|
|
||||||
public MappingData() {
|
|
||||||
this(new HashMap<>(), new HashMap<>(), new HashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
package io.github.moehreag.mojmap_patcher.api.data;
|
|
||||||
|
|
||||||
public record Method(int startLine, int endLine, String returnType, String name, String descriptor) {
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package io.github.moehreag.mojmap_patcher.parser;
|
|
||||||
|
|
||||||
import io.github.moehreag.mojmap_patcher.Constants;
|
|
||||||
import io.github.moehreag.mojmap_patcher.api.data.Field;
|
|
||||||
import io.github.moehreag.mojmap_patcher.api.data.MappingData;
|
|
||||||
import io.github.moehreag.mojmap_patcher.api.data.Method;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
public class ProguardParser {
|
|
||||||
|
|
||||||
public static MappingData read(String mappings) {
|
|
||||||
String[] lines = mappings.split("\n");
|
|
||||||
|
|
||||||
MappingData data = new MappingData();
|
|
||||||
String currentClass = null;
|
|
||||||
for (String line : lines) {
|
|
||||||
if (line.startsWith("#")) { // Filter out comments
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] mapping = line.split("->");
|
|
||||||
String deobf = mapping[0].trim();
|
|
||||||
String obf = mapping[1].trim();
|
|
||||||
if (line.startsWith(" ")) { // It's a field/method mapping
|
|
||||||
if (currentClass == null) {
|
|
||||||
throw new IllegalStateException("Field/Method mapping outside a class");
|
|
||||||
}
|
|
||||||
if (Constants.METHOD_PATTERN.matcher(deobf).matches()) {
|
|
||||||
String[] method = deobf.split(":");
|
|
||||||
String[] descriptor = method[2].split(" ");
|
|
||||||
int bracket = descriptor[1].indexOf("(");
|
|
||||||
data.classMethods().computeIfAbsent(currentClass, s -> new HashMap<>()).put(new Method(Integer.parseInt(method[0]), Integer.parseInt(method[1]), obf, descriptor[1].substring(0, bracket), descriptor[1].substring(bracket + 1)),
|
|
||||||
new Method(Integer.parseInt(method[0]), Integer.parseInt(method[1]), descriptor[0], descriptor[1].substring(0, bracket), descriptor[1].substring(bracket + 1)));
|
|
||||||
} else {
|
|
||||||
String[] field = deobf.split(" ");
|
|
||||||
data.classFields().computeIfAbsent(currentClass, s -> new HashMap<>()).put(new Field(field[1], obf), new Field(field[1], field[0]));
|
|
||||||
}
|
|
||||||
} else { // It's a class mapping
|
|
||||||
currentClass = deobf;
|
|
||||||
data.classes().put(obf, deobf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package io.github.moehreag.mojmap_patcher.provider;
|
|
||||||
|
|
||||||
import com.google.gson.*;
|
|
||||||
import io.github.moehreag.mojmap_patcher.Constants;
|
|
||||||
import io.github.moehreag.mojmap_patcher.HttpHelper;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class MojmapProvider {
|
|
||||||
|
|
||||||
public static Optional<String> get(String gameVersion) {
|
|
||||||
return HttpHelper.getJson(Constants.VERSION_MANIFEST).map(manifest -> {
|
|
||||||
String versionName;
|
|
||||||
if (gameVersion.startsWith("latest-")) {
|
|
||||||
versionName = manifest.get("latest").getAsJsonObject().get(gameVersion.split("-")[1]).getAsString();
|
|
||||||
} else {
|
|
||||||
versionName = gameVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("Loading version: " + versionName);
|
|
||||||
|
|
||||||
for (JsonElement element : manifest.get("versions").getAsJsonArray()) {
|
|
||||||
JsonObject version = element.getAsJsonObject();
|
|
||||||
if (version.get("id").getAsString().equals(versionName)) {
|
|
||||||
|
|
||||||
JsonObject versionManifest = HttpHelper.getJson(version.get("url").getAsString()).orElseThrow();
|
|
||||||
String mappingsUrl = versionManifest
|
|
||||||
.get("downloads").getAsJsonObject().get("client_mappings").getAsJsonObject().get("url").getAsString();
|
|
||||||
|
|
||||||
return HttpHelper.getString(mappingsUrl).orElseThrow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in a new issue