1
0
mirror of synced 2024-11-27 23:50:47 +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 os
import struct
import sys
from PIL import Image # type: ignore
from typing import Any, Dict, List, Optional, Tuple
@ -12,7 +11,7 @@ from bemani.protocol.node import Node
from .swf import SWF
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:
@ -140,7 +139,7 @@ class Unknown2:
}
class TXP2File(TrackedCoverage):
class TXP2File(TrackedCoverage, VerboseOutput):
def __init__(self, contents: bytes, verbose: bool = False) -> None:
# Make sure our coverage engine is initialized.
super().__init__()
@ -213,7 +212,8 @@ class TXP2File(TrackedCoverage):
# Parse out the file structure.
with self.covered(len(contents), verbose):
self.__parse(verbose)
with self.debugging(verbose):
self.__parse(verbose)
def as_dict(self) -> Dict[str, Any]:
return {
@ -265,15 +265,7 @@ class TXP2File(TrackedCoverage):
offset += 1
return out
def descramble_pman(self, offset: int, verbose: bool) -> 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
def descramble_pman(self, offset: int) -> PMAN:
# Unclear what the first three unknowns are, but the fourth
# looks like it could possibly be two int16s indicating unknown?
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)
names[entry_no] = name
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')):
raise Exception(f"Name CRC failed for {name}")
@ -333,18 +325,7 @@ class TXP2File(TrackedCoverage):
flags3=flags3,
)
def __parse(
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
def __parse(self, verbose: bool) -> None:
# First, check the signature
if self.data[0:4] == b"2PXT":
self.endian = "<"
@ -389,7 +370,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(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):
interesting_offset = offset + (x * 12)
@ -418,7 +399,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(texture_offset, 8)
if deflated_size != (texture_length - 8):
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)
# Get the data offset.
@ -439,7 +420,7 @@ class TXP2File(TrackedCoverage):
# I assume they're like the above, so lets put in some asertions.
if deflated_size != (texture_length - 8):
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.
lz_data = None
@ -633,7 +614,7 @@ class TXP2File(TrackedCoverage):
'RGBA', (width, height), raw_data[64:], 'raw', 'BGRA',
)
else:
vprint(f"Unsupported format {hex(fmt)} for texture {name}")
self.vprint(f"Unsupported format {hex(fmt)} for texture {name}")
img = None
self.textures.append(
@ -652,7 +633,7 @@ class TXP2File(TrackedCoverage):
)
)
else:
vprint("Bit 0x000001 - textures; NOT PRESENT")
self.vprint("Bit 0x000001 - textures; NOT PRESENT")
# Mapping between texture index and the name of the texture.
if feature_mask & 0x02:
@ -661,17 +642,17 @@ class TXP2File(TrackedCoverage):
self.add_coverage(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:
self.texturemap = self.descramble_pman(offset, verbose)
self.texturemap = self.descramble_pman(offset)
else:
vprint("Bit 0x000002 - texturemapping; NOT PRESENT")
self.vprint("Bit 0x000002 - texturemapping; NOT PRESENT")
if feature_mask & 0x04:
vprint("Bit 0x000004 - legacy lz mode on")
self.vprint("Bit 0x000004 - legacy lz mode on")
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
# region of texture that this particular graphic makes up.
@ -683,7 +664,7 @@ class TXP2File(TrackedCoverage):
self.add_coverage(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:
for i in range(length):
@ -702,9 +683,9 @@ class TXP2File(TrackedCoverage):
region = TextureRegion(texture_no, left, top, right, bottom)
self.texture_to_region.append(region)
vprint(f" {region}, offset: {hex(descriptor_offset)}")
self.vprint(f" {region}, offset: {hex(descriptor_offset)}")
else:
vprint("Bit 0x000008 - regions; NOT PRESENT")
self.vprint("Bit 0x000008 - regions; NOT PRESENT")
if feature_mask & 0x10:
# 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)
header_offset += 4
vprint(f"Bit 0x000010 - regionmapping; offset: {hex(offset)}")
self.vprint(f"Bit 0x000010 - regionmapping; offset: {hex(offset)}")
if offset != 0:
self.regionmap = self.descramble_pman(offset, verbose)
self.regionmap = self.descramble_pman(offset)
else:
vprint("Bit 0x000010 - regionmapping; NOT PRESENT")
self.vprint("Bit 0x000010 - regionmapping; NOT PRESENT")
if feature_mask & 0x20:
vprint("Bit 0x000020 - text obfuscation on")
self.vprint("Bit 0x000020 - text obfuscation on")
else:
vprint("Bit 0x000020 - text obfuscation off")
self.vprint("Bit 0x000020 - text obfuscation off")
if feature_mask & 0x40:
# 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)
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:
for i in range(length):
@ -749,7 +730,7 @@ class TXP2File(TrackedCoverage):
bytedata = self.get_until_null(name_offset)
self.add_coverage(name_offset, len(bytedata) + 1, unique=False)
name = descramble_text(bytedata, self.text_obfuscated)
vprint(f" {name}")
self.vprint(f" {name}")
self.unknown1.append(
Unknown1(
@ -759,7 +740,7 @@ class TXP2File(TrackedCoverage):
)
self.add_coverage(unk_offset + 4, 12)
else:
vprint("Bit 0x000040 - unknown; NOT PRESENT")
self.vprint("Bit 0x000040 - unknown; NOT PRESENT")
if feature_mask & 0x80:
# 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)
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.
if offset != 0:
self.unk_pman1 = self.descramble_pman(offset, verbose)
self.unk_pman1 = self.descramble_pman(offset)
else:
vprint("Bit 0x000080 - unknownmapping; NOT PRESENT")
self.vprint("Bit 0x000080 - unknownmapping; NOT PRESENT")
if feature_mask & 0x100:
# 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)
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:
for i in range(length):
@ -793,7 +774,7 @@ class TXP2File(TrackedCoverage):
)
self.add_coverage(unk_offset, 4)
else:
vprint("Bit 0x000100 - unknown; NOT PRESENT")
self.vprint("Bit 0x000100 - unknown; NOT PRESENT")
if feature_mask & 0x200:
# 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)
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.
if offset != 0:
self.unk_pman2 = self.descramble_pman(offset, verbose)
self.unk_pman2 = self.descramble_pman(offset)
else:
vprint("Bit 0x000200 - unknownmapping; NOT PRESENT")
self.vprint("Bit 0x000200 - unknownmapping; NOT PRESENT")
if feature_mask & 0x400:
# 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)
header_offset += 4
vprint(f"Bit 0x000400 - unknown; offset: {hex(offset)}")
self.vprint(f"Bit 0x000400 - unknown; offset: {hex(offset)}")
else:
vprint("Bit 0x000400 - unknown; NOT PRESENT")
self.vprint("Bit 0x000400 - unknown; NOT PRESENT")
if feature_mask & 0x800:
# 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)
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):
interesting_offset = offset + (x * 12)
@ -844,7 +825,7 @@ class TXP2File(TrackedCoverage):
bytedata = self.get_until_null(name_offset)
self.add_coverage(name_offset, len(bytedata) + 1, unique=False)
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:
self.swfdata.append(
@ -855,7 +836,7 @@ class TXP2File(TrackedCoverage):
)
self.add_coverage(swf_offset, swf_length)
else:
vprint("Bit 0x000800 - swfdata; NOT PRESENT")
self.vprint("Bit 0x000800 - swfdata; NOT PRESENT")
if feature_mask & 0x1000:
# A mapping structure that allows looking up SWF data by name.
@ -863,12 +844,12 @@ class TXP2File(TrackedCoverage):
self.add_coverage(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:
self.swfmap = self.descramble_pman(offset, verbose)
self.swfmap = self.descramble_pman(offset)
else:
vprint("Bit 0x001000 - swfmapping; NOT PRESENT")
self.vprint("Bit 0x001000 - swfmapping; NOT PRESENT")
if feature_mask & 0x2000:
# 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)
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):
shape_base_offset = offset + (x * 12)
@ -906,12 +887,12 @@ class TXP2File(TrackedCoverage):
self.shapes.append(shape)
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):
vprint(f" {line}")
self.vprint(f" {line}")
else:
vprint("Bit 0x002000 - shapes; NOT PRESENT")
self.vprint("Bit 0x002000 - shapes; NOT PRESENT")
if feature_mask & 0x4000:
# 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)
header_offset += 4
vprint(f"Bit 0x004000 - shapesmapping; offset: {hex(offset)}")
self.vprint(f"Bit 0x004000 - shapesmapping; offset: {hex(offset)}")
if offset != 0:
self.shapemap = self.descramble_pman(offset, verbose)
self.shapemap = self.descramble_pman(offset)
else:
vprint("Bit 0x004000 - shapesmapping; NOT PRESENT")
self.vprint("Bit 0x004000 - shapesmapping; NOT PRESENT")
if feature_mask & 0x8000:
# 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)
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
# bad and make things read only.
self.read_only = True
else:
vprint("Bit 0x008000 - unknown; NOT PRESENT")
self.vprint("Bit 0x008000 - unknown; NOT PRESENT")
if feature_mask & 0x10000:
# 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)])
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 we find non-zero versions of this, then that means updating the file is
@ -966,7 +947,7 @@ class TXP2File(TrackedCoverage):
else:
self.fontdata = None
else:
vprint("Bit 0x010000 - fontinfo; NOT PRESENT")
self.vprint("Bit 0x010000 - fontinfo; NOT PRESENT")
if feature_mask & 0x20000:
# 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)
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:
for i in range(len(self.swfdata)):
@ -988,7 +969,7 @@ class TXP2File(TrackedCoverage):
f"{self.endian}III",
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)
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.add_coverage(afp_header, afp_header_length)
else:
vprint("Bit 0x020000 - swfheaders; NOT PRESENT")
self.vprint("Bit 0x020000 - swfheaders; NOT PRESENT")
if feature_mask & 0x40000:
vprint("Bit 0x040000 - modern lz mode on")
self.vprint("Bit 0x040000 - modern lz mode on")
else:
vprint("Bit 0x040000 - modern lz mode off")
self.vprint("Bit 0x040000 - modern lz mode off")
if feature_mask & 0xFFF80000:
# 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 AP2Action, AP2Tag, AP2Property
from .util import TrackedCoverage, _hex
from .util import TrackedCoverage, VerboseOutput, _hex
class SWF(TrackedCoverage):
class SWF(TrackedCoverage, VerboseOutput):
def __init__(
self,
name: str,
@ -47,15 +47,7 @@ class SWF(TrackedCoverage):
'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:
# 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
def __parse_bytecode(self, datachunk: bytes, string_offsets: List[int] = [], prefix: str = "") -> None:
# 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]
@ -79,7 +71,7 @@ class SWF(TrackedCoverage):
offset_ptr = 2
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:
while offset_ptr < len(datachunk):
@ -94,7 +86,7 @@ class SWF(TrackedCoverage):
lineno = offset_ptr - start_offset
if opcode in AP2Action.actions_without_params():
vprint(f"{prefix} {lineno}: {action_name}")
self.vprint(f"{prefix} {lineno}: {action_name}")
offset_ptr += 1
elif opcode == AP2Action.DEFINE_FUNCTION2:
function_flags, funcname_offset, bytecode_offset, _, bytecode_count = struct.unpack(
@ -108,16 +100,16 @@ class SWF(TrackedCoverage):
funcname = self.__get_string(funcname_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.__parse_bytecode(datachunk[offset_ptr:(offset_ptr + bytecode_count)], string_offsets=string_offsets, prefix=prefix + " ", verbose=verbose)
vprint(f"{prefix} END_{action_name}")
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 + " ")
self.vprint(f"{prefix} END_{action_name}")
offset_ptr += bytecode_count
elif opcode == AP2Action.PUSH:
obj_count = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
offset_ptr += 2
vprint(f"{prefix} {lineno}: {action_name}")
self.vprint(f"{prefix} {lineno}: {action_name}")
while obj_count > 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:
# Integer "0" object.
vprint(f"{prefix} INTEGER: 0")
self.vprint(f"{prefix} INTEGER: 0")
elif obj_to_create == 0x1:
# Float object, represented internally as a double.
fval = struct.unpack(">f", datachunk[offset_ptr:(offset_ptr + 4)])[0]
offset_ptr += 4
vprint(f"{prefix} FLOAT: {fval}")
self.vprint(f"{prefix} FLOAT: {fval}")
elif obj_to_create == 0x2:
# Null pointer object.
vprint(f"{prefix} NULL")
self.vprint(f"{prefix} NULL")
elif obj_to_create == 0x3:
# Undefined constant.
vprint(f"{prefix} UNDEFINED")
self.vprint(f"{prefix} UNDEFINED")
elif obj_to_create == 0x4:
# Register value.
regno = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0]
offset_ptr += 1
vprint(f"{prefix} REGISTER NO: {regno}")
self.vprint(f"{prefix} REGISTER NO: {regno}")
elif obj_to_create == 0x5:
# Boolean "TRUE" object.
vprint(f"{prefix} BOOLEAN: True")
self.vprint(f"{prefix} BOOLEAN: True")
elif obj_to_create == 0x6:
# Boolean "FALSE" object.
vprint(f"{prefix} BOOLEAN: False")
self.vprint(f"{prefix} BOOLEAN: False")
elif obj_to_create == 0x7:
# Integer object.
ival = struct.unpack(">I", datachunk[offset_ptr:(offset_ptr + 4)])[0]
offset_ptr += 4
vprint(f"{prefix} INTEGER: {ival}")
self.vprint(f"{prefix} INTEGER: {ival}")
elif obj_to_create == 0x8:
# String constant object.
const_offset = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0]
const = self.__get_string(string_offsets[const_offset])
offset_ptr += 1
vprint(f"{prefix} STRING CONST: {const}")
self.vprint(f"{prefix} STRING CONST: {const}")
elif obj_to_create == 0x9:
# String constant, but with 16 bits for the offset. Probably not used except
# on the largest files.
@ -170,102 +162,102 @@ class SWF(TrackedCoverage):
const = self.__get_string(string_offsets[const_offset])
offset_ptr += 2
vprint(f"{prefix} STRING_CONTS: {const}")
self.vprint(f"{prefix} STRING_CONTS: {const}")
elif obj_to_create == 0xa:
# NaN constant.
vprint(f"{prefix} NAN")
self.vprint(f"{prefix} NAN")
elif obj_to_create == 0xb:
# Infinity constant.
vprint(f"{prefix} INFINITY")
self.vprint(f"{prefix} INFINITY")
elif obj_to_create == 0xc:
# 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:
# 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:
# Pointer to "parent" object, whatever currently is executing the bytecode.
# This seems to be the parent of the movie clip, or the current movieclip
# if that isn't set.
vprint(f"{prefix} POINTER TO PARENT")
self.vprint(f"{prefix} POINTER TO PARENT")
elif obj_to_create == 0xf:
# Current movie clip.
vprint(f"{prefix} POINTER TO CURRENT MOVIECLIP")
self.vprint(f"{prefix} POINTER TO CURRENT MOVIECLIP")
elif obj_to_create == 0x10:
# Unknown property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x100
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:
# Class property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x300
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:
# Func property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x400
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:
# Other property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x200
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:
# Event property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x500
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:
# Key constants.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x600
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:
# Pointer to global object.
vprint(f"{prefix} POINTER TO GLOBAL OBJECT")
self.vprint(f"{prefix} POINTER TO GLOBAL OBJECT")
elif obj_to_create == 0x24:
# Some other property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x700
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:
# Some other property name.
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x800
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:
# Integer object but one byte.
ival = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0]
offset_ptr += 1
vprint(f"{prefix} INTEGER: {ival}")
self.vprint(f"{prefix} INTEGER: {ival}")
else:
raise Exception(f"Unsupported object {hex(obj_to_create)} to push!")
obj_count -= 1
vprint(f"{prefix} END_{action_name}")
self.vprint(f"{prefix} END_{action_name}")
elif opcode == AP2Action.STORE_REGISTER:
obj_count = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
offset_ptr += 2
vprint(f"{prefix} {lineno}: {action_name}")
self.vprint(f"{prefix} {lineno}: {action_name}")
while obj_count > 0:
register_no = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0]
offset_ptr += 1
obj_count -= 1
vprint(f"{prefix} REGISTER NO: {register_no}")
vprint(f"{prefix} END_{action_name}")
self.vprint(f"{prefix} REGISTER NO: {register_no}")
self.vprint(f"{prefix} END_{action_name}")
elif opcode == AP2Action.STORE_REGISTER2:
register_no = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
offset_ptr += 2
vprint(f"{prefix} {lineno}: {action_name}")
vprint(f"{prefix} REGISTER NO: {register_no}")
vprint(f"{prefix} END_{action_name}")
self.vprint(f"{prefix} {lineno}: {action_name}")
self.vprint(f"{prefix} REGISTER NO: {register_no}")
self.vprint(f"{prefix} END_{action_name}")
elif opcode == AP2Action.IF:
jump_if_true_offset = struct.unpack(">H", datachunk[(offset_ptr + 1):(offset_ptr + 3)])[0]
offset_ptr += 3
@ -275,7 +267,7 @@ class SWF(TrackedCoverage):
# to be absolute instead of relative.
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:
if2_type, jump_if_true_offset = struct.unpack(">BH", datachunk[(offset_ptr + 1):(offset_ptr + 4)])
offset_ptr += 4
@ -301,7 +293,7 @@ class SWF(TrackedCoverage):
12: "IS NOT UNDEFINED",
}[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:
jump_offset = struct.unpack(">H", datachunk[(offset_ptr + 1):(offset_ptr + 3)])[0]
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
# to be absolute instead of relative.
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:
amount_to_add = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
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:
constraint = struct.unpack(">b", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
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:
register_no, amount_to_add = struct.unpack(">BB", datachunk[(offset_ptr + 1):(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:
flags = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
offset_ptr += 2
@ -342,19 +334,11 @@ class SWF(TrackedCoverage):
else:
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:
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:
# 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
def __parse_tag(self, ap2_version: int, afp_version: int, ap2data: bytes, tagid: int, size: int, dataoffset: int, prefix: str = "") -> None:
if tagid == AP2Tag.AP2_SHAPE:
if size != 4:
raise Exception(f"Invalid shape size {size}")
@ -363,7 +347,7 @@ class SWF(TrackedCoverage):
self.add_coverage(dataoffset, size)
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:
sprite_flags, sprite_id = struct.unpack("<HH", ap2data[dataoffset:(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
self.add_coverage(dataoffset + 4, 4)
vprint(f"{prefix} Tag ID: {sprite_id}")
self.__parse_tags(ap2_version, afp_version, ap2data, subtags_offset, prefix=" " + prefix, verbose=verbose)
self.vprint(f"{prefix} Tag ID: {sprite_id}")
self.__parse_tags(ap2_version, afp_version, ap2data, subtags_offset, prefix=" " + prefix)
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)])
self.add_coverage(dataoffset, 12)
@ -385,17 +369,17 @@ class SWF(TrackedCoverage):
fontname = self.__get_string(fontname_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):
entry_offset = dataoffset + 12 + (data_offset * 2) + (i * 2)
entry_value = struct.unpack("<H", ap2data[entry_offset:(entry_offset + 2)])[0]
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:
datachunk = ap2data[dataoffset:(dataoffset + size)]
self.__parse_bytecode(datachunk, prefix=prefix, verbose=verbose)
self.__parse_bytecode(datachunk, prefix=prefix)
self.add_coverage(dataoffset, size)
elif tagid == AP2Tag.AP2_PLACE_OBJECT:
# 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])
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
unhandled_flags = flags
@ -413,14 +397,14 @@ class SWF(TrackedCoverage):
src_tag_id = struct.unpack("<H", datachunk[running_pointer:(running_pointer + 2)])[0]
self.add_coverage(dataoffset + 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:
unhandled_flags &= ~0x10
unk2 = struct.unpack("<H", datachunk[running_pointer:(running_pointer + 2)])[0]
self.add_coverage(dataoffset + running_pointer, 2)
running_pointer += 2
vprint(f"{prefix} Unk2: {hex(unk2)}")
self.vprint(f"{prefix} Unk2: {hex(unk2)}")
if flags & 0x20:
unhandled_flags &= ~0x20
@ -428,21 +412,21 @@ class SWF(TrackedCoverage):
self.add_coverage(dataoffset + running_pointer, 2)
name = self.__get_string(nameoffset)
running_pointer += 2
vprint(f"{prefix} Name: {name}")
self.vprint(f"{prefix} Name: {name}")
if flags & 0x40:
unhandled_flags &= ~0x40
unk3 = struct.unpack("<H", datachunk[running_pointer:(running_pointer + 2)])[0]
self.add_coverage(dataoffset + running_pointer, 2)
running_pointer += 2
vprint(f"{prefix} Unk3: {hex(unk3)}")
self.vprint(f"{prefix} Unk3: {hex(unk3)}")
if flags & 0x20000:
unhandled_flags &= ~0x20000
blend = struct.unpack("<B", datachunk[running_pointer:(running_pointer + 1)])[0]
self.add_coverage(dataoffset + 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.
misalignment = running_pointer & 3
@ -462,7 +446,7 @@ class SWF(TrackedCoverage):
transform.a = float(a_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:
unhandled_flags &= ~0x200
@ -472,7 +456,7 @@ class SWF(TrackedCoverage):
transform.b = float(b_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:
unhandled_flags &= ~0x400
@ -482,7 +466,7 @@ class SWF(TrackedCoverage):
transform.tx = 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
color = Color(1.0, 1.0, 1.0, 1.0)
@ -498,7 +482,7 @@ class SWF(TrackedCoverage):
color.g = float(g) * 0.003921569
color.b = float(b) * 0.003921569
color.a = float(a) * 0.003921569
vprint(f"{prefix} Color: {color}")
self.vprint(f"{prefix} Color: {color}")
if flags & 0x1000:
unhandled_flags &= ~0x1000
@ -510,7 +494,7 @@ class SWF(TrackedCoverage):
acolor.g = float(g) * 0.003921569
acolor.b = float(b) * 0.003921569
acolor.a = float(a) * 0.003921569
vprint(f"{prefix} AColor: {color}")
self.vprint(f"{prefix} AColor: {color}")
if flags & 0x2000:
unhandled_flags &= ~0x2000
@ -522,7 +506,7 @@ class SWF(TrackedCoverage):
color.g = float((rgba >> 16) & 0xFF) * 0.003921569
color.b = float((rgba >> 8) & 0xFF) * 0.003921569
color.a = float(rgba & 0xFF) * 0.003921569
vprint(f"{prefix} Color: {color}")
self.vprint(f"{prefix} Color: {color}")
if flags & 0x4000:
unhandled_flags &= ~0x4000
@ -534,7 +518,7 @@ class SWF(TrackedCoverage):
acolor.g = float((rgba >> 16) & 0xFF) * 0.003921569
acolor.b = float((rgba >> 8) & 0xFF) * 0.003921569
acolor.a = float(rgba & 0xFF) * 0.003921569
vprint(f"{prefix} AColor: {color}")
self.vprint(f"{prefix} AColor: {color}")
if flags & 0x80:
# Object event triggers.
@ -559,7 +543,7 @@ class SWF(TrackedCoverage):
for i, bytecode_offset in enumerate(bytecode_offsets[:-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):
evt_offset = running_pointer + 12 + (evt * 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_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.__parse_bytecode(datachunk[bytecode_offset:(bytecode_offset + bytecode_length)], prefix=prefix + " ", verbose=verbose)
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 + " ")
self.add_coverage(dataoffset + bytecode_offset, bytecode_length)
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
# 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.
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:
# 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.
point = Point(x / 20.0, y / 20.0)
vprint(f"{prefix} Point: {point}")
self.vprint(f"{prefix} Point: {point}")
if flags & 0x2000000:
# Same as above, but initializing to 0, 0 instead of from data.
unhandled_flags &= ~0x2000000
point = Point(0.0, 0.0)
vprint(f"{prefix} Point: {point}")
self.vprint(f"{prefix} Point: {point}")
if flags & 0x40000:
# 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.
point = Point(x * 3.051758e-05, y * 3.051758e-05)
vprint(f"{prefix} Point: {point}")
self.vprint(f"{prefix} Point: {point}")
if flags & 0x80000:
# 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.
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.
unhandled_flags &= ~0xD
if flags & 0x1:
vprint(f"{prefix} Update object request")
self.vprint(f"{prefix} Update object request")
else:
vprint(f"{prefix} Create object request")
self.vprint(f"{prefix} Create object request")
if flags & 0x4:
vprint(f"{prefix} Use transform matrix")
self.vprint(f"{prefix} Use transform matrix")
else:
vprint(f"{prefix} Ignore transform matrix")
self.vprint(f"{prefix} Ignore transform matrix")
if flags & 0x4:
vprint(f"{prefix} Use color information")
self.vprint(f"{prefix} Use color information")
else:
vprint(f"{prefix} Ignore color information")
self.vprint(f"{prefix} Ignore color information")
if unhandled_flags != 0:
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}")
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)
elif tagid == AP2Tag.AP2_DEFINE_EDIT_TEXT:
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)])
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
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)
vprint(f"{prefix} Rectangle: {rect}")
self.vprint(f"{prefix} Rectangle: {rect}")
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(
r=(rgba & 0xFF) / 255.0,
@ -717,9 +701,9 @@ class SWF(TrackedCoverage):
b=((rgba >> 16) & 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 & 0x200 is unk str below is a HTML tag.
@ -727,19 +711,11 @@ class SWF(TrackedCoverage):
if flags & 0x80:
# Has some sort of string pointer.
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:
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:
# 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
def __parse_tags(self, ap2_version: int, afp_version: int, ap2data: bytes, tags_base_offset: int, prefix: str = "") -> None:
unknown_tags_flags, unknown_tags_count, frame_count, tags_count, unknown_tags_offset, frame_offset, tags_offset = struct.unpack(
"<HHIIIII",
ap2data[tags_base_offset:(tags_base_offset + 24)]
@ -752,7 +728,7 @@ class SWF(TrackedCoverage):
frame_offset += tags_base_offset
# 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):
tag = struct.unpack("<I", ap2data[tags_offset:(tags_offset + 4)])[0]
self.add_coverage(tags_offset, 4)
@ -763,12 +739,12 @@ class SWF(TrackedCoverage):
if size > 0x200000:
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.__parse_tag(ap2_version, afp_version, ap2data, tagid, size, tags_offset + 4, prefix=prefix, verbose=verbose)
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)
tags_offset += ((size + 3) & 0xFFFFFFFC) + 4 # Skip past tag header and data, rounding to the nearest 4 bytes.
# 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):
frame_info = struct.unpack("<I", ap2data[frame_offset:(frame_offset + 4)])[0]
self.add_coverage(frame_offset, 4)
@ -776,18 +752,18 @@ class SWF(TrackedCoverage):
start_tag_id = frame_info & 0xFFFFF
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
# Now, parse unknown tags? I have no idea what these are, but they're referencing strings that
# 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):
unk1, stringoffset = struct.unpack("<HH", ap2data[unknown_tags_offset:(unknown_tags_offset + 4)])
strval = self.__get_string(stringoffset)
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
def __descramble(self, scrambled_data: bytes, descramble_info: bytes) -> bytes:
@ -862,20 +838,10 @@ class SWF(TrackedCoverage):
def parse(self, verbose: bool = False) -> None:
with self.covered(len(self.data), verbose):
self.__parse(verbose)
with self.debugging(verbose):
self.__parse(verbose)
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.
data = self.__descramble(self.data, self.descramble_info)
@ -935,25 +901,25 @@ class SWF(TrackedCoverage):
# Get exported SWF name.
self.exported_name = self.__get_string(nameoffset)
self.add_coverage(nameoffset + stringtable_offset, len(self.exported_name) + 1, unique=False)
vprint(f"{os.linesep}AFP name: {self.name}")
vprint(f"Container Version: {hex(ap2_data_version)}")
vprint(f"Version: {hex(version)}")
vprint(f"Exported Name: {self.exported_name}")
vprint(f"SWF Flags: {hex(flags)}")
self.vprint(f"{os.linesep}AFP name: {self.name}")
self.vprint(f"Container Version: {hex(ap2_data_version)}")
self.vprint(f"Version: {hex(version)}")
self.vprint(f"Exported Name: {self.exported_name}")
self.vprint(f"SWF Flags: {hex(flags)}")
if flags & 0x1:
vprint(f" 0x1: Movie background color: {swf_color}")
self.vprint(f" 0x1: Movie background color: {swf_color}")
else:
vprint(" 0x2: No movie background color")
self.vprint(" 0x2: No movie background color")
if flags & 0x2:
vprint(" 0x2: FPS is an integer")
self.vprint(" 0x2: FPS is an integer")
else:
vprint(" 0x2: FPS is a float")
self.vprint(" 0x2: FPS is a float")
if flags & 0x4:
vprint(" 0x4: Imported tag initializer section present")
self.vprint(" 0x4: Imported tag initializer section present")
else:
vprint(" 0x4: Imported tag initializer section not present")
vprint(f"Dimensions: {width}x{height}")
vprint(f"Requested FPS: {fps}")
self.vprint(" 0x4: Imported tag initializer section not present")
self.vprint(f"Dimensions: {width}x{height}")
self.vprint(f"Requested FPS: {fps}")
# Exported assets
num_exported_assets = struct.unpack("<H", data[32:34])[0]
@ -962,7 +928,7 @@ class SWF(TrackedCoverage):
self.add_coverage(40, 4)
# 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):
asset_data_offset, asset_string_offset = struct.unpack("<HH", data[asset_offset:(asset_offset + 4)])
self.add_coverage(asset_offset, 4)
@ -970,12 +936,12 @@ class SWF(TrackedCoverage):
asset_name = self.__get_string(asset_string_offset)
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
tags_offset = struct.unpack("<I", data[36:40])[0]
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_count = struct.unpack("<h", data[34:36])[0]
@ -984,7 +950,7 @@ class SWF(TrackedCoverage):
self.add_coverage(34, 2)
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):
# 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)])
@ -992,7 +958,7 @@ class SWF(TrackedCoverage):
swf_name = self.__get_string(swf_name_offset)
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.
for j in range(count):
@ -1001,7 +967,7 @@ class SWF(TrackedCoverage):
asset_name = self.__get_string(asset_name_offset)
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
@ -1014,7 +980,7 @@ class SWF(TrackedCoverage):
unk1, length = struct.unpack("<HH", data[imported_tag_initializers_offset:(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):
item_offset = imported_tag_initializers_offset + 4 + (i * 12)
@ -1022,11 +988,11 @@ class SWF(TrackedCoverage):
self.add_coverage(item_offset, 12)
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)]
self.__parse_bytecode(bytecode_data, verbose=verbose)
self.__parse_bytecode(bytecode_data)
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:
self.print_coverage()

View File

@ -90,3 +90,31 @@ class TrackedCoverage:
# Print final range
offset = len(self.coverage)
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)