Skip to content

Instantly share code, notes, and snippets.

@SciresM
Last active December 26, 2020 19:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SciresM/402dab55eec8fc1a6d9d5260ce2f9dac to your computer and use it in GitHub Desktop.
Save SciresM/402dab55eec8fc1a6d9d5260ce2f9dac to your computer and use it in GitHub Desktop.
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))
@isrmicha
Copy link

Nice !
You are the true hero man

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment