added re-encription tool

This commit is contained in:
Nixietab 2026-08-06 00:18:53 -03:00
parent 47f8eb112e
commit 7b7cf916ba

View file

@ -0,0 +1,132 @@
#!/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()