Initial tools commit
This commit is contained in:
commit
29c72411f2
|
@ -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': 'パパパヤーぶっちゃけPOP’N大迷惑'},
|
||||
'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))
|
||||
|
||||
for idx in range(0, 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)
|
||||
)
|
||||
|
||||
for p in [(0x0d, 14), (0x0c, 12), (0x0a, 10), (0x07, 8), (0x06, 6), (0x04, 4), (0x01, 2), (0x00, 0)]:
|
||||
v ^= is_bit_set(m, p[0]) << p[1]
|
||||
|
||||
v &= 0xffff
|
||||
|
||||
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
|
||||
|
||||
key3 += 1
|
||||
|
||||
return bytearray(output_data)
|
||||
|
||||
|
||||
def encrypt(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))
|
||||
|
||||
for idx in range(0, 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)
|
||||
)
|
||||
|
||||
for p in [(0x0d, 14), (0x0c, 12), (0x0a, 10), (0x07, 8), (0x06, 6), (0x04, 4), (0x01, 2), (0x00, 0)]:
|
||||
v ^= is_bit_set(m, p[0]) << p[1]
|
||||
|
||||
v &= 0xffff
|
||||
|
||||
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
|
||||
|
||||
key3 += 1
|
||||
|
||||
return bytearray(output_data)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('--input', help='Input file', default=None)
|
||||
parser.add_argument('--output', help='Output file', default=None)
|
||||
parser.add_argument('--sha1', help='Force usage of a specific SHA-1 for encryption keys (optional)', default=None)
|
||||
|
||||
parser.add_argument('--key1', help='Key 1 (optional)', default=None, type=int)
|
||||
parser.add_argument('--key2', help='Key 2 (optional)', default=None, type=int)
|
||||
parser.add_argument('--key3', help='Key 3 (optional)', default=None, type=int)
|
||||
|
||||
parser.add_argument('--native', help='Native decryption code only', default=False, action='store_true')
|
||||
parser.add_argument('--encrypt', help='Encrypt input instead of decrypt (uses 0,0,0 as key)', default=False, action='store_true')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.input:
|
||||
parser.print_help(sys.stderr)
|
||||
exit(-1)
|
||||
|
||||
if not os.path.exists(args.input):
|
||||
print("Could not find file:", args.input)
|
||||
exit(-1)
|
||||
|
||||
if args.output is None:
|
||||
args.output = os.path.splitext(args.input)[0] + '.MP3'
|
||||
|
||||
if os.path.dirname(args.output) and not os.path.exists(os.path.dirname(args.output)):
|
||||
os.makedirs(os.path.dirname(args.output))
|
||||
|
||||
with open(args.input, "rb") as infile:
|
||||
data = infile.read()
|
||||
|
||||
if args.encrypt:
|
||||
key1 = 0
|
||||
key2 = 0
|
||||
key3 = 0
|
||||
|
||||
if args.native:
|
||||
decrypt_func = encrypt
|
||||
|
||||
else:
|
||||
import enc573
|
||||
decrypt_func = enc573.encrypt
|
||||
|
||||
else:
|
||||
key1 = args.key1
|
||||
key2 = args.key2
|
||||
key3 = args.key3
|
||||
|
||||
if key1 is None or key2 is None or key3 is None:
|
||||
sha1 = args.sha1
|
||||
if sha1 is None:
|
||||
m = hashlib.sha1()
|
||||
m.update(data)
|
||||
sha1 = m.hexdigest()
|
||||
|
||||
if sha1 is None:
|
||||
raise Exception("A SHA-1 must be set to continue")
|
||||
|
||||
print("Using SHA-1:", sha1)
|
||||
|
||||
key1, key2, key3 = get_key_information(sha1)
|
||||
if key1 is None:
|
||||
raise Exception("Couldn't find key information for file with SHA-1 hash of %s" % (sha1))
|
||||
|
||||
if args.native:
|
||||
decrypt_func, decrypt_ddrsbm_func = (decrypt, decrypt_ddrsbm)
|
||||
|
||||
else:
|
||||
import enc573
|
||||
decrypt_func, decrypt_ddrsbm_func = (enc573.decrypt, enc573.decrypt_ddrsbm)
|
||||
|
||||
if isinstance(key1, int) and isinstance(key2, int) and isinstance(key3, int):
|
||||
output_data = decrypt_func(data, len(data) // 2, key1, key2, key3)
|
||||
|
||||
else:
|
||||
output_data = decrypt_ddrsbm_func(data, len(data) // 2, key1)
|
||||
|
||||
with open(args.output, "wb") as outfile:
|
||||
outfile.write(output_data)
|
||||
|
||||
print("Saved to", args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,6 @@
|
|||
from distutils.core import setup
|
||||
from Cython.Build import cythonize
|
||||
|
||||
setup(
|
||||
ext_modules = cythonize(["enc573.pyx"], annotate=True, language_level=3)
|
||||
)
|
|
@ -0,0 +1,57 @@
|
|||
# sys573tool
|
||||
|
||||
This is a combination of `dump_sys573_gamefs.py`, `build_sys573_gamefs.py`, and `calc_checksum.py`.
|
||||
|
||||
```
|
||||
usage: sys573tool.py [-h] --mode {dump,build,checksum}
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--mode {dump,build,checksum}
|
||||
Operation mode
|
||||
```
|
||||
|
||||
Used to dump a game's data from .DATs:
|
||||
```
|
||||
usage: dump_sys573_gamefs.py [-h] --input INPUT [--output OUTPUT]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--input INPUT Input folder
|
||||
--output OUTPUT Output folder
|
||||
```
|
||||
|
||||
Used to rebuild a game's .DAT files (experimental, most likely won't work for you without modification for your use case):
|
||||
```
|
||||
usage: build_sys573_gamefs.py [-h] --input INPUT [--input-modified-list INPUT_MODIFIED_LIST] --base BASE
|
||||
[--output OUTPUT] --key {EXTREME,EURO2,MAX2,DDR5,MAMBO} [--override-edit-section]
|
||||
[--patch-dir PATCH_DIR]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--input INPUT Input folder
|
||||
--input-modified-list INPUT_MODIFIED_LIST
|
||||
Input modified list
|
||||
--base BASE Base file folder
|
||||
--output OUTPUT Output file
|
||||
--key {EXTREME,EURO2,MAX2,DDR5,MAMBO}
|
||||
Encryption key
|
||||
--override-edit-section
|
||||
Allows use of end of CARD 2 which would otherwise be used for edit data saved to flash card.
|
||||
REQUIRED ENABLE_EDIT_SECTOR_OVERRIDE ENABLED IN ASM PATCHES!
|
||||
--patch-dir PATCH_DIR
|
||||
Path to use for patch files
|
||||
```
|
||||
|
||||
Used to fix the checksums in the .DAT file after editing data:
|
||||
```
|
||||
usage: calc_checksum.py [-h] --input INPUT [INPUT ...] [--output OUTPUT]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--input INPUT [INPUT ...]
|
||||
Input DAT file (list all in order)
|
||||
--output OUTPUT Output folder
|
||||
```
|
||||
|
||||
For a better example of how to use these tools, check out https://github.com/987123879113/ddr5thmix-solo.
|
|
@ -0,0 +1,429 @@
|
|||
import argparse
|
||||
import copy
|
||||
import ctypes
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import struct
|
||||
import string
|
||||
|
||||
import comp573
|
||||
import sum573
|
||||
|
||||
|
||||
def rebuild_checksum_table(cards):
|
||||
card_sizes = [len(x) // 0x8000 for x in cards]
|
||||
|
||||
CHUNK_SIZE = 0x20000
|
||||
LAST_CHUNK_OFFSET = len(cards[0]) - CHUNK_SIZE
|
||||
LAST_CHUNK_CHECKSUM_OFFSET = LAST_CHUNK_OFFSET + 0x10
|
||||
|
||||
# Set entire checksum.dat section to zero
|
||||
cards[0] = cards[0][:LAST_CHUNK_CHECKSUM_OFFSET] + bytearray([0] * 0x1ff0) + cards[0][LAST_CHUNK_OFFSET + 0x2000:]
|
||||
|
||||
# Calculate checksums for GAME.DAT
|
||||
cards = sum573.add_checksums(cards, card_sizes, CHUNK_SIZE, LAST_CHUNK_CHECKSUM_OFFSET, 0, 1)
|
||||
|
||||
# Balance out the sums at this point because otherwise the chunk checksum won't match
|
||||
cards = sum573.balance_sums(cards, card_sizes, LAST_CHUNK_OFFSET)
|
||||
|
||||
# Set the real checksum of the last section finally
|
||||
table_checksum_idx = len(cards[0]) // CHUNK_SIZE
|
||||
table_checksum_offset = LAST_CHUNK_CHECKSUM_OFFSET + ((table_checksum_idx - 1) * 4)
|
||||
cards[0][table_checksum_offset:table_checksum_offset+4] = sum573.checksum_chunk(cards[0], LAST_CHUNK_OFFSET, CHUNK_SIZE)
|
||||
|
||||
# Add checksums for other DATs now
|
||||
cards = sum573.add_checksums(cards, card_sizes, CHUNK_SIZE, LAST_CHUNK_CHECKSUM_OFFSET, 1, len(cards) - 1)
|
||||
|
||||
sum573.balance_sums(cards, card_sizes, LAST_CHUNK_OFFSET)
|
||||
|
||||
|
||||
def get_filename_hash(filename, entry):
|
||||
hash = 0
|
||||
|
||||
if 'filename_hash' in entry:
|
||||
return entry['filename_hash']
|
||||
|
||||
if filename.startswith("_output_") and filename.endswith(".bin"):
|
||||
hash = int(filename.replace("_output_", "").replace(".bin", ""), 16)
|
||||
return hash
|
||||
|
||||
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
|
||||
|
||||
hash &= 0xffffffff
|
||||
|
||||
return hash
|
||||
|
||||
|
||||
def encrypt_data(data, input_key):
|
||||
def calculate_key(input):
|
||||
key = 0
|
||||
|
||||
for c in input.upper():
|
||||
if c in string.ascii_uppercase:
|
||||
key -= 0x37
|
||||
|
||||
elif c in string.ascii_lowercase:
|
||||
key -= 0x57
|
||||
|
||||
elif c in string.digits:
|
||||
key -= 0x30
|
||||
|
||||
key += ord(c)
|
||||
|
||||
return key & 0xff
|
||||
|
||||
val = 0x41C64E6D
|
||||
key1 = (val * calculate_key(input_key)) & 0xffffffff
|
||||
counter = 0
|
||||
|
||||
for idx, c in enumerate(data):
|
||||
val = ((key1 + counter) >> 5) ^ c
|
||||
data[idx] = val & 0xff
|
||||
counter += 0x3039
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_filetable(input_folder, input_modified_list, patch_dir=""):
|
||||
entries = []
|
||||
new_entries = []
|
||||
|
||||
if input_modified_list and os.path.exists(input_modified_list):
|
||||
new_entries += json.load(open(input_modified_list))
|
||||
|
||||
metadata_path = os.path.join(input_folder, "_metadata.json")
|
||||
if os.path.exists(metadata_path):
|
||||
entries = json.load(open(metadata_path)).get('files', [])
|
||||
|
||||
for entry in json.load(open(metadata_path)).get('modified', []):
|
||||
exists = False
|
||||
for new_entry in new_entries:
|
||||
if entry['filename'] == new_entry['filename']:
|
||||
exists = True
|
||||
|
||||
if not exists:
|
||||
new_entries.append(entry)
|
||||
|
||||
new_entry_filenames = []
|
||||
if new_entries:
|
||||
for entry in new_entries:
|
||||
entry['_path'] = os.path.join(input_folder, entry['filename'])
|
||||
entry['filename_hash'] = get_filename_hash(entry['filename'], entry)
|
||||
entry['_modified'] = True
|
||||
|
||||
if entry.get('patch', None) is not None:
|
||||
entry['patch'] = os.path.join(patch_dir, entry['patch'])
|
||||
|
||||
new_entry_filenames.append(entry['filename'])
|
||||
|
||||
if entries:
|
||||
for entry in entries:
|
||||
entry['_path'] = os.path.join(input_folder, entry['filename'])
|
||||
entry['filename_hash'] = get_filename_hash(entry['filename'], entry)
|
||||
|
||||
if entry.get('patch', None) is not None:
|
||||
entry['patch'] = os.path.join(patch_dir, entry['patch'])
|
||||
|
||||
# # Free some space by removing any MP3s actually inside the flash card by default
|
||||
# if entry['filename'].startswith("data/mp3/enc"):
|
||||
# entry['_free'] = True
|
||||
|
||||
# # Free even more space by removing all of the data in the mdb folder besides the mdb.bin
|
||||
# if entry['filename'].startswith("data/mdb/") and entry['filename'] not in ['data/mdb/mdb.bin']:
|
||||
# entry['_free'] = True
|
||||
|
||||
# Free up the space used by files that will be overwritten
|
||||
if entry['filename'] in new_entry_filenames:
|
||||
for entry2 in new_entries:
|
||||
if entry2['filename'] == entry['filename']:
|
||||
size = entry['filesize']
|
||||
|
||||
if (size % 0x800) != 0:
|
||||
padding = 0x800 - (size % 0x800)
|
||||
|
||||
else:
|
||||
padding = 0x800
|
||||
|
||||
entry2['_orig_filesize'] = entry['filesize'] + padding
|
||||
|
||||
break
|
||||
|
||||
entry['_free'] = True
|
||||
|
||||
return sorted(entries + new_entries, key=lambda x: x['filename_hash'])
|
||||
|
||||
|
||||
def get_data_from_entry(entry):
|
||||
data = open(os.path.normpath(entry['_path']), "rb").read()
|
||||
|
||||
if entry.get('patch', None) is not None:
|
||||
if entry.get('patch_format') == "bsdiff4":
|
||||
import bsdiff4
|
||||
data = bsdiff4.patch(data, open(entry['patch'], "rb").read())
|
||||
|
||||
return bytearray(data)
|
||||
|
||||
|
||||
def create_gamedata(entries, base_offset, memory, enc_key, override_edit_section):
|
||||
# You can modify this to default to unused and you can probably squeeze a little bit more data
|
||||
# into the cards, but you will almost surely run over some data you shouldn't touch so be careful.
|
||||
memory_map = [bytearray([1] * len(mem)) for mem in memory] # 0 = unused, 1 = used
|
||||
memory_map[0][:0x200000] = [1] * 0x200000 # Reserve this section for the program code
|
||||
memory_map[1][0x18c0000:0x1b66800] = [1] * (0x1b66800 - 0x18c0000) # Reserve this section because it's where system sounds reside (not in actual file table)
|
||||
|
||||
if override_edit_section:
|
||||
memory_map[1][0x1b66800:0x2000000] = [0] * (0x2000000 - 0x1b66800) # Unreserve the space where edit data is normally stored
|
||||
|
||||
# Find the data
|
||||
entries_work = entries[::]
|
||||
|
||||
# Mark unmodified data as used and freed data as unused
|
||||
for entry in entries_work[::]:
|
||||
if entry.get('_modified', False):
|
||||
continue
|
||||
|
||||
cur_memory = entry['offset']
|
||||
|
||||
size = entry['filesize']
|
||||
if (size % 0x800) != 0:
|
||||
size += 0x800 - (size % 0x800)
|
||||
|
||||
if entry.get('_free', False):
|
||||
memory_map[entry.get('flag_loc', 0)][cur_memory:cur_memory + size] = [0] * size
|
||||
|
||||
else:
|
||||
memory_map[entry.get('flag_loc', 0)][cur_memory:cur_memory + size] = [1] * size
|
||||
|
||||
entries_work.remove(entry)
|
||||
|
||||
entries_work = [x for x in entries_work if not x.get('_free', False)]
|
||||
entries = [x for x in entries if not x.get('_free', False)]
|
||||
|
||||
entries_work_priority = [x for x in entries_work if x['filename'] in ['data/mp3/mp3_tab.bin', 'data/mdb/mdb.bin'] or x['filename'].startswith("boot/") or x['filename'].startswith("soft/")]
|
||||
entries_work = entries_work_priority + [x for x in entries_work if x not in entries_work_priority]
|
||||
|
||||
data_hashes = {}
|
||||
used_addresses = []
|
||||
cur_memory = 0
|
||||
|
||||
# Certain files need to be at specific offsets or else they won't work properly (seemingly, maybe it's a bug with my code somewhere)
|
||||
# so put any "important" files where they should be for the most part
|
||||
for entry in entries_work[::]:
|
||||
if not entry.get('_modified', False):
|
||||
continue
|
||||
|
||||
if not entry['filename'].startswith('boot/') and not entry['filename'].startswith('soft/') and not entry['filename'].startswith('data/fpga/'):
|
||||
continue
|
||||
|
||||
if 'offset' in entry:
|
||||
cur_memory = entry['offset']
|
||||
|
||||
data = get_data_from_entry(entry)
|
||||
|
||||
if entry.get('flag_comp', 0) == 1:
|
||||
data = comp573.encode_lz(data, len(data))
|
||||
|
||||
if entry.get('flag_enc', 0) == 1:
|
||||
data = encrypt_data(data, enc_key)
|
||||
|
||||
entry['filesize'] = len(data)
|
||||
|
||||
datahash = hashlib.sha1(data).hexdigest()
|
||||
data_hashes[datahash] = {
|
||||
'offset': cur_memory,
|
||||
'filesize': entry['filesize'],
|
||||
'loc': entry.get('flag_loc', 0),
|
||||
}
|
||||
|
||||
if len(data) > entry['filesize']:
|
||||
print("Filesize is too large: %08x vs %08x" % (len(data), entry['filesize']))
|
||||
|
||||
elif len(data) < entry['filesize']:
|
||||
entry['filesize'] = len(data)
|
||||
|
||||
# Pad with 0xff
|
||||
data += b'\xff' * (entry['filesize'] - len(data))
|
||||
|
||||
size = entry['filesize']
|
||||
padding = 0
|
||||
if (size % 0x800) != 0:
|
||||
padding = 0x800 - (size % 0x800)
|
||||
|
||||
else:
|
||||
padding = 0x800
|
||||
|
||||
size += padding
|
||||
|
||||
memory[entry.get('flag_loc', 0)][cur_memory + entry['filesize']:cur_memory + entry['filesize'] + padding] = bytearray([0xff] * padding)
|
||||
memory[entry.get('flag_loc', 0)][cur_memory:cur_memory + entry['filesize']] = data
|
||||
memory_map[entry.get('flag_loc', 0)][cur_memory:cur_memory + entry['filesize']] = [1] * size
|
||||
|
||||
entries_work.remove(entry)
|
||||
used_addresses.append((entry.get('flag_loc', 0), cur_memory))
|
||||
|
||||
# For everything else, just try to find a fitting space in the available areas
|
||||
entries_len = len(entries_work[::])
|
||||
for entry_idx, entry in enumerate(entries_work[::]):
|
||||
if not entry.get('_modified', False):
|
||||
continue
|
||||
|
||||
data = get_data_from_entry(entry)
|
||||
orig_len = len(data)
|
||||
|
||||
if entry.get('flag_comp', 0) == 1:
|
||||
data = comp573.encode_lz(data, len(data))
|
||||
|
||||
if entry.get('flag_enc', 0) == 1:
|
||||
data = encrypt_data(data, enc_key)
|
||||
|
||||
datahash = hashlib.sha1(data).hexdigest()
|
||||
|
||||
size = len(data)
|
||||
padding = 0
|
||||
if (size % 0x800) != 0:
|
||||
padding = 0x800 - (size % 0x800)
|
||||
|
||||
else:
|
||||
padding = 0x800
|
||||
|
||||
is_dupe = False
|
||||
if datahash in data_hashes:
|
||||
cur_memory = data_hashes[datahash]['offset']
|
||||
loc = data_hashes[datahash]['loc']
|
||||
is_dupe = True
|
||||
|
||||
else:
|
||||
# This code is not optimized. It sucks but it works.
|
||||
# The general idea I was trying to implement is to find the first
|
||||
# string of 0s in the memory map that could fit the size of the data
|
||||
# padded to the nearest sector (0x800).
|
||||
# The padding is key because if you can't clear out the sector properly
|
||||
# then the game has a higher chance of crashing for some reason.
|
||||
# Possibly due to decompression reading in garbage data as compressed data.
|
||||
for loc in range(0, len(memory_map)):
|
||||
loc = entry.get('flag_loc', 1)
|
||||
|
||||
cur_memory = 0
|
||||
|
||||
while cur_memory < len(memory_map[loc]):
|
||||
if (cur_memory % 0x800) != 0:
|
||||
cur_memory += (0x800 - (cur_memory % 0x800))
|
||||
|
||||
idx = memory_map[loc].find(0, cur_memory)
|
||||
|
||||
if idx == -1:
|
||||
cur_memory = -1
|
||||
break
|
||||
|
||||
if (idx % 0x800) != 0:
|
||||
cur_memory = idx + 1
|
||||
continue
|
||||
|
||||
cur_memory = idx
|
||||
idx = memory_map[loc].find(1, cur_memory, cur_memory + len(data) + padding)
|
||||
|
||||
if idx != -1:
|
||||
cur_memory = idx
|
||||
|
||||
if (idx % 0x800) != 0:
|
||||
cur_memory += 0x800 - (idx % 0x800)
|
||||
|
||||
continue
|
||||
|
||||
else:
|
||||
break
|
||||
|
||||
if cur_memory > len(memory_map[loc]):
|
||||
cur_memory = -1
|
||||
break
|
||||
|
||||
if cur_memory > len(memory_map[loc]):
|
||||
cur_memory = - 1
|
||||
continue
|
||||
|
||||
if loc == 0 and cur_memory + len(data) + padding >= base_offset:
|
||||
continue
|
||||
|
||||
if cur_memory > 0:
|
||||
break
|
||||
|
||||
if cur_memory == -1 or (loc == 0 and cur_memory + len(data) + padding >= base_offset):
|
||||
print("Couldn't find position for %08x" % len(data), entry)
|
||||
exit(1)
|
||||
|
||||
|
||||
print("%d / %d: Placing %08x @ %08x in card %d for %s" % (entry_idx, entries_len, len(data), cur_memory, loc, entry['filename']))
|
||||
|
||||
if not is_dupe and (loc, cur_memory) in used_addresses:
|
||||
print("Can't reuse address!")
|
||||
exit(1)
|
||||
|
||||
size += padding
|
||||
memory[loc][cur_memory:cur_memory + len(data)] = data
|
||||
memory[loc][cur_memory + len(data):cur_memory + len(data) + padding] = bytearray([0xff] * padding)
|
||||
memory_map[loc][cur_memory:cur_memory + len(data) + padding] = [1] * size
|
||||
|
||||
entry['filesize'] = len(data)
|
||||
used_addresses.append((loc, cur_memory))
|
||||
|
||||
data_hashes[datahash] = {
|
||||
'offset': cur_memory,
|
||||
'filesize': entry['filesize'],
|
||||
'loc': loc,
|
||||
}
|
||||
|
||||
# Update master entries for the file table lazily
|
||||
for e in entries:
|
||||
if e == entry:
|
||||
e['offset'] = cur_memory
|
||||
|
||||
entries_work.remove(entry)
|
||||
|
||||
entries = sorted(entries, key=lambda x: x['filename_hash'])
|
||||
|
||||
for idx, entry in enumerate(entries):
|
||||
print("%08x" % entry['filename_hash'], entry)
|
||||
memory[0][base_offset + 0x4000 + (idx * 0x10):base_offset + 0x4000 + ((idx + 1) * 0x10)] = struct.pack("<IHHBBHI", entry['filename_hash'], entry['offset'] // 0x800, entry.get('flag_loc', 0), entry.get('flag_comp', 0), entry.get('flag_enc', 0), entry.get('unk', 0), entry['filesize'])
|
||||
|
||||
idx = len(entries)
|
||||
memory[0][base_offset + 0x4000 + (idx * 0x10):base_offset + 0x4000 + ((idx + 1) * 0x10)] = struct.pack("<IIII", 0, 0, 0, 0)
|
||||
|
||||
return memory
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('--input', help='Input folder', default=None, required=True)
|
||||
parser.add_argument('--input-modified-list', help='Input modified list', default=None)
|
||||
parser.add_argument('--base', help='Base file folder', default=None, required=True)
|
||||
parser.add_argument('--output', help='Output file', default="output")
|
||||
parser.add_argument('--key', help='Encryption key', choices=['EXTREME', 'EURO2', 'MAX2', 'DDR5', 'MAMBO'], required=True)
|
||||
parser.add_argument('--override-edit-section', help='Allows use of end of CARD 2 which would otherwise be used for edit data saved to flash card. REQUIRED ENABLE_EDIT_SECTOR_OVERRIDE ENABLED IN ASM PATCHES!', default=False, action='store_true')
|
||||
parser.add_argument('--patch-dir', help='Path to use for patch files', default="")
|
||||
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
os.makedirs(args.output, exist_ok=True)
|
||||
|
||||
# Settings are specific to DDR Extreme for now
|
||||
basefileinfo = [("GAME.DAT", 16), ("CARD.DAT", 32)]
|
||||
base_offset = 0xFE0000
|
||||
filetable = get_filetable(args.input, args.input_modified_list, args.patch_dir)
|
||||
|
||||
card_datas = create_gamedata(filetable, base_offset, [bytearray(open(os.path.join(args.base, info[0]), "rb").read()) for info in basefileinfo], args.key, args.override_edit_section)
|
||||
card_datas = [bytearray(data)[:basefileinfo[i][1] * 1024 * 1024] for i, data in enumerate(card_datas)]
|
||||
|
||||
rebuild_checksum_table(card_datas)
|
||||
|
||||
for i, data in enumerate(card_datas):
|
||||
open(os.path.join(args.output, basefileinfo[i][0]), "wb").write(data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,86 @@
|
|||
import argparse
|
||||
import os
|
||||
|
||||
import sum573
|
||||
|
||||
|
||||
def rebuild_checksum_table(cards):
|
||||
card_sizes = [len(x) // 0x8000 for x in cards]
|
||||
|
||||
CHUNK_SIZE = 0x20000
|
||||
LAST_CHUNK_OFFSET = len(cards[0]) - CHUNK_SIZE
|
||||
LAST_CHUNK_CHECKSUM_OFFSET = LAST_CHUNK_OFFSET + 0x10
|
||||
|
||||
# Set entire checksum.dat section to zero
|
||||
cards[0] = cards[0][:LAST_CHUNK_CHECKSUM_OFFSET] + bytearray([0] * 0x1ff0) + cards[0][LAST_CHUNK_OFFSET + 0x2000:]
|
||||
|
||||
# Calculate checksums for GAME.DAT
|
||||
cards = sum573.add_checksums(cards, card_sizes, CHUNK_SIZE, LAST_CHUNK_CHECKSUM_OFFSET, 0, 1)
|
||||
|
||||
# Balance out the sums at this point because otherwise the chunk checksum won't match
|
||||
cards = sum573.balance_sums(cards, card_sizes, LAST_CHUNK_OFFSET)
|
||||
|
||||
# Set the real checksum of the last section finally
|
||||
table_checksum_idx = len(cards[0]) // CHUNK_SIZE
|
||||
table_checksum_offset = LAST_CHUNK_CHECKSUM_OFFSET + ((table_checksum_idx - 1) * 4)
|
||||
cards[0][table_checksum_offset:table_checksum_offset+4] = sum573.checksum_chunk(cards[0], LAST_CHUNK_OFFSET, CHUNK_SIZE)
|
||||
|
||||
# Add checksums for other DATs now
|
||||
cards = sum573.add_checksums(cards, card_sizes, CHUNK_SIZE, LAST_CHUNK_CHECKSUM_OFFSET, 1, len(cards) - 1)
|
||||
|
||||
sum573.balance_sums(cards, card_sizes, LAST_CHUNK_OFFSET)
|
||||
|
||||
|
||||
def verify_checksums(cards):
|
||||
card_sizes = [len(x) // 0x8000 for x in cards]
|
||||
|
||||
chunk_size = 0x20000
|
||||
last_chunk_offset = len(cards[0]) - chunk_size
|
||||
last_chunk_checksum_offset = last_chunk_offset + 0x10
|
||||
|
||||
checksums = [int.from_bytes(cards[0][last_chunk_checksum_offset+x:last_chunk_checksum_offset+x+4], 'little') for x in range(0, 0x2000, 4)]
|
||||
|
||||
is_valid = True
|
||||
for real_card_index, card_data in enumerate(cards):
|
||||
for i in range(0, len(card_data) // chunk_size):
|
||||
offset = (i * chunk_size) + (0x20 if real_card_index == 0 and i == 0 else 0)
|
||||
length = chunk_size - (0x20 if real_card_index == 0 and i == 0 else 0)
|
||||
checksum_bytes = int.from_bytes(sum573.checksum_chunk(card_data, offset, length), 'little')
|
||||
|
||||
target_checksum = checksums[i + (sum(card_sizes[:real_card_index]) // 4)]
|
||||
if checksum_bytes != target_checksum:
|
||||
print("Sector %d of DAT %d is invalid! %08x vs %08x" % (i, real_card_index, checksum_bytes, target_checksum))
|
||||
is_valid = False
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('--input', help='Input DAT file (list all in order)', nargs='+', required=True)
|
||||
parser.add_argument('--output', help='Output folder', default="output")
|
||||
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
cards = [bytearray(open(x, "rb").read()) for x in args.input]
|
||||
|
||||
for x in args.input:
|
||||
print(x)
|
||||
|
||||
is_valid = verify_checksums(cards)
|
||||
print("Is checksum table valid?", is_valid)
|
||||
|
||||
if not is_valid:
|
||||
rebuild_checksum_table(cards)
|
||||
|
||||
is_valid = verify_checksums(cards)
|
||||
print("Is checksum table valid?", is_valid)
|
||||
|
||||
os.makedirs(args.output, exist_ok=True)
|
||||
for i, x in enumerate(args.input):
|
||||
open(os.path.join(args.output, os.path.basename(x)), "wb").write(cards[i])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,423 @@
|
|||
# cython: cdivision=True
|
||||
# distutils: language=c++
|
||||
|
||||
from libc.stdint cimport uint8_t
|
||||
|
||||
cdef extern from *:
|
||||
"""
|
||||
template <typename T>
|
||||
T* array_new(int n) {
|
||||
return new T[n];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void array_delete(T* x) {
|
||||
delete [] x;
|
||||
}
|
||||
"""
|
||||
T* array_new[T](int)
|
||||
void array_delete[T](T* x)
|
||||
|
||||
from libcpp.vector cimport vector
|
||||
|
||||
|
||||
cdef inline int find_data(unsigned char *data, int data_len, unsigned char c, int offset):
|
||||
while offset < data_len:
|
||||
if data[offset] == c:
|
||||
return offset
|
||||
|
||||
offset += 1
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
cpdef bytearray decode_lz(unsigned char *input_data, int data_len):
|
||||
cdef bytearray output = bytearray()
|
||||
cdef int idx = 0
|
||||
cdef int idx2 = 0
|
||||
cdef int start_offset = 0
|
||||
cdef int distance = 0
|
||||
cdef int control = 0
|
||||
cdef unsigned char data = 0
|
||||
cdef int length = 0
|
||||
|
||||
while True:
|
||||
control >>= 1
|
||||
|
||||
if (control & 0x100) == 0:
|
||||
control = input_data[idx] | 0xff00
|
||||
idx += 1
|
||||
|
||||
data = input_data[idx]
|
||||
idx += 1
|
||||
|
||||
if (control & 1) == 0:
|
||||
output.append(data)
|
||||
continue
|
||||
|
||||
# print("idx: %02x" % idx)
|
||||
|
||||
length = -1
|
||||
if (data & 0x80) == 0:
|
||||
distance = ((data & 0x03) << 8) | input_data[idx]
|
||||
length = (data >> 2) + 2
|
||||
idx += 1
|
||||
|
||||
elif (data & 0x40) == 0:
|
||||
distance = (data & 0x0f) + 1
|
||||
length = (data >> 4) - 7
|
||||
|
||||
# print("%04x %02x %02x" % (control, data, input_data[idx-1]), distance, length)
|
||||
|
||||
if length != -1:
|
||||
start_offset = len(output)
|
||||
idx2 = 0
|
||||
|
||||
while idx2 <= length:
|
||||
output.append(output[(start_offset - distance) + idx2])
|
||||
idx2 += 1
|
||||
|
||||
continue
|
||||
|
||||
if data == 0xff:
|
||||
break
|
||||
|
||||
length = data - 0xb9
|
||||
# print("%02x %02x" % (data, length))
|
||||
while length >= 0:
|
||||
output.append(input_data[idx])
|
||||
idx += 1
|
||||
length -= 1
|
||||
|
||||
return output
|
||||
|
||||
|
||||
cpdef bytearray decode_lz0(unsigned char *data, int data_len):
|
||||
cdef bytearray output = bytearray()
|
||||
cdef int base_data_idx = 0
|
||||
cdef int data_idx = 0
|
||||
cdef int output_idx = 0
|
||||
cdef unsigned char cur_byte = 0
|
||||
cdef int cur_bit = 1
|
||||
|
||||
print("decode_lz0 called")
|
||||
|
||||
while True:
|
||||
while True:
|
||||
cur_bit -= 1
|
||||
data_idx = base_data_idx
|
||||
|
||||
if cur_bit == 0:
|
||||
cur_byte = data[base_data_idx]
|
||||
data_idx = base_data_idx + 1
|
||||
cur_bit = 8
|
||||
|
||||
if (cur_byte & 1) != 0:
|
||||
break
|
||||
|
||||
cur_byte >>= 1
|
||||
base_data_idx = data_idx + 1
|
||||
output.append(data[data_idx])
|
||||
output_idx += 1
|
||||
|
||||
cur_bit -= 1
|
||||
cur_byte >>= 1
|
||||
|
||||
if cur_bit == 0:
|
||||
cur_byte = data[data_idx]
|
||||
data_idx += 1
|
||||
cur_bit = 8
|
||||
|
||||
if (cur_byte & 1) == 0:
|
||||
uVar2 = (data[data_idx] << 8) | data[data_idx+1]
|
||||
cur_byte >>= 1
|
||||
|
||||
if uVar2 == 0:
|
||||
return output
|
||||
|
||||
base_data_idx = data_idx + 2
|
||||
if (data[data_idx+1] & 0x0f) == 0:
|
||||
bVar1 = data[base_data_idx]
|
||||
base_data_idx = data_idx + 3
|
||||
iVar3 = bVar1 + 1
|
||||
|
||||
else:
|
||||
iVar3 = (uVar2 & 0x0f) + 2
|
||||
|
||||
uVar4 = (uVar2 >> 4)
|
||||
|
||||
else:
|
||||
cur_bit -= 1
|
||||
cur_byte >>= 1
|
||||
|
||||
if cur_bit == 0:
|
||||
cur_byte = data[data_idx]
|
||||
data_idx += 1
|
||||
cur_bit = 8
|
||||
|
||||
cur_bit -= 1
|
||||
uVar4 = cur_byte >> 1
|
||||
|
||||
if cur_bit == 0:
|
||||
uVar4 = data[data_idx]
|
||||
data_idx += 1
|
||||
cur_bit = 8
|
||||
|
||||
iVar3 = (cur_byte & 1) * 2 + 2 + (uVar4 & 1)
|
||||
cur_byte = uVar4 >> 1
|
||||
uVar4 = data[data_idx]
|
||||
base_data_idx = data_idx + 1
|
||||
|
||||
if data[data_idx] == 0:
|
||||
uVar4 = 0x100
|
||||
|
||||
while iVar3 != 0:
|
||||
bVar1 = output[output_idx-uVar4]
|
||||
iVar3 -= 1
|
||||
output.append(bVar1)
|
||||
output_idx += 1
|
||||
|
||||
return output
|
||||
|
||||
|
||||
cpdef bytearray encode_lz(unsigned char *data, int data_len):
|
||||
cdef uint8_t *output = array_new[uint8_t](data_len * 2)
|
||||
cdef int output_len = 0
|
||||
cdef int i = 0
|
||||
cdef int j = 0
|
||||
cdef int v = 0
|
||||
cdef int run_length = 0
|
||||
cdef int last_history_idx = 0
|
||||
cdef int history_idx = 0
|
||||
cdef int cmd_offset = 0
|
||||
cdef int cmd_bit = 0
|
||||
|
||||
cdef int offset = 0
|
||||
cdef list compress_commands = []
|
||||
cdef list history_commands = []
|
||||
|
||||
# Compress runs of previous characters
|
||||
while offset < data_len:
|
||||
# Run detection
|
||||
if output_len > 0 and data[offset] == output[output_len-1]:
|
||||
c = output[output_len-1]
|
||||
run_length = 1
|
||||
|
||||
while offset + run_length < data_len and data[offset+run_length] == c and run_length < 0x21:
|
||||
run_length += 1
|
||||
|
||||
if run_length > 1:
|
||||
compress_commands.append([
|
||||
'repeat',
|
||||
run_length
|
||||
])
|
||||
|
||||
j = 0
|
||||
while j < run_length:
|
||||
output[output_len] = c
|
||||
output_len += 1
|
||||
j += 1
|
||||
|
||||
offset += run_length
|
||||
continue
|
||||
|
||||
# History check
|
||||
last_history_idx = max(output_len - 0x400, 0)
|
||||
history_idx = find_data(output, output_len, data[offset], last_history_idx)
|
||||
if history_idx != -1:
|
||||
history_commands.clear()
|
||||
|
||||
while True:
|
||||
history_idx = find_data(output, output_len, data[offset], last_history_idx)
|
||||
last_history_idx = history_idx + 1
|
||||
|
||||
if history_idx == -1:
|
||||
break
|
||||
|
||||
# Check how long we can match the history
|
||||
i = 1
|
||||
|
||||
while offset + i < data_len:
|
||||
if history_idx + i > output_len and output[-1] == data[offset+i]:
|
||||
# Copy + repeat
|
||||
i += 1
|
||||
|
||||
elif history_idx + i < output_len and output[history_idx+i] == data[offset+i]:
|
||||
i += 1
|
||||
|
||||
else:
|
||||
break
|
||||
|
||||
history_back_idx = output_len - history_idx
|
||||
if i in [1, 2, 3, 4] and history_back_idx >= 1 and history_back_idx <= 16:
|
||||
# Can use a short copy
|
||||
history_commands.append([
|
||||
'short_copy',
|
||||
history_back_idx,
|
||||
i,
|
||||
1
|
||||
])
|
||||
|
||||
elif history_back_idx <= 0x3ff and i >= 3 and i <= 0x21:
|
||||
# Can use a long copy
|
||||
history_commands.append([
|
||||
'long_copy',
|
||||
history_back_idx,
|
||||
i,
|
||||
2
|
||||
])
|
||||
|
||||
best_compression = None
|
||||
for x in history_commands:
|
||||
if best_compression is None or x[2] - x[3] >= best_compression[2] - best_compression[3]:
|
||||
best_compression = x
|
||||
|
||||
if best_compression and best_compression[2] - best_compression[3] > 0:
|
||||
compress_commands.append(best_compression)
|
||||
|
||||
j = 0
|
||||
while j < best_compression[2]:
|
||||
output[output_len] = data[offset]
|
||||
output_len += 1
|
||||
offset += 1
|
||||
j += 1
|
||||
|
||||
continue
|
||||
|
||||
compress_commands.append([
|
||||
'raw',
|
||||
data[offset]
|
||||
])
|
||||
|
||||
output[output_len] = data[offset]
|
||||
output_len += 1
|
||||
offset += 1
|
||||
|
||||
compress_commands.append(['eof'])
|
||||
|
||||
# Step 2: Compress down raw runs
|
||||
# Step 3: Build down repeat commands
|
||||
compress_commands2 = []
|
||||
i = 0
|
||||
compress_commands_len = len(compress_commands)
|
||||
while i < compress_commands_len:
|
||||
if compress_commands[i][0] == 'raw':
|
||||
run_length = 1
|
||||
|
||||
while compress_commands[i+run_length][0] == 'raw':
|
||||
run_length += 1
|
||||
|
||||
if run_length == 1:
|
||||
compress_commands2.append(compress_commands[i])
|
||||
i += 1
|
||||
continue
|
||||
|
||||
raw_bulk = bytearray()
|
||||
for j in range(run_length):
|
||||
raw_bulk.append(compress_commands[i+j][1])
|
||||
|
||||
while len(raw_bulk) > 7:
|
||||
copy_len = min(len(raw_bulk), 0x46)
|
||||
chunk = raw_bulk[:copy_len]
|
||||
raw_bulk = raw_bulk[copy_len:]
|
||||
|
||||
compress_commands2.append([
|
||||
'raw_bulk',
|
||||
chunk,
|
||||
len(chunk)
|
||||
])
|
||||
|
||||
while len(raw_bulk) > 0:
|
||||
copy_len = 1
|
||||
chunk = raw_bulk[:copy_len]
|
||||
raw_bulk = raw_bulk[copy_len:]
|
||||
|
||||
compress_commands2.append([
|
||||
'raw',
|
||||
chunk[0]
|
||||
])
|
||||
|
||||
i += run_length
|
||||
|
||||
elif compress_commands[i][0] == 'repeat':
|
||||
history_back_idx = 1
|
||||
length = compress_commands[i][1]
|
||||
|
||||
while length > 0:
|
||||
if length in [1, 2, 3, 4] and history_back_idx >= 1 and history_back_idx <= 16:
|
||||
copy_len = length
|
||||
|
||||
# Can use a short copy
|
||||
compress_commands2.append([
|
||||
'short_copy',
|
||||
history_back_idx,
|
||||
copy_len,
|
||||
1
|
||||
])
|
||||
|
||||
length -= copy_len
|
||||
|
||||
elif history_back_idx <= 0x3ff and length >= 3:
|
||||
copy_len = min(length, 0x21)
|
||||
|
||||
# Can use a long copy
|
||||
compress_commands2.append([
|
||||
'long_copy',
|
||||
history_back_idx,
|
||||
copy_len,
|
||||
2
|
||||
])
|
||||
|
||||
length -= copy_len
|
||||
|
||||
i += 1
|
||||
|
||||
else:
|
||||
compress_commands2.append(compress_commands[i])
|
||||
i += 1
|
||||
|
||||
output_buffer = bytearray([0])
|
||||
cmd_offset = 0
|
||||
cmd_bit = 0
|
||||
|
||||
# Step 4: Build actual data now
|
||||
for x in compress_commands2:
|
||||
# print("%04x" % len(output_buffer), x)
|
||||
|
||||
if cmd_bit == 8:
|
||||
cmd_offset = len(output_buffer)
|
||||
output_buffer += int.to_bytes(0, 1, 'little')
|
||||
cmd_bit = 0
|
||||
|
||||
if x[0] == 'raw':
|
||||
output_buffer += int.to_bytes(x[1], 1, 'little')
|
||||
|
||||
elif x[0] == 'eof':
|
||||
output_buffer[cmd_offset] |= 1 << cmd_bit
|
||||
output_buffer += int.to_bytes(0xff, 1, 'little')
|
||||
|
||||
else:
|
||||
output_buffer[cmd_offset] |= 1 << cmd_bit
|
||||
|
||||
if x[0] == 'raw_bulk':
|
||||
# 1 + x bytes
|
||||
output_buffer += int.to_bytes(0xb9 + len(x[1]) - 1, 1, 'little')
|
||||
output_buffer += x[1]
|
||||
|
||||
elif x[0] == 'short_copy':
|
||||
# 1 byte
|
||||
v = ((x[2] + 6) << 4) | (x[1] - 1)
|
||||
output_buffer += int.to_bytes(v, 1, 'little')
|
||||
|
||||
elif x[0] == 'long_copy':
|
||||
# 2 bytes
|
||||
v = (((x[2] - 3) << 2) << 8) | (x[1] & 0x3ff)
|
||||
output_buffer += int.to_bytes(v, 2, 'big')
|
||||
|
||||
# if len(output_buffer) >= 0x170:
|
||||
# exit(1)
|
||||
|
||||
cmd_bit += 1
|
||||
|
||||
return output_buffer
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,243 @@
|
|||
# cython: cdivision=True
|
||||
|
||||
cpdef unsigned int get_filename_hash(unsigned char *filename, unsigned int filename_len):
|
||||
cdef int hash = 0
|
||||
cdef unsigned int cidx = 0
|
||||
cdef unsigned int i = 0
|
||||
|
||||
while cidx < filename_len:
|
||||
i = 0
|
||||
while i < 6:
|
||||
hash = ((hash >> 31) & 0x4c11db7) ^ ((hash << 1) | ((filename[cidx] >> i) & 1))
|
||||
i += 1
|
||||
|
||||
cidx += 1
|
||||
|
||||
return hash & 0xffffffff
|
||||
|
||||
|
||||
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
|
||||
|
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
from distutils.core import setup
|
||||
from Cython.Build import cythonize
|
||||
|
||||
setup(
|
||||
ext_modules = cythonize(["comp573.pyx", "enc573.pyx", "sum573.pyx"], annotate=True, language_level=3)
|
||||
)
|
|
@ -0,0 +1,103 @@
|
|||
# cython: cdivision=True
|
||||
|
||||
cpdef inline unsigned short calc_final_sum(unsigned int val):
|
||||
cdef unsigned short output = val & 0xffff
|
||||
|
||||
while val > 0xffff:
|
||||
val = output + (val >> 16)
|
||||
output = val
|
||||
|
||||
return output
|
||||
|
||||
|
||||
cdef inline (unsigned int, unsigned int) sum_chunk(unsigned char *data, unsigned int offset, unsigned int chunk_size=0x20000):
|
||||
cdef unsigned int a = 0
|
||||
cdef unsigned int b = 0
|
||||
cdef unsigned int i = 0
|
||||
|
||||
while i < chunk_size:
|
||||
a += data[offset+i]
|
||||
b += data[offset+i+1]
|
||||
i += 2
|
||||
|
||||
return [a, b]
|
||||
|
||||
|
||||
cpdef bytearray checksum_chunk(unsigned char *data, unsigned int offset, unsigned int chunk_size=0x20000):
|
||||
cdef unsigned int sum1, sum2
|
||||
cdef unsigned short a = 0
|
||||
cdef unsigned short b = 0
|
||||
|
||||
sum1, sum2 = sum_chunk(data, offset, chunk_size)
|
||||
a = calc_final_sum(calc_final_sum(sum1) & 0xffff)
|
||||
b = calc_final_sum(calc_final_sum(sum2) & 0xffff)
|
||||
|
||||
return bytearray([a & 0xff, b & 0xff, (a >> 8) & 0xff, (b >> 8) & 0xff])
|
||||
|
||||
|
||||
cpdef list balance_sums(list cards, list card_sizes, unsigned int last_chunk_offset):
|
||||
cdef unsigned int last_chunk_checksum_offset = last_chunk_offset + 0x10
|
||||
cdef unsigned int i = 0
|
||||
cdef unsigned int j = 0
|
||||
cdef unsigned int a = 0
|
||||
cdef unsigned int b = 0
|
||||
cdef unsigned int pad = 0
|
||||
cdef unsigned char val = 0
|
||||
cdef unsigned int card_sum = sum(card_sizes)
|
||||
|
||||
cards[0][last_chunk_checksum_offset + card_sum:last_chunk_offset + 0x2000] = bytearray([0] * (0x2000 - card_sum - 0x10))
|
||||
|
||||
a, b = sum_chunk(cards[0], last_chunk_offset, 0x2000)
|
||||
while i < 2:
|
||||
pad = 0x10000 - calc_final_sum(a if i == 0 else b)
|
||||
j = card_sum
|
||||
|
||||
while pad > 0 and j < 0x2000:
|
||||
val = pad if pad < 0xff else 0xff
|
||||
|
||||
cards[0][last_chunk_checksum_offset + j + i] += val
|
||||
|
||||
pad -= val
|
||||
j += 2
|
||||
|
||||
i += 1
|
||||
|
||||
return cards
|
||||
|
||||
|
||||
cpdef add_checksums(list cards, list card_sizes, unsigned int chunk_size, unsigned int last_chunk_checksum_offset, unsigned int card_start_index, unsigned int card_count):
|
||||
cdef unsigned int real_card_index = 0
|
||||
cdef unsigned int i = 0
|
||||
cdef unsigned int card_offset = 0
|
||||
cdef unsigned int checksum_offset = 0
|
||||
cdef unsigned int total_cards = len(cards)
|
||||
cdef bytearray final_sum
|
||||
|
||||
while real_card_index < total_cards:
|
||||
card_data = cards[real_card_index]
|
||||
|
||||
if real_card_index < card_start_index:
|
||||
# Skip first DAT because it's already been done
|
||||
real_card_index += 1
|
||||
continue
|
||||
|
||||
if real_card_index - card_start_index > card_count:
|
||||
break
|
||||
|
||||
card_offset = (sum(card_sizes[:real_card_index]) // 4) * real_card_index
|
||||
|
||||
i = 0
|
||||
s = len(card_data) // chunk_size
|
||||
while i < s:
|
||||
offset = (i * chunk_size) + (0x20 if real_card_index == 0 and i == 0 else 0)
|
||||
length = chunk_size - (0x20 if real_card_index == 0 and i == 0 else 0)
|
||||
final_sum = checksum_chunk(card_data, offset, length)
|
||||
|
||||
checksum_offset = last_chunk_checksum_offset + ((i + card_offset) * 4)
|
||||
cards[0][checksum_offset:checksum_offset+4] = final_sum
|
||||
|
||||
i += 1
|
||||
|
||||
real_card_index += 1
|
||||
|
||||
return cards
|
|
@ -0,0 +1,23 @@
|
|||
import argparse
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('--mode', help='Operation mode', required=True, choices=['dump', 'build', 'checksum'])
|
||||
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
if args.mode == "dump":
|
||||
import dump_sys573_gamefs
|
||||
dump_sys573_gamefs.main()
|
||||
|
||||
elif args.mode == "build":
|
||||
import build_sys573_gamefs
|
||||
build_sys573_gamefs.main()
|
||||
|
||||
elif args.mode == "checksum":
|
||||
import calc_checksum
|
||||
calc_checksum.main()
|
||||
|
||||
else:
|
||||
print("Unknown mode:", args.mode)
|
|
@ -0,0 +1,91 @@
|
|||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
|
||||
BASE_ADDRESS = 0x101200
|
||||
|
||||
def dump_file(infile, entry):
|
||||
if entry['is_folder']:
|
||||
return
|
||||
|
||||
target_path = entry['_path'] if entry['is_folder'] else os.path.dirname(entry['_path'])
|
||||
if not os.path.exists(target_path):
|
||||
os.makedirs(target_path)
|
||||
|
||||
cur_offset = infile.tell()
|
||||
infile.seek(entry['offset'])
|
||||
|
||||
data = infile.read(entry['filesize'])
|
||||
with open(entry['_path'], "wb") as outfile:
|
||||
outfile.write(data)
|
||||
|
||||
infile.seek(cur_offset)
|
||||
|
||||
return hashlib.sha1(data).hexdigest()
|
||||
|
||||
|
||||
def read_folder(infile, target_offset, curpath):
|
||||
entries = []
|
||||
|
||||
cur_offset = infile.tell()
|
||||
infile.seek(target_offset)
|
||||
|
||||
while True:
|
||||
filename = infile.read(0x10).decode('ascii').strip('\0')
|
||||
infile.read(0x04)
|
||||
filesize = int.from_bytes(infile.read(4), byteorder='big')
|
||||
infile.read(0x04)
|
||||
entry_type = int.from_bytes(infile.read(1), byteorder='big')
|
||||
offset = int.from_bytes(infile.read(3), byteorder='big') * 0x4000
|
||||
|
||||
if filename in ['.', '..']:
|
||||
continue
|
||||
|
||||
if not filename:
|
||||
break
|
||||
|
||||
entry = {
|
||||
'filename': filename,
|
||||
'offset': (BASE_ADDRESS - 0x4000) + offset,
|
||||
'filesize': filesize,
|
||||
'is_folder': entry_type == 1,
|
||||
'_path': os.path.join(curpath, filename),
|
||||
}
|
||||
|
||||
entry['_checksum'] = dump_file(infile, entry)
|
||||
|
||||
print(entry)
|
||||
|
||||
entries.append(entry)
|
||||
|
||||
print()
|
||||
|
||||
for entry in entries:
|
||||
if entry['is_folder']:
|
||||
print("Diving into %s @ %08x" % (entry['filename'], entry['offset']))
|
||||
entries += read_folder(infile, entry['offset'], entry['_path'])
|
||||
|
||||
infile.seek(cur_offset)
|
||||
|
||||
return entries
|
||||
|
||||
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('-s', '--save-metadata', help='Save JSON metadata', default=False, action='store_true')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.output:
|
||||
args.output = os.path.splitext(os.path.basename(args.input))[0]
|
||||
|
||||
with open(args.input, "rb") as infile:
|
||||
entries = read_folder(infile, BASE_ADDRESS, args.output)
|
||||
|
||||
if args.save_metadata:
|
||||
metadata_path = os.path.join(args.output, "_metadata.json")
|
||||
json.dump(entries, open(metadata_path, "w"), indent=4)
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# GFDM MP3 encryption key generator
|
||||
# Point this tool at DA_LIST.BIN
|
||||
|
||||
import argparse
|
||||
import struct
|
||||
import sys
|
||||
|
||||
def generate_key(filename):
|
||||
enc_table = [
|
||||
0x006f3a5d, 0x0065f710, 0x0072a6cf, 0x0049cbfb, 0x004c77f8, 0x00778885, 0x007ae64a, 0x0015990a,
|
||||
0x002c2e6b, 0x00385225, 0x0061de2d, 0x003002e3, 0x00674ca7, 0x00362403, 0x00456126, 0x00109449,
|
||||
0x00453c03, 0x005c61a4, 0x001e3f73, 0x004716f5, 0x0040f1a4, 0x004df73c, 0x0096137b, 0x0052a72f,
|
||||
0x00667a2a, 0x007bf27e, 0x000a7036, 0x00165ab6, 0x0032bb75, 0x003e2961, 0x00792923, 0x001f101f
|
||||
]
|
||||
|
||||
hashsum = [sum(filename[::2]), sum(filename[1::2])]
|
||||
|
||||
v0 = ((hashsum[0] << 8) | hashsum[1]) ^ 0xaaaaaaaa
|
||||
enc_key = enc_table[v0 & 0x1f]
|
||||
v0 = (v0 * enc_key)
|
||||
a0 = v0 & 0xffff0000
|
||||
v0 = ((v0 * enc_key) >> 15) & 0xffff
|
||||
a0 |= v0
|
||||
|
||||
output = [
|
||||
((hashsum[1] ^ a0) >> 16) & 0xffff,
|
||||
(hashsum[0] ^ a0) & 0xffff,
|
||||
]
|
||||
|
||||
output.append((((output[1] & 0x3c0) >> 6) | ((output[0] & 0x3c) << 2)) & 0xff)
|
||||
|
||||
return output
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('--input', help='Input dump binary file', required=True)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.input, "rb") as infile:
|
||||
infile.seek(0, 2)
|
||||
filelen = infile.tell()
|
||||
infile.seek(0)
|
||||
|
||||
while infile.tell() < filelen:
|
||||
file_type, filename = struct.unpack("<H10s", infile.read(12))
|
||||
filename = filename.decode('shift-jis').strip('\0').strip()
|
||||
|
||||
output = generate_key(filename.encode('shift-jis'))
|
||||
|
||||
print("%04x %04x %02x %s" % (output[0], output[1], output[2], filename))
|
|
@ -0,0 +1,374 @@
|
|||
import glob
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import hexdump
|
||||
|
||||
USE_TIMESIGS = False
|
||||
|
||||
def read_string(data, offset):
|
||||
string_bytes = data[offset:data.index(b'\0', offset)]
|
||||
return string_bytes.decode('shift-jis').strip('\0')
|
||||
|
||||
|
||||
def convert_raw_chart(found_charts, song_info=None):
|
||||
song_id = song_info['song_id']
|
||||
bpm = song_info['bpm']
|
||||
|
||||
# Parse ttb file for timestamps
|
||||
ttb_path = os.path.join("raw_data_dct", "ttb%03d02.bin" % song_id)
|
||||
ttb_by_measure = {}
|
||||
ttb_data = bytearray(open(ttb_path, "rb").read())
|
||||
ttb_header = ttb_data[:8]
|
||||
ttb_data = ttb_data[8:]
|
||||
|
||||
measure_timestamps = {}
|
||||
cur_bars = 0
|
||||
|
||||
bpms = {0: 0}
|
||||
|
||||
song_offset_time = int.from_bytes(ttb_data[2:4], 'little')
|
||||
song_offset = (((song_offset_time * 441) / 75) * 100) / 44100
|
||||
song_offset = -song_offset
|
||||
|
||||
timing_info_by_bar = {}
|
||||
requires_timing_info = False
|
||||
last_time_sig = None
|
||||
measure_to_beat = {}
|
||||
last_measure = 0
|
||||
|
||||
cur_bar_len = 4
|
||||
for i in range(4, len(ttb_data) - 4, 4):
|
||||
prev_bar_len = cur_bar_len
|
||||
cur_time = int.from_bytes(ttb_data[i+2:i+4], 'little')
|
||||
prev_time = int.from_bytes(ttb_data[i+2-4:i+4-4], 'little')
|
||||
cur_bar_len = int.from_bytes(ttb_data[i-4:i+2-4], 'little')
|
||||
|
||||
cur_timestamp = (((cur_time * 441) / 75) * 100) / 44100
|
||||
prev_timestamp = (((prev_time * 441) / 75) * 100) / 44100
|
||||
|
||||
if song_offset is None:
|
||||
song_offset = -prev_timestamp
|
||||
|
||||
d = (cur_bar_len / 4) * 4 if USE_TIMESIGS else 4
|
||||
cur_bpm = 1 / (((cur_timestamp - prev_timestamp) * (1000 / d)) / 60000)
|
||||
|
||||
# print(cur_bars, "%04x (%f) %04x (%f)" % (prev_time, prev_timestamp, cur_time, cur_timestamp), cur_bpm, cur_bar_len)
|
||||
|
||||
if last_time_sig is None or cur_bar_len != last_time_sig:
|
||||
timing_info_by_bar[cur_bars] = cur_bar_len
|
||||
last_time_sig = cur_bar_len
|
||||
|
||||
if cur_bar_len != 4:
|
||||
requires_timing_info = True
|
||||
|
||||
bpms[cur_bars] = cur_bpm
|
||||
cur_bars += cur_bar_len if USE_TIMESIGS else 4
|
||||
|
||||
if cur_bar_len != 4:
|
||||
print("Found bar of %d in %s" % (cur_bar_len, song_info['title']))
|
||||
|
||||
### Handle conversion of chart
|
||||
chart = """#TITLE:%s;
|
||||
#MUSIC:bgm.mp3;
|
||||
#PREVIEW:preview.mp3;
|
||||
#OFFSET:%lf;
|
||||
#BPMS:%s;
|
||||
#DISPLAYBPM:%d;
|
||||
""" % (song_info.get('title', '(Untitled)'), song_offset, ",".join(["%d=%f" % (k, bpms[k]) for k in bpms]), song_info.get('bpm', 128))
|
||||
|
||||
if requires_timing_info and USE_TIMESIGS:
|
||||
chart += "#TIMESIGNATURES:%s;" % (",".join(["%d=%d=4" % (k, timing_info_by_bar[k]) for k in timing_info_by_bar]))
|
||||
|
||||
for idx, data in found_charts:
|
||||
valid_charts = [0, 1, 2, 7, 8]
|
||||
|
||||
if idx not in valid_charts:
|
||||
continue
|
||||
|
||||
chart_type = {
|
||||
0: "dance-single",
|
||||
1: "dance-single",
|
||||
2: "dance-single",
|
||||
3: "dance-couple",
|
||||
4: "dance-couple",
|
||||
5: "dance-couple",
|
||||
7: "dance-double",
|
||||
8: "dance-double",
|
||||
}[idx]
|
||||
|
||||
chart_diff = {
|
||||
0: "Easy",
|
||||
1: "Medium",
|
||||
2: "Hard",
|
||||
3: "Easy",
|
||||
4: "Medium",
|
||||
5: "Hard",
|
||||
7: "Easy",
|
||||
8: "Medium",
|
||||
}[idx]
|
||||
|
||||
diff_rating = {
|
||||
0: song_info['diffs']['single']['basic'],
|
||||
1: song_info['diffs']['single']['trick'],
|
||||
2: song_info['diffs']['single']['maniac'],
|
||||
3: song_info['diffs']['couple']['basic'],
|
||||
4: song_info['diffs']['couple']['trick'],
|
||||
5: song_info['diffs']['couple']['maniac'],
|
||||
7: song_info['diffs']['double']['basic'],
|
||||
8: song_info['diffs']['double']['trick'],
|
||||
}[idx]
|
||||
|
||||
chunks = [data[i:i+8] for i in range(0, len(data), 8)]
|
||||
events = []
|
||||
last_measure = 0
|
||||
|
||||
for chunk in chunks:
|
||||
def get_arrows_str(n):
|
||||
s = ""
|
||||
s += "1" if (n & 8) else "0"
|
||||
s += "1" if (n & 4) else "0"
|
||||
s += "1" if (n & 2) else "0"
|
||||
s += "1" if (n & 1) else "0"
|
||||
return s
|
||||
|
||||
measure = chunk[2]
|
||||
beat = chunk[3]
|
||||
cmd = int.from_bytes(chunk[4:], 'little')
|
||||
|
||||
beat = round((beat / 256) * 192)
|
||||
|
||||
event = {
|
||||
'measure': measure,
|
||||
'beat': beat,
|
||||
}
|
||||
|
||||
if cmd == 4:
|
||||
# Is a note
|
||||
p1_note = chunk[0]
|
||||
p2_note = chunk[1]
|
||||
|
||||
p1_str = get_arrows_str(p1_note)
|
||||
p2_str = get_arrows_str(p2_note)
|
||||
|
||||
note_data = p1_str
|
||||
|
||||
if chart_type == "dance-single":
|
||||
if p2_note != 0:
|
||||
print("P2 note has data for single chart")
|
||||
# exit(1)
|
||||
|
||||
else:
|
||||
note_data += p2_str
|
||||
|
||||
event['cmd'] = 'note'
|
||||
event['data'] = note_data
|
||||
|
||||
elif cmd == 0x100:
|
||||
# End song
|
||||
event['cmd'] = 'end'
|
||||
last_measure = measure + 1
|
||||
|
||||
else:
|
||||
print("Unknown cmd value", cmd)
|
||||
exit(1)
|
||||
|
||||
events.append(event)
|
||||
|
||||
if song_info is None:
|
||||
song_info = {}
|
||||
|
||||
measure_data = {}
|
||||
for i in range(last_measure):
|
||||
measure_data[i] = []
|
||||
|
||||
measure_data = {}
|
||||
for event in events:
|
||||
if event['cmd'] != "note":
|
||||
continue
|
||||
|
||||
if event['measure'] not in measure_data:
|
||||
d = "00000000" if "double" in chart_type else "0000"
|
||||
measure_data[event['measure']] = [d] * 192
|
||||
|
||||
# print(event['beat'], len(measure_data[event['measure']]))
|
||||
measure_data[event['measure']][event['beat']] = event['data']
|
||||
|
||||
for i in range(last_measure):
|
||||
if i not in measure_data:
|
||||
d = "00000000" if "double" in chart_type else "0000"
|
||||
measure_data[i] = [d]
|
||||
|
||||
arrow_data = "\n,\n".join(["\n".join(measure_data[k]) for k in sorted(list(measure_data.keys()))])
|
||||
|
||||
chart +="""
|
||||
#NOTES:
|
||||
%s:
|
||||
:
|
||||
%s:
|
||||
%d:
|
||||
0,0,0,0,0:
|
||||
%s
|
||||
;""" % (chart_type, chart_diff, diff_rating, arrow_data)
|
||||
|
||||
return chart
|
||||
|
||||
|
||||
|
||||
songlist_info = {}
|
||||
data = bytearray(open("dct.exe", "rb").read())
|
||||
|
||||
base_diff = 0x8000f800
|
||||
songlist_offset = 0x4d48
|
||||
song_count = 0x340 // 0x40
|
||||
|
||||
for i in range(0, song_count * 0x40, 0x40):
|
||||
chunk = data[songlist_offset+i:songlist_offset+i+0x40]
|
||||
|
||||
song_id = int.from_bytes(chunk[0x06:0x08], 'little')
|
||||
is_unlocked = chunk[0]
|
||||
unk_flag = chunk[1]
|
||||
timing_type = int.from_bytes(chunk[2:4], 'little')
|
||||
audio_idx = chunk[0x15]
|
||||
bpm = int.from_bytes(chunk[0x04:0x06], 'little')
|
||||
|
||||
diffs = {
|
||||
'single': {
|
||||
'basic': int.from_bytes(chunk[0x24:0x24+2], 'little') / 2,
|
||||
'trick': int.from_bytes(chunk[0x26:0x26+2], 'little') / 2,
|
||||
'maniac': int.from_bytes(chunk[0x28:0x28+2], 'little') / 2,
|
||||
},
|
||||
'double': {
|
||||
'basic': int.from_bytes(chunk[0x34:0x34+2], 'little') / 2,
|
||||
'trick': int.from_bytes(chunk[0x36:0x36+2], 'little') / 2,
|
||||
},
|
||||
'couple': {
|
||||
'basic': int.from_bytes(chunk[0x2c:0x2c+2], 'little') / 2,
|
||||
'trick': int.from_bytes(chunk[0x2e:0x2e+2], 'little') / 2,
|
||||
'maniac': int.from_bytes(chunk[0x30:0x30+2], 'little') / 2,
|
||||
},
|
||||
}
|
||||
|
||||
title_ptr = int.from_bytes(chunk[8:8+4], 'little') - base_diff
|
||||
title = read_string(data, title_ptr)
|
||||
|
||||
artist_ptr = int.from_bytes(chunk[12:12+4], 'little') - base_diff
|
||||
artist = read_string(data, artist_ptr)
|
||||
|
||||
image_ptr = int.from_bytes(chunk[16:16+4], 'little') - base_diff
|
||||
image = read_string(data, image_ptr)
|
||||
|
||||
songlist_info[song_id] = {
|
||||
'song_id': song_id,
|
||||
'title': title,
|
||||
'artist': artist,
|
||||
'title_image': image,
|
||||
'diffs': diffs,
|
||||
'bpm': bpm,
|
||||
'timing_type': timing_type,
|
||||
'is_unlocked': is_unlocked,
|
||||
'bgm_filename': "D%04d.MP3" % (audio_idx - 2),
|
||||
'preview_filename': "D%04d.MP3" % (audio_idx - 28),
|
||||
}
|
||||
|
||||
print(title)
|
||||
hexdump.hexdump(chunk)
|
||||
print()
|
||||
|
||||
|
||||
for filename in glob.glob("raw_data_dct/seq*.bin"):
|
||||
data = bytearray(open(filename, "rb").read())
|
||||
|
||||
header = data[:0x78]
|
||||
data = data[0x78:]
|
||||
|
||||
found_charts = []
|
||||
for i in range(0, len(header), 0x0c):
|
||||
idx = i // 0x0c
|
||||
exists = int.from_bytes(header[i:i+4], 'little')
|
||||
length = int.from_bytes(header[i+4:i+8], 'little') * 8
|
||||
offset = int.from_bytes(header[i+8:i+12], 'little') * 8
|
||||
|
||||
if exists == 0:
|
||||
assert(length == 0 and offset == 0)
|
||||
continue
|
||||
|
||||
# print("%d %d %04x %04x | %08x -> %08x (%08x)" % (idx, exists, length, offset, offset, offset + length, len(data)))
|
||||
|
||||
chart_data = data[offset:offset+length]
|
||||
found_charts.append((idx, chart_data))
|
||||
|
||||
if len(found_charts) != 5:
|
||||
print("Found %d charts in %s" % (len(found_charts), filename))
|
||||
|
||||
basename = os.path.splitext(os.path.basename(filename))[0]
|
||||
song_id = int(basename[3:6], 10)
|
||||
|
||||
song_info = songlist_info.get(song_id, None)
|
||||
if song_info is not None:
|
||||
basename = song_info['title']
|
||||
|
||||
else:
|
||||
song_info = {
|
||||
'title': "Unknown",
|
||||
'song_id': song_id,
|
||||
'bpm': 128,
|
||||
'diffs': {
|
||||
'single': {
|
||||
'basic': 1,
|
||||
'trick': 2,
|
||||
'maniac': 3,
|
||||
},
|
||||
'double': {
|
||||
'basic': 1,
|
||||
'trick': 2,
|
||||
},
|
||||
'couple': {
|
||||
'basic': 1,
|
||||
'trick': 2,
|
||||
'maniac': 3,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
basepath = os.path.join("charts_output_dct", basename)
|
||||
os.makedirs(basepath, exist_ok=True)
|
||||
|
||||
for idx, chart in found_charts:
|
||||
chart_mapping = {
|
||||
0: "single_basic.bin",
|
||||
1: "single_trick.bin",
|
||||
2: "single_maniac.bin",
|
||||
3: "couple_basic.bin",
|
||||
4: "couple_trick.bin",
|
||||
5: "couple_maniac.bin",
|
||||
7: "double_basic.bin",
|
||||
8: "double_trick.bin",
|
||||
}
|
||||
|
||||
chart_filename = chart_mapping.get(idx, "%02d.bin" % idx)
|
||||
|
||||
if idx not in chart_mapping:
|
||||
print("Found unknown chart", idx)
|
||||
|
||||
# open(os.path.join(basepath, chart_filename), "wb").write(chart)
|
||||
|
||||
# if "night" in song_info['title'].lower():
|
||||
try:
|
||||
chart_converted = convert_raw_chart(found_charts, song_info)
|
||||
open(os.path.join(basepath, "chart.sm"), "w").write(chart_converted)
|
||||
except:
|
||||
print("Couldn't convert %s" % (filename))
|
||||
|
||||
# if song_info is not None:
|
||||
# # json.dump(song_info, open(os.path.join(basepath, "_metadata.json"), "w"), indent=4)
|
||||
|
||||
# if 'bgm_filename' in song_info:
|
||||
# shutil.copyfile(os.path.join("cd_data", song_info['bgm_filename']), os.path.join(basepath, "bgm.mp3"))
|
||||
|
||||
# if 'preview_filename' in song_info:
|
||||
# shutil.copyfile(os.path.join("cd_data", song_info['preview_filename']), os.path.join(basepath, "preview.mp3"))
|
||||
|
||||
# shutil.copyfile(filename, os.path.join(basepath, os.path.basename(filename)))
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
import glob
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import hexdump
|
||||
|
||||
USE_TIMESIGS = False
|
||||
|
||||
def read_string(data, offset):
|
||||
string_bytes = data[offset:data.index(b'\0', offset)]
|
||||
return string_bytes.decode('shift-jis').strip('\0')
|
||||
|
||||
|
||||
def convert_raw_chart(found_charts, song_info=None):
|
||||
song_id = song_info['song_id']
|
||||
bpm = song_info['bpm']
|
||||
|
||||
# Parse ttb file for timestamps
|
||||
ttb_by_measure = {}
|
||||
ttb_data = bytearray(open(os.path.join("raw_data", "ttb%03d02.bin" % song_id), "rb").read())
|
||||
ttb_header = ttb_data[:8]
|
||||
ttb_data = ttb_data[8:]
|
||||
|
||||
measure_timestamps = {}
|
||||
cur_bars = 0
|
||||
|
||||
bpms = {0: 0}
|
||||
|
||||
song_offset_time = int.from_bytes(ttb_data[2:4], 'little')
|
||||
song_offset = (((song_offset_time * 441) / 75) * 100) / 44100
|
||||
song_offset = -song_offset
|
||||
|
||||
timing_info_by_bar = {}
|
||||
requires_timing_info = False
|
||||
last_time_sig = None
|
||||
measure_to_beat = {}
|
||||
last_measure = 0
|
||||
|
||||
cur_bar_len = 4
|
||||
for i in range(4, len(ttb_data) - 4, 4):
|
||||
prev_bar_len = cur_bar_len
|
||||
cur_time = int.from_bytes(ttb_data[i+2:i+4], 'little')
|
||||
prev_time = int.from_bytes(ttb_data[i+2-4:i+4-4], 'little')
|
||||
cur_bar_len = int.from_bytes(ttb_data[i-4:i+2-4], 'little')
|
||||
|
||||
cur_timestamp = (((cur_time * 441) / 75) * 100) / 44100
|
||||
prev_timestamp = (((prev_time * 441) / 75) * 100) / 44100
|
||||
|
||||
if song_offset is None:
|
||||
song_offset = -prev_timestamp
|
||||
|
||||
d = (cur_bar_len / 4) * 4 if USE_TIMESIGS else 4
|
||||
cur_bpm = 1 / (((cur_timestamp - prev_timestamp) * (1000 / d)) / 60000)
|
||||
|
||||
# print(cur_bars, "%04x (%f) %04x (%f)" % (prev_time, prev_timestamp, cur_time, cur_timestamp), cur_bpm, cur_bar_len)
|
||||
|
||||
if last_time_sig is None or cur_bar_len != last_time_sig:
|
||||
timing_info_by_bar[cur_bars] = cur_bar_len
|
||||
last_time_sig = cur_bar_len
|
||||
|
||||
if cur_bar_len != 4:
|
||||
requires_timing_info = True
|
||||
|
||||
bpms[cur_bars] = cur_bpm
|
||||
cur_bars += cur_bar_len if USE_TIMESIGS else 4
|
||||
|
||||
if cur_bar_len != 4:
|
||||
print("Found bar of %d in %s" % (cur_bar_len, song_info['title']))
|
||||
|
||||
### Handle conversion of chart
|
||||
chart = """#TITLE:%s;
|
||||
#MUSIC:bgm.mp3;
|
||||
#PREVIEW:preview.mp3;
|
||||
#OFFSET:%lf;
|
||||
#BPMS:%s;
|
||||
#DISPLAYBPM:%d;
|
||||
""" % (song_info.get('title', '(Untitled)'), song_offset, ",".join(["%d=%f" % (k, bpms[k]) for k in bpms]), song_info.get('bpm', 128))
|
||||
|
||||
if requires_timing_info and USE_TIMESIGS:
|
||||
chart += "#TIMESIGNATURES:%s;" % (",".join(["%d=%d=4" % (k, timing_info_by_bar[k]) for k in timing_info_by_bar]))
|
||||
|
||||
for idx, data in found_charts:
|
||||
chart_type = {
|
||||
0: "dance-single",
|
||||
1: "dance-single",
|
||||
2: "dance-single",
|
||||
7: "dance-double",
|
||||
8: "dance-double",
|
||||
}[idx]
|
||||
|
||||
chart_diff = {
|
||||
0: "Easy",
|
||||
1: "Medium",
|
||||
2: "Hard",
|
||||
7: "Easy",
|
||||
8: "Medium",
|
||||
}[idx]
|
||||
|
||||
diff_rating = {
|
||||
0: song_info['diffs']['single']['basic'],
|
||||
1: song_info['diffs']['single']['trick'],
|
||||
2: song_info['diffs']['single']['maniac'],
|
||||
7: song_info['diffs']['double']['basic'],
|
||||
8: song_info['diffs']['double']['trick'],
|
||||
}[idx]
|
||||
|
||||
chunks = [data[i:i+8] for i in range(0, len(data), 8)]
|
||||
events = []
|
||||
last_measure = 0
|
||||
|
||||
for chunk in chunks:
|
||||
def get_arrows_str(n):
|
||||
s = ""
|
||||
s += "1" if (n & 8) else "0"
|
||||
s += "1" if (n & 4) else "0"
|
||||
s += "1" if (n & 2) else "0"
|
||||
s += "1" if (n & 1) else "0"
|
||||
return s
|
||||
|
||||
measure = chunk[2]
|
||||
beat = chunk[3]
|
||||
cmd = int.from_bytes(chunk[4:], 'little')
|
||||
|
||||
beat = round((beat / 256) * 192)
|
||||
|
||||
event = {
|
||||
'measure': measure,
|
||||
'beat': beat,
|
||||
}
|
||||
|
||||
if cmd == 4:
|
||||
# Is a note
|
||||
p1_note = chunk[0]
|
||||
p2_note = chunk[1]
|
||||
|
||||
p1_str = get_arrows_str(p1_note)
|
||||
p2_str = get_arrows_str(p2_note)
|
||||
|
||||
note_data = p1_str
|
||||
|
||||
if chart_type == "dance-single":
|
||||
if p2_note != 0:
|
||||
print("P2 note has data for single chart")
|
||||
exit(1)
|
||||
|
||||
else:
|
||||
note_data += p2_str
|
||||
|
||||
event['cmd'] = 'note'
|
||||
event['data'] = note_data
|
||||
|
||||
elif cmd == 0x100:
|
||||
# End song
|
||||
event['cmd'] = 'end'
|
||||
last_measure = measure + 1
|
||||
|
||||
else:
|
||||
print("Unknown cmd value", cmd)
|
||||
exit(1)
|
||||
|
||||
events.append(event)
|
||||
|
||||
if song_info is None:
|
||||
song_info = {}
|
||||
|
||||
measure_data = {}
|
||||
for i in range(last_measure):
|
||||
measure_data[i] = []
|
||||
|
||||
measure_data = {}
|
||||
for event in events:
|
||||
if event['cmd'] != "note":
|
||||
continue
|
||||
|
||||
if event['measure'] not in measure_data:
|
||||
d = "00000000" if "double" in chart_type else "0000"
|
||||
measure_data[event['measure']] = [d] * 192
|
||||
|
||||
# print(event['beat'], len(measure_data[event['measure']]))
|
||||
measure_data[event['measure']][event['beat']] = event['data']
|
||||
|
||||
for i in range(last_measure):
|
||||
if i not in measure_data:
|
||||
d = "00000000" if "double" in chart_type else "0000"
|
||||
measure_data[i] = [d]
|
||||
|
||||
arrow_data = "\n,\n".join(["\n".join(measure_data[k]) for k in sorted(list(measure_data.keys()))])
|
||||
|
||||
chart +="""
|
||||
#NOTES:
|
||||
%s:
|
||||
:
|
||||
%s:
|
||||
%d:
|
||||
0,0,0,0,0:
|
||||
%s
|
||||
;""" % (chart_type, chart_diff, diff_rating, arrow_data)
|
||||
|
||||
return chart
|
||||
|
||||
|
||||
|
||||
songlist_info = {}
|
||||
data = bytearray(open("disney_rave.exe", "rb").read())
|
||||
|
||||
base_diff = 0x8000f800
|
||||
songlist_offset = 0x487c
|
||||
song_count = 0x780 // 0x40
|
||||
|
||||
for i in range(0, song_count * 0x40, 0x40):
|
||||
chunk = data[songlist_offset+i:songlist_offset+i+0x40]
|
||||
|
||||
song_id = int.from_bytes(chunk[0x06:0x08], 'little')
|
||||
is_unlocked = chunk[0]
|
||||
unk_flag = chunk[1]
|
||||
timing_type = int.from_bytes(chunk[2:4], 'little')
|
||||
audio_idx = chunk[0x15]
|
||||
bpm = int.from_bytes(chunk[0x04:0x06], 'little')
|
||||
|
||||
diffs = {
|
||||
'single': {
|
||||
'basic': int.from_bytes(chunk[0x22:0x22+2], 'little') / 2,
|
||||
'trick': int.from_bytes(chunk[0x24:0x24+2], 'little') / 2,
|
||||
'maniac': int.from_bytes(chunk[0x26:0x26+2], 'little') / 2,
|
||||
},
|
||||
'double': {
|
||||
'basic': int.from_bytes(chunk[0x32:0x32+2], 'little') / 2,
|
||||
'trick': int.from_bytes(chunk[0x34:0x34+2], 'little') / 2,
|
||||
},
|
||||
'couple': {
|
||||
'basic': int.from_bytes(chunk[0x2a:0x2a+2], 'little') / 2,
|
||||
'trick': int.from_bytes(chunk[0x2c:0x2c+2], 'little') / 2,
|
||||
'maniac': int.from_bytes(chunk[0x2e:0x2e+2], 'little') / 2,
|
||||
},
|
||||
}
|
||||
|
||||
title_ptr = int.from_bytes(chunk[8:8+4], 'little') - base_diff
|
||||
title = read_string(data, title_ptr)
|
||||
|
||||
artist_ptr = int.from_bytes(chunk[12:12+4], 'little') - base_diff
|
||||
artist = read_string(data, artist_ptr)
|
||||
|
||||
image_ptr = int.from_bytes(chunk[16:16+4], 'little') - base_diff
|
||||
image = read_string(data, image_ptr)
|
||||
|
||||
songlist_info[song_id] = {
|
||||
'song_id': song_id,
|
||||
'title': title,
|
||||
'artist': artist,
|
||||
'title_image': image,
|
||||
'diffs': diffs,
|
||||
'bpm': bpm,
|
||||
'timing_type': timing_type,
|
||||
'is_unlocked': is_unlocked,
|
||||
'bgm_filename': "D%04d.MP3" % (audio_idx - 2),
|
||||
'preview_filename': "D%04d.MP3" % (audio_idx - 28),
|
||||
}
|
||||
|
||||
print(title)
|
||||
hexdump.hexdump(chunk)
|
||||
print()
|
||||
|
||||
|
||||
for filename in glob.glob("charts/seq*.bin"):
|
||||
data = bytearray(open(filename, "rb").read())
|
||||
|
||||
header = data[:0x78]
|
||||
data = data[0x78:]
|
||||
|
||||
found_charts = []
|
||||
for i in range(0, len(header), 0x0c):
|
||||
idx = i // 0x0c
|
||||
exists = int.from_bytes(header[i:i+4], 'little')
|
||||
length = int.from_bytes(header[i+4:i+8], 'little') * 8
|
||||
offset = int.from_bytes(header[i+8:i+12], 'little') * 8
|
||||
|
||||
if exists == 0:
|
||||
assert(length == 0 and offset == 0)
|
||||
continue
|
||||
|
||||
# print("%d %d %04x %04x | %08x -> %08x (%08x)" % (idx, exists, length, offset, offset, offset + length, len(data)))
|
||||
|
||||
chart_data = data[offset:offset+length]
|
||||
found_charts.append((idx, chart_data))
|
||||
|
||||
if len(found_charts) != 5:
|
||||
print("Found %d charts in %s" % (len(found_charts), filename))
|
||||
|
||||
basename = os.path.splitext(os.path.basename(filename))[0]
|
||||
song_id = int(basename[3:6], 10)
|
||||
|
||||
song_info = songlist_info.get(song_id, None)
|
||||
if song_info is not None:
|
||||
basename = song_info['title']
|
||||
|
||||
basepath = os.path.join("charts_output", basename)
|
||||
os.makedirs(basepath, exist_ok=True)
|
||||
|
||||
for idx, chart in found_charts:
|
||||
chart_mapping = {
|
||||
0: "single_basic.bin",
|
||||
1: "single_trick.bin",
|
||||
2: "single_maniac.bin",
|
||||
7: "double_basic.bin",
|
||||
8: "double_trick.bin",
|
||||
}
|
||||
|
||||
chart_filename = chart_mapping.get(idx, "%02d.bin" % idx)
|
||||
|
||||
if idx not in chart_mapping:
|
||||
print("Found unknown chart", idx)
|
||||
|
||||
# open(os.path.join(basepath, chart_filename), "wb").write(chart)
|
||||
|
||||
# if "night" in song_info['title'].lower():
|
||||
# try:
|
||||
chart_converted = convert_raw_chart(found_charts, song_info)
|
||||
open(os.path.join(basepath, "chart.sm"), "w").write(chart_converted)
|
||||
# except:
|
||||
# print("Couldn't convert %s" % (filename))
|
||||
|
||||
if song_info is not None:
|
||||
# json.dump(song_info, open(os.path.join(basepath, "_metadata.json"), "w"), indent=4)
|
||||
|
||||
if 'bgm_filename' in song_info:
|
||||
shutil.copyfile(os.path.join("cd_data", song_info['bgm_filename']), os.path.join(basepath, "bgm.mp3"))
|
||||
|
||||
if 'preview_filename' in song_info:
|
||||
shutil.copyfile(os.path.join("cd_data", song_info['preview_filename']), os.path.join(basepath, "preview.mp3"))
|
||||
|
||||
# shutil.copyfile(filename, os.path.join(basepath, os.path.basename(filename)))
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import argparse
|
||||
|
||||
def decrypt_data_internal(data, key):
|
||||
def calculate_crc32(input):
|
||||
crc = -1
|
||||
|
||||
for c in bytearray(input, encoding='ascii'):
|
||||
crc ^= c << 24
|
||||
|
||||
for _ in range(8):
|
||||
if crc & 0x80000000:
|
||||
crc = (crc << 1) ^ 0x4C11DB7
|
||||
else:
|
||||
crc <<= 1
|
||||
|
||||
return crc
|
||||
|
||||
decryption_key = calculate_crc32(key)
|
||||
|
||||
for i in range(len(data)):
|
||||
data[i] ^= (decryption_key >> 8) & 0xff # This 8 can be variable it seems, but it usually is 8?
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('--insert', help='Input configuration text into output file', default=False, action='store_true')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.insert:
|
||||
# Insert into GAME.DAT
|
||||
config = decrypt_data_internal(bytearray(open("config.txt", "rb").read()), "/s573/config.dat")
|
||||
import hexdump
|
||||
hexdump.hexdump(config)
|
||||
|
||||
open("config.dat", "wb").write(config)
|
||||
|
||||
else:
|
||||
# Extract from GAME.DAT
|
||||
data = bytearray(open("GAME.DAT", "rb").read())[0x1fc4 * 0x800:0x1fc4 * 0x800 + 0x7f6]
|
||||
config = decrypt_data_internal(data, "/s573/config.dat")
|
||||
open("config.txt", "wb").write(config)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# Viper Tools
|
||||
|
||||
### ppp2nd_dumper.py
|
||||
|
||||
This tool lets you dump all data from a ParaParaParadise 2nd Mix HDD with proper filenames. The checksum algorithm is also documented but not used, but it could be useful for anyone wanting to modify data. The only file that is not properly named is filename hash `90547703` which is the bootloader.
|
||||
|
||||
```
|
||||
usage: ppp2nd_dumper.py [-h] -i INPUT [-o OUTPUT]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-i INPUT, --input INPUT
|
||||
Input file
|
||||
-o OUTPUT, --output OUTPUT
|
||||
Output folder
|
||||
```
|
|
@ -0,0 +1,878 @@
|
|||
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 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)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('-i', '--input', help='Input file', default=None, required=True)
|
||||
parser.add_argument('-o', '--output', help='Output folder', default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
found_filenames = [
|
||||
"copydisk/prog.zin",
|
||||
"fpga/prog.zin",
|
||||
"game/prog.zin",
|
||||
"gl/prog.zin",
|
||||
"grphinit/prog.zin",
|
||||
"hdimage/model/hikari.gtx",
|
||||
"hdimage/model/hikari.mbd",
|
||||
"hdimage/model/kango.mbd",
|
||||
"hdimage/model/kango1.gtx",
|
||||
"hdimage/model/kango2.gtx",
|
||||
"hdimage/model/kango2.mbd",
|
||||
"hdimage/model/kimo.gtx",
|
||||
"hdimage/model/kimo.mbd",
|
||||
"hdimage/model/kimo2.gtx",
|
||||
"hdimage/model/kimo2.mbd",
|
||||
"hdimage/model/money.mbd",
|
||||
"hdimage/model/money2.mbd",
|
||||
"hdimage/model/pants.gtx",
|
||||
"hdimage/model/pants.mbd",
|
||||
"hdimage/model/pants2.gtx",
|
||||
"hdimage/model/pants2.mbd",
|
||||
"hdimage/model/prim.gtx",
|
||||
"hdimage/model/prim.mbd",
|
||||
"hdimage/model/usao.gtx",
|
||||
"hdimage/model/usao.mbd",
|
||||
"hdimage/model/yoko.mbd",
|
||||
"hdimage/model/yoko1.gtx",
|
||||
"hdimage/model/yoko2.gtx",
|
||||
"hdimage/model/yoko2.mbd",
|
||||
"hdimage/motion/01speedw.hgm",
|
||||
"hdimage/motion/01speedw.hhd",
|
||||
"hdimage/motion/02eurobe.hgm",
|
||||
"hdimage/motion/02eurobe.hhd",
|
||||
"hdimage/motion/03nghtof.hgm",
|
||||
"hdimage/motion/03nghtof.hhd",
|
||||
"hdimage/motion/04tryme.hgm",
|
||||
"hdimage/motion/04tryme.hhd",
|
||||
"hdimage/motion/05yestdy.hgm",
|
||||
"hdimage/motion/05yestdy.hhd",
|
||||
"hdimage/motion/06likvgn.hgm",
|
||||
"hdimage/motion/06likvgn.hhd",
|
||||
"hdimage/motion/07tora.hgm",
|
||||
"hdimage/motion/07tora.hhd",
|
||||
"hdimage/motion/08arabia.hgm",
|
||||
"hdimage/motion/08arabia.hhd",
|
||||
"hdimage/motion/09boom2f.hgm",
|
||||
"hdimage/motion/09boom2f.hhd",
|
||||
"hdimage/motion/10mickey.hgm",
|
||||
"hdimage/motion/10mickey.hhd",
|
||||
"hdimage/motion/11crzy4u.hgm",
|
||||
"hdimage/motion/11crzy4u.hhd",
|
||||
"hdimage/motion/12remmbr.hgm",
|
||||
"hdimage/motion/12remmbr.hhd",
|
||||
"hdimage/motion/13stay.hgm",
|
||||
"hdimage/motion/13stay.hhd",
|
||||
"hdimage/motion/14romeo.hgm",
|
||||
"hdimage/motion/14romeo.hhd",
|
||||
"hdimage/motion/15burnin.hgm",
|
||||
"hdimage/motion/15burnin.hhd",
|
||||
"hdimage/motion/16kingdm.hgm",
|
||||
"hdimage/motion/16kingdm.hhd",
|
||||
"hdimage/motion/17godzil.hgm",
|
||||
"hdimage/motion/17godzil.hhd",
|
||||
"hdimage/motion/18holdon.hgm",
|
||||
"hdimage/motion/18holdon.hhd",
|
||||
"hdimage/motion/19lvagan.hgm",
|
||||
"hdimage/motion/19lvagan.hhd",
|
||||
"hdimage/motion/20iwdanc.hgm",
|
||||
"hdimage/motion/20iwdanc.hhd",
|
||||
"hdimage/motion/21anvs.hgm",
|
||||
"hdimage/motion/21anvs.hhd",
|
||||
"hdimage/motion/22engylv.hgm",
|
||||
"hdimage/motion/22engylv.hhd",
|
||||
"hdimage/motion/23luv2_y.hgm",
|
||||
"hdimage/motion/23luv2_y.hhd",
|
||||
"hdimage/motion/26cant_y.hgm",
|
||||
"hdimage/motion/26cant_y.hhd",
|
||||
"hdimage/motion/27dyna_y.hgm",
|
||||
"hdimage/motion/27dyna_y.hhd",
|
||||
"hdimage/motion/30jlousy.hgm",
|
||||
"hdimage/motion/30jlousy.hhd",
|
||||
"hdimage/motion/32aisiat.hgm",
|
||||
"hdimage/motion/32aisiat.hhd",
|
||||
"hdimage/motion/33dx_eur.hgm",
|
||||
"hdimage/motion/33dx_eur.hhd",
|
||||
"hdimage/motion/34ale.hgm",
|
||||
"hdimage/motion/34ale.hhd",
|
||||
"hdimage/motion/35banana.hgm",
|
||||
"hdimage/motion/35banana.hhd",
|
||||
"hdimage/motion/36madeof.hgm",
|
||||
"hdimage/motion/36madeof.hhd",
|
||||
"hdimage/motion/37myfire.hgm",
|
||||
"hdimage/motion/37myfire.hhd",
|
||||
"hdimage/motion/38bndler.hgm",
|
||||
"hdimage/motion/38bndler.hhd",
|
||||
"hdimage/motion/39plywit.hgm",
|
||||
"hdimage/motion/39plywit.hhd",
|
||||
"hdimage/motion/40soclos.hgm",
|
||||
"hdimage/motion/40soclos.hhd",
|
||||
"hdimage/motion/41slmio.hgm",
|
||||
"hdimage/motion/41slmio.hhd",
|
||||
"hdimage/motion/42hyaku.hgm",
|
||||
"hdimage/motion/42hyaku.hhd",
|
||||
"hdimage/motion/43money.hgm",
|
||||
"hdimage/motion/43money.hhd",
|
||||
"hdimage/motion/44vl2000.hgm",
|
||||
"hdimage/motion/44vl2000.hhd",
|
||||
"hdimage/motion/45dancer.hgm",
|
||||
"hdimage/motion/45dancer.hhd",
|
||||
"hdimage/motion/46feelin.hgm",
|
||||
"hdimage/motion/46feelin.hhd",
|
||||
"hdimage/motion/47we2are.hgm",
|
||||
"hdimage/motion/47we2are.hhd",
|
||||
"hdimage/motion/48kiskis.hgm",
|
||||
"hdimage/motion/48kiskis.hhd",
|
||||
"hdimage/motion/49statin.hgm",
|
||||
"hdimage/motion/49statin.hhd",
|
||||
"hdimage/motion/50jam.hgm",
|
||||
"hdimage/motion/50jam.hhd",
|
||||
"hdimage/motion/51number.hgm",
|
||||
"hdimage/motion/51number.hhd",
|
||||
"hdimage/motion/52dltcom.hgm",
|
||||
"hdimage/motion/52dltcom.hhd",
|
||||
"hdimage/motion/53blieve.hgm",
|
||||
"hdimage/motion/53blieve.hhd",
|
||||
"hdimage/motion/56popten.hgm",
|
||||
"hdimage/motion/56popten.hhd",
|
||||
"hdimage/motion/57takeme.hgm",
|
||||
"hdimage/motion/57takeme.hhd",
|
||||
"hdimage/motion/58esybsy.hgm",
|
||||
"hdimage/motion/58esybsy.hhd",
|
||||
"hdimage/motion/59sexy.hgm",
|
||||
"hdimage/motion/59sexy.hhd",
|
||||
"hdimage/motion/63mikado.hgm",
|
||||
"hdimage/motion/63mikado.hhd",
|
||||
"hdimage/motion/64dejavu.hgm",
|
||||
"hdimage/motion/64dejavu.hhd",
|
||||
"hdimage/motion/65hiheel.hgm",
|
||||
"hdimage/motion/65hiheel.hhd",
|
||||
"hdimage/motion/66broken.hgm",
|
||||
"hdimage/motion/66broken.hhd",
|
||||
"hdimage/picture/bg/aisiat/0.gcz",
|
||||
"hdimage/picture/bg/aisiat/1.gcz",
|
||||
"hdimage/picture/bg/aisiat/index.idx",
|
||||
"hdimage/picture/bg/ale/0.gcz",
|
||||
"hdimage/picture/bg/ale/1.gcz",
|
||||
"hdimage/picture/bg/ale/index.idx",
|
||||
"hdimage/picture/bg/anvs/0.gcz",
|
||||
"hdimage/picture/bg/anvs/1.gcz",
|
||||
"hdimage/picture/bg/anvs/index.idx",
|
||||
"hdimage/picture/bg/arabia/0.gcz",
|
||||
"hdimage/picture/bg/arabia/1.gcz",
|
||||
"hdimage/picture/bg/arabia/index.idx",
|
||||
"hdimage/picture/bg/banana/0.gcz",
|
||||
"hdimage/picture/bg/banana/1.gcz",
|
||||
"hdimage/picture/bg/banana/index.idx",
|
||||
"hdimage/picture/bg/blieve/0.gcz",
|
||||
"hdimage/picture/bg/blieve/1.gcz",
|
||||
"hdimage/picture/bg/blieve/index.idx",
|
||||
"hdimage/picture/bg/bndler/0.gcz",
|
||||
"hdimage/picture/bg/bndler/1.gcz",
|
||||
"hdimage/picture/bg/bndler/index.idx",
|
||||
"hdimage/picture/bg/boom2f/0.gcz",
|
||||
"hdimage/picture/bg/boom2f/1.gcz",
|
||||
"hdimage/picture/bg/boom2f/index.idx",
|
||||
"hdimage/picture/bg/brok_y/0.gcz",
|
||||
"hdimage/picture/bg/brok_y/1.gcz",
|
||||
"hdimage/picture/bg/brok_y/index.idx",
|
||||
"hdimage/picture/bg/burnin/0.gcz",
|
||||
"hdimage/picture/bg/burnin/1.gcz",
|
||||
"hdimage/picture/bg/burnin/index.idx",
|
||||
"hdimage/picture/bg/cant_y/0.gcz",
|
||||
"hdimage/picture/bg/cant_y/1.gcz",
|
||||
"hdimage/picture/bg/cant_y/index.idx",
|
||||
"hdimage/picture/bg/crzy4u/0.gcz",
|
||||
"hdimage/picture/bg/crzy4u/1.gcz",
|
||||
"hdimage/picture/bg/crzy4u/index.idx",
|
||||
"hdimage/picture/bg/dancer/0.gcz",
|
||||
"hdimage/picture/bg/dancer/1.gcz",
|
||||
"hdimage/picture/bg/dancer/index.idx",
|
||||
"hdimage/picture/bg/dejavu/0.gcz",
|
||||
"hdimage/picture/bg/dejavu/1.gcz",
|
||||
"hdimage/picture/bg/dejavu/index.idx",
|
||||
"hdimage/picture/bg/dltcom/0.gcz",
|
||||
"hdimage/picture/bg/dltcom/1.gcz",
|
||||
"hdimage/picture/bg/dltcom/index.idx",
|
||||
"hdimage/picture/bg/dx_eur/0.gcz",
|
||||
"hdimage/picture/bg/dx_eur/1.gcz",
|
||||
"hdimage/picture/bg/dx_eur/index.idx",
|
||||
"hdimage/picture/bg/dyna_y/0.gcz",
|
||||
"hdimage/picture/bg/dyna_y/1.gcz",
|
||||
"hdimage/picture/bg/dyna_y/index.idx",
|
||||
"hdimage/picture/bg/enegy/0.gcz",
|
||||
"hdimage/picture/bg/enegy/1.gcz",
|
||||
"hdimage/picture/bg/enegy/index.idx",
|
||||
"hdimage/picture/bg/esybsy/0.gcz",
|
||||
"hdimage/picture/bg/esybsy/1.gcz",
|
||||
"hdimage/picture/bg/esybsy/index.idx",
|
||||
"hdimage/picture/bg/eurobe/0.gcz",
|
||||
"hdimage/picture/bg/eurobe/1.gcz",
|
||||
"hdimage/picture/bg/eurobe/index.idx",
|
||||
"hdimage/picture/bg/feelin/0.gcz",
|
||||
"hdimage/picture/bg/feelin/1.gcz",
|
||||
"hdimage/picture/bg/feelin/index.idx",
|
||||
"hdimage/picture/bg/godzil/0.gcz",
|
||||
"hdimage/picture/bg/godzil/1.gcz",
|
||||
"hdimage/picture/bg/godzil/index.idx",
|
||||
"hdimage/picture/bg/holdon/0.gcz",
|
||||
"hdimage/picture/bg/holdon/1.gcz",
|
||||
"hdimage/picture/bg/holdon/index.idx",
|
||||
"hdimage/picture/bg/howto/0.gcz",
|
||||
"hdimage/picture/bg/howto/index.idx",
|
||||
"hdimage/picture/bg/hyaku/0.gcz",
|
||||
"hdimage/picture/bg/hyaku/1.gcz",
|
||||
"hdimage/picture/bg/hyaku/index.idx",
|
||||
"hdimage/picture/bg/iwana/0.gcz",
|
||||
"hdimage/picture/bg/iwana/1.gcz",
|
||||
"hdimage/picture/bg/iwana/index.idx",
|
||||
"hdimage/picture/bg/jam/0.gcz",
|
||||
"hdimage/picture/bg/jam/1.gcz",
|
||||
"hdimage/picture/bg/jam/index.idx",
|
||||
"hdimage/picture/bg/jlousy/0.gcz",
|
||||
"hdimage/picture/bg/jlousy/1.gcz",
|
||||
"hdimage/picture/bg/jlousy/index.idx",
|
||||
"hdimage/picture/bg/kingdm/0.gcz",
|
||||
"hdimage/picture/bg/kingdm/1.gcz",
|
||||
"hdimage/picture/bg/kingdm/index.idx",
|
||||
"hdimage/picture/bg/kiss3/0.gcz",
|
||||
"hdimage/picture/bg/kiss3/1.gcz",
|
||||
"hdimage/picture/bg/kiss3/index.idx",
|
||||
"hdimage/picture/bg/likvgn/0.gcz",
|
||||
"hdimage/picture/bg/likvgn/1.gcz",
|
||||
"hdimage/picture/bg/likvgn/index.idx",
|
||||
"hdimage/picture/bg/luv2_y/0.gcz",
|
||||
"hdimage/picture/bg/luv2_y/1.gcz",
|
||||
"hdimage/picture/bg/luv2_y/index.idx",
|
||||
"hdimage/picture/bg/lvag/0.gcz",
|
||||
"hdimage/picture/bg/lvag/1.gcz",
|
||||
"hdimage/picture/bg/lvag/index.idx",
|
||||
"hdimage/picture/bg/madeof/0.gcz",
|
||||
"hdimage/picture/bg/madeof/1.gcz",
|
||||
"hdimage/picture/bg/madeof/index.idx",
|
||||
"hdimage/picture/bg/mickey/0.gcz",
|
||||
"hdimage/picture/bg/mickey/1.gcz",
|
||||
"hdimage/picture/bg/mickey/index.idx",
|
||||
"hdimage/picture/bg/mikado/0.gcz",
|
||||
"hdimage/picture/bg/mikado/1.gcz",
|
||||
"hdimage/picture/bg/mikado/index.idx",
|
||||
"hdimage/picture/bg/money/0.gcz",
|
||||
"hdimage/picture/bg/money/1.gcz",
|
||||
"hdimage/picture/bg/money/index.idx",
|
||||
"hdimage/picture/bg/myfire/0.gcz",
|
||||
"hdimage/picture/bg/myfire/1.gcz",
|
||||
"hdimage/picture/bg/myfire/index.idx",
|
||||
"hdimage/picture/bg/nghtof/0.gcz",
|
||||
"hdimage/picture/bg/nghtof/1.gcz",
|
||||
"hdimage/picture/bg/nghtof/index.idx",
|
||||
"hdimage/picture/bg/number/0.gcz",
|
||||
"hdimage/picture/bg/number/1.gcz",
|
||||
"hdimage/picture/bg/number/index.idx",
|
||||
"hdimage/picture/bg/plywit/0.gcz",
|
||||
"hdimage/picture/bg/plywit/index.idx",
|
||||
"hdimage/picture/bg/popten/0.gcz",
|
||||
"hdimage/picture/bg/popten/1.gcz",
|
||||
"hdimage/picture/bg/popten/index.idx",
|
||||
"hdimage/picture/bg/remmbr/0.gcz",
|
||||
"hdimage/picture/bg/remmbr/1.gcz",
|
||||
"hdimage/picture/bg/remmbr/index.idx",
|
||||
"hdimage/picture/bg/romeo/0.gcz",
|
||||
"hdimage/picture/bg/romeo/1.gcz",
|
||||
"hdimage/picture/bg/romeo/index.idx",
|
||||
"hdimage/picture/bg/sexy3/0.gcz",
|
||||
"hdimage/picture/bg/sexy3/1.gcz",
|
||||
"hdimage/picture/bg/sexy3/index.idx",
|
||||
"hdimage/picture/bg/slmio/0.gcz",
|
||||
"hdimage/picture/bg/slmio/1.gcz",
|
||||
"hdimage/picture/bg/slmio/index.idx",
|
||||
"hdimage/picture/bg/soclos/0.gcz",
|
||||
"hdimage/picture/bg/soclos/1.gcz",
|
||||
"hdimage/picture/bg/soclos/index.idx",
|
||||
"hdimage/picture/bg/spd1/0.gcz",
|
||||
"hdimage/picture/bg/spd1/1.gcz",
|
||||
"hdimage/picture/bg/spd1/index.idx",
|
||||
"hdimage/picture/bg/speedw/0.gcz",
|
||||
"hdimage/picture/bg/speedw/1.gcz",
|
||||
"hdimage/picture/bg/speedw/index.idx",
|
||||
"hdimage/picture/bg/statin/0.gcz",
|
||||
"hdimage/picture/bg/statin/1.gcz",
|
||||
"hdimage/picture/bg/statin/index.idx",
|
||||
"hdimage/picture/bg/stay/0.gcz",
|
||||
"hdimage/picture/bg/stay/1.gcz",
|
||||
"hdimage/picture/bg/stay/index.idx",
|
||||
"hdimage/picture/bg/takeme/0.gcz",
|
||||
"hdimage/picture/bg/takeme/1.gcz",
|
||||
"hdimage/picture/bg/takeme/index.idx",
|
||||
"hdimage/picture/bg/tora/0.gcz",
|
||||
"hdimage/picture/bg/tora/1.gcz",
|
||||
"hdimage/picture/bg/tora/index.idx",
|
||||
"hdimage/picture/bg/tryme/0.gcz",
|
||||
"hdimage/picture/bg/tryme/1.gcz",
|
||||
"hdimage/picture/bg/tryme/index.idx",
|
||||
"hdimage/picture/bg/ultr_y/0.gcz",
|
||||
"hdimage/picture/bg/ultr_y/1.gcz",
|
||||
"hdimage/picture/bg/ultr_y/index.idx",
|
||||
"hdimage/picture/bg/vl2000/0.gcz",
|
||||
"hdimage/picture/bg/vl2000/1.gcz",
|
||||
"hdimage/picture/bg/vl2000/index.idx",
|
||||
"hdimage/picture/bg/wetwo/0.gcz",
|
||||
"hdimage/picture/bg/wetwo/1.gcz",
|
||||
"hdimage/picture/bg/wetwo/index.idx",
|
||||
"hdimage/picture/bg/yestdy/0.gcz",
|
||||
"hdimage/picture/bg/yestdy/1.gcz",
|
||||
"hdimage/picture/bg/yestdy/index.idx",
|
||||
"hdimage/picture/bg_prk/01speedw/0.gcz",
|
||||
"hdimage/picture/bg_prk/01speedw/index.idx",
|
||||
"hdimage/picture/bg_prk/02eurobe/0.gcz",
|
||||
"hdimage/picture/bg_prk/02eurobe/index.idx",
|
||||
"hdimage/picture/bg_prk/03nghtof/0.gcz",
|
||||
"hdimage/picture/bg_prk/03nghtof/index.idx",
|
||||
"hdimage/picture/bg_prk/04tryme/0.gcz",
|
||||
"hdimage/picture/bg_prk/04tryme/index.idx",
|
||||
"hdimage/picture/bg_prk/05yestdy/0.gcz",
|
||||
"hdimage/picture/bg_prk/05yestdy/index.idx",
|
||||
"hdimage/picture/bg_prk/06likvgn/0.gcz",
|
||||
"hdimage/picture/bg_prk/06likvgn/index.idx",
|
||||
"hdimage/picture/bg_prk/07tora/0.gcz",
|
||||
"hdimage/picture/bg_prk/07tora/index.idx",
|
||||
"hdimage/picture/bg_prk/08arabia/0.gcz",
|
||||
"hdimage/picture/bg_prk/08arabia/index.idx",
|
||||
"hdimage/picture/bg_prk/09boom2f/0.gcz",
|
||||
"hdimage/picture/bg_prk/09boom2f/index.idx",
|
||||
"hdimage/picture/bg_prk/10mickey/0.gcz",
|
||||
"hdimage/picture/bg_prk/10mickey/index.idx",
|
||||
"hdimage/picture/bg_prk/11crzy4u/0.gcz",
|
||||
"hdimage/picture/bg_prk/11crzy4u/index.idx",
|
||||
"hdimage/picture/bg_prk/12remmbr/0.gcz",
|
||||
"hdimage/picture/bg_prk/12remmbr/index.idx",
|
||||
"hdimage/picture/bg_prk/13stay/0.gcz",
|
||||
"hdimage/picture/bg_prk/13stay/index.idx",
|
||||
"hdimage/picture/bg_prk/14romeo/0.gcz",
|
||||
"hdimage/picture/bg_prk/14romeo/index.idx",
|
||||
"hdimage/picture/bg_prk/15burnin/0.gcz",
|
||||
"hdimage/picture/bg_prk/15burnin/index.idx",
|
||||
"hdimage/picture/bg_prk/16kingdm/0.gcz",
|
||||
"hdimage/picture/bg_prk/16kingdm/index.idx",
|
||||
"hdimage/picture/bg_prk/17godzil/0.gcz",
|
||||
"hdimage/picture/bg_prk/17godzil/index.idx",
|
||||
"hdimage/picture/bg_prk/18holdon/0.gcz",
|
||||
"hdimage/picture/bg_prk/18holdon/index.idx",
|
||||
"hdimage/picture/bg_prk/19lvagan/0.gcz",
|
||||
"hdimage/picture/bg_prk/19lvagan/index.idx",
|
||||
"hdimage/picture/bg_prk/20iwdanc/0.gcz",
|
||||
"hdimage/picture/bg_prk/20iwdanc/index.idx",
|
||||
"hdimage/picture/bg_prk/21anvs/0.gcz",
|
||||
"hdimage/picture/bg_prk/21anvs/index.idx",
|
||||
"hdimage/picture/bg_prk/22engylv/0.gcz",
|
||||
"hdimage/picture/bg_prk/22engylv/index.idx",
|
||||
"hdimage/picture/bg_prk/23luv2_y/0.gcz",
|
||||
"hdimage/picture/bg_prk/23luv2_y/index.idx",
|
||||
"hdimage/picture/bg_prk/26cant_y/0.gcz",
|
||||
"hdimage/picture/bg_prk/26cant_y/index.idx",
|
||||
"hdimage/picture/bg_prk/27dyna_y/0.gcz",
|
||||
"hdimage/picture/bg_prk/27dyna_y/index.idx",
|
||||
"hdimage/picture/bg_prk/30jlousy/0.gcz",
|
||||
"hdimage/picture/bg_prk/30jlousy/index.idx",
|
||||
"hdimage/picture/bg_prk/32aisiat/0.gcz",
|
||||
"hdimage/picture/bg_prk/32aisiat/index.idx",
|
||||
"hdimage/picture/bg_prk/33dx_eur/0.gcz",
|
||||
"hdimage/picture/bg_prk/33dx_eur/index.idx",
|
||||
"hdimage/picture/bg_prk/34ale/0.gcz",
|
||||
"hdimage/picture/bg_prk/34ale/index.idx",
|
||||
"hdimage/picture/bg_prk/35banana/0.gcz",
|
||||
"hdimage/picture/bg_prk/35banana/index.idx",
|
||||
"hdimage/picture/bg_prk/36madeof/0.gcz",
|
||||
"hdimage/picture/bg_prk/36madeof/index.idx",
|
||||
"hdimage/picture/bg_prk/37myfire/0.gcz",
|
||||
"hdimage/picture/bg_prk/37myfire/index.idx",
|
||||
"hdimage/picture/bg_prk/38bndler/0.gcz",
|
||||
"hdimage/picture/bg_prk/38bndler/index.idx",
|
||||
"hdimage/picture/bg_prk/39plywit/0.gcz",
|
||||
"hdimage/picture/bg_prk/39plywit/index.idx",
|
||||
"hdimage/picture/bg_prk/40soclos/0.gcz",
|
||||
"hdimage/picture/bg_prk/40soclos/index.idx",
|
||||
"hdimage/picture/bg_prk/41slmio/0.gcz",
|
||||
"hdimage/picture/bg_prk/41slmio/index.idx",
|
||||
"hdimage/picture/bg_prk/42hyaku/0.gcz",
|
||||
"hdimage/picture/bg_prk/42hyaku/index.idx",
|
||||
"hdimage/picture/bg_prk/43money/0.gcz",
|
||||
"hdimage/picture/bg_prk/43money/index.idx",
|
||||
"hdimage/picture/bg_prk/44vl2000/0.gcz",
|
||||
"hdimage/picture/bg_prk/44vl2000/index.idx",
|
||||
"hdimage/picture/bg_prk/45dancer/0.gcz",
|
||||
"hdimage/picture/bg_prk/45dancer/index.idx",
|
||||
"hdimage/picture/bg_prk/46feelin/0.gcz",
|
||||
"hdimage/picture/bg_prk/46feelin/index.idx",
|
||||
"hdimage/picture/bg_prk/47wetwo/0.gcz",
|
||||
"hdimage/picture/bg_prk/47wetwo/index.idx",
|
||||
"hdimage/picture/bg_prk/48kiss3/0.gcz",
|
||||
"hdimage/picture/bg_prk/48kiss3/index.idx",
|
||||
"hdimage/picture/bg_prk/49statio/0.gcz",
|
||||
"hdimage/picture/bg_prk/49statio/index.idx",
|
||||
"hdimage/picture/bg_prk/50jam/0.gcz",
|
||||
"hdimage/picture/bg_prk/50jam/index.idx",
|
||||
"hdimage/picture/bg_prk/51number/0.gcz",
|
||||
"hdimage/picture/bg_prk/51number/index.idx",
|
||||
"hdimage/picture/bg_prk/52dltcom/0.gcz",
|
||||
"hdimage/picture/bg_prk/52dltcom/index.idx",
|
||||
"hdimage/picture/bg_prk/53blieve/0.gcz",
|
||||
"hdimage/picture/bg_prk/53blieve/index.idx",
|
||||
"hdimage/picture/bg_prk/56popten/0.gcz",
|
||||
"hdimage/picture/bg_prk/56popten/index.idx",
|
||||
"hdimage/picture/bg_prk/57takeme/0.gcz",
|
||||
"hdimage/picture/bg_prk/57takeme/index.idx",
|
||||
"hdimage/picture/bg_prk/58esybsy/0.gcz",
|
||||
"hdimage/picture/bg_prk/58esybsy/index.idx",
|
||||
"hdimage/picture/bg_prk/59sexy3/0.gcz",
|
||||
"hdimage/picture/bg_prk/59sexy3/index.idx",
|
||||
"hdimage/picture/bg_prk/63mikado/0.gcz",
|
||||
"hdimage/picture/bg_prk/63mikado/index.idx",
|
||||
"hdimage/picture/bg_prk/64dejavu/0.gcz",
|
||||
"hdimage/picture/bg_prk/64dejavu/index.idx",
|
||||
"hdimage/picture/bg_prk/65ultr_y/0.gcz",
|
||||
"hdimage/picture/bg_prk/65ultr_y/index.idx",
|
||||
"hdimage/picture/bg_prk/66brok_y/0.gcz",
|
||||
"hdimage/picture/bg_prk/66brok_y/index.idx",
|
||||
"hdimage/picture/sys/cleared/0.gcz",
|
||||
"hdimage/picture/sys/company1/0.gcz",
|
||||
"hdimage/picture/sys/company1/system.idx",
|
||||
"hdimage/picture/sys/company2/0.gcz",
|
||||
"hdimage/picture/sys/company2/system.idx",
|
||||
"hdimage/picture/sys/edparako/0.gcz",
|
||||
"hdimage/picture/sys/edparako/system.idx",
|
||||
"hdimage/picture/sys/ed_free/0.gcz",
|
||||
"hdimage/picture/sys/ed_free/1.gcz",
|
||||
"hdimage/picture/sys/ed_free/system.idx",
|
||||
"hdimage/picture/sys/ed_para/0.gcz",
|
||||
"hdimage/picture/sys/ed_para/1.gcz",
|
||||
"hdimage/picture/sys/ed_para/system.idx",
|
||||
"hdimage/picture/sys/failed/0.gcz",
|
||||
"hdimage/picture/sys/frame_n/0.gcz",
|
||||
"hdimage/picture/sys/frame_p/0.gcz",
|
||||
"hdimage/picture/sys/frame_p/1.gcz",
|
||||
"hdimage/picture/sys/gameover/0.gcz",
|
||||
"hdimage/picture/sys/gameover/system.idx",
|
||||
"hdimage/picture/sys/in_rank/0.gcz",
|
||||
"hdimage/picture/sys/in_rank/system.idx",
|
||||
"hdimage/picture/sys/keikoku/0.gcz",
|
||||
"hdimage/picture/sys/keikoku/system.idx",
|
||||
"hdimage/picture/sys/keikok_e/0.gcz",
|
||||
"hdimage/picture/sys/keikok_e/system.idx",
|
||||
"hdimage/picture/sys/linkwait/0.gcz",
|
||||
"hdimage/picture/sys/name/0.gcz",
|
||||
"hdimage/picture/sys/name/system.idx",
|
||||
"hdimage/picture/sys/ned_free/0.gcz",
|
||||
"hdimage/picture/sys/ned_free/system.idx",
|
||||
"hdimage/picture/sys/ned_para/0.gcz",
|
||||
"hdimage/picture/sys/ned_para/system.idx",
|
||||
"hdimage/picture/sys/ranking/0.gcz",
|
||||
"hdimage/picture/sys/ranking/system.idx",
|
||||
"hdimage/picture/sys/select/0.gcz",
|
||||
"hdimage/picture/sys/select/1.gcz",
|
||||
"hdimage/picture/sys/select/system.idx",
|
||||
"hdimage/picture/sys/system/0.gcz",
|
||||
"hdimage/picture/sys/testmode/0.gcz",
|
||||
"hdimage/picture/sys/title/0.gcz",
|
||||
"hdimage/picture/sys/t_result/0.gcz",
|
||||
"hdimage/picture/sys/t_result/system.idx",
|
||||
"hdimage/sound/adpcm/01speedw.adp",
|
||||
"hdimage/sound/adpcm/02eurobe.adp",
|
||||
"hdimage/sound/adpcm/03nghtof.adp",
|
||||
"hdimage/sound/adpcm/04tryme.adp",
|
||||
"hdimage/sound/adpcm/05yestdy.adp",
|
||||
"hdimage/sound/adpcm/06likvgn.adp",
|
||||
"hdimage/sound/adpcm/07tora.adp",
|
||||
"hdimage/sound/adpcm/08arabia.adp",
|
||||
"hdimage/sound/adpcm/09boom2f.adp",
|
||||
"hdimage/sound/adpcm/10mickey.adp",
|
||||
"hdimage/sound/adpcm/11crzy4u.adp",
|
||||
"hdimage/sound/adpcm/12remmbr.adp",
|
||||
"hdimage/sound/adpcm/13stay.adp",
|
||||
"hdimage/sound/adpcm/14romeo.adp",
|
||||
"hdimage/sound/adpcm/15burnin.adp",
|
||||
"hdimage/sound/adpcm/16kingdm.adp",
|
||||
"hdimage/sound/adpcm/17godzil.adp",
|
||||
"hdimage/sound/adpcm/18holdon.adp",
|
||||
"hdimage/sound/adpcm/19lvagan.adp",
|
||||
"hdimage/sound/adpcm/20iwdanc.adp",
|
||||
"hdimage/sound/adpcm/21anvs.adp",
|
||||
"hdimage/sound/adpcm/22engylv.adp",
|
||||
"hdimage/sound/adpcm/23luv2_y.adp",
|
||||
"hdimage/sound/adpcm/24sysstr.adp",
|
||||
"hdimage/sound/adpcm/25systek.adp",
|
||||
"hdimage/sound/adpcm/26cant_y.adp",
|
||||
"hdimage/sound/adpcm/27dyna_y.adp",
|
||||
"hdimage/sound/adpcm/28sysnam.adp",
|
||||
"hdimage/sound/adpcm/29sysstr.adp",
|
||||
"hdimage/sound/adpcm/30jlousy.adp",
|
||||
"hdimage/sound/adpcm/31sysetc.adp",
|
||||
"hdimage/sound/adpcm/32aisiat.adp",
|
||||
"hdimage/sound/adpcm/33dx_eur.adp",
|
||||
"hdimage/sound/adpcm/34ale.adp",
|
||||
"hdimage/sound/adpcm/35banana.adp",
|
||||
"hdimage/sound/adpcm/36madeof.adp",
|
||||
"hdimage/sound/adpcm/37myfire.adp",
|
||||
"hdimage/sound/adpcm/38bndler.adp",
|
||||
"hdimage/sound/adpcm/39plywit.adp",
|
||||
"hdimage/sound/adpcm/40soclos.adp",
|
||||
"hdimage/sound/adpcm/41slmio.adp",
|
||||
"hdimage/sound/adpcm/42hyaku.adp",
|
||||
"hdimage/sound/adpcm/43mon_s.adp",
|
||||
"hdimage/sound/adpcm/44vl2000.adp",
|
||||
"hdimage/sound/adpcm/45dancer.adp",
|
||||
"hdimage/sound/adpcm/46feelin.adp",
|
||||
"hdimage/sound/adpcm/47wetwo.adp",
|
||||
"hdimage/sound/adpcm/48kiss3.adp",
|
||||
"hdimage/sound/adpcm/49statio.adp",
|
||||
"hdimage/sound/adpcm/50jam.adp",
|
||||
"hdimage/sound/adpcm/51number.adp",
|
||||
"hdimage/sound/adpcm/52dltcom.adp",
|
||||
"hdimage/sound/adpcm/53blieve.adp",
|
||||
"hdimage/sound/adpcm/54mikado.adp",
|
||||
"hdimage/sound/adpcm/55dejavu.adp",
|
||||
"hdimage/sound/adpcm/56popten.adp",
|
||||
"hdimage/sound/adpcm/57takeme.adp",
|
||||
"hdimage/sound/adpcm/58esybsy.adp",
|
||||
"hdimage/sound/adpcm/59sexy3.adp",
|
||||
"hdimage/sound/adpcm/60syslop.adp",
|
||||
"hdimage/sound/adpcm/65ultr_y.adp",
|
||||
"hdimage/sound/adpcm/66brok_y.adp",
|
||||
"hdimage/sound/adpcm/ending.adp",
|
||||
"hdimage/sound/playdata/01_1.bin",
|
||||
"hdimage/sound/playdata/01_a.bin",
|
||||
"hdimage/sound/playdata/01_b.bin",
|
||||
"hdimage/sound/playdata/01_c.bin",
|
||||
"hdimage/sound/playdata/02_1.bin",
|
||||
"hdimage/sound/playdata/02_a.bin",
|
||||
"hdimage/sound/playdata/02_b.bin",
|
||||
"hdimage/sound/playdata/02_c.bin",
|
||||
"hdimage/sound/playdata/03_1.bin",
|
||||
"hdimage/sound/playdata/03_a.bin",
|
||||
"hdimage/sound/playdata/03_b.bin",
|
||||
"hdimage/sound/playdata/03_c.bin",
|
||||
"hdimage/sound/playdata/04_1.bin",
|
||||
"hdimage/sound/playdata/04_a.bin",
|
||||
"hdimage/sound/playdata/04_b.bin",
|
||||
"hdimage/sound/playdata/04_c.bin",
|
||||
"hdimage/sound/playdata/05_1.bin",
|
||||
"hdimage/sound/playdata/05_a.bin",
|
||||
"hdimage/sound/playdata/05_b.bin",
|
||||
"hdimage/sound/playdata/05_c.bin",
|
||||
"hdimage/sound/playdata/06_1.bin",
|
||||
"hdimage/sound/playdata/06_a.bin",
|
||||
"hdimage/sound/playdata/06_b.bin",
|
||||
"hdimage/sound/playdata/06_c.bin",
|
||||
"hdimage/sound/playdata/07_1.bin",
|
||||
"hdimage/sound/playdata/07_a.bin",
|
||||
"hdimage/sound/playdata/07_b.bin",
|
||||
"hdimage/sound/playdata/07_c.bin",
|
||||
"hdimage/sound/playdata/08_1.bin",
|
||||
"hdimage/sound/playdata/08_a.bin",
|
||||
"hdimage/sound/playdata/08_b.bin",
|
||||
"hdimage/sound/playdata/08_c.bin",
|
||||
"hdimage/sound/playdata/09_1.bin",
|
||||
"hdimage/sound/playdata/09_a.bin",
|
||||
"hdimage/sound/playdata/09_b.bin",
|
||||
"hdimage/sound/playdata/09_c.bin",
|
||||
"hdimage/sound/playdata/10_1.bin",
|
||||
"hdimage/sound/playdata/10_a.bin",
|
||||
"hdimage/sound/playdata/10_b.bin",
|
||||
"hdimage/sound/playdata/10_c.bin",
|
||||
"hdimage/sound/playdata/11_1.bin",
|
||||
"hdimage/sound/playdata/11_a.bin",
|
||||
"hdimage/sound/playdata/11_b.bin",
|
||||
"hdimage/sound/playdata/11_c.bin",
|
||||
"hdimage/sound/playdata/12_1.bin",
|
||||
"hdimage/sound/playdata/12_a.bin",
|
||||
"hdimage/sound/playdata/12_b.bin",
|
||||
"hdimage/sound/playdata/12_c.bin",
|
||||
"hdimage/sound/playdata/13_1.bin",
|
||||
"hdimage/sound/playdata/13_a.bin",
|
||||
"hdimage/sound/playdata/13_b.bin",
|
||||
"hdimage/sound/playdata/13_c.bin",
|
||||
"hdimage/sound/playdata/14_1.bin",
|
||||
"hdimage/sound/playdata/14_a.bin",
|
||||
"hdimage/sound/playdata/14_b.bin",
|
||||
"hdimage/sound/playdata/14_c.bin",
|
||||
"hdimage/sound/playdata/15_1.bin",
|
||||
"hdimage/sound/playdata/15_a.bin",
|
||||
"hdimage/sound/playdata/15_b.bin",
|
||||
"hdimage/sound/playdata/15_c.bin",
|
||||
"hdimage/sound/playdata/16_1.bin",
|
||||
"hdimage/sound/playdata/16_a.bin",
|
||||
"hdimage/sound/playdata/16_b.bin",
|
||||
"hdimage/sound/playdata/16_c.bin",
|
||||
"hdimage/sound/playdata/17_1.bin",
|
||||
"hdimage/sound/playdata/17_a.bin",
|
||||
"hdimage/sound/playdata/17_b.bin",
|
||||
"hdimage/sound/playdata/17_c.bin",
|
||||
"hdimage/sound/playdata/18_1.bin",
|
||||
"hdimage/sound/playdata/18_a.bin",
|
||||
"hdimage/sound/playdata/18_b.bin",
|
||||
"hdimage/sound/playdata/18_c.bin",
|
||||
"hdimage/sound/playdata/19_1.bin",
|
||||
"hdimage/sound/playdata/19_a.bin",
|
||||
"hdimage/sound/playdata/19_b.bin",
|
||||
"hdimage/sound/playdata/19_c.bin",
|
||||
"hdimage/sound/playdata/20_1.bin",
|
||||
"hdimage/sound/playdata/20_a.bin",
|
||||
"hdimage/sound/playdata/20_b.bin",
|
||||
"hdimage/sound/playdata/20_c.bin",
|
||||
"hdimage/sound/playdata/21_1.bin",
|
||||
"hdimage/sound/playdata/21_a.bin",
|
||||
"hdimage/sound/playdata/21_b.bin",
|
||||
"hdimage/sound/playdata/21_c.bin",
|
||||
"hdimage/sound/playdata/22_1.bin",
|
||||
"hdimage/sound/playdata/22_a.bin",
|
||||
"hdimage/sound/playdata/22_b.bin",
|
||||
"hdimage/sound/playdata/22_c.bin",
|
||||
"hdimage/sound/playdata/23_1.bin",
|
||||
"hdimage/sound/playdata/23_a.bin",
|
||||
"hdimage/sound/playdata/23_b.bin",
|
||||
"hdimage/sound/playdata/23_c.bin",
|
||||
"hdimage/sound/playdata/26_1.bin",
|
||||
"hdimage/sound/playdata/26_a.bin",
|
||||
"hdimage/sound/playdata/26_b.bin",
|
||||
"hdimage/sound/playdata/26_c.bin",
|
||||
"hdimage/sound/playdata/27_1.bin",
|
||||
"hdimage/sound/playdata/27_a.bin",
|
||||
"hdimage/sound/playdata/27_b.bin",
|
||||
"hdimage/sound/playdata/27_c.bin",
|
||||
"hdimage/sound/playdata/30_1.bin",
|
||||
"hdimage/sound/playdata/30_a.bin",
|
||||
"hdimage/sound/playdata/30_b.bin",
|
||||
"hdimage/sound/playdata/30_c.bin",
|
||||
"hdimage/sound/playdata/32_1.bin",
|
||||
"hdimage/sound/playdata/32_a.bin",
|
||||
"hdimage/sound/playdata/32_b.bin",
|
||||
"hdimage/sound/playdata/32_c.bin",
|
||||
"hdimage/sound/playdata/33_1.bin",
|
||||
"hdimage/sound/playdata/33_a.bin",
|
||||
"hdimage/sound/playdata/33_b.bin",
|
||||
"hdimage/sound/playdata/33_c.bin",
|
||||
"hdimage/sound/playdata/34_1.bin",
|
||||
"hdimage/sound/playdata/34_a.bin",
|
||||
"hdimage/sound/playdata/34_b.bin",
|
||||
"hdimage/sound/playdata/34_c.bin",
|
||||
"hdimage/sound/playdata/35_1.bin",
|
||||
"hdimage/sound/playdata/35_a.bin",
|
||||
"hdimage/sound/playdata/35_b.bin",
|
||||
"hdimage/sound/playdata/35_c.bin",
|
||||
"hdimage/sound/playdata/36_1.bin",
|
||||
"hdimage/sound/playdata/36_a.bin",
|
||||
"hdimage/sound/playdata/36_b.bin",
|
||||
"hdimage/sound/playdata/36_c.bin",
|
||||
"hdimage/sound/playdata/37_1.bin",
|
||||
"hdimage/sound/playdata/37_a.bin",
|
||||
"hdimage/sound/playdata/37_b.bin",
|
||||
"hdimage/sound/playdata/37_c.bin",
|
||||
"hdimage/sound/playdata/38_1.bin",
|
||||
"hdimage/sound/playdata/38_a.bin",
|
||||
"hdimage/sound/playdata/38_b.bin",
|
||||
"hdimage/sound/playdata/38_c.bin",
|
||||
"hdimage/sound/playdata/39_1.bin",
|
||||
"hdimage/sound/playdata/39_a.bin",
|
||||
"hdimage/sound/playdata/39_b.bin",
|
||||
"hdimage/sound/playdata/39_c.bin",
|
||||
"hdimage/sound/playdata/40_1.bin",
|
||||
"hdimage/sound/playdata/40_a.bin",
|
||||
"hdimage/sound/playdata/40_b.bin",
|
||||
"hdimage/sound/playdata/40_c.bin",
|
||||
"hdimage/sound/playdata/41_1.bin",
|
||||
"hdimage/sound/playdata/41_a.bin",
|
||||
"hdimage/sound/playdata/41_b.bin",
|
||||
"hdimage/sound/playdata/41_c.bin",
|
||||
"hdimage/sound/playdata/42_1.bin",
|
||||
"hdimage/sound/playdata/42_a.bin",
|
||||
"hdimage/sound/playdata/42_b.bin",
|
||||
"hdimage/sound/playdata/42_c.bin",
|
||||
"hdimage/sound/playdata/43_1.bin",
|
||||
"hdimage/sound/playdata/43_a.bin",
|
||||
"hdimage/sound/playdata/43_b.bin",
|
||||
"hdimage/sound/playdata/43_c.bin",
|
||||
"hdimage/sound/playdata/44_1.bin",
|
||||
"hdimage/sound/playdata/44_a.bin",
|
||||
"hdimage/sound/playdata/44_b.bin",
|
||||
"hdimage/sound/playdata/44_c.bin",
|
||||
"hdimage/sound/playdata/45_1.bin",
|
||||
"hdimage/sound/playdata/45_a.bin",
|
||||
"hdimage/sound/playdata/45_b.bin",
|
||||
"hdimage/sound/playdata/45_c.bin",
|
||||
"hdimage/sound/playdata/46_1.bin",
|
||||
"hdimage/sound/playdata/46_a.bin",
|
||||
"hdimage/sound/playdata/46_b.bin",
|
||||
"hdimage/sound/playdata/46_c.bin",
|
||||
"hdimage/sound/playdata/47_1.bin",
|
||||
"hdimage/sound/playdata/47_a.bin",
|
||||
"hdimage/sound/playdata/47_b.bin",
|
||||
"hdimage/sound/playdata/47_c.bin",
|
||||
"hdimage/sound/playdata/48_1.bin",
|
||||
"hdimage/sound/playdata/48_a.bin",
|
||||
"hdimage/sound/playdata/48_b.bin",
|
||||
"hdimage/sound/playdata/48_c.bin",
|
||||
"hdimage/sound/playdata/49_1.bin",
|
||||
"hdimage/sound/playdata/49_a.bin",
|
||||
"hdimage/sound/playdata/49_b.bin",
|
||||
"hdimage/sound/playdata/49_c.bin",
|
||||
"hdimage/sound/playdata/50_1.bin",
|
||||
"hdimage/sound/playdata/50_a.bin",
|
||||
"hdimage/sound/playdata/50_c.bin",
|
||||
"hdimage/sound/playdata/51_1.bin",
|
||||
"hdimage/sound/playdata/51_a.bin",
|
||||
"hdimage/sound/playdata/51_c.bin",
|
||||
"hdimage/sound/playdata/52_1.bin",
|
||||
"hdimage/sound/playdata/52_a.bin",
|
||||
"hdimage/sound/playdata/52_c.bin",
|
||||
"hdimage/sound/playdata/53_1.bin",
|
||||
"hdimage/sound/playdata/53_a.bin",
|
||||
"hdimage/sound/playdata/53_c.bin",
|
||||
"hdimage/sound/playdata/56_1.bin",
|
||||
"hdimage/sound/playdata/56_a.bin",
|
||||
"hdimage/sound/playdata/56_c.bin",
|
||||
"hdimage/sound/playdata/57_1.bin",
|
||||
"hdimage/sound/playdata/57_a.bin",
|
||||
"hdimage/sound/playdata/57_c.bin",
|
||||
"hdimage/sound/playdata/58_1.bin",
|
||||
"hdimage/sound/playdata/58_a.bin",
|
||||
"hdimage/sound/playdata/58_c.bin",
|
||||
"hdimage/sound/playdata/59_1.bin",
|
||||
"hdimage/sound/playdata/59_a.bin",
|
||||
"hdimage/sound/playdata/59_c.bin",
|
||||
"hdimage/sound/playdata/63_1.bin",
|
||||
"hdimage/sound/playdata/63_a.bin",
|
||||
"hdimage/sound/playdata/63_c.bin",
|
||||
"hdimage/sound/playdata/64_1.bin",
|
||||
"hdimage/sound/playdata/64_a.bin",
|
||||
"hdimage/sound/playdata/64_c.bin",
|
||||
"hdimage/sound/playdata/65_1.bin",
|
||||
"hdimage/sound/playdata/65_a.bin",
|
||||
"hdimage/sound/playdata/65_c.bin",
|
||||
"hdimage/sound/playdata/66_1.bin",
|
||||
"hdimage/sound/playdata/66_a.bin",
|
||||
"hdimage/sound/playdata/66_c.bin",
|
||||
"hdimage/sound/playdata/sys_h.bin",
|
||||
"hdimage/sound/system/ppp2nd.bin",
|
||||
"hdimage/sound/system/ppp2nd.mb",
|
||||
"hdimage/sound/testmode/o4ao5a.adp",
|
||||
"sound/prog.zin",
|
||||
]
|
||||
|
||||
filename_hashes = {}
|
||||
for filename in found_filenames:
|
||||
filename_hashes[get_filename_hash(filename)] = filename
|
||||
|
||||
# Parse file table
|
||||
with open(args.input, "rb") as infile:
|
||||
infile.seek(0x200)
|
||||
|
||||
entries = []
|
||||
while True:
|
||||
filename_hash, file_offset, file_size, flags, checksum, decomp_size = 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,
|
||||
'decomp_size': decomp_size,
|
||||
'flags': flags,
|
||||
'checksum': checksum,
|
||||
}
|
||||
|
||||
entries.append(entry)
|
||||
|
||||
for entry in entries:
|
||||
print("%08x %08x %08x %08x %08x %08x" % (entry['filename_hash'], entry['offset'], entry['size'], entry['decomp_size'], entry['flags'], entry['checksum']), filename_hashes.get(entry['filename_hash'], ""))
|
||||
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:
|
||||
data = infile.read(entry['size'])
|
||||
|
||||
is_zin = output_filename.endswith(".zin") or entry['filename_hash'] in [0x90547703]
|
||||
if is_zin:
|
||||
# .zin files (and the bootloader) are supposed to be checksummed before decompression
|
||||
checksum = sum(data) & 0xffffffff
|
||||
|
||||
if entry['decomp_size'] != 0:
|
||||
data = decompress_gcz(data)
|
||||
|
||||
if not is_zin:
|
||||
# Calculate checksum of extracted data
|
||||
checksum = sum(data) & 0xffffffff
|
||||
|
||||
outfile.write(data)
|
||||
|
||||
if checksum != entry['checksum']:
|
||||
print("Invalid checksum for extracted data!")
|
Loading…
Reference in New Issue