Last active
May 5, 2024 14:11
-
-
Save SciresM/402dab55eec8fc1a6d9d5260ce2f9dac to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os, sys, zlib, traceback | |
from Crypto.Cipher import AES | |
from struct import unpack as up | |
XORPAD = '3F99BB49B43CBBD339FE5FEA463316A8'.decode('hex') | |
KEY = 'CAECB4CA65678965CBE67D7A3AFD228C'.decode('hex') | |
IV = 'A65D5EA2D54AD0436DD46158C191361D'.decode('hex') | |
def safe_open(path, mode): | |
import os | |
dn = os.path.split(path)[0] | |
try: | |
os.makedirs(dn) | |
except OSError: | |
if not os.path.isdir(dn): | |
raise | |
except WindowsError: | |
if not os.path.isdir(dn): | |
raise | |
return open(path, mode) | |
def read_data(fp, ofs, size): | |
fp.seek(ofs) | |
dat = fp.read(size) | |
assert len(dat) == size | |
return dat | |
def process_encryption(data, enc_size, dec_size, enc_type, external_key_id): | |
# Ensure valid input. | |
assert len(data) >= enc_size | |
assert dec_size <= enc_size | |
data = data[:enc_size] | |
# External keys not yet supported, in memory it's basically a lookup table | |
assert external_key_id == 0 | |
# Ensure valid encryption type | |
assert enc_type in [0, 1, 2] | |
if enc_type == 0: | |
# No encryption | |
return data[:dec_size] | |
elif enc_type == 1: | |
# AES encryption | |
assert (enc_size & 0xF) == 0 | |
return AES.new(KEY, AES.MODE_CBC, IV).decrypt(data)[:dec_size] | |
else: #if enc_type == 2 | |
# xorpad encryption | |
return ''.join(chr(ord(c) ^ ord(XORPAD[i % len(XORPAD)])) for i,c in enumerate(data)) | |
def process_compression(data, dec_size, out_size, cmp_type): | |
# Ensure valid input | |
assert len(data) == dec_size | |
# Ensure valid compression type | |
assert cmp_type in [0, 1] | |
if cmp_type == 0: | |
# No compression | |
assert dec_size == out_size | |
return data | |
else: # if cmp_type == 1 | |
# zlib compression without header | |
return zlib.decompress(data, -zlib.MAX_WBITS) | |
def get_ext(data): | |
if data.startswith('BNTX'): | |
return 'bntx' | |
elif data.startswith(b'\x89PNG\x0D\x0A\x1A\x0A'): | |
return 'png' | |
elif data.startswith(b'\x8D\x2E\x54\xF6'): | |
return 'msg' | |
return 'bin' | |
def extract_cafe_mix_archive(archive, out_dir): | |
fp = open(archive, 'rb') | |
_00, archive_id, _08, version, num_files, body_crc, _18, _1C = up('<8I', fp.read(0x20)) | |
assert _00 == 0x10 | |
print 'Archive: %08X' % archive_id | |
print 'Version: %d.%d.%d' % (((version >> 16) & 0xFF), ((version >> 8) & 0xFF), ((version >> 0) & 0xFF)) | |
print 'Num Files: %d' % num_files | |
entry_headers = [fp.read(0x30) for i in xrange(num_files)] | |
for i,header in enumerate(entry_headers): | |
_00, _type_maybe, store_size, store_offset, enc_size, dec_size, out_size, crc, _20, cmp_type, enc_type, _26, _27, external_key_id, _2C = up('<IIIIIIIIIBBBBII', header) | |
if external_key_id != 0: | |
print 'Skipping file %d (%08x) due to external key usage (%08x).' % (i, _00, external_key_id) | |
continue | |
print 'Processing file %d (%08x)...' % (i, _00) | |
#print '%X %X %X %X %X' % (store_offset, store_size, enc_size, dec_size, out_size) | |
store_data = read_data(fp, store_offset, store_size) | |
dec_data = process_encryption(store_data, enc_size, dec_size, enc_type, external_key_id) | |
out_data = process_compression(dec_data, dec_size, out_size, cmp_type) | |
assert len(out_data) == out_size | |
assert (zlib.crc32(out_data) & 0xFFFFFFFF) == crc | |
with safe_open('%s/%08X.%s' % (out_dir, _00, get_ext(out_data)), 'wb') as f: | |
f.write(out_data) | |
def main(argc, argv): | |
if argc < 2 or argc > 3: | |
print 'Usage: %s archive [outdir]' % argv[0] | |
return 1 | |
archive = argv[1] | |
if argc >= 3: | |
out_dir = argv[2] | |
elif archive.endswith('.arc'): | |
out_dir = archive[:-4] | |
else: | |
out_dir = archive + '_out' | |
try: | |
extract_cafe_mix_archive(archive, out_dir) | |
except Exception as e: | |
print 'An error occured: %s' % str(e) | |
traceback.print_exc(file=sys.stdout) | |
return 1 | |
return 0 | |
if __name__ == '__main__': | |
sys.exit(main(len(sys.argv), sys.argv)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice !
You are the true hero man