diff --git a/build.gradle b/build.gradle index 34424ab..357bdb4 100644 --- a/build.gradle +++ b/build.gradle @@ -71,9 +71,8 @@ publishing { name = 'Installer Tools' description = 'A collection of command line tools that are useful for the Forge installer, that are not worth being their own standalone projects.' url = 'https://github.com/MinecraftForge/InstallerTools' - PomUtils.setGitHubDetails(pom, 'MergeTool') - PomUtils.setGitHubDetails(pom, 'MergeTool') + PomUtils.setGitHubDetails(pom, 'InstallerTools') license PomUtils.Licenses.LGPLv2_1 diff --git a/src/main/java/net/minecraftforge/installertools/BundlerExtract.java b/src/main/java/net/minecraftforge/installertools/BundlerExtract.java index 088c40c..c8d7e2b 100644 --- a/src/main/java/net/minecraftforge/installertools/BundlerExtract.java +++ b/src/main/java/net/minecraftforge/installertools/BundlerExtract.java @@ -103,7 +103,7 @@ public void process(String[] args) throws IOException { for (FileList.Entry entry : versions.entries) extractFile("versions", fs, entry, new File(output, "versions/" + entry.path)); } else { - error("Must specify either --jar only, or --all"); + error("Must specify either --jar-only, --libraries, or --all"); } } } catch (OptionException e) { diff --git a/src/main/java/net/minecraftforge/installertools/MappingsCsv.java b/src/main/java/net/minecraftforge/installertools/MappingsCsv.java new file mode 100644 index 0000000..cca0c9d --- /dev/null +++ b/src/main/java/net/minecraftforge/installertools/MappingsCsv.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.installertools; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import joptsimple.OptionException; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import net.minecraftforge.srgutils.IMappingFile; + +public class MappingsCsv extends Task { + public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + public static final long ZIPTIME = 628041600000L; + + @Override + public void process(String[] args) throws IOException { + OptionParser parser = new OptionParser(); + OptionSpec mapO = parser.accepts("srg").withRequiredArg().ofType(File.class).required(); + OptionSpec clientO = parser.accepts("client").withRequiredArg().ofType(File.class).required(); + OptionSpec serverO = parser.accepts("server").withRequiredArg().ofType(File.class).required(); + OptionSpec outputO = parser.accepts("output").withRequiredArg().ofType(File.class).required(); + + try { + OptionSet options = parser.parse(args); + + File map = options.valueOf(mapO); + File client = options.valueOf(clientO); + File server = options.valueOf(serverO); + File output = options.valueOf(outputO); + + log("SRG: " + map); + log("Client: " + client); + log("Server: " + server); + log("Output: " + output); + + if (output.exists() && !delete(output)) + error("Could not delete output file: " + output); + + File parent = output.getAbsoluteFile().getParentFile(); + if (parent != null && !parent.exists() && !parent.mkdirs()) + error("Could not make output folders: " + parent); + + if (!map.exists()) + error("SRG does not exist: " + map); + if (!client.exists()) + error("Client does not exist: " + client); + if (!server.exists()) + error("Server does not exist: " + server); + + + IMappingFile pg_client = IMappingFile.load(client); + IMappingFile pg_server = IMappingFile.load(server); + IMappingFile srg = IMappingFile.load(map); + + Map cfields = new TreeMap<>(); + Map sfields = new TreeMap<>(); + Map cmethods = new TreeMap<>(); + Map smethods = new TreeMap<>(); + gatherNames(srg, pg_client, cfields, cmethods); + gatherNames(srg, pg_server, sfields, smethods); + + String[] header = new String[] {"searge", "name", "side", "desc"}; + List fields = new ArrayList<>(); + List methods = new ArrayList<>(); + fields.add(header); + methods.add(header); + + for (String name : cfields.keySet()) { + String cname = cfields.get(name); + String sname = sfields.get(name); + if (cname.equals(sname)) { + fields.add(new String[]{name, cname, "2", ""}); + sfields.remove(name); + } else + fields.add(new String[]{name, cname, "0", ""}); + } + + for (String name : cmethods.keySet()) { + String cname = cmethods.get(name); + String sname = smethods.get(name); + if (cname.equals(sname)) { + methods.add(new String[]{name, cname, "2", ""}); + smethods.remove(name); + } else + methods.add(new String[]{name, cname, "0", ""}); + } + + sfields.forEach((k,v) -> fields.add(new String[] {k, v, "1", ""})); + smethods.forEach((k,v) -> methods.add(new String[] {k, v, "1", ""})); + + try (FileOutputStream fos = new FileOutputStream(output); + ZipOutputStream out = new ZipOutputStream(fos)) { + writeCsv("fields.csv", fields, out); + writeCsv("methods.csv", methods, out); + } + + + } catch (OptionException e) { + parser.printHelpOn(System.out); + e.printStackTrace(); + } + } + + private boolean delete(File path) throws IOException { + if (path.isDirectory()) { + return Files.walk(path.toPath()) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .map(File::delete) + .anyMatch(v -> !v); + } + return path.delete(); + } + + private void gatherNames(IMappingFile srg, IMappingFile official, Map fields, Map methods) { + for (IMappingFile.IClass cls : official.getClasses()) { + IMappingFile.IClass obf = srg.getClass(cls.getMapped()); + if (obf == null) // Class exists in official source, but doesn't make it past obfusication so it's not in our mappings. + continue; + + for (IMappingFile.IField fld : cls.getFields()) { + String name = obf.remapField(fld.getMapped()); + if (name.startsWith("field_") || name.startsWith("f_")) + fields.put(name, fld.getOriginal()); + } + + for (IMappingFile.IMethod mtd : cls.getMethods()) { + String name = obf.remapMethod(mtd.getMapped(), mtd.getMappedDescriptor()); + if (name.startsWith("func_") || name.startsWith("m_")) + methods.put(name, mtd.getOriginal()); + } + } + } + + + public static ZipEntry getStableEntry(String name) { + return getStableEntry(name, ZIPTIME); + } + + public static ZipEntry getStableEntry(String name, long time) { + TimeZone _default = TimeZone.getDefault(); + TimeZone.setDefault(GMT); + ZipEntry ret = new ZipEntry(name); + ret.setTime(time); + TimeZone.setDefault(_default); + return ret; + } + + + protected static void writeCsv(String name, List mappings, ZipOutputStream out) throws IOException { + if (mappings.size() <= 1) + return; + + out.putNextEntry(getStableEntry(name)); + + byte[] comma = ",".getBytes(StandardCharsets.UTF_8); + byte[] lf = "\n".getBytes(StandardCharsets.UTF_8); + + for (String[] row : mappings) { + for (int x = 0; x < row.length; x++) { + out.write(row[x].getBytes(StandardCharsets.UTF_8)); + if (x != row.length - 1) + out.write(comma); + } + out.write(lf); + } + out.closeEntry(); + } +} diff --git a/src/main/java/net/minecraftforge/installertools/Tasks.java b/src/main/java/net/minecraftforge/installertools/Tasks.java index ea602ed..ec2e38b 100644 --- a/src/main/java/net/minecraftforge/installertools/Tasks.java +++ b/src/main/java/net/minecraftforge/installertools/Tasks.java @@ -17,6 +17,7 @@ public enum Tasks { DOWNLOAD_MOJMAPS(DownloadMojmaps::new), EXTRACT_FILES(ExtractFiles::new), BUNDLER_EXTRACT(BundlerExtract::new), + MAPPINGS_CSV(MappingsCsv::new) ; private Supplier supplier;