diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui/GuiController.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/gui/GuiController.java | 729 |
1 files changed, 0 insertions, 729 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java deleted file mode 100644 index cccc9e8..0000000 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ /dev/null | |||
| @@ -1,729 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui; | ||
| 13 | |||
| 14 | import com.google.common.collect.Lists; | ||
| 15 | import com.google.common.util.concurrent.ThreadFactoryBuilder; | ||
| 16 | import cuchaz.enigma.Enigma; | ||
| 17 | import cuchaz.enigma.EnigmaProfile; | ||
| 18 | import cuchaz.enigma.EnigmaProject; | ||
| 19 | import cuchaz.enigma.analysis.*; | ||
| 20 | import cuchaz.enigma.api.service.ObfuscationTestService; | ||
| 21 | import cuchaz.enigma.bytecode.translators.SourceFixVisitor; | ||
| 22 | import cuchaz.enigma.config.Config; | ||
| 23 | import cuchaz.enigma.gui.dialog.ProgressDialog; | ||
| 24 | import cuchaz.enigma.gui.stats.StatsGenerator; | ||
| 25 | import cuchaz.enigma.gui.stats.StatsMember; | ||
| 26 | import cuchaz.enigma.gui.util.History; | ||
| 27 | import cuchaz.enigma.network.EnigmaClient; | ||
| 28 | import cuchaz.enigma.network.EnigmaServer; | ||
| 29 | import cuchaz.enigma.network.IntegratedEnigmaServer; | ||
| 30 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 31 | import cuchaz.enigma.network.packet.LoginC2SPacket; | ||
| 32 | import cuchaz.enigma.network.packet.Packet; | ||
| 33 | import cuchaz.enigma.source.*; | ||
| 34 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 35 | import cuchaz.enigma.translation.Translator; | ||
| 36 | import cuchaz.enigma.translation.mapping.*; | ||
| 37 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 38 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 39 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 40 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 41 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 42 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 43 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 44 | import cuchaz.enigma.utils.I18n; | ||
| 45 | import cuchaz.enigma.utils.Message; | ||
| 46 | import cuchaz.enigma.utils.ReadableToken; | ||
| 47 | import cuchaz.enigma.utils.Utils; | ||
| 48 | import org.objectweb.asm.tree.ClassNode; | ||
| 49 | |||
| 50 | import javax.annotation.Nullable; | ||
| 51 | import javax.swing.JOptionPane; | ||
| 52 | import javax.swing.SwingUtilities; | ||
| 53 | import java.awt.*; | ||
| 54 | import java.awt.event.ItemEvent; | ||
| 55 | import java.io.*; | ||
| 56 | import java.nio.file.Path; | ||
| 57 | import java.util.Collection; | ||
| 58 | import java.util.List; | ||
| 59 | import java.util.Set; | ||
| 60 | import java.util.concurrent.CompletableFuture; | ||
| 61 | import java.util.concurrent.ExecutorService; | ||
| 62 | import java.util.concurrent.Executors; | ||
| 63 | import java.util.stream.Collectors; | ||
| 64 | import java.util.stream.Stream; | ||
| 65 | |||
| 66 | public class GuiController { | ||
| 67 | private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor( | ||
| 68 | new ThreadFactoryBuilder() | ||
| 69 | .setDaemon(true) | ||
| 70 | .setNameFormat("decompiler-thread") | ||
| 71 | .build() | ||
| 72 | ); | ||
| 73 | |||
| 74 | private final Gui gui; | ||
| 75 | public final Enigma enigma; | ||
| 76 | |||
| 77 | public EnigmaProject project; | ||
| 78 | private DecompilerService decompilerService; | ||
| 79 | private Decompiler decompiler; | ||
| 80 | private IndexTreeBuilder indexTreeBuilder; | ||
| 81 | |||
| 82 | private Path loadedMappingPath; | ||
| 83 | private MappingFormat loadedMappingFormat; | ||
| 84 | |||
| 85 | private DecompiledClassSource currentSource; | ||
| 86 | private Source uncommentedSource; | ||
| 87 | |||
| 88 | private EnigmaClient client; | ||
| 89 | private EnigmaServer server; | ||
| 90 | |||
| 91 | public GuiController(Gui gui, EnigmaProfile profile) { | ||
| 92 | this.gui = gui; | ||
| 93 | this.enigma = Enigma.builder() | ||
| 94 | .setProfile(profile) | ||
| 95 | .build(); | ||
| 96 | |||
| 97 | decompilerService = Config.getInstance().decompiler.service; | ||
| 98 | } | ||
| 99 | |||
| 100 | public boolean isDirty() { | ||
| 101 | return project != null && project.getMapper().isDirty(); | ||
| 102 | } | ||
| 103 | |||
| 104 | public CompletableFuture<Void> openJar(final Path jarPath) { | ||
| 105 | this.gui.onStartOpenJar(); | ||
| 106 | |||
| 107 | return ProgressDialog.runOffThread(gui.getFrame(), progress -> { | ||
| 108 | project = enigma.openJar(jarPath, progress); | ||
| 109 | indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); | ||
| 110 | decompiler = createDecompiler(); | ||
| 111 | gui.onFinishOpenJar(jarPath.getFileName().toString()); | ||
| 112 | refreshClasses(); | ||
| 113 | }); | ||
| 114 | } | ||
| 115 | |||
| 116 | private Decompiler createDecompiler() { | ||
| 117 | return decompilerService.create(name -> { | ||
| 118 | ClassNode node = project.getClassCache().getClassNode(name); | ||
| 119 | |||
| 120 | if (node == null) { | ||
| 121 | return null; | ||
| 122 | } | ||
| 123 | |||
| 124 | ClassNode fixedNode = new ClassNode(); | ||
| 125 | node.accept(new SourceFixVisitor(Utils.ASM_VERSION, fixedNode, project.getJarIndex())); | ||
| 126 | return fixedNode; | ||
| 127 | }, new SourceSettings(true, true)); | ||
| 128 | } | ||
| 129 | |||
| 130 | public void closeJar() { | ||
| 131 | this.project = null; | ||
| 132 | this.gui.onCloseJar(); | ||
| 133 | } | ||
| 134 | |||
| 135 | public CompletableFuture<Void> openMappings(MappingFormat format, Path path) { | ||
| 136 | if (project == null) return CompletableFuture.completedFuture(null); | ||
| 137 | |||
| 138 | gui.setMappingsFile(path); | ||
| 139 | |||
| 140 | return ProgressDialog.runOffThread(gui.getFrame(), progress -> { | ||
| 141 | try { | ||
| 142 | MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); | ||
| 143 | |||
| 144 | EntryTree<EntryMapping> mappings = format.read(path, progress, saveParameters); | ||
| 145 | project.setMappings(mappings); | ||
| 146 | |||
| 147 | loadedMappingFormat = format; | ||
| 148 | loadedMappingPath = path; | ||
| 149 | |||
| 150 | refreshClasses(); | ||
| 151 | refreshCurrentClass(); | ||
| 152 | } catch (MappingParseException e) { | ||
| 153 | JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage()); | ||
| 154 | } | ||
| 155 | }); | ||
| 156 | } | ||
| 157 | |||
| 158 | public void openMappings(EntryTree<EntryMapping> mappings) { | ||
| 159 | if (project == null) return; | ||
| 160 | |||
| 161 | project.setMappings(mappings); | ||
| 162 | refreshClasses(); | ||
| 163 | refreshCurrentClass(); | ||
| 164 | } | ||
| 165 | |||
| 166 | public CompletableFuture<Void> saveMappings(Path path) { | ||
| 167 | return saveMappings(path, loadedMappingFormat); | ||
| 168 | } | ||
| 169 | |||
| 170 | public CompletableFuture<Void> saveMappings(Path path, MappingFormat format) { | ||
| 171 | if (project == null) return CompletableFuture.completedFuture(null); | ||
| 172 | |||
| 173 | return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { | ||
| 174 | EntryRemapper mapper = project.getMapper(); | ||
| 175 | MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); | ||
| 176 | |||
| 177 | MappingDelta<EntryMapping> delta = mapper.takeMappingDelta(); | ||
| 178 | boolean saveAll = !path.equals(loadedMappingPath); | ||
| 179 | |||
| 180 | loadedMappingFormat = format; | ||
| 181 | loadedMappingPath = path; | ||
| 182 | |||
| 183 | if (saveAll) { | ||
| 184 | format.write(mapper.getObfToDeobf(), path, progress, saveParameters); | ||
| 185 | } else { | ||
| 186 | format.write(mapper.getObfToDeobf(), delta, path, progress, saveParameters); | ||
| 187 | } | ||
| 188 | }); | ||
| 189 | } | ||
| 190 | |||
| 191 | public void closeMappings() { | ||
| 192 | if (project == null) return; | ||
| 193 | |||
| 194 | project.setMappings(null); | ||
| 195 | |||
| 196 | this.gui.setMappingsFile(null); | ||
| 197 | refreshClasses(); | ||
| 198 | refreshCurrentClass(); | ||
| 199 | } | ||
| 200 | |||
| 201 | public CompletableFuture<Void> dropMappings() { | ||
| 202 | if (project == null) return CompletableFuture.completedFuture(null); | ||
| 203 | |||
| 204 | return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> project.dropMappings(progress)); | ||
| 205 | } | ||
| 206 | |||
| 207 | public CompletableFuture<Void> exportSource(final Path path) { | ||
| 208 | if (project == null) return CompletableFuture.completedFuture(null); | ||
| 209 | |||
| 210 | return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { | ||
| 211 | EnigmaProject.JarExport jar = project.exportRemappedJar(progress); | ||
| 212 | EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService); | ||
| 213 | |||
| 214 | source.write(path, progress); | ||
| 215 | }); | ||
| 216 | } | ||
| 217 | |||
| 218 | public CompletableFuture<Void> exportJar(final Path path) { | ||
| 219 | if (project == null) return CompletableFuture.completedFuture(null); | ||
| 220 | |||
| 221 | return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { | ||
| 222 | EnigmaProject.JarExport jar = project.exportRemappedJar(progress); | ||
| 223 | jar.write(path, progress); | ||
| 224 | }); | ||
| 225 | } | ||
| 226 | |||
| 227 | public Token getToken(int pos) { | ||
| 228 | if (this.currentSource == null) { | ||
| 229 | return null; | ||
| 230 | } | ||
| 231 | return this.currentSource.getIndex().getReferenceToken(pos); | ||
| 232 | } | ||
| 233 | |||
| 234 | @Nullable | ||
| 235 | public EntryReference<Entry<?>, Entry<?>> getReference(Token token) { | ||
| 236 | if (this.currentSource == null) { | ||
| 237 | return null; | ||
| 238 | } | ||
| 239 | return this.currentSource.getIndex().getReference(token); | ||
| 240 | } | ||
| 241 | |||
| 242 | public ReadableToken getReadableToken(Token token) { | ||
| 243 | if (this.currentSource == null) { | ||
| 244 | return null; | ||
| 245 | } | ||
| 246 | |||
| 247 | SourceIndex index = this.currentSource.getIndex(); | ||
| 248 | return new ReadableToken( | ||
| 249 | index.getLineNumber(token.start), | ||
| 250 | index.getColumnNumber(token.start), | ||
| 251 | index.getColumnNumber(token.end) | ||
| 252 | ); | ||
| 253 | } | ||
| 254 | |||
| 255 | /** | ||
| 256 | * Navigates to the declaration with respect to navigation history | ||
| 257 | * | ||
| 258 | * @param entry the entry whose declaration will be navigated to | ||
| 259 | */ | ||
| 260 | public void openDeclaration(Entry<?> entry) { | ||
| 261 | if (entry == null) { | ||
| 262 | throw new IllegalArgumentException("Entry cannot be null!"); | ||
| 263 | } | ||
| 264 | openReference(new EntryReference<>(entry, entry.getName())); | ||
| 265 | } | ||
| 266 | |||
| 267 | /** | ||
| 268 | * Navigates to the reference with respect to navigation history | ||
| 269 | * | ||
| 270 | * @param reference the reference | ||
| 271 | */ | ||
| 272 | public void openReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 273 | if (reference == null) { | ||
| 274 | throw new IllegalArgumentException("Reference cannot be null!"); | ||
| 275 | } | ||
| 276 | if (this.gui.referenceHistory == null) { | ||
| 277 | this.gui.referenceHistory = new History<>(reference); | ||
| 278 | } else { | ||
| 279 | if (!reference.equals(this.gui.referenceHistory.getCurrent())) { | ||
| 280 | this.gui.referenceHistory.push(reference); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | setReference(reference); | ||
| 284 | } | ||
| 285 | |||
| 286 | /** | ||
| 287 | * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded. | ||
| 288 | * | ||
| 289 | * @param reference the reference | ||
| 290 | */ | ||
| 291 | private void setReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 292 | // get the reference target class | ||
| 293 | ClassEntry classEntry = reference.getLocationClassEntry(); | ||
| 294 | if (!project.isRenamable(classEntry)) { | ||
| 295 | throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!"); | ||
| 296 | } | ||
| 297 | |||
| 298 | if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) { | ||
| 299 | // deobfuscate the class, then navigate to the reference | ||
| 300 | loadClass(classEntry, () -> showReference(reference)); | ||
| 301 | } else { | ||
| 302 | showReference(reference); | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | /** | ||
| 307 | * Navigates to the reference without modifying history. Assumes the class is loaded. | ||
| 308 | * | ||
| 309 | * @param reference | ||
| 310 | */ | ||
| 311 | private void showReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 312 | Collection<Token> tokens = getTokensForReference(reference); | ||
| 313 | if (tokens.isEmpty()) { | ||
| 314 | // DEBUG | ||
| 315 | System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.currentSource.getEntry())); | ||
| 316 | } else { | ||
| 317 | this.gui.showTokens(tokens); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | public Collection<Token> getTokensForReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 322 | EntryRemapper mapper = this.project.getMapper(); | ||
| 323 | |||
| 324 | SourceIndex index = this.currentSource.getIndex(); | ||
| 325 | return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) | ||
| 326 | .stream() | ||
| 327 | .flatMap(r -> index.getReferenceTokens(r).stream()) | ||
| 328 | .collect(Collectors.toList()); | ||
| 329 | } | ||
| 330 | |||
| 331 | public void openPreviousReference() { | ||
| 332 | if (hasPreviousReference()) { | ||
| 333 | setReference(gui.referenceHistory.goBack()); | ||
| 334 | } | ||
| 335 | } | ||
| 336 | |||
| 337 | public boolean hasPreviousReference() { | ||
| 338 | return gui.referenceHistory != null && gui.referenceHistory.canGoBack(); | ||
| 339 | } | ||
| 340 | |||
| 341 | public void openNextReference() { | ||
| 342 | if (hasNextReference()) { | ||
| 343 | setReference(gui.referenceHistory.goForward()); | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | public boolean hasNextReference() { | ||
| 348 | return gui.referenceHistory != null && gui.referenceHistory.canGoForward(); | ||
| 349 | } | ||
| 350 | |||
| 351 | public void navigateTo(Entry<?> entry) { | ||
| 352 | if (!project.isRenamable(entry)) { | ||
| 353 | // entry is not in the jar. Ignore it | ||
| 354 | return; | ||
| 355 | } | ||
| 356 | openDeclaration(entry); | ||
| 357 | } | ||
| 358 | |||
| 359 | public void navigateTo(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 360 | if (!project.isRenamable(reference.getLocationClassEntry())) { | ||
| 361 | return; | ||
| 362 | } | ||
| 363 | openReference(reference); | ||
| 364 | } | ||
| 365 | |||
| 366 | private void refreshClasses() { | ||
| 367 | List<ClassEntry> obfClasses = Lists.newArrayList(); | ||
| 368 | List<ClassEntry> deobfClasses = Lists.newArrayList(); | ||
| 369 | this.addSeparatedClasses(obfClasses, deobfClasses); | ||
| 370 | this.gui.setObfClasses(obfClasses); | ||
| 371 | this.gui.setDeobfClasses(deobfClasses); | ||
| 372 | } | ||
| 373 | |||
| 374 | public void addSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) { | ||
| 375 | EntryRemapper mapper = project.getMapper(); | ||
| 376 | |||
| 377 | Collection<ClassEntry> classes = project.getJarIndex().getEntryIndex().getClasses(); | ||
| 378 | Stream<ClassEntry> visibleClasses = classes.stream() | ||
| 379 | .filter(entry -> !entry.isInnerClass()); | ||
| 380 | |||
| 381 | visibleClasses.forEach(entry -> { | ||
| 382 | ClassEntry deobfEntry = mapper.deobfuscate(entry); | ||
| 383 | |||
| 384 | List<ObfuscationTestService> obfService = enigma.getServices().get(ObfuscationTestService.TYPE); | ||
| 385 | boolean obfuscated = deobfEntry.equals(entry); | ||
| 386 | |||
| 387 | if (obfuscated && !obfService.isEmpty()) { | ||
| 388 | if (obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) { | ||
| 389 | obfuscated = false; | ||
| 390 | } | ||
| 391 | } | ||
| 392 | |||
| 393 | if (obfuscated) { | ||
| 394 | obfClasses.add(entry); | ||
| 395 | } else { | ||
| 396 | deobfClasses.add(entry); | ||
| 397 | } | ||
| 398 | }); | ||
| 399 | } | ||
| 400 | |||
| 401 | public void refreshCurrentClass() { | ||
| 402 | refreshCurrentClass(null); | ||
| 403 | } | ||
| 404 | |||
| 405 | private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 406 | refreshCurrentClass(reference, RefreshMode.MINIMAL); | ||
| 407 | } | ||
| 408 | |||
| 409 | private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference, RefreshMode mode) { | ||
| 410 | if (currentSource != null) { | ||
| 411 | if (reference == null) { | ||
| 412 | int obfSelectionStart = currentSource.getObfuscatedOffset(gui.editor.getSelectionStart()); | ||
| 413 | int obfSelectionEnd = currentSource.getObfuscatedOffset(gui.editor.getSelectionEnd()); | ||
| 414 | |||
| 415 | Rectangle viewportBounds = gui.sourceScroller.getViewport().getViewRect(); | ||
| 416 | // Here we pick an "anchor position", which we want to stay in the same vertical location on the screen after the new text has been set | ||
| 417 | int anchorModelPos = gui.editor.getSelectionStart(); | ||
| 418 | Rectangle anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos); | ||
| 419 | if (anchorViewPos.y < viewportBounds.y || anchorViewPos.y >= viewportBounds.y + viewportBounds.height) { | ||
| 420 | anchorModelPos = gui.editor.viewToModel(new Point(0, viewportBounds.y)); | ||
| 421 | anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos); | ||
| 422 | } | ||
| 423 | int obfAnchorPos = currentSource.getObfuscatedOffset(anchorModelPos); | ||
| 424 | Rectangle anchorViewPos_f = anchorViewPos; | ||
| 425 | int scrollX = gui.sourceScroller.getHorizontalScrollBar().getValue(); | ||
| 426 | |||
| 427 | loadClass(currentSource.getEntry(), () -> SwingUtilities.invokeLater(() -> { | ||
| 428 | int newAnchorModelPos = currentSource.getDeobfuscatedOffset(obfAnchorPos); | ||
| 429 | Rectangle newAnchorViewPos = Utils.safeModelToView(gui.editor, newAnchorModelPos); | ||
| 430 | int newScrollY = newAnchorViewPos.y - (anchorViewPos_f.y - viewportBounds.y); | ||
| 431 | |||
| 432 | gui.editor.select(currentSource.getDeobfuscatedOffset(obfSelectionStart), currentSource.getDeobfuscatedOffset(obfSelectionEnd)); | ||
| 433 | // Changing the selection scrolls to the caret position inside a SwingUtilities.invokeLater call, so | ||
| 434 | // we need to wrap our change to the scroll position inside another invokeLater so it happens after | ||
| 435 | // the caret's own scrolling. | ||
| 436 | SwingUtilities.invokeLater(() -> { | ||
| 437 | gui.sourceScroller.getHorizontalScrollBar().setValue(Math.min(scrollX, gui.sourceScroller.getHorizontalScrollBar().getMaximum())); | ||
| 438 | gui.sourceScroller.getVerticalScrollBar().setValue(Math.min(newScrollY, gui.sourceScroller.getVerticalScrollBar().getMaximum())); | ||
| 439 | }); | ||
| 440 | }), mode); | ||
| 441 | } else { | ||
| 442 | loadClass(currentSource.getEntry(), () -> showReference(reference), mode); | ||
| 443 | } | ||
| 444 | } | ||
| 445 | } | ||
| 446 | |||
| 447 | private void loadClass(ClassEntry classEntry, Runnable callback) { | ||
| 448 | loadClass(classEntry, callback, RefreshMode.MINIMAL); | ||
| 449 | } | ||
| 450 | |||
| 451 | private void loadClass(ClassEntry classEntry, Runnable callback, RefreshMode mode) { | ||
| 452 | ClassEntry targetClass = classEntry.getOutermostClass(); | ||
| 453 | |||
| 454 | boolean requiresDecompile = mode == RefreshMode.FULL || currentSource == null || !currentSource.getEntry().equals(targetClass); | ||
| 455 | if (requiresDecompile) { | ||
| 456 | currentSource = null; // Or the GUI may try to find a nonexistent token | ||
| 457 | gui.setEditorText(I18n.translate("info_panel.editor.class.decompiling")); | ||
| 458 | } | ||
| 459 | |||
| 460 | DECOMPILER_SERVICE.submit(() -> { | ||
| 461 | try { | ||
| 462 | if (requiresDecompile || mode == RefreshMode.JAVADOCS) { | ||
| 463 | currentSource = decompileSource(targetClass, mode == RefreshMode.JAVADOCS); | ||
| 464 | } | ||
| 465 | |||
| 466 | remapSource(project.getMapper().getDeobfuscator()); | ||
| 467 | callback.run(); | ||
| 468 | } catch (Throwable t) { | ||
| 469 | System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName()); | ||
| 470 | t.printStackTrace(System.err); | ||
| 471 | } | ||
| 472 | }); | ||
| 473 | } | ||
| 474 | |||
| 475 | private DecompiledClassSource decompileSource(ClassEntry targetClass, boolean onlyRefreshJavadocs) { | ||
| 476 | try { | ||
| 477 | if (!onlyRefreshJavadocs || currentSource == null || !currentSource.getEntry().equals(targetClass)) { | ||
| 478 | uncommentedSource = decompiler.getSource(targetClass.getFullName()); | ||
| 479 | } | ||
| 480 | |||
| 481 | Source source = uncommentedSource.addJavadocs(project.getMapper()); | ||
| 482 | |||
| 483 | if (source == null) { | ||
| 484 | gui.setEditorText(I18n.translate("info_panel.editor.class.not_found") + " " + targetClass); | ||
| 485 | return DecompiledClassSource.text(targetClass, "Unable to find class"); | ||
| 486 | } | ||
| 487 | |||
| 488 | SourceIndex index = source.index(); | ||
| 489 | index.resolveReferences(project.getMapper().getObfResolver()); | ||
| 490 | |||
| 491 | return new DecompiledClassSource(targetClass, index); | ||
| 492 | } catch (Throwable t) { | ||
| 493 | StringWriter traceWriter = new StringWriter(); | ||
| 494 | t.printStackTrace(new PrintWriter(traceWriter)); | ||
| 495 | |||
| 496 | return DecompiledClassSource.text(targetClass, traceWriter.toString()); | ||
| 497 | } | ||
| 498 | } | ||
| 499 | |||
| 500 | private void remapSource(Translator translator) { | ||
| 501 | if (currentSource == null) { | ||
| 502 | return; | ||
| 503 | } | ||
| 504 | |||
| 505 | currentSource.remapSource(project, translator); | ||
| 506 | |||
| 507 | gui.setEditorTheme(Config.getInstance().lookAndFeel); | ||
| 508 | gui.setSource(currentSource); | ||
| 509 | } | ||
| 510 | |||
| 511 | public void modifierChange(ItemEvent event) { | ||
| 512 | if (event.getStateChange() == ItemEvent.SELECTED) { | ||
| 513 | EntryRemapper mapper = project.getMapper(); | ||
| 514 | Entry<?> entry = gui.cursorReference.entry; | ||
| 515 | AccessModifier modifier = (AccessModifier) event.getItem(); | ||
| 516 | |||
| 517 | EntryMapping mapping = mapper.getDeobfMapping(entry); | ||
| 518 | if (mapping != null) { | ||
| 519 | mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier)); | ||
| 520 | } else { | ||
| 521 | mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier)); | ||
| 522 | } | ||
| 523 | |||
| 524 | refreshCurrentClass(); | ||
| 525 | } | ||
| 526 | } | ||
| 527 | |||
| 528 | public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { | ||
| 529 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 530 | ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry); | ||
| 531 | return ClassInheritanceTreeNode.findNode(rootNode, entry); | ||
| 532 | } | ||
| 533 | |||
| 534 | public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) { | ||
| 535 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 536 | return this.indexTreeBuilder.buildClassImplementations(translator, entry); | ||
| 537 | } | ||
| 538 | |||
| 539 | public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) { | ||
| 540 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 541 | MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry); | ||
| 542 | return MethodInheritanceTreeNode.findNode(rootNode, entry); | ||
| 543 | } | ||
| 544 | |||
| 545 | public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) { | ||
| 546 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 547 | List<MethodImplementationsTreeNode> rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry); | ||
| 548 | if (rootNodes.isEmpty()) { | ||
| 549 | return null; | ||
| 550 | } | ||
| 551 | if (rootNodes.size() > 1) { | ||
| 552 | System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one."); | ||
| 553 | } | ||
| 554 | return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry); | ||
| 555 | } | ||
| 556 | |||
| 557 | public ClassReferenceTreeNode getClassReferences(ClassEntry entry) { | ||
| 558 | Translator deobfuscator = project.getMapper().getDeobfuscator(); | ||
| 559 | ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry); | ||
| 560 | rootNode.load(project.getJarIndex(), true); | ||
| 561 | return rootNode; | ||
| 562 | } | ||
| 563 | |||
| 564 | public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) { | ||
| 565 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 566 | FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry); | ||
| 567 | rootNode.load(project.getJarIndex(), true); | ||
| 568 | return rootNode; | ||
| 569 | } | ||
| 570 | |||
| 571 | public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) { | ||
| 572 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 573 | MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry); | ||
| 574 | rootNode.load(project.getJarIndex(), true, recursive); | ||
| 575 | return rootNode; | ||
| 576 | } | ||
| 577 | |||
| 578 | public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) { | ||
| 579 | rename(reference, newName, refreshClassTree, true); | ||
| 580 | } | ||
| 581 | |||
| 582 | public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean jumpToReference) { | ||
| 583 | Entry<?> entry = reference.getNameableEntry(); | ||
| 584 | project.getMapper().mapFromObf(entry, new EntryMapping(newName)); | ||
| 585 | |||
| 586 | if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) | ||
| 587 | this.gui.moveClassTree(reference, newName); | ||
| 588 | |||
| 589 | refreshCurrentClass(jumpToReference ? reference : null); | ||
| 590 | } | ||
| 591 | |||
| 592 | public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 593 | removeMapping(reference, true); | ||
| 594 | } | ||
| 595 | |||
| 596 | public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) { | ||
| 597 | project.getMapper().removeByObf(reference.getNameableEntry()); | ||
| 598 | |||
| 599 | if (reference.entry instanceof ClassEntry) | ||
| 600 | this.gui.moveClassTree(reference, false, true); | ||
| 601 | refreshCurrentClass(jumpToReference ? reference : null); | ||
| 602 | } | ||
| 603 | |||
| 604 | public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) { | ||
| 605 | changeDocs(reference, updatedDocs, true); | ||
| 606 | } | ||
| 607 | |||
| 608 | public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean jumpToReference) { | ||
| 609 | changeDoc(reference.entry, Utils.isBlank(updatedDocs) ? null : updatedDocs); | ||
| 610 | |||
| 611 | refreshCurrentClass(jumpToReference ? reference : null, RefreshMode.JAVADOCS); | ||
| 612 | } | ||
| 613 | |||
| 614 | private void changeDoc(Entry<?> obfEntry, String newDoc) { | ||
| 615 | EntryRemapper mapper = project.getMapper(); | ||
| 616 | if (mapper.getDeobfMapping(obfEntry) == null) { | ||
| 617 | markAsDeobfuscated(obfEntry, false); // NPE | ||
| 618 | } | ||
| 619 | mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false); | ||
| 620 | } | ||
| 621 | |||
| 622 | private void markAsDeobfuscated(Entry<?> obfEntry, boolean renaming) { | ||
| 623 | EntryRemapper mapper = project.getMapper(); | ||
| 624 | mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming); | ||
| 625 | } | ||
| 626 | |||
| 627 | public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 628 | markAsDeobfuscated(reference, true); | ||
| 629 | } | ||
| 630 | |||
| 631 | public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) { | ||
| 632 | EntryRemapper mapper = project.getMapper(); | ||
| 633 | Entry<?> entry = reference.getNameableEntry(); | ||
| 634 | mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName())); | ||
| 635 | |||
| 636 | if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) | ||
| 637 | this.gui.moveClassTree(reference, true, false); | ||
| 638 | |||
| 639 | refreshCurrentClass(jumpToReference ? reference : null); | ||
| 640 | } | ||
| 641 | |||
| 642 | public void openStats(Set<StatsMember> includedMembers) { | ||
| 643 | ProgressDialog.runOffThread(gui.getFrame(), progress -> { | ||
| 644 | String data = new StatsGenerator(project).generate(progress, includedMembers); | ||
| 645 | |||
| 646 | try { | ||
| 647 | File statsFile = File.createTempFile("stats", ".html"); | ||
| 648 | |||
| 649 | try (FileWriter w = new FileWriter(statsFile)) { | ||
| 650 | w.write( | ||
| 651 | Utils.readResourceToString("/stats.html") | ||
| 652 | .replace("/*data*/", data) | ||
| 653 | ); | ||
| 654 | } | ||
| 655 | |||
| 656 | Desktop.getDesktop().open(statsFile); | ||
| 657 | } catch (IOException e) { | ||
| 658 | throw new Error(e); | ||
| 659 | } | ||
| 660 | }); | ||
| 661 | } | ||
| 662 | |||
| 663 | public void setDecompiler(DecompilerService service) { | ||
| 664 | uncommentedSource = null; | ||
| 665 | decompilerService = service; | ||
| 666 | decompiler = createDecompiler(); | ||
| 667 | refreshCurrentClass(null, RefreshMode.FULL); | ||
| 668 | } | ||
| 669 | |||
| 670 | public EnigmaClient getClient() { | ||
| 671 | return client; | ||
| 672 | } | ||
| 673 | |||
| 674 | public EnigmaServer getServer() { | ||
| 675 | return server; | ||
| 676 | } | ||
| 677 | |||
| 678 | public void createClient(String username, String ip, int port, char[] password) throws IOException { | ||
| 679 | client = new EnigmaClient(this, ip, port); | ||
| 680 | client.connect(); | ||
| 681 | client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, username)); | ||
| 682 | gui.setConnectionState(ConnectionState.CONNECTED); | ||
| 683 | } | ||
| 684 | |||
| 685 | public void createServer(int port, char[] password) throws IOException { | ||
| 686 | server = new IntegratedEnigmaServer(project.getJarChecksum(), password, EntryRemapper.mapped(project.getJarIndex(), new HashEntryTree<>(project.getMapper().getObfToDeobf())), port); | ||
| 687 | server.start(); | ||
| 688 | client = new EnigmaClient(this, "127.0.0.1", port); | ||
| 689 | client.connect(); | ||
| 690 | client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, EnigmaServer.OWNER_USERNAME)); | ||
| 691 | gui.setConnectionState(ConnectionState.HOSTING); | ||
| 692 | } | ||
| 693 | |||
| 694 | public synchronized void disconnectIfConnected(String reason) { | ||
| 695 | if (client == null && server == null) { | ||
| 696 | return; | ||
| 697 | } | ||
| 698 | |||
| 699 | if (client != null) { | ||
| 700 | client.disconnect(); | ||
| 701 | } | ||
| 702 | if (server != null) { | ||
| 703 | server.stop(); | ||
| 704 | } | ||
| 705 | client = null; | ||
| 706 | server = null; | ||
| 707 | SwingUtilities.invokeLater(() -> { | ||
| 708 | if (reason != null) { | ||
| 709 | JOptionPane.showMessageDialog(gui.getFrame(), I18n.translate(reason), I18n.translate("disconnect.disconnected"), JOptionPane.INFORMATION_MESSAGE); | ||
| 710 | } | ||
| 711 | gui.setConnectionState(ConnectionState.NOT_CONNECTED); | ||
| 712 | }); | ||
| 713 | } | ||
| 714 | |||
| 715 | public void sendPacket(Packet<ServerPacketHandler> packet) { | ||
| 716 | if (client != null) { | ||
| 717 | client.sendPacket(packet); | ||
| 718 | } | ||
| 719 | } | ||
| 720 | |||
| 721 | public void addMessage(Message message) { | ||
| 722 | gui.addMessage(message); | ||
| 723 | } | ||
| 724 | |||
| 725 | public void updateUserList(List<String> users) { | ||
| 726 | gui.setUserList(users); | ||
| 727 | } | ||
| 728 | |||
| 729 | } | ||