diff --git a/bemani/utils/afputils.py b/bemani/utils/afputils.py index 0500100..acce1a1 100644 --- a/bemani/utils/afputils.py +++ b/bemani/utils/afputils.py @@ -49,7 +49,7 @@ def write_bytecode(swf: SWF, directory: str, *, verbose: bool) -> None: if lut: buff = [ os.linesep.join([ - '// Defined frame labels from SWF container, as used for frame lookups.', + '// Defined frame labels from animation container, as used for frame lookups.', 'FRAME_LUT = {', *[f" {name!r}: {frame}," for name, frame in lut.items()], '};', @@ -178,13 +178,13 @@ def extract_txp2( filename = os.path.join(output_dir, name) if pretend: - print(f"Would write {filename}.afp SWF data...") - print(f"Would write {filename}.bsi SWF descramble data...") + print(f"Would write {filename}.afp animation data...") + print(f"Would write {filename}.bsi animation descramble data...") else: - print(f"Writing {filename}.afp SWF data...") + print(f"Writing {filename}.afp animation data...") with open(f"{filename}.afp", "wb") as bfp: bfp.write(swf.data) - print(f"Writing {filename}.bsi SWF descramble data...") + print(f"Writing {filename}.bsi animation descramble data...") with open(f"{filename}.bsi", "wb") as bfp: bfp.write(swf.descramble_info) @@ -406,7 +406,7 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras renderer.add_shape(name, shape) if verbose: - print(f"Added {name} to SWF shape library.", file=sys.stderr) + print(f"Added {name} to animation shape library.", file=sys.stderr) # Now, split and load textures into the renderer. sheets: Dict[str, Any] = {} @@ -432,17 +432,17 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras renderer.add_texture(name, sprite) if verbose: - print(f"Added {name} to SWF texture library.", file=sys.stderr) + print(f"Added {name} to animation texture library.", file=sys.stderr) else: print(f"Cannot load {name} from {texturename} because it is not a supported format!") - # Finally, load the SWF data itself into the renderer. + # Finally, load the animation data itself into the renderer. for i, name in enumerate(afpfile.swfmap.entries): swf = afpfile.swfdata[i] renderer.add_swf(name, swf) if verbose: - print(f"Added {name} to SWF library.", file=sys.stderr) + print(f"Added {name} to animation library.", file=sys.stderr) continue @@ -469,7 +469,7 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras renderer.add_shape(shapename, shape) if verbose: - print(f"Added {shapename} to SWF shape library.", file=sys.stderr) + print(f"Added {shapename} to animation shape library.", file=sys.stderr) elif fname.startswith(f"tex{os.sep}") and fname.endswith(".png"): if not need_extras: continue @@ -483,7 +483,7 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras renderer.add_texture(texname, tex) if verbose: - print(f"Added {texname} to SWF texture library.", file=sys.stderr) + print(f"Added {texname} to animation texture library.", file=sys.stderr) elif fname.startswith(f"afp{os.sep}"): # Trim off directory, see if it has a corresponding bsi. afpname = fname[(3 + len(os.sep)):] @@ -496,7 +496,7 @@ def load_containers(renderer: AFPRenderer, containers: List[str], *, need_extras renderer.add_swf(afpname, flash) if verbose: - print(f"Added {afpname} to SWF library.", file=sys.stderr) + print(f"Added {afpname} to animation library.", file=sys.stderr) continue @@ -678,7 +678,7 @@ def render_path( else: background = None - # Calculate the size of the SWF so we can apply scaling options. + # Calculate the size of the animation so we can apply scaling options. swf_location = renderer.compute_path_location(path) requested_width = force_width if force_width is not None else swf_location.width requested_height = force_height if force_height is not None else swf_location.height @@ -811,113 +811,129 @@ def render_path( def main() -> int: - parser = argparse.ArgumentParser(description="Konami AFP graphic file unpacker/repacker") + parser = argparse.ArgumentParser(description="Konami AFP graphic file unpacker/repacker.") subparsers = parser.add_subparsers(help='Action to take', dest='action') - extract_parser = subparsers.add_parser('extract', help='Extract relevant file data and textures from a TXP2 container') + extract_parser = subparsers.add_parser( + 'extract', + help='Extract relevant file data and textures from a TXP2 container', + description="Extract textures, sprites, decompiled bytecode, AFP, BSI and GEO files from a TXP2 container.", + ) extract_parser.add_argument( "file", metavar="FILE", - help="The file to extract", + help="The TXP2 container to extract", ) extract_parser.add_argument( "dir", metavar="DIR", - help="Directory to extract to", + help="The directory to extract all contents to", ) extract_parser.add_argument( "-p", "--pretend", action="store_true", - help="Pretend to extract instead of extracting", + help="Pretend to extract instead of extracting, printing what would have been extracted", ) extract_parser.add_argument( "-v", "--verbose", action="store_true", - help="Display verbuse debugging output", + help="Display verbose debugging output", ) extract_parser.add_argument( "-r", "--write-raw", action="store_true", - help="Always write raw texture files", + help="Always write raw texture data instead of only writing raw texture data for unrecognized texture formats", ) extract_parser.add_argument( "-m", "--write-mappings", action="store_true", - help="Write mapping files to disk", + help="Write mapping files to disk as XML files", ) extract_parser.add_argument( "-g", "--generate-mapping-overlays", action="store_true", - help="Generate overlay images showing mappings", + help="Generate overlay images showing mappings between textures and individual sprites", ) extract_parser.add_argument( "-s", "--split-textures", action="store_true", - help="Split textures into individual sprites", + help="Split textures into individual sprite image files", ) extract_parser.add_argument( "-b", "--write-binaries", action="store_true", - help="Write binary SWF files to disk", + help="Write raw AFP/BSI/GEO files to disk", ) extract_parser.add_argument( "-y", "--write-bytecode", action="store_true", - help="Write decompiled bytecode files to disk", + help="Write decompiled bytecode files found in AFP files to disk", ) - update_parser = subparsers.add_parser('update', help='Update relevant textures in a TXP2 container from a directory') + update_parser = subparsers.add_parser( + 'update', + help='Update relevant textures in a TXP2 container from a directory', + description="Update textures and sprites in a TXP2 container based on images in a directory.", + ) update_parser.add_argument( "file", metavar="FILE", - help="The file to update", + help="The TXP2 container to update", ) update_parser.add_argument( "dir", metavar="DIR", - help="Directory to update from", + help="Directory to update all contents from", ) update_parser.add_argument( "-p", "--pretend", action="store_true", - help="Pretend to update instead of updating", + help="Pretend to update instead of updating, printing what would have been updated", ) update_parser.add_argument( "-v", "--verbose", action="store_true", - help="Display verbuse debugging output", + help="Display verbose debugging output", ) - print_parser = subparsers.add_parser('print', help='Print the TXP2 container contents as a JSON dictionary') + print_parser = subparsers.add_parser( + 'print', + help='Print a TXP2 container\'s contents as a JSON dictionary', + description='Print a TXP2 container\'s contents as a JSON dictionary.', + ) print_parser.add_argument( "file", metavar="FILE", - help="The file to print", - ) - print_parser.add_argument( - "-d", - "--decompile-bytecode", - action="store_true", - help="Attempt to decompile and print bytecode instead of printing the raw representation.", + help="The TXP2 container to print", ) print_parser.add_argument( "-v", "--verbose", action="store_true", - help="Display verbuse debugging output", + help="Display verbose debugging output", + ) + print_parser.add_argument( + "-d", + "--decompile-bytecode", + action="store_true", + help="Attempt to decompile and print pseudocode instead of printing the raw bytecode.", ) - parseafp_parser = subparsers.add_parser('parseafp', help='Parse a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container') + parseafp_parser = subparsers.add_parser( + 'parseafp', + help='Parse a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container', + description='Parse a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container.', + ) parseafp_parser.add_argument( "afp", metavar="AFPFILE", @@ -928,20 +944,24 @@ def main() -> int: metavar="BSIFILE", help="The BSI file to parse", ) - parseafp_parser.add_argument( - "-d", - "--decompile-bytecode", - action="store_true", - help="Attempt to decompile and print bytecode instead of printing the raw representation.", - ) parseafp_parser.add_argument( "-v", "--verbose", action="store_true", - help="Display verbuse debugging output", + help="Display verbose debugging output", + ) + parseafp_parser.add_argument( + "-d", + "--decompile-bytecode", + action="store_true", + help="Attempt to decompile and print pseudocode instead of printing the raw bytecode.", ) - decompile_parser = subparsers.add_parser('decompile', help='Decompile bytecode in a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container') + decompile_parser = subparsers.add_parser( + 'decompile', + help='Decompile bytecode in a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container', + description='Decompile bytecode in a raw AFP/BSI file pair previously extracted from an IFS or TXP2 container.', + ) decompile_parser.add_argument( "afp", metavar="AFPFILE", @@ -952,22 +972,26 @@ def main() -> int: metavar="BSIFILE", help="The BSI file to parse", ) + decompile_parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Display verbose debugging output", + ) decompile_parser.add_argument( "-d", "--directory", metavar="DIR", default='.', type=str, - help="Directory to extract to after decompiling. Defaults to current directory.", - ) - decompile_parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="Display verbuse debugging output", + help="Directory to write decompiled pseudocode files. Defaults to current directory.", ) - parsegeo_parser = subparsers.add_parser('parsegeo', help='Parse a raw GEO file previously extracted from an IFS or TXP2 container') + parsegeo_parser = subparsers.add_parser( + 'parsegeo', + help='Parse a raw GEO file previously extracted from an IFS or TXP2 container', + description='Parse a raw GEO file previously extracted from an IFS or TXP2 container.', + ) parsegeo_parser.add_argument( "geo", metavar="GEOFILE", @@ -977,23 +1001,44 @@ def main() -> int: "-v", "--verbose", action="store_true", - help="Display verbuse debugging output", + help="Display verbose debugging output", ) - render_parser = subparsers.add_parser('render', help='Render a particular animation out of a series of SWFs') + render_parser = subparsers.add_parser( + 'render', + help='Render a particular animation out of a collection of TXP2 or IFS containers', + description='Render a particular animation out of a collection of TXP2 or IFS containers.', + ) render_parser.add_argument( "container", metavar="CONTAINER", type=str, nargs='+', - help="A container file to use for loading SWF data. Can be either a TXP2 or IFS container.", + help="A container to use for loading animation data. Can be either a TXP2 or IFS container.", + ) + render_parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Display verbose debugging output", + ) + render_parser.add_argument( + "-s", + "--show-progress", + action="store_true", + help="Display per-frame rendering progress", + ) + render_parser.add_argument( + "--disable-threads", + action="store_true", + help="Disable multi-threaded rendering. The animation will be rendered on a single core and threads will not be spawned.", ) render_parser.add_argument( "--path", metavar="PATH", type=str, required=True, - help='A path to render, specified as "moviename" of the animation to render. Use the "list" command to discover paths in a file.', + help='The path of the animation to render. Use the "list" command to discover paths in a container.', ) render_parser.add_argument( "--output", @@ -1002,130 +1047,157 @@ def main() -> int: default="out.gif", help=( 'The output file (ending either in .gif, .webp or .png) where the render should be saved. If .png is chosen then the ' - 'output will be a series of png files for each rendered frame.' + 'output will be a series of png files for each rendered frame. If .gif or .webp is chosen the output will be an ' + 'animated image.' ), ) render_parser.add_argument( "--background-color", type=str, default=None, - help="Set the background color of the animation as a comma-separated RGB or RGBA color, overriding a default if present in the SWF.", + help=( + "Set the background color of the animation as a comma-separated RGB or RGBA color (such as 255,0,0 for red or " + "0,255,0,128 for translucent green), overriding any default in the animation." + ), ) render_parser.add_argument( "--background-image", type=str, default=None, help=( - "Set a background image or animation to be placed behind the animation. Note that the background will be stretched to fit " - "the animation. If a .png is specified and multiple rendered frames are present, it will use that series as an animation." + "Set a background image or animation to be placed behind the animation but in front of the background color. " + "Note that the background will be stretched to fit the animation. If a .png is specified and multiple rendered " + "frames are present it will use that series as an animation. If a static image is specified and mulitple frames " + "are not present it will use that as a static background. If an animated image is specified it will use that " + "as an animated background." ), ) render_parser.add_argument( "--background-loop-start", type=int, default=None, - help="The starting frame of the background animation loop. Specify this to loop to a background animation frame other than the first.", + help=( + "The starting frame of the background animation loop. Specify this to loop to a background animation frame other " + "than the first. For example, if your background animation has 10 frames and you specify a loop start of 6, the " + "resulting background animation loop will contain frames 6, 7, 8, 9 and 10 played in that order." + ), ) render_parser.add_argument( "--background-loop-end", type=int, default=None, - help="The ending frame of the background animation loop. Specify this to loop from a background animation frame other than the last.", + help=( + "The ending frame of the background animation loop. Specify this to loop from a background animation frame other " + "than the last. For example, if your background animation has 10 frames and you specify a loop end of 8, the " + "resulting background animation loop will contain frames 1, 2, 3, 4, 5, 6, 7 and 8 played in that order." + ), ) render_parser.add_argument( "--background-loop-offset", type=int, default=None, - help="The very first frame of the background animation. Specify this to start the first loop anywhere other than the loop start frame.", + help=( + "The very first frame of the background animation. Specify this to start the first loop anywhere other than the " + "loop start frame. For example, if your background animation has 10 frames and you specify a loop offset of 7, the " + "resulting background animation loop will contain frames 7, 8, 9 10, 1, 2, 3, 4, 5 and 6 played in that order. Note " + "that this can work in conjunction with the --background-loop-start and --background-loop-end parameters. For " + "example, if your background animation has 10 frames and you specify a loop start of 3, a loop end of 7 and a loop " + "offset of 5, the resulting background animation loop will contain frames 5, 6, 7, 3 and 4 played in that order." + ), ) render_parser.add_argument( "--only-depths", type=str, default=None, - help="Only render objects on these depth planes. Can provide either a number or a comma-separated list of numbers, or a range such as 3-5.", + help="Only render objects on these depth planes. Specify a number, a comma-separated list of numbers or a range such as 3-5.", ) render_parser.add_argument( "--only-frames", type=str, default=None, - help="Only render these frames. Can provide either a number or a comma-separated list of numbers, or a range such as 10-20.", + help=( + "Only render these frames. Specify a number, a comma-separated list of numbers or a range such as 10-20. Note that the " + "first frame is frame 1, not frame 0." + ), ) render_parser.add_argument( "--force-width", type=int, default=None, - help="Force the width of the rendered image to a specific pixel value, such as 640 or 800.", + help=( + "Force the width of the rendered animation to a specific pixel value, such as 640 or 800. If the forced width does not match " + "the animation's original width it will be stretched horizontally." + ), ) render_parser.add_argument( "--force-height", type=int, default=None, - help="Force the height of the rendered image to a specific pixel value, such as 480 or 600.", + help=( + "Force the height of the rendered animation to a specific pixel value, such as 480 or 600. If the forced height does not match " + "the animation's original height it will be stretched vertically." + ), ) render_parser.add_argument( "--force-aspect-ratio", type=str, default=None, - help="Force the aspect ratio of the rendered image, as a colon-separated aspect ratio such as 16:9 or 4:3, after applying any forced width and height.", + help=( + "Force the aspect ratio of the rendered animation, as a colon-separated aspect ratio such as 16:9 or 4:3, after applying " + "any forced width and height. The resulting animation will be stretched to meet the requested aspect ratio." + ), ) render_parser.add_argument( "--scale-width", type=float, default=1.0, - help="Scale the final width of the animation by some factor, such as 2.0 or 0.5, after applying any forced width, height and aspect ratio.", + help=( + "Scale the final width of the animation by some factor, such as 2.0 or 0.5, after applying any forced width, height and " + "aspect ratio. The resulting animation will be stretched horizontally by the scaling factor." + ), ) render_parser.add_argument( "--scale-height", type=float, default=1.0, - help="Scale the final height of the animation by some factor, such as 2.0 or 0.5, after applying any forced width, height and aspect ratio.", - ) - render_parser.add_argument( - "--disable-threads", - action="store_true", - help="Disable multi-threaded rendering.", + help=( + "Scale the final height of the animation by some factor, such as 2.0 or 0.5, after applying any forced width, height and " + "aspect ratio. The resulting animation will be stretched vertically by the scaling factor." + ), ) render_parser.add_argument( "--enable-anti-aliasing", action="store_true", - help="Enable anti-aliased rendering, using bilinear interpolation and super-sampling depending on the circumstance.", - ) - render_parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="Display verbuse debugging output", - ) - render_parser.add_argument( - "-s", - "--show-progress", - action="store_true", - help="Display per-frame rendering progress", + help="Enable anti-aliased rendering, using bilinear interpolation and super-sampling where appropriate to produce the best resulting animation.", ) - list_parser = subparsers.add_parser('list', help='List out the possible paths to render from a series of SWFs') + list_parser = subparsers.add_parser( + 'list', + help='List out the possible paths to render from a collection of TXP2 or IFS containers', + description='List out the possible paths to render from a collection of TXP2 or IFS containers.', + ) list_parser.add_argument( "container", metavar="CONTAINER", type=str, nargs='+', - help="A container file to use for loading SWF data. Can be either a TXP2 or IFS container.", - ) - list_parser.add_argument( - "--include-frames", - action="store_true", - help="Include number of frames per path in the output list.", - ) - list_parser.add_argument( - "--include-size", - action="store_true", - help="Include width/height of the path in the output list.", + help="A container to use for loading animation data. Can be either a TXP2 or IFS container.", ) list_parser.add_argument( "-v", "--verbose", action="store_true", - help="Display verbuse debugging output", + help="Display verbose debugging output", + ) + list_parser.add_argument( + "--include-frames", + action="store_true", + help="Include number of frames per animation in the output list.", + ) + list_parser.add_argument( + "--include-size", + action="store_true", + help="Include width/height per animation in the output list.", ) args = parser.parse_args()