diff options
| author | 2020-12-20 12:25:56 +0100 | |
|---|---|---|
| committer | 2020-12-20 12:25:56 +0100 | |
| commit | 31ad56b7fe24a7958d3c0eae41b3c75a3f022f81 (patch) | |
| tree | 98a30d7face25356c48ad859a66011d78d5e1617 /enigma | |
| parent | Add true, false and null to invalid identifiers (diff) | |
| parent | fix invisible root toggle (diff) | |
| download | enigma-fork-31ad56b7fe24a7958d3c0eae41b3c75a3f022f81.tar.gz enigma-fork-31ad56b7fe24a7958d3c0eae41b3c75a3f022f81.tar.xz enigma-fork-31ad56b7fe24a7958d3c0eae41b3c75a3f022f81.zip | |
Merge pull request #331 from YanisBft/structure-panel
Structure panel
Diffstat (limited to 'enigma')
8 files changed, 202 insertions, 15 deletions
diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java index fcd2c96..a01eca1 100644 --- a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java +++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java | |||
| @@ -18,6 +18,7 @@ import java.util.stream.Stream; | |||
| 18 | 18 | ||
| 19 | import com.google.common.base.Functions; | 19 | import com.google.common.base.Functions; |
| 20 | import com.google.common.base.Preconditions; | 20 | import com.google.common.base.Preconditions; |
| 21 | import cuchaz.enigma.api.service.ObfuscationTestService; | ||
| 21 | import cuchaz.enigma.classprovider.ObfuscationFixClassProvider; | 22 | import cuchaz.enigma.classprovider.ObfuscationFixClassProvider; |
| 22 | import org.objectweb.asm.ClassWriter; | 23 | import org.objectweb.asm.ClassWriter; |
| 23 | import org.objectweb.asm.tree.ClassNode; | 24 | import org.objectweb.asm.tree.ClassNode; |
| @@ -158,6 +159,35 @@ public class EnigmaProject { | |||
| 158 | return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry()); | 159 | return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry()); |
| 159 | } | 160 | } |
| 160 | 161 | ||
| 162 | public boolean isObfuscated(Entry<?> entry) { | ||
| 163 | String name = entry.getName(); | ||
| 164 | |||
| 165 | List<ObfuscationTestService> obfuscationTestServices = this.getEnigma().getServices().get(ObfuscationTestService.TYPE); | ||
| 166 | if (!obfuscationTestServices.isEmpty()) { | ||
| 167 | for (ObfuscationTestService service : obfuscationTestServices) { | ||
| 168 | if (service.testDeobfuscated(entry)) { | ||
| 169 | return false; | ||
| 170 | } | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | List<NameProposalService> nameProposalServices = this.getEnigma().getServices().get(NameProposalService.TYPE); | ||
| 175 | if (!nameProposalServices.isEmpty()) { | ||
| 176 | for (NameProposalService service : nameProposalServices) { | ||
| 177 | if (service.proposeName(entry, mapper).isPresent()) { | ||
| 178 | return false; | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | String mappedName = mapper.deobfuscate(entry).getName(); | ||
| 184 | if (mappedName != null && !mappedName.isEmpty() && !mappedName.equals(name)) { | ||
| 185 | return false; | ||
| 186 | } | ||
| 187 | |||
| 188 | return true; | ||
| 189 | } | ||
| 190 | |||
| 161 | public JarExport exportRemappedJar(ProgressListener progress) { | 191 | public JarExport exportRemappedJar(ProgressListener progress) { |
| 162 | Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses(); | 192 | Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses(); |
| 163 | ClassProvider fixingClassProvider = new ObfuscationFixClassProvider(classProvider, jarIndex); | 193 | ClassProvider fixingClassProvider = new ObfuscationFixClassProvider(classProvider, jarIndex); |
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java new file mode 100644 index 0000000..f310aa7 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java | |||
| @@ -0,0 +1,123 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import cuchaz.enigma.EnigmaProject; | ||
| 4 | import cuchaz.enigma.api.service.NameProposalService; | ||
| 5 | import cuchaz.enigma.translation.TranslateResult; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 7 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 9 | |||
| 10 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 11 | import java.util.List; | ||
| 12 | |||
| 13 | public class StructureTreeNode extends DefaultMutableTreeNode { | ||
| 14 | private final List<NameProposalService> nameProposalServices; | ||
| 15 | private final EntryRemapper mapper; | ||
| 16 | private final ClassEntry parentEntry; | ||
| 17 | private final ParentedEntry entry; | ||
| 18 | |||
| 19 | public StructureTreeNode(EnigmaProject project, ClassEntry parentEntry, ParentedEntry entry) { | ||
| 20 | this.nameProposalServices = project.getEnigma().getServices().get(NameProposalService.TYPE); | ||
| 21 | this.mapper = project.getMapper(); | ||
| 22 | this.parentEntry = parentEntry; | ||
| 23 | this.entry = entry; | ||
| 24 | } | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Returns the parented entry corresponding to this tree node. | ||
| 28 | */ | ||
| 29 | public ParentedEntry getEntry() { | ||
| 30 | return this.entry; | ||
| 31 | } | ||
| 32 | |||
| 33 | public void load(EnigmaProject project, boolean hideDeobfuscated) { | ||
| 34 | List<ParentedEntry> children = project.getJarIndex().getChildrenByClass().get(this.parentEntry); | ||
| 35 | |||
| 36 | for (ParentedEntry child : children) { | ||
| 37 | StructureTreeNode childNode = new StructureTreeNode(project, this.parentEntry, child); | ||
| 38 | |||
| 39 | if (child instanceof ClassEntry) { | ||
| 40 | childNode = new StructureTreeNode(project, (ClassEntry) child, child); | ||
| 41 | childNode.load(project, hideDeobfuscated); | ||
| 42 | } | ||
| 43 | |||
| 44 | // don't add deobfuscated members if hideDeobfuscated is true, unless it's an inner class | ||
| 45 | if (hideDeobfuscated && !project.isObfuscated(child) && !(child instanceof ClassEntry)) { | ||
| 46 | continue; | ||
| 47 | } | ||
| 48 | |||
| 49 | // don't add constructor methods if hideDeobfuscated is true | ||
| 50 | if (hideDeobfuscated && (child instanceof MethodEntry) && ((MethodEntry) child).isConstructor()) { | ||
| 51 | continue; | ||
| 52 | } | ||
| 53 | |||
| 54 | this.add(childNode); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | @Override | ||
| 59 | public String toString() { | ||
| 60 | TranslateResult<ParentedEntry> translateResult = this.mapper.extendedDeobfuscate(this.entry); | ||
| 61 | String result = translateResult.getValue().getName(); | ||
| 62 | |||
| 63 | if (translateResult.isObfuscated()) { | ||
| 64 | if (!this.nameProposalServices.isEmpty()) { | ||
| 65 | for (NameProposalService service : this.nameProposalServices) { | ||
| 66 | if (service.proposeName(this.entry, this.mapper).isPresent()) { | ||
| 67 | result = service.proposeName(this.entry, this.mapper).get(); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | if (this.entry instanceof FieldDefEntry) { | ||
| 74 | FieldDefEntry field = (FieldDefEntry) translateResult.getValue(); | ||
| 75 | String returnType = this.parseDesc(field.getDesc()); | ||
| 76 | |||
| 77 | result = result + ": " + returnType; | ||
| 78 | } else if (this.entry instanceof MethodDefEntry) { | ||
| 79 | MethodDefEntry method = (MethodDefEntry) translateResult.getValue(); | ||
| 80 | String args = this.parseArgs(method.getDesc().getArgumentDescs()); | ||
| 81 | String returnType = this.parseDesc(method.getDesc().getReturnDesc()); | ||
| 82 | |||
| 83 | if (method.isConstructor()) { | ||
| 84 | result = method.getParent().getSimpleName() + args; | ||
| 85 | } else { | ||
| 86 | result = result + args + ": " + returnType; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | return result; | ||
| 91 | } | ||
| 92 | |||
| 93 | private String parseArgs(List<TypeDescriptor> args) { | ||
| 94 | if (args.size() > 0) { | ||
| 95 | String result = "("; | ||
| 96 | |||
| 97 | for (int i = 0; i < args.size(); i++) { | ||
| 98 | if (i > 0) { | ||
| 99 | result += ", "; | ||
| 100 | } | ||
| 101 | |||
| 102 | result += this.parseDesc(args.get(i)); | ||
| 103 | } | ||
| 104 | |||
| 105 | return result + ")"; | ||
| 106 | } | ||
| 107 | |||
| 108 | return "()"; | ||
| 109 | } | ||
| 110 | |||
| 111 | private String parseDesc(TypeDescriptor desc) { | ||
| 112 | if (desc.isVoid()) return "void"; | ||
| 113 | if (desc.isPrimitive()) return desc.getPrimitive().getKeyword(); | ||
| 114 | if (desc.isType()) return desc.getTypeEntry().getSimpleName(); | ||
| 115 | |||
| 116 | if (desc.isArray()) { | ||
| 117 | if (desc.getArrayType().isPrimitive()) return desc.getArrayType().getPrimitive().getKeyword() + "[]"; | ||
| 118 | if (desc.getArrayType().isType()) return desc.getArrayType().getTypeEntry().getSimpleName() + "[]"; | ||
| 119 | } | ||
| 120 | |||
| 121 | return null; | ||
| 122 | } | ||
| 123 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java index b5ad91a..aa360cf 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java | |||
| @@ -11,8 +11,7 @@ | |||
| 11 | 11 | ||
| 12 | package cuchaz.enigma.analysis.index; | 12 | package cuchaz.enigma.analysis.index; |
| 13 | 13 | ||
| 14 | import com.google.common.collect.HashMultimap; | 14 | import com.google.common.collect.*; |
| 15 | import com.google.common.collect.Multimap; | ||
| 16 | import cuchaz.enigma.Enigma; | 15 | import cuchaz.enigma.Enigma; |
| 17 | import cuchaz.enigma.ProgressListener; | 16 | import cuchaz.enigma.ProgressListener; |
| 18 | import cuchaz.enigma.analysis.ReferenceTargetType; | 17 | import cuchaz.enigma.analysis.ReferenceTargetType; |
| @@ -23,10 +22,7 @@ import cuchaz.enigma.translation.representation.Lambda; | |||
| 23 | import cuchaz.enigma.translation.representation.entry.*; | 22 | import cuchaz.enigma.translation.representation.entry.*; |
| 24 | import cuchaz.enigma.utils.I18n; | 23 | import cuchaz.enigma.utils.I18n; |
| 25 | 24 | ||
| 26 | import java.util.Arrays; | 25 | import java.util.*; |
| 27 | import java.util.Collection; | ||
| 28 | import java.util.HashSet; | ||
| 29 | import java.util.Set; | ||
| 30 | 26 | ||
| 31 | public class JarIndex implements JarIndexer { | 27 | public class JarIndex implements JarIndexer { |
| 32 | private final Set<String> indexedClasses = new HashSet<>(); | 28 | private final Set<String> indexedClasses = new HashSet<>(); |
| @@ -40,6 +36,7 @@ public class JarIndex implements JarIndexer { | |||
| 40 | private final Collection<JarIndexer> indexers; | 36 | private final Collection<JarIndexer> indexers; |
| 41 | 37 | ||
| 42 | private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create(); | 38 | private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create(); |
| 39 | private final ListMultimap<ClassEntry, ParentedEntry> childrenByClass; | ||
| 43 | 40 | ||
| 44 | public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) { | 41 | public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) { |
| 45 | this.entryIndex = entryIndex; | 42 | this.entryIndex = entryIndex; |
| @@ -49,6 +46,7 @@ public class JarIndex implements JarIndexer { | |||
| 49 | this.packageVisibilityIndex = packageVisibilityIndex; | 46 | this.packageVisibilityIndex = packageVisibilityIndex; |
| 50 | this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); | 47 | this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); |
| 51 | this.entryResolver = new IndexEntryResolver(this); | 48 | this.entryResolver = new IndexEntryResolver(this); |
| 49 | this.childrenByClass = ArrayListMultimap.create(); | ||
| 52 | } | 50 | } |
| 53 | 51 | ||
| 54 | public static JarIndex empty() { | 52 | public static JarIndex empty() { |
| @@ -101,6 +99,9 @@ public class JarIndex implements JarIndexer { | |||
| 101 | } | 99 | } |
| 102 | 100 | ||
| 103 | indexers.forEach(indexer -> indexer.indexClass(classEntry)); | 101 | indexers.forEach(indexer -> indexer.indexClass(classEntry)); |
| 102 | if (classEntry.isInnerClass() && !classEntry.getAccess().isSynthetic()) { | ||
| 103 | childrenByClass.put(classEntry.getParent(), classEntry); | ||
| 104 | } | ||
| 104 | } | 105 | } |
| 105 | 106 | ||
| 106 | @Override | 107 | @Override |
| @@ -110,6 +111,9 @@ public class JarIndex implements JarIndexer { | |||
| 110 | } | 111 | } |
| 111 | 112 | ||
| 112 | indexers.forEach(indexer -> indexer.indexField(fieldEntry)); | 113 | indexers.forEach(indexer -> indexer.indexField(fieldEntry)); |
| 114 | if (!fieldEntry.getAccess().isSynthetic()) { | ||
| 115 | childrenByClass.put(fieldEntry.getParent(), fieldEntry); | ||
| 116 | } | ||
| 113 | } | 117 | } |
| 114 | 118 | ||
| 115 | @Override | 119 | @Override |
| @@ -119,6 +123,9 @@ public class JarIndex implements JarIndexer { | |||
| 119 | } | 123 | } |
| 120 | 124 | ||
| 121 | indexers.forEach(indexer -> indexer.indexMethod(methodEntry)); | 125 | indexers.forEach(indexer -> indexer.indexMethod(methodEntry)); |
| 126 | if (!methodEntry.getAccess().isSynthetic() && !methodEntry.getName().equals("<clinit>")) { | ||
| 127 | childrenByClass.put(methodEntry.getParent(), methodEntry); | ||
| 128 | } | ||
| 122 | 129 | ||
| 123 | if (!methodEntry.isConstructor()) { | 130 | if (!methodEntry.isConstructor()) { |
| 124 | methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry); | 131 | methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry); |
| @@ -176,6 +183,10 @@ public class JarIndex implements JarIndexer { | |||
| 176 | return entryResolver; | 183 | return entryResolver; |
| 177 | } | 184 | } |
| 178 | 185 | ||
| 186 | public ListMultimap<ClassEntry, ParentedEntry> getChildrenByClass() { | ||
| 187 | return this.childrenByClass; | ||
| 188 | } | ||
| 189 | |||
| 179 | public boolean isIndexed(String internalName) { | 190 | public boolean isIndexed(String internalName) { |
| 180 | return indexedClasses.contains(internalName); | 191 | return indexedClasses.contains(internalName); |
| 181 | } | 192 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java index a7dccfc..6a1b82f 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java | |||
| @@ -235,14 +235,14 @@ public class TypeDescriptor implements Translatable { | |||
| 235 | } | 235 | } |
| 236 | 236 | ||
| 237 | public enum Primitive { | 237 | public enum Primitive { |
| 238 | BYTE('B'), | 238 | BYTE('B', "byte"), |
| 239 | CHARACTER('C'), | 239 | CHARACTER('C', "char"), |
| 240 | SHORT('S'), | 240 | SHORT('S', "short"), |
| 241 | INTEGER('I'), | 241 | INTEGER('I', "int"), |
| 242 | LONG('J'), | 242 | LONG('J', "long"), |
| 243 | FLOAT('F'), | 243 | FLOAT('F', "float"), |
| 244 | DOUBLE('D'), | 244 | DOUBLE('D', "double"), |
| 245 | BOOLEAN('Z'); | 245 | BOOLEAN('Z', "boolean"); |
| 246 | 246 | ||
| 247 | private static final Map<Character, Primitive> lookup; | 247 | private static final Map<Character, Primitive> lookup; |
| 248 | 248 | ||
| @@ -254,9 +254,11 @@ public class TypeDescriptor implements Translatable { | |||
| 254 | } | 254 | } |
| 255 | 255 | ||
| 256 | private char code; | 256 | private char code; |
| 257 | private String keyword; | ||
| 257 | 258 | ||
| 258 | Primitive(char code) { | 259 | Primitive(char code, String keyword) { |
| 259 | this.code = code; | 260 | this.code = code; |
| 261 | this.keyword = keyword; | ||
| 260 | } | 262 | } |
| 261 | 263 | ||
| 262 | public static Primitive get(char code) { | 264 | public static Primitive get(char code) { |
| @@ -266,5 +268,12 @@ public class TypeDescriptor implements Translatable { | |||
| 266 | public char getCode() { | 268 | public char getCode() { |
| 267 | return this.code; | 269 | return this.code; |
| 268 | } | 270 | } |
| 271 | |||
| 272 | /** | ||
| 273 | * Returns the Java keyword corresponding to this primitive. | ||
| 274 | */ | ||
| 275 | public String getKeyword() { | ||
| 276 | return this.keyword; | ||
| 277 | } | ||
| 269 | } | 278 | } |
| 270 | } | 279 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java index 4a50021..b4a22f1 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java | |||
| @@ -134,6 +134,9 @@ public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable< | |||
| 134 | return name; | 134 | return name; |
| 135 | } | 135 | } |
| 136 | 136 | ||
| 137 | /** | ||
| 138 | * Returns whether this class entry has a parent, and therefore is an inner class. | ||
| 139 | */ | ||
| 137 | public boolean isInnerClass() { | 140 | public boolean isInnerClass() { |
| 138 | return parent != null; | 141 | return parent != null; |
| 139 | } | 142 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java index ff392fe..6fd412a 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java | |||
| @@ -29,6 +29,13 @@ public interface Entry<P extends Entry<?>> extends Translatable { | |||
| 29 | return getName(); | 29 | return getName(); |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | /** | ||
| 33 | * Returns the parent entry of this entry. | ||
| 34 | * | ||
| 35 | * <p>The parent entry should be a {@linkplain MethodEntry method} for local variables, | ||
| 36 | * a {@linkplain ClassEntry class} for methods, fields and inner classes, and {@code null} | ||
| 37 | * for other classes.</p> | ||
| 38 | */ | ||
| 32 | @Nullable | 39 | @Nullable |
| 33 | P getParent(); | 40 | P getParent(); |
| 34 | 41 | ||
diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index 9db4e1f..8195bb1 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json | |||
| @@ -101,6 +101,8 @@ | |||
| 101 | "info_panel.identifier.index": "Index", | 101 | "info_panel.identifier.index": "Index", |
| 102 | "info_panel.editor.class.decompiling": "(decompiling...)", | 102 | "info_panel.editor.class.decompiling": "(decompiling...)", |
| 103 | "info_panel.editor.class.not_found": "Unable to find class:", | 103 | "info_panel.editor.class.not_found": "Unable to find class:", |
| 104 | "info_panel.tree.structure": "Structure", | ||
| 105 | "info_panel.tree.structure.hide_deobfuscated": "Hide deobfuscated members", | ||
| 104 | "info_panel.tree.inheritance": "Inheritance", | 106 | "info_panel.tree.inheritance": "Inheritance", |
| 105 | "info_panel.tree.implementations": "Implementations", | 107 | "info_panel.tree.implementations": "Implementations", |
| 106 | "info_panel.tree.calls": "Call Graph", | 108 | "info_panel.tree.calls": "Call Graph", |
diff --git a/enigma/src/main/resources/lang/fr_fr.json b/enigma/src/main/resources/lang/fr_fr.json index 127b9c8..43bea4d 100644 --- a/enigma/src/main/resources/lang/fr_fr.json +++ b/enigma/src/main/resources/lang/fr_fr.json | |||
| @@ -101,6 +101,8 @@ | |||
| 101 | "info_panel.identifier.index": "Index", | 101 | "info_panel.identifier.index": "Index", |
| 102 | "info_panel.editor.class.decompiling": "(décompilation...)", | 102 | "info_panel.editor.class.decompiling": "(décompilation...)", |
| 103 | "info_panel.editor.class.not_found": "Impossible de trouver la classe :", | 103 | "info_panel.editor.class.not_found": "Impossible de trouver la classe :", |
| 104 | "info_panel.tree.structure": "Structure", | ||
| 105 | "info_panel.tree.structure.hide_deobfuscated": "Masquer les membres déobfusqués", | ||
| 104 | "info_panel.tree.inheritance": "Héritage", | 106 | "info_panel.tree.inheritance": "Héritage", |
| 105 | "info_panel.tree.implementations": "Implémentations", | 107 | "info_panel.tree.implementations": "Implémentations", |
| 106 | "info_panel.tree.calls": "Graphique des appels", | 108 | "info_panel.tree.calls": "Graphique des appels", |