Initial tools commit

pull/5/head
987123879113 2 years ago
commit 29c72411f2
  1. 3
      README.md
  2. 17
      python1/README.md
  3. 514
      python1/python1_dumper.py
  4. 51
      sys573/jamma/dancemaniax.md
  5. 51
      sys573/jamma/ddr.md
  6. 53
      sys573/jamma/drummania.md
  7. 53
      sys573/jamma/guitarfreaks.md
  8. 51
      sys573/jamma/mamboagogo.md
  9. 20
      sys573/msudecrypt/README.md
  10. 21
      sys573/msudecrypt/msu_ebf_fix_checksum.py
  11. 100
      sys573/msudecrypt/msudecrypt.py
  12. 38
      sys573/py573a/README.md
  13. 6717
      sys573/py573a/db.json
  14. 227
      sys573/py573a/enc573.pyx
  15. 326
      sys573/py573a/py573a.py
  16. 6
      sys573/py573a/setup.py
  17. 57
      sys573/sys573tool/README.md
  18. 429
      sys573/sys573tool/build_sys573_gamefs.py
  19. 86
      sys573/sys573tool/calc_checksum.py
  20. 423
      sys573/sys573tool/comp573.pyx
  21. 1085
      sys573/sys573tool/dump_sys573_gamefs.py
  22. 243
      sys573/sys573tool/enc573.pyx
  23. BIN
      sys573/sys573tool/hash_list.pkl
  24. 6
      sys573/sys573tool/setup.py
  25. 103
      sys573/sys573tool/sum573.pyx
  26. 23
      sys573/sys573tool/sys573tool.py
  27. 91
      sys573/tools/dump_pythonfs_eamuse_hdd.py
  28. 52
      sys573/tools/gfdm_genkey.py
  29. 374
      sys573/tools/parse_charts_dct.py
  30. 333
      sys573/tools/parse_charts_disneysrave.py
  31. 50
      sys573/tools/sys573_config_tool.py
  32. 16
      viper/README.md
  33. 878
      viper/ppp2nd_dumper.py

@ -0,0 +1,3 @@
# gobbletools
All tools in this repository are provided as-is. These tools were all made for my own personal use only. I am uploading them because the tools contain information that can be helpful to other people who want to work with these formats. If you find a bug or missing functionality then I will accept pull requests.

@ -0,0 +1,17 @@
# Python1 Tools
### python1_dumper.py
This tool lets you dump data from pop'n music Python 1 HDDs. It can be modified to work on other Python 1 games but the filename pattern matching is for pop'n music. It will not find all filenames, and if used on other games, it will just output filenames based on the filename hash.
```
usage: python1_dumper.py [-h] -i INPUT [-o OUTPUT] [-d]
optional arguments:
-h, --help show this help message and exit
-i INPUT, --input INPUT
Input folder
-o OUTPUT, --output OUTPUT
Output folder
-d, --decrypt Decryption for pop'n 13 and 14
```

