diff options
| author | 2015-03-09 12:53:11 -0400 | |
|---|---|---|
| committer | 2015-03-09 12:53:11 -0400 | |
| commit | d6b2a223a7973941e5e4fb45c8ceec4885891496 (patch) | |
| tree | 5728ab513d0b4ed85a720da7eb48c6591dd3f8b0 /src | |
| parent | add tracking for mismatched fields/methods (diff) | |
| download | enigma-d6b2a223a7973941e5e4fb45c8ceec4885891496.tar.gz enigma-d6b2a223a7973941e5e4fb45c8ceec4885891496.tar.xz enigma-d6b2a223a7973941e5e4fb45c8ceec4885891496.zip | |
starting on field matching gui
Diffstat (limited to 'src')
| -rw-r--r-- | src/cuchaz/enigma/ConvertMain.java | 136 | ||||
| -rw-r--r-- | src/cuchaz/enigma/convert/ClassMatches.java (renamed from src/cuchaz/enigma/convert/Matches.java) | 6 | ||||
| -rw-r--r-- | src/cuchaz/enigma/convert/FieldMatches.java | 35 | ||||
| -rw-r--r-- | src/cuchaz/enigma/convert/MappingsConverter.java | 8 | ||||
| -rw-r--r-- | src/cuchaz/enigma/convert/MatchesReader.java | 8 | ||||
| -rw-r--r-- | src/cuchaz/enigma/convert/MatchesWriter.java | 6 | ||||
| -rw-r--r-- | src/cuchaz/enigma/gui/ClassMatchingGui.java | 145 | ||||
| -rw-r--r-- | src/cuchaz/enigma/gui/CodeReader.java | 164 | ||||
| -rw-r--r-- | src/cuchaz/enigma/gui/FieldMatchingGui.java | 131 | ||||
| -rw-r--r-- | src/cuchaz/enigma/gui/Gui.java | 2 | ||||
| -rw-r--r-- | src/cuchaz/enigma/gui/GuiTricks.java | 58 |
11 files changed, 495 insertions, 204 deletions
diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java index 2afd9ca9..624eb40a 100644 --- a/src/cuchaz/enigma/ConvertMain.java +++ b/src/cuchaz/enigma/ConvertMain.java | |||
| @@ -6,14 +6,16 @@ import java.io.FileWriter; | |||
| 6 | import java.io.IOException; | 6 | import java.io.IOException; |
| 7 | import java.util.jar.JarFile; | 7 | import java.util.jar.JarFile; |
| 8 | 8 | ||
| 9 | import cuchaz.enigma.convert.ClassMatches; | ||
| 10 | import cuchaz.enigma.convert.FieldMatches; | ||
| 9 | import cuchaz.enigma.convert.MappingsConverter; | 11 | import cuchaz.enigma.convert.MappingsConverter; |
| 10 | import cuchaz.enigma.convert.Matches; | ||
| 11 | import cuchaz.enigma.convert.MatchesReader; | 12 | import cuchaz.enigma.convert.MatchesReader; |
| 12 | import cuchaz.enigma.convert.MatchesWriter; | 13 | import cuchaz.enigma.convert.MatchesWriter; |
| 13 | import cuchaz.enigma.gui.ClassMatchingGui; | 14 | import cuchaz.enigma.gui.ClassMatchingGui; |
| 14 | import cuchaz.enigma.gui.ClassMatchingGui.SaveListener; | 15 | import cuchaz.enigma.gui.FieldMatchingGui; |
| 15 | import cuchaz.enigma.mapping.MappingParseException; | 16 | import cuchaz.enigma.mapping.MappingParseException; |
| 16 | import cuchaz.enigma.mapping.Mappings; | 17 | import cuchaz.enigma.mapping.Mappings; |
| 18 | import cuchaz.enigma.mapping.MappingsChecker; | ||
| 17 | import cuchaz.enigma.mapping.MappingsReader; | 19 | import cuchaz.enigma.mapping.MappingsReader; |
| 18 | import cuchaz.enigma.mapping.MappingsWriter; | 20 | import cuchaz.enigma.mapping.MappingsWriter; |
| 19 | 21 | ||
| @@ -30,11 +32,13 @@ public class ConvertMain { | |||
| 30 | File inMappingsFile = new File("../Enigma Mappings/1.8.mappings"); | 32 | File inMappingsFile = new File("../Enigma Mappings/1.8.mappings"); |
| 31 | File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings"); | 33 | File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings"); |
| 32 | Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); | 34 | Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); |
| 33 | File matchingFile = new File(inMappingsFile.getName() + ".matching"); | 35 | File classMatchingFile = new File(inMappingsFile.getName() + ".class.matching"); |
| 36 | File fieldMatchingFile = new File(inMappingsFile.getName() + ".field.matching"); | ||
| 34 | 37 | ||
| 35 | //computeMatches(matchingFile, sourceJar, destJar, mappings); | 38 | //computeMatches(classMatchingFile, sourceJar, destJar, mappings); |
| 36 | editMatches(matchingFile, sourceJar, destJar, mappings); | 39 | //editClasssMatches(classMatchingFile, sourceJar, destJar, mappings); |
| 37 | //convertMappings(outMappingsFile, sourceJar, destJar, mappings, matchingFile); | 40 | //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchingFile); |
| 41 | editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchingFile, fieldMatchingFile); | ||
| 38 | 42 | ||
| 39 | /* TODO | 43 | /* TODO |
| 40 | // write out the converted mappings | 44 | // write out the converted mappings |
| @@ -45,28 +49,25 @@ public class ConvertMain { | |||
| 45 | */ | 49 | */ |
| 46 | } | 50 | } |
| 47 | 51 | ||
| 48 | private static void computeMatches(File matchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) | 52 | private static void computeMatches(File classMatchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) |
| 49 | throws IOException { | 53 | throws IOException { |
| 50 | Matches matches = MappingsConverter.computeMatches(sourceJar, destJar, mappings); | 54 | ClassMatches classMatches = MappingsConverter.computeMatches(sourceJar, destJar, mappings); |
| 51 | MatchesWriter.write(matches, matchingFile); | 55 | MatchesWriter.writeClasses(classMatches, classMatchingFile); |
| 52 | System.out.println("Wrote:\n\t" + matchingFile.getAbsolutePath()); | 56 | System.out.println("Wrote:\n\t" + classMatchingFile.getAbsolutePath()); |
| 53 | } | 57 | } |
| 54 | 58 | ||
| 55 | private static void editMatches(final File matchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) | 59 | private static void editClasssMatches(final File classMatchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings) |
| 56 | throws IOException { | 60 | throws IOException { |
| 57 | System.out.println("Reading matches..."); | 61 | System.out.println("Reading matches..."); |
| 58 | Matches matches = MatchesReader.read(matchingFile); | 62 | ClassMatches classMatches = MatchesReader.readClasses(classMatchingFile); |
| 59 | System.out.println("Indexing source jar..."); | 63 | Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); |
| 60 | Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar); | 64 | deobfuscators.source.setMappings(mappings); |
| 61 | sourceDeobfuscator.setMappings(mappings); | ||
| 62 | System.out.println("Indexing dest jar..."); | ||
| 63 | Deobfuscator destDeobfuscator = new Deobfuscator(destJar); | ||
| 64 | System.out.println("Starting GUI..."); | 65 | System.out.println("Starting GUI..."); |
| 65 | new ClassMatchingGui(matches, sourceDeobfuscator, destDeobfuscator).setSaveListener(new SaveListener() { | 66 | new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new ClassMatchingGui.SaveListener() { |
| 66 | @Override | 67 | @Override |
| 67 | public void save(Matches matches) { | 68 | public void save(ClassMatches matches) { |
| 68 | try { | 69 | try { |
| 69 | MatchesWriter.write(matches, matchingFile); | 70 | MatchesWriter.writeClasses(matches, classMatchingFile); |
| 70 | } catch (IOException ex) { | 71 | } catch (IOException ex) { |
| 71 | throw new Error(ex); | 72 | throw new Error(ex); |
| 72 | } | 73 | } |
| @@ -74,21 +75,100 @@ public class ConvertMain { | |||
| 74 | }); | 75 | }); |
| 75 | } | 76 | } |
| 76 | 77 | ||
| 77 | private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File matchingFile) | 78 | private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchingFile) |
| 78 | throws IOException { | 79 | throws IOException { |
| 79 | System.out.println("Reading matches..."); | 80 | System.out.println("Reading matches..."); |
| 80 | Matches matches = MatchesReader.read(matchingFile); | 81 | ClassMatches classMatches = MatchesReader.readClasses(classMatchingFile); |
| 81 | System.out.println("Indexing source jar..."); | 82 | Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); |
| 82 | Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar); | 83 | deobfuscators.source.setMappings(mappings); |
| 83 | sourceDeobfuscator.setMappings(mappings); | ||
| 84 | System.out.println("Indexing dest jar..."); | ||
| 85 | Deobfuscator destDeobfuscator = new Deobfuscator(destJar); | ||
| 86 | 84 | ||
| 87 | Mappings newMappings = MappingsConverter.newMappings(matches, mappings, sourceDeobfuscator, destDeobfuscator); | 85 | Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.source); |
| 88 | 86 | ||
| 89 | try (FileWriter out = new FileWriter(outMappingsFile)) { | 87 | try (FileWriter out = new FileWriter(outMappingsFile)) { |
| 90 | new MappingsWriter().write(out, newMappings); | 88 | new MappingsWriter().write(out, newMappings); |
| 91 | } | 89 | } |
| 92 | System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath()); | 90 | System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath()); |
| 93 | } | 91 | } |
| 92 | |||
| 93 | private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchingFile, final File fieldMatchingFile) | ||
| 94 | throws IOException, MappingParseException { | ||
| 95 | |||
| 96 | System.out.println("Reading matches..."); | ||
| 97 | ClassMatches classMatches = MatchesReader.readClasses(classMatchingFile); | ||
| 98 | FieldMatches fieldMatches; | ||
| 99 | if (fieldMatchingFile.exists() /* TEMP */ && false) { | ||
| 100 | // TODO | ||
| 101 | //fieldMatches = MatchesReader.readFields(fieldMatchingFile); | ||
| 102 | } else { | ||
| 103 | fieldMatches = new FieldMatches(); | ||
| 104 | } | ||
| 105 | |||
| 106 | // prep deobfuscators | ||
| 107 | Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar); | ||
| 108 | deobfuscators.source.setMappings(sourceMappings); | ||
| 109 | Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile)); | ||
| 110 | MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex()); | ||
| 111 | checker.dropBrokenMappings(destMappings); | ||
| 112 | deobfuscators.dest.setMappings(destMappings); | ||
| 113 | |||
| 114 | new FieldMatchingGui(classMatches, fieldMatches, checker.getDroppedFieldMappings(), deobfuscators.source, deobfuscators.dest).setSaveListener(new FieldMatchingGui.SaveListener() { | ||
| 115 | @Override | ||
| 116 | public void save(FieldMatches matches) { | ||
| 117 | /* TODO | ||
| 118 | try { | ||
| 119 | MatchesWriter.writeFields(matches, fieldMatchingFile); | ||
| 120 | } catch (IOException ex) { | ||
| 121 | throw new Error(ex); | ||
| 122 | } | ||
| 123 | */ | ||
| 124 | } | ||
| 125 | }); | ||
| 126 | } | ||
| 127 | |||
| 128 | private static class Deobfuscators { | ||
| 129 | |||
| 130 | public Deobfuscator source; | ||
| 131 | public Deobfuscator dest; | ||
| 132 | |||
| 133 | public Deobfuscators(JarFile sourceJar, JarFile destJar) { | ||
| 134 | System.out.println("Indexing source jar..."); | ||
| 135 | IndexerThread sourceIndexer = new IndexerThread(sourceJar); | ||
| 136 | sourceIndexer.start(); | ||
| 137 | System.out.println("Indexing dest jar..."); | ||
| 138 | IndexerThread destIndexer = new IndexerThread(destJar); | ||
| 139 | destIndexer.start(); | ||
| 140 | sourceIndexer.joinOrBail(); | ||
| 141 | destIndexer.joinOrBail(); | ||
| 142 | source = sourceIndexer.deobfuscator; | ||
| 143 | dest = destIndexer.deobfuscator; | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | private static class IndexerThread extends Thread { | ||
| 148 | |||
| 149 | private JarFile m_jarFile; | ||
| 150 | public Deobfuscator deobfuscator; | ||
| 151 | |||
| 152 | public IndexerThread(JarFile jarFile) { | ||
| 153 | m_jarFile = jarFile; | ||
| 154 | deobfuscator = null; | ||
| 155 | } | ||
| 156 | |||
| 157 | public void joinOrBail() { | ||
| 158 | try { | ||
| 159 | join(); | ||
| 160 | } catch (InterruptedException ex) { | ||
| 161 | throw new Error(ex); | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | @Override | ||
| 166 | public void run() { | ||
| 167 | try { | ||
| 168 | deobfuscator = new Deobfuscator(m_jarFile); | ||
| 169 | } catch (IOException ex) { | ||
| 170 | throw new Error(ex); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 94 | } | 174 | } |
diff --git a/src/cuchaz/enigma/convert/Matches.java b/src/cuchaz/enigma/convert/ClassMatches.java index 19bb155f..f8b2afdc 100644 --- a/src/cuchaz/enigma/convert/Matches.java +++ b/src/cuchaz/enigma/convert/ClassMatches.java | |||
| @@ -14,7 +14,7 @@ import com.google.common.collect.Sets; | |||
| 14 | import cuchaz.enigma.mapping.ClassEntry; | 14 | import cuchaz.enigma.mapping.ClassEntry; |
| 15 | 15 | ||
| 16 | 16 | ||
| 17 | public class Matches implements Iterable<ClassMatch> { | 17 | public class ClassMatches implements Iterable<ClassMatch> { |
| 18 | 18 | ||
| 19 | Collection<ClassMatch> m_matches; | 19 | Collection<ClassMatch> m_matches; |
| 20 | Map<ClassEntry,ClassMatch> m_matchesBySource; | 20 | Map<ClassEntry,ClassMatch> m_matchesBySource; |
| @@ -25,11 +25,11 @@ public class Matches implements Iterable<ClassMatch> { | |||
| 25 | Set<ClassEntry> m_unmatchedSourceClasses; | 25 | Set<ClassEntry> m_unmatchedSourceClasses; |
| 26 | Set<ClassEntry> m_unmatchedDestClasses; | 26 | Set<ClassEntry> m_unmatchedDestClasses; |
| 27 | 27 | ||
| 28 | public Matches() { | 28 | public ClassMatches() { |
| 29 | this(new ArrayList<ClassMatch>()); | 29 | this(new ArrayList<ClassMatch>()); |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | public Matches(Collection<ClassMatch> matches) { | 32 | public ClassMatches(Collection<ClassMatch> matches) { |
| 33 | m_matches = matches; | 33 | m_matches = matches; |
| 34 | m_matchesBySource = Maps.newHashMap(); | 34 | m_matchesBySource = Maps.newHashMap(); |
| 35 | m_matchesByDest = Maps.newHashMap(); | 35 | m_matchesByDest = Maps.newHashMap(); |
diff --git a/src/cuchaz/enigma/convert/FieldMatches.java b/src/cuchaz/enigma/convert/FieldMatches.java new file mode 100644 index 00000000..f78a8f55 --- /dev/null +++ b/src/cuchaz/enigma/convert/FieldMatches.java | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | package cuchaz.enigma.convert; | ||
| 2 | |||
| 3 | import java.util.Collection; | ||
| 4 | import java.util.Set; | ||
| 5 | |||
| 6 | import com.google.common.collect.BiMap; | ||
| 7 | import com.google.common.collect.HashBiMap; | ||
| 8 | import com.google.common.collect.Sets; | ||
| 9 | |||
| 10 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 11 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 12 | |||
| 13 | |||
| 14 | public class FieldMatches { | ||
| 15 | |||
| 16 | private BiMap<FieldEntry,FieldEntry> m_matches; | ||
| 17 | private Set<FieldEntry> m_unmatchedSourceFields; | ||
| 18 | |||
| 19 | public FieldMatches() { | ||
| 20 | m_matches = HashBiMap.create(); | ||
| 21 | m_unmatchedSourceFields = Sets.newHashSet(); | ||
| 22 | } | ||
| 23 | |||
| 24 | public void addUnmatchedSourceFields(Set<FieldEntry> fieldEntries) { | ||
| 25 | m_unmatchedSourceFields.addAll(fieldEntries); | ||
| 26 | } | ||
| 27 | |||
| 28 | public Collection<ClassEntry> getSourceClassesWithUnmatchedFields() { | ||
| 29 | Set<ClassEntry> classEntries = Sets.newHashSet(); | ||
| 30 | for (FieldEntry fieldEntry : m_unmatchedSourceFields) { | ||
| 31 | classEntries.add(fieldEntry.getClassEntry()); | ||
| 32 | } | ||
| 33 | return classEntries; | ||
| 34 | } | ||
| 35 | } | ||
diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index 5883878c..667ee9de 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java | |||
| @@ -37,7 +37,7 @@ import cuchaz.enigma.mapping.MethodMapping; | |||
| 37 | 37 | ||
| 38 | public class MappingsConverter { | 38 | public class MappingsConverter { |
| 39 | 39 | ||
| 40 | public static Matches computeMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { | 40 | public static ClassMatches computeMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { |
| 41 | 41 | ||
| 42 | // index jars | 42 | // index jars |
| 43 | System.out.println("Indexing source jar..."); | 43 | System.out.println("Indexing source jar..."); |
| @@ -49,7 +49,7 @@ public class MappingsConverter { | |||
| 49 | 49 | ||
| 50 | // compute the matching | 50 | // compute the matching |
| 51 | ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); | 51 | ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); |
| 52 | return new Matches(matching.matches()); | 52 | return new ClassMatches(matching.matches()); |
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry,ClassEntry> knownMatches) { | 55 | public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry,ClassEntry> knownMatches) { |
| @@ -115,7 +115,7 @@ public class MappingsConverter { | |||
| 115 | return lastMatching; | 115 | return lastMatching; |
| 116 | } | 116 | } |
| 117 | 117 | ||
| 118 | public static Mappings newMappings(Matches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { | 118 | public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { |
| 119 | 119 | ||
| 120 | // sort the unique matches by size of inner class chain | 120 | // sort the unique matches by size of inner class chain |
| 121 | Multimap<Integer,Entry<ClassEntry,ClassEntry>> matchesByDestChainSize = HashMultimap.create(); | 121 | Multimap<Integer,Entry<ClassEntry,ClassEntry>> matchesByDestChainSize = HashMultimap.create(); |
| @@ -172,7 +172,7 @@ public class MappingsConverter { | |||
| 172 | return newMappings; | 172 | return newMappings; |
| 173 | } | 173 | } |
| 174 | 174 | ||
| 175 | private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping mapping, final Matches matches, boolean useSimpleName) { | 175 | private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping mapping, final ClassMatches matches, boolean useSimpleName) { |
| 176 | 176 | ||
| 177 | ClassNameReplacer replacer = new ClassNameReplacer() { | 177 | ClassNameReplacer replacer = new ClassNameReplacer() { |
| 178 | @Override | 178 | @Override |
diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java index 808f8d0a..b43535cb 100644 --- a/src/cuchaz/enigma/convert/MatchesReader.java +++ b/src/cuchaz/enigma/convert/MatchesReader.java | |||
| @@ -14,19 +14,19 @@ import cuchaz.enigma.mapping.ClassEntry; | |||
| 14 | 14 | ||
| 15 | public class MatchesReader { | 15 | public class MatchesReader { |
| 16 | 16 | ||
| 17 | public static Matches read(File file) | 17 | public static ClassMatches readClasses(File file) |
| 18 | throws IOException { | 18 | throws IOException { |
| 19 | try (BufferedReader in = new BufferedReader(new FileReader(file))) { | 19 | try (BufferedReader in = new BufferedReader(new FileReader(file))) { |
| 20 | Matches matches = new Matches(); | 20 | ClassMatches matches = new ClassMatches(); |
| 21 | String line = null; | 21 | String line = null; |
| 22 | while ((line = in.readLine()) != null) { | 22 | while ((line = in.readLine()) != null) { |
| 23 | matches.add(readMatch(line)); | 23 | matches.add(readClassMatch(line)); |
| 24 | } | 24 | } |
| 25 | return matches; | 25 | return matches; |
| 26 | } | 26 | } |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | private static ClassMatch readMatch(String line) | 29 | private static ClassMatch readClassMatch(String line) |
| 30 | throws IOException { | 30 | throws IOException { |
| 31 | String[] sides = line.split(":", 2); | 31 | String[] sides = line.split(":", 2); |
| 32 | return new ClassMatch(readClasses(sides[0]), readClasses(sides[1])); | 32 | return new ClassMatch(readClasses(sides[0]), readClasses(sides[1])); |
diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java index 49ffb6d7..6658e2a3 100644 --- a/src/cuchaz/enigma/convert/MatchesWriter.java +++ b/src/cuchaz/enigma/convert/MatchesWriter.java | |||
| @@ -9,16 +9,16 @@ import cuchaz.enigma.mapping.ClassEntry; | |||
| 9 | 9 | ||
| 10 | public class MatchesWriter { | 10 | public class MatchesWriter { |
| 11 | 11 | ||
| 12 | public static void write(Matches matches, File file) | 12 | public static void writeClasses(ClassMatches matches, File file) |
| 13 | throws IOException { | 13 | throws IOException { |
| 14 | try (FileWriter out = new FileWriter(file)) { | 14 | try (FileWriter out = new FileWriter(file)) { |
| 15 | for (ClassMatch match : matches) { | 15 | for (ClassMatch match : matches) { |
| 16 | writeMatch(out, match); | 16 | writeClassMatch(out, match); |
| 17 | } | 17 | } |
| 18 | } | 18 | } |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | private static void writeMatch(FileWriter out, ClassMatch match) | 21 | private static void writeClassMatch(FileWriter out, ClassMatch match) |
| 22 | throws IOException { | 22 | throws IOException { |
| 23 | writeClasses(out, match.sourceClasses); | 23 | writeClasses(out, match.sourceClasses); |
| 24 | out.write(":"); | 24 | out.write(":"); |
diff --git a/src/cuchaz/enigma/gui/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java index ff7cda99..b6744515 100644 --- a/src/cuchaz/enigma/gui/ClassMatchingGui.java +++ b/src/cuchaz/enigma/gui/ClassMatchingGui.java | |||
| @@ -15,7 +15,6 @@ import javax.swing.BoxLayout; | |||
| 15 | import javax.swing.ButtonGroup; | 15 | import javax.swing.ButtonGroup; |
| 16 | import javax.swing.JButton; | 16 | import javax.swing.JButton; |
| 17 | import javax.swing.JCheckBox; | 17 | import javax.swing.JCheckBox; |
| 18 | import javax.swing.JEditorPane; | ||
| 19 | import javax.swing.JFrame; | 18 | import javax.swing.JFrame; |
| 20 | import javax.swing.JLabel; | 19 | import javax.swing.JLabel; |
| 21 | import javax.swing.JPanel; | 20 | import javax.swing.JPanel; |
| @@ -28,22 +27,18 @@ import javax.swing.WindowConstants; | |||
| 28 | import com.beust.jcommander.internal.Lists; | 27 | import com.beust.jcommander.internal.Lists; |
| 29 | import com.beust.jcommander.internal.Maps; | 28 | import com.beust.jcommander.internal.Maps; |
| 30 | import com.google.common.collect.BiMap; | 29 | import com.google.common.collect.BiMap; |
| 31 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 32 | 30 | ||
| 33 | import cuchaz.enigma.Constants; | 31 | import cuchaz.enigma.Constants; |
| 34 | import cuchaz.enigma.Deobfuscator; | 32 | import cuchaz.enigma.Deobfuscator; |
| 35 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 36 | import cuchaz.enigma.analysis.Token; | ||
| 37 | import cuchaz.enigma.convert.ClassIdentifier; | 33 | import cuchaz.enigma.convert.ClassIdentifier; |
| 38 | import cuchaz.enigma.convert.ClassIdentity; | 34 | import cuchaz.enigma.convert.ClassIdentity; |
| 39 | import cuchaz.enigma.convert.ClassMatch; | 35 | import cuchaz.enigma.convert.ClassMatch; |
| 36 | import cuchaz.enigma.convert.ClassMatches; | ||
| 40 | import cuchaz.enigma.convert.ClassMatching; | 37 | import cuchaz.enigma.convert.ClassMatching; |
| 41 | import cuchaz.enigma.convert.ClassNamer; | 38 | import cuchaz.enigma.convert.ClassNamer; |
| 42 | import cuchaz.enigma.convert.MappingsConverter; | 39 | import cuchaz.enigma.convert.MappingsConverter; |
| 43 | import cuchaz.enigma.convert.Matches; | ||
| 44 | import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; | 40 | import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; |
| 45 | import cuchaz.enigma.mapping.ClassEntry; | 41 | import cuchaz.enigma.mapping.ClassEntry; |
| 46 | import cuchaz.enigma.mapping.Entry; | ||
| 47 | import cuchaz.enigma.mapping.Mappings; | 42 | import cuchaz.enigma.mapping.Mappings; |
| 48 | import cuchaz.enigma.mapping.MappingsChecker; | 43 | import cuchaz.enigma.mapping.MappingsChecker; |
| 49 | import de.sciss.syntaxpane.DefaultSyntaxKit; | 44 | import de.sciss.syntaxpane.DefaultSyntaxKit; |
| @@ -55,21 +50,21 @@ public class ClassMatchingGui { | |||
| 55 | Matched { | 50 | Matched { |
| 56 | 51 | ||
| 57 | @Override | 52 | @Override |
| 58 | public Collection<ClassEntry> getSourceClasses(Matches matches) { | 53 | public Collection<ClassEntry> getSourceClasses(ClassMatches matches) { |
| 59 | return matches.getUniqueMatches().keySet(); | 54 | return matches.getUniqueMatches().keySet(); |
| 60 | } | 55 | } |
| 61 | }, | 56 | }, |
| 62 | Unmatched { | 57 | Unmatched { |
| 63 | 58 | ||
| 64 | @Override | 59 | @Override |
| 65 | public Collection<ClassEntry> getSourceClasses(Matches matches) { | 60 | public Collection<ClassEntry> getSourceClasses(ClassMatches matches) { |
| 66 | return matches.getUnmatchedSourceClasses(); | 61 | return matches.getUnmatchedSourceClasses(); |
| 67 | } | 62 | } |
| 68 | }, | 63 | }, |
| 69 | Ambiguous { | 64 | Ambiguous { |
| 70 | 65 | ||
| 71 | @Override | 66 | @Override |
| 72 | public Collection<ClassEntry> getSourceClasses(Matches matches) { | 67 | public Collection<ClassEntry> getSourceClasses(ClassMatches matches) { |
| 73 | return matches.getAmbiguouslyMatchedSourceClasses(); | 68 | return matches.getAmbiguouslyMatchedSourceClasses(); |
| 74 | } | 69 | } |
| 75 | }; | 70 | }; |
| @@ -82,7 +77,7 @@ public class ClassMatchingGui { | |||
| 82 | return button; | 77 | return button; |
| 83 | } | 78 | } |
| 84 | 79 | ||
| 85 | public abstract Collection<ClassEntry> getSourceClasses(Matches matches); | 80 | public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches); |
| 86 | 81 | ||
| 87 | public static SourceType getDefault() { | 82 | public static SourceType getDefault() { |
| 88 | return values()[0]; | 83 | return values()[0]; |
| @@ -90,23 +85,22 @@ public class ClassMatchingGui { | |||
| 90 | } | 85 | } |
| 91 | 86 | ||
| 92 | public static interface SaveListener { | 87 | public static interface SaveListener { |
| 93 | public void save(Matches matches); | 88 | public void save(ClassMatches matches); |
| 94 | } | 89 | } |
| 95 | 90 | ||
| 96 | // controls | 91 | // controls |
| 97 | private JFrame m_frame; | 92 | private JFrame m_frame; |
| 98 | private ClassSelector m_sourceClasses; | 93 | private ClassSelector m_sourceClasses; |
| 99 | private ClassSelector m_destClasses; | 94 | private ClassSelector m_destClasses; |
| 100 | private JEditorPane m_sourceReader; | 95 | private CodeReader m_sourceReader; |
| 101 | private JEditorPane m_destReader; | 96 | private CodeReader m_destReader; |
| 102 | private JLabel m_sourceClassLabel; | 97 | private JLabel m_sourceClassLabel; |
| 103 | private JLabel m_destClassLabel; | 98 | private JLabel m_destClassLabel; |
| 104 | private JButton m_matchButton; | 99 | private JButton m_matchButton; |
| 105 | private Map<SourceType,JRadioButton> m_sourceTypeButtons; | 100 | private Map<SourceType,JRadioButton> m_sourceTypeButtons; |
| 106 | private JCheckBox m_advanceCheck; | 101 | private JCheckBox m_advanceCheck; |
| 107 | private SelectionHighlightPainter m_selectionHighlightPainter; | ||
| 108 | 102 | ||
| 109 | private Matches m_matches; | 103 | private ClassMatches m_classMatches; |
| 110 | private Deobfuscator m_sourceDeobfuscator; | 104 | private Deobfuscator m_sourceDeobfuscator; |
| 111 | private Deobfuscator m_destDeobfuscator; | 105 | private Deobfuscator m_destDeobfuscator; |
| 112 | private ClassEntry m_sourceClass; | 106 | private ClassEntry m_sourceClass; |
| @@ -114,14 +108,14 @@ public class ClassMatchingGui { | |||
| 114 | private SourceType m_sourceType; | 108 | private SourceType m_sourceType; |
| 115 | private SaveListener m_saveListener; | 109 | private SaveListener m_saveListener; |
| 116 | 110 | ||
| 117 | public ClassMatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { | 111 | public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { |
| 118 | 112 | ||
| 119 | m_matches = matches; | 113 | m_classMatches = matches; |
| 120 | m_sourceDeobfuscator = sourceDeobfuscator; | 114 | m_sourceDeobfuscator = sourceDeobfuscator; |
| 121 | m_destDeobfuscator = destDeobfuscator; | 115 | m_destDeobfuscator = destDeobfuscator; |
| 122 | 116 | ||
| 123 | // init frame | 117 | // init frame |
| 124 | m_frame = new JFrame(Constants.Name); | 118 | m_frame = new JFrame(Constants.Name + " - Class Matcher"); |
| 125 | final Container pane = m_frame.getContentPane(); | 119 | final Container pane = m_frame.getContentPane(); |
| 126 | pane.setLayout(new BorderLayout()); | 120 | pane.setLayout(new BorderLayout()); |
| 127 | 121 | ||
| @@ -188,8 +182,8 @@ public class ClassMatchingGui { | |||
| 188 | 182 | ||
| 189 | // init source panels | 183 | // init source panels |
| 190 | DefaultSyntaxKit.initKit(); | 184 | DefaultSyntaxKit.initKit(); |
| 191 | m_sourceReader = makeReader(); | 185 | m_sourceReader = new CodeReader(); |
| 192 | m_destReader = makeReader(); | 186 | m_destReader = new CodeReader(); |
| 193 | 187 | ||
| 194 | // init all the splits | 188 | // init all the splits |
| 195 | JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader)); | 189 | JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader)); |
| @@ -238,8 +232,6 @@ public class ClassMatchingGui { | |||
| 238 | m_frame.setVisible(true); | 232 | m_frame.setVisible(true); |
| 239 | m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); | 233 | m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); |
| 240 | 234 | ||
| 241 | m_selectionHighlightPainter = new SelectionHighlightPainter(); | ||
| 242 | |||
| 243 | // init state | 235 | // init state |
| 244 | updateDestMappings(); | 236 | updateDestMappings(); |
| 245 | setSourceType(SourceType.getDefault()); | 237 | setSourceType(SourceType.getDefault()); |
| @@ -247,19 +239,6 @@ public class ClassMatchingGui { | |||
| 247 | m_saveListener = null; | 239 | m_saveListener = null; |
| 248 | } | 240 | } |
| 249 | 241 | ||
| 250 | private JEditorPane makeReader() { | ||
| 251 | |||
| 252 | JEditorPane reader = new JEditorPane(); | ||
| 253 | reader.setEditable(false); | ||
| 254 | reader.setContentType("text/java"); | ||
| 255 | |||
| 256 | // turn off token highlighting (it's wrong most of the time anyway...) | ||
| 257 | DefaultSyntaxKit kit = (DefaultSyntaxKit)reader.getEditorKit(); | ||
| 258 | kit.toggleComponent(reader, "de.sciss.syntaxpane.components.TokenMarker"); | ||
| 259 | |||
| 260 | return reader; | ||
| 261 | } | ||
| 262 | |||
| 263 | public void setSaveListener(SaveListener val) { | 242 | public void setSaveListener(SaveListener val) { |
| 264 | m_saveListener = val; | 243 | m_saveListener = val; |
| 265 | } | 244 | } |
| @@ -267,7 +246,7 @@ public class ClassMatchingGui { | |||
| 267 | private void updateDestMappings() { | 246 | private void updateDestMappings() { |
| 268 | 247 | ||
| 269 | Mappings newMappings = MappingsConverter.newMappings( | 248 | Mappings newMappings = MappingsConverter.newMappings( |
| 270 | m_matches, | 249 | m_classMatches, |
| 271 | m_sourceDeobfuscator.getMappings(), | 250 | m_sourceDeobfuscator.getMappings(), |
| 272 | m_sourceDeobfuscator, | 251 | m_sourceDeobfuscator, |
| 273 | m_destDeobfuscator | 252 | m_destDeobfuscator |
| @@ -294,13 +273,13 @@ public class ClassMatchingGui { | |||
| 294 | 273 | ||
| 295 | // show the source classes | 274 | // show the source classes |
| 296 | m_sourceType = val; | 275 | m_sourceType = val; |
| 297 | m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_matches), m_sourceDeobfuscator)); | 276 | m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator)); |
| 298 | 277 | ||
| 299 | // update counts | 278 | // update counts |
| 300 | for (SourceType sourceType : SourceType.values()) { | 279 | for (SourceType sourceType : SourceType.values()) { |
| 301 | m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", | 280 | m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)", |
| 302 | sourceType.name(), | 281 | sourceType.name(), |
| 303 | sourceType.getSourceClasses(m_matches).size() | 282 | sourceType.getSourceClasses(m_classMatches).size() |
| 304 | )); | 283 | )); |
| 305 | } | 284 | } |
| 306 | } | 285 | } |
| @@ -345,7 +324,7 @@ public class ClassMatchingGui { | |||
| 345 | if (m_sourceClass != null) { | 324 | if (m_sourceClass != null) { |
| 346 | 325 | ||
| 347 | // show the dest class(es) | 326 | // show the dest class(es) |
| 348 | ClassMatch match = m_matches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass)); | 327 | ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass)); |
| 349 | assert(match != null); | 328 | assert(match != null); |
| 350 | if (match.destClasses.isEmpty()) { | 329 | if (match.destClasses.isEmpty()) { |
| 351 | 330 | ||
| @@ -376,7 +355,12 @@ public class ClassMatchingGui { | |||
| 376 | } | 355 | } |
| 377 | 356 | ||
| 378 | setDestClass(null); | 357 | setDestClass(null); |
| 379 | decompileClass(m_sourceClass, m_sourceDeobfuscator, m_sourceReader); | 358 | m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, new Runnable() { |
| 359 | @Override | ||
| 360 | public void run() { | ||
| 361 | m_sourceReader.navigateToClassDeclaration(m_sourceClass); | ||
| 362 | } | ||
| 363 | }); | ||
| 380 | 364 | ||
| 381 | updateMatchButton(); | 365 | updateMatchButton(); |
| 382 | } | 366 | } |
| @@ -386,7 +370,7 @@ public class ClassMatchingGui { | |||
| 386 | ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); | 370 | ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass); |
| 387 | 371 | ||
| 388 | // set up identifiers | 372 | // set up identifiers |
| 389 | ClassNamer namer = new ClassNamer(m_matches.getUniqueMatches()); | 373 | ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches()); |
| 390 | ClassIdentifier sourceIdentifier = new ClassIdentifier( | 374 | ClassIdentifier sourceIdentifier = new ClassIdentifier( |
| 391 | m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), | 375 | m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), |
| 392 | namer.getSourceNamer(), true | 376 | namer.getSourceNamer(), true |
| @@ -401,7 +385,7 @@ public class ClassMatchingGui { | |||
| 401 | // rank all the unmatched dest classes against the source class | 385 | // rank all the unmatched dest classes against the source class |
| 402 | ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); | 386 | ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); |
| 403 | List<ClassEntry> scoredDestClasses = Lists.newArrayList(); | 387 | List<ClassEntry> scoredDestClasses = Lists.newArrayList(); |
| 404 | for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) { | 388 | for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) { |
| 405 | ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); | 389 | ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); |
| 406 | float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) | 390 | float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) |
| 407 | /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); | 391 | /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); |
| @@ -420,59 +404,14 @@ public class ClassMatchingGui { | |||
| 420 | m_destClass = classEntry; | 404 | m_destClass = classEntry; |
| 421 | m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : ""); | 405 | m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : ""); |
| 422 | 406 | ||
| 423 | decompileClass(m_destClass, m_destDeobfuscator, m_destReader); | 407 | m_destReader.decompileClass(m_destClass, m_destDeobfuscator, new Runnable() { |
| 424 | |||
| 425 | updateMatchButton(); | ||
| 426 | } | ||
| 427 | |||
| 428 | protected void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final JEditorPane reader) { | ||
| 429 | |||
| 430 | if (classEntry == null) { | ||
| 431 | reader.setText(null); | ||
| 432 | return; | ||
| 433 | } | ||
| 434 | |||
| 435 | reader.setText("(decompiling...)"); | ||
| 436 | |||
| 437 | // run in a separate thread to keep ui responsive | ||
| 438 | new Thread() { | ||
| 439 | @Override | 408 | @Override |
| 440 | public void run() { | 409 | public void run() { |
| 441 | 410 | m_destReader.navigateToClassDeclaration(m_destClass); | |
| 442 | // get the outermost class | ||
| 443 | ClassEntry outermostClassEntry = classEntry; | ||
| 444 | while (outermostClassEntry.isInnerClass()) { | ||
| 445 | outermostClassEntry = outermostClassEntry.getOuterClassEntry(); | ||
| 446 | } | ||
| 447 | |||
| 448 | // decompile it | ||
| 449 | CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName()); | ||
| 450 | String source = deobfuscator.getSource(sourceTree); | ||
| 451 | reader.setText(source); | ||
| 452 | SourceIndex sourceIndex = deobfuscator.getSourceIndex(sourceTree, source); | ||
| 453 | |||
| 454 | // navigate to the class declaration | ||
| 455 | Token token = sourceIndex.getDeclarationToken(classEntry); | ||
| 456 | if (token == null) { | ||
| 457 | // couldn't find the class declaration token, might be an anonymous class | ||
| 458 | // look for any declaration in that class instead | ||
| 459 | for (Entry entry : sourceIndex.declarations()) { | ||
| 460 | if (entry.getClassEntry().equals(classEntry)) { | ||
| 461 | token = sourceIndex.getDeclarationToken(entry); | ||
| 462 | break; | ||
| 463 | } | ||
| 464 | } | ||
| 465 | } | ||
| 466 | |||
| 467 | if (token != null) { | ||
| 468 | GuiTricks.navigateToToken(reader, token, m_selectionHighlightPainter); | ||
| 469 | } else { | ||
| 470 | // couldn't find anything =( | ||
| 471 | System.out.println("Unable to find declaration in source for " + classEntry); | ||
| 472 | } | ||
| 473 | |||
| 474 | } | 411 | } |
| 475 | }.start(); | 412 | }); |
| 413 | |||
| 414 | updateMatchButton(); | ||
| 476 | } | 415 | } |
| 477 | 416 | ||
| 478 | private void updateMatchButton() { | 417 | private void updateMatchButton() { |
| @@ -480,7 +419,7 @@ public class ClassMatchingGui { | |||
| 480 | ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); | 419 | ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); |
| 481 | ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); | 420 | ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); |
| 482 | 421 | ||
| 483 | BiMap<ClassEntry,ClassEntry> uniqueMatches = m_matches.getUniqueMatches(); | 422 | BiMap<ClassEntry,ClassEntry> uniqueMatches = m_classMatches.getUniqueMatches(); |
| 484 | boolean twoSelected = m_sourceClass != null && m_destClass != null; | 423 | boolean twoSelected = m_sourceClass != null && m_destClass != null; |
| 485 | boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); | 424 | boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest); |
| 486 | boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest); | 425 | boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest); |
| @@ -529,11 +468,11 @@ public class ClassMatchingGui { | |||
| 529 | ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); | 468 | ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass); |
| 530 | 469 | ||
| 531 | // remove the classes from their match | 470 | // remove the classes from their match |
| 532 | m_matches.removeSource(obfSource); | 471 | m_classMatches.removeSource(obfSource); |
| 533 | m_matches.removeDest(obfDest); | 472 | m_classMatches.removeDest(obfDest); |
| 534 | 473 | ||
| 535 | // add them as matched classes | 474 | // add them as matched classes |
| 536 | m_matches.add(new ClassMatch(obfSource, obfDest)); | 475 | m_classMatches.add(new ClassMatch(obfSource, obfDest)); |
| 537 | 476 | ||
| 538 | ClassEntry nextClass = null; | 477 | ClassEntry nextClass = null; |
| 539 | if (m_advanceCheck.isSelected()) { | 478 | if (m_advanceCheck.isSelected()) { |
| @@ -554,8 +493,8 @@ public class ClassMatchingGui { | |||
| 554 | ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); | 493 | ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass); |
| 555 | 494 | ||
| 556 | // remove the source to break the match, then add the source back as unmatched | 495 | // remove the source to break the match, then add the source back as unmatched |
| 557 | m_matches.removeSource(obfSource); | 496 | m_classMatches.removeSource(obfSource); |
| 558 | m_matches.add(new ClassMatch(obfSource, null)); | 497 | m_classMatches.add(new ClassMatch(obfSource, null)); |
| 559 | 498 | ||
| 560 | save(); | 499 | save(); |
| 561 | updateMatches(); | 500 | updateMatches(); |
| @@ -577,7 +516,7 @@ public class ClassMatchingGui { | |||
| 577 | 516 | ||
| 578 | private void save() { | 517 | private void save() { |
| 579 | if (m_saveListener != null) { | 518 | if (m_saveListener != null) { |
| 580 | m_saveListener.save(m_matches); | 519 | m_saveListener.save(m_classMatches); |
| 581 | } | 520 | } |
| 582 | } | 521 | } |
| 583 | 522 | ||
| @@ -589,15 +528,15 @@ public class ClassMatchingGui { | |||
| 589 | ClassMatching matching = MappingsConverter.computeMatching( | 528 | ClassMatching matching = MappingsConverter.computeMatching( |
| 590 | m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), | 529 | m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(), |
| 591 | m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(), | 530 | m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(), |
| 592 | m_matches.getUniqueMatches() | 531 | m_classMatches.getUniqueMatches() |
| 593 | ); | 532 | ); |
| 594 | Matches newMatches = new Matches(matching.matches()); | 533 | ClassMatches newMatches = new ClassMatches(matching.matches()); |
| 595 | System.out.println(String.format("Automatch found %d new matches", | 534 | System.out.println(String.format("Automatch found %d new matches", |
| 596 | newMatches.getUniqueMatches().size() - m_matches.getUniqueMatches().size() | 535 | newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size() |
| 597 | )); | 536 | )); |
| 598 | 537 | ||
| 599 | // update the current matches | 538 | // update the current matches |
| 600 | m_matches = newMatches; | 539 | m_classMatches = newMatches; |
| 601 | save(); | 540 | save(); |
| 602 | updateMatches(); | 541 | updateMatches(); |
| 603 | } | 542 | } |
diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java new file mode 100644 index 00000000..05feb59e --- /dev/null +++ b/src/cuchaz/enigma/gui/CodeReader.java | |||
| @@ -0,0 +1,164 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | import java.awt.Rectangle; | ||
| 4 | import java.awt.event.ActionEvent; | ||
| 5 | import java.awt.event.ActionListener; | ||
| 6 | |||
| 7 | import javax.swing.JEditorPane; | ||
| 8 | import javax.swing.SwingUtilities; | ||
| 9 | import javax.swing.Timer; | ||
| 10 | import javax.swing.text.BadLocationException; | ||
| 11 | import javax.swing.text.Highlighter.HighlightPainter; | ||
| 12 | |||
| 13 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 14 | |||
| 15 | import cuchaz.enigma.Deobfuscator; | ||
| 16 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 17 | import cuchaz.enigma.analysis.Token; | ||
| 18 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 19 | import cuchaz.enigma.mapping.Entry; | ||
| 20 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 21 | |||
| 22 | |||
| 23 | public class CodeReader extends JEditorPane { | ||
| 24 | |||
| 25 | private static final long serialVersionUID = 3673180950485748810L; | ||
| 26 | |||
| 27 | private static final Object m_lock = new Object(); | ||
| 28 | |||
| 29 | private SelectionHighlightPainter m_highlightPainter; | ||
| 30 | private SourceIndex m_sourceIndex; | ||
| 31 | |||
| 32 | public CodeReader() { | ||
| 33 | |||
| 34 | setEditable(false); | ||
| 35 | setContentType("text/java"); | ||
| 36 | |||
| 37 | // turn off token highlighting (it's wrong most of the time anyway...) | ||
| 38 | DefaultSyntaxKit kit = (DefaultSyntaxKit)getEditorKit(); | ||
| 39 | kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker"); | ||
| 40 | |||
| 41 | m_highlightPainter = new SelectionHighlightPainter(); | ||
| 42 | m_sourceIndex = null; | ||
| 43 | } | ||
| 44 | |||
| 45 | public void setCode(String code) { | ||
| 46 | // sadly, the java lexer is not thread safe, so we have to serialize all these calls | ||
| 47 | synchronized (m_lock) { | ||
| 48 | setText(code); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) { | ||
| 53 | decompileClass(classEntry, deobfuscator, null); | ||
| 54 | } | ||
| 55 | |||
| 56 | public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Runnable callback) { | ||
| 57 | |||
| 58 | if (classEntry == null) { | ||
| 59 | setCode(null); | ||
| 60 | return; | ||
| 61 | } | ||
| 62 | |||
| 63 | setCode("(decompiling...)"); | ||
| 64 | |||
| 65 | // run decompilation in a separate thread to keep ui responsive | ||
| 66 | new Thread() { | ||
| 67 | @Override | ||
| 68 | public void run() { | ||
| 69 | |||
| 70 | // get the outermost class | ||
| 71 | ClassEntry outermostClassEntry = classEntry; | ||
| 72 | while (outermostClassEntry.isInnerClass()) { | ||
| 73 | outermostClassEntry = outermostClassEntry.getOuterClassEntry(); | ||
| 74 | } | ||
| 75 | |||
| 76 | // decompile it | ||
| 77 | CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName()); | ||
| 78 | String source = deobfuscator.getSource(sourceTree); | ||
| 79 | setCode(source); | ||
| 80 | m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source); | ||
| 81 | |||
| 82 | if (callback != null) { | ||
| 83 | callback.run(); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | }.start(); | ||
| 87 | } | ||
| 88 | |||
| 89 | public void navigateToClassDeclaration(ClassEntry classEntry) { | ||
| 90 | |||
| 91 | // navigate to the class declaration | ||
| 92 | Token token = m_sourceIndex.getDeclarationToken(classEntry); | ||
| 93 | if (token == null) { | ||
| 94 | // couldn't find the class declaration token, might be an anonymous class | ||
| 95 | // look for any declaration in that class instead | ||
| 96 | for (Entry entry : m_sourceIndex.declarations()) { | ||
| 97 | if (entry.getClassEntry().equals(classEntry)) { | ||
| 98 | token = m_sourceIndex.getDeclarationToken(entry); | ||
| 99 | break; | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | if (token != null) { | ||
| 105 | navigateToToken(token); | ||
| 106 | } else { | ||
| 107 | // couldn't find anything =( | ||
| 108 | System.out.println("Unable to find declaration in source for " + classEntry); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | public void navigateToToken(final Token token) { | ||
| 113 | navigateToToken(this, token, m_highlightPainter); | ||
| 114 | } | ||
| 115 | |||
| 116 | // HACKHACK: someday we can update the main GUI to use this code reader | ||
| 117 | public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { | ||
| 118 | |||
| 119 | // set the caret position to the token | ||
| 120 | editor.setCaretPosition(token.start); | ||
| 121 | editor.grabFocus(); | ||
| 122 | |||
| 123 | try { | ||
| 124 | // make sure the token is visible in the scroll window | ||
| 125 | Rectangle start = editor.modelToView(token.start); | ||
| 126 | Rectangle end = editor.modelToView(token.end); | ||
| 127 | final Rectangle show = start.union(end); | ||
| 128 | show.grow(start.width * 10, start.height * 6); | ||
| 129 | SwingUtilities.invokeLater(new Runnable() { | ||
| 130 | @Override | ||
| 131 | public void run() { | ||
| 132 | editor.scrollRectToVisible(show); | ||
| 133 | } | ||
| 134 | }); | ||
| 135 | } catch (BadLocationException ex) { | ||
| 136 | throw new Error(ex); | ||
| 137 | } | ||
| 138 | |||
| 139 | // highlight the token momentarily | ||
| 140 | final Timer timer = new Timer(200, new ActionListener() { | ||
| 141 | private int m_counter = 0; | ||
| 142 | private Object m_highlight = null; | ||
| 143 | |||
| 144 | @Override | ||
| 145 | public void actionPerformed(ActionEvent event) { | ||
| 146 | if (m_counter % 2 == 0) { | ||
| 147 | try { | ||
| 148 | m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); | ||
| 149 | } catch (BadLocationException ex) { | ||
| 150 | // don't care | ||
| 151 | } | ||
| 152 | } else if (m_highlight != null) { | ||
| 153 | editor.getHighlighter().removeHighlight(m_highlight); | ||
| 154 | } | ||
| 155 | |||
| 156 | if (m_counter++ > 6) { | ||
| 157 | Timer timer = (Timer)event.getSource(); | ||
| 158 | timer.stop(); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | }); | ||
| 162 | timer.start(); | ||
| 163 | } | ||
| 164 | } | ||
diff --git a/src/cuchaz/enigma/gui/FieldMatchingGui.java b/src/cuchaz/enigma/gui/FieldMatchingGui.java new file mode 100644 index 00000000..de9ba142 --- /dev/null +++ b/src/cuchaz/enigma/gui/FieldMatchingGui.java | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | import java.awt.BorderLayout; | ||
| 4 | import java.awt.Container; | ||
| 5 | import java.awt.Dimension; | ||
| 6 | import java.awt.FlowLayout; | ||
| 7 | import java.util.Map; | ||
| 8 | |||
| 9 | import javax.swing.BoxLayout; | ||
| 10 | import javax.swing.JFrame; | ||
| 11 | import javax.swing.JLabel; | ||
| 12 | import javax.swing.JPanel; | ||
| 13 | import javax.swing.JScrollPane; | ||
| 14 | import javax.swing.JSplitPane; | ||
| 15 | import javax.swing.WindowConstants; | ||
| 16 | |||
| 17 | import cuchaz.enigma.Constants; | ||
| 18 | import cuchaz.enigma.Deobfuscator; | ||
| 19 | import cuchaz.enigma.convert.ClassMatches; | ||
| 20 | import cuchaz.enigma.convert.FieldMatches; | ||
| 21 | import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; | ||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 24 | import cuchaz.enigma.mapping.FieldMapping; | ||
| 25 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 26 | |||
| 27 | |||
| 28 | public class FieldMatchingGui { | ||
| 29 | |||
| 30 | public static interface SaveListener { | ||
| 31 | public void save(FieldMatches matches); | ||
| 32 | } | ||
| 33 | |||
| 34 | // controls | ||
| 35 | private JFrame m_frame; | ||
| 36 | private ClassSelector m_sourceClasses; | ||
| 37 | private CodeReader m_sourceReader; | ||
| 38 | private CodeReader m_destReader; | ||
| 39 | |||
| 40 | private ClassMatches m_classMatches; | ||
| 41 | private FieldMatches m_fieldMatches; | ||
| 42 | private Map<FieldEntry,FieldMapping> m_droppedFieldMappings; | ||
| 43 | private Deobfuscator m_sourceDeobfuscator; | ||
| 44 | private Deobfuscator m_destDeobfuscator; | ||
| 45 | private SaveListener m_saveListener; | ||
| 46 | |||
| 47 | public FieldMatchingGui(ClassMatches classMatches, FieldMatches fieldMatches, Map<FieldEntry,FieldMapping> droppedFieldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { | ||
| 48 | |||
| 49 | m_classMatches = classMatches; | ||
| 50 | m_fieldMatches = fieldMatches; | ||
| 51 | m_droppedFieldMappings = droppedFieldMappings; | ||
| 52 | m_sourceDeobfuscator = sourceDeobfuscator; | ||
| 53 | m_destDeobfuscator = destDeobfuscator; | ||
| 54 | |||
| 55 | // init frame | ||
| 56 | m_frame = new JFrame(Constants.Name + " - Field Matcher"); | ||
| 57 | final Container pane = m_frame.getContentPane(); | ||
| 58 | pane.setLayout(new BorderLayout()); | ||
| 59 | |||
| 60 | // init classes side | ||
| 61 | JPanel classesPanel = new JPanel(); | ||
| 62 | classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS)); | ||
| 63 | classesPanel.setPreferredSize(new Dimension(200, 0)); | ||
| 64 | pane.add(classesPanel, BorderLayout.WEST); | ||
| 65 | classesPanel.add(new JLabel("Classes")); | ||
| 66 | |||
| 67 | m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); | ||
| 68 | m_sourceClasses.setListener(new ClassSelectionListener() { | ||
| 69 | @Override | ||
| 70 | public void onSelectClass(ClassEntry classEntry) { | ||
| 71 | setSourceClass(classEntry); | ||
| 72 | } | ||
| 73 | }); | ||
| 74 | JScrollPane sourceScroller = new JScrollPane(m_sourceClasses); | ||
| 75 | classesPanel.add(sourceScroller); | ||
| 76 | |||
| 77 | // init fields side | ||
| 78 | JPanel fieldsPanel = new JPanel(); | ||
| 79 | fieldsPanel.setLayout(new BoxLayout(fieldsPanel, BoxLayout.PAGE_AXIS)); | ||
| 80 | fieldsPanel.setPreferredSize(new Dimension(200, 0)); | ||
| 81 | pane.add(fieldsPanel, BorderLayout.WEST); | ||
| 82 | fieldsPanel.add(new JLabel("Destination Fields")); | ||
| 83 | |||
| 84 | // init readers | ||
| 85 | DefaultSyntaxKit.initKit(); | ||
| 86 | m_sourceReader = new CodeReader(); | ||
| 87 | m_destReader = new CodeReader(); | ||
| 88 | |||
| 89 | // init all the splits | ||
| 90 | JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, new JScrollPane(m_sourceReader)); | ||
| 91 | splitLeft.setResizeWeight(0); // let the right side take all the slack | ||
| 92 | JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), fieldsPanel); | ||
| 93 | splitRight.setResizeWeight(1); // let the left side take all the slack | ||
| 94 | JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight); | ||
| 95 | splitCenter.setResizeWeight(0.5); // resize 50:50 | ||
| 96 | pane.add(splitCenter, BorderLayout.CENTER); | ||
| 97 | splitCenter.resetToPreferredSizes(); | ||
| 98 | |||
| 99 | // init bottom panel | ||
| 100 | JPanel bottomPanel = new JPanel(); | ||
| 101 | bottomPanel.setLayout(new FlowLayout()); | ||
| 102 | |||
| 103 | // show the frame | ||
| 104 | pane.doLayout(); | ||
| 105 | m_frame.setSize(1024, 576); | ||
| 106 | m_frame.setMinimumSize(new Dimension(640, 480)); | ||
| 107 | m_frame.setVisible(true); | ||
| 108 | m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); | ||
| 109 | |||
| 110 | // init state | ||
| 111 | m_saveListener = null; | ||
| 112 | m_fieldMatches.addUnmatchedSourceFields(droppedFieldMappings.keySet()); | ||
| 113 | m_sourceClasses.setClasses(m_fieldMatches.getSourceClassesWithUnmatchedFields()); | ||
| 114 | } | ||
| 115 | |||
| 116 | public void setSaveListener(SaveListener val) { | ||
| 117 | m_saveListener = val; | ||
| 118 | } | ||
| 119 | |||
| 120 | protected void setSourceClass(ClassEntry obfSourceClass) { | ||
| 121 | |||
| 122 | // get the matched dest class | ||
| 123 | final ClassEntry obfDestClass = m_classMatches.getUniqueMatches().get(obfSourceClass); | ||
| 124 | if (obfDestClass == null) { | ||
| 125 | throw new Error("No matching dest class for source class: " + obfSourceClass); | ||
| 126 | } | ||
| 127 | |||
| 128 | m_sourceReader.decompileClass(obfSourceClass, m_sourceDeobfuscator); | ||
| 129 | m_destReader.decompileClass(obfDestClass, m_destDeobfuscator); | ||
| 130 | } | ||
| 131 | } | ||
diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java index 38a6ee9a..ea05d255 100644 --- a/src/cuchaz/enigma/gui/Gui.java +++ b/src/cuchaz/enigma/gui/Gui.java | |||
| @@ -737,7 +737,7 @@ public class Gui { | |||
| 737 | if (token == null) { | 737 | if (token == null) { |
| 738 | throw new IllegalArgumentException("Token cannot be null!"); | 738 | throw new IllegalArgumentException("Token cannot be null!"); |
| 739 | } | 739 | } |
| 740 | GuiTricks.navigateToToken(m_editor, token, m_selectionHighlightPainter); | 740 | CodeReader.navigateToToken(m_editor, token, m_selectionHighlightPainter); |
| 741 | redraw(); | 741 | redraw(); |
| 742 | } | 742 | } |
| 743 | 743 | ||
diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java index 5a3a01de..df9e2215 100644 --- a/src/cuchaz/enigma/gui/GuiTricks.java +++ b/src/cuchaz/enigma/gui/GuiTricks.java | |||
| @@ -11,21 +11,11 @@ | |||
| 11 | package cuchaz.enigma.gui; | 11 | package cuchaz.enigma.gui; |
| 12 | 12 | ||
| 13 | import java.awt.Font; | 13 | import java.awt.Font; |
| 14 | import java.awt.Rectangle; | ||
| 15 | import java.awt.event.ActionEvent; | ||
| 16 | import java.awt.event.ActionListener; | ||
| 17 | import java.awt.event.MouseEvent; | 14 | import java.awt.event.MouseEvent; |
| 18 | 15 | ||
| 19 | import javax.swing.JComponent; | 16 | import javax.swing.JComponent; |
| 20 | import javax.swing.JEditorPane; | ||
| 21 | import javax.swing.JLabel; | 17 | import javax.swing.JLabel; |
| 22 | import javax.swing.SwingUtilities; | ||
| 23 | import javax.swing.Timer; | ||
| 24 | import javax.swing.ToolTipManager; | 18 | import javax.swing.ToolTipManager; |
| 25 | import javax.swing.text.BadLocationException; | ||
| 26 | import javax.swing.text.Highlighter.HighlightPainter; | ||
| 27 | |||
| 28 | import cuchaz.enigma.analysis.Token; | ||
| 29 | 19 | ||
| 30 | public class GuiTricks { | 20 | public class GuiTricks { |
| 31 | 21 | ||
| @@ -43,52 +33,4 @@ public class GuiTricks { | |||
| 43 | manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); | 33 | manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); |
| 44 | manager.setInitialDelay(oldDelay); | 34 | manager.setInitialDelay(oldDelay); |
| 45 | } | 35 | } |
| 46 | |||
| 47 | public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { | ||
| 48 | |||
| 49 | // set the caret position to the token | ||
| 50 | editor.setCaretPosition(token.start); | ||
| 51 | editor.grabFocus(); | ||
| 52 | |||
| 53 | try { | ||
| 54 | // make sure the token is visible in the scroll window | ||
| 55 | Rectangle start = editor.modelToView(token.start); | ||
| 56 | Rectangle end = editor.modelToView(token.end); | ||
| 57 | final Rectangle show = start.union(end); | ||
| 58 | show.grow(start.width * 10, start.height * 6); | ||
| 59 | SwingUtilities.invokeLater(new Runnable() { | ||
| 60 | @Override | ||
| 61 | public void run() { | ||
| 62 | editor.scrollRectToVisible(show); | ||
| 63 | } | ||
| 64 | }); | ||
| 65 | } catch (BadLocationException ex) { | ||
| 66 | throw new Error(ex); | ||
| 67 | } | ||
| 68 | |||
| 69 | // highlight the token momentarily | ||
| 70 | final Timer timer = new Timer(200, new ActionListener() { | ||
| 71 | private int m_counter = 0; | ||
| 72 | private Object m_highlight = null; | ||
| 73 | |||
| 74 | @Override | ||
| 75 | public void actionPerformed(ActionEvent event) { | ||
| 76 | if (m_counter % 2 == 0) { | ||
| 77 | try { | ||
| 78 | m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); | ||
| 79 | } catch (BadLocationException ex) { | ||
| 80 | // don't care | ||
| 81 | } | ||
| 82 | } else if (m_highlight != null) { | ||
| 83 | editor.getHighlighter().removeHighlight(m_highlight); | ||
| 84 | } | ||
| 85 | |||
| 86 | if (m_counter++ > 6) { | ||
| 87 | Timer timer = (Timer)event.getSource(); | ||
| 88 | timer.stop(); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | }); | ||
| 92 | timer.start(); | ||
| 93 | } | ||
| 94 | } | 36 | } |