diff options
Diffstat (limited to 'src/cuchaz/enigma/Deobfuscator.java')
| -rw-r--r-- | src/cuchaz/enigma/Deobfuscator.java | 595 |
1 files changed, 234 insertions, 361 deletions
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java index 82c786c..679518a 100644 --- a/src/cuchaz/enigma/Deobfuscator.java +++ b/src/cuchaz/enigma/Deobfuscator.java | |||
| @@ -61,12 +61,11 @@ import cuchaz.enigma.mapping.MethodMapping; | |||
| 61 | import cuchaz.enigma.mapping.TranslationDirection; | 61 | import cuchaz.enigma.mapping.TranslationDirection; |
| 62 | import cuchaz.enigma.mapping.Translator; | 62 | import cuchaz.enigma.mapping.Translator; |
| 63 | 63 | ||
| 64 | public class Deobfuscator | 64 | public class Deobfuscator { |
| 65 | { | 65 | |
| 66 | public interface ProgressListener | 66 | public interface ProgressListener { |
| 67 | { | 67 | void init(int totalWork, String title); |
| 68 | void init( int totalWork, String title ); | 68 | void onProgress(int numDone, String message); |
| 69 | void onProgress( int numDone, String message ); | ||
| 70 | } | 69 | } |
| 71 | 70 | ||
| 72 | private File m_file; | 71 | private File m_file; |
| @@ -77,121 +76,104 @@ public class Deobfuscator | |||
| 77 | private MappingsRenamer m_renamer; | 76 | private MappingsRenamer m_renamer; |
| 78 | private Map<TranslationDirection,Translator> m_translatorCache; | 77 | private Map<TranslationDirection,Translator> m_translatorCache; |
| 79 | 78 | ||
| 80 | public Deobfuscator( File file ) | 79 | public Deobfuscator(File file) throws IOException { |
| 81 | throws IOException | ||
| 82 | { | ||
| 83 | m_file = file; | 80 | m_file = file; |
| 84 | m_jar = new JarFile( m_file ); | 81 | m_jar = new JarFile(m_file); |
| 85 | 82 | ||
| 86 | // build the jar index | 83 | // build the jar index |
| 87 | m_jarIndex = new JarIndex(); | 84 | m_jarIndex = new JarIndex(); |
| 88 | m_jarIndex.indexJar( m_jar, true ); | 85 | m_jarIndex.indexJar(m_jar, true); |
| 89 | 86 | ||
| 90 | // config the decompiler | 87 | // config the decompiler |
| 91 | m_settings = DecompilerSettings.javaDefaults(); | 88 | m_settings = DecompilerSettings.javaDefaults(); |
| 92 | m_settings.setMergeVariables( true ); | 89 | m_settings.setMergeVariables(true); |
| 93 | m_settings.setForceExplicitImports( true ); | 90 | m_settings.setForceExplicitImports(true); |
| 94 | m_settings.setForceExplicitTypeArguments( true ); | 91 | m_settings.setForceExplicitTypeArguments(true); |
| 95 | // DEBUG | 92 | // DEBUG |
| 96 | //m_settings.setShowSyntheticMembers( true ); | 93 | // m_settings.setShowSyntheticMembers( true ); |
| 97 | 94 | ||
| 98 | // init defaults | 95 | // init defaults |
| 99 | m_translatorCache = Maps.newTreeMap(); | 96 | m_translatorCache = Maps.newTreeMap(); |
| 100 | 97 | ||
| 101 | // init mappings | 98 | // init mappings |
| 102 | setMappings( new Mappings() ); | 99 | setMappings(new Mappings()); |
| 103 | } | 100 | } |
| 104 | 101 | ||
| 105 | public String getJarName( ) | 102 | public String getJarName() { |
| 106 | { | ||
| 107 | return m_file.getName(); | 103 | return m_file.getName(); |
| 108 | } | 104 | } |
| 109 | 105 | ||
| 110 | public JarIndex getJarIndex( ) | 106 | public JarIndex getJarIndex() { |
| 111 | { | ||
| 112 | return m_jarIndex; | 107 | return m_jarIndex; |
| 113 | } | 108 | } |
| 114 | 109 | ||
| 115 | public Mappings getMappings( ) | 110 | public Mappings getMappings() { |
| 116 | { | ||
| 117 | return m_mappings; | 111 | return m_mappings; |
| 118 | } | 112 | } |
| 119 | public void setMappings( Mappings val ) | 113 | |
| 120 | { | 114 | public void setMappings(Mappings val) { |
| 121 | if( val == null ) | 115 | if (val == null) { |
| 122 | { | ||
| 123 | val = new Mappings(); | 116 | val = new Mappings(); |
| 124 | } | 117 | } |
| 125 | 118 | ||
| 126 | // pass 1: look for any classes that got moved to inner classes | 119 | // pass 1: look for any classes that got moved to inner classes |
| 127 | Map<String,String> renames = Maps.newHashMap(); | 120 | Map<String,String> renames = Maps.newHashMap(); |
| 128 | for( ClassMapping classMapping : val.classes() ) | 121 | for (ClassMapping classMapping : val.classes()) { |
| 129 | { | ||
| 130 | // make sure we strip the packages off of obfuscated inner classes | 122 | // make sure we strip the packages off of obfuscated inner classes |
| 131 | String innerClassName = new ClassEntry( classMapping.getObfName() ).getSimpleName(); | 123 | String innerClassName = new ClassEntry(classMapping.getObfName()).getSimpleName(); |
| 132 | String outerClassName = m_jarIndex.getOuterClass( innerClassName ); | 124 | String outerClassName = m_jarIndex.getOuterClass(innerClassName); |
| 133 | if( outerClassName != null ) | 125 | if (outerClassName != null) { |
| 134 | { | ||
| 135 | // build the composite class name | 126 | // build the composite class name |
| 136 | String newName = outerClassName + "$" + innerClassName; | 127 | String newName = outerClassName + "$" + innerClassName; |
| 137 | 128 | ||
| 138 | // add a rename | 129 | // add a rename |
| 139 | renames.put( classMapping.getObfName(), newName ); | 130 | renames.put(classMapping.getObfName(), newName); |
| 140 | 131 | ||
| 141 | System.out.println( String.format( "Converted class mapping %s to %s", classMapping.getObfName(), newName ) ); | 132 | System.out.println(String.format("Converted class mapping %s to %s", classMapping.getObfName(), newName)); |
| 142 | } | 133 | } |
| 143 | } | 134 | } |
| 144 | for( Map.Entry<String,String> entry : renames.entrySet() ) | 135 | for (Map.Entry<String,String> entry : renames.entrySet()) { |
| 145 | { | 136 | val.renameObfClass(entry.getKey(), entry.getValue()); |
| 146 | val.renameObfClass( entry.getKey(), entry.getValue() ); | ||
| 147 | } | 137 | } |
| 148 | 138 | ||
| 149 | // pass 2: look for fields/methods that are actually declared in superclasses | 139 | // pass 2: look for fields/methods that are actually declared in superclasses |
| 150 | MappingsRenamer renamer = new MappingsRenamer( m_jarIndex, val ); | 140 | MappingsRenamer renamer = new MappingsRenamer(m_jarIndex, val); |
| 151 | for( ClassMapping classMapping : val.classes() ) | 141 | for (ClassMapping classMapping : val.classes()) { |
| 152 | { | 142 | ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfName()); |
| 153 | ClassEntry obfClassEntry = new ClassEntry( classMapping.getObfName() ); | ||
| 154 | 143 | ||
| 155 | // fields | 144 | // fields |
| 156 | for( FieldMapping fieldMapping : Lists.newArrayList( classMapping.fields() ) ) | 145 | for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { |
| 157 | { | 146 | FieldEntry fieldEntry = new FieldEntry(obfClassEntry, fieldMapping.getObfName()); |
| 158 | FieldEntry fieldEntry = new FieldEntry( obfClassEntry, fieldMapping.getObfName() ); | 147 | ClassEntry resolvedObfClassEntry = m_jarIndex.resolveEntryClass(fieldEntry); |
| 159 | ClassEntry resolvedObfClassEntry = m_jarIndex.resolveEntryClass( fieldEntry ); | 148 | if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(fieldEntry.getClassEntry())) { |
| 160 | if( resolvedObfClassEntry != null && !resolvedObfClassEntry.equals( fieldEntry.getClassEntry() ) ) | 149 | boolean wasMoved = renamer.moveFieldToObfClass(classMapping, fieldMapping, resolvedObfClassEntry); |
| 161 | { | 150 | if (wasMoved) { |
| 162 | boolean wasMoved = renamer.moveFieldToObfClass( classMapping, fieldMapping, resolvedObfClassEntry ); | 151 | System.out.println(String.format("Moved field %s to class %s", fieldEntry, resolvedObfClassEntry)); |
| 163 | if( wasMoved ) | 152 | } else { |
| 164 | { | 153 | System.err.println(String.format("WARNING: Would move field %s to class %s but the field was already there. Dropping instead.", fieldEntry, resolvedObfClassEntry)); |
| 165 | System.out.println( String.format( "Moved field %s to class %s", fieldEntry, resolvedObfClassEntry ) ); | ||
| 166 | } | ||
| 167 | else | ||
| 168 | { | ||
| 169 | System.err.println( String.format( "WARNING: Would move field %s to class %s but the field was already there. Dropping instead.", fieldEntry, resolvedObfClassEntry ) ); | ||
| 170 | } | 154 | } |
| 171 | } | 155 | } |
| 172 | } | 156 | } |
| 173 | 157 | ||
| 174 | // methods | 158 | // methods |
| 175 | for( MethodMapping methodMapping : Lists.newArrayList( classMapping.methods() ) ) | 159 | for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { |
| 176 | { | ||
| 177 | // skip constructors | 160 | // skip constructors |
| 178 | if( methodMapping.isConstructor() ) | 161 | if (methodMapping.isConstructor()) { |
| 179 | { | ||
| 180 | continue; | 162 | continue; |
| 181 | } | 163 | } |
| 182 | 164 | ||
| 183 | MethodEntry methodEntry = new MethodEntry( obfClassEntry, methodMapping.getObfName(), methodMapping.getObfSignature() ); | 165 | MethodEntry methodEntry = new MethodEntry( |
| 184 | ClassEntry resolvedObfClassEntry = m_jarIndex.resolveEntryClass( methodEntry ); | 166 | obfClassEntry, |
| 185 | if( resolvedObfClassEntry != null && !resolvedObfClassEntry.equals( methodEntry.getClassEntry() ) ) | 167 | methodMapping.getObfName(), |
| 186 | { | 168 | methodMapping.getObfSignature() |
| 187 | boolean wasMoved = renamer.moveMethodToObfClass( classMapping, methodMapping, resolvedObfClassEntry ); | 169 | ); |
| 188 | if( wasMoved ) | 170 | ClassEntry resolvedObfClassEntry = m_jarIndex.resolveEntryClass(methodEntry); |
| 189 | { | 171 | if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(methodEntry.getClassEntry())) { |
| 190 | System.out.println( String.format( "Moved method %s to class %s", methodEntry, resolvedObfClassEntry ) ); | 172 | boolean wasMoved = renamer.moveMethodToObfClass(classMapping, methodMapping, resolvedObfClassEntry); |
| 191 | } | 173 | if (wasMoved) { |
| 192 | else | 174 | System.out.println(String.format("Moved method %s to class %s", methodEntry, resolvedObfClassEntry)); |
| 193 | { | 175 | } else { |
| 194 | System.err.println( String.format( "WARNING: Would move method %s to class %s but the method was already there. Dropping instead.", methodEntry, resolvedObfClassEntry ) ); | 176 | System.err.println(String.format("WARNING: Would move method %s to class %s but the method was already there. Dropping instead.", methodEntry, resolvedObfClassEntry)); |
| 195 | } | 177 | } |
| 196 | } | 178 | } |
| 197 | } | 179 | } |
| @@ -201,13 +183,11 @@ public class Deobfuscator | |||
| 201 | 183 | ||
| 202 | // drop mappings that don't match the jar | 184 | // drop mappings that don't match the jar |
| 203 | List<ClassEntry> unknownClasses = Lists.newArrayList(); | 185 | List<ClassEntry> unknownClasses = Lists.newArrayList(); |
| 204 | for( ClassMapping classMapping : val.classes() ) | 186 | for (ClassMapping classMapping : val.classes()) { |
| 205 | { | 187 | checkClassMapping(unknownClasses, classMapping); |
| 206 | checkClassMapping( unknownClasses, classMapping ); | ||
| 207 | } | 188 | } |
| 208 | if( !unknownClasses.isEmpty() ) | 189 | if (!unknownClasses.isEmpty()) { |
| 209 | { | 190 | throw new Error("Unable to find classes in jar: " + unknownClasses); |
| 210 | throw new Error( "Unable to find classes in jar: " + unknownClasses ); | ||
| 211 | } | 191 | } |
| 212 | 192 | ||
| 213 | m_mappings = val; | 193 | m_mappings = val; |
| @@ -215,453 +195,346 @@ public class Deobfuscator | |||
| 215 | m_translatorCache.clear(); | 195 | m_translatorCache.clear(); |
| 216 | } | 196 | } |
| 217 | 197 | ||
| 218 | private void checkClassMapping( List<ClassEntry> unknownClasses, ClassMapping classMapping ) | 198 | private void checkClassMapping(List<ClassEntry> unknownClasses, ClassMapping classMapping) { |
| 219 | { | ||
| 220 | // check the class | 199 | // check the class |
| 221 | ClassEntry classEntry = new ClassEntry( classMapping.getObfName() ); | 200 | ClassEntry classEntry = new ClassEntry(classMapping.getObfName()); |
| 222 | String outerClassName = m_jarIndex.getOuterClass( classEntry.getSimpleName() ); | 201 | String outerClassName = m_jarIndex.getOuterClass(classEntry.getSimpleName()); |
| 223 | if( outerClassName != null ) | 202 | if (outerClassName != null) { |
| 224 | { | 203 | classEntry = new ClassEntry(outerClassName + "$" + classMapping.getObfName()); |
| 225 | classEntry = new ClassEntry( outerClassName + "$" + classMapping.getObfName() ); | ||
| 226 | } | 204 | } |
| 227 | if( !m_jarIndex.getObfClassEntries().contains( classEntry ) ) | 205 | if (!m_jarIndex.getObfClassEntries().contains(classEntry)) { |
| 228 | { | 206 | unknownClasses.add(classEntry); |
| 229 | unknownClasses.add( classEntry ); | ||
| 230 | } | 207 | } |
| 231 | 208 | ||
| 232 | // check the fields | 209 | // check the fields |
| 233 | for( FieldMapping fieldMapping : Lists.newArrayList( classMapping.fields() ) ) | 210 | for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { |
| 234 | { | 211 | FieldEntry fieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName()); |
| 235 | FieldEntry fieldEntry = new FieldEntry( classEntry, fieldMapping.getObfName() ); | 212 | if (!m_jarIndex.containsObfField(fieldEntry)) { |
| 236 | if( !m_jarIndex.containsObfField( fieldEntry ) ) | 213 | System.err.println("WARNING: unable to find field " + fieldEntry + ". dropping mapping."); |
| 237 | { | 214 | classMapping.removeFieldMapping(fieldMapping); |
| 238 | System.err.println( "WARNING: unable to find field " + fieldEntry + ". dropping mapping." ); | ||
| 239 | classMapping.removeFieldMapping( fieldMapping ); | ||
| 240 | } | 215 | } |
| 241 | } | 216 | } |
| 242 | 217 | ||
| 243 | // check methods | 218 | // check methods |
| 244 | for( MethodMapping methodMapping : Lists.newArrayList( classMapping.methods() ) ) | 219 | for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { |
| 245 | { | 220 | BehaviorEntry obfBehaviorEntry = BehaviorEntryFactory.createObf(classEntry, methodMapping); |
| 246 | BehaviorEntry obfBehaviorEntry = BehaviorEntryFactory.createObf( classEntry, methodMapping ); | 221 | if (!m_jarIndex.containsObfBehavior(obfBehaviorEntry)) { |
| 247 | if( !m_jarIndex.containsObfBehavior( obfBehaviorEntry ) ) | 222 | System.err.println("WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping."); |
| 248 | { | 223 | classMapping.removeMethodMapping(methodMapping); |
| 249 | System.err.println( "WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping." ); | 224 | } |
| 250 | classMapping.removeMethodMapping( methodMapping ); | ||
| 251 | } | ||
| 252 | } | 225 | } |
| 253 | 226 | ||
| 254 | // check inner classes | 227 | // check inner classes |
| 255 | for( ClassMapping innerClassMapping : classMapping.innerClasses() ) | 228 | for (ClassMapping innerClassMapping : classMapping.innerClasses()) { |
| 256 | { | 229 | checkClassMapping(unknownClasses, innerClassMapping); |
| 257 | checkClassMapping( unknownClasses, innerClassMapping ); | ||
| 258 | } | 230 | } |
| 259 | } | 231 | } |
| 260 | 232 | ||
| 261 | public Translator getTranslator( TranslationDirection direction ) | 233 | public Translator getTranslator(TranslationDirection direction) { |
| 262 | { | 234 | Translator translator = m_translatorCache.get(direction); |
| 263 | Translator translator = m_translatorCache.get( direction ); | 235 | if (translator == null) { |
| 264 | if( translator == null ) | 236 | translator = m_mappings.getTranslator(direction); |
| 265 | { | 237 | m_translatorCache.put(direction, translator); |
| 266 | translator = m_mappings.getTranslator( direction ); | ||
| 267 | m_translatorCache.put( direction, translator ); | ||
| 268 | } | 238 | } |
| 269 | return translator; | 239 | return translator; |
| 270 | } | 240 | } |
| 271 | 241 | ||
| 272 | public void getSeparatedClasses( List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses ) | 242 | public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) { |
| 273 | { | 243 | for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) { |
| 274 | for( ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries() ) | ||
| 275 | { | ||
| 276 | // skip inner classes | 244 | // skip inner classes |
| 277 | if( obfClassEntry.isInnerClass() ) | 245 | if (obfClassEntry.isInnerClass()) { |
| 278 | { | ||
| 279 | continue; | 246 | continue; |
| 280 | } | 247 | } |
| 281 | 248 | ||
| 282 | // separate the classes | 249 | // separate the classes |
| 283 | ClassEntry deobfClassEntry = deobfuscateEntry( obfClassEntry ); | 250 | ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry); |
| 284 | if( !deobfClassEntry.equals( obfClassEntry ) ) | 251 | if (!deobfClassEntry.equals(obfClassEntry)) { |
| 285 | { | ||
| 286 | // if the class has a mapping, clearly it's deobfuscated | 252 | // if the class has a mapping, clearly it's deobfuscated |
| 287 | deobfClasses.add( deobfClassEntry ); | 253 | deobfClasses.add(deobfClassEntry); |
| 288 | } | 254 | } else if (!obfClassEntry.getPackageName().equals(Constants.NonePackage)) { |
| 289 | else if( !obfClassEntry.getPackageName().equals( Constants.NonePackage ) ) | ||
| 290 | { | ||
| 291 | // also call it deobufscated if it's not in the none package | 255 | // also call it deobufscated if it's not in the none package |
| 292 | deobfClasses.add( obfClassEntry ); | 256 | deobfClasses.add(obfClassEntry); |
| 293 | } | 257 | } else { |
| 294 | else | ||
| 295 | { | ||
| 296 | // otherwise, assume it's still obfuscated | 258 | // otherwise, assume it's still obfuscated |
| 297 | obfClasses.add( obfClassEntry ); | 259 | obfClasses.add(obfClassEntry); |
| 298 | } | 260 | } |
| 299 | } | 261 | } |
| 300 | } | 262 | } |
| 301 | 263 | ||
| 302 | public CompilationUnit getSourceTree( String obfClassName ) | 264 | public CompilationUnit getSourceTree(String obfClassName) { |
| 303 | { | ||
| 304 | // is this class deobfuscated? | 265 | // is this class deobfuscated? |
| 305 | // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out | 266 | // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out |
| 306 | // the decompiler only sees the deobfuscated class, so we need to load it by the deobfuscated name | 267 | // the decompiler only sees the deobfuscated class, so we need to load it by the deobfuscated name |
| 307 | String lookupClassName = obfClassName; | 268 | String lookupClassName = obfClassName; |
| 308 | ClassMapping classMapping = m_mappings.getClassByObf( obfClassName ); | 269 | ClassMapping classMapping = m_mappings.getClassByObf(obfClassName); |
| 309 | if( classMapping != null && classMapping.getDeobfName() != null ) | 270 | if (classMapping != null && classMapping.getDeobfName() != null) { |
| 310 | { | ||
| 311 | lookupClassName = classMapping.getDeobfName(); | 271 | lookupClassName = classMapping.getDeobfName(); |
| 312 | } | 272 | } |
| 313 | 273 | ||
| 314 | // is this class even in the jar? | 274 | // is this class even in the jar? |
| 315 | if( !m_jarIndex.containsObfClass( new ClassEntry( obfClassName ) ) ) | 275 | if (!m_jarIndex.containsObfClass(new ClassEntry(obfClassName))) { |
| 316 | { | ||
| 317 | return null; | 276 | return null; |
| 318 | } | 277 | } |
| 319 | 278 | ||
| 320 | // set the type loader | 279 | // set the type loader |
| 321 | m_settings.setTypeLoader( new TranslatingTypeLoader( | 280 | m_settings.setTypeLoader(new TranslatingTypeLoader( |
| 322 | m_jar, | 281 | m_jar, |
| 323 | m_jarIndex, | 282 | m_jarIndex, |
| 324 | getTranslator( TranslationDirection.Obfuscating ), | 283 | getTranslator(TranslationDirection.Obfuscating), |
| 325 | getTranslator( TranslationDirection.Deobfuscating ) | 284 | getTranslator(TranslationDirection.Deobfuscating) |
| 326 | ) ); | 285 | )); |
| 327 | 286 | ||
| 328 | // decompile it! | 287 | // decompile it! |
| 329 | TypeDefinition resolvedType = new MetadataSystem( m_settings.getTypeLoader() ).lookupType( lookupClassName ).resolve(); | 288 | TypeDefinition resolvedType = new MetadataSystem(m_settings.getTypeLoader()).lookupType(lookupClassName).resolve(); |
| 330 | DecompilerContext context = new DecompilerContext(); | 289 | DecompilerContext context = new DecompilerContext(); |
| 331 | context.setCurrentType( resolvedType ); | 290 | context.setCurrentType(resolvedType); |
| 332 | context.setSettings( m_settings ); | 291 | context.setSettings(m_settings); |
| 333 | AstBuilder builder = new AstBuilder( context ); | 292 | AstBuilder builder = new AstBuilder(context); |
| 334 | builder.addType( resolvedType ); | 293 | builder.addType(resolvedType); |
| 335 | builder.runTransformations( null ); | 294 | builder.runTransformations(null); |
| 336 | return builder.getCompilationUnit(); | 295 | return builder.getCompilationUnit(); |
| 337 | } | 296 | } |
| 338 | 297 | ||
| 339 | public SourceIndex getSourceIndex( CompilationUnit sourceTree, String source ) | 298 | public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) { |
| 340 | { | ||
| 341 | // build the source index | 299 | // build the source index |
| 342 | SourceIndex index = new SourceIndex( source ); | 300 | SourceIndex index = new SourceIndex(source); |
| 343 | sourceTree.acceptVisitor( new SourceIndexVisitor(), index ); | 301 | sourceTree.acceptVisitor(new SourceIndexVisitor(), index); |
| 344 | 302 | ||
| 345 | // DEBUG | 303 | // DEBUG |
| 346 | //sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); | 304 | // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); |
| 347 | 305 | ||
| 348 | // resolve all the classes in the source references | 306 | // resolve all the classes in the source references |
| 349 | for( Token token : index.referenceTokens() ) | 307 | for (Token token : index.referenceTokens()) { |
| 350 | { | 308 | EntryReference<Entry,Entry> deobfReference = index.getDeobfReference(token); |
| 351 | EntryReference<Entry,Entry> deobfReference = index.getDeobfReference( token ); | ||
| 352 | 309 | ||
| 353 | // get the obfuscated entry | 310 | // get the obfuscated entry |
| 354 | Entry obfEntry = obfuscateEntry( deobfReference.entry ); | 311 | Entry obfEntry = obfuscateEntry(deobfReference.entry); |
| 355 | 312 | ||
| 356 | // try to resolve the class | 313 | // try to resolve the class |
| 357 | ClassEntry resolvedObfClassEntry = m_jarIndex.resolveEntryClass( obfEntry ); | 314 | ClassEntry resolvedObfClassEntry = m_jarIndex.resolveEntryClass(obfEntry); |
| 358 | if( resolvedObfClassEntry != null && !resolvedObfClassEntry.equals( obfEntry.getClassEntry() ) ) | 315 | if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) { |
| 359 | { | ||
| 360 | // change the class of the entry | 316 | // change the class of the entry |
| 361 | obfEntry = obfEntry.cloneToNewClass( resolvedObfClassEntry ); | 317 | obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry); |
| 362 | 318 | ||
| 363 | // save the new deobfuscated reference | 319 | // save the new deobfuscated reference |
| 364 | deobfReference.entry = deobfuscateEntry( obfEntry ); | 320 | deobfReference.entry = deobfuscateEntry(obfEntry); |
| 365 | index.replaceDeobfReference( token, deobfReference ); | 321 | index.replaceDeobfReference(token, deobfReference); |
| 366 | } | 322 | } |
| 367 | 323 | ||
| 368 | // DEBUG | 324 | // DEBUG |
| 369 | //System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) ); | 325 | // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) ); |
| 370 | } | 326 | } |
| 371 | 327 | ||
| 372 | return index; | 328 | return index; |
| 373 | } | 329 | } |
| 374 | 330 | ||
| 375 | public String getSource( CompilationUnit sourceTree ) | 331 | public String getSource(CompilationUnit sourceTree) { |
| 376 | { | ||
| 377 | // render the AST into source | 332 | // render the AST into source |
| 378 | StringWriter buf = new StringWriter(); | 333 | StringWriter buf = new StringWriter(); |
| 379 | sourceTree.acceptVisitor( new InsertParenthesesVisitor(), null ); | 334 | sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null); |
| 380 | sourceTree.acceptVisitor( new JavaOutputVisitor( new PlainTextOutput( buf ), m_settings ), null ); | 335 | sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), m_settings), null); |
| 381 | return buf.toString(); | 336 | return buf.toString(); |
| 382 | } | 337 | } |
| 383 | 338 | ||
| 384 | public void writeSources( File dirOut, ProgressListener progress ) | 339 | public void writeSources(File dirOut, ProgressListener progress) throws IOException { |
| 385 | throws IOException | ||
| 386 | { | ||
| 387 | // get the classes to decompile | 340 | // get the classes to decompile |
| 388 | Set<ClassEntry> classEntries = Sets.newHashSet(); | 341 | Set<ClassEntry> classEntries = Sets.newHashSet(); |
| 389 | for( ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries() ) | 342 | for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) { |
| 390 | { | ||
| 391 | // skip inner classes | 343 | // skip inner classes |
| 392 | if( obfClassEntry.isInnerClass() ) | 344 | if (obfClassEntry.isInnerClass()) { |
| 393 | { | ||
| 394 | continue; | 345 | continue; |
| 395 | } | 346 | } |
| 396 | 347 | ||
| 397 | classEntries.add( obfClassEntry ); | 348 | classEntries.add(obfClassEntry); |
| 398 | } | 349 | } |
| 399 | 350 | ||
| 400 | if( progress != null ) | 351 | if (progress != null) { |
| 401 | { | 352 | progress.init(classEntries.size(), "Decompiling classes..."); |
| 402 | progress.init( classEntries.size(), "Decompiling classes..." ); | ||
| 403 | } | 353 | } |
| 404 | 354 | ||
| 405 | // DEOBFUSCATE ALL THE THINGS!! @_@ | 355 | // DEOBFUSCATE ALL THE THINGS!! @_@ |
| 406 | int i = 0; | 356 | int i = 0; |
| 407 | for( ClassEntry obfClassEntry : classEntries ) | 357 | for (ClassEntry obfClassEntry : classEntries) { |
| 408 | { | 358 | ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); |
| 409 | ClassEntry deobfClassEntry = deobfuscateEntry( new ClassEntry( obfClassEntry ) ); | 359 | if (progress != null) { |
| 410 | if( progress != null ) | 360 | progress.onProgress(i++, deobfClassEntry.toString()); |
| 411 | { | ||
| 412 | progress.onProgress( i++, deobfClassEntry.toString() ); | ||
| 413 | } | 361 | } |
| 414 | 362 | ||
| 415 | try | 363 | try { |
| 416 | { | ||
| 417 | // get the source | 364 | // get the source |
| 418 | String source = getSource( getSourceTree( obfClassEntry.getName() ) ); | 365 | String source = getSource(getSourceTree(obfClassEntry.getName())); |
| 419 | 366 | ||
| 420 | // write the file | 367 | // write the file |
| 421 | File file = new File( dirOut, deobfClassEntry.getName().replace( '.', '/' ) + ".java" ); | 368 | File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java"); |
| 422 | file.getParentFile().mkdirs(); | 369 | file.getParentFile().mkdirs(); |
| 423 | try( FileWriter out = new FileWriter( file ) ) | 370 | try (FileWriter out = new FileWriter(file)) { |
| 424 | { | 371 | out.write(source); |
| 425 | out.write( source ); | ||
| 426 | } | 372 | } |
| 427 | } | 373 | } catch (Throwable t) { |
| 428 | catch( Throwable t ) | 374 | throw new Error("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")", t); |
| 429 | { | ||
| 430 | throw new Error( "Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")", t ); | ||
| 431 | } | 375 | } |
| 432 | } | 376 | } |
| 433 | if( progress != null ) | 377 | if (progress != null) { |
| 434 | { | 378 | progress.onProgress(i, "Done!"); |
| 435 | progress.onProgress( i, "Done!" ); | ||
| 436 | } | 379 | } |
| 437 | } | 380 | } |
| 438 | 381 | ||
| 439 | public void writeJar( File out, ProgressListener progress ) | 382 | public void writeJar(File out, ProgressListener progress) { |
| 440 | { | 383 | try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) { |
| 441 | try( JarOutputStream outJar = new JarOutputStream( new FileOutputStream( out ) ) ) | 384 | if (progress != null) { |
| 442 | { | 385 | progress.init(JarClassIterator.getClassEntries(m_jar).size(), "Translating classes..."); |
| 443 | if( progress != null ) | ||
| 444 | { | ||
| 445 | progress.init( JarClassIterator.getClassEntries( m_jar ).size(), "Translating classes..." ); | ||
| 446 | } | 386 | } |
| 447 | 387 | ||
| 448 | // prep the loader | 388 | // prep the loader |
| 449 | TranslatingTypeLoader loader = new TranslatingTypeLoader( | 389 | TranslatingTypeLoader loader = new TranslatingTypeLoader( |
| 450 | m_jar, | 390 | m_jar, |
| 451 | m_jarIndex, | 391 | m_jarIndex, |
| 452 | getTranslator( TranslationDirection.Obfuscating ), | 392 | getTranslator(TranslationDirection.Obfuscating), |
| 453 | getTranslator( TranslationDirection.Deobfuscating ) | 393 | getTranslator(TranslationDirection.Deobfuscating) |
| 454 | ); | 394 | ); |
| 455 | 395 | ||
| 456 | int i = 0; | 396 | int i = 0; |
| 457 | for( CtClass c : JarClassIterator.classes( m_jar ) ) | 397 | for (CtClass c : JarClassIterator.classes(m_jar)) { |
| 458 | { | 398 | if (progress != null) { |
| 459 | if( progress != null ) | 399 | progress.onProgress(i++, c.getName()); |
| 460 | { | ||
| 461 | progress.onProgress( i++, c.getName() ); | ||
| 462 | } | 400 | } |
| 463 | 401 | ||
| 464 | try | 402 | try { |
| 465 | { | 403 | c = loader.transformClass(c); |
| 466 | c = loader.transformClass( c ); | 404 | outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class")); |
| 467 | outJar.putNextEntry( new JarEntry( c.getName().replace( '.', '/' ) + ".class" ) ); | 405 | outJar.write(c.toBytecode()); |
| 468 | outJar.write( c.toBytecode() ); | ||
| 469 | outJar.closeEntry(); | 406 | outJar.closeEntry(); |
| 470 | } | 407 | } catch (Throwable t) { |
| 471 | catch( Throwable t ) | 408 | throw new Error("Unable to deobfuscate class " + c.getName(), t); |
| 472 | { | ||
| 473 | throw new Error( "Unable to deobfuscate class " + c.getName(), t ); | ||
| 474 | } | 409 | } |
| 475 | } | 410 | } |
| 476 | if( progress != null ) | 411 | if (progress != null) { |
| 477 | { | 412 | progress.onProgress(i, "Done!"); |
| 478 | progress.onProgress( i, "Done!" ); | ||
| 479 | } | 413 | } |
| 480 | 414 | ||
| 481 | outJar.close(); | 415 | outJar.close(); |
| 482 | } | 416 | } catch (IOException ex) { |
| 483 | catch( IOException ex ) | 417 | throw new Error("Unable to write to Jar file!"); |
| 484 | { | ||
| 485 | throw new Error( "Unable to write to Jar file!" ); | ||
| 486 | } | 418 | } |
| 487 | } | 419 | } |
| 488 | 420 | ||
| 489 | public <T extends Entry> T obfuscateEntry( T deobfEntry ) | 421 | public <T extends Entry> T obfuscateEntry(T deobfEntry) { |
| 490 | { | 422 | if (deobfEntry == null) { |
| 491 | if( deobfEntry == null ) | ||
| 492 | { | ||
| 493 | return null; | 423 | return null; |
| 494 | } | 424 | } |
| 495 | return getTranslator( TranslationDirection.Obfuscating ).translateEntry( deobfEntry ); | 425 | return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry); |
| 496 | } | 426 | } |
| 497 | 427 | ||
| 498 | public <T extends Entry> T deobfuscateEntry( T obfEntry ) | 428 | public <T extends Entry> T deobfuscateEntry(T obfEntry) { |
| 499 | { | 429 | if (obfEntry == null) { |
| 500 | if( obfEntry == null ) | ||
| 501 | { | ||
| 502 | return null; | 430 | return null; |
| 503 | } | 431 | } |
| 504 | return getTranslator( TranslationDirection.Deobfuscating ).translateEntry( obfEntry ); | 432 | return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry); |
| 505 | } | 433 | } |
| 506 | 434 | ||
| 507 | public <E extends Entry,C extends Entry> EntryReference<E,C> obfuscateReference( EntryReference<E,C> deobfReference ) | 435 | public <E extends Entry,C extends Entry> EntryReference<E,C> obfuscateReference(EntryReference<E,C> deobfReference) { |
| 508 | { | 436 | if (deobfReference == null) { |
| 509 | if( deobfReference == null ) | ||
| 510 | { | ||
| 511 | return null; | 437 | return null; |
| 512 | } | 438 | } |
| 513 | return new EntryReference<E,C>( | 439 | return new EntryReference<E,C>( |
| 514 | obfuscateEntry( deobfReference.entry ), | 440 | obfuscateEntry(deobfReference.entry), |
| 515 | obfuscateEntry( deobfReference.context ), | 441 | obfuscateEntry(deobfReference.context), |
| 516 | deobfReference | 442 | deobfReference |
| 517 | ); | 443 | ); |
| 518 | } | 444 | } |
| 519 | 445 | ||
| 520 | public <E extends Entry,C extends Entry> EntryReference<E,C> deobfuscateReference( EntryReference<E,C> obfReference ) | 446 | public <E extends Entry,C extends Entry> EntryReference<E,C> deobfuscateReference(EntryReference<E,C> obfReference) { |
| 521 | { | 447 | if (obfReference == null) { |
| 522 | if( obfReference == null ) | ||
| 523 | { | ||
| 524 | return null; | 448 | return null; |
| 525 | } | 449 | } |
| 526 | return new EntryReference<E,C>( | 450 | return new EntryReference<E,C>( |
| 527 | deobfuscateEntry( obfReference.entry ), | 451 | deobfuscateEntry(obfReference.entry), |
| 528 | deobfuscateEntry( obfReference.context ), | 452 | deobfuscateEntry(obfReference.context), |
| 529 | obfReference | 453 | obfReference |
| 530 | ); | 454 | ); |
| 531 | } | 455 | } |
| 532 | 456 | ||
| 533 | public boolean isObfuscatedIdentifier( Entry obfEntry ) | 457 | public boolean isObfuscatedIdentifier(Entry obfEntry) { |
| 534 | { | 458 | return m_jarIndex.containsObfEntry(obfEntry); |
| 535 | return m_jarIndex.containsObfEntry( obfEntry ); | ||
| 536 | } | 459 | } |
| 537 | 460 | ||
| 538 | public boolean isRenameable( EntryReference<Entry,Entry> obfReference ) | 461 | public boolean isRenameable(EntryReference<Entry,Entry> obfReference) { |
| 539 | { | 462 | return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry()); |
| 540 | return obfReference.isNamed() && isObfuscatedIdentifier( obfReference.getNameableEntry() ); | ||
| 541 | } | 463 | } |
| 542 | 464 | ||
| 543 | |||
| 544 | // NOTE: these methods are a bit messy... oh well | 465 | // NOTE: these methods are a bit messy... oh well |
| 545 | 466 | ||
| 546 | public boolean hasDeobfuscatedName( Entry obfEntry ) | 467 | public boolean hasDeobfuscatedName(Entry obfEntry) { |
| 547 | { | 468 | Translator translator = getTranslator(TranslationDirection.Deobfuscating); |
| 548 | Translator translator = getTranslator( TranslationDirection.Deobfuscating ); | 469 | if (obfEntry instanceof ClassEntry) { |
| 549 | if( obfEntry instanceof ClassEntry ) | 470 | return translator.translate((ClassEntry)obfEntry) != null; |
| 550 | { | 471 | } else if (obfEntry instanceof FieldEntry) { |
| 551 | return translator.translate( (ClassEntry)obfEntry ) != null; | 472 | return translator.translate((FieldEntry)obfEntry) != null; |
| 552 | } | 473 | } else if (obfEntry instanceof MethodEntry) { |
| 553 | else if( obfEntry instanceof FieldEntry ) | 474 | return translator.translate((MethodEntry)obfEntry) != null; |
| 554 | { | 475 | } else if (obfEntry instanceof ConstructorEntry) { |
| 555 | return translator.translate( (FieldEntry)obfEntry ) != null; | ||
| 556 | } | ||
| 557 | else if( obfEntry instanceof MethodEntry ) | ||
| 558 | { | ||
| 559 | return translator.translate( (MethodEntry)obfEntry ) != null; | ||
| 560 | } | ||
| 561 | else if( obfEntry instanceof ConstructorEntry ) | ||
| 562 | { | ||
| 563 | // constructors have no names | 476 | // constructors have no names |
| 564 | return false; | 477 | return false; |
| 565 | } | 478 | } else if (obfEntry instanceof ArgumentEntry) { |
| 566 | else if( obfEntry instanceof ArgumentEntry ) | 479 | return translator.translate((ArgumentEntry)obfEntry) != null; |
| 567 | { | 480 | } else { |
| 568 | return translator.translate( (ArgumentEntry)obfEntry ) != null; | 481 | throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); |
| 569 | } | ||
| 570 | else | ||
| 571 | { | ||
| 572 | throw new Error( "Unknown entry type: " + obfEntry.getClass().getName() ); | ||
| 573 | } | 482 | } |
| 574 | } | 483 | } |
| 575 | 484 | ||
| 576 | public void rename( Entry obfEntry, String newName ) | 485 | public void rename(Entry obfEntry, String newName) { |
| 577 | { | 486 | if (obfEntry instanceof ClassEntry) { |
| 578 | if( obfEntry instanceof ClassEntry ) | 487 | m_renamer.setClassName((ClassEntry)obfEntry, Descriptor.toJvmName(newName)); |
| 579 | { | 488 | } else if (obfEntry instanceof FieldEntry) { |
| 580 | m_renamer.setClassName( (ClassEntry)obfEntry, Descriptor.toJvmName( newName ) ); | 489 | m_renamer.setFieldName((FieldEntry)obfEntry, newName); |
| 581 | } | 490 | } else if (obfEntry instanceof MethodEntry) { |
| 582 | else if( obfEntry instanceof FieldEntry ) | 491 | m_renamer.setMethodTreeName((MethodEntry)obfEntry, newName); |
| 583 | { | 492 | } else if (obfEntry instanceof ConstructorEntry) { |
| 584 | m_renamer.setFieldName( (FieldEntry)obfEntry, newName ); | 493 | throw new IllegalArgumentException("Cannot rename constructors"); |
| 585 | } | 494 | } else if (obfEntry instanceof ArgumentEntry) { |
| 586 | else if( obfEntry instanceof MethodEntry ) | 495 | m_renamer.setArgumentName((ArgumentEntry)obfEntry, newName); |
| 587 | { | 496 | } else { |
| 588 | m_renamer.setMethodTreeName( (MethodEntry)obfEntry, newName ); | 497 | throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); |
| 589 | } | ||
| 590 | else if( obfEntry instanceof ConstructorEntry ) | ||
| 591 | { | ||
| 592 | throw new IllegalArgumentException( "Cannot rename constructors" ); | ||
| 593 | } | ||
| 594 | else if( obfEntry instanceof ArgumentEntry ) | ||
| 595 | { | ||
| 596 | m_renamer.setArgumentName( (ArgumentEntry)obfEntry, newName ); | ||
| 597 | } | ||
| 598 | else | ||
| 599 | { | ||
| 600 | throw new Error( "Unknown entry type: " + obfEntry.getClass().getName() ); | ||
| 601 | } | 498 | } |
| 602 | 499 | ||
| 603 | // clear caches | 500 | // clear caches |
| 604 | m_translatorCache.clear(); | 501 | m_translatorCache.clear(); |
| 605 | } | 502 | } |
| 606 | 503 | ||
| 607 | public void removeMapping( Entry obfEntry ) | 504 | public void removeMapping(Entry obfEntry) { |
| 608 | { | 505 | if (obfEntry instanceof ClassEntry) { |
| 609 | if( obfEntry instanceof ClassEntry ) | 506 | m_renamer.removeClassMapping((ClassEntry)obfEntry); |
| 610 | { | 507 | } else if (obfEntry instanceof FieldEntry) { |
| 611 | m_renamer.removeClassMapping( (ClassEntry)obfEntry ); | 508 | m_renamer.removeFieldMapping((FieldEntry)obfEntry); |
| 612 | } | 509 | } else if (obfEntry instanceof MethodEntry) { |
| 613 | else if( obfEntry instanceof FieldEntry ) | 510 | m_renamer.removeMethodTreeMapping((MethodEntry)obfEntry); |
| 614 | { | 511 | } else if (obfEntry instanceof ConstructorEntry) { |
| 615 | m_renamer.removeFieldMapping( (FieldEntry)obfEntry ); | 512 | throw new IllegalArgumentException("Cannot rename constructors"); |
| 616 | } | 513 | } else if (obfEntry instanceof ArgumentEntry) { |
| 617 | else if( obfEntry instanceof MethodEntry ) | 514 | m_renamer.removeArgumentMapping((ArgumentEntry)obfEntry); |
| 618 | { | 515 | } else { |
| 619 | m_renamer.removeMethodTreeMapping( (MethodEntry)obfEntry ); | 516 | throw new Error("Unknown entry type: " + obfEntry); |
| 620 | } | ||
| 621 | else if( obfEntry instanceof ConstructorEntry ) | ||
| 622 | { | ||
| 623 | throw new IllegalArgumentException( "Cannot rename constructors" ); | ||
| 624 | } | ||
| 625 | else if( obfEntry instanceof ArgumentEntry ) | ||
| 626 | { | ||
| 627 | m_renamer.removeArgumentMapping( (ArgumentEntry)obfEntry ); | ||
| 628 | } | ||
| 629 | else | ||
| 630 | { | ||
| 631 | throw new Error( "Unknown entry type: " + obfEntry ); | ||
| 632 | } | 517 | } |
| 633 | 518 | ||
| 634 | // clear caches | 519 | // clear caches |
| 635 | m_translatorCache.clear(); | 520 | m_translatorCache.clear(); |
| 636 | } | 521 | } |
| 637 | 522 | ||
| 638 | public void markAsDeobfuscated( Entry obfEntry ) | 523 | public void markAsDeobfuscated(Entry obfEntry) { |
| 639 | { | 524 | if (obfEntry instanceof ClassEntry) { |
| 640 | if( obfEntry instanceof ClassEntry ) | 525 | m_renamer.markClassAsDeobfuscated((ClassEntry)obfEntry); |
| 641 | { | 526 | } else if (obfEntry instanceof FieldEntry) { |
| 642 | m_renamer.markClassAsDeobfuscated( (ClassEntry)obfEntry ); | 527 | m_renamer.markFieldAsDeobfuscated((FieldEntry)obfEntry); |
| 643 | } | 528 | } else if (obfEntry instanceof MethodEntry) { |
| 644 | else if( obfEntry instanceof FieldEntry ) | 529 | m_renamer.markMethodTreeAsDeobfuscated((MethodEntry)obfEntry); |
| 645 | { | 530 | } else if (obfEntry instanceof ConstructorEntry) { |
| 646 | m_renamer.markFieldAsDeobfuscated( (FieldEntry)obfEntry ); | 531 | throw new IllegalArgumentException("Cannot rename constructors"); |
| 647 | } | 532 | } else if (obfEntry instanceof ArgumentEntry) { |
| 648 | else if( obfEntry instanceof MethodEntry ) | 533 | m_renamer.markArgumentAsDeobfuscated((ArgumentEntry)obfEntry); |
| 649 | { | 534 | } else { |
| 650 | m_renamer.markMethodTreeAsDeobfuscated( (MethodEntry)obfEntry ); | 535 | throw new Error("Unknown entry type: " + obfEntry); |
| 651 | } | ||
| 652 | else if( obfEntry instanceof ConstructorEntry ) | ||
| 653 | { | ||
| 654 | throw new IllegalArgumentException( "Cannot rename constructors" ); | ||
| 655 | } | 536 | } |
| 656 | else if( obfEntry instanceof ArgumentEntry ) | 537 | |
| 657 | { | ||
| 658 | m_renamer.markArgumentAsDeobfuscated( (ArgumentEntry)obfEntry ); | ||
| 659 | } | ||
| 660 | else | ||
| 661 | { | ||
| 662 | throw new Error( "Unknown entry type: " + obfEntry ); | ||
| 663 | } | ||
| 664 | |||
| 665 | // clear caches | 538 | // clear caches |
| 666 | m_translatorCache.clear(); | 539 | m_translatorCache.clear(); |
| 667 | } | 540 | } |