summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Juuz2025-08-20 20:13:18 +0300
committerGravatar GitHub2025-08-20 18:13:18 +0100
commitb0c154881683d6d5cb25569db32037c35498facb (patch)
tree8d2d08c9ea90bc9910c1e4c98d715a9ee294ba65
parentOptimize JAR indexing (#552) (diff)
downloadenigma-fork-b0c154881683d6d5cb25569db32037c35498facb.tar.gz
enigma-fork-b0c154881683d6d5cb25569db32037c35498facb.tar.xz
enigma-fork-b0c154881683d6d5cb25569db32037c35498facb.zip
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
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java9
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java1
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java26
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java6
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java11
-rw-r--r--enigma/src/main/java/cuchaz/enigma/Enigma.java24
-rw-r--r--enigma/src/main/java/cuchaz/enigma/EnigmaProject.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/Utils.java14
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;
5import java.nio.file.Files; 5import java.nio.file.Files;
6import java.nio.file.Path; 6import java.nio.file.Path;
7import java.nio.file.Paths; 7import java.nio.file.Paths;
8import java.util.List;
8import java.util.concurrent.BlockingQueue; 9import java.util.concurrent.BlockingQueue;
9import java.util.concurrent.Executors; 10import java.util.concurrent.Executors;
10import java.util.concurrent.LinkedBlockingDeque; 11import java.util.concurrent.LinkedBlockingDeque;
@@ -55,7 +56,7 @@ public class DedicatedEnigmaServer extends EnigmaServer {
55 public static void main(String[] args) { 56 public static void main(String[] args) {
56 OptionParser parser = new OptionParser(); 57 OptionParser parser = new OptionParser();
57 58
58 OptionSpec<Path> jarOpt = parser.accepts("jar", "Jar file to open at startup").withRequiredArg().required().withValuesConvertedBy(PathConverter.INSTANCE); 59 OptionSpec<Path> 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);
59 60
60 OptionSpec<Path> mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup").withRequiredArg().required().withValuesConvertedBy(PathConverter.INSTANCE); 61 OptionSpec<Path> mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup").withRequiredArg().required().withValuesConvertedBy(PathConverter.INSTANCE);
61 62
@@ -68,7 +69,7 @@ public class DedicatedEnigmaServer extends EnigmaServer {
68 OptionSpec<Path> logFileOpt = parser.accepts("log", "The log file to write to").withRequiredArg().withValuesConvertedBy(PathConverter.INSTANCE).defaultsTo(Paths.get("log.txt")); 69 OptionSpec<Path> logFileOpt = parser.accepts("log", "The log file to write to").withRequiredArg().withValuesConvertedBy(PathConverter.INSTANCE).defaultsTo(Paths.get("log.txt"));
69 70
70 OptionSet parsedArgs = parser.parse(args); 71 OptionSet parsedArgs = parser.parse(args);
71 Path jar = parsedArgs.valueOf(jarOpt); 72 List<Path> jars = parsedArgs.valuesOf(jarOpt);
72 Path mappingsFile = parsedArgs.valueOf(mappingsOpt); 73 Path mappingsFile = parsedArgs.valueOf(mappingsOpt);
73 Path profileFile = parsedArgs.valueOf(profileOpt); 74 Path profileFile = parsedArgs.valueOf(profileOpt);
74 int port = parsedArgs.valueOf(portOpt); 75 int port = parsedArgs.valueOf(portOpt);
@@ -85,12 +86,12 @@ public class DedicatedEnigmaServer extends EnigmaServer {
85 DedicatedEnigmaServer server; 86 DedicatedEnigmaServer server;
86 87
87 try { 88 try {
88 byte[] checksum = Utils.zipSha1(parsedArgs.valueOf(jarOpt)); 89 byte[] checksum = Utils.zipSha1(jars.toArray(new Path[0]));
89 90
90 EnigmaProfile profile = EnigmaProfile.read(profileFile); 91 EnigmaProfile profile = EnigmaProfile.read(profileFile);
91 Enigma enigma = Enigma.builder().setProfile(profile).build(); 92 Enigma enigma = Enigma.builder().setProfile(profile).build();
92 System.out.println("Indexing Jar..."); 93 System.out.println("Indexing Jar...");
93 EnigmaProject project = enigma.openJar(jar, new ClasspathClassProvider(), ProgressListener.none()); 94 EnigmaProject project = enigma.openJars(jars, new ClasspathClassProvider(), ProgressListener.none());
94 95
95 MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY; 96 MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY;
96 EntryRemapper mappings; 97 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 {
147 147
148 private void setupUi() { 148 private void setupUi() {
149 this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 149 this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
150 this.jarFileChooser.setMultiSelectionEnabled(true);
150 151
151 this.exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 152 this.exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
152 this.exportSourceFileChooser.setAcceptAllFileFilterUsed(false); 153 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;
23import java.util.Set; 23import java.util.Set;
24import java.util.concurrent.CompletableFuture; 24import java.util.concurrent.CompletableFuture;
25import java.util.concurrent.ExecutionException; 25import java.util.concurrent.ExecutionException;
26import java.util.stream.Collectors;
26import java.util.stream.Stream; 27import java.util.stream.Stream;
27 28
28import javax.swing.JOptionPane; 29import javax.swing.JOptionPane;
@@ -119,20 +120,27 @@ public class GuiController implements ClientPacketHandler {
119 return project != null && project.getMapper().isDirty(); 120 return project != null && project.getMapper().isDirty();
120 } 121 }
121 122
122 public CompletableFuture<Void> openJar(final Path jarPath) { 123 public CompletableFuture<Void> openJar(final List<Path> jarPaths) {
123 this.gui.onStartOpenJar(); 124 this.gui.onStartOpenJar();
124 125
125 return ProgressDialog.runOffThread(gui.getFrame(), progress -> { 126 return ProgressDialog.runOffThread(gui.getFrame(), progress -> {
126 project = enigma.openJar(jarPath, new ClasspathClassProvider(), progress); 127 project = enigma.openJars(jarPaths, new ClasspathClassProvider(), progress);
127 indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); 128 indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex());
128 chp = new ClassHandleProvider(project, UiConfig.getDecompiler().service); 129 chp = new ClassHandleProvider(project, UiConfig.getDecompiler().service);
129 SwingUtilities.invokeLater(() -> { 130 SwingUtilities.invokeLater(() -> {
130 gui.onFinishOpenJar(jarPath.getFileName().toString()); 131 gui.onFinishOpenJar(getFileNames(jarPaths));
131 refreshClasses(); 132 refreshClasses();
132 }); 133 });
133 }); 134 });
134 } 135 }
135 136
137 private static String getFileNames(List<Path> jarPaths) {
138 return jarPaths.stream()
139 .map(Path::getFileName)
140 .map(Object::toString)
141 .collect(Collectors.joining(", "));
142 }
143
136 public void closeJar() { 144 public void closeJar() {
137 this.chp.destroy(); 145 this.chp.destroy();
138 this.chp = null; 146 this.chp = null;
@@ -241,17 +249,15 @@ public class GuiController implements ClientPacketHandler {
241 } 249 }
242 250
243 public void reloadAll() { 251 public void reloadAll() {
244 Path jarPath = this.project.getJarPath(); 252 List<Path> jarPaths = this.project.getJarPaths();
245 MappingFormat loadedMappingFormat = this.loadedMappingFormat; 253 MappingFormat loadedMappingFormat = this.loadedMappingFormat;
246 Path loadedMappingPath = this.loadedMappingPath; 254 Path loadedMappingPath = this.loadedMappingPath;
247 255
248 if (jarPath != null) { 256 this.closeJar();
249 this.closeJar(); 257 CompletableFuture<Void> f = this.openJar(jarPaths);
250 CompletableFuture<Void> f = this.openJar(jarPath);
251 258
252 if (loadedMappingFormat != null && loadedMappingPath != null) { 259 if (loadedMappingFormat != null && loadedMappingPath != null) {
253 f.whenComplete((v, t) -> this.openMappings(loadedMappingFormat, loadedMappingPath)); 260 f.whenComplete((v, t) -> this.openMappings(loadedMappingFormat, loadedMappingPath));
254 }
255 } 261 }
256 } 262 }
257 263
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 {
38 public static void main(String[] args) throws IOException { 38 public static void main(String[] args) throws IOException {
39 OptionParser parser = new OptionParser(); 39 OptionParser parser = new OptionParser();
40 40
41 OptionSpec<Path> jar = parser.accepts("jar", "Jar file to open at startup").withRequiredArg().withValuesConvertedBy(PathConverter.INSTANCE); 41 OptionSpec<Path> 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);
42 42
43 OptionSpec<Path> mappings = parser.accepts("mappings", "Mappings file to open at startup").withRequiredArg().withValuesConvertedBy(PathConverter.INSTANCE); 43 OptionSpec<Path> mappings = parser.accepts("mappings", "Mappings file to open at startup").withRequiredArg().withValuesConvertedBy(PathConverter.INSTANCE);
44 44
@@ -137,8 +137,8 @@ public class Main {
137 } 137 }
138 138
139 if (options.has(jar)) { 139 if (options.has(jar)) {
140 Path jarPath = options.valueOf(jar); 140 List<Path> jarPaths = options.valuesOf(jar);
141 controller.openJar(jarPath).whenComplete((v, t) -> { 141 controller.openJar(jarPaths).whenComplete((v, t) -> {
142 if (options.has(mappings)) { 142 if (options.has(mappings)) {
143 Path mappingsPath = options.valueOf(mappings); 143 Path mappingsPath = options.valueOf(mappings);
144 144
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;
7import java.nio.file.Files; 7import java.nio.file.Files;
8import java.nio.file.Path; 8import java.nio.file.Path;
9import java.util.Arrays; 9import java.util.Arrays;
10import java.util.List;
10import java.util.Locale; 11import java.util.Locale;
11import java.util.Map; 12import java.util.Map;
12import java.util.stream.Collectors; 13import java.util.stream.Collectors;
@@ -235,15 +236,15 @@ public class MenuBar {
235 return; 236 return;
236 } 237 }
237 238
238 File file = d.getSelectedFile(); 239 File[] files = d.getSelectedFiles();
239 240
240 // checks if the file name is not empty 241 // checks if the file name is not empty
241 if (file != null) { 242 if (files.length >= 1) {
242 Path path = file.toPath(); 243 List<Path> paths = Arrays.stream(files).map(File::toPath).toList();
243 244
244 // checks if the file name corresponds to an existing file 245 // checks if the file name corresponds to an existing file
245 if (Files.exists(path)) { 246 if (paths.stream().allMatch(Files::exists)) {
246 this.gui.getController().openJar(path); 247 this.gui.getController().openJar(paths);
247 } 248 }
248 249
249 UiConfig.setLastSelectedDir(d.getCurrentDirectory().getAbsolutePath()); 250 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 {
57 } 57 }
58 58
59 public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, ProgressListener progress) throws IOException { 59 public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, ProgressListener progress) throws IOException {
60 JarClassProvider jarClassProvider = new JarClassProvider(path); 60 return openJars(List.of(path), libraryClassProvider, progress);
61 }
62
63 public EnigmaProject openJars(List<Path> paths, ClassProvider libraryClassProvider, ProgressListener progress) throws IOException {
64 ClassProvider jarClassProvider = getJarClassProvider(paths);
61 ClassProvider classProvider = new CachingClassProvider(new CombiningClassProvider(jarClassProvider, libraryClassProvider)); 65 ClassProvider classProvider = new CachingClassProvider(new CombiningClassProvider(jarClassProvider, libraryClassProvider));
62 Set<String> scope = jarClassProvider.getClassNames(); 66 Set<String> scope = Set.copyOf(jarClassProvider.getClassNames());
63 67
64 JarIndex index = JarIndex.empty(); 68 JarIndex index = JarIndex.empty();
65 ClassProvider classProviderWithFrames = index.indexJar(scope, classProvider, progress); 69 ClassProvider classProviderWithFrames = index.indexJar(scope, classProvider, progress);
66 services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(scope, classProviderWithFrames, index)); 70 services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(scope, classProviderWithFrames, index));
67 71
68 return new EnigmaProject(this, path, classProvider, index, Utils.zipSha1(path)); 72 return new EnigmaProject(this, paths, classProvider, index, Utils.zipSha1(paths.toArray(new Path[0])));
73 }
74
75 private ClassProvider getJarClassProvider(List<Path> jars) throws IOException {
76 if (jars.size() == 1) {
77 return new JarClassProvider(jars.get(0));
78 }
79
80 var classProviders = new ClassProvider[jars.size()];
81
82 for (int i = 0; i < jars.size(); i++) {
83 classProviders[i] = new JarClassProvider(jars.get(i));
84 }
85
86 return new CombiningClassProvider(classProviders);
69 } 87 }
70 88
71 public EnigmaProfile getProfile() { 89 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;
48public class EnigmaProject { 48public class EnigmaProject {
49 private final Enigma enigma; 49 private final Enigma enigma;
50 50
51 private final Path jarPath; 51 private final List<Path> jarPaths;
52 private final ClassProvider classProvider; 52 private final ClassProvider classProvider;
53 private final JarIndex jarIndex; 53 private final JarIndex jarIndex;
54 private final byte[] jarChecksum; 54 private final byte[] jarChecksum;
55 55
56 private EntryRemapper mapper; 56 private EntryRemapper mapper;
57 57
58 public EnigmaProject(Enigma enigma, Path jarPath, ClassProvider classProvider, JarIndex jarIndex, byte[] jarChecksum) { 58 public EnigmaProject(Enigma enigma, List<Path> jarPaths, ClassProvider classProvider, JarIndex jarIndex, byte[] jarChecksum) {
59 Preconditions.checkArgument(jarChecksum.length == 20); 59 Preconditions.checkArgument(jarChecksum.length == 20);
60 this.enigma = enigma; 60 this.enigma = enigma;
61 this.jarPath = jarPath; 61 this.jarPaths = List.copyOf(jarPaths);
62 this.classProvider = classProvider; 62 this.classProvider = classProvider;
63 this.jarIndex = jarIndex; 63 this.jarIndex = jarIndex;
64 this.jarChecksum = jarChecksum; 64 this.jarChecksum = jarChecksum;
@@ -78,8 +78,8 @@ public class EnigmaProject {
78 return enigma; 78 return enigma;
79 } 79 }
80 80
81 public Path getJarPath() { 81 public List<Path> getJarPaths() {
82 return jarPath; 82 return jarPaths;
83 } 83 }
84 84
85 public ClassProvider getClassProvider() { 85 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;
28import java.util.zip.ZipEntry; 28import java.util.zip.ZipEntry;
29import java.util.zip.ZipFile; 29import java.util.zip.ZipFile;
30 30
31import com.google.common.base.Preconditions;
31import com.google.common.io.CharStreams; 32import com.google.common.io.CharStreams;
32 33
33public class Utils { 34public class Utils {
@@ -53,7 +54,8 @@ public class Utils {
53 } 54 }
54 } 55 }
55 56
56 public static byte[] zipSha1(Path path) throws IOException { 57 public static byte[] zipSha1(Path... paths) throws IOException {
58 Preconditions.checkArgument(paths.length >= 1, "Must provide at least one zip");
57 MessageDigest digest; 59 MessageDigest digest;
58 60
59 try { 61 try {
@@ -63,6 +65,14 @@ public class Utils {
63 throw new RuntimeException(e); 65 throw new RuntimeException(e);
64 } 66 }
65 67
68 for (Path path : paths) {
69 appendZipSha1(digest, path);
70 }
71
72 return digest.digest();
73 }
74
75 private static void appendZipSha1(MessageDigest digest, Path path) throws IOException {
66 try (ZipFile zip = new ZipFile(path.toFile())) { 76 try (ZipFile zip = new ZipFile(path.toFile())) {
67 List<? extends ZipEntry> entries = Collections.list(zip.entries()); 77 List<? extends ZipEntry> entries = Collections.list(zip.entries());
68 // only compare classes (some implementations may not generate directory entries) 78 // only compare classes (some implementations may not generate directory entries)
@@ -83,8 +93,6 @@ public class Utils {
83 } 93 }
84 } 94 }
85 } 95 }
86
87 return digest.digest();
88 } 96 }
89 97
90 public static void withLock(Lock l, Runnable op) { 98 public static void withLock(Lock l, Runnable op) {