mirror of https://github.com/DragonMinded/firebeat
trunk
parent
9d6f753cb7
commit
61f3a570a7
16 changed files with 65 additions and 478 deletions
@ -0,0 +1,2 @@ |
||||
# Skip Dongle Check
|
||||
2C530: 7F C3 F3 78 -> 38 60 00 00
|
@ -0,0 +1,5 @@ |
||||
# Skip Dongle Check
|
||||
51D53: 01 -> 00
|
||||
51DC3: 02 -> 00
|
||||
51E17: 03 -> 00
|
||||
51E4B: 04 -> 00
|
@ -0,0 +1,7 @@ |
||||
# If you have used a donor board from a Pop'n Music or Beatmania III to do a
|
||||
# mainboard repair for Keyboard Mania, sometimes it can throw an E940 error
|
||||
# after passing all hardware checks. This seems to be an error verifying that
|
||||
# the software is running on the right type of Firebeat. This skips this check
|
||||
# and allows you to transplant parts between Firebeat boards to make a Keyboard
|
||||
# Mania main board.
|
||||
5A44: 41 82 -> 48 00
|
@ -0,0 +1,7 @@ |
||||
# If you have used a donor board from a Pop'n Music or Beatmania III to do a
|
||||
# mainboard repair for Keyboard Mania, sometimes it can throw an E940 error
|
||||
# after passing all hardware checks. This seems to be an error verifying that
|
||||
# the software is running on the right type of Firebeat. This skips this check
|
||||
# and allows you to transplant parts between Firebeat boards to make a Keyboard
|
||||
# Mania main board.
|
||||
4F40: 41 82 -> 48 00
|
@ -0,0 +1,4 @@ |
||||
# Skip Dongle Check
|
||||
B8EC: 48 00 E8 E1 -> 38 60 00 00
|
||||
B914: 48 00 E7 4D -> 38 60 00 00
|
||||
B924: 48 00 E1 C9 -> 38 60 00 00
|
@ -0,0 +1,3 @@ |
||||
# Skip Dongle Check
|
||||
CCFC: 48 01 43 F9 -> 38 60 00 00
|
||||
EFD8: 48 01 21 1D -> 38 60 00 00
|
@ -0,0 +1,8 @@ |
||||
# Skip Dongle Check
|
||||
509B6: FF FF -> 00 00
|
||||
56498: 48 03 4A 4D -> 38 60 00 00
|
||||
56520: 48 03 42 E5 -> 38 60 00 00
|
||||
78A84: 48 01 24 61 -> 38 60 00 00
|
||||
78ABC: 48 01 1D 49 -> 38 60 00 00
|
||||
78BD8: 41 82 -> 48 00
|
||||
8AF4A: FF FF -> 00 00
|
@ -0,0 +1,6 @@ |
||||
# Skip Dongle Check
|
||||
5BEB0: 48 04 20 2D -> 38 60 00 00
|
||||
5BF54: 48 04 3B 21 -> 38 60 00 00
|
||||
858E0: 48 01 85 FD -> 38 60 00 00
|
||||
859FC: 41 82 -> 48 00
|
||||
8E622: FF FF -> 00 00
|
@ -0,0 +1,7 @@ |
||||
# Skip Dongle Check on Mickey Tunes original disk.
|
||||
B924: 48 01 26 01 -> 38 60 00 00
|
||||
CD7C: 3D 60 80 10 -> 39 60 00 00
|
||||
CD80: 81 6B 02 94 -> 39 60 00 00
|
||||
DB10: 48 01 04 15 -> 38 60 00 00
|
||||
1D0FE: FF FF -> 00 00
|
||||
1E66A: FF Ff -> 00 00
|
@ -0,0 +1,6 @@ |
||||
# Skip Dongle Check on Mickey Tunes Update disk
|
||||
30524: 48 01 D0 09 -> 38 60 00 00
|
||||
31E0C: 3D 60 80 15 81 6B 6A 4C -> 39 60 00 00 39 60 00 00
|
||||
32BB0: 48 01 A9 7D -> 38 60 00 00
|
||||
43A76: FF FF -> 00 00
|
||||
4DC72: FF FF -> 00 00
|
@ -0,0 +1 @@ |
||||
git+https://github.com/DragonMinded/arcadeutils.git@main#egg=arcadeutils |
@ -1,113 +0,0 @@ |
||||
#! /usr/bin/env python3 |
||||
import argparse |
||||
import os |
||||
import sys |
||||
|
||||
from binary import Binary |
||||
|
||||
|
||||
def main() -> int: |
||||
# Create the argument parser |
||||
parser = argparse.ArgumentParser( |
||||
description="Utilities for diffing or patching binary files.", |
||||
) |
||||
subparsers = parser.add_subparsers(help='commands', dest='command') |
||||
|
||||
# Parser for diffing two binary files |
||||
diff_parser = subparsers.add_parser('diff', help='Diff two same-length binary files.') |
||||
diff_parser.add_argument( |
||||
'file1', |
||||
metavar='FILE1', |
||||
type=str, |
||||
help='The base file that we will output diffs relative to.', |
||||
) |
||||
diff_parser.add_argument( |
||||
'file2', |
||||
metavar='FILE2', |
||||
type=str, |
||||
help='The file that we will compare against the base file to find diffs.', |
||||
) |
||||
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 a binary file.') |
||||
patch_parser.add_argument( |
||||
'bin', |
||||
metavar='BIN', |
||||
type=str, |
||||
help='The binary file we should patch.', |
||||
) |
||||
patch_parser.add_argument( |
||||
'out', |
||||
metavar='OUT', |
||||
type=str, |
||||
help='The file we should write the patched binary to.', |
||||
) |
||||
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 == 'diff': |
||||
with open(args.file1, "rb") as fp: |
||||
file1 = fp.read() |
||||
with open(args.file2, "rb") as fp: |
||||
file2 = fp.read() |
||||
|
||||
try: |
||||
differences = Binary.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.bin, "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: |
||||
new = Binary.patch(old, differences, reverse=args.reverse) |
||||
except Exception as e: |
||||
print(f"Could not patch {args.bin}: {str(e)}", file=sys.stderr) |
||||
return 1 |
||||
|
||||
with open(args.out, "wb") as fp: |
||||
fp.write(new) |
||||
|
||||
print(f"Patched {args.bin} 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()) |
@ -1,263 +0,0 @@ |
||||
from typing import List, Optional, Tuple, cast |
||||
from typing_extensions import Final |
||||
|
||||
|
||||
class BinaryException(Exception): |
||||
pass |
||||
|
||||
|
||||
class Binary: |
||||
|
||||
CHUNK_SIZE: Final[int] = 1024 |
||||
|
||||
@staticmethod |
||||
def _hex(val: int) -> str: |
||||
out = hex(val)[2:] |
||||
out = out.upper() |
||||
if len(out) == 1: |
||||
out = "0" + out |
||||
return out |
||||
|
||||
@staticmethod |
||||
def diff(bin1: bytes, bin2: bytes) -> List[str]: |
||||
binlength = len(bin1) |
||||
if binlength != len(bin2): |
||||
raise BinaryException("Cannot diff different-sized binary blobs!") |
||||
|
||||
# First, get the list of differences |
||||
differences: List[Tuple[int, bytes, bytes]] = [] |
||||
|
||||
# Chunk the differences, assuming files are usually about the same, |
||||
# for a massive speed boost. |
||||
for offset in range(0, binlength, Binary.CHUNK_SIZE): |
||||
if bin1[offset:(offset + Binary.CHUNK_SIZE)] != bin2[offset:(offset + Binary.CHUNK_SIZE)]: |
||||
for i in range(Binary.CHUNK_SIZE): |
||||
byte1 = bin1[offset + i] |
||||
byte2 = bin2[offset + i] |
||||
|
||||
if byte1 != byte2: |
||||
differences.append((offset + i, bytes([byte1]), bytes([byte2]))) |
||||
|
||||
# Don't bother with any combination crap if we have nothing to do |
||||
if not differences: |
||||
return [] |
||||
|
||||
# Now, combine them for easier printing |
||||
cur_block: Tuple[int, bytes, bytes] = differences[0] |
||||
ret: List[str] = [] |
||||
|
||||
# Now, include the original byte size for later comparison/checks |
||||
ret.append(f"# File size: {len(bin1)}") |
||||
|
||||
def _hexrun(val: bytes) -> str: |
||||
return " ".join(Binary._hex(v) for v in val) |
||||
|
||||
def _output(val: Tuple[int, bytes, bytes]) -> None: |
||||
start = val[0] - len(val[1]) + 1 |
||||
|
||||
ret.append( |
||||
f"{Binary._hex(start)}: {_hexrun(val[1])} -> {_hexrun(val[2])}" |
||||
) |
||||
|
||||
def _combine(val: Tuple[int, bytes, bytes]) -> None: |
||||
nonlocal cur_block |
||||
|
||||
if cur_block[0] + 1 == val[0]: |
||||
# This is a continuation of a run |
||||
cur_block = ( |
||||
val[0], |
||||
cur_block[1] + val[1], |
||||
cur_block[2] + val[2], |
||||
) |
||||
else: |
||||
# This is a new run |
||||
_output(cur_block) |
||||
cur_block = val |
||||
|
||||
# Combine and output runs of differences |
||||
for diff in differences[1:]: |
||||
_combine(diff) |
||||
|
||||
# Make sure we output the last difference |
||||
_output(cur_block) |
||||
|
||||
# Return our summation |
||||
return ret |
||||
|
||||
@staticmethod |
||||
def size(patchlines: List[str]) -> Optional[int]: |
||||
for patch in patchlines: |
||||
if patch.startswith('#'): |
||||
# This is a comment, ignore it, unless its a file-size comment |
||||
patch = patch[1:].strip().lower() |
||||
if patch.startswith('file size:'): |
||||
return int(patch[10:].strip()) |
||||
return None |
||||
|
||||
@staticmethod |
||||
def _convert(val: str) -> Optional[int]: |
||||
val = val.strip() |
||||
if val == '*': |
||||
return None |
||||
return int(val, 16) |
||||
|
||||
@staticmethod |
||||
def _gather_differences(patchlines: List[str], reverse: bool) -> List[Tuple[int, Optional[bytes], bytes]]: |
||||
# First, separate out into a list of offsets and old/new bytes |
||||
differences: List[Tuple[int, Optional[bytes], bytes]] = [] |
||||
|
||||
for patch in patchlines: |
||||
if patch.startswith('#'): |
||||
# This is a comment, ignore it. |
||||
continue |
||||
start_offset, patch_contents = patch.split(':', 1) |
||||
before, after = patch_contents.split('->') |
||||
beforevals = [ |
||||
Binary._convert(x) for x in before.split(" ") if x.strip() |
||||
] |
||||
aftervals = [ |
||||
Binary._convert(x) for x in after.split(" ") if x.strip() |
||||
] |
||||
|
||||
if len(beforevals) != len(aftervals): |
||||
raise BinaryException( |
||||
f"Patch before and after length mismatch at " |
||||
f"offset {start_offset}!" |
||||
) |
||||
if len(beforevals) == 0: |
||||
raise BinaryException( |
||||
f"Must have at least one byte to change at " |
||||
f"offset {start_offset}!" |
||||
) |
||||
|
||||
offset = int(start_offset.strip(), 16) |
||||
|
||||
for i in range(len(beforevals)): |
||||
if aftervals[i] is None: |
||||
raise BinaryException( |
||||
f"Cannot convert a location to a wildcard " |
||||
f"at offset {start_offset}" |
||||
) |
||||
if beforevals[i] is None and reverse: |
||||
raise BinaryException( |
||||
f"Patch offset {start_offset} specifies a wildcard and cannot " |
||||
f"be reversed!" |
||||
) |
||||
differences.append( |
||||
( |
||||
offset + i, |
||||
bytes([beforevals[i] or 0]) if beforevals[i] is not None else None, |
||||
bytes([aftervals[i] or 0]), |
||||
) |
||||
) |
||||
|
||||
# Now, if we're doing the reverse, just switch them |
||||
if reverse: |
||||
# We cast here because mypy can't see that we have already asserted that x[2] will never |
||||
# be optional in the above loop if reverse is set to True. |
||||
differences = [cast(Tuple[int, Optional[bytes], bytes], (x[0], x[2], x[1])) for x in differences] |
||||
|
||||
# Finally, return it |
||||
return differences |
||||
|
||||
@staticmethod |
||||
def patch( |
||||
binary: bytes, |
||||
patchlines: List[str], |
||||
*, |
||||
reverse: bool = False, |
||||
) -> bytes: |
||||
# First, grab the differences |
||||
file_size = Binary.size(patchlines) |
||||
if file_size is not None and file_size != len(binary): |
||||
raise BinaryException( |
||||
f"Patch is for binary of size {file_size} but binary is {len(binary)} " |
||||
f"bytes long!" |
||||
) |
||||
differences: List[Tuple[int, Optional[bytes], bytes]] = sorted( |
||||
Binary._gather_differences(patchlines, reverse), |
||||
key=lambda diff: diff[0], |
||||
) |
||||
chunks: List[bytes] = [] |
||||
last_patch_end: int = 0 |
||||
|
||||
# Now, apply the changes to the binary data |
||||
for diff in differences: |
||||
offset, old, new = diff |
||||
|
||||
if len(binary) < offset: |
||||
raise BinaryException( |
||||
f"Patch offset {Binary._hex(offset)} is beyond the end of " |
||||
f"the binary!" |
||||
) |
||||
if old is not None and binary[offset:(offset + 1)] != old: |
||||
raise BinaryException( |
||||
f"Patch offset {Binary._hex(offset)} expecting {Binary._hex(old[0])} " |
||||
f"but found {Binary._hex(binary[offset])}!" |
||||
) |
||||
|
||||
if last_patch_end < offset: |
||||
chunks.append(binary[last_patch_end:offset]) |
||||
chunks.append(new) |
||||
last_patch_end = offset + 1 |
||||
|
||||
# Return the new data! |
||||
chunks.append(binary[last_patch_end:]) |
||||
return b"".join(chunks) |
||||
|
||||
@staticmethod |
||||
def can_patch( |
||||
binary: bytes, |
||||
patchlines: List[str], |
||||
*, |
||||
reverse: bool = False, |
||||
ignore_size_differences: bool = False, |
||||
) -> Tuple[bool, str]: |
||||
# First, grab the differences |
||||
if not ignore_size_differences: |
||||
file_size = Binary.size(patchlines) |
||||
if file_size is not None and file_size != len(binary): |
||||
return ( |
||||
False, |
||||
f"Patch is for binary of size {file_size} but binary is {len(binary)} " |
||||
f"bytes long!" |
||||
) |
||||
differences: List[Tuple[int, Optional[bytes], bytes]] = Binary._gather_differences(patchlines, reverse) |
||||
|
||||
# Now, verify the changes to the binary data |
||||
for diff in differences: |
||||
offset, old, _ = diff |
||||
|
||||
if len(binary) < offset: |
||||
return ( |
||||
False, |
||||
f"Patch offset {Binary._hex(offset)} is beyond the end of " |
||||
f"the binary!" |
||||
) |
||||
if old is not None and binary[offset:(offset + 1)] != old: |
||||
return ( |
||||
False, |
||||
f"Patch offset {Binary._hex(offset)} expecting {Binary._hex(old[0])} " |
||||
f"but found {Binary._hex(binary[offset])}!" |
||||
) |
||||
|
||||
# Didn't find any problems |
||||
return (True, "") |
||||
|
||||
@staticmethod |
||||
def description(patchlines: List[str]) -> Optional[str]: |
||||
for patch in patchlines: |
||||
if patch.startswith('#'): |
||||
# This is a comment, ignore it, unless its a description comment |
||||
patch = patch[1:].strip().lower() |
||||
if patch.startswith('description:'): |
||||
return patch[12:].strip() |
||||
return None |
||||
|
||||
@staticmethod |
||||
def needed_amount(patchlines: List[str]) -> int: |
||||
# First, grab the differences. |
||||
differences: List[Tuple[int, Optional[bytes], bytes]] = Binary._gather_differences(patchlines, False) |
||||
|
||||
# Now, get the maximum byte we need to apply this patch. |
||||
return max([offset for offset, _, _ in differences]) + 1 if differences else 0 |
Loading…
Reference in new issue