1
0
mirror of synced 2025-02-25 22:38:00 +01:00

Refactor verbose debugging to its own class, clean up classes that used it.

This commit is contained in:
Jennifer Taylor 2021-04-11 20:45:17 +00:00
parent 897e779b20
commit 522f8eaa29
3 changed files with 212 additions and 237 deletions

View File

@ -1,7 +1,6 @@
import io import io
import os import os
import struct import struct
import sys
from PIL import Image # type: ignore from PIL import Image # type: ignore
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
@ -12,7 +11,7 @@ from bemani.protocol.node import Node
from .swf import SWF from .swf import SWF
from .geo import Shape from .geo import Shape
from .util import TrackedCoverage, scramble_text, descramble_text, pad, align, _hex from .util import TrackedCoverage, VerboseOutput, scramble_text, descramble_text, pad, align, _hex
class PMAN: class PMAN:
@ -140,7 +139,7 @@ class Unknown2:
} }
class TXP2File(TrackedCoverage): class TXP2File(TrackedCoverage, VerboseOutput):
def __init__(self, contents: bytes, verbose: bool = False) -> None: def __init__(self, contents: bytes, verbose: bool = False) -> None:
# Make sure our coverage engine is initialized. # Make sure our coverage engine is initialized.
super().__init__() super().__init__()
@ -213,7 +212,8 @@ class TXP2File(TrackedCoverage):
# Parse out the file structure. # Parse out the file structure.
with self.covered(len(contents), verbose): with self.covered(len(contents), verbose):
self.__parse(verbose) with self.debugging(verbose):
self.__parse(verbose)
def as_dict(self) -> Dict[str, Any]: def as_dict(self) -> Dict[str, Any]:
return { return {
@ -265,15 +265,7 @@ class TXP2File(TrackedCoverage):
offset += 1 offset += 1
return out return out
def descramble_pman(self, offset: int, verbose: bool) -> PMAN: def descramble_pman(self, offset: int) -> PMAN:
# Suppress debug text unless asked
if verbose:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
print(*args, **kwargs, file=sys.stderr)
else:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
pass
# Unclear what the first three unknowns are, but the fourth # Unclear what the first three unknowns are, but the fourth
# looks like it could possibly be two int16s indicating unknown? # looks like it could possibly be two int16s indicating unknown?
magic, expect_zero, flags1, flags2, numentries, flags3, data_offset = struct.unpack( magic, expect_zero, flags1, flags2, numentries, flags3, data_offset = struct.unpack(
@ -312,7 +304,7 @@ class TXP2File(TrackedCoverage):
name = descramble_text(bytedata, self.text_obfuscated) name = descramble_text(bytedata, self.text_obfuscated)
names[entry_no] = name names[entry_no] = name
ordering[entry_no] = i ordering[entry_no] = i
vprint(f" {entry_no}: {name}, offset: {hex(nameoffset)}") self.vprint(f" {entry_no}: {name}, offset: {hex(nameoffset)}")
if name_crc != TXP2File.crc32(name.encode('ascii')): if name_crc != TXP2File.crc32(name.encode('ascii')):
raise Exception(f"Name CRC failed for {name}") raise Exception(f"Name CRC failed for {name}")
@ -333,18 +325,7 @@ class TXP2File(TrackedCoverage):
flags3=flags3, flags3=flags3,
) )
def __parse( def __parse(self, verbose: bool) -> None:
self,
verbose: bool = False,
) -> None:
# Suppress debug text unless asked
if verbose:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
print(*args, **kwargs, file=sys.stderr)
else:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
pass
# First, check the signature # First, check the signature
if self.data[0:4] == b"2PXT": if self.data[0:4] == b"2PXT":
self.endian = "<" self.endian = "<"
@ -389,7 +370,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 8) self.add_coverage(header_offset, 8)
header_offset += 8 header_offset += 8
vprint(f"Bit 0x000001 - textures; count: {length}, offset: {hex(offset)}") self.vprint(f"Bit 0x000001 - textures; count: {length}, offset: {hex(offset)}")
for x in range(length): for x in range(length):
interesting_offset = offset + (x * 12) interesting_offset = offset + (x * 12)
@ -418,7 +399,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(texture_offset, 8) self.add_coverage(texture_offset, 8)
if deflated_size != (texture_length - 8): if deflated_size != (texture_length - 8):
raise Exception("We got an incorrect length for lz texture!") raise Exception("We got an incorrect length for lz texture!")
vprint(f" {name}, length: {texture_length}, offset: {hex(texture_offset)}, deflated_size: {deflated_size}, inflated_size: {inflated_size}") self.vprint(f" {name}, length: {texture_length}, offset: {hex(texture_offset)}, deflated_size: {deflated_size}, inflated_size: {inflated_size}")
inflated_size = (inflated_size + 3) & (~3) inflated_size = (inflated_size + 3) & (~3)
# Get the data offset. # Get the data offset.
@ -439,7 +420,7 @@ class TXP2File(TrackedCoverage):
# I assume they're like the above, so lets put in some asertions. # I assume they're like the above, so lets put in some asertions.
if deflated_size != (texture_length - 8): if deflated_size != (texture_length - 8):
raise Exception("We got an incorrect length for raw texture!") raise Exception("We got an incorrect length for raw texture!")
vprint(f" {name}, length: {texture_length}, offset: {hex(texture_offset)}, deflated_size: {deflated_size}, inflated_size: {inflated_size}") self.vprint(f" {name}, length: {texture_length}, offset: {hex(texture_offset)}, deflated_size: {deflated_size}, inflated_size: {inflated_size}")
# Just grab the raw data. # Just grab the raw data.
lz_data = None lz_data = None
@ -633,7 +614,7 @@ class TXP2File(TrackedCoverage):
'RGBA', (width, height), raw_data[64:], 'raw', 'BGRA', 'RGBA', (width, height), raw_data[64:], 'raw', 'BGRA',
) )
else: else:
vprint(f"Unsupported format {hex(fmt)} for texture {name}") self.vprint(f"Unsupported format {hex(fmt)} for texture {name}")
img = None img = None
self.textures.append( self.textures.append(
@ -652,7 +633,7 @@ class TXP2File(TrackedCoverage):
) )
) )
else: else:
vprint("Bit 0x000001 - textures; NOT PRESENT") self.vprint("Bit 0x000001 - textures; NOT PRESENT")
# Mapping between texture index and the name of the texture. # Mapping between texture index and the name of the texture.
if feature_mask & 0x02: if feature_mask & 0x02:
@ -661,17 +642,17 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 4) self.add_coverage(header_offset, 4)
header_offset += 4 header_offset += 4
vprint(f"Bit 0x000002 - texturemapping; offset: {hex(offset)}") self.vprint(f"Bit 0x000002 - texturemapping; offset: {hex(offset)}")
if offset != 0: if offset != 0:
self.texturemap = self.descramble_pman(offset, verbose) self.texturemap = self.descramble_pman(offset)
else: else:
vprint("Bit 0x000002 - texturemapping; NOT PRESENT") self.vprint("Bit 0x000002 - texturemapping; NOT PRESENT")
if feature_mask & 0x04: if feature_mask & 0x04:
vprint("Bit 0x000004 - legacy lz mode on") self.vprint("Bit 0x000004 - legacy lz mode on")
else: else:
vprint("Bit 0x000004 - legacy lz mode off") self.vprint("Bit 0x000004 - legacy lz mode off")
# Mapping between region index and the texture it goes to as well as the # Mapping between region index and the texture it goes to as well as the
# region of texture that this particular graphic makes up. # region of texture that this particular graphic makes up.
@ -683,7 +664,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 8) self.add_coverage(header_offset, 8)
header_offset += 8 header_offset += 8
vprint(f"Bit 0x000008 - regions; count: {length}, offset: {hex(offset)}") self.vprint(f"Bit 0x000008 - regions; count: {length}, offset: {hex(offset)}")
if offset != 0 and length > 0: if offset != 0 and length > 0:
for i in range(length): for i in range(length):
@ -702,9 +683,9 @@ class TXP2File(TrackedCoverage):
region = TextureRegion(texture_no, left, top, right, bottom) region = TextureRegion(texture_no, left, top, right, bottom)
self.texture_to_region.append(region) self.texture_to_region.append(region)
vprint(f" {region}, offset: {hex(descriptor_offset)}") self.vprint(f" {region}, offset: {hex(descriptor_offset)}")
else: else:
vprint("Bit 0x000008 - regions; NOT PRESENT") self.vprint("Bit 0x000008 - regions; NOT PRESENT")
if feature_mask & 0x10: if feature_mask & 0x10:
# Names of the graphics regions, so we can look into the texture_to_region # Names of the graphics regions, so we can look into the texture_to_region
@ -713,17 +694,17 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 4) self.add_coverage(header_offset, 4)
header_offset += 4 header_offset += 4
vprint(f"Bit 0x000010 - regionmapping; offset: {hex(offset)}") self.vprint(f"Bit 0x000010 - regionmapping; offset: {hex(offset)}")
if offset != 0: if offset != 0:
self.regionmap = self.descramble_pman(offset, verbose) self.regionmap = self.descramble_pman(offset)
else: else:
vprint("Bit 0x000010 - regionmapping; NOT PRESENT") self.vprint("Bit 0x000010 - regionmapping; NOT PRESENT")
if feature_mask & 0x20: if feature_mask & 0x20:
vprint("Bit 0x000020 - text obfuscation on") self.vprint("Bit 0x000020 - text obfuscation on")
else: else:
vprint("Bit 0x000020 - text obfuscation off") self.vprint("Bit 0x000020 - text obfuscation off")
if feature_mask & 0x40: if feature_mask & 0x40:
# Two unknown bytes, first is a length or a count. Secound is # Two unknown bytes, first is a length or a count. Secound is
@ -732,7 +713,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 8) self.add_coverage(header_offset, 8)
header_offset += 8 header_offset += 8
vprint(f"Bit 0x000040 - unknown; count: {length}, offset: {hex(offset)}") self.vprint(f"Bit 0x000040 - unknown; count: {length}, offset: {hex(offset)}")
if offset != 0 and length > 0: if offset != 0 and length > 0:
for i in range(length): for i in range(length):
@ -749,7 +730,7 @@ class TXP2File(TrackedCoverage):
bytedata = self.get_until_null(name_offset) bytedata = self.get_until_null(name_offset)
self.add_coverage(name_offset, len(bytedata) + 1, unique=False) self.add_coverage(name_offset, len(bytedata) + 1, unique=False)
name = descramble_text(bytedata, self.text_obfuscated) name = descramble_text(bytedata, self.text_obfuscated)
vprint(f" {name}") self.vprint(f" {name}")
self.unknown1.append( self.unknown1.append(
Unknown1( Unknown1(
@ -759,7 +740,7 @@ class TXP2File(TrackedCoverage):
) )
self.add_coverage(unk_offset + 4, 12) self.add_coverage(unk_offset + 4, 12)
else: else:
vprint("Bit 0x000040 - unknown; NOT PRESENT") self.vprint("Bit 0x000040 - unknown; NOT PRESENT")
if feature_mask & 0x80: if feature_mask & 0x80:
# One unknown byte, treated as an offset. This is clearly the mapping for the parsed # One unknown byte, treated as an offset. This is clearly the mapping for the parsed
@ -768,13 +749,13 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 4) self.add_coverage(header_offset, 4)
header_offset += 4 header_offset += 4
vprint(f"Bit 0x000080 - unknownmapping; offset: {hex(offset)}") self.vprint(f"Bit 0x000080 - unknownmapping; offset: {hex(offset)}")
# TODO: I have no idea what this is for. # TODO: I have no idea what this is for.
if offset != 0: if offset != 0:
self.unk_pman1 = self.descramble_pman(offset, verbose) self.unk_pman1 = self.descramble_pman(offset)
else: else:
vprint("Bit 0x000080 - unknownmapping; NOT PRESENT") self.vprint("Bit 0x000080 - unknownmapping; NOT PRESENT")
if feature_mask & 0x100: if feature_mask & 0x100:
# Two unknown bytes, first is a length or a count. Secound is # Two unknown bytes, first is a length or a count. Secound is
@ -783,7 +764,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 8) self.add_coverage(header_offset, 8)
header_offset += 8 header_offset += 8
vprint(f"Bit 0x000100 - unknown; count: {length}, offset: {hex(offset)}") self.vprint(f"Bit 0x000100 - unknown; count: {length}, offset: {hex(offset)}")
if offset != 0 and length > 0: if offset != 0 and length > 0:
for i in range(length): for i in range(length):
@ -793,7 +774,7 @@ class TXP2File(TrackedCoverage):
) )
self.add_coverage(unk_offset, 4) self.add_coverage(unk_offset, 4)
else: else:
vprint("Bit 0x000100 - unknown; NOT PRESENT") self.vprint("Bit 0x000100 - unknown; NOT PRESENT")
if feature_mask & 0x200: if feature_mask & 0x200:
# One unknown byte, treated as an offset. Almost positive its a string mapping # One unknown byte, treated as an offset. Almost positive its a string mapping
@ -802,13 +783,13 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 4) self.add_coverage(header_offset, 4)
header_offset += 4 header_offset += 4
vprint(f"Bit 0x000200 - unknownmapping; offset: {hex(offset)}") self.vprint(f"Bit 0x000200 - unknownmapping; offset: {hex(offset)}")
# TODO: I have no idea what this is for. # TODO: I have no idea what this is for.
if offset != 0: if offset != 0:
self.unk_pman2 = self.descramble_pman(offset, verbose) self.unk_pman2 = self.descramble_pman(offset)
else: else:
vprint("Bit 0x000200 - unknownmapping; NOT PRESENT") self.vprint("Bit 0x000200 - unknownmapping; NOT PRESENT")
if feature_mask & 0x400: if feature_mask & 0x400:
# One unknown byte, treated as an offset. I have no idea what this is used for, # One unknown byte, treated as an offset. I have no idea what this is used for,
@ -818,9 +799,9 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 4) self.add_coverage(header_offset, 4)
header_offset += 4 header_offset += 4
vprint(f"Bit 0x000400 - unknown; offset: {hex(offset)}") self.vprint(f"Bit 0x000400 - unknown; offset: {hex(offset)}")
else: else:
vprint("Bit 0x000400 - unknown; NOT PRESENT") self.vprint("Bit 0x000400 - unknown; NOT PRESENT")
if feature_mask & 0x800: if feature_mask & 0x800:
# SWF raw data that is loaded and passed to AFP core. It is equivalent to the # SWF raw data that is loaded and passed to AFP core. It is equivalent to the
@ -829,7 +810,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 8) self.add_coverage(header_offset, 8)
header_offset += 8 header_offset += 8
vprint(f"Bit 0x000800 - swfdata; count: {length}, offset: {hex(offset)}") self.vprint(f"Bit 0x000800 - swfdata; count: {length}, offset: {hex(offset)}")
for x in range(length): for x in range(length):
interesting_offset = offset + (x * 12) interesting_offset = offset + (x * 12)
@ -844,7 +825,7 @@ class TXP2File(TrackedCoverage):
bytedata = self.get_until_null(name_offset) bytedata = self.get_until_null(name_offset)
self.add_coverage(name_offset, len(bytedata) + 1, unique=False) self.add_coverage(name_offset, len(bytedata) + 1, unique=False)
name = descramble_text(bytedata, self.text_obfuscated) name = descramble_text(bytedata, self.text_obfuscated)
vprint(f" {name}, length: {swf_length}, offset: {hex(swf_offset)}") self.vprint(f" {name}, length: {swf_length}, offset: {hex(swf_offset)}")
if swf_offset != 0: if swf_offset != 0:
self.swfdata.append( self.swfdata.append(
@ -855,7 +836,7 @@ class TXP2File(TrackedCoverage):
) )
self.add_coverage(swf_offset, swf_length) self.add_coverage(swf_offset, swf_length)
else: else:
vprint("Bit 0x000800 - swfdata; NOT PRESENT") self.vprint("Bit 0x000800 - swfdata; NOT PRESENT")
if feature_mask & 0x1000: if feature_mask & 0x1000:
# A mapping structure that allows looking up SWF data by name. # A mapping structure that allows looking up SWF data by name.
@ -863,12 +844,12 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 4) self.add_coverage(header_offset, 4)
header_offset += 4 header_offset += 4
vprint(f"Bit 0x001000 - swfmapping; offset: {hex(offset)}") self.vprint(f"Bit 0x001000 - swfmapping; offset: {hex(offset)}")
if offset != 0: if offset != 0:
self.swfmap = self.descramble_pman(offset, verbose) self.swfmap = self.descramble_pman(offset)
else: else:
vprint("Bit 0x001000 - swfmapping; NOT PRESENT") self.vprint("Bit 0x001000 - swfmapping; NOT PRESENT")
if feature_mask & 0x2000: if feature_mask & 0x2000:
# These are shapes as used with the SWF data above. They contain mappings between a # These are shapes as used with the SWF data above. They contain mappings between a
@ -878,7 +859,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 8) self.add_coverage(header_offset, 8)
header_offset += 8 header_offset += 8
vprint(f"Bit 0x002000 - shapes; count: {length}, offset: {hex(offset)}") self.vprint(f"Bit 0x002000 - shapes; count: {length}, offset: {hex(offset)}")
for x in range(length): for x in range(length):
shape_base_offset = offset + (x * 12) shape_base_offset = offset + (x * 12)
@ -906,12 +887,12 @@ class TXP2File(TrackedCoverage):
self.shapes.append(shape) self.shapes.append(shape)
self.add_coverage(shape_offset, shape_length) self.add_coverage(shape_offset, shape_length)
vprint(f" {name}, length: {shape_length}, offset: {hex(shape_offset)}") self.vprint(f" {name}, length: {shape_length}, offset: {hex(shape_offset)}")
for line in str(shape).split(os.linesep): for line in str(shape).split(os.linesep):
vprint(f" {line}") self.vprint(f" {line}")
else: else:
vprint("Bit 0x002000 - shapes; NOT PRESENT") self.vprint("Bit 0x002000 - shapes; NOT PRESENT")
if feature_mask & 0x4000: if feature_mask & 0x4000:
# Mapping so that shapes can be looked up by name to get their offset. # Mapping so that shapes can be looked up by name to get their offset.
@ -919,12 +900,12 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 4) self.add_coverage(header_offset, 4)
header_offset += 4 header_offset += 4
vprint(f"Bit 0x004000 - shapesmapping; offset: {hex(offset)}") self.vprint(f"Bit 0x004000 - shapesmapping; offset: {hex(offset)}")
if offset != 0: if offset != 0:
self.shapemap = self.descramble_pman(offset, verbose) self.shapemap = self.descramble_pman(offset)
else: else:
vprint("Bit 0x004000 - shapesmapping; NOT PRESENT") self.vprint("Bit 0x004000 - shapesmapping; NOT PRESENT")
if feature_mask & 0x8000: if feature_mask & 0x8000:
# One unknown byte, treated as an offset. I have no idea what this is because # One unknown byte, treated as an offset. I have no idea what this is because
@ -933,13 +914,13 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 4) self.add_coverage(header_offset, 4)
header_offset += 4 header_offset += 4
vprint(f"Bit 0x008000 - unknown; offset: {hex(offset)}") self.vprint(f"Bit 0x008000 - unknown; offset: {hex(offset)}")
# Since I've never seen this, I'm going to assume that it showing up is # Since I've never seen this, I'm going to assume that it showing up is
# bad and make things read only. # bad and make things read only.
self.read_only = True self.read_only = True
else: else:
vprint("Bit 0x008000 - unknown; NOT PRESENT") self.vprint("Bit 0x008000 - unknown; NOT PRESENT")
if feature_mask & 0x10000: if feature_mask & 0x10000:
# Included font package, BINXRPC encoded. This is basically a texture sheet with an XML # Included font package, BINXRPC encoded. This is basically a texture sheet with an XML
@ -953,7 +934,7 @@ class TXP2File(TrackedCoverage):
expect_zero, length, binxrpc_offset = struct.unpack(f"{self.endian}III", self.data[offset:(offset + 12)]) expect_zero, length, binxrpc_offset = struct.unpack(f"{self.endian}III", self.data[offset:(offset + 12)])
self.add_coverage(offset, 12) self.add_coverage(offset, 12)
vprint(f"Bit 0x010000 - fontinfo; offset: {hex(offset)}, binxrpc offset: {hex(binxrpc_offset)}") self.vprint(f"Bit 0x010000 - fontinfo; offset: {hex(offset)}, binxrpc offset: {hex(binxrpc_offset)}")
if expect_zero != 0: if expect_zero != 0:
# If we find non-zero versions of this, then that means updating the file is # If we find non-zero versions of this, then that means updating the file is
@ -966,7 +947,7 @@ class TXP2File(TrackedCoverage):
else: else:
self.fontdata = None self.fontdata = None
else: else:
vprint("Bit 0x010000 - fontinfo; NOT PRESENT") self.vprint("Bit 0x010000 - fontinfo; NOT PRESENT")
if feature_mask & 0x20000: if feature_mask & 0x20000:
# This is the byteswapping headers that allow us to byteswap the SWF data before passing it # This is the byteswapping headers that allow us to byteswap the SWF data before passing it
@ -975,7 +956,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(header_offset, 4) self.add_coverage(header_offset, 4)
header_offset += 4 header_offset += 4
vprint(f"Bit 0x020000 - swfheaders; offset: {hex(offset)}") self.vprint(f"Bit 0x020000 - swfheaders; offset: {hex(offset)}")
if offset > 0 and len(self.swfdata) > 0: if offset > 0 and len(self.swfdata) > 0:
for i in range(len(self.swfdata)): for i in range(len(self.swfdata)):
@ -988,7 +969,7 @@ class TXP2File(TrackedCoverage):
f"{self.endian}III", f"{self.endian}III",
self.data[structure_offset:(structure_offset + 12)] self.data[structure_offset:(structure_offset + 12)]
) )
vprint(f" length: {afp_header_length}, offset: {hex(afp_header)}") self.vprint(f" length: {afp_header_length}, offset: {hex(afp_header)}")
self.add_coverage(structure_offset, 12) self.add_coverage(structure_offset, 12)
if expect_zero != 0: if expect_zero != 0:
@ -999,12 +980,12 @@ class TXP2File(TrackedCoverage):
self.swfdata[i].descramble_info = self.data[afp_header:(afp_header + afp_header_length)] self.swfdata[i].descramble_info = self.data[afp_header:(afp_header + afp_header_length)]
self.add_coverage(afp_header, afp_header_length) self.add_coverage(afp_header, afp_header_length)
else: else:
vprint("Bit 0x020000 - swfheaders; NOT PRESENT") self.vprint("Bit 0x020000 - swfheaders; NOT PRESENT")
if feature_mask & 0x40000: if feature_mask & 0x40000:
vprint("Bit 0x040000 - modern lz mode on") self.vprint("Bit 0x040000 - modern lz mode on")
else: else:
vprint("Bit 0x040000 - modern lz mode off") self.vprint("Bit 0x040000 - modern lz mode off")
if feature_mask & 0xFFF80000: if feature_mask & 0xFFF80000:
# We don't know these bits at all! # We don't know these bits at all!

