From b0c154881683d6d5cb25569db32037c35498facb Mon Sep 17 00:00:00 2001 From: Juuz Date: Wed, 20 Aug 2025 20:13:18 +0300 Subject: Support multiple input jars (#553) * Support multiple input jars This is needed for FabricMC/fabric-loom#1354. * Remove unnecessary null check in GuiController.reloadAll * Remove outdated TODO--- .../enigma/network/DedicatedEnigmaServer.java | 9 ++++---- .../src/main/java/cuchaz/enigma/gui/Gui.java | 1 + .../main/java/cuchaz/enigma/gui/GuiController.java | 26 +++++++++++++--------- .../src/main/java/cuchaz/enigma/gui/Main.java | 6 ++--- .../java/cuchaz/enigma/gui/elements/MenuBar.java | 11 ++++----- enigma/src/main/java/cuchaz/enigma/Enigma.java | 24 +++++++++++++++++--- .../src/main/java/cuchaz/enigma/EnigmaProject.java | 10 ++++----- .../src/main/java/cuchaz/enigma/utils/Utils.java | 14 +++++++++--- 8 files changed, 68 insertions(+), 33 deletions(-) diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java index fbee40d..f20a9d2 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java @@ -5,6 +5,7 @@ import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; @@ -55,7 +56,7 @@ public class DedicatedEnigmaServer extends EnigmaServer { public static void main(String[] args) { OptionParser parser = new OptionParser(); - OptionSpec jarOpt = parser.accepts("jar", "Jar file to open at startup").withRequiredArg().required().withValuesConvertedBy(PathConverter.INSTANCE); + OptionSpec jarOpt = parser.accepts("jar", "Jar file to open at startup; if there are multiple jars, the order must be the same between the server and all clients").withRequiredArg().required().withValuesConvertedBy(PathConverter.INSTANCE); OptionSpec mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup").withRequiredArg().required().withValuesConvertedBy(PathConverter.INSTANCE); @@ -68,7 +69,7 @@ public class DedicatedEnigmaServer extends EnigmaServer { OptionSpec logFileOpt = parser.accepts("log", "The log file to write to").withRequiredArg().withValuesConvertedBy(PathConverter.INSTANCE).defaultsTo(Paths.get("log.txt")); OptionSet parsedArgs = parser.parse(args); - Path jar = parsedArgs.valueOf(jarOpt); + List jars = parsedArgs.valuesOf(jarOpt); Path mappingsFile = parsedArgs.valueOf(mappingsOpt); Path profileFile = parsedArgs.valueOf(profileOpt); int port = parsedArgs.valueOf(portOpt); @@ -85,12 +86,12 @@ public class DedicatedEnigmaServer extends EnigmaServer { DedicatedEnigmaServer server; try { - byte[] checksum = Utils.zipSha1(parsedArgs.valueOf(jarOpt)); + byte[] checksum = Utils.zipSha1(jars.toArray(new Path[0])); EnigmaProfile profile = EnigmaProfile.read(profileFile); Enigma enigma = Enigma.builder().setProfile(profile).build(); System.out.println("Indexing Jar..."); - EnigmaProject project = enigma.openJar(jar, new ClasspathClassProvider(), ProgressListener.none()); + EnigmaProject project = enigma.openJars(jars, new ClasspathClassProvider(), ProgressListener.none()); MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY; EntryRemapper mappings; diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java index 05146d4..21120b8 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java @@ -147,6 +147,7 @@ public class Gui { private void setupUi() { this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + this.jarFileChooser.setMultiSelectionEnabled(true); this.exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); this.exportSourceFileChooser.setAcceptAllFileFilterUsed(false); diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java index ad10abf..ece11b0 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -23,6 +23,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; import java.util.stream.Stream; import javax.swing.JOptionPane; @@ -119,20 +120,27 @@ public class GuiController implements ClientPacketHandler { return project != null && project.getMapper().isDirty(); } - public CompletableFuture openJar(final Path jarPath) { + public CompletableFuture openJar(final List jarPaths) { this.gui.onStartOpenJar(); return ProgressDialog.runOffThread(gui.getFrame(), progress -> { - project = enigma.openJar(jarPath, new ClasspathClassProvider(), progress); + project = enigma.openJars(jarPaths, new ClasspathClassProvider(), progress); indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); chp = new ClassHandleProvider(project, UiConfig.getDecompiler().service); SwingUtilities.invokeLater(() -> { - gui.onFinishOpenJar(jarPath.getFileName().toString()); + gui.onFinishOpenJar(getFileNames(jarPaths)); refreshClasses(); }); }); } + private static String getFileNames(List jarPaths) { + return jarPaths.stream() + .map(Path::getFileName) + .map(Object::toString) + .collect(Collectors.joining(", ")); + } + public void closeJar() { this.chp.destroy(); this.chp = null; @@ -241,17 +249,15 @@ public class GuiController implements ClientPacketHandler { } public void reloadAll() { - Path jarPath = this.project.getJarPath(); + List jarPaths = this.project.getJarPaths(); MappingFormat loadedMappingFormat = this.loadedMappingFormat; Path loadedMappingPath = this.loadedMappingPath; - if (jarPath != null) { - this.closeJar(); - CompletableFuture f = this.openJar(jarPath); + this.closeJar(); + CompletableFuture f = this.openJar(jarPaths); - if (loadedMappingFormat != null && loadedMappingPath != null) { - f.whenComplete((v, t) -> this.openMappings(loadedMappingFormat, loadedMappingPath)); - } + if (loadedMappingFormat != null && loadedMappingPath != null) { + f.whenComplete((v, t) -> this.openMappings(loadedMappingFormat, loadedMappingPath)); } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java index 213a5fe..43b16cd 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java @@ -38,7 +38,7 @@ public class Main { public static void main(String[] args) throws IOException { OptionParser parser = new OptionParser(); - OptionSpec jar = parser.accepts("jar", "Jar file to open at startup").withRequiredArg().withValuesConvertedBy(PathConverter.INSTANCE); + OptionSpec jar = parser.accepts("jar", "Jar file to open at startup; if there are multiple jars, the order must be the same between all collab session members").withRequiredArg().withValuesConvertedBy(PathConverter.INSTANCE); OptionSpec mappings = parser.accepts("mappings", "Mappings file to open at startup").withRequiredArg().withValuesConvertedBy(PathConverter.INSTANCE); @@ -137,8 +137,8 @@ public class Main { } if (options.has(jar)) { - Path jarPath = options.valueOf(jar); - controller.openJar(jarPath).whenComplete((v, t) -> { + List jarPaths = options.valuesOf(jar); + controller.openJar(jarPaths).whenComplete((v, t) -> { if (options.has(mappings)) { Path mappingsPath = options.valueOf(mappings); diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index d7054f7..78050bb 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; @@ -235,15 +236,15 @@ public class MenuBar { return; } - File file = d.getSelectedFile(); + File[] files = d.getSelectedFiles(); // checks if the file name is not empty - if (file != null) { - Path path = file.toPath(); + if (files.length >= 1) { + List paths = Arrays.stream(files).map(File::toPath).toList(); // checks if the file name corresponds to an existing file - if (Files.exists(path)) { - this.gui.getController().openJar(path); + if (paths.stream().allMatch(Files::exists)) { + this.gui.getController().openJar(paths); } UiConfig.setLastSelectedDir(d.getCurrentDirectory().getAbsolutePath()); diff --git a/enigma/src/main/java/cuchaz/enigma/Enigma.java b/enigma/src/main/java/cuchaz/enigma/Enigma.java index 414285d..315b5f6 100644 --- a/enigma/src/main/java/cuchaz/enigma/Enigma.java +++ b/enigma/src/main/java/cuchaz/enigma/Enigma.java @@ -57,15 +57,33 @@ public class Enigma { } public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, ProgressListener progress) throws IOException { - JarClassProvider jarClassProvider = new JarClassProvider(path); + return openJars(List.of(path), libraryClassProvider, progress); + } + + public EnigmaProject openJars(List paths, ClassProvider libraryClassProvider, ProgressListener progress) throws IOException { + ClassProvider jarClassProvider = getJarClassProvider(paths); ClassProvider classProvider = new CachingClassProvider(new CombiningClassProvider(jarClassProvider, libraryClassProvider)); - Set scope = jarClassProvider.getClassNames(); + Set scope = Set.copyOf(jarClassProvider.getClassNames()); JarIndex index = JarIndex.empty(); ClassProvider classProviderWithFrames = index.indexJar(scope, classProvider, progress); services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(scope, classProviderWithFrames, index)); - return new EnigmaProject(this, path, classProvider, index, Utils.zipSha1(path)); + return new EnigmaProject(this, paths, classProvider, index, Utils.zipSha1(paths.toArray(new Path[0]))); + } + + private ClassProvider getJarClassProvider(List jars) throws IOException { + if (jars.size() == 1) { + return new JarClassProvider(jars.get(0)); + } + + var classProviders = new ClassProvider[jars.size()]; + + for (int i = 0; i < jars.size(); i++) { + classProviders[i] = new JarClassProvider(jars.get(i)); + } + + return new CombiningClassProvider(classProviders); } public EnigmaProfile getProfile() { diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java index f9db4d1..1051f45 100644 --- a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java +++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java @@ -48,17 +48,17 @@ import cuchaz.enigma.utils.I18n; public class EnigmaProject { private final Enigma enigma; - private final Path jarPath; + private final List jarPaths; private final ClassProvider classProvider; private final JarIndex jarIndex; private final byte[] jarChecksum; private EntryRemapper mapper; - public EnigmaProject(Enigma enigma, Path jarPath, ClassProvider classProvider, JarIndex jarIndex, byte[] jarChecksum) { + public EnigmaProject(Enigma enigma, List jarPaths, ClassProvider classProvider, JarIndex jarIndex, byte[] jarChecksum) { Preconditions.checkArgument(jarChecksum.length == 20); this.enigma = enigma; - this.jarPath = jarPath; + this.jarPaths = List.copyOf(jarPaths); this.classProvider = classProvider; this.jarIndex = jarIndex; this.jarChecksum = jarChecksum; @@ -78,8 +78,8 @@ public class EnigmaProject { return enigma; } - public Path getJarPath() { - return jarPath; + public List getJarPaths() { + return jarPaths; } public ClassProvider getClassProvider() { diff --git a/enigma/src/main/java/cuchaz/enigma/utils/Utils.java b/enigma/src/main/java/cuchaz/enigma/utils/Utils.java index 081c941..a1926a8 100644 --- a/enigma/src/main/java/cuchaz/enigma/utils/Utils.java +++ b/enigma/src/main/java/cuchaz/enigma/utils/Utils.java @@ -28,6 +28,7 @@ import java.util.function.Supplier; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import com.google.common.base.Preconditions; import com.google.common.io.CharStreams; public class Utils { @@ -53,7 +54,8 @@ public class Utils { } } - public static byte[] zipSha1(Path path) throws IOException { + public static byte[] zipSha1(Path... paths) throws IOException { + Preconditions.checkArgument(paths.length >= 1, "Must provide at least one zip"); MessageDigest digest; try { @@ -63,6 +65,14 @@ public class Utils { throw new RuntimeException(e); } + for (Path path : paths) { + appendZipSha1(digest, path); + } + + return digest.digest(); + } + + private static void appendZipSha1(MessageDigest digest, Path path) throws IOException { try (ZipFile zip = new ZipFile(path.toFile())) { List entries = Collections.list(zip.entries()); // only compare classes (some implementations may not generate directory entries) @@ -83,8 +93,6 @@ public class Utils { } } } - - return digest.digest(); } public static void withLock(Lock l, Runnable op) { -- cgit v1.2.3