Initial tools commit

This commit is contained in:
987123879113 2021-04-24 22:46:33 +09:00
commit 29c72411f2
33 changed files with 12497 additions and 0 deletions

3
README.md Normal file
View File

@ -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.

17
python1/README.md Normal file
View File

@ -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
```

514
python1/python1_dumper.py Normal file
View File

@ -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

View File

@ -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>

51
sys573/jamma/ddr.md Normal file
View File

@ -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>

53
sys573/jamma/drummania.md Normal file
View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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.

View File

@ -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))

View File

@ -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)

38
sys573/py573a/README.md Normal file
View File

@ -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.

6717
sys573/py573a/db.json Normal file

File diff suppressed because it is too large Load Diff

227
sys573/py573a/enc573.pyx Normal file
View File

@ -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

326
sys573/py573a/py573a.py Normal file
View File

@ -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()

6
sys573/py573a/setup.py Normal file
View File

@ -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)
)

View File

@ -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.

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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.

View File

@ -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)
)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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)))

View File

@ -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)))

View File

@ -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()

16
viper/README.md Normal file
View File

@ -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
```

878
viper/ppp2nd_dumper.py Normal file
View File

@ -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!")