View File

@ -6,10 +6,10 @@ from typing import Any, Dict, List, Tuple
from .types import Matrix, Color, Point, Rectangle from .types import Matrix, Color, Point, Rectangle
from .types import AP2Action, AP2Tag, AP2Property from .types import AP2Action, AP2Tag, AP2Property
from .util import TrackedCoverage, _hex from .util import TrackedCoverage, VerboseOutput, _hex
class SWF(TrackedCoverage): class SWF(TrackedCoverage, VerboseOutput):
def __init__( def __init__(
self, self,
name: str, name: str,
@ -47,15 +47,7 @@ class SWF(TrackedCoverage):
'descramble_info': "".join(_hex(x) for x in self.descramble_info), 'descramble_info': "".join(_hex(x) for x in self.descramble_info),
} }
def __parse_bytecode(self, datachunk: bytes, string_offsets: List[int] = [], prefix: str = "", verbose: bool = False) -> None: def __parse_bytecode(self, datachunk: bytes, string_offsets: List[int] = [], prefix: str = "") -> None:
# Suppress debug text unless asked
if verbose:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
print(*args, **kwargs, file=sys.stderr)
else:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
pass
# First, we need to check if this is a SWF-style bytecode or an AP2 bytecode. # First, we need to check if this is a SWF-style bytecode or an AP2 bytecode.
ap2_sentinel = struct.unpack("<B", datachunk[0:1])[0] ap2_sentinel = struct.unpack("<B", datachunk[0:1])[0]
@ -79,7 +71,7 @@ class SWF(TrackedCoverage):
offset_ptr = 2 offset_ptr = 2
start_offset = offset_ptr start_offset = offset_ptr
vprint(f"{prefix} Flags: {hex(flags)}, Bytecode Actual Offset: {hex(offset_ptr)}") self.vprint(f"{prefix} Flags: {hex(flags)}, Bytecode Actual Offset: {hex(offset_ptr)}")
# Actually parse out the opcodes: # Actually parse out the opcodes:
while offset_ptr < len(datachunk): while offset_ptr < len(datachunk):
@ -94,7 +86,7 @@ class SWF(TrackedCoverage):
lineno = offset_ptr - start_offset lineno = offset_ptr - start_offset
if opcode in AP2Action.actions_without_params(): if opcode in AP2Action.actions_without_params():
vprint(f"{prefix} {lineno}: {action_name}") self.vprint(f"{prefix} {lineno}: {action_name}")
offset_ptr += 1 offset_ptr += 1
elif opcode == AP2Action.DEFINE_FUNCTION2: elif opcode == AP2Action.DEFINE_FUNCTION2:
function_flags, funcname_offset, bytecode_offset, _, bytecode_count = struct.unpack( function_flags, funcname_offset, bytecode_offset, _, bytecode_count = struct.unpack(
@ -108,16 +100,16 @@ class SWF(TrackedCoverage):
funcname = self.__get_string(funcname_offset) funcname = self.__get_string(funcname_offset)
offset_ptr += 10 + (3 * bytecode_offset) offset_ptr += 10 + (3 * bytecode_offset)
vprint(f"{prefix} {lineno}: {action_name} Flags: {hex(function_flags)}, Name: {funcname}, Bytecode Offset: {hex(bytecode_offset)}, Bytecode Length: {hex(bytecode_count)}") self.vprint(f"{prefix} {lineno}: {action_name} Flags: {hex(function_flags)}, Name: {funcname}, Bytecode Offset: {hex(bytecode_offset)}, Bytecode Length: {hex(bytecode_count)}")
self.__parse_bytecode(datachunk[offset_ptr:(offset_ptr + bytecode_count)], string_offsets=string_offsets, prefix=prefix + " ", verbose=verbose) self.__parse_bytecode(datachunk[offset_ptr:(offset_ptr + bytecode_count)], string_offsets=string_offsets, prefix=prefix + " ")
vprint(f"{prefix} END_{action_name}") self.vprint(f"{prefix} END_{action_name}")
offset_ptr += bytecode_count offset_ptr += bytecode_count
elif opcode == AP2Action.PUSH: elif opcode == AP2Action.PUSH:
obj_count = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] obj_count = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
offset_ptr += 2 offset_ptr += 2
vprint(f"{prefix} {lineno}: {action_name}") self.vprint(f"{prefix} {lineno}: {action_name}")
while obj_count > 0: while obj_count > 0:
obj_to_create = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] obj_to_create = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0]
@ -125,44 +117,44 @@ class SWF(TrackedCoverage):
if obj_to_create == 0x0: if obj_to_create == 0x0:
# Integer "0" object. # Integer "0" object.
vprint(f"{prefix} INTEGER: 0") self.vprint(f"{prefix} INTEGER: 0")
elif obj_to_create == 0x1: elif obj_to_create == 0x1:
# Float object, represented internally as a double. # Float object, represented internally as a double.
fval = struct.unpack(">f", datachunk[offset_ptr:(offset_ptr + 4)])[0] fval = struct.unpack(">f", datachunk[offset_ptr:(offset_ptr + 4)])[0]
offset_ptr += 4 offset_ptr += 4
vprint(f"{prefix} FLOAT: {fval}") self.vprint(f"{prefix} FLOAT: {fval}")
elif obj_to_create == 0x2: elif obj_to_create == 0x2:
# Null pointer object. # Null pointer object.
vprint(f"{prefix} NULL") self.vprint(f"{prefix} NULL")
elif obj_to_create == 0x3: elif obj_to_create == 0x3:
# Undefined constant. # Undefined constant.
vprint(f"{prefix} UNDEFINED") self.vprint(f"{prefix} UNDEFINED")
elif obj_to_create == 0x4: elif obj_to_create == 0x4:
# Register value. # Register value.
regno = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] regno = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0]
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} REGISTER NO: {regno}") self.vprint(f"{prefix} REGISTER NO: {regno}")
elif obj_to_create == 0x5: elif obj_to_create == 0x5:
# Boolean "TRUE" object. # Boolean "TRUE" object.
vprint(f"{prefix} BOOLEAN: True") self.vprint(f"{prefix} BOOLEAN: True")
elif obj_to_create == 0x6: elif obj_to_create == 0x6:
# Boolean "FALSE" object. # Boolean "FALSE" object.
vprint(f"{prefix} BOOLEAN: False") self.vprint(f"{prefix} BOOLEAN: False")
elif obj_to_create == 0x7: elif obj_to_create == 0x7:
# Integer object. # Integer object.
ival = struct.unpack(">I", datachunk[offset_ptr:(offset_ptr + 4)])[0] ival = struct.unpack(">I", datachunk[offset_ptr:(offset_ptr + 4)])[0]
offset_ptr += 4 offset_ptr += 4
vprint(f"{prefix} INTEGER: {ival}") self.vprint(f"{prefix} INTEGER: {ival}")
elif obj_to_create == 0x8: elif obj_to_create == 0x8:
# String constant object. # String constant object.
const_offset = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] const_offset = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0]
const = self.__get_string(string_offsets[const_offset]) const = self.__get_string(string_offsets[const_offset])
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} STRING CONST: {const}") self.vprint(f"{prefix} STRING CONST: {const}")
elif obj_to_create == 0x9: elif obj_to_create == 0x9:
# String constant, but with 16 bits for the offset. Probably not used except # String constant, but with 16 bits for the offset. Probably not used except
# on the largest files. # on the largest files.
@ -170,102 +162,102 @@ class SWF(TrackedCoverage):
const = self.__get_string(string_offsets[const_offset]) const = self.__get_string(string_offsets[const_offset])
offset_ptr += 2 offset_ptr += 2
vprint(f"{prefix} STRING_CONTS: {const}") self.vprint(f"{prefix} STRING_CONTS: {const}")
elif obj_to_create == 0xa: elif obj_to_create == 0xa:
# NaN constant. # NaN constant.
vprint(f"{prefix} NAN") self.vprint(f"{prefix} NAN")
elif obj_to_create == 0xb: elif obj_to_create == 0xb:
# Infinity constant. # Infinity constant.
vprint(f"{prefix} INFINITY") self.vprint(f"{prefix} INFINITY")
elif obj_to_create == 0xc: elif obj_to_create == 0xc:
# Pointer to "this" object, whatever currently is executing the bytecode. # Pointer to "this" object, whatever currently is executing the bytecode.
vprint(f"{prefix} POINTER TO THIS") self.vprint(f"{prefix} POINTER TO THIS")
elif obj_to_create == 0xd: elif obj_to_create == 0xd:
# Pointer to "root" object, which is the movieclip this bytecode exists in. # Pointer to "root" object, which is the movieclip this bytecode exists in.
vprint(f"{prefix} POINTER TO ROOT") self.vprint(f"{prefix} POINTER TO ROOT")
elif obj_to_create == 0xe: elif obj_to_create == 0xe:
# Pointer to "parent" object, whatever currently is executing the bytecode. # Pointer to "parent" object, whatever currently is executing the bytecode.
# This seems to be the parent of the movie clip, or the current movieclip # This seems to be the parent of the movie clip, or the current movieclip
# if that isn't set. # if that isn't set.
vprint(f"{prefix} POINTER TO PARENT") self.vprint(f"{prefix} POINTER TO PARENT")
elif obj_to_create == 0xf: elif obj_to_create == 0xf:
# Current movie clip. # Current movie clip.
vprint(f"{prefix} POINTER TO CURRENT MOVIECLIP") self.vprint(f"{prefix} POINTER TO CURRENT MOVIECLIP")
elif obj_to_create == 0x10: elif obj_to_create == 0x10:
# Unknown property name. # Unknown property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x100 propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x100
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} PROPERTY CONST NAME: {AP2Property.property_to_name(propertyval)}") self.vprint(f"{prefix} PROPERTY CONST NAME: {AP2Property.property_to_name(propertyval)}")
elif obj_to_create == 0x13: elif obj_to_create == 0x13:
# Class property name. # Class property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x300 propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x300
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} CLASS CONST NAME: {AP2Property.property_to_name(propertyval)}") self.vprint(f"{prefix} CLASS CONST NAME: {AP2Property.property_to_name(propertyval)}")
elif obj_to_create == 0x16: elif obj_to_create == 0x16:
# Func property name. # Func property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x400 propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x400
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} FUNC CONST NAME: {AP2Property.property_to_name(propertyval)}") self.vprint(f"{prefix} FUNC CONST NAME: {AP2Property.property_to_name(propertyval)}")
elif obj_to_create == 0x19: elif obj_to_create == 0x19:
# Other property name. # Other property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x200 propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x200
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} OTHER CONST NAME: {AP2Property.property_to_name(propertyval)}") self.vprint(f"{prefix} OTHER CONST NAME: {AP2Property.property_to_name(propertyval)}")
elif obj_to_create == 0x1c: elif obj_to_create == 0x1c:
# Event property name. # Event property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x500 propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x500
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} EVENT CONST NAME: {AP2Property.property_to_name(propertyval)}") self.vprint(f"{prefix} EVENT CONST NAME: {AP2Property.property_to_name(propertyval)}")
elif obj_to_create == 0x1f: elif obj_to_create == 0x1f:
# Key constants. # Key constants.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x600 propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x600
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} KEY CONST NAME: {AP2Property.property_to_name(propertyval)}") self.vprint(f"{prefix} KEY CONST NAME: {AP2Property.property_to_name(propertyval)}")
elif obj_to_create == 0x22: elif obj_to_create == 0x22:
# Pointer to global object. # Pointer to global object.
vprint(f"{prefix} POINTER TO GLOBAL OBJECT") self.vprint(f"{prefix} POINTER TO GLOBAL OBJECT")
elif obj_to_create == 0x24: elif obj_to_create == 0x24:
# Some other property name. # Some other property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x700 propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x700
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} ETC2 CONST NAME: {AP2Property.property_to_name(propertyval)}") self.vprint(f"{prefix} ETC2 CONST NAME: {AP2Property.property_to_name(propertyval)}")
elif obj_to_create == 0x27: elif obj_to_create == 0x27:
# Some other property name. # Some other property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x800 propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x800
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} ORGFUNC2 CONST NAME: {AP2Property.property_to_name(propertyval)}") self.vprint(f"{prefix} ORGFUNC2 CONST NAME: {AP2Property.property_to_name(propertyval)}")
elif obj_to_create == 0x37: elif obj_to_create == 0x37:
# Integer object but one byte. # Integer object but one byte.
ival = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] ival = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0]
offset_ptr += 1 offset_ptr += 1
vprint(f"{prefix} INTEGER: {ival}") self.vprint(f"{prefix} INTEGER: {ival}")
else: else:
raise Exception(f"Unsupported object {hex(obj_to_create)} to push!") raise Exception(f"Unsupported object {hex(obj_to_create)} to push!")
obj_count -= 1 obj_count -= 1
vprint(f"{prefix} END_{action_name}") self.vprint(f"{prefix} END_{action_name}")
elif opcode == AP2Action.STORE_REGISTER: elif opcode == AP2Action.STORE_REGISTER:
obj_count = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] obj_count = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
offset_ptr += 2 offset_ptr += 2
vprint(f"{prefix} {lineno}: {action_name}") self.vprint(f"{prefix} {lineno}: {action_name}")
while obj_count > 0: while obj_count > 0:
register_no = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] register_no = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0]
offset_ptr += 1 offset_ptr += 1
obj_count -= 1 obj_count -= 1
vprint(f"{prefix} REGISTER NO: {register_no}") self.vprint(f"{prefix} REGISTER NO: {register_no}")
vprint(f"{prefix} END_{action_name}") self.vprint(f"{prefix} END_{action_name}")
elif opcode == AP2Action.STORE_REGISTER2: elif opcode == AP2Action.STORE_REGISTER2:
register_no = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] register_no = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
offset_ptr += 2 offset_ptr += 2
vprint(f"{prefix} {lineno}: {action_name}") self.vprint(f"{prefix} {lineno}: {action_name}")
vprint(f"{prefix} REGISTER NO: {register_no}") self.vprint(f"{prefix} REGISTER NO: {register_no}")
vprint(f"{prefix} END_{action_name}") self.vprint(f"{prefix} END_{action_name}")
elif opcode == AP2Action.IF: elif opcode == AP2Action.IF:
jump_if_true_offset = struct.unpack(">H", datachunk[(offset_ptr + 1):(offset_ptr + 3)])[0] jump_if_true_offset = struct.unpack(">H", datachunk[(offset_ptr + 1):(offset_ptr + 3)])[0]
offset_ptr += 3 offset_ptr += 3
@ -275,7 +267,7 @@ class SWF(TrackedCoverage):
# to be absolute instead of relative. # to be absolute instead of relative.
jump_if_true_offset += offset_ptr - start_offset jump_if_true_offset += offset_ptr - start_offset
vprint(f"{prefix} {lineno}: Offset If True: {jump_if_true_offset}") self.vprint(f"{prefix} {lineno}: Offset If True: {jump_if_true_offset}")
elif opcode == AP2Action.IF2: elif opcode == AP2Action.IF2:
if2_type, jump_if_true_offset = struct.unpack(">BH", datachunk[(offset_ptr + 1):(offset_ptr + 4)]) if2_type, jump_if_true_offset = struct.unpack(">BH", datachunk[(offset_ptr + 1):(offset_ptr + 4)])
offset_ptr += 4 offset_ptr += 4
@ -301,7 +293,7 @@ class SWF(TrackedCoverage):
12: "IS NOT UNDEFINED", 12: "IS NOT UNDEFINED",
}[if2_type] }[if2_type]
vprint(f"{prefix} {lineno}: {action_name} {if2_typestr}, Offset If True: {jump_if_true_offset}") self.vprint(f"{prefix} {lineno}: {action_name} {if2_typestr}, Offset If True: {jump_if_true_offset}")
elif opcode == AP2Action.JUMP: elif opcode == AP2Action.JUMP:
jump_offset = struct.unpack(">H", datachunk[(offset_ptr + 1):(offset_ptr + 3)])[0] jump_offset = struct.unpack(">H", datachunk[(offset_ptr + 1):(offset_ptr + 3)])[0]
offset_ptr += 3 offset_ptr += 3
@ -310,22 +302,22 @@ class SWF(TrackedCoverage):
# "END" pointer at the end of a chunk. We need to handle this. We probably need function lines # "END" pointer at the end of a chunk. We need to handle this. We probably need function lines
# to be absolute instead of relative. # to be absolute instead of relative.
jump_offset += offset_ptr - start_offset jump_offset += offset_ptr - start_offset
vprint(f"{prefix} {lineno}: {action_name} Offset: {jump_offset}") self.vprint(f"{prefix} {lineno}: {action_name} Offset: {jump_offset}")
elif opcode == AP2Action.ADD_NUM_VARIABLE: elif opcode == AP2Action.ADD_NUM_VARIABLE:
amount_to_add = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] amount_to_add = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
offset_ptr += 2 offset_ptr += 2
vprint(f"{prefix} {lineno}: {action_name} Add Value: {amount_to_add}") self.vprint(f"{prefix} {lineno}: {action_name} Add Value: {amount_to_add}")
elif opcode == AP2Action.START_DRAG: elif opcode == AP2Action.START_DRAG:
constraint = struct.unpack(">b", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] constraint = struct.unpack(">b", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
offset_ptr += 2 offset_ptr += 2
vprint(f"{prefix} {lineno}: {action_name} Constrain Mouse: {'yes' if constraint > 0 else ('no' if constraint == 0 else 'check stack')}") self.vprint(f"{prefix} {lineno}: {action_name} Constrain Mouse: {'yes' if constraint > 0 else ('no' if constraint == 0 else 'check stack')}")
elif opcode == AP2Action.ADD_NUM_REGISTER: elif opcode == AP2Action.ADD_NUM_REGISTER:
register_no, amount_to_add = struct.unpack(">BB", datachunk[(offset_ptr + 1):(offset_ptr + 3)]) register_no, amount_to_add = struct.unpack(">BB", datachunk[(offset_ptr + 1):(offset_ptr + 3)])
offset_ptr += 3 offset_ptr += 3
vprint(f"{prefix} {lineno}: {action_name} Register No: {register_no}, Add Value: {amount_to_add}") self.vprint(f"{prefix} {lineno}: {action_name} Register No: {register_no}, Add Value: {amount_to_add}")
elif opcode == AP2Action.GOTO_FRAME2: elif opcode == AP2Action.GOTO_FRAME2:
flags = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0] flags = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
offset_ptr += 2 offset_ptr += 2
@ -342,19 +334,11 @@ class SWF(TrackedCoverage):
else: else:
additional_frames = 0 additional_frames = 0
vprint(f"{prefix} {lineno}: {action_name} AND {post} Additional Frames: {additional_frames}") self.vprint(f"{prefix} {lineno}: {action_name} AND {post} Additional Frames: {additional_frames}")
else: else:
raise Exception(f"Can't advance, no handler for opcode {opcode} ({hex(opcode)})!") raise Exception(f"Can't advance, no handler for opcode {opcode} ({hex(opcode)})!")
def __parse_tag(self, ap2_version: int, afp_version: int, ap2data: bytes, tagid: int, size: int, dataoffset: int, prefix: str = "", verbose: bool = False) -> None: def __parse_tag(self, ap2_version: int, afp_version: int, ap2data: bytes, tagid: int, size: int, dataoffset: int, prefix: str = "") -> None:
# Suppress debug text unless asked
if verbose:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
print(*args, **kwargs, file=sys.stderr)
else:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
pass
if tagid == AP2Tag.AP2_SHAPE: if tagid == AP2Tag.AP2_SHAPE:
if size != 4: if size != 4:
raise Exception(f"Invalid shape size {size}") raise Exception(f"Invalid shape size {size}")
@ -363,7 +347,7 @@ class SWF(TrackedCoverage):
self.add_coverage(dataoffset, size) self.add_coverage(dataoffset, size)
shape_reference = f"{self.exported_name}_shape{shape_id}" shape_reference = f"{self.exported_name}_shape{shape_id}"
vprint(f"{prefix} Tag ID: {shape_id}, AFP Reference: {shape_reference}, IFS GEO Filename: {md5(shape_reference.encode('utf-8')).hexdigest()}") self.vprint(f"{prefix} Tag ID: {shape_id}, AFP Reference: {shape_reference}, IFS GEO Filename: {md5(shape_reference.encode('utf-8')).hexdigest()}")
elif tagid == AP2Tag.AP2_DEFINE_SPRITE: elif tagid == AP2Tag.AP2_DEFINE_SPRITE:
sprite_flags, sprite_id = struct.unpack("<HH", ap2data[dataoffset:(dataoffset + 4)]) sprite_flags, sprite_id = struct.unpack("<HH", ap2data[dataoffset:(dataoffset + 4)])
self.add_coverage(dataoffset, 4) self.add_coverage(dataoffset, 4)
@ -376,8 +360,8 @@ class SWF(TrackedCoverage):
subtags_offset = struct.unpack("<I", ap2data[(dataoffset + 4):(dataoffset + 8)])[0] + dataoffset subtags_offset = struct.unpack("<I", ap2data[(dataoffset + 4):(dataoffset + 8)])[0] + dataoffset
self.add_coverage(dataoffset + 4, 4) self.add_coverage(dataoffset + 4, 4)
vprint(f"{prefix} Tag ID: {sprite_id}") self.vprint(f"{prefix} Tag ID: {sprite_id}")
self.__parse_tags(ap2_version, afp_version, ap2data, subtags_offset, prefix=" " + prefix, verbose=verbose) self.__parse_tags(ap2_version, afp_version, ap2data, subtags_offset, prefix=" " + prefix)
elif tagid == AP2Tag.AP2_DEFINE_FONT: elif tagid == AP2Tag.AP2_DEFINE_FONT:
unk, font_id, fontname_offset, xml_prefix_offset, data_offset, data_count = struct.unpack("<HHHHHH", ap2data[dataoffset:(dataoffset + 12)]) unk, font_id, fontname_offset, xml_prefix_offset, data_offset, data_count = struct.unpack("<HHHHHH", ap2data[dataoffset:(dataoffset + 12)])
self.add_coverage(dataoffset, 12) self.add_coverage(dataoffset, 12)
@ -385,17 +369,17 @@ class SWF(TrackedCoverage):
fontname = self.__get_string(fontname_offset) fontname = self.__get_string(fontname_offset)
xml_prefix = self.__get_string(xml_prefix_offset) xml_prefix = self.__get_string(xml_prefix_offset)
vprint(f"{prefix} Tag ID: {font_id}, Font Name: {fontname}, XML Prefix: {xml_prefix}, Entries: {data_count}") self.vprint(f"{prefix} Tag ID: {font_id}, Font Name: {fontname}, XML Prefix: {xml_prefix}, Entries: {data_count}")
for i in range(data_count): for i in range(data_count):
entry_offset = dataoffset + 12 + (data_offset * 2) + (i * 2) entry_offset = dataoffset + 12 + (data_offset * 2) + (i * 2)
entry_value = struct.unpack("<H", ap2data[entry_offset:(entry_offset + 2)])[0] entry_value = struct.unpack("<H", ap2data[entry_offset:(entry_offset + 2)])[0]
self.add_coverage(entry_offset, 2) self.add_coverage(entry_offset, 2)
vprint(f"{prefix} Height: {entry_value}") self.vprint(f"{prefix} Height: {entry_value}")
elif tagid == AP2Tag.AP2_DO_ACTION: elif tagid == AP2Tag.AP2_DO_ACTION:
datachunk = ap2data[dataoffset:(dataoffset + size)] datachunk = ap2data[dataoffset:(dataoffset + size)]
self.__parse_bytecode(datachunk, prefix=prefix, verbose=verbose) self.__parse_bytecode(datachunk, prefix=prefix)
self.add_coverage(dataoffset, size) self.add_coverage(dataoffset, size)
elif tagid == AP2Tag.AP2_PLACE_OBJECT: elif tagid == AP2Tag.AP2_PLACE_OBJECT:
# Allow us to keep track of what we've consumed. # Allow us to keep track of what we've consumed.
@ -403,7 +387,7 @@ class SWF(TrackedCoverage):
flags, depth, object_id = struct.unpack("<IHH", datachunk[0:8]) flags, depth, object_id = struct.unpack("<IHH", datachunk[0:8])
self.add_coverage(dataoffset, 8) self.add_coverage(dataoffset, 8)
vprint(f"{prefix} Flags: {hex(flags)}, Object ID: {object_id}, Depth: {depth}") self.vprint(f"{prefix} Flags: {hex(flags)}, Object ID: {object_id}, Depth: {depth}")
running_pointer = 8 running_pointer = 8
unhandled_flags = flags unhandled_flags = flags
@ -413,14 +397,14 @@ class SWF(TrackedCoverage):
src_tag_id = struct.unpack("<H", datachunk[running_pointer:(running_pointer + 2)])[0] src_tag_id = struct.unpack("<H", datachunk[running_pointer:(running_pointer + 2)])[0]
self.add_coverage(dataoffset + running_pointer, 2) self.add_coverage(dataoffset + running_pointer, 2)
running_pointer += 2 running_pointer += 2
vprint(f"{prefix} Source Tag ID: {src_tag_id}") self.vprint(f"{prefix} Source Tag ID: {src_tag_id}")
if flags & 0x10: if flags & 0x10:
unhandled_flags &= ~0x10 unhandled_flags &= ~0x10
unk2 = struct.unpack("<H", datachunk[running_pointer:(running_pointer + 2)])[0] unk2 = struct.unpack("<H", datachunk[running_pointer:(running_pointer + 2)])[0]
self.add_coverage(dataoffset + running_pointer, 2) self.add_coverage(dataoffset + running_pointer, 2)
running_pointer += 2 running_pointer += 2
vprint(f"{prefix} Unk2: {hex(unk2)}") self.vprint(f"{prefix} Unk2: {hex(unk2)}")
if flags & 0x20: if flags & 0x20:
unhandled_flags &= ~0x20 unhandled_flags &= ~0x20
@ -428,21 +412,21 @@ class SWF(TrackedCoverage):
self.add_coverage(dataoffset + running_pointer, 2) self.add_coverage(dataoffset + running_pointer, 2)
name = self.__get_string(nameoffset) name = self.__get_string(nameoffset)
running_pointer += 2 running_pointer += 2
vprint(f"{prefix} Name: {name}") self.vprint(f"{prefix} Name: {name}")
if flags & 0x40: if flags & 0x40:
unhandled_flags &= ~0x40 unhandled_flags &= ~0x40
unk3 = struct.unpack("<H", datachunk[running_pointer:(running_pointer + 2)])[0] unk3 = struct.unpack("<H", datachunk[running_pointer:(running_pointer + 2)])[0]
self.add_coverage(dataoffset + running_pointer, 2) self.add_coverage(dataoffset + running_pointer, 2)
running_pointer += 2 running_pointer += 2
vprint(f"{prefix} Unk3: {hex(unk3)}") self.vprint(f"{prefix} Unk3: {hex(unk3)}")
if flags & 0x20000: if flags & 0x20000:
unhandled_flags &= ~0x20000 unhandled_flags &= ~0x20000
blend = struct.unpack("<B", datachunk[running_pointer:(running_pointer + 1)])[0] blend = struct.unpack("<B", datachunk[running_pointer:(running_pointer + 1)])[0]
self.add_coverage(dataoffset + running_pointer, 1) self.add_coverage(dataoffset + running_pointer, 1)
running_pointer += 1 running_pointer += 1
vprint(f"{prefix} Blend: {hex(blend)}") self.vprint(f"{prefix} Blend: {hex(blend)}")
# Due to possible misalignment, we need to realign. # Due to possible misalignment, we need to realign.
misalignment = running_pointer & 3 misalignment = running_pointer & 3
@ -462,7 +446,7 @@ class SWF(TrackedCoverage):
transform.a = float(a_int) * 0.0009765625 transform.a = float(a_int) * 0.0009765625
transform.d = float(d_int) * 0.0009765625 transform.d = float(d_int) * 0.0009765625
vprint(f"{prefix} Transform Matrix A: {transform.a}, D: {transform.d}") self.vprint(f"{prefix} Transform Matrix A: {transform.a}, D: {transform.d}")
if flags & 0x200: if flags & 0x200:
unhandled_flags &= ~0x200 unhandled_flags &= ~0x200
@ -472,7 +456,7 @@ class SWF(TrackedCoverage):
transform.b = float(b_int) * 0.0009765625 transform.b = float(b_int) * 0.0009765625
transform.c = float(c_int) * 0.0009765625 transform.c = float(c_int) * 0.0009765625
vprint(f"{prefix} Transform Matrix B: {transform.b}, C: {transform.c}") self.vprint(f"{prefix} Transform Matrix B: {transform.b}, C: {transform.c}")
if flags & 0x400: if flags & 0x400:
unhandled_flags &= ~0x400 unhandled_flags &= ~0x400
@ -482,7 +466,7 @@ class SWF(TrackedCoverage):
transform.tx = float(tx_int) / 20.0 transform.tx = float(tx_int) / 20.0
transform.ty = float(tx_int) / 20.0 transform.ty = float(tx_int) / 20.0
vprint(f"{prefix} Transform Matrix TX: {transform.tx}, TY: {transform.ty}") self.vprint(f"{prefix} Transform Matrix TX: {transform.tx}, TY: {transform.ty}")
# Handle object colors # Handle object colors
color = Color(1.0, 1.0, 1.0, 1.0) color = Color(1.0, 1.0, 1.0, 1.0)
@ -498,7 +482,7 @@ class SWF(TrackedCoverage):
color.g = float(g) * 0.003921569 color.g = float(g) * 0.003921569
color.b = float(b) * 0.003921569 color.b = float(b) * 0.003921569
color.a = float(a) * 0.003921569 color.a = float(a) * 0.003921569
vprint(f"{prefix} Color: {color}") self.vprint(f"{prefix} Color: {color}")
if flags & 0x1000: if flags & 0x1000:
unhandled_flags &= ~0x1000 unhandled_flags &= ~0x1000
@ -510,7 +494,7 @@ class SWF(TrackedCoverage):
acolor.g = float(g) * 0.003921569 acolor.g = float(g) * 0.003921569
acolor.b = float(b) * 0.003921569 acolor.b = float(b) * 0.003921569
acolor.a = float(a) * 0.003921569 acolor.a = float(a) * 0.003921569
vprint(f"{prefix} AColor: {color}") self.vprint(f"{prefix} AColor: {color}")
if flags & 0x2000: if flags & 0x2000:
unhandled_flags &= ~0x2000 unhandled_flags &= ~0x2000
@ -522,7 +506,7 @@ class SWF(TrackedCoverage):
color.g = float((rgba >> 16) & 0xFF) * 0.003921569 color.g = float((rgba >> 16) & 0xFF) * 0.003921569
color.b = float((rgba >> 8) & 0xFF) * 0.003921569 color.b = float((rgba >> 8) & 0xFF) * 0.003921569
color.a = float(rgba & 0xFF) * 0.003921569 color.a = float(rgba & 0xFF) * 0.003921569
vprint(f"{prefix} Color: {color}") self.vprint(f"{prefix} Color: {color}")
if flags & 0x4000: if flags & 0x4000:
unhandled_flags &= ~0x4000 unhandled_flags &= ~0x4000
@ -534,7 +518,7 @@ class SWF(TrackedCoverage):
acolor.g = float((rgba >> 16) & 0xFF) * 0.003921569 acolor.g = float((rgba >> 16) & 0xFF) * 0.003921569
acolor.b = float((rgba >> 8) & 0xFF) * 0.003921569 acolor.b = float((rgba >> 8) & 0xFF) * 0.003921569
acolor.a = float(rgba & 0xFF) * 0.003921569 acolor.a = float(rgba & 0xFF) * 0.003921569
vprint(f"{prefix} AColor: {color}") self.vprint(f"{prefix} AColor: {color}")
if flags & 0x80: if flags & 0x80:
# Object event triggers. # Object event triggers.
@ -559,7 +543,7 @@ class SWF(TrackedCoverage):
for i, bytecode_offset in enumerate(bytecode_offsets[:-1]): for i, bytecode_offset in enumerate(bytecode_offsets[:-1]):
beginning_to_end[bytecode_offset] = bytecode_offsets[i + 1] beginning_to_end[bytecode_offset] = bytecode_offsets[i + 1]
vprint(f"{prefix} Event Triggers, Count: {count}") self.vprint(f"{prefix} Event Triggers, Count: {count}")
for evt in range(count): for evt in range(count):
evt_offset = running_pointer + 12 + (evt * 8) evt_offset = running_pointer + 12 + (evt * 8)
evt_flags, _, keycode, bytecode_offset = struct.unpack("<IBBH", datachunk[evt_offset:(evt_offset + 8)]) evt_flags, _, keycode, bytecode_offset = struct.unpack("<IBBH", datachunk[evt_offset:(evt_offset + 8)])
@ -598,8 +582,8 @@ class SWF(TrackedCoverage):
bytecode_offset += evt_offset bytecode_offset += evt_offset
bytecode_length = beginning_to_end[bytecode_offset] - bytecode_offset bytecode_length = beginning_to_end[bytecode_offset] - bytecode_offset
vprint(f"{prefix} Flags: {hex(evt_flags)} ({', '.join(events)}), KeyCode: {hex(keycode)}, Bytecode Offset: {hex(dataoffset + bytecode_offset)}, Length: {bytecode_length}") self.vprint(f"{prefix} Flags: {hex(evt_flags)} ({', '.join(events)}), KeyCode: {hex(keycode)}, Bytecode Offset: {hex(dataoffset + bytecode_offset)}, Length: {bytecode_length}")
self.__parse_bytecode(datachunk[bytecode_offset:(bytecode_offset + bytecode_length)], prefix=prefix + " ", verbose=verbose) self.__parse_bytecode(datachunk[bytecode_offset:(bytecode_offset + bytecode_length)], prefix=prefix + " ")
self.add_coverage(dataoffset + bytecode_offset, bytecode_length) self.add_coverage(dataoffset + bytecode_offset, bytecode_length)
running_pointer += event_size running_pointer += event_size
@ -617,7 +601,7 @@ class SWF(TrackedCoverage):
# all in the range of 0-7, corresponding to some sort of filter. They get sizes # all in the range of 0-7, corresponding to some sort of filter. They get sizes
# looked up and I presume there's data following this corresponding to those sizes. # looked up and I presume there's data following this corresponding to those sizes.
# I don't know however as I've not encountered data with this bit. # I don't know however as I've not encountered data with this bit.
vprint(f"{prefix} Unknown Filter data Count: {count}, Size: {filter_size}") self.vprint(f"{prefix} Unknown Filter data Count: {count}, Size: {filter_size}")
if flags & 0x1000000: if flags & 0x1000000:
# Some sort of point, perhaps an x, y offset for the object? # Some sort of point, perhaps an x, y offset for the object?
@ -628,13 +612,13 @@ class SWF(TrackedCoverage):
# TODO: This doesn't seem right when run past Pop'n Music data. # TODO: This doesn't seem right when run past Pop'n Music data.
point = Point(x / 20.0, y / 20.0) point = Point(x / 20.0, y / 20.0)
vprint(f"{prefix} Point: {point}") self.vprint(f"{prefix} Point: {point}")
if flags & 0x2000000: if flags & 0x2000000:
# Same as above, but initializing to 0, 0 instead of from data. # Same as above, but initializing to 0, 0 instead of from data.
unhandled_flags &= ~0x2000000 unhandled_flags &= ~0x2000000
point = Point(0.0, 0.0) point = Point(0.0, 0.0)
vprint(f"{prefix} Point: {point}") self.vprint(f"{prefix} Point: {point}")
if flags & 0x40000: if flags & 0x40000:
# Some pair of shorts, not sure, its in DDR PS3 data. # Some pair of shorts, not sure, its in DDR PS3 data.
@ -645,7 +629,7 @@ class SWF(TrackedCoverage):
# TODO: I have no idea what these are. # TODO: I have no idea what these are.
point = Point(x * 3.051758e-05, y * 3.051758e-05) point = Point(x * 3.051758e-05, y * 3.051758e-05)
vprint(f"{prefix} Point: {point}") self.vprint(f"{prefix} Point: {point}")
if flags & 0x80000: if flags & 0x80000:
# Some pair of shorts, not sure, its in DDR PS3 data. # Some pair of shorts, not sure, its in DDR PS3 data.
@ -656,22 +640,22 @@ class SWF(TrackedCoverage):
# TODO: I have no idea what these are. # TODO: I have no idea what these are.
point = Point(x * 3.051758e-05, y * 3.051758e-05) point = Point(x * 3.051758e-05, y * 3.051758e-05)
vprint(f"{prefix} Point: {point}") self.vprint(f"{prefix} Point: {point}")
# This flag states whether we are creating a new object on this depth, or updating one. # This flag states whether we are creating a new object on this depth, or updating one.
unhandled_flags &= ~0xD unhandled_flags &= ~0xD
if flags & 0x1: if flags & 0x1:
vprint(f"{prefix} Update object request") self.vprint(f"{prefix} Update object request")
else: else:
vprint(f"{prefix} Create object request") self.vprint(f"{prefix} Create object request")
if flags & 0x4: if flags & 0x4:
vprint(f"{prefix} Use transform matrix") self.vprint(f"{prefix} Use transform matrix")
else: else:
vprint(f"{prefix} Ignore transform matrix") self.vprint(f"{prefix} Ignore transform matrix")
if flags & 0x4: if flags & 0x4:
vprint(f"{prefix} Use color information") self.vprint(f"{prefix} Use color information")
else: else:
vprint(f"{prefix} Ignore color information") self.vprint(f"{prefix} Ignore color information")
if unhandled_flags != 0: if unhandled_flags != 0:
raise Exception(f"Did not handle {hex(unhandled_flags)} flag bits!") raise Exception(f"Did not handle {hex(unhandled_flags)} flag bits!")
@ -685,7 +669,7 @@ class SWF(TrackedCoverage):
raise Exception(f"Invalid shape size {size}") raise Exception(f"Invalid shape size {size}")
object_id, depth = struct.unpack("<HH", ap2data[dataoffset:(dataoffset + 4)]) object_id, depth = struct.unpack("<HH", ap2data[dataoffset:(dataoffset + 4)])
vprint(f"{prefix} Object ID: {object_id}, Depth: {depth}") self.vprint(f"{prefix} Object ID: {object_id}, Depth: {depth}")
self.add_coverage(dataoffset, 4) self.add_coverage(dataoffset, 4)
elif tagid == AP2Tag.AP2_DEFINE_EDIT_TEXT: elif tagid == AP2Tag.AP2_DEFINE_EDIT_TEXT:
if size != 44: if size != 44:
@ -700,16 +684,16 @@ class SWF(TrackedCoverage):
rgba, f1, f2, f3, f4, variable_name_offset, default_text_offset = struct.unpack("<IiiiiHH", ap2data[(dataoffset + 20):(dataoffset + 44)]) rgba, f1, f2, f3, f4, variable_name_offset, default_text_offset = struct.unpack("<IiiiiHH", ap2data[(dataoffset + 20):(dataoffset + 44)])
self.add_coverage(dataoffset + 20, 24) self.add_coverage(dataoffset + 20, 24)
vprint(f"{prefix} Tag ID: {edit_text_id}, Font Tag: {defined_font_tag_id}, Height Selection: {font_height}, Flags: {hex(flags)}") self.vprint(f"{prefix} Tag ID: {edit_text_id}, Font Tag: {defined_font_tag_id}, Height Selection: {font_height}, Flags: {hex(flags)}")
unk_string2 = self.__get_string(unk_str2_offset) or None unk_string2 = self.__get_string(unk_str2_offset) or None
vprint(f"{prefix} Unk String: {unk_string2}") self.vprint(f"{prefix} Unk String: {unk_string2}")
rect = Rectangle(f1 / 20.0, f2 / 20.0, f3 / 20.0, f4 / 20.0) rect = Rectangle(f1 / 20.0, f2 / 20.0, f3 / 20.0, f4 / 20.0)
vprint(f"{prefix} Rectangle: {rect}") self.vprint(f"{prefix} Rectangle: {rect}")
variable_name = self.__get_string(variable_name_offset) or None variable_name = self.__get_string(variable_name_offset) or None
vprint(f"{prefix} Variable Name: {variable_name}") self.vprint(f"{prefix} Variable Name: {variable_name}")
color = Color( color = Color(
r=(rgba & 0xFF) / 255.0, r=(rgba & 0xFF) / 255.0,
@ -717,9 +701,9 @@ class SWF(TrackedCoverage):
b=((rgba >> 16) & 0xFF) / 255.0, b=((rgba >> 16) & 0xFF) / 255.0,
a=((rgba >> 24) & 0xFF) / 255.0, a=((rgba >> 24) & 0xFF) / 255.0,
) )
vprint(f"{prefix} Text Color: {color}") self.vprint(f"{prefix} Text Color: {color}")
vprint(f"{prefix} Unk1: {unk1}, Unk2: {unk2}, Unk3: {unk3}, Unk4: {unk4}") self.vprint(f"{prefix} Unk1: {unk1}, Unk2: {unk2}, Unk3: {unk3}, Unk4: {unk4}")
# flags & 0x20 means something with offset 16-18. # flags & 0x20 means something with offset 16-18.
# flags & 0x200 is unk str below is a HTML tag. # flags & 0x200 is unk str below is a HTML tag.
@ -727,19 +711,11 @@ class SWF(TrackedCoverage):
if flags & 0x80: if flags & 0x80:
# Has some sort of string pointer. # Has some sort of string pointer.
default_text = self.__get_string(default_text_offset) or None default_text = self.__get_string(default_text_offset) or None
vprint(f"{prefix} Default Text: {default_text}") self.vprint(f"{prefix} Default Text: {default_text}")
else: else:
raise Exception(f"Unimplemented tag {hex(tagid)}!") raise Exception(f"Unimplemented tag {hex(tagid)}!")
def __parse_tags(self, ap2_version: int, afp_version: int, ap2data: bytes, tags_base_offset: int, prefix: str = "", verbose: bool = False) -> None: def __parse_tags(self, ap2_version: int, afp_version: int, ap2data: bytes, tags_base_offset: int, prefix: str = "") -> None:
# Suppress debug text unless asked
if verbose:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
print(*args, **kwargs, file=sys.stderr)
else:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
pass
unknown_tags_flags, unknown_tags_count, frame_count, tags_count, unknown_tags_offset, frame_offset, tags_offset = struct.unpack( unknown_tags_flags, unknown_tags_count, frame_count, tags_count, unknown_tags_offset, frame_offset, tags_offset = struct.unpack(
"<HHIIIII", "<HHIIIII",
ap2data[tags_base_offset:(tags_base_offset + 24)] ap2data[tags_base_offset:(tags_base_offset + 24)]
@ -752,7 +728,7 @@ class SWF(TrackedCoverage):
frame_offset += tags_base_offset frame_offset += tags_base_offset
# First, parse regular tags. # First, parse regular tags.
vprint(f"{prefix}Number of Tags: {tags_count}") self.vprint(f"{prefix}Number of Tags: {tags_count}")
for i in range(tags_count): for i in range(tags_count):
tag = struct.unpack("<I", ap2data[tags_offset:(tags_offset + 4)])[0] tag = struct.unpack("<I", ap2data[tags_offset:(tags_offset + 4)])[0]
self.add_coverage(tags_offset, 4) self.add_coverage(tags_offset, 4)
@ -763,12 +739,12 @@ class SWF(TrackedCoverage):
if size > 0x200000: if size > 0x200000:
raise Exception(f"Invalid tag size {size} ({hex(size)})") raise Exception(f"Invalid tag size {size} ({hex(size)})")
vprint(f"{prefix} Tag: {hex(tagid)} ({AP2Tag.tag_to_name(tagid)}), Size: {hex(size)}, Offset: {hex(tags_offset + 4)}") self.vprint(f"{prefix} Tag: {hex(tagid)} ({AP2Tag.tag_to_name(tagid)}), Size: {hex(size)}, Offset: {hex(tags_offset + 4)}")
self.__parse_tag(ap2_version, afp_version, ap2data, tagid, size, tags_offset + 4, prefix=prefix, verbose=verbose) self.__parse_tag(ap2_version, afp_version, ap2data, tagid, size, tags_offset + 4, prefix=prefix)
tags_offset += ((size + 3) & 0xFFFFFFFC) + 4 # Skip past tag header and data, rounding to the nearest 4 bytes. tags_offset += ((size + 3) & 0xFFFFFFFC) + 4 # Skip past tag header and data, rounding to the nearest 4 bytes.
# Now, parse frames. # Now, parse frames.
vprint(f"{prefix}Number of Frames: {frame_count}") self.vprint(f"{prefix}Number of Frames: {frame_count}")
for i in range(frame_count): for i in range(frame_count):
frame_info = struct.unpack("<I", ap2data[frame_offset:(frame_offset + 4)])[0] frame_info = struct.unpack("<I", ap2data[frame_offset:(frame_offset + 4)])[0]
self.add_coverage(frame_offset, 4) self.add_coverage(frame_offset, 4)
@ -776,18 +752,18 @@ class SWF(TrackedCoverage):
start_tag_id = frame_info & 0xFFFFF start_tag_id = frame_info & 0xFFFFF
num_tags_to_play = (frame_info >> 20) & 0xFFF num_tags_to_play = (frame_info >> 20) & 0xFFF
vprint(f"{prefix} Frame Start Tag: {hex(start_tag_id)}, Count: {num_tags_to_play}") self.vprint(f"{prefix} Frame Start Tag: {hex(start_tag_id)}, Count: {num_tags_to_play}")
frame_offset += 4 frame_offset += 4
# Now, parse unknown tags? I have no idea what these are, but they're referencing strings that # Now, parse unknown tags? I have no idea what these are, but they're referencing strings that
# are otherwise unused. # are otherwise unused.
vprint(f"{prefix}Number of Unknown Tags: {unknown_tags_count}, Flags: {hex(unknown_tags_flags)}") self.vprint(f"{prefix}Number of Unknown Tags: {unknown_tags_count}, Flags: {hex(unknown_tags_flags)}")
for i in range(unknown_tags_count): for i in range(unknown_tags_count):
unk1, stringoffset = struct.unpack("<HH", ap2data[unknown_tags_offset:(unknown_tags_offset + 4)]) unk1, stringoffset = struct.unpack("<HH", ap2data[unknown_tags_offset:(unknown_tags_offset + 4)])
strval = self.__get_string(stringoffset) strval = self.__get_string(stringoffset)
self.add_coverage(unknown_tags_offset, 4) self.add_coverage(unknown_tags_offset, 4)
vprint(f"{prefix} Unknown Tag: {hex(unk1)} Name: {strval}") self.vprint(f"{prefix} Unknown Tag: {hex(unk1)} Name: {strval}")
unknown_tags_offset += 4 unknown_tags_offset += 4
def __descramble(self, scrambled_data: bytes, descramble_info: bytes) -> bytes: def __descramble(self, scrambled_data: bytes, descramble_info: bytes) -> bytes:
@ -862,20 +838,10 @@ class SWF(TrackedCoverage):
def parse(self, verbose: bool = False) -> None: def parse(self, verbose: bool = False) -> None:
with self.covered(len(self.data), verbose): with self.covered(len(self.data), verbose):
self.__parse(verbose) with self.debugging(verbose):
self.__parse(verbose)
def __parse(self, verbose: bool) -> None: def __parse(self, verbose: bool) -> None:
# Suppress debug text unless asked
if verbose:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
print(*args, **kwargs, file=sys.stderr)
# Reinitialize coverage.
self.strings = {}
else:
def vprint(*args: Any, **kwargs: Any) -> None: # type: ignore
pass
# First, use the byteswap header to descramble the data. # First, use the byteswap header to descramble the data.
data = self.__descramble(self.data, self.descramble_info) data = self.__descramble(self.data, self.descramble_info)
@ -935,25 +901,25 @@ class SWF(TrackedCoverage):
# Get exported SWF name. # Get exported SWF name.
self.exported_name = self.__get_string(nameoffset) self.exported_name = self.__get_string(nameoffset)
self.add_coverage(nameoffset + stringtable_offset, len(self.exported_name) + 1, unique=False) self.add_coverage(nameoffset + stringtable_offset, len(self.exported_name) + 1, unique=False)
vprint(f"{os.linesep}AFP name: {self.name}") self.vprint(f"{os.linesep}AFP name: {self.name}")
vprint(f"Container Version: {hex(ap2_data_version)}") self.vprint(f"Container Version: {hex(ap2_data_version)}")
vprint(f"Version: {hex(version)}") self.vprint(f"Version: {hex(version)}")
vprint(f"Exported Name: {self.exported_name}") self.vprint(f"Exported Name: {self.exported_name}")
vprint(f"SWF Flags: {hex(flags)}") self.vprint(f"SWF Flags: {hex(flags)}")
if flags & 0x1: if flags & 0x1:
vprint(f" 0x1: Movie background color: {swf_color}") self.vprint(f" 0x1: Movie background color: {swf_color}")
else: else:
vprint(" 0x2: No movie background color") self.vprint(" 0x2: No movie background color")
if flags & 0x2: if flags & 0x2:
vprint(" 0x2: FPS is an integer") self.vprint(" 0x2: FPS is an integer")
else: else:
vprint(" 0x2: FPS is a float") self.vprint(" 0x2: FPS is a float")
if flags & 0x4: if flags & 0x4:
vprint(" 0x4: Imported tag initializer section present") self.vprint(" 0x4: Imported tag initializer section present")
else: else:
vprint(" 0x4: Imported tag initializer section not present") self.vprint(" 0x4: Imported tag initializer section not present")
vprint(f"Dimensions: {width}x{height}") self.vprint(f"Dimensions: {width}x{height}")
vprint(f"Requested FPS: {fps}") self.vprint(f"Requested FPS: {fps}")
# Exported assets # Exported assets
num_exported_assets = struct.unpack("<H", data[32:34])[0] num_exported_assets = struct.unpack("<H", data[32:34])[0]
@ -962,7 +928,7 @@ class SWF(TrackedCoverage):
self.add_coverage(40, 4) self.add_coverage(40, 4)
# Parse exported asset tag names and their tag IDs. # Parse exported asset tag names and their tag IDs.
vprint(f"Number of Exported Tags: {num_exported_assets}") self.vprint(f"Number of Exported Tags: {num_exported_assets}")
for assetno in range(num_exported_assets): for assetno in range(num_exported_assets):
asset_data_offset, asset_string_offset = struct.unpack("<HH", data[asset_offset:(asset_offset + 4)]) asset_data_offset, asset_string_offset = struct.unpack("<HH", data[asset_offset:(asset_offset + 4)])
self.add_coverage(asset_offset, 4) self.add_coverage(asset_offset, 4)
@ -970,12 +936,12 @@ class SWF(TrackedCoverage):
asset_name = self.__get_string(asset_string_offset) asset_name = self.__get_string(asset_string_offset)
self.add_coverage(asset_string_offset + stringtable_offset, len(asset_name) + 1, unique=False) self.add_coverage(asset_string_offset + stringtable_offset, len(asset_name) + 1, unique=False)
vprint(f" {assetno}: Tag Name: {asset_name} Tag ID: {asset_data_offset}") self.vprint(f" {assetno}: Tag Name: {asset_name} Tag ID: {asset_data_offset}")
# Tag sections # Tag sections
tags_offset = struct.unpack("<I", data[36:40])[0] tags_offset = struct.unpack("<I", data[36:40])[0]
self.add_coverage(36, 4) self.add_coverage(36, 4)
self.__parse_tags(ap2_data_version, version, data, tags_offset, verbose=verbose) self.__parse_tags(ap2_data_version, version, data, tags_offset)
# Imported tags sections # Imported tags sections
imported_tags_count = struct.unpack("<h", data[34:36])[0] imported_tags_count = struct.unpack("<h", data[34:36])[0]
@ -984,7 +950,7 @@ class SWF(TrackedCoverage):
self.add_coverage(34, 2) self.add_coverage(34, 2)
self.add_coverage(44, 4) self.add_coverage(44, 4)
vprint(f"Number of Imported Tags: {imported_tags_count}") self.vprint(f"Number of Imported Tags: {imported_tags_count}")
for i in range(imported_tags_count): for i in range(imported_tags_count):
# First grab the SWF this is importing from, and the number of assets being imported. # First grab the SWF this is importing from, and the number of assets being imported.
swf_name_offset, count = struct.unpack("<HH", data[imported_tags_offset:(imported_tags_offset + 4)]) swf_name_offset, count = struct.unpack("<HH", data[imported_tags_offset:(imported_tags_offset + 4)])
@ -992,7 +958,7 @@ class SWF(TrackedCoverage):
swf_name = self.__get_string(swf_name_offset) swf_name = self.__get_string(swf_name_offset)
self.add_coverage(swf_name_offset + stringtable_offset, len(swf_name) + 1, unique=False) self.add_coverage(swf_name_offset + stringtable_offset, len(swf_name) + 1, unique=False)
vprint(f" Source SWF: {swf_name}") self.vprint(f" Source SWF: {swf_name}")
# Now, grab the actual asset names being imported. # Now, grab the actual asset names being imported.
for j in range(count): for j in range(count):
@ -1001,7 +967,7 @@ class SWF(TrackedCoverage):
asset_name = self.__get_string(asset_name_offset) asset_name = self.__get_string(asset_name_offset)
self.add_coverage(asset_name_offset + stringtable_offset, len(asset_name) + 1, unique=False) self.add_coverage(asset_name_offset + stringtable_offset, len(asset_name) + 1, unique=False)
vprint(f" Tag ID: {asset_id_no}, Requested Asset: {asset_name}") self.vprint(f" Tag ID: {asset_id_no}, Requested Asset: {asset_name}")
imported_tags_data_offset += 4 imported_tags_data_offset += 4
@ -1014,7 +980,7 @@ class SWF(TrackedCoverage):
unk1, length = struct.unpack("<HH", data[imported_tag_initializers_offset:(imported_tag_initializers_offset + 4)]) unk1, length = struct.unpack("<HH", data[imported_tag_initializers_offset:(imported_tag_initializers_offset + 4)])
self.add_coverage(imported_tag_initializers_offset, 4) self.add_coverage(imported_tag_initializers_offset, 4)
vprint(f"Imported Tag Initializer Offset: {hex(imported_tag_initializers_offset)}, Length: {length}") self.vprint(f"Imported Tag Initializer Offset: {hex(imported_tag_initializers_offset)}, Length: {length}")
for i in range(length): for i in range(length):
item_offset = imported_tag_initializers_offset + 4 + (i * 12) item_offset = imported_tag_initializers_offset + 4 + (i * 12)
@ -1022,11 +988,11 @@ class SWF(TrackedCoverage):
self.add_coverage(item_offset, 12) self.add_coverage(item_offset, 12)
if action_bytecode_length != 0: if action_bytecode_length != 0:
vprint(f" Tag ID: {tag_id}, Frame: {frame}, Bytecode Offset: {hex(action_bytecode_offset + imported_tag_initializers_offset)}") self.vprint(f" Tag ID: {tag_id}, Frame: {frame}, Bytecode Offset: {hex(action_bytecode_offset + imported_tag_initializers_offset)}")
bytecode_data = data[(action_bytecode_offset + imported_tag_initializers_offset):(action_bytecode_offset + imported_tag_initializers_offset + action_bytecode_length)] bytecode_data = data[(action_bytecode_offset + imported_tag_initializers_offset):(action_bytecode_offset + imported_tag_initializers_offset + action_bytecode_length)]
self.__parse_bytecode(bytecode_data, verbose=verbose) self.__parse_bytecode(bytecode_data)
else: else:
vprint(f" Tag ID: {tag_id}, Frame: {frame}, No Bytecode Present") self.vprint(f" Tag ID: {tag_id}, Frame: {frame}, No Bytecode Present")
if verbose: if verbose:
self.print_coverage() self.print_coverage()

View File

@ -90,3 +90,31 @@ class TrackedCoverage:
# Print final range # Print final range
offset = len(self.coverage) offset = len(self.coverage)
print(f"Uncovered: {hex(start)} - {hex(offset)} ({offset-start} bytes)", file=sys.stderr) print(f"Uncovered: {hex(start)} - {hex(offset)} ({offset-start} bytes)", file=sys.stderr)
class VerboseOutputManager:
def __init__(self, covered_class: "VerboseOutput", verbose: bool) -> None:
self.covered_class = covered_class
self.verbose = verbose
def __enter__(self) -> "VerboseOutputManager":
if self.verbose:
self.covered_class._verbose = True
else:
self.covered_class._verbose = False
return self
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
self.covered_class._verbose = False
class VerboseOutput:
def __init__(self) -> None:
self._verbose: bool = False
def debugging(self, verbose: bool) -> VerboseOutputManager:
return VerboseOutputManager(self, verbose)
def vprint(self, *args: Any, **kwargs: Any) -> None:
if self._verbose:
print(*args, **kwargs, file=sys.stderr)