@ -0,0 +1,514 @@
import argparse
import ctypes
import os
import string
import struct
def get_filename_hash(filename):
hash = 0
for cidx, c in enumerate(filename):
for i in range(6):
hash = ctypes.c_int(((hash >> 31) & 0x4c11db7) ^ ((hash << 1) | ((ord(c) >> i) & 1))).value
return hash & 0xffffffff
def decrypt_data(data):
from Crypto.Cipher import Blowfish
keys = {
'13G0': {'iv': 'desvsrow', 'key': 'パパパヤーぶっちゃけPOP’N大迷惑'},
'13S0': {'iv': 'wacvsjun', 'key': 'いろはにほへぽちりぬるぽ'},
'13P0': {'iv': 'tvCxT5no', 'key': 'キャホーイ!祭りだ!わっしょこい!'},
'13G1': {'iv': 'LTgStteb', 'key': 'いつだってお祭りさわぎのカーニバル。なんでもあるよ〜。'},
'13G2': {'iv': 'wNyxSyDQ', 'key': 'ドンドンチキチキカーニバル!'},
'13G3': {'iv': 'wClBXmGr', 'key': 'ぼくらの街にやってきた素敵で愉快なカーニバル'},
'13G4': {'iv': 'PiWHlj56', 'key': 'さぁさぁ\u3000みなさんお待ちかね\u3000とってもゆかいなカーニバル!'},
'13G5': {'iv': 'dkWEN8Qk', 'key': 'いつだってお祭りさわぎのカーニバル。なんでもあるよ〜。'},
'13G6': {'iv': 'WmPMVqCr', 'key': 'わくわくカーニバル!なにで遊ぶかまよっちゃう〜'},
'13G7': {'iv': '2V00iGp5', 'key': 'リズムにのってボタンを叩けば、わくわくカーニバル・オン・ステージ!'},
'13G8': {'iv': 'WlMhESmP', 'key': 'ようこそ、きらめきカーニバルへ'},
'13G9': {'iv': 'cisXpifl', 'key': 'わくわく\u3000はじまる\u3000カーニバル!'},
'13S1': {'iv': 'C47vuHWv', 'key': 'カーニバルがやってきた'},
'13S2': {'iv': 'DUtTySYu', 'key': 'ポップンカーニバルのお通りだい'},
'13S3': {'iv': 'bLMNB8DN', 'key': 'ポップンカーニバルでココロうきうき'},
'13S4': {'iv': 'o5INlY6V', 'key': 'さわげやポップン'},
'13S5': {'iv': '2brvTlYu', 'key': '集まれポップンカーニバル'},
'13S6': {'iv': 'grnOhljs', 'key': 'ポップンサーカスが街にやってきた!'},
'13S7': {'iv': '43fFbQqx', 'key': '吃驚仰天摩訶不思議'},
'13S8': {'iv': 'T9je5pa5', 'key': 'ぼくらの街にやってきた素敵で愉快なカーニバル'},
'13S9': {'iv': 'zzAqUyUa', 'key': '摩訶不思議な宴をお楽しみあそばせ。'},
'13P1': {'iv': 'XfXeyeGD', 'key': 'ぼくもわたしもお祭り大好き'},
'13P2': {'iv': 'hUgqKFXC', 'key': 'うかれてはじけて'},
'13P3': {'iv': 'MgOREWWd', 'key': 'みんなの町にやってきた!'},
'13P4': {'iv': 'vvDq1vyZ', 'key': '夢のチケット発売中。'},
'13P5': {'iv': 'KvdgJSRY', 'key': '聞こえてくるよ遠くから'},
'13P6': {'iv': 'K8osNm4z', 'key': '乙女心に恋心'},
'13P7': {'iv': 'AToePkrl', 'key': 'さぁさぁみんな寄っといで!'},
'13P8': {'iv': 'N2JPvhOd', 'key': '楽しい音楽ショー'},
'13P9': {'iv': 'NZuuPltz', 'key': '火の輪くぐりに\u3000トライアゲエイン'},
'13A0': {'iv': 'rP2eb6LK', 'key': 'さあ!ハッピータイム'},
'13A1': {'iv': 'X9YNso6d', 'key': 'サーカスがぼくらの街にやってくるくるミラクル'},
'13A2': {'iv': 'oP2U6O7R', 'key': '世界じゅうの音楽のどきわく大行進ー♪お祭り騒ぎはまだまだ続くよ。'},
'13A3': {'iv': 'MzkBVL9m', 'key': 'すてきなショーの\u3000\u3000'},
'13A4': {'iv': 'AbzKfkud', 'key': '見てるだけじゃ楽しくないない'},
'13A5': {'iv': 'sqYSM25b', 'key': 'みんなでお祭り盛り上げろ。'},
'13A6': {'iv': 'R63VSaLE', 'key': 'あの娘もポワンと頬染める。'},
'13A7': {'iv': 'K5eDOnXm', 'key': 'ひとときの夢をごらんあれ〜'},
'13A8': {'iv': 'ww6JPyWf', 'key': '1人で・2人で・3人で!'},
'13A9': {'iv': 'XS7JUaUJ', 'key': 'うっふんもあるでよ〜。'},
}
try:
key_info = keys[data[:4].decode('ascii')]
enc_len = int.from_bytes(data[4:8], 'little')
cipher = Blowfish.new(key_info['key'].encode('shift-jis'), Blowfish.MODE_CBC, key_info['iv'].encode('ascii'))
dec_data = cipher.decrypt(data)[8:]
return bytearray(dec_data)[:enc_len]
except:
# Not encrypted?
return data
def decompress_gcz(data):
data_length = len(data)
offset = 0
output = []
while offset < data_length:
flag = data[offset]
offset += 1
for bit in range(8):
if flag & (1 << bit):
output.append(data[offset])
offset += 1
else:
if offset + 2 > data_length:
break
cmd1, cmd2 = data[offset:offset+2]
lookback_length = (cmd1 & 0x0f) + 3
lookback_offset = ((cmd1 & 0xf0) << 4) + cmd2
offset += 2
if cmd1 == 0 and cmd2 == 0:
break
for _ in range(lookback_length):
loffset = len(output) - lookback_offset
if loffset <= 0 or loffset >= len(output):
output.append(0)
else:
output.append(output[loffset])
return bytearray(output)
def get_file_data(infile, fileinfo):
cur_offset = infile.tell()
infile.seek(fileinfo['offset'])
data = infile.read(fileinfo['size'])
infile.seek(cur_offset)
return data
def parse_system_idx(data):
filename_hashes_add = {}
count = int.from_bytes(data[6:8], byteorder="little")
stroff = int.from_bytes(data[8:10], byteorder="little") - 0x180
for i in range(count):
filename = data[stroff+i*0x20:stroff+(i+1)*0x20].decode('ascii').strip('\0').lstrip('/')
filename = "image/%s" % filename.lower()
filename_hashes_add[get_filename_hash(filename)] = filename
return filename_hashes_add
def find_strings_binary(exe_data, search_key):
found_strings = []
i = 0
while i < len(exe_data) - len(search_key):
if exe_data[i:i+len(search_key)] == search_key or exe_data[i:i+len(search_key)] == search_key.upper() or exe_data[i:i+len(search_key)] == search_key.lower():
str_start = i
str_end = i + len(search_key)
while exe_data[str_end] != 0:
str_end += 1
while chr(exe_data[str_start - 1]) in string.printable:
str_start -= 1
found_str = exe_data[str_start:str_end].decode('ascii').lower()
if found_str.startswith("disk0:/"):
found_str = found_str[len("disk0:/"):]
if found_str.startswith("disk0:"):
found_str = found_str[len("disk0:"):]
if found_str[0] == '/':
found_str = found_str[1:]
if '%' not in found_str:
found_strings.append(found_str)
i = str_end + 1
else:
i += 1
return found_strings
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', help='Input folder', default=None, required=True)
parser.add_argument('-o', '--output', help='Output folder', default=None)
parser.add_argument('-d', '--decrypt', help='Decryption for pop\'n 13 and 14', default=False, action='store_true')
args = parser.parse_args()
if not os.path.exists(args.output):
os.makedirs(args.output)
entries = []
with open(args.input, "rb") as infile:
system_files = []
# Not sure how this really works
for offset in [0x20]:
infile.seek(offset)
sysfile_offset = int.from_bytes(infile.read(4), byteorder="little")
sysfile_offset2 = int.from_bytes(infile.read(4), byteorder="little")
infile.seek(offset + 0x10)
sysfile_size = int.from_bytes(infile.read(4), byteorder="big")
checksum = int.from_bytes(infile.read(4), byteorder="big")
system_files.append({
'offsets': [x for x in [sysfile_offset, sysfile_offset2] if x != 0],
'size': sysfile_size,
'checksum': checksum,
})
for offset in [0xa0]:
infile.seek(offset)
sysfile_offset = int.from_bytes(infile.read(4), byteorder="little") * 0x100
infile.seek(offset + 0x10)
sysfile_size = int.from_bytes(infile.read(4), byteorder="little")
infile.seek(4, 1)
checksum = int.from_bytes(infile.read(4), byteorder="little")
system_files.append({
'offsets': [x for x in [sysfile_offset] if x != 0],
'size': sysfile_size,
'checksum': checksum,
})
filetable_offset = 0
for offset in [0x100]:
infile.seek(offset)
sysfile_offset = int.from_bytes(infile.read(4), byteorder="little") * 0x100
sysfile_offset2 = int.from_bytes(infile.read(4), byteorder="little") * 0x100
infile.seek(offset + 0x10)
sysfile_size = int.from_bytes(infile.read(4), byteorder="little")
checksum = int.from_bytes(infile.read(4), byteorder="little")
filetable_offset = sysfile_offset + 0x100000
system_files.append({
'offsets': [x for x in [sysfile_offset, sysfile_offset2] if x != 0],
'size': sysfile_size,
'checksum': checksum,
})
for idx, sysfile in enumerate(system_files):
for idx2, offset in enumerate(sysfile['offsets']):
infile.seek(offset)
print(sysfile)
if idx2 == 0:
output_filename = "sysfile_%d.bin" % idx
else:
output_filename = "sysfile_%d_%d.bin" % (idx, idx2)
raw_data = infile.read(sysfile['size'])
with open(os.path.join(args.output, "raw_" + output_filename), "wb") as outfile:
outfile.write(raw_data)
checksum = sum([int.from_bytes(raw_data[i:i+2], 'big') for i in range(0, len(raw_data), 2)]) & 0x7fffffff
if checksum != sysfile.get('checksum', 0):
print("sysfile %s checksum did not match! %08x vs %08x" % (output_filename, checksum, sysfile.get('checksum', 0)))
try:
dec_data = decompress_gcz(raw_data)
with open(os.path.join(args.output, output_filename), "wb") as outfile:
outfile.write(dec_data)
except:
pass
filenames = [
"ee/boot/game.bin",
"boot/game.bin",
"game.bin",
"boot.bin",
"title.txt",
]
exe_data = open(os.path.join(args.output, "sysfile_2.bin"), "rb").read()
print("Filetable Offset: %08x" % filetable_offset)
infile.seek(filetable_offset)
# Find game.bin if it exists. Hacky piece of shit code
filename_hashes = {}
for filename in filenames:
filename_hashes[get_filename_hash(filename)] = filename
while True:
filename_hash, file_offset, file_size, file_unk, checksum, file_unk3 = struct.unpack("<IIIIII", infile.read(0x18))
if filename_hash == 0:
break
file_offset *= 0x200
entry = {
'filename_hash': filename_hash,
'offset': file_offset,
'size': file_size,
'checksum': checksum,
}
entries.append(entry)
for entry in entries:
infile.seek(entry['offset'])
if entry['filename_hash'] in filename_hashes:
output_filename = filename_hashes[entry['filename_hash']]
else:
continue
output_filename = os.path.join(args.output, output_filename)
if os.path.exists(output_filename):
continue
basedir = os.path.dirname(output_filename)
if basedir and not os.path.exists(basedir):
os.makedirs(basedir)
with open(output_filename, "wb") as outfile:
print(entry)
print(output_filename)
data = infile.read(entry['size'])
if args.decrypt:
data = decrypt_data(data)
outfile.write(data)
exe_path = os.path.join(args.output, "ee", "boot", "game.bin")
if os.path.exists(exe_path):
exe_data += open(exe_path, "rb").read()
print("Found", exe_path)
infile.seek(filetable_offset)
found_filenames = [
'libsd.irx',
'mcman.irx',
'mcserv.irx',
'padman.irx',
'sddrviop.irx',
'sio2man.irx',
'snd.irx',
'snd2.irx',
'filesys.irx',
'filesys2.irx',
]
found_filenames += find_strings_binary(exe_data, b".bin")
found_filenames += find_strings_binary(exe_data, b".gcz")
found_filenames += find_strings_binary(exe_data, b".gzz")
found_filenames += find_strings_binary(exe_data, b".irx")
found_filenames += find_strings_binary(exe_data, b".mhd")
found_filenames += find_strings_binary(exe_data, b".idx")
for folder in ["image/", "patch/", "modules/", "irx/", "iop/", "iop/modules", ""]:
for filename in found_filenames + filenames:
filenames.append(folder + filename)
filenames.append((folder + filename).upper())
filenames.append((folder + filename).lower())
# TODO: Find strings that contain number formats and try to bruteforce them automatically
# Specific files from pop'n music 9
for i1 in range(0, 10):
for i2 in range(0, 10):
for i3 in range(0, 100):
filenames.append("md_xx/md_%d%d%02d.dat" % (i1, i2, i3))
for i in range(0, 1000):
for subfolder in ["", "image/"]:
for ext in ["gcz", "gzz"]:
filenames.append("%snorma/d_n_%03d.%s" % (subfolder, i, ext))
filenames.append("%sbg/bg_%03d.%s" % (subfolder, i, ext))
filenames.append("%snt/nt_%03d.%s" % (subfolder, i, ext))
filenames.append("%skc/kc_%03d.%s" % (subfolder, i, ext))
filenames.append("%skc_normal/banner_%02d.%s" % (subfolder, i, ext))
filenames.append("%srj/rj_%03d.%s" % (subfolder, i, ext))
filenames.append("%scg/cg_%03d.%s" % (subfolder, i, ext))
filenames.append("%sha_art/ha_at%03d.%s" % (subfolder, i, ext))
filenames.append("%sha_art/ha_art_%03d.%s" % (subfolder, i, ext))
filenames.append("%sprize_picture/g_%02d.%s" % (subfolder, i, ext))
filename_hashes = {}
for filename in filenames:
filename_hashes[get_filename_hash(filename)] = filename
while True:
filename_hash, file_offset, file_size, file_unk, checksum, file_unk3 = struct.unpack("<IIIIII", infile.read(0x18))
if filename_hash == 0:
break
file_offset *= 0x200
entry = {
'filename_hash': filename_hash,
'offset': file_offset,
'size': file_size,
'checksum': checksum,
}
entries.append(entry)
if entry['filename_hash'] in filename_hashes and filename_hashes[entry['filename_hash']].endswith("system.idx"):
# Parse system.idx for more filenames
filename_hashes.update(parse_system_idx(get_file_data(infile, entry)))
base_filename = filename_hashes[entry['filename_hash']].lstrip("image/").rstrip("/system.idx")
for subfolder in ["", "image/"]:
for ext in ["gcz", "gzz"]:
ha_chara_filename = "%sha_chara/ha_%s.%s" % (subfolder, base_filename, ext)
filename_hashes[get_filename_hash(ha_chara_filename)] = ha_chara_filename
for entry in entries:
infile.seek(entry['offset'])
if entry['filename_hash'] in filename_hashes:
output_filename = filename_hashes[entry['filename_hash']]
else:
output_filename = "%08x.bin" % entry['filename_hash']
output_filename = os.path.join(args.output, output_filename)
if os.path.exists(output_filename):
continue
basedir = os.path.dirname(output_filename)
if basedir and not os.path.exists(basedir):
os.makedirs(basedir)
with open(output_filename, "wb") as outfile:
print(entry)
print(output_filename)
data = infile.read(entry['size'])
if args.decrypt:
data = decrypt_data(data)
outfile.write(data)
checksum = sum(data) & 0xffffffff
if checksum != entry['checksum']:
print("%s checksum did not match! %08x vs %08x" % (output_filename, checksum, entry['checksum']))
# Seems to always be 0x20000000? But later HDDs use encrypted data so the PM09 header won't be found
next_file_offset = 0x20000000
file_id = 0
while True:
infile.seek(next_file_offset)
next_file_offset = infile.tell() + 0x1400000
header = infile.read(4)
if len(header) != 4:
break
if header != b'PM09':
continue
print("Dumping sound data @ %08x" % (infile.tell() - 4))
filecount = int.from_bytes(infile.read(4), byteorder="little")
file_id2 = 0
for i in range(filecount):
file_offset, file_size = struct.unpack("<II", infile.read(8))
file_offset *= 0x200
if file_offset == 0 and file_size == 0:
break
output_folder = os.path.join(args.output, "_data/%04d/" % file_id)
if not os.path.exists(output_folder):
os.makedirs(output_folder)
output_filename = os.path.join(output_folder, "%04d_%d.bin" % (file_id, file_id2))
if os.path.exists(output_filename):
continue
print("Dumping", output_filename)
with open(output_filename, "wb") as outfile:
entry = {
'offset': file_offset,
'size': file_size,
}
data = get_file_data(infile, entry)
if args.decrypt:
data = decrypt_data(data)
try:
data = decompress_gcz(data)
except:
pass
outfile.write(data)
file_id2 += 1
file_id += 1

