Skip to content

Commit

Permalink
it's christmas eve and where am i? on the computer
Browse files Browse the repository at this point in the history
implement mojmap package proposal
  • Loading branch information
ix0rai committed Dec 25, 2024
1 parent 838be24 commit 58cb956
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
import org.jetbrains.annotations.Nullable;
import org.quiltmc.enigma.api.Enigma;
import org.quiltmc.enigma.api.ProgressListener;
import org.quiltmc.enigma.api.analysis.index.jar.EntryIndex;
import org.quiltmc.enigma.api.analysis.index.jar.JarIndex;
import org.quiltmc.enigma.api.analysis.index.mapping.MappingsIndex;
import org.quiltmc.enigma.api.analysis.index.mapping.PackageIndex;
import org.quiltmc.enigma.api.translation.mapping.EntryMapping;
import org.quiltmc.enigma.api.translation.mapping.EntryRemapper;
import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree;
import org.quiltmc.enigma.api.translation.mapping.tree.EntryTreeNode;
import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry;
import org.quiltmc.enigma.api.translation.representation.entry.Entry;
import org.tinylog.Logger;

Expand All @@ -27,6 +30,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;

public class MojmapNameProposer extends NameProposer {
public static final String ID = "mojmap";
Expand All @@ -36,6 +40,7 @@ public class MojmapNameProposer extends NameProposer {
// must be static for now. nasty hack to make sure we don't read mojmaps twice
// we can guarantee that this is nonnull for the other proposer because jar proposal blocks dynamic proposal
public static EntryTree<EntryMapping> mojmaps;
private PackageEntryList packageOverrides;

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public MojmapNameProposer(Optional<String> mojmapPath, Optional<String> packageNameOverridesPath) {
Expand Down Expand Up @@ -68,10 +73,64 @@ public void insertProposedNames(Enigma enigma, JarIndex index, Map<Entry<?>, Ent
});

if (mojmaps != null) {
packageOverrides = readPackageJson(this.packageNameOverridesPath);
mojmaps.getRootNodes().forEach((node) -> this.proposeNodeAndChildren(mappings, node));
}
}

@Override
public void proposeDynamicNames(EntryRemapper remapper, Entry<?> obfEntry, EntryMapping oldMapping, EntryMapping newMapping, Map<Entry<?>, EntryMapping> mappings) {
if (packageOverrides != null) {
if (obfEntry == null) {
// rename all classes as per overrides
var classes = remapper.getJarIndex().getIndex(EntryIndex.class).getClasses();
for (ClassEntry classEntry : classes) {
proposePackageName(classEntry, null, mappings);
}
} else if (obfEntry instanceof ClassEntry classEntry) {
// rename class
proposePackageName(classEntry, newMapping, mappings);
}
}
}

private void proposePackageName(ClassEntry entry, @Nullable EntryMapping newMapping, Map<Entry<?>, EntryMapping> mappings) {
if (entry.isInnerClass()) {
//throw new RuntimeException("cannot propose package name for an inner class: " + entry);
return;
}

// todo don't run through mojmaps here -- use whatever's passed?
EntryMapping mojmap = mojmaps.get(entry);
if (mojmap == null || mojmap.targetName() == null) {
Logger.error("no mojmap for outer class: " + entry);
return;
}

// todo string manip is stupid here
String mojTarget = mojmap.targetName();
String target;
String obfPackage = mojTarget.substring(0, mojTarget.lastIndexOf('/'));

if (newMapping != null && newMapping.targetName() != null) {
target = mojTarget.substring(0, mojTarget.lastIndexOf('/')) + newMapping.targetName().substring(newMapping.targetName().lastIndexOf('/') + 1);
} else {
target = mojTarget;
}

Optional<PackageEntry> optionalPackageEntry = packageOverrides.findEntry(obfPackage);
optionalPackageEntry.ifPresent(packageEntry -> {
String deobfPackageString = packageEntry.toDeobfPackageString();
String obfPackageString = packageEntry.toObfPackageString();

if (!deobfPackageString.equals(obfPackageString)) {
String newTarget = target.replace(obfPackage + "/", deobfPackageString + "/");
// todo why is this stack overflowing
mappings.put(entry, new EntryMapping(newTarget));
}
});
}

private void proposeNodeAndChildren(Map<Entry<?>, EntryMapping> mappings, EntryTreeNode<EntryMapping> node) {
if (node.getValue() != null && node.getValue().targetName() != null) {
this.insertProposal(mappings, node.getEntry(), node.getValue().targetName());
Expand All @@ -81,12 +140,13 @@ private void proposeNodeAndChildren(Map<Entry<?>, EntryMapping> mappings, EntryT
}

@Nullable
public static List<PackageEntry> readPackageJson(Gson gson, String path) {
public static PackageEntryList readPackageJson(Gson gson, String path) {
try {
if (path != null) {
Reader jsonReader = new FileReader(path);
List<PackageEntry> entries = gson.fromJson(jsonReader, PackageEntryList.class);
PackageEntryList entries = gson.fromJson(jsonReader, PackageEntryList.class);
for (PackageEntry entry : entries) {
// todo validate entry for invalid name
setupInheritance(entry);
}

Expand All @@ -106,19 +166,19 @@ private static void setupInheritance(PackageEntry entry) {
}
}

public static List<PackageEntry> readPackageJson(String path) {
public static PackageEntryList readPackageJson(String path) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
return readPackageJson(gson, path);
}

public static List<PackageEntry> updatePackageJson(List<PackageEntry> oldJson, EntryTree<EntryMapping> mappings) {
List<PackageEntry> newJson = createPackageJson(mappings);
public static PackageEntryList updatePackageJson(List<PackageEntry> oldJson, EntryTree<EntryMapping> mappings) {
PackageEntryList newJson = createPackageJson(mappings);

for (PackageEntry rootEntry : oldJson) {
rootEntry.forEach(oldEntry -> {
if (oldEntry.deobf != null) {
PackageEntry newEntry = null;
String oldEntryString = oldEntry.toPackageString();
String oldEntryString = oldEntry.toObfPackageString();

for (PackageEntry root : newJson) {
newEntry = root.findEntry(oldEntryString);
Expand All @@ -137,12 +197,12 @@ public static List<PackageEntry> updatePackageJson(List<PackageEntry> oldJson, E
return newJson;
}

public static List<PackageEntry> createPackageJson(EntryTree<EntryMapping> mappings) {
public static PackageEntryList createPackageJson(EntryTree<EntryMapping> mappings) {
MappingsIndex index = new MappingsIndex(new PackageIndex());
index.indexMappings(mappings, ProgressListener.createEmpty());

var packageNames = index.getIndex(PackageIndex.class).getPackageNames();
List<PackageEntry> rootPackages = new ArrayList<>();
PackageEntryList rootPackages = new PackageEntryList();

Map<Integer, List<String>> packageNamesByDepth = new HashMap<>();

Expand All @@ -158,7 +218,7 @@ public static List<PackageEntry> createPackageJson(EntryTree<EntryMapping> mappi
packageNamesByDepth.computeIfAbsent(slashes, k -> new ArrayList<>()).add(packageName);
}

for (int i = 0; i < packageNamesByDepth.keySet().stream().mapToInt(num -> num).max().getAsInt(); i++) {
for (int i = 0; i <= packageNamesByDepth.keySet().stream().mapToInt(num -> num).max().getAsInt(); i++) {
List<String> names = packageNamesByDepth.get(i);

if (i == 0) {
Expand Down Expand Up @@ -197,6 +257,16 @@ public static void writePackageJson(Path path, List<PackageEntry> entries) {
}

public static class PackageEntryList extends ArrayList<PackageEntry> {
public Optional<PackageEntry> findEntry(String obf) {
for (PackageEntry entry : this) {
var found = entry.findEntry(obf);
if (found != null) {
return Optional.of(found);
}
}

return Optional.empty();
}
}

public static class PackageEntry {
Expand Down Expand Up @@ -235,11 +305,19 @@ public PackageEntry findEntry(String obf) {
return null;
}

public String toPackageString() {
public String toObfPackageString() {
return buildPackageString(entry -> entry.obf);
}

public String toDeobfPackageString() {
return buildPackageString(entry -> entry.deobf != null && !entry.deobf.isEmpty() ? entry.deobf : entry.obf);
}

private String buildPackageString(Function<PackageEntry, String> nameGetter) {
List<String> packages = new ArrayList<>();
PackageEntry entry = this;
while (entry != null) {
packages.add(entry.obf);
packages.add(nameGetter.apply(entry));
entry = entry.parent;
}

Expand All @@ -248,7 +326,7 @@ public String toPackageString() {
}

private boolean equals(String obfPackage) {
return this.toPackageString().equals(obfPackage);
return this.toObfPackageString().equals(obfPackage);
}
}
}
75 changes: 60 additions & 15 deletions src/test/java/org/quiltmc/enigma_plugin/MojmapTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.quiltmc.enigma.api.Enigma;
import org.quiltmc.enigma.api.EnigmaProfile;
Expand Down Expand Up @@ -42,14 +41,21 @@

public class MojmapTest {
private static final Path cacheDir = Path.of("build/mojmap_cache");
private static final File cachedServerJar = cacheDir.resolve("server.jar").toFile();
private static final File cachedServerMappings = cacheDir.resolve("server-mappings.txt").toFile();
private static final Path cachedServerJar = cacheDir.resolve("server.jar");
private static final Path cachedServerMappings = cacheDir.resolve("server-mappings.txt");
private static final Path testResources = Path.of("./src/test/resources");
private static final Path mojmapTest = testResources.resolve("mojmap_test");
private static final Path emptyOverrides = mojmapTest.resolve("example_mappings_empty.json");
private static final Path overrideRenaming = mojmapTest.resolve("override_based_renaming");
private static final Path overrideRenamingOverrides = overrideRenaming.resolve("input_overrides.json");
private static final Path overrideRenamingJar = overrideRenaming.resolve("input.jar");
private static final Path overrideRenamingMappings = overrideRenaming.resolve("input.mapping");

private static EnigmaProject project;

@BeforeAll
static void downloadMojmaps() throws IOException {
if (!cachedServerJar.exists() || !cachedServerMappings.exists()) {
if (!cachedServerJar.toFile().exists() || !cachedServerMappings.toFile().exists()) {
System.out.println("Could not find server jar or mappings, downloading...");
String mcVersion = "1.21";

Expand All @@ -73,9 +79,9 @@ static void downloadMojmaps() throws IOException {
}

System.out.println("Downloading server jar...");
download(serverJar.get().getUrl(), cachedServerJar);
download(serverJar.get().getUrl(), cachedServerJar.toFile());
System.out.println("Downloading server mappings...");
download(serverMappings.get().getUrl(), cachedServerMappings);
download(serverMappings.get().getUrl(), cachedServerMappings.toFile());
}
}

Expand All @@ -93,17 +99,16 @@ private static void download(String url, File file) throws IOException {
}
}

@BeforeEach
void setupEnigma() throws IOException {
var profile = EnigmaProfile.parse(new StringReader("""
void setupEnigma(Path jarPath, Path mojmapPath, Path overridesPath) throws IOException {
String profileString = """
{
"services": {
"name_proposal": [
{
"id": "quiltmc:name_proposal/fallback",
"args": {
"mojmap_path": "./build/mojmap_cache/server-mappings.txt",
"package_name_overrides_path": "./src/test/resources/mojmap_test/package_name_overrides.json"
"mojmap_path": "{MOJMAP_PATH}",
"package_name_overrides_path": "{OVERRIDES_PATH}"
}
},
{
Expand All @@ -112,15 +117,21 @@ void setupEnigma() throws IOException {
]
}
}
"""));
""";

profileString = profileString.replace("{MOJMAP_PATH}", mojmapPath.toString());
profileString = profileString.replace("{OVERRIDES_PATH}", overridesPath.toString());

var profile = EnigmaProfile.parse(new StringReader(profileString));

var enigma = Enigma.builder().setProfile(profile).build();
project = enigma.openJar(cachedServerJar.toPath(), new ClasspathClassProvider(), ProgressListener.createEmpty());
project = enigma.openJar(jarPath, new ClasspathClassProvider(), ProgressListener.createEmpty());
}

@Test
void testStaticProposal() {
void testStaticProposal() throws IOException {
setupEnigma(cachedServerJar, cachedServerMappings, emptyOverrides);

// assert that all types propose properly
// note that mojmaps do not contain params

Expand All @@ -137,7 +148,9 @@ void testStaticProposal() {
}

@Test
void testDynamicProposal() {
void testDynamicProposal() throws IOException {
setupEnigma(cachedServerJar, cachedServerMappings, emptyOverrides);

ClassEntry a = new ClassEntry("a");
assertMapping(a, "com/mojang/math/Axis", TokenType.JAR_PROPOSED);

Expand All @@ -150,6 +163,8 @@ void testDynamicProposal() {
@SuppressWarnings("OptionalGetWithoutIsPresent")
@Test
void testOverrideGeneration() throws IOException, MappingParseException {
setupEnigma(cachedServerJar, cachedServerMappings, emptyOverrides);

Path tempFile = Files.createTempFile("temp_package_overrides", "json");
Path mappingPath = Path.of("./src/test/resources/mojmap_test/example_mappings.mapping");
var mappings = project.getEnigma().readMappings(mappingPath).get();
Expand All @@ -165,6 +180,8 @@ void testOverrideGeneration() throws IOException, MappingParseException {

@Test
void testPackageNameOverrideGenerationMojmap() throws IOException {
setupEnigma(cachedServerJar, cachedServerMappings, emptyOverrides);

Path tempFile = Files.createTempFile("temp_package_overrides_moj", "json");

var entries = MojmapNameProposer.createPackageJson(MojmapNameProposer.mojmaps);
Expand Down Expand Up @@ -196,6 +213,34 @@ void testOverrideUpdating() throws IOException, MappingParseException {
Assertions.assertEquals(expected, actual);
}

@Test
void testOverrideRenaming() throws IOException, MappingParseException {
setupEnigma(overrideRenamingJar, overrideRenamingMappings, overrideRenamingOverrides);
project.setMappings(project.getEnigma().readMappings(overrideRenamingMappings).get(), ProgressListener.createEmpty());

ClassEntry a = new ClassEntry("a");
ClassEntry b = new ClassEntry("b");
ClassEntry c = new ClassEntry("c");
ClassEntry d = new ClassEntry("d");
ClassEntry e = new ClassEntry("e");
ClassEntry f = new ClassEntry("f");
ClassEntry g = new ClassEntry("g");

assertMapping(a, "a/Class1PackageA", TokenType.DEOBFUSCATED);
assertMapping(b, "a/b_/Class2PackageAB", TokenType.DEOBFUSCATED);
assertMapping(c, "b_/a/Class3PackageBA", TokenType.DEOBFUSCATED);
assertMapping(d, "c_/a_/Class4PackageCA", TokenType.DEOBFUSCATED);
assertMapping(e, "b_/b/Class5PackageBB", TokenType.DEOBFUSCATED);
assertMapping(f, "b_/b/c_/Class6PackageBBC", TokenType.DEOBFUSCATED);
// todo test for not proposing when no overrides are present on the class
// todo no mojmap found for g?
}

@Test
void testDynamicOverrideRenaming() {
// todo
}

private void assertMapping(Entry<?> entry, String name, TokenType type) {
var mapping = project.getRemapper().getMapping(entry);
assertEquals(entry, name, mapping.targetName());
Expand Down
16 changes: 14 additions & 2 deletions src/test/resources/mojmap_test/example_mappings_empty.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
{
"obf": "b",
"deobf": "",
"children": []
"children": [
{
"obf": "c",
"deobf": "",
"children": []
}
]
}
]
},
Expand All @@ -22,7 +28,13 @@
{
"obf": "b",
"deobf": "",
"children": []
"children": [
{
"obf": "c",
"deobf": "",
"children": []
}
]
}
]
},
Expand Down
Loading

0 comments on commit 58cb956

Please sign in to comment.