-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdecrypt_xb1_bins.py
172 lines (146 loc) · 5.86 KB
/
decrypt_xb1_bins.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""
Python script to decrypt/un-gzip the `.bin` song files included in XB1/TDMX.
Tested to work with files from v1.2.2 (check pins in #xb1_modding). May not
work with newer versions with updated keys.
Prerequisites:
1. Install Python.
2. Install a version of OpenSSL, making sure that `openssl` is on the PATH.
(In other words, make sure you can run `openssl` from the command line.)
Instructions:
1. Create a new empty folder.
2. Copy the encrypted `.bin` song files to the new folder:
- Location: 'TaikoTDM\Taiko no Tatsujin_Data\StreamingAssets\sound'
- You only need to copy the files that start with 'song_'
3. Copy all the fumen subfolders to the new folder, too:
- Location: 'TaikoTDM\Taiko no Tatsujin_Data\StreamingAssets\fumen'
- You don't need to organize the folders. This script will move each
'song_' file into each of the fumen subfolders once decrypted.
4. Put this script in the same folder as the song files/fumen folders.
5. Make sure the folder looks like this:
folder/
│ # Sample folder
├─ 87oto/
│ ├─ 87oto_e.bin
│ ├─ 87oto_e_1.bin
│ ├─ 87oto_e_2.bin
│ ├─ [...]
│ └─ 87oto_m.bin
│
│ # Rest of folders
├─ ac7roc/
├─ blurb/
├─ [...]
├─ yukai/
│
│ # Song files
├─ song_87oto.bin
├─ song_ac7roc.bin
├─ song_blurb.bin
├─ [...]
├─ song_yukai.bin
│
│ # This script
└─ decrypt_xb1_bins.py
6. Run the script
"""
import gzip
import os
import shutil
import subprocess
from subprocess import CalledProcessError
# Valid for TDMX v1.2.2
KEYS = {
"fumen": "794A7A4E5651764C42484C625857364269476B337450414C46536665466D5746",
"song": "51795670785570353734733644547466445348384A4564367834645769357138"
}
def is_decrypted(bytestring, filetype, fname):
# decrypted songs should start with @UTF marker
if filetype == "song":
decrypted = bytestring.startswith(b"@UTF")
# decrypted fumens should contain the filename starting at byte 10
else:
assert filetype == "fumen"
decrypted = b".bin" in bytestring
return decrypted
def is_gunzipped(bytestring):
measure_seperator = b'\x00' + (b'\xFF' * 24) + b'\x00'
return measure_seperator in bytestring
def decrypt_file(root, fname, filetype):
fpath = os.path.join(root, fname)
tmp_fpath = os.path.join(root, f"tempfile")
with open(fpath, "rb") as file:
bytestring = file.read()
iv_bytes = bytestring[:16].hex().upper()
if is_decrypted(bytestring, filetype, fname) or is_gunzipped(bytestring):
print(f" {fname} has already been decrypted")
return False
command = (f"openssl aes-256-cbc -d -in {fpath} -out {tmp_fpath} "
f"-K {KEYS[filetype]} -iv {iv_bytes}")
try:
subprocess.check_output(command, shell=True)
except CalledProcessError as e:
print(f" - {fname} is invalid: {e}")
return False
with open(tmp_fpath, "rb") as file:
bytestring = file.read()
bytestring = bytestring[16:] # discard iv bytes
if is_decrypted(bytestring, filetype, fname):
print(f" Successfully decrypted {fname}")
with open(tmp_fpath, "wb") as file:
file.write(bytestring)
os.remove(fpath)
shutil.move(tmp_fpath, fpath)
return True
else:
print(f" Couldn't find expected bytes in decrypted file")
os.remove(tmp_fpath)
return False
def gunzip_file(root, fname):
fpath = os.path.join(root, fname)
tmp_fpath = os.path.join(root, "temp.bin")
try:
with gzip.open(fpath, 'rb') as f_in:
with open(tmp_fpath, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
print(f" Successfully gunzipped {fname}")
os.remove(fpath)
shutil.move(tmp_fpath, fpath)
return True
except Exception as e:
print(f" {fname} couldn't be gunzipped: {e}")
return False
def main():
SONG_DIR = os.path.join("C:\\", "users", "joshu", "Desktop", "XB1")
print(f"Looking for files in {SONG_DIR}... (Expected # of XB1 songs: 77)")
contents = os.listdir(SONG_DIR)
folders = [f for f in contents if os.path.isdir(os.path.join(SONG_DIR, f))]
bins = [b for b in contents if b.endswith(".bin") and b.startswith("song")]
print(f"Found {len(folders)} fumen folders: {folders}")
print(f"Found {len(bins)} song bins: {bins}")
print("\nDecrypting song bins...")
for song_bin in bins:
print(f"\n - Decrypting {song_bin}...")
decrypt_file(root=SONG_DIR, fname=song_bin, filetype="song")
print("\nDecrypting + ungzipping fumen files...")
for folder_name in folders:
print(f"\n - Decrypting + un-gzipping fumens in '{folder_name}/'...")
folder_path = os.path.join(SONG_DIR, folder_name)
for fumen in [f for f in os.listdir(folder_path)
if f.endswith(".bin") and not f.startswith("song")]:
s = decrypt_file(root=folder_path, fname=fumen, filetype="fumen")
if s:
gunzip_file(root=folder_path, fname=fumen)
print("\nCopying song files to fumen folders...")
for folder_name in folders: # Only copy files if its fumen folder exists
song_name = f"song_{folder_name}.bin"
src = os.path.join(SONG_DIR, song_name)
folder_path = os.path.join(SONG_DIR, folder_name)
dest = os.path.join(folder_path, song_name)
if not os.path.exists(src):
print(f" - Fumen folder {folder_name} missing song file")
else:
print(f" - Copying from {src} to {dest}")
shutil.copy(src, dest)
input("Press Enter to continue...")
if __name__ == "__main__":
main()