firebeat/utils/exe_utils

197 lines
6.1 KiB
Python
Executable File

#! /usr/bin/env python3
import argparse
import os
import sys
from arcadeutils.binary import BinaryDiff
from firebeat import FirebeatExe
def main() -> int:
# Create the argument parser
parser = argparse.ArgumentParser(
description="Utilities for working with a Firebeat EXE file.",
)
subparsers = parser.add_subparsers(help='commands', dest='command')
# Parser for executable unpacking
unpack_parser = subparsers.add_parser('unpack', help='Unpack a Firebeat EXE to a raw PPC binary file.')
unpack_parser.add_argument(
'exe',
metavar='EXE',
type=str,
help='The EXE file (FIREBEAT.EXE or HIKARU.EXE) that we should unpack.',
)
unpack_parser.add_argument(
'bin',
metavar='BIN',
type=str,
help='The raw binary file we should write the unpacked PPC binary to.',
)
unpack_parser.add_argument(
'--ppp',
action='store_true',
help='The EXE file we are unpacking is from the ParaParaParadise series.',
)
# Parser for executable packing
pack_parser = subparsers.add_parser('pack', help='Pack a raw PPC binary file into a Firebeat EXE.')
pack_parser.add_argument(
'bin',
metavar='BIN',
type=str,
help='The raw binary file we should pack into a Firebeat EXE.',
)
pack_parser.add_argument(
'exe',
metavar='EXE',
type=str,
help='The EXE file (FIREBEAT.EXE or HIKARU.EXE) that we should pack the binary into.',
)
pack_parser.add_argument(
'--ppp',
action='store_true',
help='The raw binary file we are packing is from the ParaParaParadise series.',
)
# Parser for diffing two binary files
diff_parser = subparsers.add_parser('diff', help='Diff contents of two Firebeat EXE files.')
diff_parser.add_argument(
'file1',
metavar='FILE1',
type=str,
help='The base Firebeat EXE file that we will unpack and output diffs relative to.',
)
diff_parser.add_argument(
'file2',
metavar='FILE2',
type=str,
help='The Firebeta EXE file that we will unpack and compare against the base file to find diffs.',
)
diff_parser.add_argument(
'--ppp',
action='store_true',
help='The EXE files we are diffing are from the ParaParaParadise series.',
)
diff_parser.add_argument(
'--patch-file',
metavar='FILE',
type=str,
help='Write patches to a file instead of stdout.',
)
# Parser for patching a binary file
patch_parser = subparsers.add_parser('patch', help='Patch the contents of a Firebeat EXE file.')
patch_parser.add_argument(
'exe',
metavar='EXE',
type=str,
help='The EXE file whose contents we should unpack and patch.',
)
patch_parser.add_argument(
'out',
metavar='OUT',
type=str,
help='The Firebeat EXE file we should write the patched and repacked binary to.',
)
patch_parser.add_argument(
'--ppp',
action='store_true',
help='The EXE file we are patching is from the ParaParaParadise series.',
)
patch_parser.add_argument(
'--patch-file',
metavar='FILE',
type=str,
help='Read patches from a file instead of stdin.',
)
patch_parser.add_argument(
'--reverse',
action="store_true",
help='Perform the patch in reverse.',
)
# Grab what we're doing
args = parser.parse_args()
if args.command == 'pack':
with open(args.bin, "rb") as fp:
data = fp.read()
try:
packed = FirebeatExe.raw_to_exe(data, is_ppp=args.ppp)
except Exception as e:
print(f"Failed to pack {args.bin}: {str(e)}", file=sys.stderr)
return 1
with open(args.exe, "wb") as fp:
fp.write(packed)
print(f"Packed {args.bin} and wrote to {args.exe}.")
elif args.command == 'unpack':
with open(args.exe, "rb") as fp:
data = fp.read()
try:
unpacked = FirebeatExe.exe_to_raw(data, is_ppp=args.ppp)
except Exception as e:
print(f"Failed to unpack {args.exe}: {str(e)}", file=sys.stderr)
return 1
with open(args.bin, "wb") as fp:
fp.write(unpacked)
print(f"Unpacked {args.exe} and wrote to {args.bin}.")
elif args.command == 'diff':
with open(args.file1, "rb") as fp:
file1 = fp.read()
with open(args.file2, "rb") as fp:
file2 = fp.read()
try:
file1 = FirebeatExe.exe_to_raw(file1, is_ppp=args.ppp)
file2 = FirebeatExe.exe_to_raw(file2, is_ppp=args.ppp)
differences = BinaryDiff.diff(file1, file2)
except Exception as e:
print(f"Could not diff {args.file1} against {args.file2}: {str(e)}", file=sys.stderr)
return 1
if not args.patch_file:
for line in differences:
print(line)
else:
with open(args.patch_file, "w") as fp:
fp.write(os.linesep.join(differences))
elif args.command == 'patch':
with open(args.exe, "rb") as fp:
old = fp.read()
if not args.patch_file:
differences = sys.stdin.readlines()
else:
with open(args.patch_file, "r") as fp:
differences = fp.readlines()
differences = [d.strip() for d in differences if d.strip()]
try:
old = FirebeatExe.exe_to_raw(old, is_ppp=args.ppp)
new = BinaryDiff.patch(old, differences, reverse=args.reverse)
new = FirebeatExe.raw_to_exe(new, is_ppp=args.ppp)
except Exception as e:
print(f"Could not patch {args.exe}: {str(e)}", file=sys.stderr)
return 1
with open(args.out, "wb") as fp:
fp.write(new)
print(f"Patched {args.exe} and wrote to {args.out}.")
else:
print("Please specify a valid command!", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())