From 72176117bf75866fc702bbb094e7adc6661f2b01 Mon Sep 17 00:00:00 2001 From: Toshimichi0915 Date: Sun, 12 Jun 2022 21:24:41 +0900 Subject: Add Recaf format support (#451) * Add Recaf format support * Update language files Co-authored-by: 2xsaiko --- enigma/build.gradle | 2 + .../translation/mapping/serde/MappingFormat.java | 4 +- .../mapping/serde/recaf/RecafMappingsReader.java | 61 ++++++++++++++++ .../mapping/serde/recaf/RecafMappingsWriter.java | 85 ++++++++++++++++++++++ enigma/src/main/resources/lang/en_us.json | 1 + enigma/src/main/resources/lang/fr_fr.json | 1 + enigma/src/main/resources/lang/ja_jp.json | 1 + enigma/src/main/resources/lang/zh_cn.json | 1 + .../translation/mapping/serde/recaf/TestRecaf.java | 46 ++++++++++++ enigma/src/test/resources/recaf.mappings | 10 +++ 10 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/recaf/RecafMappingsReader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/recaf/RecafMappingsWriter.java create mode 100644 enigma/src/test/java/cuchaz/enigma/translation/mapping/serde/recaf/TestRecaf.java create mode 100644 enigma/src/test/resources/recaf.mappings diff --git a/enigma/build.gradle b/enigma/build.gradle index 8de7615f..b4a40629 100644 --- a/enigma/build.gradle +++ b/enigma/build.gradle @@ -12,6 +12,8 @@ dependencies { implementation 'net.fabricmc:cfr:0.1.0' proGuard 'com.guardsquare:proguard-base:7.2.0-beta2' + + testImplementation 'com.google.jimfs:jimfs:1.2' } // Generate "version.txt" file diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java index ca275ebd..062c8775 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java @@ -6,6 +6,7 @@ import cuchaz.enigma.translation.mapping.MappingDelta; import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader; import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsWriter; import cuchaz.enigma.translation.mapping.serde.proguard.ProguardMappingsReader; +import cuchaz.enigma.translation.mapping.serde.recaf.RecafMappingsReader; import cuchaz.enigma.translation.mapping.serde.srg.SrgMappingsWriter; import cuchaz.enigma.translation.mapping.serde.tiny.TinyMappingsReader; import cuchaz.enigma.translation.mapping.serde.tiny.TinyMappingsWriter; @@ -24,7 +25,8 @@ public enum MappingFormat { TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader()), TINY_FILE(TinyMappingsWriter.INSTANCE, TinyMappingsReader.INSTANCE), SRG_FILE(SrgMappingsWriter.INSTANCE, null), - PROGUARD(null, ProguardMappingsReader.INSTANCE); + PROGUARD(null, ProguardMappingsReader.INSTANCE), + RECAF(null, RecafMappingsReader.INSTANCE); private final MappingsWriter writer; diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/recaf/RecafMappingsReader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/recaf/RecafMappingsReader.java new file mode 100644 index 00000000..483e4e40 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/recaf/RecafMappingsReader.java @@ -0,0 +1,61 @@ +package cuchaz.enigma.translation.mapping.serde.recaf; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.serde.MappingsReader; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RecafMappingsReader implements MappingsReader { + + public static final RecafMappingsReader INSTANCE = new RecafMappingsReader(); + private static final Pattern METHOD_PATTERN = Pattern.compile("(.*?)\\.(.*?)(\\(.*?) (.*)"); + private static final Pattern FIELD_PATTERN = Pattern.compile("(.*?)\\.(.*?) (.*?) (.*)"); + private static final Pattern CLASS_PATTERN = Pattern.compile("(.*?) (.*)"); + + @Override + public EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException { + EntryTree mappings = new HashEntryTree<>(); + List lines = Files.readAllLines(path); + + for (String line : lines) { + Matcher methodMatcher = METHOD_PATTERN.matcher(line); + if (methodMatcher.find()) { + ClassEntry owner = new ClassEntry(methodMatcher.group(1)); + String name = methodMatcher.group(2); + MethodDescriptor desc = new MethodDescriptor(methodMatcher.group(3)); + mappings.insert(new MethodEntry(owner, name, desc), new EntryMapping(methodMatcher.group(4))); + continue; + } + + Matcher fieldMatcher = FIELD_PATTERN.matcher(line); + if (fieldMatcher.find()) { + ClassEntry owner = new ClassEntry(fieldMatcher.group(1)); + String name = fieldMatcher.group(2); + TypeDescriptor desc = new TypeDescriptor(fieldMatcher.group(3)); + mappings.insert(new FieldEntry(owner, name, desc), new EntryMapping(fieldMatcher.group(4))); + continue; + } + + Matcher classMatcher = CLASS_PATTERN.matcher(line); + if (classMatcher.find()) { + mappings.insert(new ClassEntry(classMatcher.group(1)), new EntryMapping(classMatcher.group(2))); + } + } + return mappings; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/recaf/RecafMappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/recaf/RecafMappingsWriter.java new file mode 100644 index 00000000..aa29ff60 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/recaf/RecafMappingsWriter.java @@ -0,0 +1,85 @@ +package cuchaz.enigma.translation.mapping.serde.recaf; + +import com.google.common.collect.Lists; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.serde.MappingsWriter; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; + +public class RecafMappingsWriter implements MappingsWriter { + + public static final RecafMappingsWriter INSTANCE = new RecafMappingsWriter(); + + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + try { + Files.deleteIfExists(path); + Files.createFile(path); + } catch (IOException e) { + e.printStackTrace(); + } + + try (BufferedWriter writer = Files.newBufferedWriter(path)) { + Lists.newArrayList(mappings) + .stream() + .map(EntryTreeNode::getEntry) + .forEach(entry -> writeEntry(writer, mappings, entry)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void writeEntry(Writer writer, EntryTree mappings, Entry entry) { + EntryTreeNode node = mappings.findNode(entry); + if (node == null) { + return; + } + + EntryMapping mapping = mappings.get(entry); + + try { + if (mapping != null && mapping.targetName() != null) { + if (entry instanceof ClassEntry classEntry) { + + writer.write(classEntry.getFullName()); + writer.write(" "); + writer.write(mapping.targetName()); + + } else if (entry instanceof FieldEntry fieldEntry) { + + writer.write(fieldEntry.getFullName()); + writer.write(" "); + writer.write(fieldEntry.getDesc().toString()); + writer.write(" "); + writer.write(mapping.targetName()); + + } else if (entry instanceof MethodEntry methodEntry) { + + writer.write(methodEntry.getFullName()); + writer.write(methodEntry.getDesc().toString()); + writer.write(" "); + writer.write(mapping.targetName()); + + } + writer.write("\n"); + } + } catch (IOException e) { + e.printStackTrace(); + } + + node.getChildren().forEach(child -> writeEntry(writer, mappings, child)); + } +} diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index 4488bb2e..4ec2f087 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json @@ -8,6 +8,7 @@ "mapping_format.tiny_file": "Tiny File", "mapping_format.srg_file": "SRG File", "mapping_format.proguard": "Proguard", + "mapping_format.recaf": "Recaf", "type.methods": "Methods", "type.fields": "Fields", "type.parameters": "Parameters", diff --git a/enigma/src/main/resources/lang/fr_fr.json b/enigma/src/main/resources/lang/fr_fr.json index 2c9ec2cb..d3d0c297 100644 --- a/enigma/src/main/resources/lang/fr_fr.json +++ b/enigma/src/main/resources/lang/fr_fr.json @@ -8,6 +8,7 @@ "mapping_format.tiny_file": "Fichier Tiny", "mapping_format.srg_file": "Fichier SRG", "mapping_format.proguard": "Proguard", + "mapping_format.recaf": "Recaf", "type.methods": "Méthodes", "type.fields": "Champs", "type.parameters": "Paramètres", diff --git a/enigma/src/main/resources/lang/ja_jp.json b/enigma/src/main/resources/lang/ja_jp.json index 50449b6b..09e7ee08 100644 --- a/enigma/src/main/resources/lang/ja_jp.json +++ b/enigma/src/main/resources/lang/ja_jp.json @@ -8,6 +8,7 @@ "mapping_format.tiny_file": "Tiny ファイル", "mapping_format.srg_file": "SRG ファイル", "mapping_format.proguard": "Proguard", + "mapping_format.recaf": "Recaf", "type.methods": "メソッド", "type.fields": "フィールド", "type.parameters": "パラメータ", diff --git a/enigma/src/main/resources/lang/zh_cn.json b/enigma/src/main/resources/lang/zh_cn.json index fc36cb88..fe806fb3 100644 --- a/enigma/src/main/resources/lang/zh_cn.json +++ b/enigma/src/main/resources/lang/zh_cn.json @@ -8,6 +8,7 @@ "mapping_format.tiny_file": "Tiny 文件", "mapping_format.srg_file": "SRG 文件", "mapping_format.proguard": "Proguard 文件", + "mapping_format.recaf": "Recaf 文件", "type.methods": "方法", "type.fields": "字段", "type.parameters": "参数", diff --git a/enigma/src/test/java/cuchaz/enigma/translation/mapping/serde/recaf/TestRecaf.java b/enigma/src/test/java/cuchaz/enigma/translation/mapping/serde/recaf/TestRecaf.java new file mode 100644 index 00000000..1026f576 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/translation/mapping/serde/recaf/TestRecaf.java @@ -0,0 +1,46 @@ +package cuchaz.enigma.translation.mapping.serde.recaf; + +import com.google.common.collect.Sets; +import com.google.common.jimfs.Jimfs; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import org.junit.Test; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +public class TestRecaf { + + @Test + public void testIntegrity() throws Exception { + Set contents; + try (InputStream in = getClass().getResourceAsStream("/recaf.mappings")) { + contents = Sets.newHashSet(new String(in.readAllBytes(), StandardCharsets.UTF_8).split("\\R")); + } + + try (FileSystem fs = Jimfs.newFileSystem()) { + + Path path = fs.getPath("recaf.mappings"); + Files.writeString(path, String.join("\n", contents)); + + RecafMappingsWriter writer = RecafMappingsWriter.INSTANCE; + RecafMappingsReader reader = RecafMappingsReader.INSTANCE; + + EntryTree mappings = reader.read(path, ProgressListener.none(), null); + writer.write(mappings, path, ProgressListener.none(), null); + + reader.read(path, ProgressListener.none(), null); + Set newContents = new HashSet<>(Files.readAllLines(path)); + + assertEquals(contents, newContents); + } + } +} diff --git a/enigma/src/test/resources/recaf.mappings b/enigma/src/test/resources/recaf.mappings new file mode 100644 index 00000000..479099ed --- /dev/null +++ b/enigma/src/test/resources/recaf.mappings @@ -0,0 +1,10 @@ +a testClass +a.a Z testField +a.a()V testMethod0 +a.a(Z)V testMethod1 +a.b()V testMethod2 +a/a testInnerClass +a/a.a Z testInnerField +a/a.a()V testInnerMethod0 +a/a.a(Z)V testInnerMethod1 +a/a.b()V testInnerMethod2 -- cgit v1.2.3