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