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.
Files changed (133) hide show
  1. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/.gitignore +5 -0
  2. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/CONTRIBUTING.md +21 -3
  3. pyflashkit-1.3.0/PKG-INFO +406 -0
  4. pyflashkit-1.3.0/README.md +380 -0
  5. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/__init__.py +5 -1
  6. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/abc/__init__.py +11 -3
  7. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/abc/builder.py +135 -140
  8. pyflashkit-1.3.0/flashkit/abc/constants.py +100 -0
  9. pyflashkit-1.3.0/flashkit/abc/disasm.py +385 -0
  10. pyflashkit-1.3.0/flashkit/abc/opcodes.py +513 -0
  11. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/abc/parser.py +60 -42
  12. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/abc/types.py +178 -16
  13. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/abc/writer.py +64 -19
  14. pyflashkit-1.3.0/flashkit/analysis/__init__.py +82 -0
  15. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/call_graph.py +40 -21
  16. pyflashkit-1.3.0/flashkit/analysis/class_graph.py +274 -0
  17. pyflashkit-1.3.0/flashkit/analysis/complexity.py +76 -0
  18. pyflashkit-1.3.0/flashkit/analysis/const_args.py +230 -0
  19. pyflashkit-1.3.0/flashkit/analysis/dead_code.py +174 -0
  20. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/field_access.py +30 -23
  21. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/inheritance.py +20 -0
  22. pyflashkit-1.3.0/flashkit/analysis/liveness.py +159 -0
  23. pyflashkit-1.3.0/flashkit/analysis/method_fingerprint.py +382 -0
  24. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/references.py +46 -29
  25. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/strings.py +25 -27
  26. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/analysis/unified.py +41 -23
  27. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/__init__.py +13 -2
  28. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/classes.py +13 -1
  29. pyflashkit-1.3.0/flashkit/cli/decompile.py +83 -0
  30. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/disasm.py +32 -13
  31. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/extract.py +11 -2
  32. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/field_access.py +14 -3
  33. pyflashkit-1.3.0/flashkit/cli/pool.py +97 -0
  34. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/strings.py +13 -1
  35. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/tags.py +1 -1
  36. pyflashkit-1.3.0/flashkit/decompile/__init__.py +270 -0
  37. pyflashkit-1.3.0/flashkit/decompile/_adapter.py +562 -0
  38. pyflashkit-1.3.0/flashkit/decompile/ast/__init__.py +43 -0
  39. pyflashkit-1.3.0/flashkit/decompile/ast/nodes.py +310 -0
  40. pyflashkit-1.3.0/flashkit/decompile/ast/printer.py +469 -0
  41. pyflashkit-1.3.0/flashkit/decompile/cache.py +112 -0
  42. pyflashkit-1.3.0/flashkit/decompile/class_.py +885 -0
  43. pyflashkit-1.3.0/flashkit/decompile/helpers.py +288 -0
  44. pyflashkit-1.3.0/flashkit/decompile/method.py +306 -0
  45. pyflashkit-1.3.0/flashkit/decompile/patterns.py +319 -0
  46. pyflashkit-1.3.0/flashkit/decompile/stack.py +705 -0
  47. pyflashkit-1.3.0/flashkit/decompile/structure.py +490 -0
  48. pyflashkit-1.3.0/flashkit/graph/__init__.py +19 -0
  49. pyflashkit-1.3.0/flashkit/graph/cfg.py +353 -0
  50. pyflashkit-1.3.0/flashkit/graph/dominators.py +273 -0
  51. pyflashkit-1.3.0/flashkit/graph/loops.py +176 -0
  52. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/info/class_info.py +75 -6
  53. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/info/member_info.py +66 -72
  54. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/workspace/workspace.py +82 -5
  55. pyflashkit-1.3.0/pyflashkit.egg-info/PKG-INFO +406 -0
  56. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyflashkit.egg-info/SOURCES.txt +46 -1
  57. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyflashkit.egg-info/requires.txt +1 -0
  58. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyproject.toml +10 -2
  59. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/abc/test_builder.py +10 -10
  60. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/abc/test_disasm.py +15 -15
  61. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/test_call_graph.py +1 -1
  62. pyflashkit-1.3.0/tests/analysis/test_class_graph.py +10 -0
  63. pyflashkit-1.3.0/tests/analysis/test_complexity.py +52 -0
  64. pyflashkit-1.3.0/tests/analysis/test_const_args.py +111 -0
  65. pyflashkit-1.3.0/tests/analysis/test_dead_code.py +67 -0
  66. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/test_field_access.py +3 -3
  67. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/test_inheritance.py +11 -2
  68. pyflashkit-1.3.0/tests/analysis/test_liveness.py +68 -0
  69. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/test_references.py +1 -1
  70. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/test_strings.py +2 -2
  71. pyflashkit-1.3.0/tests/analysis/test_type_hints.py +38 -0
  72. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/cli/test_cli.py +1 -1
  73. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/conftest.py +22 -13
  74. pyflashkit-1.3.0/tests/decompile/test_ast.py +498 -0
  75. pyflashkit-1.3.0/tests/decompile/test_cache.py +124 -0
  76. pyflashkit-1.3.0/tests/decompile/test_decompile.py +117 -0
  77. pyflashkit-1.3.0/tests/decompile/test_patterns.py +328 -0
  78. pyflashkit-1.3.0/tests/decompile/test_stack.py +585 -0
  79. pyflashkit-1.3.0/tests/decompile/test_structure.py +287 -0
  80. pyflashkit-1.3.0/tests/decompile/test_structure_advanced.py +173 -0
  81. pyflashkit-1.3.0/tests/graph/test_cfg.py +386 -0
  82. pyflashkit-1.3.0/tests/graph/test_dominators.py +316 -0
  83. pyflashkit-1.3.0/tests/graph/test_loops.py +281 -0
  84. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/info/test_class_info.py +70 -9
  85. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/info/test_member_info.py +84 -128
  86. pyflashkit-1.3.0/tests/swf/__init__.py +0 -0
  87. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/test_integration.py +3 -3
  88. pyflashkit-1.3.0/tests/test_public_api.py +41 -0
  89. pyflashkit-1.3.0/tests/workspace/__init__.py +0 -0
  90. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/workspace/test_workspace.py +1 -1
  91. pyflashkit-1.3.0/tests/workspace/test_workspace_properties.py +44 -0
  92. pyflashkit-1.1.0/PKG-INFO +0 -315
  93. pyflashkit-1.1.0/README.md +0 -294
  94. pyflashkit-1.1.0/flashkit/abc/constants.py +0 -198
  95. pyflashkit-1.1.0/flashkit/abc/disasm.py +0 -474
  96. pyflashkit-1.1.0/flashkit/analysis/__init__.py +0 -34
  97. pyflashkit-1.1.0/pyflashkit.egg-info/PKG-INFO +0 -315
  98. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/.github/workflows/ci.yml +0 -0
  99. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/.github/workflows/release.yml +0 -0
  100. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/LICENSE +0 -0
  101. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/_util.py +0 -0
  102. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/build.py +0 -0
  103. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/callees.py +0 -0
  104. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/callers.py +0 -0
  105. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/class_cmd.py +0 -0
  106. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/info.py +0 -0
  107. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/packages.py +0 -0
  108. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/refs.py +0 -0
  109. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/cli/tree.py +0 -0
  110. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/errors.py +0 -0
  111. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/info/__init__.py +0 -0
  112. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/info/package_info.py +0 -0
  113. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/swf/__init__.py +0 -0
  114. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/swf/builder.py +0 -0
  115. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/swf/parser.py +0 -0
  116. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/swf/tags.py +0 -0
  117. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/workspace/__init__.py +0 -0
  118. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/flashkit/workspace/resource.py +0 -0
  119. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyflashkit.egg-info/dependency_links.txt +0 -0
  120. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyflashkit.egg-info/entry_points.txt +0 -0
  121. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/pyflashkit.egg-info/top_level.txt +0 -0
  122. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/setup.cfg +0 -0
  123. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/__init__.py +0 -0
  124. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/abc/__init__.py +0 -0
  125. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/abc/test_parser.py +0 -0
  126. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/abc/test_writer.py +0 -0
  127. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/analysis/__init__.py +0 -0
  128. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/cli/__init__.py +0 -0
  129. {pyflashkit-1.1.0/tests/info → pyflashkit-1.3.0/tests/decompile}/__init__.py +0 -0
  130. {pyflashkit-1.1.0/tests/swf → pyflashkit-1.3.0/tests/graph}/__init__.py +0 -0
  131. {pyflashkit-1.1.0/tests/workspace → pyflashkit-1.3.0/tests/info}/__init__.py +0 -0
  132. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/swf/test_builder.py +0 -0
  133. {pyflashkit-1.1.0 → pyflashkit-1.3.0}/tests/swf/test_parser.py +0 -0
@@ -8,3 +8,8 @@ build/
8
8
  .venv/
9
9
  *.so
10
10
  .pytest_cache/
11
+ .coverage
12
+ .coverage.*
13
+ htmlcov/
14
+ coverage.xml
15
+ *.cover
@@ -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
- search/ Unified query engine
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
- search/ Search engine tests
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