pyflashkit 1.0.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.0.0/.github/workflows/ci.yml +35 -0
- pyflashkit-1.0.0/.github/workflows/release.yml +35 -0
- pyflashkit-1.0.0/.gitignore +10 -0
- pyflashkit-1.0.0/CONTRIBUTING.md +125 -0
- pyflashkit-1.0.0/LICENSE +21 -0
- pyflashkit-1.0.0/PKG-INFO +281 -0
- pyflashkit-1.0.0/README.md +260 -0
- pyflashkit-1.0.0/flashkit/__init__.py +54 -0
- pyflashkit-1.0.0/flashkit/abc/__init__.py +79 -0
- pyflashkit-1.0.0/flashkit/abc/builder.py +847 -0
- pyflashkit-1.0.0/flashkit/abc/constants.py +198 -0
- pyflashkit-1.0.0/flashkit/abc/disasm.py +364 -0
- pyflashkit-1.0.0/flashkit/abc/parser.py +434 -0
- pyflashkit-1.0.0/flashkit/abc/types.py +275 -0
- pyflashkit-1.0.0/flashkit/abc/writer.py +230 -0
- pyflashkit-1.0.0/flashkit/analysis/__init__.py +28 -0
- pyflashkit-1.0.0/flashkit/analysis/call_graph.py +317 -0
- pyflashkit-1.0.0/flashkit/analysis/inheritance.py +267 -0
- pyflashkit-1.0.0/flashkit/analysis/references.py +371 -0
- pyflashkit-1.0.0/flashkit/analysis/strings.py +299 -0
- pyflashkit-1.0.0/flashkit/cli/__init__.py +75 -0
- pyflashkit-1.0.0/flashkit/cli/_util.py +52 -0
- pyflashkit-1.0.0/flashkit/cli/build.py +36 -0
- pyflashkit-1.0.0/flashkit/cli/callees.py +30 -0
- pyflashkit-1.0.0/flashkit/cli/callers.py +30 -0
- pyflashkit-1.0.0/flashkit/cli/class_cmd.py +83 -0
- pyflashkit-1.0.0/flashkit/cli/classes.py +71 -0
- pyflashkit-1.0.0/flashkit/cli/disasm.py +77 -0
- pyflashkit-1.0.0/flashkit/cli/extract.py +36 -0
- pyflashkit-1.0.0/flashkit/cli/info.py +41 -0
- pyflashkit-1.0.0/flashkit/cli/packages.py +30 -0
- pyflashkit-1.0.0/flashkit/cli/refs.py +31 -0
- pyflashkit-1.0.0/flashkit/cli/strings.py +58 -0
- pyflashkit-1.0.0/flashkit/cli/tags.py +32 -0
- pyflashkit-1.0.0/flashkit/cli/tree.py +52 -0
- pyflashkit-1.0.0/flashkit/errors.py +33 -0
- pyflashkit-1.0.0/flashkit/info/__init__.py +31 -0
- pyflashkit-1.0.0/flashkit/info/class_info.py +176 -0
- pyflashkit-1.0.0/flashkit/info/member_info.py +275 -0
- pyflashkit-1.0.0/flashkit/info/package_info.py +60 -0
- pyflashkit-1.0.0/flashkit/search/__init__.py +16 -0
- pyflashkit-1.0.0/flashkit/search/search.py +456 -0
- pyflashkit-1.0.0/flashkit/swf/__init__.py +66 -0
- pyflashkit-1.0.0/flashkit/swf/builder.py +283 -0
- pyflashkit-1.0.0/flashkit/swf/parser.py +164 -0
- pyflashkit-1.0.0/flashkit/swf/tags.py +120 -0
- pyflashkit-1.0.0/flashkit/workspace/__init__.py +20 -0
- pyflashkit-1.0.0/flashkit/workspace/resource.py +189 -0
- pyflashkit-1.0.0/flashkit/workspace/workspace.py +232 -0
- pyflashkit-1.0.0/pyflashkit.egg-info/PKG-INFO +281 -0
- pyflashkit-1.0.0/pyflashkit.egg-info/SOURCES.txt +79 -0
- pyflashkit-1.0.0/pyflashkit.egg-info/dependency_links.txt +1 -0
- pyflashkit-1.0.0/pyflashkit.egg-info/entry_points.txt +2 -0
- pyflashkit-1.0.0/pyflashkit.egg-info/requires.txt +3 -0
- pyflashkit-1.0.0/pyflashkit.egg-info/top_level.txt +1 -0
- pyflashkit-1.0.0/pyproject.toml +31 -0
- pyflashkit-1.0.0/setup.cfg +4 -0
- pyflashkit-1.0.0/tests/__init__.py +0 -0
- pyflashkit-1.0.0/tests/abc/__init__.py +0 -0
- pyflashkit-1.0.0/tests/abc/test_builder.py +375 -0
- pyflashkit-1.0.0/tests/abc/test_disasm.py +136 -0
- pyflashkit-1.0.0/tests/abc/test_parser.py +149 -0
- pyflashkit-1.0.0/tests/abc/test_writer.py +118 -0
- pyflashkit-1.0.0/tests/analysis/__init__.py +0 -0
- pyflashkit-1.0.0/tests/analysis/test_call_graph.py +176 -0
- pyflashkit-1.0.0/tests/analysis/test_inheritance.py +193 -0
- pyflashkit-1.0.0/tests/analysis/test_references.py +177 -0
- pyflashkit-1.0.0/tests/analysis/test_strings.py +139 -0
- pyflashkit-1.0.0/tests/cli/__init__.py +0 -0
- pyflashkit-1.0.0/tests/cli/test_cli.py +251 -0
- pyflashkit-1.0.0/tests/conftest.py +289 -0
- pyflashkit-1.0.0/tests/info/__init__.py +0 -0
- pyflashkit-1.0.0/tests/info/test_class_info.py +165 -0
- pyflashkit-1.0.0/tests/search/__init__.py +0 -0
- pyflashkit-1.0.0/tests/search/test_search.py +227 -0
- pyflashkit-1.0.0/tests/swf/__init__.py +0 -0
- pyflashkit-1.0.0/tests/swf/test_builder.py +164 -0
- pyflashkit-1.0.0/tests/swf/test_parser.py +80 -0
- pyflashkit-1.0.0/tests/test_integration.py +323 -0
- pyflashkit-1.0.0/tests/workspace/__init__.py +0 -0
- pyflashkit-1.0.0/tests/workspace/test_workspace.py +206 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths-ignore:
|
|
7
|
+
- "*.md"
|
|
8
|
+
- "LICENSE"
|
|
9
|
+
- ".gitignore"
|
|
10
|
+
pull_request:
|
|
11
|
+
branches: [main]
|
|
12
|
+
paths-ignore:
|
|
13
|
+
- "*.md"
|
|
14
|
+
- "LICENSE"
|
|
15
|
+
- ".gitignore"
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
test:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
strategy:
|
|
21
|
+
matrix:
|
|
22
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ matrix.python-version }}
|
|
30
|
+
|
|
31
|
+
- name: Install
|
|
32
|
+
run: pip install -e ".[dev]"
|
|
33
|
+
|
|
34
|
+
- name: Run tests
|
|
35
|
+
run: python -m pytest -v
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- name: Install build tools
|
|
23
|
+
run: pip install build
|
|
24
|
+
|
|
25
|
+
- name: Build package
|
|
26
|
+
run: python -m build
|
|
27
|
+
|
|
28
|
+
- name: Publish to PyPI
|
|
29
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
30
|
+
|
|
31
|
+
- name: Create GitHub Release
|
|
32
|
+
uses: softprops/action-gh-release@v2
|
|
33
|
+
with:
|
|
34
|
+
files: dist/*
|
|
35
|
+
generate_release_notes: true
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Contributing to flashkit
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone <repo-url>
|
|
7
|
+
cd flashkit
|
|
8
|
+
pip install -e ".[dev]"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Running tests
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
python -m pytest
|
|
15
|
+
python -m pytest -v # verbose
|
|
16
|
+
python -m pytest tests/cli/ # just CLI tests
|
|
17
|
+
python -m pytest -k "roundtrip" # filter by name
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Project layout
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
flashkit/
|
|
24
|
+
cli/ CLI commands (one file per subcommand)
|
|
25
|
+
swf/ SWF container format
|
|
26
|
+
abc/ AVM2 bytecode parsing, writing, disassembly, builder
|
|
27
|
+
info/ Resolved class/field/method model
|
|
28
|
+
workspace/ File loading, resource management
|
|
29
|
+
analysis/ Inheritance, call graph, references, strings
|
|
30
|
+
search/ Unified query engine
|
|
31
|
+
errors.py Error hierarchy
|
|
32
|
+
|
|
33
|
+
tests/
|
|
34
|
+
abc/ ABC parser, writer, builder, disasm tests
|
|
35
|
+
swf/ SWF parser and builder tests
|
|
36
|
+
info/ ClassInfo resolution tests
|
|
37
|
+
workspace/ Workspace loading tests
|
|
38
|
+
analysis/ Analysis module tests
|
|
39
|
+
search/ Search engine tests
|
|
40
|
+
cli/ CLI integration tests
|
|
41
|
+
conftest.py Shared fixtures (build_abc_bytes, build_swf_bytes)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Writing tests
|
|
45
|
+
|
|
46
|
+
All tests use programmatically built SWFs via `AbcBuilder` and `SwfBuilder` — no real `.swf` fixture files. This keeps the repo clean and tests fast.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from flashkit.abc.builder import AbcBuilder
|
|
50
|
+
from flashkit.abc.writer import serialize_abc
|
|
51
|
+
from flashkit.swf.builder import SwfBuilder
|
|
52
|
+
|
|
53
|
+
b = AbcBuilder()
|
|
54
|
+
b.simple_class("Player", package="com.game", fields=[("hp", "int")])
|
|
55
|
+
b.script()
|
|
56
|
+
abc_bytes = serialize_abc(b.build())
|
|
57
|
+
|
|
58
|
+
swf = SwfBuilder(version=40, width=800, height=600, fps=30)
|
|
59
|
+
swf.add_abc("TestCode", abc_bytes)
|
|
60
|
+
swf_bytes = swf.build()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Adding a CLI command
|
|
64
|
+
|
|
65
|
+
1. Create `flashkit/cli/mycommand.py`:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
"""``flashkit mycommand`` — description."""
|
|
69
|
+
|
|
70
|
+
from __future__ import annotations
|
|
71
|
+
import argparse
|
|
72
|
+
from ._util import load, bold
|
|
73
|
+
|
|
74
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
75
|
+
p = sub.add_parser("mycommand", help="Short help text")
|
|
76
|
+
p.add_argument("file", help="SWF or SWZ file")
|
|
77
|
+
p.set_defaults(func=run)
|
|
78
|
+
|
|
79
|
+
def run(args: argparse.Namespace) -> None:
|
|
80
|
+
ws = load(args.file)
|
|
81
|
+
# ...
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2. Import and register it in `flashkit/cli/__init__.py`:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from . import mycommand
|
|
88
|
+
mycommand.register(sub)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
3. Add tests in `tests/cli/test_cli.py`.
|
|
92
|
+
|
|
93
|
+
## Pull requests
|
|
94
|
+
|
|
95
|
+
1. Fork the repo and create a branch from `main`.
|
|
96
|
+
2. If you added code, add tests. Run `python -m pytest` and make sure everything passes.
|
|
97
|
+
3. Keep PRs focused — one feature or fix per PR.
|
|
98
|
+
4. Use conventional commit messages for the PR title (`feat: add xyz`, `fix: handle edge case`).
|
|
99
|
+
5. Describe **what** changed and **why** in the PR body. If it's a fix, link the issue.
|
|
100
|
+
6. Make sure there are no type errors or lint warnings in the code you changed.
|
|
101
|
+
|
|
102
|
+
## Conventions
|
|
103
|
+
|
|
104
|
+
- **Zero dependencies.** Standard library only. No Click, no Rich, no Typer.
|
|
105
|
+
- **Round-trip fidelity.** `serialize(parse(data)) == data` must hold for unmodified ABC.
|
|
106
|
+
- **Conventional commits.** `feat:`, `fix:`, `test:`, `docs:`, `chore:`.
|
|
107
|
+
- **Type hints everywhere.** All public functions and methods must have type annotations.
|
|
108
|
+
- **No fixture files.** Tests build SWFs programmatically via `AbcBuilder` / `SwfBuilder`.
|
|
109
|
+
- **One command per file.** CLI commands live in `flashkit/cli/`, one module each.
|
|
110
|
+
- **Errors over silent failures.** Raise specific `FlashkitError` subclasses, never return `None` to signal failure in parsing code.
|
|
111
|
+
|
|
112
|
+
## Error handling
|
|
113
|
+
|
|
114
|
+
All public errors inherit from `FlashkitError`:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
FlashkitError
|
|
118
|
+
ParseError
|
|
119
|
+
SWFParseError
|
|
120
|
+
ABCParseError
|
|
121
|
+
SerializeError
|
|
122
|
+
ResourceError
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The CLI catches `FlashkitError` at the top level and prints a clean message. Don't let raw exceptions reach the user.
|
pyflashkit-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyflashkit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: SWF/ABC toolkit for parsing, analyzing, and manipulating Flash files and AVM2 bytecode
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Software Development :: Disassemblers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# flashkit
|
|
23
|
+
|
|
24
|
+
Parse, analyze, and manipulate Adobe Flash SWF files and AVM2 bytecode.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install -e .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from flashkit.workspace import Workspace
|
|
36
|
+
|
|
37
|
+
ws = Workspace()
|
|
38
|
+
ws.load_swf("application.swf")
|
|
39
|
+
|
|
40
|
+
# Find all classes extending Sprite
|
|
41
|
+
for cls in ws.find_classes(extends="Sprite"):
|
|
42
|
+
print(f"{cls.qualified_name} — {len(cls.fields)} fields, {len(cls.methods)} methods")
|
|
43
|
+
|
|
44
|
+
# Inspect a specific class
|
|
45
|
+
player = ws.get_class("PlayerManager")
|
|
46
|
+
print(player.super_name) # "EventDispatcher"
|
|
47
|
+
print(player.interfaces) # ["IDisposable", "ITickable"]
|
|
48
|
+
print(player.fields[0].name, player.fields[0].type_name) # "mHealth", "Number"
|
|
49
|
+
|
|
50
|
+
# Search strings used in bytecode
|
|
51
|
+
from flashkit.analysis import StringIndex
|
|
52
|
+
strings = StringIndex.from_workspace(ws)
|
|
53
|
+
for s in strings.search("config"):
|
|
54
|
+
print(s)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## CLI
|
|
60
|
+
|
|
61
|
+
### `flashkit info`
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
$ flashkit info application.swf
|
|
65
|
+
File: application.swf
|
|
66
|
+
Format: SWF
|
|
67
|
+
SWF version: 40
|
|
68
|
+
Tags: 142
|
|
69
|
+
ABC blocks: 1
|
|
70
|
+
Classes: 823
|
|
71
|
+
Methods: 14210
|
|
72
|
+
Strings: 35482
|
|
73
|
+
Packages: 47
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `flashkit classes`
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
flashkit classes app.swf # all classes
|
|
80
|
+
flashkit classes app.swf -s Manager # search by name
|
|
81
|
+
flashkit classes app.swf -p com.game # filter by package
|
|
82
|
+
flashkit classes app.swf -e Sprite # filter by superclass
|
|
83
|
+
flashkit classes app.swf -i # interfaces only
|
|
84
|
+
flashkit classes app.swf -v # verbose output
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `flashkit class`
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
$ flashkit class application.swf PlayerManager
|
|
91
|
+
PlayerManager
|
|
92
|
+
Package: com.game
|
|
93
|
+
Extends: EventDispatcher
|
|
94
|
+
Implements: IDisposable, ITickable
|
|
95
|
+
|
|
96
|
+
Instance Fields (3)
|
|
97
|
+
mHealth: Number
|
|
98
|
+
mName: String
|
|
99
|
+
mLevel: int
|
|
100
|
+
|
|
101
|
+
Instance Methods (5)
|
|
102
|
+
init(): void
|
|
103
|
+
get name(): String
|
|
104
|
+
set name(value: String): void
|
|
105
|
+
takeDamage(amount: Number): void
|
|
106
|
+
serialize(): ByteArray
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `flashkit strings`
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
flashkit strings app.swf # list all
|
|
113
|
+
flashkit strings app.swf -s config # search
|
|
114
|
+
flashkit strings app.swf -s config -v # with usage locations
|
|
115
|
+
flashkit strings app.swf -s "\\d+" -r # regex
|
|
116
|
+
flashkit strings app.swf -c # classify (URLs, debug)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `flashkit tags`
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
flashkit tags app.swf
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `flashkit disasm`
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
flashkit disasm app.swf --class PlayerManager
|
|
129
|
+
flashkit disasm app.swf --method-index 42
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `flashkit tree`
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
flashkit tree app.swf BaseEntity # show descendants
|
|
136
|
+
flashkit tree app.swf PlayerManager -a # show ancestors
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### `flashkit callers` / `flashkit callees`
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
flashkit callers app.swf toString
|
|
143
|
+
flashkit callees app.swf PlayerManager.init
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `flashkit refs`
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
flashkit refs app.swf Point
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `flashkit packages` / `flashkit extract` / `flashkit build`
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
flashkit packages app.swf # list packages
|
|
156
|
+
flashkit extract app.swf -o ./output # extract ABC blocks
|
|
157
|
+
flashkit build app.swf -o rebuilt.swf # rebuild (compressed)
|
|
158
|
+
flashkit build app.swf -o out.swf -d # rebuild (decompressed)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Library
|
|
164
|
+
|
|
165
|
+
### Load and query
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from flashkit.workspace import Workspace
|
|
169
|
+
|
|
170
|
+
ws = Workspace()
|
|
171
|
+
ws.load_swf("application.swf")
|
|
172
|
+
ws.load_swz("module.swz")
|
|
173
|
+
|
|
174
|
+
print(ws.summary())
|
|
175
|
+
|
|
176
|
+
cls = ws.get_class("MyClass")
|
|
177
|
+
print(cls.name, cls.super_name, cls.interfaces)
|
|
178
|
+
print(cls.fields) # list of FieldInfo
|
|
179
|
+
print(cls.methods) # list of MethodInfoResolved
|
|
180
|
+
|
|
181
|
+
ws.find_classes(extends="Sprite")
|
|
182
|
+
ws.find_classes(package="com.example", is_interface=True)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Analysis
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from flashkit.analysis import InheritanceGraph, CallGraph, StringIndex
|
|
189
|
+
|
|
190
|
+
graph = InheritanceGraph.from_classes(ws.classes)
|
|
191
|
+
graph.get_children("BaseEntity")
|
|
192
|
+
graph.get_all_parents("MyClass")
|
|
193
|
+
graph.get_implementors("ISerializable")
|
|
194
|
+
|
|
195
|
+
calls = CallGraph.from_workspace(ws)
|
|
196
|
+
calls.get_callers("toString")
|
|
197
|
+
calls.get_callees("MyClass.init")
|
|
198
|
+
|
|
199
|
+
strings = StringIndex.from_workspace(ws)
|
|
200
|
+
strings.search("config")
|
|
201
|
+
strings.url_strings()
|
|
202
|
+
strings.classes_using_string("http://example.com")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
<details>
|
|
206
|
+
<summary><strong>Parse SWF and ABC directly</strong></summary>
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from flashkit.swf import parse_swf, TAG_DO_ABC2
|
|
210
|
+
from flashkit.abc import parse_abc, serialize_abc
|
|
211
|
+
|
|
212
|
+
header, tags, version, length = parse_swf(swf_bytes)
|
|
213
|
+
|
|
214
|
+
for tag in tags:
|
|
215
|
+
if tag.tag_type == TAG_DO_ABC2:
|
|
216
|
+
null_idx = tag.payload.index(0, 4)
|
|
217
|
+
abc = parse_abc(tag.payload[null_idx + 1:])
|
|
218
|
+
print(f"{len(abc.instances)} classes, {len(abc.methods)} methods")
|
|
219
|
+
|
|
220
|
+
# Round-trip fidelity: serialize(parse(data)) == data
|
|
221
|
+
assert serialize_abc(abc) == tag.payload[null_idx + 1:]
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
</details>
|
|
225
|
+
|
|
226
|
+
<details>
|
|
227
|
+
<summary><strong>Build SWF programmatically</strong></summary>
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from flashkit.abc import AbcBuilder, serialize_abc
|
|
231
|
+
from flashkit.swf import SwfBuilder
|
|
232
|
+
|
|
233
|
+
b = AbcBuilder()
|
|
234
|
+
b.simple_class("Player", package="com.game",
|
|
235
|
+
fields=[("hp", "int"), ("name", "String")])
|
|
236
|
+
b.script()
|
|
237
|
+
abc_bytes = serialize_abc(b.build())
|
|
238
|
+
|
|
239
|
+
swf = SwfBuilder(version=40, width=800, height=600, fps=30)
|
|
240
|
+
swf.add_abc("GameCode", abc_bytes)
|
|
241
|
+
swf_bytes = swf.build(compress=True)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
</details>
|
|
245
|
+
|
|
246
|
+
<details>
|
|
247
|
+
<summary><strong>Disassemble method bodies</strong></summary>
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from flashkit.abc import decode_instructions
|
|
251
|
+
|
|
252
|
+
for body in abc.method_bodies:
|
|
253
|
+
for instr in decode_instructions(body.code):
|
|
254
|
+
print(f"0x{instr.offset:04X} {instr.mnemonic} {instr.operands}")
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
</details>
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Project structure
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
flashkit/
|
|
265
|
+
cli/ CLI (one module per command)
|
|
266
|
+
swf/ SWF container (parse, build, tags)
|
|
267
|
+
abc/ AVM2 bytecode (parse, write, disasm, builder)
|
|
268
|
+
info/ Resolved class model (ClassInfo, FieldInfo, MethodInfo)
|
|
269
|
+
workspace/ File loading and class index
|
|
270
|
+
analysis/ Inheritance, call graph, references, strings
|
|
271
|
+
search/ Unified query engine
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## References
|
|
275
|
+
|
|
276
|
+
- [AVM2 Overview (Adobe)](https://www.adobe.com/content/dam/acom/en/devnet/pdf/avm2overview.pdf)
|
|
277
|
+
- [SWF File Format Specification](https://open-flash.github.io/mirrors/swf-spec-19.pdf)
|
|
278
|
+
|
|
279
|
+
## License
|
|
280
|
+
|
|
281
|
+
MIT
|