sceutils 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sceutils/__init__.py +57 -0
- sceutils/__main__.py +57 -0
- sceutils/axfs.py +308 -0
- sceutils/decrypt_cpupdate.py +136 -0
- sceutils/emmc_extract.py +231 -0
- sceutils/extract_idstorage.py +27 -0
- sceutils/find_imported_by.py +35 -0
- sceutils/keys.py +57 -0
- sceutils/keys_external.py +333 -0
- sceutils/keys_internal.py +121 -0
- sceutils/keys_proto.py +137 -0
- sceutils/module_info.py +138 -0
- sceutils/pup_fiction.py +379 -0
- sceutils/pup_info.py +100 -0
- sceutils/scedecrypt.py +52 -0
- sceutils/scetypes.py +692 -0
- sceutils/sceutils.py +94 -0
- sceutils/self2elf.py +242 -0
- sceutils/selfinfo.py +249 -0
- sceutils/ud0_extract_pup.py +42 -0
- sceutils/unpack_bootimage_new.py +19 -0
- sceutils/util.py +72 -0
- sceutils-0.1.0.dist-info/METADATA +106 -0
- sceutils-0.1.0.dist-info/RECORD +31 -0
- sceutils-0.1.0.dist-info/WHEEL +5 -0
- sceutils-0.1.0.dist-info/entry_points.txt +3 -0
- sceutils-0.1.0.dist-info/licenses/LICENSE +21 -0
- sceutils-0.1.0.dist-info/top_level.txt +2 -0
- unarzl/__init__.py +25 -0
- unarzl/__main__.py +29 -0
- unarzl/arzl.py +497 -0
sceutils/pup_fiction.py
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from unarzl import unarzl
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import glob
|
|
6
|
+
import os
|
|
7
|
+
import pathlib
|
|
8
|
+
import struct
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
|
|
13
|
+
from Cryptodome.Cipher import AES
|
|
14
|
+
|
|
15
|
+
from sceutils import keys
|
|
16
|
+
from sceutils import pup_info
|
|
17
|
+
from .decrypt_cpupdate import decrypt_unpack_cpup, extract_cpup
|
|
18
|
+
from .scedecrypt import scedecrypt
|
|
19
|
+
from .self2elf import self2elf
|
|
20
|
+
from .util import c_str, make_filename, u8, u32
|
|
21
|
+
|
|
22
|
+
PARTITIONS = ["os0", "vs0", "sa0", "pd0"]
|
|
23
|
+
|
|
24
|
+
def pup_extract_files(pup: str, output: str):
|
|
25
|
+
typecount = defaultdict(int)
|
|
26
|
+
|
|
27
|
+
with open(pup, "rb") as fin:
|
|
28
|
+
header = pup_info.SCEUF.read(fin)
|
|
29
|
+
if header.magic[0:5] != b"SCEUF":
|
|
30
|
+
print("Invalid PUP")
|
|
31
|
+
return -1
|
|
32
|
+
header.print()
|
|
33
|
+
|
|
34
|
+
for x in range(header.seg_num):
|
|
35
|
+
fin.seek(pup_info.SCEUF_HEADER_SIZE + x * pup_info.SCEUF_FILEREC_SIZE)
|
|
36
|
+
rec = fin.read(pup_info.SCEUF_FILEREC_SIZE)
|
|
37
|
+
filetype, offset, length, flags = struct.unpack("<QQQQ", rec)
|
|
38
|
+
|
|
39
|
+
filename = pup_info.pup_types.get(filetype)
|
|
40
|
+
if not filename:
|
|
41
|
+
fin.seek(offset)
|
|
42
|
+
hdr = fin.read(0x1000)
|
|
43
|
+
filename = make_filename(hdr, filetype, typecount)
|
|
44
|
+
print(f"{filename=} {filetype=} {offset=:x} {length=:x} {flags=:x}")
|
|
45
|
+
|
|
46
|
+
with open(os.path.join(output, filename), "wb") as fout:
|
|
47
|
+
fin.seek(offset)
|
|
48
|
+
fout.write(fin.read(length))
|
|
49
|
+
print("-" * 80)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def join_files(mask: str, output: str):
|
|
53
|
+
files = sorted(glob.glob(mask))
|
|
54
|
+
if len(files) == 0:
|
|
55
|
+
return
|
|
56
|
+
with open(output, "wb") as fout:
|
|
57
|
+
for filename in files:
|
|
58
|
+
with open(filename, "rb") as fin:
|
|
59
|
+
fout.write(fin.read())
|
|
60
|
+
os.remove(filename)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def pup_decrypt_packages(src: str, dst: str):
|
|
64
|
+
files = [os.path.basename(p) for p in glob.glob(os.path.join(src, "*.pkg"))]
|
|
65
|
+
files.sort()
|
|
66
|
+
|
|
67
|
+
for filename in files:
|
|
68
|
+
filepath = os.path.join(src, filename)
|
|
69
|
+
with open(filepath, "rb") as fin:
|
|
70
|
+
try:
|
|
71
|
+
scedecrypt(fin, dst, silent=True)
|
|
72
|
+
print(f"Decrypted {filename}")
|
|
73
|
+
except KeyError:
|
|
74
|
+
print(f"[!] Couldn't decrypt {filename}")
|
|
75
|
+
|
|
76
|
+
for filename in ["cui_setupper.self", "psp2swu.self"]:
|
|
77
|
+
filepath = os.path.join(src, filename)
|
|
78
|
+
with open(filepath, "rb") as fin:
|
|
79
|
+
with open(os.path.join(dst, filename.replace(".self", ".elf")), "wb") as fout:
|
|
80
|
+
try:
|
|
81
|
+
self2elf(fin, fout, silent=True, ignore_sysver=True)
|
|
82
|
+
print(f"Decrypted {filename}")
|
|
83
|
+
except KeyError:
|
|
84
|
+
print(f"[!] Couldn't decrypt {filename}")
|
|
85
|
+
|
|
86
|
+
for partition in PARTITIONS:
|
|
87
|
+
join_files(os.path.join(dst, f"{partition}-*.pkg.seg02"), os.path.join(dst, f"{partition}.bin"))
|
|
88
|
+
|
|
89
|
+
print("-" * 80)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def slb2_extract(src, dst):
|
|
93
|
+
with open(src, "rb") as fin:
|
|
94
|
+
hdr = fin.read(0x200)
|
|
95
|
+
magic, version, flags, file_count, total_blocks = struct.unpack("<IIIII", hdr[0:20])
|
|
96
|
+
if magic != 0x32424C53:
|
|
97
|
+
raise RuntimeError("Invalid SLB2 file")
|
|
98
|
+
print(f"SLB2 version: {version}, flags: 0x{flags:X}, file_count: {file_count}, total_blocks: 0x{total_blocks:X}")
|
|
99
|
+
|
|
100
|
+
for x in range(file_count):
|
|
101
|
+
entry_start = 0x20 + x * 0x30
|
|
102
|
+
entry = hdr[entry_start:entry_start + 0x30]
|
|
103
|
+
filename = c_str(entry[0x10:])
|
|
104
|
+
|
|
105
|
+
block_offset, filesize = struct.unpack("<II", entry[0:8])
|
|
106
|
+
|
|
107
|
+
with open(os.path.join(dst, filename), "wb") as fout:
|
|
108
|
+
fin.seek(block_offset * 0x200)
|
|
109
|
+
fout.write(fin.read(filesize))
|
|
110
|
+
print(f"- {filename}")
|
|
111
|
+
|
|
112
|
+
print("-" * 80)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def enc_decrypt(src, dst):
|
|
116
|
+
with open(src, "rb") as fin:
|
|
117
|
+
data = fin.read()
|
|
118
|
+
|
|
119
|
+
magic, offset, plaintext_size, unk, data_size = struct.unpack("<IIIII", data[0:20])
|
|
120
|
+
|
|
121
|
+
if magic != 0x64B2C8E5:
|
|
122
|
+
raise RuntimeError("enc format invalid")
|
|
123
|
+
|
|
124
|
+
aes = AES.new(keys.ENC_KEY, AES.MODE_CBC, keys.ENC_IV)
|
|
125
|
+
data = data[offset:offset + data_size]
|
|
126
|
+
with open(dst, "wb") as fout:
|
|
127
|
+
fout.write(aes.decrypt(data))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def decrypt_scewm(src, dst):
|
|
131
|
+
aes = AES.new(keys.SCEWM_KEY, AES.MODE_CBC, keys.SCEWM_IV)
|
|
132
|
+
with open(src, "rb") as fin:
|
|
133
|
+
data = fin.read()[0x20:]
|
|
134
|
+
with open(os.path.join(dst, os.path.basename(src)), "wb") as fout:
|
|
135
|
+
dec = aes.decrypt(data)
|
|
136
|
+
fout.write(dec[0x100:-0x100])
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def decrypt_sceas(src, dst):
|
|
140
|
+
if not os.path.exists(src):
|
|
141
|
+
return
|
|
142
|
+
with open(src, "rb") as fin:
|
|
143
|
+
fin.seek(0x20)
|
|
144
|
+
data = fin.read()
|
|
145
|
+
try:
|
|
146
|
+
aes = AES.new(keys.SCEAS_KEY, AES.MODE_CBC, keys.SCEAS_IV)
|
|
147
|
+
with open(os.path.join(dst, os.path.basename(src)), "wb") as fout:
|
|
148
|
+
fout.write(aes.decrypt(data))
|
|
149
|
+
print(f"Decrypted: {os.path.basename(src)}")
|
|
150
|
+
except ImportError:
|
|
151
|
+
print("Dont have SCEAS keys")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def slb2_decrypt(src, dst):
|
|
155
|
+
for filename in ["second_loader.enc", "secure_kernel.enc"]:
|
|
156
|
+
dst_filename = filename.replace(".enc", ".bin")
|
|
157
|
+
src_path = os.path.join(src, filename)
|
|
158
|
+
if not os.path.exists(src_path):
|
|
159
|
+
print(f"couldnt find {filename}")
|
|
160
|
+
continue
|
|
161
|
+
enc_decrypt(src_path, os.path.join(dst, dst_filename))
|
|
162
|
+
print(f"Decrypted {filename} to {dst_filename}")
|
|
163
|
+
|
|
164
|
+
for filename in ["kernel_boot_loader.self", "prog_rvk.srvk"]:
|
|
165
|
+
filepath = os.path.join(src, filename)
|
|
166
|
+
if not os.path.exists(filepath):
|
|
167
|
+
print(f"couldnt find {filename}")
|
|
168
|
+
continue
|
|
169
|
+
with open(filepath, "rb") as fin:
|
|
170
|
+
scedecrypt(fin, dst, silent=True)
|
|
171
|
+
print(f"Decrypted {filename}")
|
|
172
|
+
|
|
173
|
+
for filename in ["kprx_auth_sm.self"]:
|
|
174
|
+
dst_filename = filename.replace(".self", ".elf")
|
|
175
|
+
filepath = os.path.join(src, filename)
|
|
176
|
+
if not os.path.exists(filepath):
|
|
177
|
+
print(f"couldnt find {filename}")
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
print(f"self2elf {filename}")
|
|
181
|
+
with open(filepath, "rb") as fin:
|
|
182
|
+
with open(os.path.join(dst, dst_filename), "wb") as fout:
|
|
183
|
+
self2elf(fin, fout, silent=True)
|
|
184
|
+
|
|
185
|
+
kernel_boot_loader_path = os.path.join(dst, "kernel_boot_loader.self.seg03")
|
|
186
|
+
if os.path.exists(kernel_boot_loader_path):
|
|
187
|
+
print("unarzl nsbl.bin")
|
|
188
|
+
with open(kernel_boot_loader_path, "rb") as f:
|
|
189
|
+
kbl_seg03 = f.read()
|
|
190
|
+
nsbl = unarzl(kbl_seg03)
|
|
191
|
+
with open("nsbl.bin", "wb") as f:
|
|
192
|
+
f.write(nsbl)
|
|
193
|
+
else:
|
|
194
|
+
print("couldnt find kernel_boot_loader.self.seg03")
|
|
195
|
+
print("-" * 80)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def extract_fs(output: str):
|
|
199
|
+
fs_output = os.path.join(output, "fs")
|
|
200
|
+
os.mkdir(fs_output)
|
|
201
|
+
|
|
202
|
+
for partition in PARTITIONS:
|
|
203
|
+
partition_in = os.path.join(output, "PUP_dec", f"{partition}.bin")
|
|
204
|
+
if not os.path.exists(partition_in):
|
|
205
|
+
continue
|
|
206
|
+
print(f"Extract {partition}")
|
|
207
|
+
partition_out = os.path.join(fs_output, partition)
|
|
208
|
+
os.mkdir(partition_out)
|
|
209
|
+
subprocess.call(["7z", "x", partition_in, f"-o{partition_out}"], stdout=open(os.devnull, 'wb'), stderr=open(os.devnull, 'wb'))
|
|
210
|
+
|
|
211
|
+
vs0_tarpatch = os.path.join(output, "fs", "vs0_tarpatch")
|
|
212
|
+
for filename in glob.glob(os.path.join(output, "PUP_dec", "vs0_tarpatch-*.pkg.seg02")):
|
|
213
|
+
print(f"tarpatch {os.path.basename(filename)}")
|
|
214
|
+
subprocess.call(["7z", "x", filename, f"-o{vs0_tarpatch}"], stdout=open(os.devnull, 'wb'), stderr=open(os.devnull, 'wb'))
|
|
215
|
+
|
|
216
|
+
print("-" * 80)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def mkdir_p(path: str):
|
|
220
|
+
os.makedirs(path, exist_ok=True)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def decrypt_selfs(in_dir: str, out_dir: str, blacklist=None):
|
|
224
|
+
if not blacklist:
|
|
225
|
+
blacklist = []
|
|
226
|
+
|
|
227
|
+
for root, dirs, files in os.walk(in_dir):
|
|
228
|
+
for filename in files:
|
|
229
|
+
name, ext = os.path.splitext(filename)
|
|
230
|
+
if (ext in [".self", ".skprx", ".suprx"] or filename == "eboot.bin") and filename not in blacklist:
|
|
231
|
+
relpath = os.path.relpath(root, in_dir)
|
|
232
|
+
mkdir_p(os.path.join(out_dir, relpath))
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
with open(os.path.join(root, filename), "rb") as fin:
|
|
236
|
+
with open(os.path.join(out_dir, relpath, f"{name}.elf"), "wb") as fout:
|
|
237
|
+
self2elf(fin, fout, silent=True)
|
|
238
|
+
print(f"self2elf {filename}")
|
|
239
|
+
except KeyError:
|
|
240
|
+
print(f"[!] no key {filename}")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def decrypt_fs(output):
|
|
244
|
+
for partition in PARTITIONS + ["vs0_tarpatch"]:
|
|
245
|
+
part_in = os.path.join(output, "fs", partition)
|
|
246
|
+
part_out = os.path.join(output, "fs_dec", partition)
|
|
247
|
+
decrypt_selfs(part_in, part_out)
|
|
248
|
+
print("-" * 80)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def decrypt_os0(output, os0="os0"):
|
|
252
|
+
os0_in = os.path.join(output, "fs", os0)
|
|
253
|
+
os0_out = os.path.join(output, "fs_dec", os0)
|
|
254
|
+
|
|
255
|
+
configs = ["psp2config_dolce.skprx", "psp2config_vita.skprx", "psp2config.skprx"]
|
|
256
|
+
|
|
257
|
+
for filename in configs:
|
|
258
|
+
in_path = os.path.join(os0_in, filename)
|
|
259
|
+
if os.path.exists(in_path):
|
|
260
|
+
print(f"Decrypt {filename}")
|
|
261
|
+
with open(in_path, "rb") as fin:
|
|
262
|
+
scedecrypt(fin, os0_out, silent=True)
|
|
263
|
+
print("-" * 80)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def decrypt_cpup(pup_dst, output):
|
|
267
|
+
CpUp = glob.glob(os.path.join(pup_dst, "*.CpUp"))
|
|
268
|
+
seg = glob.glob(os.path.join(output, "devkit_cp-*.pkg.seg02"))
|
|
269
|
+
for path in CpUp + seg:
|
|
270
|
+
output_name = os.path.splitext(os.path.basename(path))[0]
|
|
271
|
+
output_path = os.path.join(output, output_name)
|
|
272
|
+
|
|
273
|
+
print(f"Decrypting {output_name}")
|
|
274
|
+
print("-" * 80)
|
|
275
|
+
|
|
276
|
+
tar = decrypt_unpack_cpup(path, output_path)
|
|
277
|
+
if not tar:
|
|
278
|
+
continue
|
|
279
|
+
try:
|
|
280
|
+
dst = os.path.join(output_path, "dec")
|
|
281
|
+
extract_cpup(tar, dst)
|
|
282
|
+
except Exception as e:
|
|
283
|
+
print("Exception while extracting fsimages", e)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def extract_bootimage(filename: str, output: str):
|
|
287
|
+
with open(filename, "rb") as f:
|
|
288
|
+
data = f.read()
|
|
289
|
+
|
|
290
|
+
data = data[data.find(b"SceKernelBootimage") - 4:]
|
|
291
|
+
base_va = 0x81000000
|
|
292
|
+
off = u32(data, 0xCC) - base_va
|
|
293
|
+
num = u32(data, off)
|
|
294
|
+
|
|
295
|
+
for x in range(num):
|
|
296
|
+
entry_off = off + 8 + 12 * x
|
|
297
|
+
|
|
298
|
+
name_off = u32(data, entry_off) - base_va
|
|
299
|
+
name = c_str(data[name_off:name_off + 0x100])
|
|
300
|
+
basename = name[name.rfind("/") + 1:]
|
|
301
|
+
start = u32(data, entry_off + 4) - base_va
|
|
302
|
+
size = u32(data, entry_off + 8)
|
|
303
|
+
name, _ = os.path.splitext(basename)
|
|
304
|
+
|
|
305
|
+
print(f"Writing {name}...")
|
|
306
|
+
mod = data[start:start + size]
|
|
307
|
+
with open(os.path.join(output, name+".elf"), "wb") as fout:
|
|
308
|
+
fout.write(mod)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def extract_pup(pup, output):
|
|
312
|
+
if os.path.exists(output):
|
|
313
|
+
print(f"{output} already exists, remove it first")
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
print(f"Extracting {pup} to {output}")
|
|
317
|
+
|
|
318
|
+
os.mkdir(output)
|
|
319
|
+
|
|
320
|
+
pup_dst = os.path.join(output, "PUP")
|
|
321
|
+
os.mkdir(pup_dst)
|
|
322
|
+
pup_extract_files(pup, pup_dst)
|
|
323
|
+
|
|
324
|
+
pup_dec = os.path.join(output, "PUP_dec")
|
|
325
|
+
os.mkdir(pup_dec)
|
|
326
|
+
pup_decrypt_packages(pup_dst, pup_dec)
|
|
327
|
+
|
|
328
|
+
decrypt_scewm(os.path.join(pup_dst, "package_scewm.wm"), pup_dec)
|
|
329
|
+
decrypt_sceas(os.path.join(pup_dst, "package_sceas.as"), pup_dec)
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
decrypt_cpup(pup_dst, pup_dec)
|
|
333
|
+
except Exception as e:
|
|
334
|
+
print("Failed to extract cpup", e)
|
|
335
|
+
|
|
336
|
+
slb2_path = os.path.join(pup_dec, "boot_slb2-00.pkg.seg02")
|
|
337
|
+
if os.path.exists(slb2_path):
|
|
338
|
+
slb2_dst = os.path.join(output, "SLB2")
|
|
339
|
+
os.mkdir(slb2_dst)
|
|
340
|
+
slb2_extract(slb2_path, slb2_dst)
|
|
341
|
+
|
|
342
|
+
slb2_dec = os.path.join(output, "SLB2_dec")
|
|
343
|
+
os.mkdir(slb2_dec)
|
|
344
|
+
slb2_decrypt(slb2_dst, slb2_dec)
|
|
345
|
+
else:
|
|
346
|
+
print("couldnt find boot_slb2-00.pkg.seg02")
|
|
347
|
+
|
|
348
|
+
extract_fs(output)
|
|
349
|
+
|
|
350
|
+
os.mkdir(os.path.join(output, "fs_dec"))
|
|
351
|
+
decrypt_fs(output)
|
|
352
|
+
decrypt_os0(output)
|
|
353
|
+
|
|
354
|
+
kd_folder = output+"/fs_dec/os0/kd"
|
|
355
|
+
bootimage_path = kd_folder+"/bootimage.elf"
|
|
356
|
+
if os.path.exists(bootimage_path):
|
|
357
|
+
bootimage_out = kd_folder+"/bootimage"
|
|
358
|
+
os.makedirs(bootimage_out, exist_ok=True)
|
|
359
|
+
extract_bootimage(bootimage_path, bootimage_out)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def main(argv = sys.argv):
|
|
363
|
+
class _args:
|
|
364
|
+
PUP: pathlib.Path
|
|
365
|
+
output: pathlib.Path
|
|
366
|
+
keys: pathlib.Path
|
|
367
|
+
|
|
368
|
+
parser = argparse.ArgumentParser()
|
|
369
|
+
parser.add_argument("PUP", type=pathlib.Path)
|
|
370
|
+
parser.add_argument("output", type=pathlib.Path)
|
|
371
|
+
parser.add_argument("-keys", type=pathlib.Path, default=pathlib.Path("./keys_external.py"), required=False)
|
|
372
|
+
args: _args = parser.parse_args(argv[1:]) # ty:ignore[invalid-assignment]
|
|
373
|
+
|
|
374
|
+
keys.use_keys(args.keys.as_posix())
|
|
375
|
+
extract_pup(args.PUP.as_posix(), args.output.as_posix())
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
if __name__ == "__main__":
|
|
379
|
+
main()
|
sceutils/pup_info.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import enum
|
|
5
|
+
import struct
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import IO
|
|
9
|
+
|
|
10
|
+
from .util import make_filename
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def s_read(format, f):
|
|
14
|
+
return struct.unpack(format, f.read(struct.calcsize(format)))
|
|
15
|
+
|
|
16
|
+
pup_types = {
|
|
17
|
+
0x100: "version.txt",
|
|
18
|
+
0x101: "license.xml",
|
|
19
|
+
0x200: "psp2swu.self",
|
|
20
|
+
0x204: "cui_setupper.self",
|
|
21
|
+
0x400: "package_scewm.wm",
|
|
22
|
+
0x401: "package_sceas.as",
|
|
23
|
+
0x2005: "UpdaterES1.CpUp",
|
|
24
|
+
0x2006: "UpdaterES2.CpUp",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PupTarget(enum.IntEnum):
|
|
29
|
+
TEST = 4
|
|
30
|
+
CEX = 2
|
|
31
|
+
DEX = 1
|
|
32
|
+
|
|
33
|
+
SCEUF_HEADER_SIZE = 0x80
|
|
34
|
+
SCEUF_FILEREC_SIZE = 0x20
|
|
35
|
+
@dataclass
|
|
36
|
+
class SCEUF:
|
|
37
|
+
magic: bytes #char[7]
|
|
38
|
+
format_flag: int #u8
|
|
39
|
+
format_version: int #u64
|
|
40
|
+
version: int #u32
|
|
41
|
+
build_no: int #u32
|
|
42
|
+
seg_num: int #u64
|
|
43
|
+
header_len: int #u64
|
|
44
|
+
data_len: int #u64
|
|
45
|
+
sign_alg: int #u32
|
|
46
|
+
sign_key_idx: int #u32
|
|
47
|
+
attr: int #u32
|
|
48
|
+
target: PupTarget #u32
|
|
49
|
+
sub_target: int #u32
|
|
50
|
+
support_list: int #u32
|
|
51
|
+
base_version: int #u32
|
|
52
|
+
base_build: int #u32
|
|
53
|
+
unk: bytes #char[0x30]
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def read(f: IO[bytes]):
|
|
57
|
+
ret = SCEUF(*s_read("7sBQIIQQQIIIIIIII48s", f))
|
|
58
|
+
ret.target = PupTarget(ret.target)
|
|
59
|
+
#ret.unk = None
|
|
60
|
+
return ret
|
|
61
|
+
|
|
62
|
+
def print(self):
|
|
63
|
+
print("-" * 80)
|
|
64
|
+
print(f"PUP Version: 0x{self.format_version:x}")
|
|
65
|
+
print(f"Firmware Version: 0x{self.version:08X}")
|
|
66
|
+
print(f"Build Number: {self.build_no}")
|
|
67
|
+
print(f"Number of Files: {self.seg_num}")
|
|
68
|
+
print(f"Target: {self.target.name}")
|
|
69
|
+
print("-" * 80)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def print_info(name: str):
|
|
73
|
+
typecount = defaultdict(int)
|
|
74
|
+
|
|
75
|
+
with open(name, "rb") as f:
|
|
76
|
+
header = SCEUF.read(f)
|
|
77
|
+
header.print()
|
|
78
|
+
|
|
79
|
+
for x in range(header.seg_num):
|
|
80
|
+
f.seek(SCEUF_HEADER_SIZE + x * SCEUF_FILEREC_SIZE)
|
|
81
|
+
rec = f.read(SCEUF_FILEREC_SIZE)
|
|
82
|
+
filetype, offset, length, flags = struct.unpack("<QQQQ", rec)
|
|
83
|
+
filename = pup_types.get(filetype)
|
|
84
|
+
|
|
85
|
+
if not filename:
|
|
86
|
+
f.seek(offset)
|
|
87
|
+
hdr = f.read(0x1000)
|
|
88
|
+
filename = make_filename(hdr, filetype, typecount)
|
|
89
|
+
|
|
90
|
+
print(f"{filename=} {filetype=} {offset=:x} {length=:x} {flags=:x}")
|
|
91
|
+
|
|
92
|
+
def main(argv = sys.argv):
|
|
93
|
+
if len(argv) < 2:
|
|
94
|
+
print(f"Usage: {argv[0]} (filename)")
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
fname = sys.argv[1]
|
|
97
|
+
print_info(fname)
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
main()
|
sceutils/scedecrypt.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
import pathlib
|
|
6
|
+
import sys
|
|
7
|
+
import zlib
|
|
8
|
+
from typing import IO
|
|
9
|
+
|
|
10
|
+
from Cryptodome.Cipher import AES
|
|
11
|
+
from Cryptodome.Util import Counter
|
|
12
|
+
|
|
13
|
+
from sceutils import keys, sceutils
|
|
14
|
+
from .scetypes import SceHeader
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def scedecrypt(inf: IO[bytes], outdir, decompress=True, silent=False):
|
|
18
|
+
sce = SceHeader.unpack(inf)
|
|
19
|
+
if not silent:
|
|
20
|
+
print(sce)
|
|
21
|
+
(sysver, selftype), use_spkg2 = sceutils.get_key_type(inf, sce, silent)
|
|
22
|
+
scesegs = sceutils.get_segments(inf, sce, sysver, selftype, silent=silent, use_spkg2=use_spkg2)
|
|
23
|
+
for i, sceseg in scesegs.items():
|
|
24
|
+
if not silent:
|
|
25
|
+
print(f'Decrypting segment {i}...')
|
|
26
|
+
inf.seek(sceseg.offset)
|
|
27
|
+
dat = inf.read(sceseg.size)
|
|
28
|
+
ctr = Counter.new(128, initial_value=int.from_bytes(sceseg.iv, "big"))
|
|
29
|
+
section_aes = AES.new(sceseg.key, AES.MODE_CTR, counter=ctr)
|
|
30
|
+
dat = section_aes.decrypt(dat)
|
|
31
|
+
if decompress and sceseg.compressed:
|
|
32
|
+
if not silent:
|
|
33
|
+
print(f'Decompressing segment {i}...')
|
|
34
|
+
z = zlib.decompressobj()
|
|
35
|
+
dat = z.decompress(dat)
|
|
36
|
+
with open(os.path.join(outdir, f"{os.path.basename(inf.name)}.seg{i:02}"), "wb") as outf:
|
|
37
|
+
outf.write(dat)
|
|
38
|
+
|
|
39
|
+
def main(argv = sys.argv):
|
|
40
|
+
parser = argparse.ArgumentParser()
|
|
41
|
+
parser.add_argument("filename", type=pathlib.Path)
|
|
42
|
+
parser.add_argument("outdir", type=pathlib.Path)
|
|
43
|
+
parser.add_argument("-keys", type=pathlib.Path, default="keys_external.py", required=False)
|
|
44
|
+
args = parser.parse_args(argv[1:])
|
|
45
|
+
|
|
46
|
+
keys.use_keys(args.keys)
|
|
47
|
+
|
|
48
|
+
with open(args.filename, "rb") as inf:
|
|
49
|
+
scedecrypt(inf, args.outdir)
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
main()
|