pyflashkit 1.0.0__py3-none-any.whl
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.
- flashkit/__init__.py +54 -0
- flashkit/abc/__init__.py +79 -0
- flashkit/abc/builder.py +847 -0
- flashkit/abc/constants.py +198 -0
- flashkit/abc/disasm.py +364 -0
- flashkit/abc/parser.py +434 -0
- flashkit/abc/types.py +275 -0
- flashkit/abc/writer.py +230 -0
- flashkit/analysis/__init__.py +28 -0
- flashkit/analysis/call_graph.py +317 -0
- flashkit/analysis/inheritance.py +267 -0
- flashkit/analysis/references.py +371 -0
- flashkit/analysis/strings.py +299 -0
- flashkit/cli/__init__.py +75 -0
- flashkit/cli/_util.py +52 -0
- flashkit/cli/build.py +36 -0
- flashkit/cli/callees.py +30 -0
- flashkit/cli/callers.py +30 -0
- flashkit/cli/class_cmd.py +83 -0
- flashkit/cli/classes.py +71 -0
- flashkit/cli/disasm.py +77 -0
- flashkit/cli/extract.py +36 -0
- flashkit/cli/info.py +41 -0
- flashkit/cli/packages.py +30 -0
- flashkit/cli/refs.py +31 -0
- flashkit/cli/strings.py +58 -0
- flashkit/cli/tags.py +32 -0
- flashkit/cli/tree.py +52 -0
- flashkit/errors.py +33 -0
- flashkit/info/__init__.py +31 -0
- flashkit/info/class_info.py +176 -0
- flashkit/info/member_info.py +275 -0
- flashkit/info/package_info.py +60 -0
- flashkit/search/__init__.py +16 -0
- flashkit/search/search.py +456 -0
- flashkit/swf/__init__.py +66 -0
- flashkit/swf/builder.py +283 -0
- flashkit/swf/parser.py +164 -0
- flashkit/swf/tags.py +120 -0
- flashkit/workspace/__init__.py +20 -0
- flashkit/workspace/resource.py +189 -0
- flashkit/workspace/workspace.py +232 -0
- pyflashkit-1.0.0.dist-info/METADATA +281 -0
- pyflashkit-1.0.0.dist-info/RECORD +48 -0
- pyflashkit-1.0.0.dist-info/WHEEL +5 -0
- pyflashkit-1.0.0.dist-info/entry_points.txt +2 -0
- pyflashkit-1.0.0.dist-info/licenses/LICENSE +21 -0
- pyflashkit-1.0.0.dist-info/top_level.txt +1 -0
flashkit/swf/builder.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SWF container builder.
|
|
3
|
+
|
|
4
|
+
Provides functions to serialize SWF tags, rebuild complete SWF files,
|
|
5
|
+
create new DoABC2 tags, and build SWF files from scratch.
|
|
6
|
+
|
|
7
|
+
Usage — modify an existing SWF::
|
|
8
|
+
|
|
9
|
+
from flashkit.swf.parser import parse_swf
|
|
10
|
+
from flashkit.swf.builder import rebuild_swf, make_doabc2_tag
|
|
11
|
+
|
|
12
|
+
header, tags, version, length = parse_swf(swf_bytes)
|
|
13
|
+
new_tag = make_doabc2_tag("MyCode", abc_bytes)
|
|
14
|
+
tags.insert(-1, new_tag)
|
|
15
|
+
output = rebuild_swf(header, tags, compress=True)
|
|
16
|
+
|
|
17
|
+
Usage — build a SWF from scratch::
|
|
18
|
+
|
|
19
|
+
from flashkit.swf.builder import SwfBuilder
|
|
20
|
+
|
|
21
|
+
swf = SwfBuilder(version=40, width=800, height=600, fps=30)
|
|
22
|
+
swf.add_abc("MainCode", abc_bytes)
|
|
23
|
+
swf.set_document_class("com.example.Main")
|
|
24
|
+
output = swf.build(compress=True)
|
|
25
|
+
|
|
26
|
+
Reference: SWF File Format Specification v19, Chapter 2.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import struct
|
|
32
|
+
import zlib
|
|
33
|
+
|
|
34
|
+
from .tags import SWFTag, TAG_DO_ABC, TAG_DO_ABC2, TAG_END, TAG_SYMBOL_CLASS
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_tag_bytes(tag: SWFTag) -> bytes:
|
|
38
|
+
"""Serialize a single SWFTag to its binary representation.
|
|
39
|
+
|
|
40
|
+
Uses short headers (2 bytes) for small non-ABC tags and long headers
|
|
41
|
+
(6 bytes) for large payloads or DoABC/DoABC2 tags.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
tag: The SWFTag to serialize.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Raw bytes for this tag (header + payload).
|
|
48
|
+
"""
|
|
49
|
+
payload_len = len(tag.payload)
|
|
50
|
+
if payload_len < 0x3F and tag.tag_type not in (TAG_DO_ABC, TAG_DO_ABC2):
|
|
51
|
+
# Short header — only for small non-ABC tags
|
|
52
|
+
header = struct.pack("<H", (tag.tag_type << 6) | payload_len)
|
|
53
|
+
return header + tag.payload
|
|
54
|
+
else:
|
|
55
|
+
# Long header
|
|
56
|
+
header = struct.pack("<H", (tag.tag_type << 6) | 0x3F)
|
|
57
|
+
header += struct.pack("<I", payload_len)
|
|
58
|
+
return header + tag.payload
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def rebuild_swf(
|
|
62
|
+
header_bytes: bytes,
|
|
63
|
+
tags: list[SWFTag],
|
|
64
|
+
compress: bool = True,
|
|
65
|
+
) -> bytes:
|
|
66
|
+
"""Rebuild a complete SWF file from header and tags.
|
|
67
|
+
|
|
68
|
+
Recomputes the file length field and optionally zlib-compresses
|
|
69
|
+
the output.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
header_bytes: Original SWF header (from ``parse_swf()``).
|
|
73
|
+
tags: List of SWFTag objects.
|
|
74
|
+
compress: If True, output CWS (zlib-compressed). Default True.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Complete SWF file bytes.
|
|
78
|
+
"""
|
|
79
|
+
body = b""
|
|
80
|
+
for tag in tags:
|
|
81
|
+
body += build_tag_bytes(tag)
|
|
82
|
+
|
|
83
|
+
raw = header_bytes + body
|
|
84
|
+
# Update file length field (bytes 4-7)
|
|
85
|
+
raw = raw[:4] + struct.pack("<I", len(raw)) + raw[8:]
|
|
86
|
+
|
|
87
|
+
if compress:
|
|
88
|
+
return b"CWS" + raw[3:8] + zlib.compress(raw[8:], 9)
|
|
89
|
+
else:
|
|
90
|
+
return raw
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def make_doabc2_tag(
|
|
94
|
+
name: str,
|
|
95
|
+
abc_data: bytes,
|
|
96
|
+
lazy_init: bool = True,
|
|
97
|
+
) -> SWFTag:
|
|
98
|
+
"""Create a DoABC2 tag (type 82).
|
|
99
|
+
|
|
100
|
+
DoABC2 tags contain flags, a null-terminated name, and the ABC
|
|
101
|
+
bytecode data.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
name: Name string for the ABC block.
|
|
105
|
+
abc_data: Raw ABC bytecode bytes.
|
|
106
|
+
lazy_init: If True, set the lazy initialization flag. Default True.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
A new SWFTag with tag_type=82.
|
|
110
|
+
"""
|
|
111
|
+
flags = struct.pack("<I", 1 if lazy_init else 0)
|
|
112
|
+
name_bytes = name.encode("utf-8") + b"\x00"
|
|
113
|
+
payload = flags + name_bytes + abc_data
|
|
114
|
+
return SWFTag(tag_type=TAG_DO_ABC2, payload=payload, name=name)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def make_symbol_class_tag(symbols: list[tuple[int, str]]) -> SWFTag:
|
|
118
|
+
"""Create a SymbolClass tag (type 76).
|
|
119
|
+
|
|
120
|
+
Maps character IDs to class names. Character ID 0 with a class
|
|
121
|
+
name defines the document class.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
symbols: List of (character_id, class_name) pairs.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
A new SWFTag with tag_type=76.
|
|
128
|
+
"""
|
|
129
|
+
payload = struct.pack("<H", len(symbols))
|
|
130
|
+
for char_id, name in symbols:
|
|
131
|
+
payload += struct.pack("<H", char_id)
|
|
132
|
+
payload += name.encode("utf-8") + b"\x00"
|
|
133
|
+
return SWFTag(tag_type=TAG_SYMBOL_CLASS, payload=payload)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def make_end_tag() -> SWFTag:
|
|
137
|
+
"""Create an End tag (type 0)."""
|
|
138
|
+
return SWFTag(tag_type=TAG_END, payload=b"")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class SwfBuilder:
|
|
142
|
+
"""High-level builder for constructing SWF files from scratch.
|
|
143
|
+
|
|
144
|
+
Handles the header, RECT, frame rate, and tag assembly.
|
|
145
|
+
Add ABC blocks and symbol mappings, then call ``build()``.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
version: SWF version number. Default 40.
|
|
149
|
+
width: Stage width in pixels. Default 800.
|
|
150
|
+
height: Stage height in pixels. Default 600.
|
|
151
|
+
fps: Frame rate. Default 24.
|
|
152
|
+
frame_count: Total frames. Default 1.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
version: int = 40,
|
|
158
|
+
width: int = 800,
|
|
159
|
+
height: int = 600,
|
|
160
|
+
fps: int = 24,
|
|
161
|
+
frame_count: int = 1,
|
|
162
|
+
) -> None:
|
|
163
|
+
self.version = version
|
|
164
|
+
self.width = width
|
|
165
|
+
self.height = height
|
|
166
|
+
self.fps = fps
|
|
167
|
+
self.frame_count = frame_count
|
|
168
|
+
self._tags: list[SWFTag] = []
|
|
169
|
+
self._symbols: list[tuple[int, str]] = []
|
|
170
|
+
|
|
171
|
+
def add_tag(self, tag: SWFTag) -> None:
|
|
172
|
+
"""Add a raw SWF tag.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
tag: Any SWFTag to include before the End tag.
|
|
176
|
+
"""
|
|
177
|
+
self._tags.append(tag)
|
|
178
|
+
|
|
179
|
+
def add_abc(self, name: str, abc_data: bytes,
|
|
180
|
+
lazy_init: bool = True) -> None:
|
|
181
|
+
"""Add an ABC bytecode block as a DoABC2 tag.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
name: Name for the ABC block.
|
|
185
|
+
abc_data: Serialized ABC bytes.
|
|
186
|
+
lazy_init: Whether to set the lazy init flag.
|
|
187
|
+
"""
|
|
188
|
+
self._tags.append(make_doabc2_tag(name, abc_data, lazy_init))
|
|
189
|
+
|
|
190
|
+
def add_symbol(self, char_id: int, class_name: str) -> None:
|
|
191
|
+
"""Map a character ID to a class name.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
char_id: Character ID (0 for document class).
|
|
195
|
+
class_name: Fully qualified class name.
|
|
196
|
+
"""
|
|
197
|
+
self._symbols.append((char_id, class_name))
|
|
198
|
+
|
|
199
|
+
def set_document_class(self, class_name: str) -> None:
|
|
200
|
+
"""Set the document class (character ID 0).
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
class_name: Fully qualified class name.
|
|
204
|
+
"""
|
|
205
|
+
self.add_symbol(0, class_name)
|
|
206
|
+
|
|
207
|
+
def _build_header(self) -> bytes:
|
|
208
|
+
"""Build the SWF header bytes (before tags)."""
|
|
209
|
+
# Encode RECT in twips (1 pixel = 20 twips)
|
|
210
|
+
xmax = self.width * 20
|
|
211
|
+
ymax = self.height * 20
|
|
212
|
+
# Calculate nbits needed
|
|
213
|
+
max_val = max(xmax, ymax)
|
|
214
|
+
nbits = max_val.bit_length() if max_val > 0 else 1
|
|
215
|
+
|
|
216
|
+
# Pack RECT as bits: 5-bit nbits + 4 fields of nbits each
|
|
217
|
+
# Xmin=0, Xmax, Ymin=0, Ymax
|
|
218
|
+
total_bits = 5 + 4 * nbits
|
|
219
|
+
total_bytes = (total_bits + 7) // 8
|
|
220
|
+
rect = bytearray(total_bytes)
|
|
221
|
+
|
|
222
|
+
# Write bits
|
|
223
|
+
bit_pos = 0
|
|
224
|
+
|
|
225
|
+
def write_bits(value: int, count: int) -> None:
|
|
226
|
+
nonlocal bit_pos
|
|
227
|
+
for i in range(count - 1, -1, -1):
|
|
228
|
+
byte_idx = bit_pos // 8
|
|
229
|
+
bit_idx = 7 - (bit_pos % 8)
|
|
230
|
+
if value & (1 << i):
|
|
231
|
+
rect[byte_idx] |= (1 << bit_idx)
|
|
232
|
+
bit_pos += 1
|
|
233
|
+
|
|
234
|
+
write_bits(nbits, 5)
|
|
235
|
+
write_bits(0, nbits) # Xmin
|
|
236
|
+
write_bits(xmax, nbits) # Xmax
|
|
237
|
+
write_bits(0, nbits) # Ymin
|
|
238
|
+
write_bits(ymax, nbits) # Ymax
|
|
239
|
+
|
|
240
|
+
# Frame rate (8.8 fixed point) + frame count
|
|
241
|
+
frame_rate = struct.pack("<BB", 0, self.fps)
|
|
242
|
+
frame_count = struct.pack("<H", self.frame_count)
|
|
243
|
+
|
|
244
|
+
header = bytearray()
|
|
245
|
+
header += b"FWS"
|
|
246
|
+
header += bytes([self.version])
|
|
247
|
+
header += struct.pack("<I", 0) # file length (filled later)
|
|
248
|
+
header += rect
|
|
249
|
+
header += frame_rate
|
|
250
|
+
header += frame_count
|
|
251
|
+
return bytes(header)
|
|
252
|
+
|
|
253
|
+
def build(self, compress: bool = True) -> bytes:
|
|
254
|
+
"""Build the complete SWF file.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
compress: If True, produce CWS (zlib-compressed). Default True.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Complete SWF file bytes.
|
|
261
|
+
"""
|
|
262
|
+
header = self._build_header()
|
|
263
|
+
|
|
264
|
+
# Assemble tags
|
|
265
|
+
tags_bytes = bytearray()
|
|
266
|
+
for tag in self._tags:
|
|
267
|
+
tags_bytes += build_tag_bytes(tag)
|
|
268
|
+
|
|
269
|
+
# Add SymbolClass if symbols were defined
|
|
270
|
+
if self._symbols:
|
|
271
|
+
tags_bytes += build_tag_bytes(make_symbol_class_tag(self._symbols))
|
|
272
|
+
|
|
273
|
+
# End tag
|
|
274
|
+
tags_bytes += build_tag_bytes(make_end_tag())
|
|
275
|
+
|
|
276
|
+
# Combine and set file length
|
|
277
|
+
raw = bytearray(header) + tags_bytes
|
|
278
|
+
struct.pack_into("<I", raw, 4, len(raw))
|
|
279
|
+
|
|
280
|
+
if compress:
|
|
281
|
+
return b"CWS" + bytes(raw[3:8]) + zlib.compress(bytes(raw[8:]), 9)
|
|
282
|
+
else:
|
|
283
|
+
return bytes(raw)
|
flashkit/swf/parser.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SWF container parser.
|
|
3
|
+
|
|
4
|
+
Parses SWF files (compressed or uncompressed) into a header and a list
|
|
5
|
+
of ``SWFTag`` objects. Handles CWS (zlib-compressed) and FWS (uncompressed)
|
|
6
|
+
signatures.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from flashkit.swf.parser import parse_swf
|
|
11
|
+
|
|
12
|
+
with open("application.swf", "rb") as f:
|
|
13
|
+
header, tags, version, file_length = parse_swf(f.read())
|
|
14
|
+
|
|
15
|
+
for tag in tags:
|
|
16
|
+
print(f"{tag.type_name}: {len(tag.payload)} bytes")
|
|
17
|
+
|
|
18
|
+
Reference: SWF File Format Specification v19, Chapter 1-2.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import struct
|
|
24
|
+
import zlib
|
|
25
|
+
|
|
26
|
+
from ..errors import SWFParseError
|
|
27
|
+
from .tags import SWFTag, TAG_NAMES, TAG_END, TAG_DO_ABC, TAG_DO_ABC2, TAG_SYMBOL_CLASS
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _parse_rect_size(data: bytes, offset: int) -> int:
|
|
31
|
+
"""Calculate how many bytes a RECT structure occupies.
|
|
32
|
+
|
|
33
|
+
The RECT is a bit-packed structure where the first 5 bits encode
|
|
34
|
+
the number of bits per field (Xmin, Xmax, Ymin, Ymax).
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
data: Raw SWF bytes.
|
|
38
|
+
offset: Byte offset where the RECT starts.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Number of bytes the RECT occupies.
|
|
42
|
+
"""
|
|
43
|
+
nbits = (data[offset] >> 3) & 0x1F
|
|
44
|
+
total_bits = 5 + 4 * nbits
|
|
45
|
+
return (total_bits + 7) // 8
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def parse_swf(data: bytes) -> tuple[bytes, list[SWFTag], int, int]:
|
|
49
|
+
"""Parse a SWF file (compressed or uncompressed).
|
|
50
|
+
|
|
51
|
+
Handles both CWS (zlib-compressed) and FWS (uncompressed) formats.
|
|
52
|
+
The returned header bytes include everything up to the first tag
|
|
53
|
+
(signature, version, file length, RECT, frame rate, frame count).
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
data: Raw SWF file bytes.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Tuple of (header_bytes, tags, version, file_length).
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
SWFParseError: If the data is not a valid SWF file.
|
|
63
|
+
"""
|
|
64
|
+
if not data:
|
|
65
|
+
raise SWFParseError("SWF data is empty")
|
|
66
|
+
if len(data) < 8:
|
|
67
|
+
raise SWFParseError(
|
|
68
|
+
f"SWF data too short ({len(data)} bytes, minimum 8)")
|
|
69
|
+
|
|
70
|
+
sig = data[:3]
|
|
71
|
+
if sig == b"CWS":
|
|
72
|
+
try:
|
|
73
|
+
raw = data[:8] + zlib.decompress(data[8:])
|
|
74
|
+
except zlib.error as e:
|
|
75
|
+
raise SWFParseError(
|
|
76
|
+
f"Failed to decompress CWS data: {e}") from e
|
|
77
|
+
raw = b"FWS" + raw[3:] # fix signature to uncompressed
|
|
78
|
+
elif sig == b"FWS":
|
|
79
|
+
raw = data
|
|
80
|
+
else:
|
|
81
|
+
raise SWFParseError(f"Not a SWF file (signature: {sig!r})")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
version = raw[3]
|
|
85
|
+
file_length = struct.unpack_from("<I", raw, 4)[0]
|
|
86
|
+
|
|
87
|
+
# RECT + frame rate (2 bytes) + frame count (2 bytes)
|
|
88
|
+
rect_size = _parse_rect_size(raw, 8)
|
|
89
|
+
header_end = 8 + rect_size + 4
|
|
90
|
+
header_bytes = raw[:header_end]
|
|
91
|
+
|
|
92
|
+
# Parse tags
|
|
93
|
+
tags: list[SWFTag] = []
|
|
94
|
+
pos = header_end
|
|
95
|
+
while pos < len(raw) - 1:
|
|
96
|
+
tag_raw = struct.unpack_from("<H", raw, pos)[0]
|
|
97
|
+
tag_type = (tag_raw >> 6) & 0x3FF
|
|
98
|
+
tag_len = tag_raw & 0x3F
|
|
99
|
+
header_size = 2
|
|
100
|
+
|
|
101
|
+
if tag_len == 0x3F:
|
|
102
|
+
# Extended length: next 4 bytes are the actual length
|
|
103
|
+
tag_len = struct.unpack_from("<I", raw, pos + 2)[0]
|
|
104
|
+
header_size = 6
|
|
105
|
+
|
|
106
|
+
payload = raw[pos + header_size: pos + header_size + tag_len]
|
|
107
|
+
tag = SWFTag(tag_type=tag_type, payload=payload)
|
|
108
|
+
|
|
109
|
+
# Extract name from DoABC2 tags (4-byte flags + null-terminated name)
|
|
110
|
+
if tag_type == TAG_DO_ABC2 and len(payload) > 4:
|
|
111
|
+
null_idx = payload.index(0, 4)
|
|
112
|
+
tag.name = payload[4:null_idx].decode("utf-8", errors="replace")
|
|
113
|
+
|
|
114
|
+
tags.append(tag)
|
|
115
|
+
|
|
116
|
+
if tag_type == TAG_END:
|
|
117
|
+
break
|
|
118
|
+
pos += header_size + tag_len
|
|
119
|
+
|
|
120
|
+
except SWFParseError:
|
|
121
|
+
raise
|
|
122
|
+
except (IndexError, struct.error, ValueError, OverflowError) as e:
|
|
123
|
+
raise SWFParseError(f"Corrupted SWF data: {e}") from e
|
|
124
|
+
|
|
125
|
+
return header_bytes, tags, version, file_length
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def print_tags(tags: list[SWFTag]) -> None:
|
|
129
|
+
"""Pretty-print a SWF tag list to stdout.
|
|
130
|
+
|
|
131
|
+
Shows tag index, type, name, size, and extra info for known tag types
|
|
132
|
+
(ABC version for DoABC, symbol names for SymbolClass, etc.).
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
tags: List of SWFTag objects from ``parse_swf()``.
|
|
136
|
+
"""
|
|
137
|
+
for i, tag in enumerate(tags):
|
|
138
|
+
extra = ""
|
|
139
|
+
if tag.tag_type == TAG_DO_ABC2:
|
|
140
|
+
extra = f' name="{tag.name}"'
|
|
141
|
+
elif tag.tag_type == TAG_DO_ABC:
|
|
142
|
+
if len(tag.payload) >= 4:
|
|
143
|
+
minor, major = struct.unpack_from("<HH", tag.payload, 0)
|
|
144
|
+
extra = f" ABC v{minor}.{major}"
|
|
145
|
+
elif tag.tag_type == TAG_SYMBOL_CLASS:
|
|
146
|
+
if len(tag.payload) >= 2:
|
|
147
|
+
count = struct.unpack_from("<H", tag.payload, 0)[0]
|
|
148
|
+
extra = f" {count} symbol(s)"
|
|
149
|
+
off = 2
|
|
150
|
+
for j in range(min(count, 5)):
|
|
151
|
+
cid = struct.unpack_from("<H", tag.payload, off)[0]
|
|
152
|
+
off += 2
|
|
153
|
+
null_idx = tag.payload.index(0, off)
|
|
154
|
+
sname = tag.payload[off:null_idx].decode(
|
|
155
|
+
"utf-8", errors="replace")
|
|
156
|
+
off = null_idx + 1
|
|
157
|
+
doc = " [DOCUMENT CLASS]" if cid == 0 else ""
|
|
158
|
+
extra += (
|
|
159
|
+
f'\n CharID={cid} -> "{sname}"{doc}')
|
|
160
|
+
|
|
161
|
+
size_str = f"{len(tag.payload):>10,}"
|
|
162
|
+
print(
|
|
163
|
+
f" [{i:2d}] Tag {tag.tag_type:3d} "
|
|
164
|
+
f"({tag.type_name:<30s}) {size_str} bytes{extra}")
|
flashkit/swf/tags.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SWF tag definitions and constants.
|
|
3
|
+
|
|
4
|
+
Defines the ``SWFTag`` dataclass and well-known SWF tag type IDs.
|
|
5
|
+
The tag type constants eliminate magic numbers when working with
|
|
6
|
+
parsed SWF tag lists.
|
|
7
|
+
|
|
8
|
+
Reference: SWF File Format Specification v19, Chapter 2 (SWF structure).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ── Tag type constants ──────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
TAG_END = 0
|
|
19
|
+
TAG_SHOW_FRAME = 1
|
|
20
|
+
TAG_SET_BACKGROUND_COLOR = 9
|
|
21
|
+
TAG_DEFINE_SHAPE = 2
|
|
22
|
+
TAG_DEFINE_BITS = 6
|
|
23
|
+
TAG_DEFINE_BUTTON = 7
|
|
24
|
+
TAG_JPEG_TABLES = 8
|
|
25
|
+
TAG_DEFINE_TEXT = 11
|
|
26
|
+
TAG_DO_ACTION = 12
|
|
27
|
+
TAG_DEFINE_SOUND = 14
|
|
28
|
+
TAG_DEFINE_BITS_LOSSLESS = 20
|
|
29
|
+
TAG_DEFINE_BITS_JPEG2 = 21
|
|
30
|
+
TAG_PLACE_OBJECT2 = 26
|
|
31
|
+
TAG_REMOVE_OBJECT2 = 28
|
|
32
|
+
TAG_DEFINE_SHAPE3 = 32
|
|
33
|
+
TAG_DEFINE_TEXT2 = 33
|
|
34
|
+
TAG_DEFINE_BITS_JPEG3 = 35
|
|
35
|
+
TAG_DEFINE_BITS_LOSSLESS2 = 36
|
|
36
|
+
TAG_DEFINE_EDIT_TEXT = 37
|
|
37
|
+
TAG_DEFINE_SPRITE = 39
|
|
38
|
+
TAG_FRAME_LABEL = 43
|
|
39
|
+
TAG_DEFINE_MORPH_SHAPE = 46
|
|
40
|
+
TAG_DEFINE_FONT2 = 48
|
|
41
|
+
TAG_EXPORT_ASSETS = 56
|
|
42
|
+
TAG_IMPORT_ASSETS = 57
|
|
43
|
+
TAG_DO_INIT_ACTION = 59
|
|
44
|
+
TAG_DEFINE_VIDEO_STREAM = 60
|
|
45
|
+
TAG_DEFINE_FONT3 = 75
|
|
46
|
+
TAG_SCRIPT_LIMITS = 65
|
|
47
|
+
TAG_FILE_ATTRIBUTES = 69
|
|
48
|
+
TAG_PLACE_OBJECT3 = 70
|
|
49
|
+
TAG_DO_ABC = 72 # Raw ABC bytecode (AVM2)
|
|
50
|
+
TAG_SYMBOL_CLASS = 76 # Maps character IDs to class names
|
|
51
|
+
TAG_DEFINE_BINARY_DATA = 77
|
|
52
|
+
TAG_DO_ABC2 = 82 # Named ABC bytecode (flags + name + ABC)
|
|
53
|
+
TAG_DEFINE_SCENE_AND_FRAME_LABEL = 86
|
|
54
|
+
TAG_DEBUG_ID = 255
|
|
55
|
+
|
|
56
|
+
# ── Human-readable tag names ────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
TAG_NAMES: dict[int, str] = {
|
|
59
|
+
TAG_END: "End",
|
|
60
|
+
TAG_SHOW_FRAME: "ShowFrame",
|
|
61
|
+
TAG_SET_BACKGROUND_COLOR: "SetBackgroundColor",
|
|
62
|
+
TAG_DEFINE_SHAPE: "DefineShape",
|
|
63
|
+
TAG_DEFINE_BITS: "DefineBits",
|
|
64
|
+
TAG_DEFINE_BUTTON: "DefineButton",
|
|
65
|
+
TAG_JPEG_TABLES: "JPEGTables",
|
|
66
|
+
TAG_DEFINE_TEXT: "DefineText",
|
|
67
|
+
TAG_DO_ACTION: "DoAction",
|
|
68
|
+
TAG_DEFINE_SOUND: "DefineSound",
|
|
69
|
+
TAG_DEFINE_BITS_LOSSLESS: "DefineBitsLossless",
|
|
70
|
+
TAG_DEFINE_BITS_JPEG2: "DefineBitsJPEG2",
|
|
71
|
+
TAG_PLACE_OBJECT2: "PlaceObject2",
|
|
72
|
+
TAG_REMOVE_OBJECT2: "RemoveObject2",
|
|
73
|
+
TAG_DEFINE_SHAPE3: "DefineShape3",
|
|
74
|
+
TAG_DEFINE_TEXT2: "DefineText2",
|
|
75
|
+
TAG_DEFINE_BITS_JPEG3: "DefineBitsJPEG3",
|
|
76
|
+
TAG_DEFINE_BITS_LOSSLESS2: "DefineBitsLossless2",
|
|
77
|
+
TAG_DEFINE_EDIT_TEXT: "DefineEditText",
|
|
78
|
+
TAG_DEFINE_SPRITE: "DefineSprite",
|
|
79
|
+
TAG_FRAME_LABEL: "FrameLabel",
|
|
80
|
+
TAG_DEFINE_MORPH_SHAPE: "DefineMorphShape",
|
|
81
|
+
TAG_DEFINE_FONT2: "DefineFont2",
|
|
82
|
+
TAG_EXPORT_ASSETS: "ExportAssets",
|
|
83
|
+
TAG_IMPORT_ASSETS: "ImportAssets",
|
|
84
|
+
TAG_DO_INIT_ACTION: "DoInitAction",
|
|
85
|
+
TAG_DEFINE_VIDEO_STREAM: "DefineVideoStream",
|
|
86
|
+
TAG_DEFINE_FONT3: "DefineFont3",
|
|
87
|
+
TAG_SCRIPT_LIMITS: "ScriptLimits",
|
|
88
|
+
TAG_FILE_ATTRIBUTES: "FileAttributes",
|
|
89
|
+
TAG_PLACE_OBJECT3: "PlaceObject3",
|
|
90
|
+
TAG_DO_ABC: "DoABC",
|
|
91
|
+
TAG_SYMBOL_CLASS: "SymbolClass",
|
|
92
|
+
TAG_DEFINE_BINARY_DATA: "DefineBinaryData",
|
|
93
|
+
TAG_DO_ABC2: "DoABC2",
|
|
94
|
+
TAG_DEFINE_SCENE_AND_FRAME_LABEL: "DefineSceneAndFrameLabelData",
|
|
95
|
+
TAG_DEBUG_ID: "DebugID",
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ── SWFTag dataclass ────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class SWFTag:
|
|
103
|
+
"""A single tag from a SWF file.
|
|
104
|
+
|
|
105
|
+
SWF files are a sequence of typed tags. Each tag has a type ID,
|
|
106
|
+
a payload, and optionally a name (for DoABC2 tags).
|
|
107
|
+
|
|
108
|
+
Attributes:
|
|
109
|
+
tag_type: Numeric tag type ID (see TAG_* constants).
|
|
110
|
+
payload: Raw tag payload bytes.
|
|
111
|
+
name: Tag name string (populated for DoABC2 tags).
|
|
112
|
+
"""
|
|
113
|
+
tag_type: int
|
|
114
|
+
payload: bytes
|
|
115
|
+
name: str = ""
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def type_name(self) -> str:
|
|
119
|
+
"""Human-readable tag type name."""
|
|
120
|
+
return TAG_NAMES.get(self.tag_type, f"Unknown({self.tag_type})")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workspace and resource management.
|
|
3
|
+
|
|
4
|
+
A ``Workspace`` loads one or more SWF/SWZ files, extracts their ABC
|
|
5
|
+
bytecode, parses it, and builds a unified index of all classes, methods,
|
|
6
|
+
and strings. It is the top-level entry point for analysis.
|
|
7
|
+
|
|
8
|
+
A ``Resource`` represents a single loaded file (SWF or SWZ) with its
|
|
9
|
+
parsed tags and ABC content.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .resource import Resource, load_swf, load_swz
|
|
13
|
+
from .workspace import Workspace
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Workspace",
|
|
17
|
+
"Resource",
|
|
18
|
+
"load_swf",
|
|
19
|
+
"load_swz",
|
|
20
|
+
]
|