SWF/ABC toolkit for parsing, analyzing, and manipulating Flash files and AVM2 bytecode
Project description
flashkit
Parse, analyze, decompile, and rebuild Adobe Flash SWF files and AVM2 bytecode.
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.
Install
pip install pyflashkit
Or from source:
git clone https://github.com/bitalizer/pyflashkit.git
cd pyflashkit
pip install -e .[dev] # ``[dev]`` adds pytest + pytest-cov
Python 3.10+. No runtime dependencies.
Quick start
from flashkit.workspace import Workspace
from flashkit.decompile import decompile_class
ws = Workspace()
ws.load_swf("application.swf")
# Inspect a class
player = ws.get_class("PlayerManager")
print(player.super_name) # "EventDispatcher"
print(player.fields[0].name, player.fields[0].type_name)
# Find every class extending Sprite
for cls in ws.find_classes(extends="Sprite"):
print(cls.qualified_name)
# Recover AS3 source from bytecode
print(decompile_class(ws, name="PlayerManager"))
Features
- SWF container — parse, build, and round-trip every standard tag.
- AVM2 bytecode — parse to typed dataclasses, modify, write back with byte-perfect fidelity.
- 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.
- Disassembler — raw and resolved instruction views.
- Workspace — multi-SWF loading with cached cross-reference, string, field-access, inheritance, and call-graph indexes built in a single bytecode scan.
- Analysis layer — register liveness, constant-argument inference at call sites, dead-class / dead-method detection, entry-point candidates, McCabe cyclomatic complexity.
- CLI —
flashkit info / classes / class / strings / disasm / decompile / pool / tree / refs / callers / callees / fields / packages / extract / build.
CLI
flashkit info
$ flashkit info application.swf
File: application.swf
Format: SWF
SWF version: 40
Tags: 142
ABC blocks: 1
Classes: 823
Methods: 14210
Strings: 35482
Packages: 47
flashkit decompile
flashkit decompile app.swf --list # list classes
flashkit decompile app.swf --class PlayerManager # AS3 source for one class
flashkit decompile app.swf --class PlayerManager \
--method takeDamage # one method
flashkit decompile app.swf --all --outdir decompiled/ # whole SWF to disk
flashkit disasm
flashkit disasm app.swf --class PlayerManager
flashkit disasm app.swf --method-index 42
flashkit disasm app.swf --class Foo --raw # raw pool indices instead of names
Operands are resolved by default — getlex DevSettings, pushstring "noScale", setproperty scaleMode — so output reads next to AS3 source. Use --raw for pool-index debugging.
flashkit pool
Inspect any ABC constant pool.
flashkit pool app.swf multinames
flashkit pool app.swf strings -s "level"
flashkit pool app.swf namespaces -s flash
flashkit pool app.swf ints
flashkit pool app.swf doubles
flashkit pool app.swf namespace-sets
flashkit class
$ flashkit class application.swf PlayerManager
PlayerManager
Package: com.game
Extends: EventDispatcher
Implements: IDisposable, ITickable
Instance Fields (3)
mHealth: Number
mName: String
mLevel: int
Instance Methods (5)
init(): void
get name(): String
set name(value: String): void
takeDamage(amount: Number): void
serialize(): ByteArray
flashkit classes
flashkit classes app.swf # all classes
flashkit classes app.swf -s Manager # search by name
flashkit classes app.swf -p com.game # filter by package
flashkit classes app.swf -e Sprite # filter by superclass
flashkit classes app.swf -i # interfaces only
flashkit classes app.swf -v # verbose output
flashkit strings
flashkit strings app.swf # list all
flashkit strings app.swf -s config # search
flashkit strings app.swf -s config -v # with usage locations
flashkit strings app.swf -s "\\d+" -r # regex
flashkit strings app.swf -c # classify (URLs, debug)
flashkit tree / refs / callers / callees / fields
flashkit tree app.swf BaseEntity # show descendants
flashkit tree app.swf PlayerManager -a # show ancestors
flashkit refs app.swf Point # all references to a name
flashkit callers app.swf toString # call graph: who calls X
flashkit callees app.swf PlayerManager.init # call graph: what X calls
flashkit fields app.swf PlayerManager # field R/W summary
flashkit fields app.swf PlayerManager -f mHealth # readers/writers of one field
flashkit fields app.swf PlayerManager -m takeDamage # what fields a method touches
flashkit packages / extract / build / tags
flashkit tags app.swf # list raw SWF tags
flashkit packages app.swf # list packages
flashkit extract app.swf -o ./output # extract ABC blocks to disk
flashkit build app.swf -o rebuilt.swf # rebuild (compressed)
flashkit build app.swf -o out.swf -d # rebuild (decompressed)
Library
Workspace — load and query
from flashkit.workspace import Workspace
ws = Workspace()
ws.load_swf("application.swf")
ws.load_swz("module.swz")
print(ws.summary())
cls = ws.get_class("MyClass")
print(cls.name, cls.super_name, cls.interfaces)
print(cls.fields) # list of FieldInfo
print(cls.methods) # list of MethodInfoResolved
ws.find_classes(extends="Sprite")
ws.find_classes(package="com.example", is_interface=True)
Decompiler
Three granularities, all accept either a Workspace or a parsed AbcFile:
from flashkit.decompile import (
decompile_class, decompile_method, decompile_method_body,
list_classes, ClassSummary, DecompilerCache,
)
src = decompile_class(ws, name="com.game.Player")
src = decompile_method(ws, class_name="com.game.Player", name="update")
# Typed metadata rows (also accept dict-style ``c["name"]`` for legacy code)
for c in list_classes(ws):
print(c.full_name, c.trait_count)
# Cache parses + decompilers across many lookups on the same SWF
cache = DecompilerCache()
cache.list_classes("game.swf")
cache.decompile_class("game.swf", "Player")
cache.decompile_method("game.swf", "Player", "update")
Analysis
All indexes are built lazily on first access and cached on the workspace. One bytecode scan populates strings, references, and field access together.
# Inheritance
ws.get_subclasses("BaseEntity")
ws.get_descendants("BaseEntity") # transitive
ws.get_ancestors("PlayerManager")
ws.get_implementors("ISerializable")
# Call graph
ws.callers("toString")
ws.callees("PlayerManager.init")
# References
ws.references_to("Point")
ws.references_from("PlayerManager")
ws.find_instantiators("Point")
ws.find_type_users("ByteArray")
# Strings
ws.search_strings("config")
ws.classes_using_string("http://example.com")
ws.strings_in_class("PlayerManager")
# Field access
ws.field_writers("PlayerManager", "mHealth")
ws.field_readers("PlayerManager", "mHealth")
ws.fields_written_by("PlayerManager", "takeDamage")
ws.fields_read_by("PlayerManager", "takeDamage")
ws.constructor_assignments("PlayerManager")
ws.field_access_summary("PlayerManager")
# Structural
ws.find_classes_with_field_type("ByteArray")
ws.find_methods(return_type="String", name="get")
ws.find_fields(type_name="int")
Deeper analysis
from flashkit.analysis import (
method_liveness, ConstArgIndex,
find_dead_classes, find_dead_methods, entrypoint_candidates,
method_complexity,
)
# Per-method register liveness — useful for ``_loc3_`` rename heuristics
abc = ws.abc_blocks[0]
liv = method_liveness(abc, abc.method_bodies[0])
print(liv.read_counts, liv.write_counts)
# Constant-argument inference at every call site
const_args = ConstArgIndex.from_workspace(ws)
print(const_args.distinct_arg_values("SetFlag", slot=0)) # e.g. {0, 1, 4, 8}
# Dead-code detection (heuristic — AS3 dynamic dispatch can't be proven away)
print(find_dead_classes(ws))
print(find_dead_methods(ws))
# Entry-point candidates — Sprite / MovieClip / EventDispatcher subclasses
print(entrypoint_candidates(ws))
# McCabe cyclomatic complexity per method body
mc = method_complexity(abc, abc.method_bodies[0])
print(mc.complexity, mc.block_count)
Parse SWF and ABC directly
from flashkit.swf import parse_swf, TAG_DO_ABC2
from flashkit.abc import parse_abc, serialize_abc
header, tags, version, length = parse_swf(swf_bytes)
for tag in tags:
if tag.tag_type == TAG_DO_ABC2:
null_idx = tag.payload.index(0, 4)
abc = parse_abc(tag.payload[null_idx + 1:])
print(f"{len(abc.instances)} classes, {len(abc.methods)} methods")
# Round-trip fidelity: serialize(parse(data)) == data
assert serialize_abc(abc) == tag.payload[null_idx + 1:]
Build SWF programmatically
from flashkit.abc import AbcBuilder, serialize_abc
from flashkit.swf import SwfBuilder
b = AbcBuilder()
b.simple_class("Player", package="com.game",
fields=[("hp", "int"), ("name", "String")])
b.script()
abc_bytes = serialize_abc(b.build())
swf = SwfBuilder(version=40, width=800, height=600, fps=30)
swf.add_abc("GameCode", abc_bytes)
swf_bytes = swf.build(compress=True)
Disassemble method bodies
from flashkit.abc import decode_instructions, resolve_instructions
for body in abc.method_bodies:
# Raw — pool indices as integers
for instr in decode_instructions(body.code):
print(f"0x{instr.offset:04X} {instr.mnemonic} {instr.operands}")
# Resolved — names / strings / literals
for r in resolve_instructions(abc, decode_instructions(body.code)):
print(f"0x{r.offset:04X} {r.mnemonic} {', '.join(r.operands)}")
AVM2 constants
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:
from flashkit.abc import (
CONSTANT_QNAME, CONSTANT_TYPENAME,
TRAIT_SLOT, TRAIT_METHOD, TRAIT_GETTER,
ATTR_OVERRIDE, METHOD_HAS_PARAM_NAMES, INSTANCE_INTERFACE,
)
Project structure
flashkit/
cli/ CLI (one module per command)
swf/ SWF container (parse, build, tags)
abc/ AVM2 bytecode (parse, write, disasm, builder)
info/ Resolved class model (ClassInfo, FieldInfo, MethodInfo)
workspace/ File loading and class index
analysis/ Inheritance, call graph, references, strings,
field access, liveness, const-args, dead code,
complexity, method fingerprints, class graph
decompile/ CFG-based AS3 decompiler
graph/ CFG, dominators, loop detection (used by decompiler)
References
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyflashkit-1.3.0.tar.gz.
File metadata
- Download URL: pyflashkit-1.3.0.tar.gz
- Upload date:
- Size: 193.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9b825b99d5aec664f59144fbe9be691db0071a21eb26a9d0cca44be76d18b72
|
|
| MD5 |
52c64b71a61aeabcc81da677f198a04d
|
|
| BLAKE2b-256 |
42314f60f1bedc5f6cbac64fe5400aa02b8450bf0edd2c922aeec5ff63bd6769
|
Provenance
The following attestation bundles were made for pyflashkit-1.3.0.tar.gz:
Publisher:
release.yml on bitalizer/pyflashkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyflashkit-1.3.0.tar.gz -
Subject digest:
d9b825b99d5aec664f59144fbe9be691db0071a21eb26a9d0cca44be76d18b72 - Sigstore transparency entry: 1397761225
- Sigstore integration time:
-
Permalink:
bitalizer/pyflashkit@e50946b48d91d726aac2beec4e6252e3bd512f33 -
Branch / Tag:
refs/tags/v1.3.0 - Owner: https://github.com/bitalizer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e50946b48d91d726aac2beec4e6252e3bd512f33 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyflashkit-1.3.0-py3-none-any.whl.
File metadata
- Download URL: pyflashkit-1.3.0-py3-none-any.whl
- Upload date:
- Size: 172.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a618f4055b8d9c6c0fa09b800d43840692581e0bc9414cca1ba569a4c0c94b6a
|
|
| MD5 |
ce0d83f38b306f80b08f28a5303b364d
|
|
| BLAKE2b-256 |
399e1e66d54283ebffa871b5a2ffcc275638e39d7e3538901a0bcd549895f481
|
Provenance
The following attestation bundles were made for pyflashkit-1.3.0-py3-none-any.whl:
Publisher:
release.yml on bitalizer/pyflashkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyflashkit-1.3.0-py3-none-any.whl -
Subject digest:
a618f4055b8d9c6c0fa09b800d43840692581e0bc9414cca1ba569a4c0c94b6a - Sigstore transparency entry: 1397761237
- Sigstore integration time:
-
Permalink:
bitalizer/pyflashkit@e50946b48d91d726aac2beec4e6252e3bd512f33 -
Branch / Tag:
refs/tags/v1.3.0 - Owner: https://github.com/bitalizer
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e50946b48d91d726aac2beec4e6252e3bd512f33 -
Trigger Event:
push
-
Statement type: