Enigma icon indicating copy to clipboard operation
Enigma copied to clipboard

fix NoSuchFileException when exporting sources (not jar)

Open GoldenMine0502 opened this issue 1 year ago • 11 comments

error example: java.nio.file.NoSuchFileException: /Users/taewonkim/Develop/Java/Minecraft/net/minecraft/client/gui/chat/ChatListener.java at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92) at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:106) at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111) at java.base/sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:218) at java.base/java.nio.file.spi.FileSystemProvider.newOutputStream(FileSystemProvider.java:484) at java.base/java.nio.file.Files.newOutputStream(Files.java:228) at java.base/java.nio.file.Files.newBufferedWriter(Files.java:3008) at java.base/java.nio.file.Files.newBufferedWriter(Files.java:3056) at cuchaz.enigma.EnigmaProject$ClassSource.writeTo(EnigmaProject.java:347) at cuchaz.enigma.gui.GuiController.lambda$exportSource$6(GuiController.java:269) at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183) at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) at java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290) at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:754) at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

GoldenMine0502 avatar Feb 16 '23 10:02 GoldenMine0502

Thanks for accepting the suggestion! I think it's worth adding that this fix was necessary for me to successfully export 1.19.3 sources using the CLI. Without it, exporting doesn't work at all, as explained in the linked issue.

AlexTMjugador avatar Feb 25 '23 13:02 AlexTMjugador

thanks for reviewing. my pleasure

GoldenMine0502 avatar Feb 25 '23 17:02 GoldenMine0502

Why isn't this merged by now?

ItsProfessional avatar Sep 09 '23 16:09 ItsProfessional

Pleasee

Guigui220D avatar Dec 30 '23 10:12 Guigui220D

Here's how to patch the jar release in case you need it working now:

  • Download the file from the PR: https://github.com/FabricMC/Enigma/blob/0f1e72926a17c561ace2936c091c0c195424744c/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
  • Download the jar release: https://maven.fabricmc.net/cuchaz/enigma-swing/2.3.3/
  • Compile the .java that has the patch against the .jar: javac -cp enigma-swing-2.3.3-all.jar EnigmaProject.java
  • Move the resulting "EnigmaProject$ClassSource.class" to a folder called cuchaz/enigma
  • Update the jar file: jar uf enigma-swing-2.3.3-all.jar cuchaz/enigma/EnigmaProject*.class

If no error happened you should have a patched working enigma (did that because i don't know how to recompile the entire project but it would be nice to just have a working release)

Guigui220D avatar Jan 03 '24 10:01 Guigui220D

@Guigui220D The project uses Gradle for dependency and build management, which is a fairly standard setup. A fat distribution JAR can be built locally by running ./gradlew shadowJar, which will place the output at enigma-swing/build/libs/enigma-swing-2.3.3+local-all.jar.

Anyway, from an end-user perspective, I'd recommend against using Enigma for mass source export or automated CLI usage. Clearly, it has not seen much maintenance in this area lately, some CLI commands have always been extremely memory-intensive, and frankly, there are better ways to read and tinker with Minecraft's code, thanks to higher-level modding frameworks like Fabric itself.

AlexTMjugador avatar Jan 03 '24 10:01 AlexTMjugador

@AlexTMjugador Okay thanks! I was using Enigma as it was recommended for older versions of minecraft, but I had better luck with RetroMCP

Guigui220D avatar Jan 03 '24 11:01 Guigui220D

Anyway, from an end-user perspective, I'd recommend against using Enigma for mass source export or automated CLI usage. Clearly, it has not seen much maintenance in this area lately, some CLI commands have always been extremely memory-intensive, and frankly, there are better ways to read and tinker with Minecraft's code, thanks to higher-level modding frameworks like Fabric itself.

hi, I maintain a fork of Enigma for the Quilt project (https://GitHub.com/quiltmc/enigma). I was wondering if you have any feedback on how to improve Enigma that I could implement over there. I'd recommend checking it out via cloning the project and running ./gradlew completeTestGui first, but I would love to hear your thoughts either way

ix0rai avatar Jan 03 '24 18:01 ix0rai

forgot to ping. @AlexTMjugador ^

ix0rai avatar Jan 03 '24 18:01 ix0rai

Hey @ix0rai, please excuse that it took me a bit to get back to you!

I'm happy to share some feedback about what could be improved with respect to Enigma's CLI memory usage efficiency, as I think those would be very substantial for UX. Quilt's Enigma is pretty similar to upstream in the aspects I'm going to touch on, so I hope you find them useful.

For the discussion below, I've used Enigma to decompile and deobfuscate the Minecraft 1.20.2 client JAR into readable Java code, via its decompile command, after some initial mapping set up. I use the CFR decompiler, as it provided the overall best output when I initially set Enigma up.

Something that's obvious right of the bat is that decompilation with CFR can be very memory intensive, to the point that it can get unusable for computers with 8 GiB of or less RAM. With the help of JDK Mission Control and the built-in Java Flight Recorder, I've measured that the maximum Java heap size used during decompilation on an example run took up to 4.41 GiB, which rivals much more sophisticated LLVM compiler toolchains, known for being resource-hungry. Anecdotally, I've seen even higher heap memory usages, in the ballpark of 6 GiB.

Decompiler resource usage overview

Decompiler resource usage overview

To dig deeper into why Enigma is taking so much memory while decompiling, I've inspected the object classes responsible for most heap allocations, and the stack traces of the methods that led to such allocations. A summary of the results is attached below.

Decompiler allocation breakdown summary

Decompiler allocation breakdown summary

Most byte array and string allocations are due to the decompilation process itself constructing readable source code, so at a surface it looks like there is not much optimization potential there. But such an intense HashMap usage is interesting: conceptually, we should not need to keep so many mappings in memory to decompile class trees.

Working backwards through the stack trace, I found that the EnigmaProject.JarExport#decompileStream and EnigmaProject.JarExport#exportRemappedJar methods are the root of the whole operation, and those methods are implemented in... rather memory-unconscious ways. They fetch the entire list of 7538 classes as of MC 1.20.2, and create several remapped and decompiled maps and lists of those in memory.

I believe re-engineering the decompiler to export classes in a streaming fashion, instead of by collecting them all into maps and lists, could go a long way to improve Enigma in this regard. Memory usage patterns are similar when using other decompilers, such as Procyon, so this is not an issue specific to CFR either.

Sorry for the pretty long-wided comment! Please feel free to contact me via e-mail or Discord if you have any further questions or thoughts you want to share.

AlexTMjugador avatar Jan 07 '24 23:01 AlexTMjugador

thank you for the detail, I've brought it up to the rest of the devs! do note that in quilt's enigma Vineflower is the default decompiler and we're focusing our efforts there

ix0rai avatar Jan 09 '24 04:01 ix0rai