pyflashkit 1.1.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/.gitignore +5 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/CONTRIBUTING.md +21 -3
- pyflashkit-1.3.0/PKG-INFO +406 -0
- pyflashkit-1.3.0/README.md +380 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/__init__.py +5 -1
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/abc/__init__.py +11 -3
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/abc/builder.py +135 -140
- pyflashkit-1.3.0/flashkit/abc/constants.py +100 -0
- pyflashkit-1.3.0/flashkit/abc/disasm.py +385 -0
- pyflashkit-1.3.0/flashkit/abc/opcodes.py +513 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/abc/parser.py +60 -42
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/abc/types.py +178 -16
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/abc/writer.py +64 -19
- pyflashkit-1.3.0/flashkit/analysis/__init__.py +82 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/call_graph.py +40 -21
- pyflashkit-1.3.0/flashkit/analysis/class_graph.py +274 -0
- pyflashkit-1.3.0/flashkit/analysis/complexity.py +76 -0
- pyflashkit-1.3.0/flashkit/analysis/const_args.py +230 -0
- pyflashkit-1.3.0/flashkit/analysis/dead_code.py +174 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/field_access.py +30 -23
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/inheritance.py +20 -0
- pyflashkit-1.3.0/flashkit/analysis/liveness.py +159 -0
- pyflashkit-1.3.0/flashkit/analysis/method_fingerprint.py +382 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/references.py +46 -29
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/strings.py +25 -27
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/unified.py +41 -23
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/__init__.py +13 -2
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/classes.py +13 -1
- pyflashkit-1.3.0/flashkit/cli/decompile.py +83 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/disasm.py +32 -13
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/extract.py +11 -2
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/field_access.py +14 -3
- pyflashkit-1.3.0/flashkit/cli/pool.py +97 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/strings.py +13 -1
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/tags.py +1 -1
- pyflashkit-1.3.0/flashkit/decompile/__init__.py +270 -0
- pyflashkit-1.3.0/flashkit/decompile/_adapter.py +562 -0
- pyflashkit-1.3.0/flashkit/decompile/ast/__init__.py +43 -0
- pyflashkit-1.3.0/flashkit/decompile/ast/nodes.py +310 -0
- pyflashkit-1.3.0/flashkit/decompile/ast/printer.py +469 -0
- pyflashkit-1.3.0/flashkit/decompile/cache.py +112 -0
- pyflashkit-1.3.0/flashkit/decompile/class_.py +885 -0
- pyflashkit-1.3.0/flashkit/decompile/helpers.py +288 -0
- pyflashkit-1.3.0/flashkit/decompile/method.py +306 -0
- pyflashkit-1.3.0/flashkit/decompile/patterns.py +319 -0
- pyflashkit-1.3.0/flashkit/decompile/stack.py +705 -0
- pyflashkit-1.3.0/flashkit/decompile/structure.py +490 -0
- pyflashkit-1.3.0/flashkit/graph/__init__.py +19 -0
- pyflashkit-1.3.0/flashkit/graph/cfg.py +353 -0
- pyflashkit-1.3.0/flashkit/graph/dominators.py +273 -0
- pyflashkit-1.3.0/flashkit/graph/loops.py +176 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/info/class_info.py +75 -6
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/info/member_info.py +66 -72
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/workspace/workspace.py +82 -5
- pyflashkit-1.3.0/pyflashkit.egg-info/PKG-INFO +406 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyflashkit.egg-info/SOURCES.txt +46 -1
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyflashkit.egg-info/requires.txt +1 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyproject.toml +10 -2
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/abc/test_builder.py +10 -10
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/abc/test_disasm.py +15 -15
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/test_call_graph.py +1 -1
- pyflashkit-1.3.0/tests/analysis/test_class_graph.py +10 -0
- pyflashkit-1.3.0/tests/analysis/test_complexity.py +52 -0
- pyflashkit-1.3.0/tests/analysis/test_const_args.py +111 -0
- pyflashkit-1.3.0/tests/analysis/test_dead_code.py +67 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/test_field_access.py +3 -3
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/test_inheritance.py +11 -2
- pyflashkit-1.3.0/tests/analysis/test_liveness.py +68 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/test_references.py +1 -1
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/test_strings.py +2 -2
- pyflashkit-1.3.0/tests/analysis/test_type_hints.py +38 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/cli/test_cli.py +1 -1
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/conftest.py +22 -13
- pyflashkit-1.3.0/tests/decompile/test_ast.py +498 -0
- pyflashkit-1.3.0/tests/decompile/test_cache.py +124 -0
- pyflashkit-1.3.0/tests/decompile/test_decompile.py +117 -0
- pyflashkit-1.3.0/tests/decompile/test_patterns.py +328 -0
- pyflashkit-1.3.0/tests/decompile/test_stack.py +585 -0
- pyflashkit-1.3.0/tests/decompile/test_structure.py +287 -0
- pyflashkit-1.3.0/tests/decompile/test_structure_advanced.py +173 -0
- pyflashkit-1.3.0/tests/graph/test_cfg.py +386 -0
- pyflashkit-1.3.0/tests/graph/test_dominators.py +316 -0
- pyflashkit-1.3.0/tests/graph/test_loops.py +281 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/info/test_class_info.py +70 -9
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/info/test_member_info.py +84 -128
- pyflashkit-1.3.0/tests/swf/__init__.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/test_integration.py +3 -3
- pyflashkit-1.3.0/tests/test_public_api.py +41 -0
- pyflashkit-1.3.0/tests/workspace/__init__.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/workspace/test_workspace.py +1 -1
- pyflashkit-1.3.0/tests/workspace/test_workspace_properties.py +44 -0
- pyflashkit-1.1.0/PKG-INFO +0 -315
- pyflashkit-1.1.0/README.md +0 -294
- pyflashkit-1.1.0/flashkit/abc/constants.py +0 -198
- pyflashkit-1.1.0/flashkit/abc/disasm.py +0 -474
- pyflashkit-1.1.0/flashkit/analysis/__init__.py +0 -34
- pyflashkit-1.1.0/pyflashkit.egg-info/PKG-INFO +0 -315
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/.github/workflows/ci.yml +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/.github/workflows/release.yml +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/LICENSE +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/_util.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/build.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/callees.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/callers.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/class_cmd.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/info.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/packages.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/refs.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/tree.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/errors.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/info/__init__.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/info/package_info.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/swf/__init__.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/swf/builder.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/swf/parser.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/swf/tags.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/workspace/__init__.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/workspace/resource.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyflashkit.egg-info/dependency_links.txt +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyflashkit.egg-info/entry_points.txt +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyflashkit.egg-info/top_level.txt +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/setup.cfg +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/__init__.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/abc/__init__.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/abc/test_parser.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/abc/test_writer.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/__init__.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/cli/__init__.py +0 -0
- {pyflashkit-1.1.0/tests/info → pyflashkit-1.3.0/tests/decompile}/__init__.py +0 -0
- {pyflashkit-1.1.0/tests/swf → pyflashkit-1.3.0/tests/graph}/__init__.py +0 -0
- {pyflashkit-1.1.0/tests/workspace → pyflashkit-1.3.0/tests/info}/__init__.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/swf/test_builder.py +0 -0
- {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/swf/test_parser.py +0 -0
|
@@ -17,6 +17,20 @@ python -m pytest tests/cli/ # just CLI tests
|
|
|
17
17
|
python -m pytest -k "roundtrip" # filter by name
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
Real-SWF tests are opt-in via `FLASHKIT_TEST_SWF`. They never ship a
|
|
21
|
+
binary fixture in-repo; point the env var at a local file you have
|
|
22
|
+
on disk:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
FLASHKIT_TEST_SWF=/path/to/your.swf python -m pytest
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Coverage:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python -m pytest --cov=flashkit --cov-report=term-missing
|
|
32
|
+
```
|
|
33
|
+
|
|
20
34
|
## Project layout
|
|
21
35
|
|
|
22
36
|
```
|
|
@@ -26,8 +40,11 @@ flashkit/
|
|
|
26
40
|
abc/ AVM2 bytecode parsing, writing, disassembly, builder
|
|
27
41
|
info/ Resolved class/field/method model
|
|
28
42
|
workspace/ File loading, resource management
|
|
29
|
-
analysis/ Inheritance, call graph, references, strings
|
|
30
|
-
|
|
43
|
+
analysis/ Inheritance, call graph, references, strings,
|
|
44
|
+
field access, method fingerprints, class graph,
|
|
45
|
+
liveness, const-args, dead code, complexity
|
|
46
|
+
decompile/ CFG-based AS3 decompiler (method + class)
|
|
47
|
+
graph/ CFG, dominators, loop detection (used by decompiler)
|
|
31
48
|
errors.py Error hierarchy
|
|
32
49
|
|
|
33
50
|
tests/
|
|
@@ -36,7 +53,8 @@ tests/
|
|
|
36
53
|
info/ ClassInfo resolution tests
|
|
37
54
|
workspace/ Workspace loading tests
|
|
38
55
|
analysis/ Analysis module tests
|
|
39
|
-
|
|
56
|
+
decompile/ Decompiler structuring + cache tests
|
|
57
|
+
graph/ CFG / dominators / loops tests
|
|
40
58
|
cli/ CLI integration tests
|
|
41
59
|
conftest.py Shared fixtures (build_abc_bytes, build_swf_bytes)
|
|
42
60
|
```
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyflashkit
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: SWF/ABC toolkit for parsing, analyzing, and manipulating Flash files and AVM2 bytecode
|
|
5
|
+
Author: bitalizer
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bitalizer/pyflashkit
|
|
8
|
+
Project-URL: Repository, https://github.com/bitalizer/pyflashkit
|
|
9
|
+
Project-URL: Issues, https://github.com/bitalizer/pyflashkit/issues
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Disassemblers
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# flashkit
|
|
28
|
+
|
|
29
|
+
Parse, analyze, decompile, and rebuild Adobe Flash SWF files and AVM2 bytecode.
|
|
30
|
+
|
|
31
|
+
flashkit is a pure-Python toolkit for working with the SWF container format and the AVM2 bytecode that runs ActionScript 3. It covers everything from low-level pool surgery to full AS3 source recovery, with a CLI for one-off questions and a programmatic API for building tools on top.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install pyflashkit
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or from source:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/bitalizer/pyflashkit.git
|
|
43
|
+
cd pyflashkit
|
|
44
|
+
pip install -e .[dev] # ``[dev]`` adds pytest + pytest-cov
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Python 3.10+. No runtime dependencies.
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from flashkit.workspace import Workspace
|
|
53
|
+
from flashkit.decompile import decompile_class
|
|
54
|
+
|
|
55
|
+
ws = Workspace()
|
|
56
|
+
ws.load_swf("application.swf")
|
|
57
|
+
|
|
58
|
+
# Inspect a class
|
|
59
|
+
player = ws.get_class("PlayerManager")
|
|
60
|
+
print(player.super_name) # "EventDispatcher"
|
|
61
|
+
print(player.fields[0].name, player.fields[0].type_name)
|
|
62
|
+
|
|
63
|
+
# Find every class extending Sprite
|
|
64
|
+
for cls in ws.find_classes(extends="Sprite"):
|
|
65
|
+
print(cls.qualified_name)
|
|
66
|
+
|
|
67
|
+
# Recover AS3 source from bytecode
|
|
68
|
+
print(decompile_class(ws, name="PlayerManager"))
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Features
|
|
74
|
+
|
|
75
|
+
- **SWF container** — parse, build, and round-trip every standard tag.
|
|
76
|
+
- **AVM2 bytecode** — parse to typed dataclasses, modify, write back with byte-perfect fidelity.
|
|
77
|
+
- **AS3 decompiler** — CFG-based pipeline (basic blocks → dominators → loop nesting → stack simulation → structuring → idiom rewrites → AS3 source). Cross-block dataflow handles conditionals whose operands cross block boundaries.
|
|
78
|
+
- **Disassembler** — raw and resolved instruction views.
|
|
79
|
+
- **Workspace** — multi-SWF loading with cached cross-reference, string, field-access, inheritance, and call-graph indexes built in a single bytecode scan.
|
|
80
|
+
- **Analysis layer** — register liveness, constant-argument inference at call sites, dead-class / dead-method detection, entry-point candidates, McCabe cyclomatic complexity.
|
|
81
|
+
- **CLI** — `flashkit info / classes / class / strings / disasm / decompile / pool / tree / refs / callers / callees / fields / packages / extract / build`.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## CLI
|
|
86
|
+
|
|
87
|
+
### `flashkit info`
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
$ flashkit info application.swf
|
|
91
|
+
File: application.swf
|
|
92
|
+
Format: SWF
|
|
93
|
+
SWF version: 40
|
|
94
|
+
Tags: 142
|
|
95
|
+
ABC blocks: 1
|
|
96
|
+
Classes: 823
|
|
97
|
+
Methods: 14210
|
|
98
|
+
Strings: 35482
|
|
99
|
+
Packages: 47
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `flashkit decompile`
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
flashkit decompile app.swf --list # list classes
|
|
106
|
+
flashkit decompile app.swf --class PlayerManager # AS3 source for one class
|
|
107
|
+
flashkit decompile app.swf --class PlayerManager \
|
|
108
|
+
--method takeDamage # one method
|
|
109
|
+
flashkit decompile app.swf --all --outdir decompiled/ # whole SWF to disk
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `flashkit disasm`
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
flashkit disasm app.swf --class PlayerManager
|
|
116
|
+
flashkit disasm app.swf --method-index 42
|
|
117
|
+
flashkit disasm app.swf --class Foo --raw # raw pool indices instead of names
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Operands are resolved by default — `getlex DevSettings`, `pushstring "noScale"`, `setproperty scaleMode` — so output reads next to AS3 source. Use `--raw` for pool-index debugging.
|
|
121
|
+
|
|
122
|
+
### `flashkit pool`
|
|
123
|
+
|
|
124
|
+
Inspect any ABC constant pool.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
flashkit pool app.swf multinames
|
|
128
|
+
flashkit pool app.swf strings -s "level"
|
|
129
|
+
flashkit pool app.swf namespaces -s flash
|
|
130
|
+
flashkit pool app.swf ints
|
|
131
|
+
flashkit pool app.swf doubles
|
|
132
|
+
flashkit pool app.swf namespace-sets
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `flashkit class`
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
$ flashkit class application.swf PlayerManager
|
|
139
|
+
PlayerManager
|
|
140
|
+
Package: com.game
|
|
141
|
+
Extends: EventDispatcher
|
|
142
|
+
Implements: IDisposable, ITickable
|
|
143
|
+
|
|
144
|
+
Instance Fields (3)
|
|
145
|
+
mHealth: Number
|
|
146
|
+
mName: String
|
|
147
|
+
mLevel: int
|
|
148
|
+
|
|
149
|
+
Instance Methods (5)
|
|
150
|
+
init(): void
|
|
151
|
+
get name(): String
|
|
152
|
+
set name(value: String): void
|
|
153
|
+
takeDamage(amount: Number): void
|
|
154
|
+
serialize(): ByteArray
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `flashkit classes`
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
flashkit classes app.swf # all classes
|
|
161
|
+
flashkit classes app.swf -s Manager # search by name
|
|
162
|
+
flashkit classes app.swf -p com.game # filter by package
|
|
163
|
+
flashkit classes app.swf -e Sprite # filter by superclass
|
|
164
|
+
flashkit classes app.swf -i # interfaces only
|
|
165
|
+
flashkit classes app.swf -v # verbose output
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `flashkit strings`
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
flashkit strings app.swf # list all
|
|
172
|
+
flashkit strings app.swf -s config # search
|
|
173
|
+
flashkit strings app.swf -s config -v # with usage locations
|
|
174
|
+
flashkit strings app.swf -s "\\d+" -r # regex
|
|
175
|
+
flashkit strings app.swf -c # classify (URLs, debug)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### `flashkit tree` / `refs` / `callers` / `callees` / `fields`
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
flashkit tree app.swf BaseEntity # show descendants
|
|
182
|
+
flashkit tree app.swf PlayerManager -a # show ancestors
|
|
183
|
+
flashkit refs app.swf Point # all references to a name
|
|
184
|
+
flashkit callers app.swf toString # call graph: who calls X
|
|
185
|
+
flashkit callees app.swf PlayerManager.init # call graph: what X calls
|
|
186
|
+
flashkit fields app.swf PlayerManager # field R/W summary
|
|
187
|
+
flashkit fields app.swf PlayerManager -f mHealth # readers/writers of one field
|
|
188
|
+
flashkit fields app.swf PlayerManager -m takeDamage # what fields a method touches
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### `flashkit packages` / `extract` / `build` / `tags`
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
flashkit tags app.swf # list raw SWF tags
|
|
195
|
+
flashkit packages app.swf # list packages
|
|
196
|
+
flashkit extract app.swf -o ./output # extract ABC blocks to disk
|
|
197
|
+
flashkit build app.swf -o rebuilt.swf # rebuild (compressed)
|
|
198
|
+
flashkit build app.swf -o out.swf -d # rebuild (decompressed)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Library
|
|
204
|
+
|
|
205
|
+
### Workspace — load and query
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from flashkit.workspace import Workspace
|
|
209
|
+
|
|
210
|
+
ws = Workspace()
|
|
211
|
+
ws.load_swf("application.swf")
|
|
212
|
+
ws.load_swz("module.swz")
|
|
213
|
+
|
|
214
|
+
print(ws.summary())
|
|
215
|
+
|
|
216
|
+
cls = ws.get_class("MyClass")
|
|
217
|
+
print(cls.name, cls.super_name, cls.interfaces)
|
|
218
|
+
print(cls.fields) # list of FieldInfo
|
|
219
|
+
print(cls.methods) # list of MethodInfoResolved
|
|
220
|
+
|
|
221
|
+
ws.find_classes(extends="Sprite")
|
|
222
|
+
ws.find_classes(package="com.example", is_interface=True)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Decompiler
|
|
226
|
+
|
|
227
|
+
Three granularities, all accept either a `Workspace` or a parsed `AbcFile`:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from flashkit.decompile import (
|
|
231
|
+
decompile_class, decompile_method, decompile_method_body,
|
|
232
|
+
list_classes, ClassSummary, DecompilerCache,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
src = decompile_class(ws, name="com.game.Player")
|
|
236
|
+
src = decompile_method(ws, class_name="com.game.Player", name="update")
|
|
237
|
+
|
|
238
|
+
# Typed metadata rows (also accept dict-style ``c["name"]`` for legacy code)
|
|
239
|
+
for c in list_classes(ws):
|
|
240
|
+
print(c.full_name, c.trait_count)
|
|
241
|
+
|
|
242
|
+
# Cache parses + decompilers across many lookups on the same SWF
|
|
243
|
+
cache = DecompilerCache()
|
|
244
|
+
cache.list_classes("game.swf")
|
|
245
|
+
cache.decompile_class("game.swf", "Player")
|
|
246
|
+
cache.decompile_method("game.swf", "Player", "update")
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Analysis
|
|
250
|
+
|
|
251
|
+
All indexes are built lazily on first access and cached on the workspace. One bytecode scan populates strings, references, and field access together.
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
# Inheritance
|
|
255
|
+
ws.get_subclasses("BaseEntity")
|
|
256
|
+
ws.get_descendants("BaseEntity") # transitive
|
|
257
|
+
ws.get_ancestors("PlayerManager")
|
|
258
|
+
ws.get_implementors("ISerializable")
|
|
259
|
+
|
|
260
|
+
# Call graph
|
|
261
|
+
ws.callers("toString")
|
|
262
|
+
ws.callees("PlayerManager.init")
|
|
263
|
+
|
|
264
|
+
# References
|
|
265
|
+
ws.references_to("Point")
|
|
266
|
+
ws.references_from("PlayerManager")
|
|
267
|
+
ws.find_instantiators("Point")
|
|
268
|
+
ws.find_type_users("ByteArray")
|
|
269
|
+
|
|
270
|
+
# Strings
|
|
271
|
+
ws.search_strings("config")
|
|
272
|
+
ws.classes_using_string("http://example.com")
|
|
273
|
+
ws.strings_in_class("PlayerManager")
|
|
274
|
+
|
|
275
|
+
# Field access
|
|
276
|
+
ws.field_writers("PlayerManager", "mHealth")
|
|
277
|
+
ws.field_readers("PlayerManager", "mHealth")
|
|
278
|
+
ws.fields_written_by("PlayerManager", "takeDamage")
|
|
279
|
+
ws.fields_read_by("PlayerManager", "takeDamage")
|
|
280
|
+
ws.constructor_assignments("PlayerManager")
|
|
281
|
+
ws.field_access_summary("PlayerManager")
|
|
282
|
+
|
|
283
|
+
# Structural
|
|
284
|
+
ws.find_classes_with_field_type("ByteArray")
|
|
285
|
+
ws.find_methods(return_type="String", name="get")
|
|
286
|
+
ws.find_fields(type_name="int")
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Deeper analysis
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from flashkit.analysis import (
|
|
293
|
+
method_liveness, ConstArgIndex,
|
|
294
|
+
find_dead_classes, find_dead_methods, entrypoint_candidates,
|
|
295
|
+
method_complexity,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Per-method register liveness — useful for ``_loc3_`` rename heuristics
|
|
299
|
+
abc = ws.abc_blocks[0]
|
|
300
|
+
liv = method_liveness(abc, abc.method_bodies[0])
|
|
301
|
+
print(liv.read_counts, liv.write_counts)
|
|
302
|
+
|
|
303
|
+
# Constant-argument inference at every call site
|
|
304
|
+
const_args = ConstArgIndex.from_workspace(ws)
|
|
305
|
+
print(const_args.distinct_arg_values("SetFlag", slot=0)) # e.g. {0, 1, 4, 8}
|
|
306
|
+
|
|
307
|
+
# Dead-code detection (heuristic — AS3 dynamic dispatch can't be proven away)
|
|
308
|
+
print(find_dead_classes(ws))
|
|
309
|
+
print(find_dead_methods(ws))
|
|
310
|
+
|
|
311
|
+
# Entry-point candidates — Sprite / MovieClip / EventDispatcher subclasses
|
|
312
|
+
print(entrypoint_candidates(ws))
|
|
313
|
+
|
|
314
|
+
# McCabe cyclomatic complexity per method body
|
|
315
|
+
mc = method_complexity(abc, abc.method_bodies[0])
|
|
316
|
+
print(mc.complexity, mc.block_count)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Parse SWF and ABC directly
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
from flashkit.swf import parse_swf, TAG_DO_ABC2
|
|
323
|
+
from flashkit.abc import parse_abc, serialize_abc
|
|
324
|
+
|
|
325
|
+
header, tags, version, length = parse_swf(swf_bytes)
|
|
326
|
+
|
|
327
|
+
for tag in tags:
|
|
328
|
+
if tag.tag_type == TAG_DO_ABC2:
|
|
329
|
+
null_idx = tag.payload.index(0, 4)
|
|
330
|
+
abc = parse_abc(tag.payload[null_idx + 1:])
|
|
331
|
+
print(f"{len(abc.instances)} classes, {len(abc.methods)} methods")
|
|
332
|
+
|
|
333
|
+
# Round-trip fidelity: serialize(parse(data)) == data
|
|
334
|
+
assert serialize_abc(abc) == tag.payload[null_idx + 1:]
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Build SWF programmatically
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
from flashkit.abc import AbcBuilder, serialize_abc
|
|
341
|
+
from flashkit.swf import SwfBuilder
|
|
342
|
+
|
|
343
|
+
b = AbcBuilder()
|
|
344
|
+
b.simple_class("Player", package="com.game",
|
|
345
|
+
fields=[("hp", "int"), ("name", "String")])
|
|
346
|
+
b.script()
|
|
347
|
+
abc_bytes = serialize_abc(b.build())
|
|
348
|
+
|
|
349
|
+
swf = SwfBuilder(version=40, width=800, height=600, fps=30)
|
|
350
|
+
swf.add_abc("GameCode", abc_bytes)
|
|
351
|
+
swf_bytes = swf.build(compress=True)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Disassemble method bodies
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
from flashkit.abc import decode_instructions, resolve_instructions
|
|
358
|
+
|
|
359
|
+
for body in abc.method_bodies:
|
|
360
|
+
# Raw — pool indices as integers
|
|
361
|
+
for instr in decode_instructions(body.code):
|
|
362
|
+
print(f"0x{instr.offset:04X} {instr.mnemonic} {instr.operands}")
|
|
363
|
+
|
|
364
|
+
# Resolved — names / strings / literals
|
|
365
|
+
for r in resolve_instructions(abc, decode_instructions(body.code)):
|
|
366
|
+
print(f"0x{r.offset:04X} {r.mnemonic} {', '.join(r.operands)}")
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### AVM2 constants
|
|
370
|
+
|
|
371
|
+
The structural constants (multiname kinds, trait kinds, attribute flags, method/instance flags) are re-exported at the package level so a TraitInfo can be classified without reaching into the submodule:
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
from flashkit.abc import (
|
|
375
|
+
CONSTANT_QNAME, CONSTANT_TYPENAME,
|
|
376
|
+
TRAIT_SLOT, TRAIT_METHOD, TRAIT_GETTER,
|
|
377
|
+
ATTR_OVERRIDE, METHOD_HAS_PARAM_NAMES, INSTANCE_INTERFACE,
|
|
378
|
+
)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Project structure
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
flashkit/
|
|
387
|
+
cli/ CLI (one module per command)
|
|
388
|
+
swf/ SWF container (parse, build, tags)
|
|
389
|
+
abc/ AVM2 bytecode (parse, write, disasm, builder)
|
|
390
|
+
info/ Resolved class model (ClassInfo, FieldInfo, MethodInfo)
|
|
391
|
+
workspace/ File loading and class index
|
|
392
|
+
analysis/ Inheritance, call graph, references, strings,
|
|
393
|
+
field access, liveness, const-args, dead code,
|
|
394
|
+
complexity, method fingerprints, class graph
|
|
395
|
+
decompile/ CFG-based AS3 decompiler
|
|
396
|
+
graph/ CFG, dominators, loop detection (used by decompiler)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## References
|
|
400
|
+
|
|
401
|
+
- [AVM2 Overview (Adobe)](https://www.adobe.com/content/dam/acom/en/devnet/pdf/avm2overview.pdf)
|
|
402
|
+
- [SWF File Format Specification](https://open-flash.github.io/mirrors/swf-spec-19.pdf)
|
|
403
|
+
|
|
404
|
+
## License
|
|
405
|
+
|
|
406
|
+
MIT
|