add tinyv1/2 parsers + writers, add mapping merging + flattening, fix bugs #3
|
@ -8,7 +8,7 @@ plugins {
|
|||
}
|
||||
|
||||
group = "dev.frogmc"
|
||||
version = "0.0.1-alpha.5"
|
||||
version = "0.0.1-alpha.6"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
|
|
@ -10,12 +10,11 @@ import java.util.concurrent.ExecutorService;
|
|||
import java.util.concurrent.Executors;
|
||||
|
||||
import dev.frogmc.thyroxine.api.ParameterClassRemapper;
|
||||
import dev.frogmc.thyroxine.parser.ProguardParser;
|
||||
import dev.frogmc.thyroxine.api.data.MappingBundle;
|
||||
import dev.frogmc.thyroxine.provider.MojmapProvider;
|
||||
import dev.frogmc.thyroxine.provider.ParchmentProvider;
|
||||
import dev.frogmc.thyroxine.api.Mapper;
|
||||
import dev.frogmc.thyroxine.api.data.MappingData;
|
||||
import dev.frogmc.thyroxine.api.data.Parchment;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
|
@ -25,22 +24,33 @@ import org.objectweb.asm.commons.ClassRemapper;
|
|||
public class Thyroxine {
|
||||
|
||||
public static void run(String minecraftVersion, Path inputJar, Path outputJar, boolean skipMetaInf, boolean renameParameters) throws IOException, InterruptedException {
|
||||
MappingData data = ProguardParser.read(MojmapProvider.get(minecraftVersion,
|
||||
outputJar.resolveSibling("client-" + minecraftVersion + ".txt")).orElseThrow()).reverse();
|
||||
MappingBundle data = MojmapProvider.get(minecraftVersion,
|
||||
outputJar.resolveSibling("client-" + minecraftVersion + ".txt")).orElseThrow().reverse();
|
||||
|
||||
Parchment paramMappings = null;
|
||||
MappingBundle parchment = null;
|
||||
if (renameParameters) {
|
||||
paramMappings = ParchmentProvider.getParchment(minecraftVersion, outputJar.getParent());
|
||||
parchment = ParchmentProvider.getParchment(minecraftVersion, outputJar.getParent());
|
||||
}
|
||||
|
||||
remap(data, inputJar, outputJar, skipMetaInf, renameParameters, paramMappings);
|
||||
MappingBundle result;
|
||||
if (parchment != null) {
|
||||
result = MappingBundle.merge(data, parchment);
|
||||
} else {
|
||||
result = data;
|
||||
}
|
||||
|
||||
remap(result, inputJar, outputJar, skipMetaInf, renameParameters, "official", "named");
|
||||
}
|
||||
|
||||
public static void remap(MappingData data, Path inputJar, Path outputJar, boolean skipMetaInf, Parchment paramMappings) throws IOException, InterruptedException {
|
||||
remap(data, inputJar, outputJar, skipMetaInf, true, paramMappings);
|
||||
public static void remap(MappingData data, Path inputJar, Path outputJar, boolean skipMetaInf) throws IOException, InterruptedException {
|
||||
remap(data, inputJar, outputJar, skipMetaInf, true);
|
||||
}
|
||||
|
||||
public static void remap(MappingData data, Path inputJar, Path outputJar, boolean skipMetaInf, boolean renameParameters, Parchment paramMappings) throws IOException, InterruptedException {
|
||||
public static void remap(MappingBundle bundle, Path inputJar, Path outputJar, boolean skipMetaInf, boolean renameParameters, String srcNamespace, String dstNamespace) throws IOException, InterruptedException {
|
||||
remap(bundle.forNamespaces(srcNamespace, dstNamespace), inputJar, outputJar, skipMetaInf, renameParameters);
|
||||
}
|
||||
|
||||
public static void remap(MappingData data, Path inputJar, Path outputJar, boolean skipMetaInf, boolean renameParameters) throws IOException, InterruptedException {
|
||||
Files.deleteIfExists(outputJar);
|
||||
|
||||
System.out.println("Remapping...");
|
||||
|
@ -81,7 +91,7 @@ public class Thyroxine {
|
|||
ClassVisitor remapper = new ClassRemapper(writer, mapper);
|
||||
ClassVisitor visitor;
|
||||
if (renameParameters) {
|
||||
visitor = new ParameterClassRemapper(remapper, mapper, paramMappings);
|
||||
visitor = new ParameterClassRemapper(remapper, mapper, data);
|
||||
} else {
|
||||
visitor = remapper;
|
||||
}
|
||||
|
|
|
@ -60,4 +60,9 @@ public class Mapper extends Remapper {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mapMethodDesc(String methodDescriptor) {
|
||||
return super.mapMethodDesc(methodDescriptor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package dev.frogmc.thyroxine.api;
|
||||
|
||||
import dev.frogmc.thyroxine.api.data.Parchment;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
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.Opcodes;
|
||||
|
@ -8,22 +12,21 @@ import org.objectweb.asm.commons.ClassRemapper;
|
|||
import org.objectweb.asm.commons.Remapper;
|
||||
|
||||
public class ParameterClassRemapper extends ClassRemapper {
|
||||
private final Parchment parchment;
|
||||
private final MappingData data;
|
||||
|
||||
public ParameterClassRemapper(ClassVisitor classVisitor, Remapper remapper, Parchment parchment) {
|
||||
public ParameterClassRemapper(ClassVisitor classVisitor, Remapper remapper, MappingData data) {
|
||||
super(Opcodes.ASM9, classVisitor, remapper);
|
||||
this.parchment = parchment;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@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));
|
||||
|
||||
Parchment.Method method = parchment != null ? parchment.getClass(remapper.map(className)).flatMap(c -> c.getMethod(remapper.mapMethodName(className, name, descriptor), remapper.mapMethodDesc(descriptor)))
|
||||
.orElse(null) : null;
|
||||
return methodVisitor == null ? null : new ParameterMethodRemapper(methodVisitor, remapper, method);
|
||||
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 methodVisitor == null ? null : new ParameterMethodRemapper(methodVisitor, remapper, parameters);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
package dev.frogmc.thyroxine.api;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
import dev.frogmc.thyroxine.api.data.Parchment;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
@ -14,20 +10,19 @@ import org.objectweb.asm.commons.MethodRemapper;
|
|||
import org.objectweb.asm.commons.Remapper;
|
||||
|
||||
public class ParameterMethodRemapper extends MethodRemapper {
|
||||
private final Parchment.Method parchment;
|
||||
private final Map<Integer, String> parameters;
|
||||
private final Set<String> usedLocalNames = new HashSet<>();
|
||||
|
||||
public ParameterMethodRemapper(MethodVisitor methodVisitor, Remapper remapper, Parchment.Method parchment) {
|
||||
public ParameterMethodRemapper(MethodVisitor methodVisitor, Remapper remapper, Map<Integer, String> parameters) {
|
||||
super(Opcodes.ASM9, methodVisitor, remapper);
|
||||
this.parchment = parchment;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
||||
if (!name.equals("this")) {
|
||||
if (parchment != null) {
|
||||
Optional<Parchment.Parameter> parameter = parchment.getParameter(index);
|
||||
name = parameter.map(Parchment.Parameter::name).orElseGet(() -> getNewName(descriptor, signature));
|
||||
if (parameters.containsKey(index)) {
|
||||
name = parameters.get(index);
|
||||
} else {
|
||||
name = getNewName(descriptor, signature);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
314
src/main/java/dev/frogmc/thyroxine/api/data/MappingBundle.java
Normal file
314
src/main/java/dev/frogmc/thyroxine/api/data/MappingBundle.java
Normal file
|
@ -0,0 +1,314 @@
|
|||
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 a, MappingBundle b) {
|
||||
|
||||
List<MappingData> mergedData = new ArrayList<>();
|
||||
mergedData.addAll(a.data());
|
||||
mergedData.addAll(b.data());
|
||||
List<DocumentationData> mergedDocs = new ArrayList<>();
|
||||
mergedDocs.addAll(a.documentation());
|
||||
mergedDocs.addAll(b.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) {
|
||||
/*List<MappingData> matching = new ArrayList<>();
|
||||
for (MappingData datum : data) {
|
||||
if (!datum.dstNamespace().equals(src) && !datum.srcNamespace().equals(dst)) { // cut the graph so it has new ends
|
||||
matching.add(datum);
|
||||
}
|
||||
}
|
||||
|
||||
return new MappingBundle(matching, documentation).flatten(src, dst);*/
|
||||
return flatten(src, dst);
|
||||
}
|
||||
|
||||
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), 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() {
|
||||
MappingData data = flattenData();
|
||||
return new MappingBundle(data, get(data.dstNamespace()));
|
||||
}
|
||||
|
||||
public MappingData flattenData() {
|
||||
List<String> srcNs = srcNamespaces();
|
||||
List<String> dstNs = dstNamespaces();
|
||||
List<String> srcNsCandidates = srcNs.stream().filter(s -> !dstNs.contains(s)).toList();
|
||||
List<String> dstNsCandidates = dstNs.stream().filter(s -> !srcNs.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));
|
||||
}
|
||||
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 flatten(srcRoot, dstRoot);
|
||||
}
|
||||
|
||||
private MappingData flatten(String srcRoot, String dstRoot) {
|
||||
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) {
|
||||
/*
|
||||
* 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) {
|
||||
/*
|
||||
* 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) {
|
||||
/*
|
||||
* 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 : new ArrayList<>(available)) {
|
||||
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 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;
|
||||
}
|
||||
}
|
|
@ -1,63 +1,73 @@
|
|||
package dev.frogmc.thyroxine.api.data;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public record MappingData(Map<String, String> classes, Map<Member, String> methods, Map<Member, String> fields) {
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
public MappingData() {
|
||||
this(new HashMap<>(), new HashMap<>(), new HashMap<>());
|
||||
}
|
||||
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 reverse() {
|
||||
Map<String, String> classesReversed = new HashMap<>();
|
||||
Map<Member, String> methodsReversed = new HashMap<>();
|
||||
Map<Member, String> fieldsReversed = new HashMap<>();
|
||||
public MappingData(String srcNs, String dstNs) {
|
||||
this(srcNs, dstNs, new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> clazz : classes.entrySet())
|
||||
classesReversed.put(clazz.getValue(), clazz.getKey());
|
||||
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<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<String, String> clazz : classes.entrySet())
|
||||
classesReversed.put(clazz.getValue(), clazz.getKey());
|
||||
|
||||
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, 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()
|
||||
);
|
||||
}
|
||||
|
||||
return new MappingData(classesReversed, methodsReversed, fieldsReversed);
|
||||
}
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
||||
private Type remapType(Type type) {
|
||||
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()));
|
||||
Type.getObjectType(classes.getOrDefault(type.getInternalName(), type.getInternalName()));
|
||||
case Type.ARRAY -> Type.getObjectType(
|
||||
"[".repeat(type.getDimensions()) + remapType(type.getElementType()).getDescriptor()
|
||||
"[".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)
|
||||
remapType(type.getReturnType()),
|
||||
Arrays.stream(type.getArgumentTypes()).map(this::remapType).toArray(Type[]::new)
|
||||
);
|
||||
default -> type;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
package dev.frogmc.thyroxine.api.data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public record Parchment(String version, List<Package> packages, List<Class> classes) {
|
||||
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 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 record Field(String name, String descriptor, List<String> javadoc) {
|
||||
}
|
||||
|
||||
public record Method(String name, String descriptor, List<String> javadoc, List<Parameter> parameters) {
|
||||
public Optional<Parameter> getParameter(int index) {
|
||||
if (parameters == null){
|
||||
return Optional.empty();
|
||||
}
|
||||
return parameters.stream()
|
||||
.filter(f -> f.index == index)
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
public record Parameter(int index, String name) {
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ public class ProguardParser {
|
|||
public static MappingData read(String mappings) {
|
||||
String[] lines = mappings.split("\n");
|
||||
|
||||
MappingData data = new MappingData();
|
||||
MappingData data = new MappingData("named", "official");
|
||||
String currentClass = null;
|
||||
for (String line : lines) {
|
||||
if (line.contains("#")) {
|
||||
|
|
|
@ -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 {
|
||||
|
||||
private 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) {
|
||||
do {
|
||||
i++;
|
||||
} while (lines[i].startsWith("\t"));
|
||||
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) {
|
||||
throw new IllegalStateException("Field javadoc/Method parameter mapping/javadoc specified before a declaration");
|
||||
}
|
||||
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ package dev.frogmc.thyroxine.provider;
|
|||
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.parser.ProguardParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -14,8 +16,12 @@ import java.util.Optional;
|
|||
|
||||
public class MojmapProvider {
|
||||
|
||||
public static Optional<MappingBundle> get(String gameVersion, Path cacheFile) {
|
||||
return getMappings(gameVersion, cacheFile).map(ProguardParser::read).map(MappingBundle::new);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Optional<String> get(String gameVersion, Path cacheFile) {
|
||||
private static Optional<String> getMappings(String gameVersion, Path cacheFile) {
|
||||
if (Files.exists(cacheFile)){
|
||||
try {
|
||||
return Optional.of(Files.readString(cacheFile, StandardCharsets.UTF_8));
|
||||
|
|
|
@ -18,7 +18,10 @@ import java.util.*;
|
|||
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||
import dev.frogmc.thyroxine.Constants;
|
||||
import dev.frogmc.thyroxine.api.data.Parchment;
|
||||
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;
|
||||
|
||||
|
@ -42,15 +45,15 @@ public class ParchmentProvider {
|
|||
}
|
||||
}
|
||||
|
||||
public static Parchment getParchment(String gameVersion, Path cacheDir) throws IOException {
|
||||
public static MappingBundle getParchment(String gameVersion, Path cacheDir) throws IOException {
|
||||
return getParchment(gameVersion, findParchmentVersion(gameVersion), cacheDir);
|
||||
}
|
||||
|
||||
public static Parchment getParchment(String gameVersion, String parchmentVer, Path cacheDir) throws IOException {
|
||||
public static MappingBundle getParchment(String gameVersion, String parchmentVer, Path cacheDir) throws IOException {
|
||||
return getParchment(gameVersion, parchmentVer, cacheDir, false);
|
||||
}
|
||||
|
||||
public static Parchment getParchment(String gameVersion, String parchmentVer, Path cacheDir, boolean forceDownload) throws IOException {
|
||||
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");
|
||||
|
@ -79,29 +82,52 @@ public class ParchmentProvider {
|
|||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
Parchment parchment;
|
||||
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");
|
||||
List<Parchment.Package> packages = c.get("packages");
|
||||
List<Parchment.Class> classes = new ArrayList<>();
|
||||
// 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<Parchment.Method> methods = new ArrayList<>();
|
||||
List<Parchment.Field> fields = new ArrayList<>();
|
||||
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<Parchment.Parameter> parameters = new ArrayList<>();
|
||||
List<String> javadoc = uc.get("javadoc");
|
||||
List<UnmodifiableConfig> parametersList = Objects.requireNonNullElse(uc.get("parameters"), Collections.emptyList());
|
||||
parametersList.forEach(puC -> parameters.add(new Parchment.Parameter(puC.get("index"), puC.get("name"))));
|
||||
methods.add(new Parchment.Method(uc.get("name"), uc.get("descriptor"), uc.get("javadoc"), parameters));
|
||||
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"), javadoc));
|
||||
});
|
||||
|
||||
classes.add(new Parchment.Class(config.get("name"), config.get("javadoc"), fields, methods));
|
||||
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 = new Parchment(version, packages, classes);
|
||||
parchment.insert(parameters, new DocumentationData("named", packages, classes));
|
||||
}
|
||||
|
||||
System.out.printf("Finished loading (%.2fs)\n", (System.currentTimeMillis() - time) / 1000F);
|
||||
|
|
|
@ -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,29 @@
|
|||
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 = bundle.flattenData(); // we can only have 2 namespaces.
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
203
src/main/java/dev/frogmc/thyroxine/writer/tiny/TinyV2Writer.java
Normal file
203
src/main/java/dev/frogmc/thyroxine/writer/tiny/TinyV2Writer.java
Normal file
|
@ -0,0 +1,203 @@
|
|||
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) {
|
||||
|
||||
List<String> srcNs = bundle.srcNamespaces();
|
||||
List<String> dstNs = bundle.dstNamespaces();
|
||||
List<String> srcNamespaces = srcNs.stream().filter(s -> !dstNs.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 = bundle.get(src);
|
||||
data.classes().forEach((s, s2) -> classes.computeIfAbsent(s, a -> new HashMap<>()).put(0, s2));
|
||||
data.methods().forEach((member, s) -> methods.computeIfAbsent(member.owner(), a -> new HashMap<>())
|
||||
.computeIfAbsent(member, a -> new HashMap<>()).put(0, s));
|
||||
data.fields().forEach((member, s) -> fields.computeIfAbsent(member.owner(), a -> new HashMap<>())
|
||||
.computeIfAbsent(member, a -> new HashMap<>()).put(0, s));
|
||||
data.parameters().forEach((member, map) -> {
|
||||
Map<Integer, Map<Integer, String>> b = parameters.computeIfAbsent(member, a -> new HashMap<>());
|
||||
map.forEach((i, s) -> {
|
||||
b.computeIfAbsent(i, a -> new HashMap<>()).put(0, s);
|
||||
});
|
||||
});
|
||||
reverseMappers.addFirst(new Mapper(data.reverse(), s -> Collections.emptyList()));
|
||||
if (docs != null) {
|
||||
docs.classes().forEach(c -> {
|
||||
if (c.javadoc() != null) {
|
||||
javadocs.put(c.name(), String.join("\n", c.javadoc()));
|
||||
}
|
||||
});
|
||||
}*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue