diff --git a/CHANGELOG.md b/CHANGELOG.md index da674dcd7..2665a6574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ### Added - [#2477] Option to disable AS2 detection of uninitialized class fields - AS2 detection of uninitialized class fields is cancellable and shows progress +- AS2 - show deobfuscated class/package names in the class tree ### Fixed - [#2474] Gotos incorrectly decompiled diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/IdentifiersDeobfuscation.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/IdentifiersDeobfuscation.java index 452133c88..84a3e1be0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/IdentifiersDeobfuscation.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/IdentifiersDeobfuscation.java @@ -622,6 +622,63 @@ public class IdentifiersDeobfuscation { return writer; } + + /** + * Unescapes deobfuscated identifier + * @param s String + * @return Unescaped string + */ + public static String unescapeOIdentifier(String s) { + StringBuilder ret = new StringBuilder(s.length()); + if (s.length() < 2) { + return s; + } + if (!(s.startsWith("\u00A7") && s.endsWith("\u00A7"))) { + return s; + } + for (int i = 1; i < s.length() - 1; i++) { + char c = s.charAt(i); + if (c == '\\') { + if (i + 1 < s.length() - 1) { + i++; + c = s.charAt(i); + if (c == 'n') { + ret.append("\n"); + } else if (c == 'r') { + ret.append("\r"); + } else if (c == 't') { + ret.append("\t"); + } else if (c == 'b') { + ret.append("\b"); + } else if (c == 'f') { + ret.append("\f"); + } else if (c == '\\') { + ret.append("\\"); + } else if (c == '\u00A7') { + ret.append("\u00A7"); + } else if (c == 'x' && i + 2 < s.length() - 1) { + ret.append((char) Integer.parseInt(s.substring(i + 1, i + 3), 16)); + i += 2; + } else if (c == '{') { + int endPos = s.indexOf("}", i); + if (endPos != -1) { + int numRepeat = Integer.parseInt(s.substring(i + 1, endPos)); + i = endPos + 1; + c = s.charAt(i); + for (int j = 0; j < numRepeat; j++) { + ret.append(c); + } + } + } + } + } else { + ret.append(c); + } + } + + return ret.toString(); + } + /** * Escapes obfuscated identifier. * diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index 262941bad..ecc5398c0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -3349,7 +3349,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { if (treeItem instanceof AS2Package) { AS2Package pkg = (AS2Package) treeItem; if (pkg.isFlat()) { - String[] parts = pkg.toString().split("\\."); + String[] parts = pkg.getName().split("\\."); for (int i = 0; i < parts.length; i++) { parts[i] = Helper.makeFileName(parts[i]); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ExportAssetsTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ExportAssetsTag.java index 244f5eb87..34205662c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ExportAssetsTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ExportAssetsTag.java @@ -21,10 +21,12 @@ import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.tags.base.SymbolClassTypeTag; import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.DottedIdentifier; import com.jpexs.decompiler.flash.types.annotations.SWFArray; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.annotations.SWFVersion; import com.jpexs.decompiler.flash.types.annotations.Table; +import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.ByteArrayRange; import java.io.IOException; import java.util.ArrayList; @@ -56,6 +58,7 @@ public class ExportAssetsTag extends SymbolClassTypeTag { @SWFArray(value = "name", countField = "count") @Table(value = "assets", itemName = "asset") + @DottedIdentifier public List names; /** @@ -161,7 +164,7 @@ public class ExportAssetsTag extends SymbolClassTypeTag { Map ret = super.getNameProperties(); if (names.size() == 1) { ret.put("chid", "" + tags.get(0)); - ret.put("ex", "" + names.get(0)); + ret.put("ex", "" + DottedChain.parseNoSuffix(names.get(0)).toPrintableString(false)); } return ret; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ImportAssets2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ImportAssets2Tag.java index 60cb5f1a2..7c3646c24 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ImportAssets2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ImportAssets2Tag.java @@ -26,6 +26,7 @@ import com.jpexs.decompiler.flash.types.annotations.SWFArray; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.annotations.SWFVersion; import com.jpexs.decompiler.flash.types.annotations.Table; +import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Helper; import java.io.IOException; @@ -161,7 +162,7 @@ public class ImportAssets2Tag extends Tag implements ImportTag { Map ret = super.getNameProperties(); if (names.size() == 1) { ret.put("chid", "" + tags.get(0)); - ret.put("im", "" + names.get(0)); + ret.put("im", "" + DottedChain.parseNoSuffix(names.get(0)).toPrintableString(false)); } return ret; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ImportAssetsTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ImportAssetsTag.java index 1fe753cdd..fd59babb6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ImportAssetsTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ImportAssetsTag.java @@ -25,6 +25,7 @@ import com.jpexs.decompiler.flash.types.annotations.SWFArray; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.annotations.SWFVersion; import com.jpexs.decompiler.flash.types.annotations.Table; +import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.ByteArrayRange; import java.io.IOException; import java.util.ArrayList; @@ -137,7 +138,7 @@ public class ImportAssetsTag extends Tag implements ImportTag { Map ret = super.getNameProperties(); if (names.size() == 1) { ret.put("chid", "" + tags.get(0)); - ret.put("im", "" + names.get(0)); + ret.put("im", "" + DottedChain.parseNoSuffix(names.get(0)).toPrintableString(false)); } return ret; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SymbolClassTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SymbolClassTag.java index b08a4a855..a768f54eb 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SymbolClassTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SymbolClassTag.java @@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.tags.base.SymbolClassTypeTag; import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.DottedIdentifier; import com.jpexs.decompiler.flash.types.annotations.SWFArray; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.annotations.SWFVersion; @@ -53,6 +54,7 @@ public class SymbolClassTag extends SymbolClassTypeTag { @SWFArray(value = "name", countField = "numSymbols") @Table(value = "symbols", itemName = "symbol") + @DottedIdentifier(as3 = true) public List names; @Override diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java index bac5f7808..508422334 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java @@ -27,6 +27,7 @@ import com.jpexs.decompiler.flash.helpers.NulWriter; import com.jpexs.decompiler.flash.tags.DefineScalingGridTag; import com.jpexs.decompiler.flash.tags.DoInitActionTag; import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Helper; @@ -98,10 +99,14 @@ public abstract class CharacterTag extends Tag implements CharacterIdTag { ret.put("chid", "" + chid); } if (exportName != null) { - ret.put("exp", Helper.escapePCodeString(exportName)); + ret.put("exp", DottedChain.parseNoSuffix(exportName).toPrintableString(false)); } if (!classNames.isEmpty()) { - ret.put("cls", Helper.joinEscapePCodeString(", ", classNames)); + List escapedList = new ArrayList<>(); + for (String className : classNames) { + escapedList.add(DottedChain.parseNoSuffix(className).toPrintableString(true)); + } + ret.put("cls", String.join(", ", escapedList)); } return ret; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/AS2Package.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/AS2Package.java index 17290edd0..100fde754 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/AS2Package.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/AS2Package.java @@ -16,10 +16,12 @@ */ package com.jpexs.decompiler.flash.timeline; +import com.jpexs.decompiler.flash.IdentifiersDeobfuscation; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.tags.base.ASMSource; import com.jpexs.decompiler.flash.treeitems.Openable; import com.jpexs.decompiler.flash.treeitems.TreeItem; +import com.jpexs.decompiler.graph.DottedChain; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -187,7 +189,10 @@ public class AS2Package implements TreeItem { @Override public String toString() { - return name; + if (defaultPackage) { + return name; + } + return DottedChain.parseNoSuffix(name).toPrintableString(false); } /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/DottedIdentifier.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/DottedIdentifier.java new file mode 100644 index 000000000..88a065c68 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/annotations/DottedIdentifier.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010-2025 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.types.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Field is dotted identifier. + * + * @author JPEXS + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DottedIdentifier { + boolean as3() default false; +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/DottedChain.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/DottedChain.java index 0f0b19b4f..1f256adff 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/DottedChain.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/DottedChain.java @@ -149,7 +149,31 @@ public class DottedChain implements Serializable, Comparable { return ret; } } + + /** + * Parses a dotted chain from a deobfuscated string. + * + * @param name Name + * @return Dotted chain + */ + public static final DottedChain parsePrintable(String name) { + if (name == null) { + return DottedChain.EMPTY; + } else if (name.isEmpty()) { + return DottedChain.TOPLEVEL; + } else { + String[] parts = name.split("\\."); + List newParts = new ArrayList<>(); + for (String part : parts) { + newParts.add(new PathPart(IdentifiersDeobfuscation.unescapeOIdentifier(part), false, "")); + } + DottedChain ret = new DottedChain(); + ret.parts = newParts; + return ret; + } + } + /** * Constructs a new dotted chain. * diff --git a/src/com/jpexs/decompiler/flash/gui/FolderListPanel.java b/src/com/jpexs/decompiler/flash/gui/FolderListPanel.java index 092841ac5..e36566106 100644 --- a/src/com/jpexs/decompiler/flash/gui/FolderListPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/FolderListPanel.java @@ -16,6 +16,7 @@ */ package com.jpexs.decompiler.flash.gui; +import com.jpexs.decompiler.flash.IdentifiersDeobfuscation; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.gui.tagtree.AbstractTagTree; import com.jpexs.decompiler.flash.tags.DoInitActionTag; @@ -255,7 +256,7 @@ public class FolderListPanel extends JPanel { String expName = tag.getSwf().getExportName(tag.getCharacterId()); if (expName != null && !expName.isEmpty()) { String[] pathParts = expName.contains(".") ? expName.split("\\.") : new String[]{expName}; - s = pathParts[pathParts.length - 1]; + s = IdentifiersDeobfuscation.printIdentifier(false, pathParts[pathParts.length - 1]); } } if (s == null) { diff --git a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java index d1008fe18..541957504 100644 --- a/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/GenericTagTreePanel.java @@ -45,6 +45,7 @@ import com.jpexs.decompiler.flash.types.RGB; import com.jpexs.decompiler.flash.types.RGBA; import com.jpexs.decompiler.flash.types.annotations.Conditional; import com.jpexs.decompiler.flash.types.annotations.ConditionalType; +import com.jpexs.decompiler.flash.types.annotations.DottedIdentifier; import com.jpexs.decompiler.flash.types.annotations.EnumValue; import com.jpexs.decompiler.flash.types.annotations.EnumValues; import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; @@ -57,6 +58,7 @@ import com.jpexs.decompiler.flash.types.annotations.UUID; import com.jpexs.decompiler.flash.types.annotations.parser.AnnotationParseException; import com.jpexs.decompiler.flash.types.annotations.parser.ConditionEvaluator; import com.jpexs.decompiler.flash.types.filters.CONVOLUTIONFILTER; +import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.ConcreteClasses; import com.jpexs.helpers.ReflectionTools; @@ -903,7 +905,10 @@ public class GenericTagTreePanel extends GenericTagPanel { enumAdd = " - " + values.get(val); } - if (val instanceof byte[]) { + DottedIdentifier di = field.getAnnotation(DottedIdentifier.class); + if (val instanceof String && di != null) { + valStr += " = " + DottedChain.parseNoSuffix(val.toString()).toPrintableString(di.as3()); + } else if (val instanceof byte[]) { valStr += " = " + ((byte[]) val).length + " byte"; } else if (val instanceof ByteArrayRange) { valStr += " = " + ((ByteArrayRange) val).getLength() + " byte"; diff --git a/src/com/jpexs/decompiler/flash/gui/generictageditors/StringEditor.java b/src/com/jpexs/decompiler/flash/gui/generictageditors/StringEditor.java index bd2c5a676..7fbafb9dc 100644 --- a/src/com/jpexs/decompiler/flash/gui/generictageditors/StringEditor.java +++ b/src/com/jpexs/decompiler/flash/gui/generictageditors/StringEditor.java @@ -16,6 +16,8 @@ */ package com.jpexs.decompiler.flash.gui.generictageditors; +import com.jpexs.decompiler.flash.types.annotations.DottedIdentifier; +import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.Helper; import com.jpexs.helpers.ReflectionTools; import java.awt.Component; @@ -84,7 +86,12 @@ public class StringEditor extends JTextArea implements GenericTagEditor { @Override public void reset() { try { - setText((String) ReflectionTools.getValue(obj, field, index)); + String newValue = (String) ReflectionTools.getValue(obj, field, index); + DottedIdentifier di = field.getAnnotation(DottedIdentifier.class); + if (di != null) { + newValue = DottedChain.parseNoSuffix(newValue).toPrintableString(di.as3()); + } + setText(newValue); } catch (IllegalArgumentException | IllegalAccessException ex) { // ignore } @@ -95,10 +102,15 @@ public class StringEditor extends JTextArea implements GenericTagEditor { try { String oldValue = (String) ReflectionTools.getValue(obj, field, index); String newValue = getText(); + DottedIdentifier di = field.getAnnotation(DottedIdentifier.class); + if (di != null) { + newValue = DottedChain.parsePrintable(newValue).toRawString(); + } + if (Objects.equals(oldValue, newValue)) { return false; } - ReflectionTools.setValue(obj, field, index, getText()); + ReflectionTools.setValue(obj, field, index, newValue); } catch (IllegalArgumentException | IllegalAccessException ex) { // ignore } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java index d19301e09..7a324c8a5 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java @@ -16,6 +16,7 @@ */ package com.jpexs.decompiler.flash.gui.tagtree; +import com.jpexs.decompiler.flash.IdentifiersDeobfuscation; import com.jpexs.decompiler.flash.gui.AppStrings; import com.jpexs.decompiler.flash.gui.MainPanel; import com.jpexs.decompiler.flash.gui.TreeNodeType; @@ -349,7 +350,7 @@ public class TagTree extends AbstractTagTree { String expName = tag.getSwf().getExportName(tag.getCharacterId()); if (expName != null && !expName.isEmpty()) { String[] pathParts = expName.contains(".") ? expName.split("\\.") : new String[]{expName}; - return pathParts[pathParts.length - 1]; + return IdentifiersDeobfuscation.printIdentifier(false, pathParts[pathParts.length - 1]); } } if (value != null) { diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 0cb1f0314..9962d8329 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -3360,7 +3360,7 @@ public class TagTreeContextMenu extends JPopupMenu { while (it instanceof AS2Package) { pkg = (AS2Package) it; if (!pkg.isDefaultPackage()) { - pkgParts.add(0, pkg.getName()); + pkgParts.add(0, DottedChain.parseNoSuffix(pkg.getName()).toPrintableString(false)); } it = model.getParent(it); } @@ -3376,7 +3376,7 @@ public class TagTreeContextMenu extends JPopupMenu { if (addScriptDialog.showDialog() != JOptionPane.OK_OPTION) { return; } - createClass(addScriptDialog.getClassName(), swf); + createAs2Class(addScriptDialog.getClassName(), swf); } private void addAs3ClassActionPerformed(ActionEvent evt) { @@ -3887,7 +3887,10 @@ public class TagTreeContextMenu extends JPopupMenu { addSpriteInitScript(sprite.getSwf(), sprite); } - private void createClass(String className, SWF swf) { + private void createAs2Class(String className, SWF swf) { + + className = DottedChain.parsePrintable(className).toRawString(); + ReadOnlyTagList tags = swf.getTags(); List exportedIds = new ArrayList<>(); for (int i = 0; i < tags.size(); i++) { @@ -4043,7 +4046,7 @@ public class TagTreeContextMenu extends JPopupMenu { } addInstanceEventScript(swf, tim, placeType, frame); } else if (addScriptDialog.getScriptType() == AddScriptDialog.TYPE_CLASS) { - createClass(addScriptDialog.getClassName(), swf); + createAs2Class(addScriptDialog.getClassName(), swf); } else if (addScriptDialog.getScriptType() == AddScriptDialog.TYPE_SPRITE_INIT) { DefineSpriteTag sprite = addScriptDialog.getSprite(); addSpriteInitScript(swf, sprite);