@ -0,0 +1,51 @@
<style>
.jamma table th:first-of-type {
width: 45%;
}
.jamma table th:nth-of-type(2) {
width: 5%;
}
.jamma table th:nth-of-type(3) {
width: 5%;
}
.jamma table th:nth-of-type(4) {
width: 45%;
}
</style>
<div class="jamma">
## System 573 Dance Maniax JAMMA Pinout
Bottom | | | Top
:------: | :------: | :------: | :------:
| | A | 1 | |
| | B | 2 | |
| | C | 3 | |
| | D | 4 | |
| | E | 5 | |
| | F | 6 | |
| | H | 7 | |
| | J | 8 | |
| | K | 9 | |
| | L | 10 | |
| | M | 11 | |
| | N | 12 | |
| | P | 13 | |
| Service | R | 14 | |
| | S | 15 | Test |
| | T | 16 | Coin |
| P2 Start | U | 17 | P1 Start |
| P2 Left | V | 18 | P1 Left |
| P2 Right | W | 19 | P1 Right |
| P2 D1 1 | X | 20 | P1 D1 1 |
| P2 D1 2 | Y | 21 | P1 D1 2 |
| P2 U 1 | Z | 22 | P1 U 1 |
| P2 U 2 | a | 23 | P1 U 2 |
| | b | 24 | |
| P2 D0 1 | c | 25 | P1 D0 1 |
| P2 D0 2 | d | 26 | P1 D0 2 |
| | e | 27 | |
| | f | 28 | |
</div>

