Skip to content

Instantly share code, notes, and snippets.

@SciresM
Last active May 23, 2025 20:36
Show Gist options
  • Save SciresM/402dab55eec8fc1a6d9d5260ce2f9dac to your computer and use it in GitHub Desktop.
Save SciresM/402dab55eec8fc1a6d9d5260ce2f9dac to your computer and use it in GitHub Desktop.

Revisions

  1. SciresM revised this gist Jun 25, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion cafe_mix.py
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@
    from Crypto.Cipher import AES
    from struct import unpack as up

    XORPAD = '3F99BB49B43CBBD339FE5FEA463316A8'.decode('hex').upper()
    XORPAD = '3F99BB49B43CBBD339FE5FEA463316A8'.decode('hex')
    KEY = 'CAECB4CA65678965CBE67D7A3AFD228C'.decode('hex')
    IV = 'A65D5EA2D54AD0436DD46158C191361D'.decode('hex')

  2. SciresM created this gist Jun 25, 2020.
    116 changes: 116 additions & 0 deletions cafe_mix.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,116 @@
    import os, sys, zlib, traceback
    from Crypto.Cipher import AES
    from struct import unpack as up

    XORPAD = '3F99BB49B43CBBD339FE5FEA463316A8'.decode('hex').upper()
    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))