197 lines
6.1 KiB
Python
Executable File
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())
|