@ -0,0 +1,51 @@
<style>
.jamma table th:first-of-type {
width: 45%;
}
.jamma table th:nth-of-type(2) {
width: 5%;
}
.jamma table th:nth-of-type(3) {
width: 5%;
}
.jamma table th:nth-of-type(4) {
width: 45%;
}
</style>
<div class="jamma">
## System 573 Dance Dance Revolution JAMMA Pinout
Bottom | | | Top
:------: | :------: | :------: | :------:
| | A | 1 | |
| | B | 2 | |
| | C | 3 | |
| | D | 4 | |
| | E | 5 | |
| | F | 6 | |
| | H | 7 | |
| | J | 8 | |
| | K | 9 | |
| | L | 10 | |
| | M | 11 | |
| | N | 12 | |
| | P | 13 | |
| Service | R | 14 | |
| | S | 15 | Test |
| | T | 16 | Coin |
| P2 Start | U | 17 | P1 Start |
| P2 Up | V | 18 | P1 Up |
| P2 Down | W | 19 | P1 Down |
| P2 Left | X | 20 | P1 Left |
| P2 Right | Y | 21 | P1 Right |
| | Z | 22 | |
| P2 Select L | a | 23 | P1 Select L |
| P2 Select R | b | 24 | P1 Select R |
| | c | 25 | |
| | d | 26 | |
| | e | 27 | |
| | f | 28 | |
</div>

