mirror of
https://codeberg.org/Nixietab/EOSSDK-Holos.git
synced 2026-08-21 20:23:28 -04:00
132 lines
4.8 KiB
Python
132 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import os, sys, struct, argparse
|
|
from Crypto.Cipher import AES
|
|
|
|
KEY_DEFAULT = b"rK7CcATuZk7LAmhqqU4iBLNmAq8QbK3s"
|
|
KEY_ALTERNATE = b"x4DZmD6D2HhkzT6qD8HpKeZdgM9HCmXP"
|
|
|
|
|
|
def xor_crypt(data, key):
|
|
cipher = AES.new(key, AES.MODE_ECB)
|
|
out = bytearray(len(data))
|
|
for off in range(0, len(data), 16):
|
|
ctr = off // 16 + 1
|
|
ks = cipher.encrypt(struct.pack('<q', ctr) + b'\x00' * 8)
|
|
for j, b in enumerate(data[off:off+16]):
|
|
out[off+j] = b ^ ks[j]
|
|
return bytes(out)
|
|
|
|
|
|
def process_file(inpath, outpath, key, dry_run=False):
|
|
try:
|
|
with open(inpath, 'rb') as f:
|
|
data = f.read()
|
|
if data[:7] != b'UnityFS':
|
|
print(f" Warning: '{os.path.basename(inpath)}' does not start with UnityFS magic")
|
|
if dry_run:
|
|
print(f" Would encrypt: {os.path.basename(inpath)} -> {os.path.basename(outpath)}")
|
|
return True
|
|
encrypted = xor_crypt(data, key)
|
|
os.makedirs(os.path.dirname(outpath) or '.', exist_ok=True)
|
|
with open(outpath, 'wb') as f:
|
|
f.write(encrypted)
|
|
print(f" {os.path.basename(inpath)} -> {os.path.basename(outpath)} ({len(data)} bytes)")
|
|
return True
|
|
except Exception as e:
|
|
print(f" ERROR {os.path.basename(inpath)}: {e}", file=sys.stderr)
|
|
return False
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Re-encrypt decrypted Unity bundles for Holo's Hanafuda")
|
|
parser.add_argument('input', help='Input .unity3d file or directory of .unity3d files')
|
|
parser.add_argument('-o', '--output', help='Output file or directory')
|
|
parser.add_argument('--key-alternate', action='store_true',
|
|
help='Use alternate bundle key candidate (x4DZmD6D2HhkzT6qD8HpKeZdgM9HCmXP)')
|
|
parser.add_argument('--key-hex', metavar='HEX',
|
|
help='64-char hex AES-256 key (overrides defaults)')
|
|
parser.add_argument('--in-place', action='store_true',
|
|
help='Write output next to the original .bundle in StandaloneWindows64/')
|
|
parser.add_argument('--dry-run', action='store_true',
|
|
help='List what would be encrypted without writing')
|
|
args = parser.parse_args()
|
|
|
|
if args.key_hex:
|
|
key = bytes.fromhex(args.key_hex)
|
|
if len(key) != 32:
|
|
print("Error: --key-hex must be exactly 64 hex chars (32 bytes)", file=sys.stderr)
|
|
sys.exit(1)
|
|
elif args.key_alternate:
|
|
key = KEY_ALTERNATE
|
|
else:
|
|
key = KEY_DEFAULT
|
|
|
|
inpath = args.input
|
|
|
|
if os.path.isdir(inpath):
|
|
files = sorted(f for f in os.listdir(inpath) if f.endswith('.unity3d'))
|
|
if not files:
|
|
print(f"No .unity3d files found in '{inpath}'")
|
|
return
|
|
|
|
if args.output:
|
|
outdir = args.output
|
|
elif args.in_place:
|
|
outdir = os.path.normpath(os.path.join(inpath, '..', '..', 'StandaloneWindows64'))
|
|
else:
|
|
outdir = os.path.normpath(os.path.join(inpath, '..', 'reencrypted'))
|
|
|
|
if not args.dry_run:
|
|
os.makedirs(outdir, exist_ok=True)
|
|
|
|
ok = errs = 0
|
|
for fname in files:
|
|
fpath = os.path.join(inpath, fname)
|
|
base = fname.removesuffix('.unity3d')
|
|
outpath = os.path.join(outdir, base + '.bundle')
|
|
if process_file(fpath, outpath, key, args.dry_run):
|
|
ok += 1
|
|
else:
|
|
errs += 1
|
|
|
|
total = len(files)
|
|
print(f"\n{ok} OK, {errs} errors of {total}")
|
|
if not args.dry_run:
|
|
print(f"Output directory: {outdir}/")
|
|
|
|
else:
|
|
if not os.path.exists(inpath):
|
|
print(f"Error: '{inpath}' not found", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
if args.output:
|
|
outpath = args.output
|
|
elif args.in_place:
|
|
base = os.path.splitext(os.path.basename(inpath))[0]
|
|
bundle_name = base + '.bundle'
|
|
candidates = [
|
|
os.path.join(os.path.dirname(inpath), bundle_name),
|
|
os.path.normpath(os.path.join(
|
|
os.path.dirname(inpath), '..', '..', 'StandaloneWindows64', bundle_name)),
|
|
]
|
|
outpath = None
|
|
for c in candidates:
|
|
if os.path.exists(c):
|
|
outpath = c
|
|
break
|
|
if not outpath:
|
|
print(f"Error: --in-place but no existing .bundle found for '{inpath}'",
|
|
file=sys.stderr)
|
|
sys.exit(1)
|
|
else:
|
|
basedir = os.path.dirname(inpath) or '.'
|
|
basename = os.path.splitext(os.path.basename(inpath))[0] + '.bundle'
|
|
outpath = os.path.join(basedir, basename)
|
|
|
|
process_file(inpath, outpath, key, args.dry_run)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|