From 522f8eaa292f3e8bc0f8f4960802969b29e804e5 Mon Sep 17 00:00:00 2001 From: Jennifer Taylor Date: Sun, 11 Apr 2021 20:45:17 +0000 Subject: [PATCH] Refactor verbose debugging to its own class, clean up classes that used it. --- bemani/format/afp/container.py | 139 +++++++--------- bemani/format/afp/swf.py | 282 +++++++++++++++------------------ bemani/format/afp/util.py | 28 ++++ 3 files changed, 212 insertions(+), 237 deletions(-) diff --git a/bemani/format/afp/container.py b/bemani/format/afp/container.py index 9e1b308..5421ce6 100644 --- a/bemani/format/afp/container.py +++ b/bemani/format/afp/container.py @@ -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! diff --git a/bemani/format/afp/swf.py b/bemani/format/afp/swf.py index 10f24a0..981a2be 100644 --- a/bemani/format/afp/swf.py +++ b/bemani/format/afp/swf.py @@ -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[(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("> 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("> 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( " 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("> 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(" 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(" 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)