@ -0,0 +1,53 @@
<style>
.jamma table th:first-of-type {
width: 45%;
}
.jamma table th:nth-of-type(2) {
width: 5%;
}
.jamma table th:nth-of-type(3) {
width: 5%;
}
.jamma table th:nth-of-type(4) {
width: 45%;
}
</style>
<div class="jamma">
## System 573 Drummania JAMMA Pinout
TODO: Verify on real hardware
Bottom | | | Top
:------: | :------: | :------: | :------:
| | A | 1 | |
| | B | 2 | |
| | C | 3 | |
| | D | 4 | |
| | E | 5 | |
| | F | 6 | |
| | H | 7 | |
| | J | 8 | |
| | K | 9 | |
| | L | 10 | |
| | M | 11 | |
| | N | 12 | |
| | P | 13 | |
| Service | R | 14 | |
| | S | 15 | Test |
| | T | 16 | Coin |
| | U | 17 | Start |
| | V | 18 | Hi-hat |
| | W | 19 | Snare |
| Select L | X | 20 | High Tom |
| Select R | Y | 21 | Low Tom |
| | Z | 22 | Cymbal |
| | a | 23 | |
| | b | 24 | Bass Drum |
| | c | 25 | |
| | d | 26 | |
| | e | 27 | |
| | f | 28 | |
</div>

@ -0,0 +1,53 @@
<style>
.jamma table th:first-of-type {
width: 45%;
}
.jamma table th:nth-of-type(2) {
width: 5%;
}
.jamma table th:nth-of-type(3) {
width: 5%;
}
.jamma table th:nth-of-type(4) {
width: 45%;
}
</style>
<div class="jamma">
## System 573 Guitar Freaks JAMMA Pinout
TODO: Verify P2 controls
Bottom | | | Top
:------: | :------: | :------: | :------:
| | A | 1 | |
| | B | 2 | |
| | C | 3 | |
| | D | 4 | |
| | E | 5 | |
| | F | 6 | |
| | H | 7 | |
| | J | 8 | |
| | K | 9 | |
| | L | 10 | |
| | M | 11 | |
| | N | 12 | |
| | P | 13 | |
| Service | R | 14 | |
| | S | 15 | Test |
| | T | 16 | Coin |
| P2 Start | U | 17 | P1 Start |
| P2 Pick | V | 18 | P1 Pick |
| P2 Wail | W | 19 | P1 Wail |
| P2 Effect (2) | X | 20 | P1 Effect (2) |
| P2 Effect (1) | Y | 21 | P2 Effect (1) |
| P2 R | Z | 22 | P1 R |
| P2 G | a | 23 | P1 G |
| P2 B | b | 24 | P1 B |
| | c | 25 | |
| | d | 26 | |
| | e | 27 | |
| | f | 28 | |
</div>

@ -0,0 +1,51 @@
<style>
.jamma table th:first-of-type {
width: 45%;
}
.jamma table th:nth-of-type(2) {
width: 5%;
}
.jamma table th:nth-of-type(3) {
width: 5%;
}
.jamma table th:nth-of-type(4) {
width: 45%;
}
</style>
<div class="jamma">
## System 573 Mambo a Go Go JAMMA Pinout
Bottom | | | Top
:------: | :------: | :------: | :------:
| | A | 1 | |
| | B | 2 | |
| | C | 3 | |
| | D | 4 | |
| | E | 5 | |
| | F | 6 | |
| | H | 7 | |
| | J | 8 | |
| | K | 9 | |
| | L | 10 | |
| | M | 11 | |
| | N | 12 | |
| | P | 13 | |
| Service | R | 14 | |
| | S | 15 | Test |
| Right Pad 7 | T | 16 | Coin |
| Left Pad 1 | U | 17 | Start |
| Center Pad 4 | V | 18 | Center Pad 6 |
| Center Pad 5 | W | 19 | Center Pad 4 |
| Center Pad 5 | X | 20 | Left |
| Center Pad 6 | Y | 21 | Right |
| Right Pad 8 | Z | 22 | Left Pad 2 |
| Right Pad 7 | a | 23 | Left Pad 1 |
| Right Pad 9 | b | 24 | Left Pad 3 |
| Right Pad 9 | c | 25 | Left Pad 3 |
| Right Pad 8 | d | 26 | Left Pad 2 |
| | e | 27 | |
| | f | 28 | |
</div>

@ -0,0 +1,20 @@
# System 573 Multisession Unit Audio Decryption Tool
This tool works on all multisession unit discs.
This is a full recreation of the algorithm used to decrypt data on the real multisession unit device. It does not require access to hardware at all to decrypt anything, or generate any metadata.
## Usage
```
usage: msudecrypt.py [-h] --input INPUT [--output OUTPUT]
optional arguments:
-h, --help show this help message and exit
--input INPUT Input file
--output OUTPUT Output file
```
Without `--output (filename.mp3)` being specified, it will automatically output a file in the same location as input.dat/.mp3 with a .mp3/.dat extension.

@ -0,0 +1,21 @@
import struct
import sys
import ctypes
with open(sys.argv[1], "rb") as infile:
data = bytearray(infile.read())
data[0x1c:0x20] = struct.pack("<I", 0)
checksum = 0
for i in range(0, len(data), 4):
checksum += struct.unpack(">I", data[i:i+4])[0]
checksum &= 0xffffffff
checksum_diff = 0xffffffff - checksum
print("%08x %08x" % (checksum, checksum_diff))
if checksum_diff != 0:
with open(sys.argv[1], "rb+") as outfile:
outfile.seek(0x1c)
outfile.write(struct.pack(">I", checksum_diff))

