commit
29c72411f2
33 changed files with 12497 additions and 0 deletions
@ -0,0 +1,3 @@ |
||||
# gobbletools |
||||
|
||||
All tools in this repository are provided as-is. These tools were all made for my own personal use only. I am uploading them because the tools contain information that can be helpful to other people who want to work with these formats. If you find a bug or missing functionality then I will accept pull requests. |
@ -0,0 +1,17 @@ |
||||
# Python1 Tools |
||||
|
||||
### python1_dumper.py |
||||
|
||||
This tool lets you dump data from pop'n music Python 1 HDDs. It can be modified to work on other Python 1 games but the filename pattern matching is for pop'n music. It will not find all filenames, and if used on other games, it will just output filenames based on the filename hash. |
||||
|
||||
``` |
||||
usage: python1_dumper.py [-h] -i INPUT [-o OUTPUT] [-d] |
||||
|
||||
optional arguments: |
||||
-h, --help show this help message and exit |
||||
-i INPUT, --input INPUT |
||||
Input folder |
||||
-o OUTPUT, --output OUTPUT |
||||
Output folder |
||||
-d, --decrypt Decryption for pop'n 13 and 14 |
||||
``` |
@ -0,0 +1,514 @@ |
||||
import argparse |
||||
import ctypes |
||||
import os |
||||
import string |
||||
import struct |
||||
|
||||
def get_filename_hash(filename): |
||||
hash = 0 |
||||
|
||||
for cidx, c in enumerate(filename): |
||||
for i in range(6): |
||||
hash = ctypes.c_int(((hash >> 31) & 0x4c11db7) ^ ((hash << 1) | ((ord(c) >> i) & 1))).value |
||||
|
||||
return hash & 0xffffffff |
||||
|
||||
|
||||
def decrypt_data(data): |
||||
from Crypto.Cipher import Blowfish |
||||
|
||||
keys = { |
||||
'13G0': {'iv': 'desvsrow', 'key': 'ใใใใคใผใถใฃใกใใ๏ผฐ๏ผฏ๏ผฐโ๏ผฎๅคง่ฟทๆ'}, |
||||
'13S0': {'iv': 'wacvsjun', 'key': 'ใใใฏใซใปใธใฝใกใใฌใใฝ'}, |
||||
'13P0': {'iv': 'tvCxT5no', 'key': 'ใญใฃใใผใค๏ผ็ฅญใใ ๏ผใใฃใใใใ๏ผ'}, |
||||
'13G1': {'iv': 'LTgStteb', 'key': 'ใใคใ ใฃใฆใ็ฅญใใใใใฎใซใผใใใซใใชใใงใใใใใใ'}, |
||||
'13G2': {'iv': 'wNyxSyDQ', 'key': 'ใใณใใณใใญใใญใซใผใใใซ๏ผ'}, |
||||
'13G3': {'iv': 'wClBXmGr', 'key': 'ใผใใใฎ่กใซใใฃใฆใใ็ด ๆตใงๆๅฟซใชใซใผใใใซ'}, |
||||
'13G4': {'iv': 'PiWHlj56', 'key': 'ใใใใ\u3000ใฟใชใใใๅพ
ใกใใญ\u3000ใจใฃใฆใใใใใชใซใผใใใซ๏ผ'}, |
||||
'13G5': {'iv': 'dkWEN8Qk', 'key': 'ใใคใ ใฃใฆใ็ฅญใใใใใฎใซใผใใใซใใชใใงใใใใใใ'}, |
||||
'13G6': {'iv': 'WmPMVqCr', 'key': 'ใใใใใซใผใใใซ๏ผใชใซใง้ใถใใพใใฃใกใใใ'}, |
||||
'13G7': {'iv': '2V00iGp5', 'key': 'ใชใบใ ใซใฎใฃใฆใใฟใณใๅฉใใฐใใใใใใซใผใใใซใปใชใณใปในใใผใธ๏ผ'}, |
||||
'13G8': {'iv': 'WlMhESmP', 'key': 'ใใใใใใใใใใซใผใใใซใธ'}, |
||||
'13G9': {'iv': 'cisXpifl', 'key': 'ใใใใ\u3000ใฏใใพใ\u3000ใซใผใใใซ๏ผ'}, |
||||
'13S1': {'iv': 'C47vuHWv', 'key': 'ใซใผใใใซใใใฃใฆใใ'}, |
||||
'13S2': {'iv': 'DUtTySYu', 'key': 'ใใใใณใซใผใใใซใฎใ้ใใ ใ'}, |
||||
'13S3': {'iv': 'bLMNB8DN', 'key': 'ใใใใณใซใผใใใซใงใณใณใญใใใใ'}, |
||||
'13S4': {'iv': 'o5INlY6V', 'key': 'ใใใใใใใใณ'}, |
||||
'13S5': {'iv': '2brvTlYu', 'key': '้ใพใใใใใณใซใผใใใซ'}, |
||||
'13S6': {'iv': 'grnOhljs', 'key': 'ใใใใณใตใผใซในใ่กใซใใฃใฆใใ๏ผ'}, |
||||
'13S7': {'iv': '43fFbQqx', 'key': 'ๅ้ฉไปฐๅคฉๆฉ่จถไธๆ่ญฐ'}, |
||||
'13S8': {'iv': 'T9je5pa5', 'key': 'ใผใใใฎ่กใซใใฃใฆใใ็ด ๆตใงๆๅฟซใชใซใผใใใซ'}, |
||||
'13S9': {'iv': 'zzAqUyUa', 'key': 'ๆฉ่จถไธๆ่ญฐใชๅฎดใใๆฅฝใใฟใใใฐใใ'}, |
||||
'13P1': {'iv': 'XfXeyeGD', 'key': 'ใผใใใใใใใ็ฅญใๅคงๅฅฝใ'}, |
||||
'13P2': {'iv': 'hUgqKFXC', 'key': 'ใใใใฆใฏใใใฆ'}, |
||||
'13P3': {'iv': 'MgOREWWd', 'key': 'ใฟใใชใฎ็บใซใใฃใฆใใ๏ผ'}, |
||||
'13P4': {'iv': 'vvDq1vyZ', 'key': 'ๅคขใฎใใฑใใ็บๅฃฒไธญใ'}, |
||||
'13P5': {'iv': 'KvdgJSRY', 'key': '่ใใใฆใใใ้ ใใใ'}, |
||||
'13P6': {'iv': 'K8osNm4z', 'key': 'ไนๅฅณๅฟใซๆๅฟ'}, |
||||
'13P7': {'iv': 'AToePkrl', 'key': 'ใใใใใฟใใชๅฏใฃใจใใง๏ผ'}, |
||||
'13P8': {'iv': 'N2JPvhOd', 'key': 'ๆฅฝใใ้ณๆฅฝใทใงใผ'}, |
||||
'13P9': {'iv': 'NZuuPltz', 'key': '็ซใฎ่ผชใใใใซ\u3000ใใฉใคใขใฒใจใคใณ'}, |
||||
'13A0': {'iv': 'rP2eb6LK', 'key': 'ใใ๏ผใใใใผใฟใคใ '}, |
||||
'13A1': {'iv': 'X9YNso6d', 'key': 'ใตใผใซในใใผใใใฎ่กใซใใฃใฆใใใใใใฉใฏใซ'}, |
||||
'13A2': {'iv': 'oP2U6O7R', 'key': 'ไธ็ใใ
ใใฎ้ณๆฅฝใฎใฉใใใๅคง่ก้ฒใผโชใ็ฅญใ้จใใฏใพใ ใพใ ็ถใใใ'}, |
||||
'13A3': {'iv': 'MzkBVL9m', 'key': 'ใใฆใใชใทใงใผใฎ\u3000\u3000'}, |
||||
'13A4': {'iv': 'AbzKfkud', 'key': '่ฆใฆใใ ใใใๆฅฝใใใชใใชใ'}, |
||||
'13A5': {'iv': 'sqYSM25b', 'key': 'ใฟใใชใงใ็ฅญใ็ใไธใใใ'}, |
||||
'13A6': {'iv': 'R63VSaLE', 'key': 'ใใฎๅจใใใฏใณใจ้ ฌๆใใใ'}, |
||||
'13A7': {'iv': 'K5eDOnXm', 'key': 'ใฒใจใจใใฎๅคขใใใใใใใ'}, |
||||
'13A8': {'iv': 'ww6JPyWf', 'key': '1ไบบใงใป2ไบบใงใป3ไบบใง๏ผ'}, |
||||
'13A9': {'iv': 'XS7JUaUJ', 'key': 'ใใฃใตใใใใใงใใใ'}, |
||||
} |
||||
|
||||
try: |
||||
key_info = keys[data[:4].decode('ascii')] |
||||
enc_len = int.from_bytes(data[4:8], 'little') |
||||
|
||||
cipher = Blowfish.new(key_info['key'].encode('shift-jis'), Blowfish.MODE_CBC, key_info['iv'].encode('ascii')) |
||||
dec_data = cipher.decrypt(data)[8:] |
||||
|
||||
return bytearray(dec_data)[:enc_len] |
||||
|
||||
except: |
||||
# Not encrypted? |
||||
return data |
||||
|
||||
|
||||
def decompress_gcz(data): |
||||
data_length = len(data) |
||||
offset = 0 |
||||
output = [] |
||||
|
||||
while offset < data_length: |
||||
flag = data[offset] |
||||
offset += 1 |
||||
|
||||
for bit in range(8): |
||||
if flag & (1 << bit): |
||||
output.append(data[offset]) |
||||
offset += 1 |
||||
|
||||
else: |
||||
if offset + 2 > data_length: |
||||
break |
||||
|
||||
cmd1, cmd2 = data[offset:offset+2] |
||||
lookback_length = (cmd1 & 0x0f) + 3 |
||||
lookback_offset = ((cmd1 & 0xf0) << 4) + cmd2 |
||||
offset += 2 |
||||
|
||||
if cmd1 == 0 and cmd2 == 0: |
||||
break |
||||
|
||||
for _ in range(lookback_length): |
||||
loffset = len(output) - lookback_offset |
||||
if loffset <= 0 or loffset >= len(output): |
||||
output.append(0) |
||||
|
||||
else: |
||||
output.append(output[loffset]) |
||||
|
||||
return bytearray(output) |
||||
|
||||
|
||||
def get_file_data(infile, fileinfo): |
||||
cur_offset = infile.tell() |
||||
|
||||
infile.seek(fileinfo['offset']) |
||||
data = infile.read(fileinfo['size']) |
||||
infile.seek(cur_offset) |
||||
|
||||
return data |
||||
|
||||
|
||||
def parse_system_idx(data): |
||||
filename_hashes_add = {} |
||||
|
||||
count = int.from_bytes(data[6:8], byteorder="little") |
||||
stroff = int.from_bytes(data[8:10], byteorder="little") - 0x180 |
||||
|
||||
for i in range(count): |
||||
filename = data[stroff+i*0x20:stroff+(i+1)*0x20].decode('ascii').strip('\0').lstrip('/') |
||||
filename = "image/%s" % filename.lower() |
||||
filename_hashes_add[get_filename_hash(filename)] = filename |
||||
|
||||
return filename_hashes_add |
||||
|
||||
|
||||
def find_strings_binary(exe_data, search_key): |
||||
found_strings = [] |
||||
i = 0 |
||||
|
||||
while i < len(exe_data) - len(search_key): |
||||
if exe_data[i:i+len(search_key)] == search_key or exe_data[i:i+len(search_key)] == search_key.upper() or exe_data[i:i+len(search_key)] == search_key.lower(): |
||||
str_start = i |
||||
str_end = i + len(search_key) |
||||
|
||||
while exe_data[str_end] != 0: |
||||
str_end += 1 |
||||
|
||||
while chr(exe_data[str_start - 1]) in string.printable: |
||||
str_start -= 1 |
||||
|
||||
found_str = exe_data[str_start:str_end].decode('ascii').lower() |
||||
|
||||
if found_str.startswith("disk0:/"): |
||||
found_str = found_str[len("disk0:/"):] |
||||
|
||||
if found_str.startswith("disk0:"): |
||||
found_str = found_str[len("disk0:"):] |
||||
|
||||
if found_str[0] == '/': |
||||
found_str = found_str[1:] |
||||
|
||||
if '%' not in found_str: |
||||
found_strings.append(found_str) |
||||
|
||||
i = str_end + 1 |
||||
|
||||
else: |
||||
i += 1 |
||||
|
||||
return found_strings |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
parser = argparse.ArgumentParser() |
||||
|
||||
parser.add_argument('-i', '--input', help='Input folder', default=None, required=True) |
||||
parser.add_argument('-o', '--output', help='Output folder', default=None) |
||||
parser.add_argument('-d', '--decrypt', help='Decryption for pop\'n 13 and 14', default=False, action='store_true') |
||||
|
||||
args = parser.parse_args() |
||||
|
||||
if not os.path.exists(args.output): |
||||
os.makedirs(args.output) |
||||
|
||||
entries = [] |
||||
with open(args.input, "rb") as infile: |
||||
system_files = [] |
||||
|
||||
# Not sure how this really works |
||||
for offset in [0x20]: |
||||
infile.seek(offset) |
||||
sysfile_offset = int.from_bytes(infile.read(4), byteorder="little") |
||||
sysfile_offset2 = int.from_bytes(infile.read(4), byteorder="little") |
||||
|
||||
infile.seek(offset + 0x10) |
||||
sysfile_size = int.from_bytes(infile.read(4), byteorder="big") |
||||
checksum = int.from_bytes(infile.read(4), byteorder="big") |
||||
|
||||
system_files.append({ |
||||
'offsets': [x for x in [sysfile_offset, sysfile_offset2] if x != 0], |
||||
'size': sysfile_size, |
||||
'checksum': checksum, |
||||
}) |
||||
|
||||
for offset in [0xa0]: |
||||
infile.seek(offset) |
||||
sysfile_offset = int.from_bytes(infile.read(4), byteorder="little") * 0x100 |
||||
|
||||
infile.seek(offset + 0x10) |
||||
sysfile_size = int.from_bytes(infile.read(4), byteorder="little") |
||||
infile.seek(4, 1) |
||||
checksum = int.from_bytes(infile.read(4), byteorder="little") |
||||
|
||||
system_files.append({ |
||||
'offsets': [x for x in [sysfile_offset] if x != 0], |
||||
'size': sysfile_size, |
||||
'checksum': checksum, |
||||
}) |
||||
|
||||
filetable_offset = 0 |
||||
for offset in [0x100]: |
||||
infile.seek(offset) |
||||
sysfile_offset = int.from_bytes(infile.read(4), byteorder="little") * 0x100 |
||||
sysfile_offset2 = int.from_bytes(infile.read(4), byteorder="little") * 0x100 |
||||
|
||||
infile.seek(offset + 0x10) |
||||
sysfile_size = int.from_bytes(infile.read(4), byteorder="little") |
||||
checksum = int.from_bytes(infile.read(4), byteorder="little") |
||||
|
||||
filetable_offset = sysfile_offset + 0x100000 |
||||
|
||||
system_files.append({ |
||||
'offsets': [x for x in [sysfile_offset, sysfile_offset2] if x != 0], |
||||
'size': sysfile_size, |
||||
'checksum': checksum, |
||||
}) |
||||
|
||||
for idx, sysfile in enumerate(system_files): |
||||
for idx2, offset in enumerate(sysfile['offsets']): |
||||
infile.seek(offset) |
||||
|
||||
print(sysfile) |
||||
|
||||
if idx2 == 0: |
||||
output_filename = "sysfile_%d.bin" % idx |
||||
|
||||
else: |
||||
output_filename = "sysfile_%d_%d.bin" % (idx, idx2) |
||||
|
||||
raw_data = infile.read(sysfile['size']) |
||||
|
||||
with open(os.path.join(args.output, "raw_" + output_filename), "wb") as outfile: |
||||
outfile.write(raw_data) |
||||
|
||||
checksum = sum([int.from_bytes(raw_data[i:i+2], 'big') for i in range(0, len(raw_data), 2)]) & 0x7fffffff |
||||
if checksum != sysfile.get('checksum', 0): |
||||
print("sysfile %s checksum did not match! %08x vs %08x" % (output_filename, checksum, sysfile.get('checksum', 0))) |
||||
|
||||
try: |
||||
dec_data = decompress_gcz(raw_data) |
||||
|
||||
with open(os.path.join(args.output, output_filename), "wb") as outfile: |
||||
outfile.write(dec_data) |
||||
|
||||
except: |
||||
pass |
||||
|
||||
filenames = [ |
||||
"ee/boot/game.bin", |
||||
"boot/game.bin", |
||||
"game.bin", |
||||
"boot.bin", |
||||
"title.txt", |
||||
] |
||||
|
||||
exe_data = open(os.path.join(args.output, "sysfile_2.bin"), "rb").read() |
||||
|
||||
print("Filetable Offset: %08x" % filetable_offset) |
||||
|
||||
infile.seek(filetable_offset) |
||||
|
||||
# Find game.bin if it exists. Hacky piece of shit code |
||||
filename_hashes = {} |
||||
for filename in filenames: |
||||
filename_hashes[get_filename_hash(filename)] = filename |
||||
|
||||
while True: |
||||
filename_hash, file_offset, file_size, file_unk, checksum, file_unk3 = struct.unpack("<IIIIII", infile.read(0x18)) |
||||
|
||||
if filename_hash == 0: |
||||
break |
||||
|
||||
file_offset *= 0x200 |
||||
|
||||
entry = { |
||||
'filename_hash': filename_hash, |
||||
'offset': file_offset, |
||||
'size': file_size, |
||||
'checksum': checksum, |
||||
} |
||||
|
||||
entries.append(entry) |
||||
|
||||
for entry in entries: |
||||
infile.seek(entry['offset']) |
||||
|
||||
if entry['filename_hash'] in filename_hashes: |
||||
output_filename = filename_hashes[entry['filename_hash']] |
||||
|
||||
else: |
||||
continue |
||||
|
||||
output_filename = os.path.join(args.output, output_filename) |
||||
|
||||
if os.path.exists(output_filename): |
||||
continue |
||||
|
||||
basedir = os.path.dirname(output_filename) |
||||
if basedir and not os.path.exists(basedir): |
||||
os.makedirs(basedir) |
||||
|
||||
with open(output_filename, "wb") as outfile: |
||||
print(entry) |
||||
print(output_filename) |
||||
|
||||
data = infile.read(entry['size']) |
||||
|
||||
if args.decrypt: |
||||
data = decrypt_data(data) |
||||
|
||||
outfile.write(data) |
||||
|
||||
exe_path = os.path.join(args.output, "ee", "boot", "game.bin") |
||||
if os.path.exists(exe_path): |
||||
exe_data += open(exe_path, "rb").read() |
||||
print("Found", exe_path) |
||||
|
||||
infile.seek(filetable_offset) |
||||
|
||||
found_filenames = [ |
||||
'libsd.irx', |
||||
'mcman.irx', |
||||
'mcserv.irx', |
||||
'padman.irx', |
||||
'sddrviop.irx', |
||||
'sio2man.irx', |
||||
'snd.irx', |
||||
'snd2.irx', |
||||
'filesys.irx', |
||||
'filesys2.irx', |
||||
] |
||||
found_filenames += find_strings_binary(exe_data, b".bin") |
||||
found_filenames += find_strings_binary(exe_data, b".gcz") |
||||
found_filenames += find_strings_binary(exe_data, b".gzz") |
||||
found_filenames += find_strings_binary(exe_data, b".irx") |
||||
found_filenames += find_strings_binary(exe_data, b".mhd") |
||||
found_filenames += find_strings_binary(exe_data, b".idx") |
||||
|
||||
for folder in ["image/", "patch/", "modules/", "irx/", "iop/", "iop/modules", ""]: |
||||
for filename in found_filenames + filenames: |
||||
filenames.append(folder + filename) |
||||
filenames.append((folder + filename).upper()) |
||||
filenames.append((folder + filename).lower()) |
||||
|
||||
# TODO: Find strings that contain number formats and try to bruteforce them automatically |
||||
# Specific files from pop'n music 9 |
||||
for i1 in range(0, 10): |
||||
for i2 in range(0, 10): |
||||
for i3 in range(0, 100): |
||||
filenames.append("md_xx/md_%d%d%02d.dat" % (i1, i2, i3)) |
||||
|
||||
for i in range(0, 1000): |
||||
for subfolder in ["", "image/"]: |
||||
for ext in ["gcz", "gzz"]: |
||||
filenames.append("%snorma/d_n_%03d.%s" % (subfolder, i, ext)) |
||||
filenames.append("%sbg/bg_%03d.%s" % (subfolder, i, ext)) |
||||
filenames.append("%snt/nt_%03d.%s" % (subfolder, i, ext)) |
||||
filenames.append("%skc/kc_%03d.%s" % (subfolder, i, ext)) |
||||
filenames.append("%skc_normal/banner_%02d.%s" % (subfolder, i, ext)) |
||||
filenames.append("%srj/rj_%03d.%s" % (subfolder, i, ext)) |
||||
filenames.append("%scg/cg_%03d.%s" % (subfolder, i, ext)) |
||||
filenames.append("%sha_art/ha_at%03d.%s" % (subfolder, i, ext)) |
||||
filenames.append("%sha_art/ha_art_%03d.%s" % (subfolder, i, ext)) |
||||
filenames.append("%sprize_picture/g_%02d.%s" % (subfolder, i, ext)) |
||||
|
||||
filename_hashes = {} |
||||
for filename in filenames: |
||||
filename_hashes[get_filename_hash(filename)] = filename |
||||
|
||||
while True: |
||||
filename_hash, file_offset, file_size, file_unk, checksum, file_unk3 = struct.unpack("<IIIIII", infile.read(0x18)) |
||||
|
||||
if filename_hash == 0: |
||||
break |
||||
|
||||
file_offset *= 0x200 |
||||
|
||||
entry = { |
||||
'filename_hash': filename_hash, |
||||
'offset': file_offset, |
||||
'size': file_size, |
||||
'checksum': checksum, |
||||
} |
||||
|
||||
entries.append(entry) |
||||
|
||||
if entry['filename_hash'] in filename_hashes and filename_hashes[entry['filename_hash']].endswith("system.idx"): |
||||
# Parse system.idx for more filenames |
||||
filename_hashes.update(parse_system_idx(get_file_data(infile, entry))) |
||||
|
||||
base_filename = filename_hashes[entry['filename_hash']].lstrip("image/").rstrip("/system.idx") |
||||
|
||||
for subfolder in ["", "image/"]: |
||||
for ext in ["gcz", "gzz"]: |
||||
ha_chara_filename = "%sha_chara/ha_%s.%s" % (subfolder, base_filename, ext) |
||||
filename_hashes[get_filename_hash(ha_chara_filename)] = ha_chara_filename |
||||
|
||||
for entry in entries: |
||||
infile.seek(entry['offset']) |
||||
|
||||
if entry['filename_hash'] in filename_hashes: |
||||
output_filename = filename_hashes[entry['filename_hash']] |
||||
|
||||
else: |
||||
output_filename = "%08x.bin" % entry['filename_hash'] |
||||
|
||||
output_filename = os.path.join(args.output, output_filename) |
||||
|
||||
if os.path.exists(output_filename): |
||||
continue |
||||
|
||||
basedir = os.path.dirname(output_filename) |
||||
if basedir and not os.path.exists(basedir): |
||||
os.makedirs(basedir) |
||||
|
||||
with open(output_filename, "wb") as outfile: |
||||
print(entry) |
||||
print(output_filename) |
||||
|
||||
data = infile.read(entry['size']) |
||||
|
||||
if args.decrypt: |
||||
data = decrypt_data(data) |
||||
|
||||
outfile.write(data) |
||||
|
||||
checksum = sum(data) & 0xffffffff |
||||
if checksum != entry['checksum']: |
||||
print("%s checksum did not match! %08x vs %08x" % (output_filename, checksum, entry['checksum'])) |
||||
|
||||
# Seems to always be 0x20000000? But later HDDs use encrypted data so the PM09 header won't be found |
||||
next_file_offset = 0x20000000 |
||||
file_id = 0 |
||||
|
||||
while True: |
||||
infile.seek(next_file_offset) |
||||
next_file_offset = infile.tell() + 0x1400000 |
||||
|
||||
header = infile.read(4) |
||||
|
||||
if len(header) != 4: |
||||
break |
||||
|
||||
if header != b'PM09': |
||||
continue |
||||
|
||||
print("Dumping sound data @ %08x" % (infile.tell() - 4)) |
||||
|
||||
filecount = int.from_bytes(infile.read(4), byteorder="little") |
||||
|
||||
file_id2 = 0 |
||||
for i in range(filecount): |
||||
file_offset, file_size = struct.unpack("<II", infile.read(8)) |
||||
file_offset *= 0x200 |
||||
|
||||
if file_offset == 0 and file_size == 0: |
||||
break |
||||
|
||||
output_folder = os.path.join(args.output, "_data/%04d/" % file_id) |
||||
|
||||
if not os.path.exists(output_folder): |
||||
os.makedirs(output_folder) |
||||
|
||||
output_filename = os.path.join(output_folder, "%04d_%d.bin" % (file_id, file_id2)) |
||||
|
||||
if os.path.exists(output_filename): |
||||
continue |
||||
|
||||
print("Dumping", output_filename) |
||||
with open(output_filename, "wb") as outfile: |
||||
entry = { |
||||
'offset': file_offset, |
||||
'size': file_size, |
||||
} |
||||
|
||||
data = get_file_data(infile, entry) |
||||
|
||||
if args.decrypt: |
||||
data = decrypt_data(data) |
||||
|
||||
try: |
||||
data = decompress_gcz(data) |
||||
|
||||
except: |
||||
pass |
||||
|
||||
|
||||
outfile.write(data) |
||||
|
||||
file_id2 += 1 |
||||
|
||||
file_id += 1 |
@ -0,0 +1,51 @@ |
||||
<style> |
||||
.jamma table th:first-of-type { |
||||
width: 45%; |
||||
} |
||||
.jamma table th:nth-of-type(2) { |
||||
width: 5%; |
||||
} |
||||
.jamma table th:nth-of-type(3) { |
||||
width: 5%; |
||||
} |
||||
.jamma table th:nth-of-type(4) { |
||||
width: 45%; |
||||
} |
||||
</style> |
||||
|
||||
<div class="jamma"> |
||||
|
||||
## System 573 Dance Maniax JAMMA Pinout |
||||
|
||||
Bottom | | | Top |
||||
:------: | :------: | :------: | :------: |
||||
| | A | 1 | | |
||||
| | B | 2 | | |
||||
| | C | 3 | | |
||||
| | D | 4 | | |
||||
| | E | 5 | | |
||||
| | F | 6 | | |
||||
| | H | 7 | | |
||||
| | J | 8 | | |
||||
| | K | 9 | | |
||||
| | L | 10 | | |
||||
| | M | 11 | | |
||||
| | N | 12 | | |
||||
| | P | 13 | | |
||||
| Service | R | 14 | | |
||||
| | S | 15 | Test | |
||||
| | T | 16 | Coin | |
||||
| P2 Start | U | 17 | P1 Start | |
||||
| P2 Left | V | 18 | P1 Left | |
||||
| P2 Right | W | 19 | P1 Right | |
||||
| P2 D1 1 | X | 20 | P1 D1 1 | |
||||
| P2 D1 2 | Y | 21 | P1 D1 2 | |
||||
| P2 U 1 | Z | 22 | P1 U 1 | |
||||
| P2 U 2 | a | 23 | P1 U 2 | |
||||
| | b | 24 | | |
||||
| P2 D0 1 | c | 25 | P1 D0 1 | |
||||
| P2 D0 2 | d | 26 | P1 D0 2 | |
||||
| | e | 27 | | |
||||
| | f | 28 | | |
||||
|
||||
</div> |
@ -0,0 +1,51 @@ |
||||
<style> |
||||
.jamma table th:first-of-type { |
||||
width: 45%; |
||||
} |
||||
.jamma table th:nth-of-type(2) { |
||||
width: 5%; |
||||
} |
||||
.jamma table th:nth-of-type(3) { |
||||
width: 5%; |
||||
} |
||||
.jamma table th:nth-of-type(4) { |
||||
width: 45%; |
||||
} |
||||
</style> |
||||
|
||||
<div class="jamma"> |
||||
|
||||
## System 573 Dance Dance Revolution JAMMA Pinout |
||||
|
||||
Bottom | | | Top |
||||
:------: | :------: | :------: | :------: |
||||
| | A | 1 | | |
||||
| | B | 2 | | |
||||
| | C | 3 | | |
||||
| | D | 4 | | |
||||
| | E | 5 | | |
||||
| | F | 6 | | |
||||
| | H | 7 | | |
||||
| | J | 8 | | |
||||
| | K | 9 | | |
||||
| | L | 10 | | |
||||
| | M | 11 | | |
||||
| | N | 12 | | |
||||
| | P | 13 | | |
||||
| Service | R | 14 | | |
||||
| | S | 15 | Test | |
||||
| | T | 16 | Coin | |
||||
| P2 Start | U | 17 | P1 Start | |
||||
| P2 Up | V | 18 | P1 Up | |
||||
| P2 Down | W | 19 | P1 Down | |
||||
| P2 Left | X | 20 | P1 Left | |
||||
| P2 Right | Y | 21 | P1 Right | |
||||
| | Z | 22 | | |
||||
| P2 Select L | a | 23 | P1 Select L | |
||||
| P2 Select R | b | 24 | P1 Select R | |
||||
| | c | 25 | | |
||||
| | d | 26 | | |
||||
| | e | 27 | | |
||||
| | f | 28 | | |
||||
|
||||
</div> |
@ -0,0 +1,53 @@ |
||||
<style> |
||||
.jamma table th:first-of-type { |
||||
width: 45%; |
||||
} |
||||
.jamma table th:nth-of-type(2) { |
||||
width: 5%; |
||||
} |
||||
.jamma table th:nth-of-type(3) { |
||||
width: 5%; |
||||
} |
||||
.jamma table th:nth-of-type(4) { |
||||
width: 45%; |
||||
} |
||||
</style> |
||||
|
||||
<div class="jamma"> |
||||
|
||||
## System 573 Drummania JAMMA Pinout |
||||
|
||||
TODO: Verify on real hardware |
||||
|
||||
Bottom | | | Top |
||||
:------: | :------: | :------: | :------: |
||||
| | A | 1 | | |
||||
| | B | 2 | | |
||||
| | C | 3 | | |
||||
| | D | 4 | | |
||||
| | E | 5 | | |
||||
| | F | 6 | | |
||||
| | H | 7 | | |
||||
| | J | 8 | | |
||||
| | K | 9 | | |
||||
| | L | 10 | | |
||||
| | M | 11 | | |
||||
| | N | 12 | | |
||||
| | P | 13 | | |
||||
| Service | R | 14 | | |
||||
| | S | 15 | Test | |
||||
| | T | 16 | Coin | |
||||
| | U | 17 | Start | |
||||
| | V | 18 | Hi-hat | |
||||
| | W | 19 | Snare | |
||||
| Select L | X | 20 | High Tom | |
||||
| Select R | Y | 21 | Low Tom | |
||||
| | Z | 22 | Cymbal | |
||||
| | a | 23 | | |
||||
| | b | 24 | Bass Drum | |
||||
| | c | 25 | | |
||||
| | d | 26 | | |
||||
| | e | 27 | | |
||||
| | f | 28 | | |
||||
|
||||
</div> |
@ -0,0 +1,53 @@ |
||||
<style> |
||||
.jamma table th:first-of-type { |
||||
width: 45%; |
||||
} |
||||
.jamma table th:nth-of-type(2) { |
||||
width: 5%; |
||||
} |
||||
.jamma table th:nth-of-type(3) { |
||||
width: 5%; |
||||
} |
||||
.jamma table th:nth-of-type(4) { |
||||
width: 45%; |
||||
} |
||||
</style> |
||||
|
||||
<div class="jamma"> |
||||
|
||||
## System 573 Guitar Freaks JAMMA Pinout |
||||
|
||||
TODO: Verify P2 controls |
||||
|
||||
Bottom | | | Top |
||||
:------: | :------: | :------: | :------: |
||||
| | A | 1 | | |
||||
| | B | 2 | | |
||||
| | C | 3 | | |
||||
| | D | 4 | | |
||||
| | E | 5 | | |
||||
| | F | 6 | | |
||||
| | H | 7 | | |
||||
| | J | 8 | | |
||||
| | K | 9 | | |
||||
| | L | 10 | | |
||||
| | M | 11 | | |
||||
| | N | 12 | | |
||||
| | P | 13 | | |
||||
| Service | R | 14 | | |
||||
| | S | 15 | Test | |
||||
| | T | 16 | Coin | |
||||
| P2 Start | U | 17 | P1 Start | |
||||
| P2 Pick | V | 18 | P1 Pick | |
||||
| P2 Wail | W | 19 | P1 Wail | |
||||
| P2 Effect (2) | X | 20 | P1 Effect (2) | |
||||
| P2 Effect (1) | Y | 21 | P2 Effect (1) | |
||||
| P2 R | Z | 22 | P1 R | |
||||
| P2 G | a | 23 | P1 G | |
||||
| P2 B | b | 24 | P1 B | |
||||
| | c | 25 | | |
||||
| | d | 26 | | |
||||
| | e | 27 | | |
||||
| | f | 28 | | |
||||
|
||||
</div> |
@ -0,0 +1,51 @@ |
||||
<style> |
||||
.jamma table th:first-of-type { |
||||
width: 45%; |
||||
} |
||||
.jamma table th:nth-of-type(2) { |
||||
width: 5%; |
||||
} |
||||
.jamma table th:nth-of-type(3) { |
||||
width: 5%; |
||||
} |
||||
.jamma table th:nth-of-type(4) { |
||||
width: 45%; |
||||
} |
||||
</style> |
||||
|
||||
<div class="jamma"> |
||||
|
||||
## System 573 Mambo a Go Go JAMMA Pinout |
||||
|
||||
Bottom | | | Top |
||||
:------: | :------: | :------: | :------: |
||||
| | A | 1 | | |
||||
| | B | 2 | | |
||||
| | C | 3 | | |
||||
| | D | 4 | | |
||||
| | E | 5 | | |
||||
| | F | 6 | | |
||||
| | H | 7 | | |
||||
| | J | 8 | | |
||||
| | K | 9 | | |
||||
| | L | 10 | | |
||||
| | M | 11 | | |
||||
| | N | 12 | | |
||||
| | P | 13 | | |
||||
| Service | R | 14 | | |
||||
| | S | 15 | Test | |
||||
| Right Pad 7 | T | 16 | Coin | |
||||
| Left Pad 1 | U | 17 | Start | |
||||
| Center Pad 4 | V | 18 | Center Pad 6 | |
||||
| Center Pad 5 | W | 19 | Center Pad 4 | |
||||
| Center Pad 5 | X | 20 | Left | |
||||
| Center Pad 6 | Y | 21 | Right | |
||||
| Right Pad 8 | Z | 22 | Left Pad 2 | |
||||
| Right Pad 7 | a | 23 | Left Pad 1 | |
||||
| Right Pad 9 | b | 24 | Left Pad 3 | |
||||
| Right Pad 9 | c | 25 | Left Pad 3 | |
||||
| Right Pad 8 | d | 26 | Left Pad 2 | |
||||
| | e | 27 | | |
||||
| | f | 28 | | |
||||
|
||||
</div> |
@ -0,0 +1,20 @@ |
||||
# System 573 Multisession Unit Audio Decryption Tool |
||||
|
||||
This tool works on all multisession unit discs. |
||||
|
||||
This is a full recreation of the algorithm used to decrypt data on the real multisession unit device. It does not require access to hardware at all to decrypt anything, or generate any metadata. |
||||
|
||||
## Usage |
||||
|
||||
``` |
||||
usage: msudecrypt.py [-h] --input INPUT [--output OUTPUT] |
||||
|
||||
optional arguments: |
||||
-h, --help show this help message and exit |
||||
--input INPUT Input file |
||||
--output OUTPUT Output file |
||||
``` |
||||
|
||||
Without `--output (filename.mp3)` being specified, it will automatically output a file in the same location as input.dat/.mp3 with a .mp3/.dat extension. |
||||
|
||||
|
@ -0,0 +1,21 @@ |
||||
import struct |
||||
import sys |
||||
import ctypes |
||||
|
||||
with open(sys.argv[1], "rb") as infile: |
||||
data = bytearray(infile.read()) |
||||
data[0x1c:0x20] = struct.pack("<I", 0) |
||||
|
||||
checksum = 0 |
||||
for i in range(0, len(data), 4): |
||||
checksum += struct.unpack(">I", data[i:i+4])[0] |
||||
checksum &= 0xffffffff |
||||
|
||||
checksum_diff = 0xffffffff - checksum |
||||
|
||||
print("%08x %08x" % (checksum, checksum_diff)) |
||||
|
||||
if checksum_diff != 0: |
||||
with open(sys.argv[1], "rb+") as outfile: |
||||
outfile.seek(0x1c) |
||||
outfile.write(struct.pack(">I", checksum_diff)) |
@ -0,0 +1,100 @@ |
||||
import argparse |
||||
import hashlib |
||||
import os |
||||
import struct |
||||
import sys |
||||
|
||||
def decrypt(data, key): |
||||
# Pad key |
||||
while len(key) % 2: |
||||
key += b"\x00" |
||||
|
||||
# Prepare key as 16-bit words |
||||
key = [int.from_bytes(key[i:i+2], byteorder="little") for i in range(0, len(key), 2)] |
||||
|
||||
# Generate extended key |
||||
expanded_key = [ 0xb7e1, 0x5618, 0xf44f, 0x9286, 0x30bd, 0xcef4, 0x6d2b, 0x0b62, |
||||
0xa999, 0x47d0, 0xe607, 0x843e, 0x2275, 0xc0ac, 0x5ee3, 0xfd1a, |
||||
0x9b51, 0x3988, 0xd7bf, 0x75f6, 0x142d, 0xb264, 0x509b, 0xeed2, |
||||
0x8d09, 0x2b40, 0xc977, 0x67ae, 0x05e5, 0xa41c, 0x4253, 0xe08a ] |
||||
|
||||
# Mix key |
||||
t0 = t1 = s1 = 0 |
||||
for x in range(32): |
||||
a2 = expanded_key[x] |
||||
a0 = key[x % len(key)] |
||||
|
||||
for k in range(3): |
||||
v0 = a2 + t1 + t0 |
||||
v1 = (v0 & 0xffff) >> 3 |
||||
a2 = v1 | (v0 << 13) |
||||
t1 = a2 & 0xffff |
||||
|
||||
a0 += t1 + t0 |
||||
v1 = a0 & 0xffff |
||||
v0 = a0 & 0x0f |
||||
a0 = (v1 << v0) | (v1 >> (0x10 - v0)) |
||||
t0 = a0 & 0xffff |
||||
|
||||
expanded_key[x] = t1 |
||||
key[x % len(key)] = t0 |
||||
|
||||
v1 = (t1 & 0xff) & 0xff |
||||
s1 = (s1 + v1) & 0xff |
||||
|
||||
v1 = ((s1 * 0x5AC056B1) >> 32) & 0xffffffff |
||||
counter = ((s1 - (((v1 + ((s1 - v1) >> 1)) >> 7) * 0xbd) & 0xffffffff) + 0x43) & 0xff |
||||
|
||||
# If there's an extra byte at the end of the file, drop it because the data must be in 16-bit words to be decrypted. |
||||
# This shouldn't affect any songs as far as I know since usually the last byte is a 00. |
||||
# TODO: Determine if the MSU even decrypts or does anything with the last byte in the case of a non-even number of bytes. |
||||
data = [int.from_bytes(data[i:i+2], byteorder="big") for i in range(0, len(data) // 2 * 2, 2)] |
||||
|
||||
# Decrypt data |
||||
for i in range(len(data)): |
||||
t0 = expanded_key[(counter + 3) % len(expanded_key)] & 0xffff |
||||
t1 = expanded_key[(counter + 2) % len(expanded_key)] & 0xffff |
||||
v1 = (data[i] - t0) & 0xffff |
||||
v0 = ((t1 + t0) & 7) + 4 |
||||
v1 = (((v1 << (0x10 - v0)) | (v1 >> v0)) ^ t1) - (expanded_key[counter % len(expanded_key)] ^ expanded_key[(counter + 1) % len(expanded_key)]) |
||||
|
||||
data[i] = v1 & 0xffff |
||||
expanded_key[counter % len(expanded_key)] = (expanded_key[counter % len(expanded_key)] + expanded_key[(counter + 1) % len(expanded_key)]) & 0xffff |
||||
counter = (counter + 1) & 0xff |
||||
|
||||
return bytearray(b"".join([c.to_bytes(2, byteorder="big") for c in data])) |
||||
|
||||
|
||||
def main(input_filename, output_filename=None): |
||||
key = bytearray("!kAiNsYuu4NkAn3594NnAnbo9tyouzUi105DaisOugEnmIn4N", encoding="ascii") |
||||
|
||||
filename = os.path.splitext(os.path.basename(input_filename))[0] |
||||
for idx, c in enumerate(filename.lower()): |
||||
key[idx + len(filename)] = ord(c) |
||||
|
||||
md5hash = hashlib.md5() |
||||
md5hash.update(key) |
||||
md5key = bytearray(md5hash.digest()) |
||||
|
||||
# Swap endianness of md5key array |
||||
for i in range(0, len(md5key), 2): |
||||
t = md5key[i] |
||||
md5key[i] = md5key[i+1] |
||||
md5key[i+1] = t |
||||
|
||||
data = bytearray(open(input_filename, "rb").read()) |
||||
data = decrypt(data, md5key) |
||||
|
||||
if not output_filename: |
||||
output_filename = os.path.splitext(input_filename)[0] + ".mp3" |
||||
|
||||
with open(output_filename, "wb") as outfile: |
||||
outfile.write(data) |
||||
|
||||
if __name__ == "__main__": |
||||
parser = argparse.ArgumentParser() |
||||
parser.add_argument('--input', help='Input file', required=True) |
||||
parser.add_argument('--output', help='Output file', default=None) |
||||
args = parser.parse_args() |
||||
|
||||
main(args.input, args.output) |
@ -0,0 +1,38 @@ |
||||
# py573a |
||||
|
||||
A Python recreation of 573a.jar with less Java, less GUI, less obfuscation, and less encryption. |
||||
|
||||
py573a.py requires Cython. Build the required files using `python setup.py build_ext --inplace`. |
||||
|
||||
py573a_native.py contains the decryption algorithm in native Python, in case you want to use the tool in an environment where Cython is inconvenient. Note: This version will be slower, but it should be easier to use without installing Cython and a C compiler, etc. |
||||
|
||||
## Usage |
||||
|
||||
``` |
||||
usage: py573a.py [-h] [--input INPUT] [--output OUTPUT] [--sha1 SHA1] |
||||
[--key1 KEY1] [--key2 KEY2] [--key3 KEY3] |
||||
|
||||
optional arguments: |
||||
-h, --help show this help message and exit |
||||
--input INPUT Input file |
||||
--output OUTPUT Output file |
||||
--sha1 SHA1 Force usage of a specific SHA-1 for encryption keys |
||||
(optional) |
||||
--key1 KEY1 Key 1 (optional) |
||||
--key2 KEY2 Key 2 (optional) |
||||
--key3 KEY3 Key 3 (optional) |
||||
``` |
||||
|
||||
Without `--output (filename.mp3)` being specified, it will automatically output a file in the same location as input.dat/.mp3 with a .mp3/.dat extension. |
||||
|
||||
### Decryption |
||||
Decrypt a DAT file. The correct key is determined by the SHA-1 hash of the file. You can manually specify the SHA-1, or else the program will automatically calculate the SHA-1 hash of the input file. The SHA-1 hash must exist in the database for decryption to be possible. |
||||
|
||||
``` |
||||
python py573a.py --input input.dat |
||||
``` |
||||
|
||||
### Database |
||||
- Dance Dance Revolution Solo Bass Mix uses a different algorithm for which the key algorithm is not yet known, so the old method is still used for those games. Everything else should work. |
||||
- If a song is not in the db.json file, you must manually enter the key information yourself or use the `--key1`, `--key2`, and `--key3` parameters to decrypt the data. |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,227 @@ |
||||
# cython: cdivision=True |
||||
|
||||
cdef rot(int c): |
||||
return ((c >> 7) & 1) | ((c << 1) & 0xff) |
||||
|
||||
|
||||
cdef int is_bit_set(int value, int n): |
||||
return (value >> n) & 1 |
||||
|
||||
|
||||
cdef int bit_swap(int v, int b15, int b14, int b13, int b12, int b11, int b10, int b9, int b8, int b7, int b6, int b5, int b4, int b3, int b2, int b1, int b0): |
||||
return (is_bit_set(v, b15) << 15) | \ |
||||
(is_bit_set(v, b14) << 14) | \ |
||||
(is_bit_set(v, b13) << 13) | \ |
||||
(is_bit_set(v, b12) << 12) | \ |
||||
(is_bit_set(v, b11) << 11) | \ |
||||
(is_bit_set(v, b10) << 10) | \ |
||||
(is_bit_set(v, b9) << 9) | \ |
||||
(is_bit_set(v, b8) << 8) | \ |
||||
(is_bit_set(v, b7) << 7) | \ |
||||
(is_bit_set(v, b6) << 6) | \ |
||||
(is_bit_set(v, b5) << 5) | \ |
||||
(is_bit_set(v, b4) << 4) | \ |
||||
(is_bit_set(v, b3) << 3) | \ |
||||
(is_bit_set(v, b2) << 2) | \ |
||||
(is_bit_set(v, b1) << 1) | \ |
||||
(is_bit_set(v, b0) << 0) |
||||
|
||||
|
||||
cpdef bytearray decrypt(unsigned char *data, int data_len, unsigned short key1, unsigned short key2, unsigned char key3): |
||||
cdef unsigned short v = 0 |
||||
cdef unsigned short m = 0 |
||||
cdef unsigned int idx = 0 |
||||
|
||||
output_data = bytearray(data_len * 2) |
||||
|
||||
while idx < data_len: |
||||
v = (data[idx * 2 + 1] << 8) | data[idx * 2] |
||||
m = key1 ^ key2 |
||||
|
||||
v = bit_swap( |
||||
v, |
||||
15 - is_bit_set(m, 0xF), |
||||
14 + is_bit_set(m, 0xF), |
||||
13 - is_bit_set(m, 0xE), |
||||
12 + is_bit_set(m, 0xE), |
||||
11 - is_bit_set(m, 0xB), |
||||
10 + is_bit_set(m, 0xB), |
||||
9 - is_bit_set(m, 0x9), |
||||
8 + is_bit_set(m, 0x9), |
||||
7 - is_bit_set(m, 0x8), |
||||
6 + is_bit_set(m, 0x8), |
||||
5 - is_bit_set(m, 0x5), |
||||
4 + is_bit_set(m, 0x5), |
||||
3 - is_bit_set(m, 0x3), |
||||
2 + is_bit_set(m, 0x3), |
||||
1 - is_bit_set(m, 0x2), |
||||
0 + is_bit_set(m, 0x2) |
||||
) |
||||
|
||||
|
||||
v ^= (is_bit_set(m, 0xD) << 14) ^ \ |
||||
(is_bit_set(m, 0xC) << 12) ^ \ |
||||
(is_bit_set(m, 0xA) << 10) ^ \ |
||||
(is_bit_set(m, 0x7) << 8) ^ \ |
||||
(is_bit_set(m, 0x6) << 6) ^ \ |
||||
(is_bit_set(m, 0x4) << 4) ^ \ |
||||
(is_bit_set(m, 0x1) << 2) ^ \ |
||||
(is_bit_set(m, 0x0) << 0) |
||||
|
||||
v ^= bit_swap( |
||||
key3, |
||||
7, 0, 6, 1, |
||||
5, 2, 4, 3, |
||||
3, 4, 2, 5, |
||||
1, 6, 0, 7 |
||||
) |
||||
|
||||
output_data[idx * 2] = (v >> 8) & 0xff |
||||
output_data[idx * 2 + 1] = v & 0xff |
||||
|
||||
key1 = ((key1 & 0x8000) | ((key1 << 1) & 0x7FFE) | ((key1 >> 14) & 1)) & 0xFFFF |
||||
|
||||
if (((key1 >> 15) ^ key1) & 1) != 0: |
||||
key2 = ((key2 << 1) | (key2 >> 15)) & 0xFFFF |
||||
|
||||
idx += 1 |
||||
key3 += 1 |
||||
|
||||
return output_data |
||||
|
||||
cpdef bytearray encrypt(unsigned char *data, int data_len, unsigned short key1, unsigned short key2, unsigned char key3): |
||||
cdef unsigned short v = 0 |
||||
cdef unsigned short m = 0 |
||||
cdef unsigned int idx = 0 |
||||
|
||||
output_data = bytearray(data_len * 2) |
||||
|
||||
while idx < data_len: |
||||
v = (data[idx * 2 + 1] << 8) | data[idx * 2] |
||||
m = key1 ^ key2 |
||||
|
||||
v = bit_swap( |
||||
v, |
||||
15 - is_bit_set(m, 0xF), |
||||
14 + is_bit_set(m, 0xF), |
||||
13 - is_bit_set(m, 0xE), |
||||
12 + is_bit_set(m, 0xE), |
||||
11 - is_bit_set(m, 0xB), |
||||
10 + is_bit_set(m, 0xB), |
||||
9 - is_bit_set(m, 0x9), |
||||
8 + is_bit_set(m, 0x9), |
||||
7 - is_bit_set(m, 0x8), |
||||
6 + is_bit_set(m, 0x8), |
||||
5 - is_bit_set(m, 0x5), |
||||
4 + is_bit_set(m, 0x5), |
||||
3 - is_bit_set(m, 0x3), |
||||
2 + is_bit_set(m, 0x3), |
||||
1 - is_bit_set(m, 0x2), |
||||
0 + is_bit_set(m, 0x2) |
||||
) |
||||
|
||||
|
||||
v ^= (is_bit_set(m, 0xD) << 14) ^ \ |
||||
(is_bit_set(m, 0xC) << 12) ^ \ |
||||
(is_bit_set(m, 0xA) << 10) ^ \ |
||||
(is_bit_set(m, 0x7) << 8) ^ \ |
||||
(is_bit_set(m, 0x6) << 6) ^ \ |
||||
(is_bit_set(m, 0x4) << 4) ^ \ |
||||
(is_bit_set(m, 0x1) << 2) ^ \ |
||||
(is_bit_set(m, 0x0) << 0) |
||||
|
||||
v ^= bit_swap( |
||||
key3, |
||||
3, 4, 2, 5, |
||||
1, 6, 0, 7, |
||||
7, 0, 6, 1, |
||||
5, 2, 4, 3 |
||||
) |
||||
|
||||
output_data[idx * 2] = (v >> 8) & 0xff |
||||
output_data[idx * 2 + 1] = v & 0xff |
||||
|
||||
key1 = ((key1 & 0x8000) | ((key1 << 1) & 0x7FFE) | ((key1 >> 14) & 1)) & 0xFFFF |
||||
|
||||
if (((key1 >> 15) ^ key1) & 1) != 0: |
||||
key2 = ((key2 << 1) | (key2 >> 15)) & 0xFFFF |
||||
|
||||
idx += 1 |
||||
key3 += 1 |
||||
|
||||
return output_data |
||||
|
||||
|
||||
cpdef bytearray decrypt_ddrsbm(unsigned char *data, int data_len, unsigned short key): |
||||
cdef unsigned int output_idx = 0 |
||||
cdef unsigned int idx = 0 |
||||
cdef unsigned int even_bit_shift = 0 |
||||
cdef unsigned int odd_bit_shift = 0 |
||||
cdef unsigned int is_even_bit_set = 0 |
||||
cdef unsigned int is_odd_bit_set = 0 |
||||
cdef unsigned int is_key_bit_set = 0 |
||||
cdef unsigned int is_scramble_bit_set = 0 |
||||
cdef unsigned int cur_bit = 0 |
||||
cdef unsigned int output_word = 0 |
||||
|
||||
output_data = bytearray(data_len * 2) |
||||
key_data = bytearray(16) |
||||
|
||||
# Generate key data based on input key |
||||
key_state = is_bit_set(key, 0x0d) << 15 | \ |
||||
is_bit_set(key, 0x0b) << 14 | \ |
||||
is_bit_set(key, 0x09) << 13 | \ |
||||
is_bit_set(key, 0x07) << 12 | \ |
||||
is_bit_set(key, 0x05) << 11 | \ |
||||
is_bit_set(key, 0x03) << 10 | \ |
||||
is_bit_set(key, 0x01) << 9 | \ |
||||
is_bit_set(key, 0x0f) << 8 | \ |
||||
is_bit_set(key, 0x0e) << 7 | \ |
||||
is_bit_set(key, 0x0c) << 6 | \ |
||||
is_bit_set(key, 0x0a) << 5 | \ |
||||
is_bit_set(key, 0x08) << 4 | \ |
||||
is_bit_set(key, 0x06) << 3 | \ |
||||
is_bit_set(key, 0x04) << 2 | \ |
||||
is_bit_set(key, 0x02) << 1 | \ |
||||
is_bit_set(key, 0x00) << 0 |
||||
|
||||
while idx < 8: |
||||
key_data[idx * 2] = key_state & 0xff |
||||
key_data[idx * 2 + 1] = (key_state >> 8) & 0xff |
||||
|
||||
key_state = (rot(key_state >> 8) << 8) | rot(key_state & 0xff) |
||||
|
||||
idx += 1 |
||||
|
||||
while idx < data_len: |
||||
output_word = 0 |
||||
cur_data = (data[(idx * 2) + 1] << 8) | data[(idx * 2)] |
||||
|
||||
cur_bit = 0 |
||||
while cur_bit < 8: |
||||
even_bit_shift = (cur_bit * 2) & 0xff |
||||
odd_bit_shift = (cur_bit * 2 + 1) & 0xff |
||||
|
||||
is_even_bit_set = int((cur_data & (1 << even_bit_shift)) != 0) |
||||
is_odd_bit_set = int((cur_data & (1 << odd_bit_shift)) != 0) |
||||
is_key_bit_set = int((key_data[idx % 16] & (1 << cur_bit)) != 0) |
||||
is_scramble_bit_set = int((key_data[(idx - 1) % 16] & (1 << cur_bit)) != 0) |
||||
|
||||
if is_scramble_bit_set == 1: |
||||
is_even_bit_set, is_odd_bit_set = is_odd_bit_set, is_even_bit_set |
||||
|
||||
if ((is_even_bit_set ^ is_key_bit_set)) == 1: |
||||
output_word |= 1 << even_bit_shift |
||||
|
||||
if is_odd_bit_set == 1: |
||||
output_word |= 1 << odd_bit_shift |
||||
|
||||
cur_bit += 1 |
||||
|
||||
output_data[output_idx] = (output_word >> 8) & 0xff |
||||
output_data[output_idx+1] = output_word & 0xff |
||||
output_idx += 2 |
||||
idx += 1 |
||||
|
||||
return output_data |
||||
|
@ -0,0 +1,326 @@ |
||||
import argparse |
||||
import hashlib |
||||
import json |
||||
import os |
||||
import sys |
||||
|
||||
DATABASE_FILENAME = "db.json" |
||||
|
||||
def get_database(filename=DATABASE_FILENAME): |
||||
db = json.load(open(filename)) |
||||
|
||||
output = {} |
||||
for sha1 in db: |
||||
k = db[sha1] |
||||
|
||||
if len(k) == 1: |
||||
output[sha1] = { |
||||
'sha1': sha1, |
||||
'key1': k[0], |
||||
} |
||||
|
||||
else: |
||||
output[sha1] = { |
||||
'sha1': sha1, |
||||
'key1': k[0], |
||||
'key2': k[1], |
||||
'key3': k[2] |
||||
} |
||||
|
||||
return output |
||||
|
||||
|
||||
|
||||
def get_key_information(sha1): |
||||
db = get_database() |
||||
sha1 = sha1.upper() |
||||
|
||||
song = db.get(sha1, None) |
||||
|
||||
if not song: |
||||
return (None, None, None) |
||||
|
||||
if 'key1' in song and 'key2' not in song and 'key3' not in song: |
||||
return (song['key1'], None, None) |
||||
|
||||
return (song['key1'], song['key2'], song['key3']) |
||||
|
||||
|
||||
def is_bit_set(value, n): |
||||
return (value >> n) & 1 |
||||
|
||||
|
||||
# Thanks SaxxonPike for helping with this one |
||||
def decrypt_ddrsbm(data, data_len, key): |
||||
def rot(c): |
||||
return ((c >> 7) & 1) | ((c << 1) & 0xff) |
||||
|
||||
key_state = is_bit_set(key, 0x0d) << 15 | \ |
||||
is_bit_set(key, 0x0b) << 14 | \ |
||||
is_bit_set(key, 0x09) << 13 | \ |
||||
is_bit_set(key, 0x07) << 12 | \ |
||||
is_bit_set(key, 0x05) << 11 | \ |
||||
is_bit_set(key, 0x03) << 10 | \ |
||||
is_bit_set(key, 0x01) << 9 | \ |
||||
is_bit_set(key, 0x0f) << 8 | \ |
||||
is_bit_set(key, 0x0e) << 7 | \ |
||||
is_bit_set(key, 0x0c) << 6 | \ |
||||
is_bit_set(key, 0x0a) << 5 | \ |
||||
is_bit_set(key, 0x08) << 4 | \ |
||||
is_bit_set(key, 0x06) << 3 | \ |
||||
is_bit_set(key, 0x04) << 2 | \ |
||||
is_bit_set(key, 0x02) << 1 | \ |
||||
is_bit_set(key, 0x00) << 0 |
||||
|
||||
key_data = bytearray(16) |
||||
for i in range(8): |
||||
key_data[i * 2] = key_state & 0xff |
||||
key_data[i * 2 + 1] = (key_state >> 8) & 0xff |
||||
|
||||
key_state = (rot(key_state >> 8) << 8) | rot(key_state & 0xff) |
||||
|
||||
scramble = bytearray([key_data[-1]]) + key_data[:-1] |
||||
|
||||
key_len = len(key_data) |
||||
scramble_len = len(scramble) |
||||
output_idx = 0 |
||||
|
||||
output_data = bytearray(len(data)) |
||||
for idx in range(0, data_len): |
||||
output_word = 0 |
||||
cur_data = (data[(idx * 2) + 1] << 8) | data[(idx * 2)] |
||||
|
||||
for cur_bit in range(0, 8): |
||||
even_bit_shift = (cur_bit * 2) & 0xff |
||||
odd_bit_shift = (cur_bit * 2 + 1) & 0xff |
||||
|
||||
is_even_bit_set = int((cur_data & (1 << even_bit_shift)) != 0) |
||||
is_odd_bit_set = int((cur_data & (1 << odd_bit_shift)) != 0) |
||||
is_key_bit_set = int((key_data[idx % key_len] & (1 << cur_bit)) != 0) |
||||
is_scramble_bit_set = int((scramble[idx % scramble_len] & (1 << cur_bit)) != 0) |
||||
|
||||
if is_scramble_bit_set == 1: |
||||
is_even_bit_set, is_odd_bit_set = is_odd_bit_set, is_even_bit_set |
||||
|
||||
if ((is_even_bit_set ^ is_key_bit_set)) == 1: |
||||
output_word |= 1 << even_bit_shift |
||||
|
||||
if is_odd_bit_set == 1: |
||||
output_word |= 1 << odd_bit_shift |
||||
|
||||
output_data[output_idx] = (output_word >> 8) & 0xff |
||||
output_data[output_idx+1] = output_word & 0xff |
||||
output_idx += 2 |
||||
|
||||
return bytearray(output_data) |
||||
|
||||
|
||||
# You crazy for this one |
||||
# Thanks anon and RC |
||||
def decrypt(data, data_len, key1, key2, key3): |
||||
def bit_swap(v, b15, b14, b13, b12, b11, b10, b9, b8, b7, b6, b5, b4, b3, b2, b1, b0): |
||||
return (is_bit_set(v, b15) << 15) | (is_bit_set(v, b14) << 14) | (is_bit_set(v, b13) << 13) | (is_bit_set(v, b12) << 12) |\ |
||||
(is_bit_set(v, b11) << 11) | (is_bit_set(v, b10) << 10) | (is_bit_set(v, b9) << 9) | (is_bit_set(v, b8) << 8) |\ |
||||
(is_bit_set(v, b7) << 7) | (is_bit_set(v, b6) << 6) | (is_bit_set(v, b5) << 5) | (is_bit_set(v, b4) << 4) |\ |
||||
(is_bit_set(v, b3) << 3) | (is_bit_set(v, b2) << 2) | (is_bit_set(v, b1) << 1) | (is_bit_set(v, b0) << 0) |
||||
|
||||
output_data = bytearray(len(data)) |
||||