@ -0,0 +1,100 @@
import argparse
import hashlib
import os
import struct
import sys
def decrypt(data, key):
# Pad key
while len(key) % 2:
key += b"\x00"
# Prepare key as 16-bit words
key = [int.from_bytes(key[i:i+2], byteorder="little") for i in range(0, len(key), 2)]
# Generate extended key
expanded_key = [ 0xb7e1, 0x5618, 0xf44f, 0x9286, 0x30bd, 0xcef4, 0x6d2b, 0x0b62,
0xa999, 0x47d0, 0xe607, 0x843e, 0x2275, 0xc0ac, 0x5ee3, 0xfd1a,
0x9b51, 0x3988, 0xd7bf, 0x75f6, 0x142d, 0xb264, 0x509b, 0xeed2,
0x8d09, 0x2b40, 0xc977, 0x67ae, 0x05e5, 0xa41c, 0x4253, 0xe08a ]
# Mix key
t0 = t1 = s1 = 0
for x in range(32):
a2 = expanded_key[x]
a0 = key[x % len(key)]
for k in range(3):
v0 = a2 + t1 + t0
v1 = (v0 & 0xffff) >> 3
a2 = v1 | (v0 << 13)
t1 = a2 & 0xffff
a0 += t1 + t0
v1 = a0 & 0xffff
v0 = a0 & 0x0f
a0 = (v1 << v0) | (v1 >> (0x10 - v0))
t0 = a0 & 0xffff
expanded_key[x] = t1
key[x % len(key)] = t0
v1 = (t1 & 0xff) & 0xff
s1 = (s1 + v1) & 0xff
v1 = ((s1 * 0x5AC056B1) >> 32) & 0xffffffff
counter = ((s1 - (((v1 + ((s1 - v1) >> 1)) >> 7) * 0xbd) & 0xffffffff) + 0x43) & 0xff
# If there's an extra byte at the end of the file, drop it because the data must be in 16-bit words to be decrypted.
# This shouldn't affect any songs as far as I know since usually the last byte is a 00.
# TODO: Determine if the MSU even decrypts or does anything with the last byte in the case of a non-even number of bytes.
data = [int.from_bytes(data[i:i+2], byteorder="big") for i in range(0, len(data) // 2 * 2, 2)]
# Decrypt data
for i in range(len(data)):
t0 = expanded_key[(counter + 3) % len(expanded_key)] & 0xffff
t1 = expanded_key[(counter + 2) % len(expanded_key)] & 0xffff
v1 = (data[i] - t0) & 0xffff
v0 = ((t1 + t0) & 7) + 4
v1 = (((v1 << (0x10 - v0)) | (v1 >> v0)) ^ t1) - (expanded_key[counter % len(expanded_key)] ^ expanded_key[(counter + 1) % len(expanded_key)])
data[i] = v1 & 0xffff
expanded_key[counter % len(expanded_key)] = (expanded_key[counter % len(expanded_key)] + expanded_key[(counter + 1) % len(expanded_key)]) & 0xffff
counter = (counter + 1) & 0xff
return bytearray(b"".join([c.to_bytes(2, byteorder="big") for c in data]))
def main(input_filename, output_filename=None):
key = bytearray("!kAiNsYuu4NkAn3594NnAnbo9tyouzUi105DaisOugEnmIn4N", encoding="ascii")
filename = os.path.splitext(os.path.basename(input_filename))[0]
for idx, c in enumerate(filename.lower()):
key[idx + len(filename)] = ord(c)
md5hash = hashlib.md5()
md5hash.update(key)
md5key = bytearray(md5hash.digest())
# Swap endianness of md5key array
for i in range(0, len(md5key), 2):
t = md5key[i]
md5key[i] = md5key[i+1]
md5key[i+1] = t
data = bytearray(open(input_filename, "rb").read())
data = decrypt(data, md5key)
if not output_filename:
output_filename = os.path.splitext(input_filename)[0] + ".mp3"
with open(output_filename, "wb") as outfile:
outfile.write(data)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--input', help='Input file', required=True)
parser.add_argument('--output', help='Output file', default=None)
args = parser.parse_args()
main(args.input, args.output)

@ -0,0 +1,38 @@
# py573a
A Python recreation of 573a.jar with less Java, less GUI, less obfuscation, and less encryption.
py573a.py requires Cython. Build the required files using `python setup.py build_ext --inplace`.
py573a_native.py contains the decryption algorithm in native Python, in case you want to use the tool in an environment where Cython is inconvenient. Note: This version will be slower, but it should be easier to use without installing Cython and a C compiler, etc.
## Usage
```
usage: py573a.py [-h] [--input INPUT] [--output OUTPUT] [--sha1 SHA1]
[--key1 KEY1] [--key2 KEY2] [--key3 KEY3]
optional arguments:
-h, --help show this help message and exit
--input INPUT Input file
--output OUTPUT Output file
--sha1 SHA1 Force usage of a specific SHA-1 for encryption keys
(optional)
--key1 KEY1 Key 1 (optional)
--key2 KEY2 Key 2 (optional)
--key3 KEY3 Key 3 (optional)
```
Without `--output (filename.mp3)` being specified, it will automatically output a file in the same location as input.dat/.mp3 with a .mp3/.dat extension.
### Decryption
Decrypt a DAT file. The correct key is determined by the SHA-1 hash of the file. You can manually specify the SHA-1, or else the program will automatically calculate the SHA-1 hash of the input file. The SHA-1 hash must exist in the database for decryption to be possible.
```
python py573a.py --input input.dat
```
### Database
- Dance Dance Revolution Solo Bass Mix uses a different algorithm for which the key algorithm is not yet known, so the old method is still used for those games. Everything else should work.
- If a song is not in the db.json file, you must manually enter the key information yourself or use the `--key1`, `--key2`, and `--key3` parameters to decrypt the data.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,227 @@
# cython: cdivision=True
cdef rot(int c):
return ((c >> 7) & 1) | ((c << 1) & 0xff)
cdef int is_bit_set(int value, int n):
return (value >> n) & 1
cdef int bit_swap(int v, int b15, int b14, int b13, int b12, int b11, int b10, int b9, int b8, int b7, int b6, int b5, int b4, int b3, int b2, int b1, int b0):
return (is_bit_set(v, b15) << 15) | \
(is_bit_set(v, b14) << 14) | \
(is_bit_set(v, b13) << 13) | \
(is_bit_set(v, b12) << 12) | \
(is_bit_set(v, b11) << 11) | \
(is_bit_set(v, b10) << 10) | \
(is_bit_set(v, b9) << 9) | \
(is_bit_set(v, b8) << 8) | \
(is_bit_set(v, b7) << 7) | \
(is_bit_set(v, b6) << 6) | \
(is_bit_set(v, b5) << 5) | \
(is_bit_set(v, b4) << 4) | \
(is_bit_set(v, b3) << 3) | \
(is_bit_set(v, b2) << 2) | \
(is_bit_set(v, b1) << 1) | \
(is_bit_set(v, b0) << 0)
cpdef bytearray decrypt(unsigned char *data, int data_len, unsigned short key1, unsigned short key2, unsigned char key3):
cdef unsigned short v = 0
cdef unsigned short m = 0
cdef unsigned int idx = 0
output_data = bytearray(data_len * 2)
while idx < data_len:
v = (data[idx * 2 + 1] << 8) | data[idx * 2]
m = key1 ^ key2
v = bit_swap(
v,
15 - is_bit_set(m, 0xF),
14 + is_bit_set(m, 0xF),
13 - is_bit_set(m, 0xE),
12 + is_bit_set(m, 0xE),
11 - is_bit_set(m, 0xB),
10 + is_bit_set(m, 0xB),
9 - is_bit_set(m, 0x9),
8 + is_bit_set(m, 0x9),
7 - is_bit_set(m, 0x8),
6 + is_bit_set(m, 0x8),
5 - is_bit_set(m, 0x5),
4 + is_bit_set(m, 0x5),
3 - is_bit_set(m, 0x3),
2 + is_bit_set(m, 0x3),
1 - is_bit_set(m, 0x2),
0 + is_bit_set(m, 0x2)
)
v ^= (is_bit_set(m, 0xD) << 14) ^ \
(is_bit_set(m, 0xC) << 12) ^ \
(is_bit_set(m, 0xA) << 10) ^ \
(is_bit_set(m, 0x7) << 8) ^ \
(is_bit_set(m, 0x6) << 6) ^ \
(is_bit_set(m, 0x4) << 4) ^ \
(is_bit_set(m, 0x1) << 2) ^ \
(is_bit_set(m, 0x0) << 0)
v ^= bit_swap(
key3,
7, 0, 6, 1,
5, 2, 4, 3,
3, 4, 2, 5,
1, 6, 0, 7
)
output_data[idx * 2] = (v >> 8) & 0xff
output_data[idx * 2 + 1] = v & 0xff
key1 = ((key1 & 0x8000) | ((key1 << 1) & 0x7FFE) | ((key1 >> 14) & 1)) & 0xFFFF
if (((key1 >> 15) ^ key1) & 1) != 0:
key2 = ((key2 << 1) | (key2 >> 15)) & 0xFFFF
idx += 1
key3 += 1
return output_data
cpdef bytearray encrypt(unsigned char *data, int data_len, unsigned short key1, unsigned short key2, unsigned char key3):
cdef unsigned short v = 0
cdef unsigned short m = 0
cdef unsigned int idx = 0
output_data = bytearray(data_len * 2)
while idx < data_len:
v = (data[idx * 2 + 1] << 8) | data[idx * 2]
m = key1 ^ key2
v = bit_swap(
v,
15 - is_bit_set(m, 0xF),
14 + is_bit_set(m, 0xF),
13 - is_bit_set(m, 0xE),
12 + is_bit_set(m, 0xE),
11 - is_bit_set(m, 0xB),
10 + is_bit_set(m, 0xB),
9 - is_bit_set(m, 0x9),
8 + is_bit_set(m, 0x9),
7 - is_bit_set(m, 0x8),
6 + is_bit_set(m, 0x8),
5 - is_bit_set(m, 0x5),
4 + is_bit_set(m, 0x5),
3 - is_bit_set(m, 0x3),
2 + is_bit_set(m, 0x3),
1 - is_bit_set(m, 0x2),
0 + is_bit_set(m, 0x2)
)
v ^= (is_bit_set(m, 0xD) << 14) ^ \
(is_bit_set(m, 0xC) << 12) ^ \
(is_bit_set(m, 0xA) << 10) ^ \
(is_bit_set(m, 0x7) << 8) ^ \
(is_bit_set(m, 0x6) << 6) ^ \
(is_bit_set(m, 0x4) << 4) ^ \
(is_bit_set(m, 0x1) << 2) ^ \
(is_bit_set(m, 0x0) << 0)
v ^= bit_swap(
key3,
3, 4, 2, 5,
1, 6, 0, 7,
7, 0, 6, 1,
5, 2, 4, 3
)
output_data[idx * 2] = (v >> 8) & 0xff
output_data[idx * 2 + 1] = v & 0xff
key1 = ((key1 & 0x8000) | ((key1 << 1) & 0x7FFE) | ((key1 >> 14) & 1)) & 0xFFFF
if (((key1 >> 15) ^ key1) & 1) != 0:
key2 = ((key2 << 1) | (key2 >> 15)) & 0xFFFF
idx += 1
key3 += 1
return output_data
cpdef bytearray decrypt_ddrsbm(unsigned char *data, int data_len, unsigned short key):
cdef unsigned int output_idx = 0
cdef unsigned int idx = 0
cdef unsigned int even_bit_shift = 0
cdef unsigned int odd_bit_shift = 0
cdef unsigned int is_even_bit_set = 0
cdef unsigned int is_odd_bit_set = 0
cdef unsigned int is_key_bit_set = 0
cdef unsigned int is_scramble_bit_set = 0
cdef unsigned int cur_bit = 0
cdef unsigned int output_word = 0
output_data = bytearray(data_len * 2)
key_data = bytearray(16)
# Generate key data based on input key
key_state = is_bit_set(key, 0x0d) << 15 | \
is_bit_set(key, 0x0b) << 14 | \
is_bit_set(key, 0x09) << 13 | \
is_bit_set(key, 0x07) << 12 | \
is_bit_set(key, 0x05) << 11 | \
is_bit_set(key, 0x03) << 10 | \
is_bit_set(key, 0x01) << 9 | \
is_bit_set(key, 0x0f) << 8 | \
is_bit_set(key, 0x0e) << 7 | \
is_bit_set(key, 0x0c) << 6 | \
is_bit_set(key, 0x0a) << 5 | \
is_bit_set(key, 0x08) << 4 | \
is_bit_set(key, 0x06) << 3 | \
is_bit_set(key, 0x04) << 2 | \
is_bit_set(key, 0x02) << 1 | \
is_bit_set(key, 0x00) << 0
while idx < 8:
key_data[idx * 2] = key_state & 0xff
key_data[idx * 2 + 1] = (key_state >> 8) & 0xff
key_state = (rot(key_state >> 8) << 8) | rot(key_state & 0xff)
idx += 1
while idx < data_len:
output_word = 0
cur_data = (data[(idx * 2) + 1] << 8) | data[(idx * 2)]
cur_bit = 0
while cur_bit < 8:
even_bit_shift = (cur_bit * 2) & 0xff
odd_bit_shift = (cur_bit * 2 + 1) & 0xff
is_even_bit_set = int((cur_data & (1 << even_bit_shift)) != 0)
is_odd_bit_set = int((cur_data & (1 << odd_bit_shift)) != 0)
is_key_bit_set = int((key_data[idx % 16] & (1 << cur_bit)) != 0)
is_scramble_bit_set = int((key_data[(idx - 1) % 16] & (1 << cur_bit)) != 0)
if is_scramble_bit_set == 1:
is_even_bit_set, is_odd_bit_set = is_odd_bit_set, is_even_bit_set
if ((is_even_bit_set ^ is_key_bit_set)) == 1:
output_word |= 1 << even_bit_shift
if is_odd_bit_set == 1:
output_word |= 1 << odd_bit_shift
cur_bit += 1
output_data[output_idx] = (output_word >> 8) & 0xff
output_data[output_idx+1] = output_word & 0xff
output_idx += 2
idx += 1
return output_data

@ -0,0 +1,326 @@
import argparse
import hashlib
import json
import os
import sys
DATABASE_FILENAME = "db.json"
def get_database(filename=DATABASE_FILENAME):
db = json.load(open(filename))
output = {}
for sha1 in db:
k = db[sha1]
if len(k) == 1:
output[sha1] = {
'sha1': sha1,
'key1': k[0],
}
else:
output[sha1] = {
'sha1': sha1,
'key1': k[0],
'key2': k[1],
'key3': k[2]
}
return output
def get_key_information(sha1):
db = get_database()
sha1 = sha1.upper()
song = db.get(sha1, None)
if not song:
return (None, None, None)
if 'key1' in song and 'key2' not in song and 'key3' not in song:
return (song['key1'], None, None)
return (song['key1'], song['key2'], song['key3'])
def is_bit_set(value, n):
return (value >> n) & 1
# Thanks SaxxonPike for helping with this one
def decrypt_ddrsbm(data, data_len, key):
def rot(c):
return ((c >> 7) & 1) | ((c << 1) & 0xff)
key_state = is_bit_set(key, 0x0d) << 15 | \
is_bit_set(key, 0x0b) << 14 | \
is_bit_set(key, 0x09) << 13 | \
is_bit_set(key, 0x07) << 12 | \
is_bit_set(key, 0x05) << 11 | \
is_bit_set(key, 0x03) << 10 | \
is_bit_set(key, 0x01) << 9 | \
is_bit_set(key, 0x0f) << 8 | \
is_bit_set(key, 0x0e) << 7 | \
is_bit_set(key, 0x0c) << 6 | \
is_bit_set(key, 0x0a) << 5 | \
is_bit_set(key, 0x08) << 4 | \
is_bit_set(key, 0x06) << 3 | \
is_bit_set(key, 0x04) << 2 | \
is_bit_set(key, 0x02) << 1 | \
is_bit_set(key, 0x00) << 0
key_data = bytearray(16)
for i in range(8):
key_data[i * 2] = key_state & 0xff
key_data[i * 2 + 1] = (key_state >> 8) & 0xff
key_state = (rot(key_state >> 8) << 8) | rot(key_state & 0xff)
scramble = bytearray([key_data[-1]]) + key_data[:-1]
key_len = len(key_data)
scramble_len = len(scramble)
output_idx = 0
output_data = bytearray(len(data))
for idx in range(0, data_len):
output_word = 0
cur_data = (data[(idx * 2) + 1] << 8) | data[(idx * 2)]
for cur_bit in range(0, 8):
even_bit_shift = (cur_bit * 2) & 0xff
odd_bit_shift = (cur_bit * 2 + 1) & 0xff
is_even_bit_set = int((cur_data & (1 << even_bit_shift)) != 0)
is_odd_bit_set = int((cur_data & (1 << odd_bit_shift)) != 0)
is_key_bit_set = int((key_data[idx % key_len] & (1 << cur_bit)) != 0)
is_scramble_bit_set = int((scramble[idx % scramble_len] & (1 << cur_bit)) != 0)
if is_scramble_bit_set == 1:
is_even_bit_set, is_odd_bit_set = is_odd_bit_set, is_even_bit_set
if ((is_even_bit_set ^ is_key_bit_set)) == 1:
output_word |= 1 << even_bit_shift
if is_odd_bit_set == 1:
output_word |= 1 << odd_bit_shift
output_data[output_idx] = (output_word >> 8) & 0xff
output_data[output_idx+1] = output_word & 0xff
output_idx += 2
return bytearray(output_data)
# You crazy for this one
# Thanks anon and RC
def decrypt(data, data_len, key1, key2, key3):
def bit_swap(v, b15, b14, b13, b12, b11, b10, b9, b8, b7, b6, b5, b4, b3, b2, b1, b0):
return (is_bit_set(v, b15) << 15) | (is_bit_set(v, b14) << 14) | (is_bit_set(v, b13) << 13) | (is_bit_set(v, b12) << 12) |\
(is_bit_set(v, b11) << 11) | (is_bit_set(v, b10) << 10) | (is_bit_set(v, b9) << 9) | (is_bit_set(v, b8) << 8) |\
(is_bit_set(v, b7) << 7) | (is_bit_set(v, b6) << 6) | (is_bit_set(v, b5) << 5) | (is_bit_set(v, b4) << 4) |\
(is_bit_set(v, b3) << 3) | (is_bit_set(v, b2) << 2) | (is_bit_set(v, b1) << 1) | (is_bit_set(v, b0) << 0)
output_data = bytearray(len(data))
</