/
unpack-psb.py
executable file
·317 lines (232 loc) · 7.99 KB
/
unpack-psb.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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#!/usr/bin/env python3
import fnmatch
import optparse
import os
import psb
import global_vars
def load_from_psb(psb_filename):
if not options.quiet:
print("Reading '%s'" % psb_filename)
# Read in the encrypted/compressed psb data
psb_data2 = bytearray(open(psb_filename, 'rb').read())
# Decrypt the psb data using the filename, or --key if provided.
if options.key:
psb_data1 = psb.unobfuscate_data(psb_data2, options.key)
else:
psb_data1 = psb.unobfuscate_data(psb_data2, psb_filename)
if options.debug:
open(psb_filename + '.1', 'wb').write(psb_data1) # compressed
if psb_filename.endswith('.psb'):
# ".psb" files are not compressed, use the decrypted data
psb_data0 = psb_data2
else:
# Uncompress the psb data
psb_data0 = psb.uncompress_data(psb_data1)
if options.debug:
open(psb_filename + '.0', 'wb').write(psb_data0) # raw
# Check we have a PSB header
header = psb.HDRLEN()
header.unpack(psb.buffer_unpacker(psb_data0))
if header.signature != b'PSB\x00':
print("PSB header not found")
return
# Unpack the PSB structure
mypsb = psb.PSB()
mypsb.unpack(psb_data0)
# Get the base filename without any .psb.m
# '.psb.m' isn't a single extension :(
if psb_filename.endswith('.psb'):
base_filename = psb_filename[:-len('.psb')]
elif psb_filename.endswith('.psb.m'):
base_filename = psb_filename[:-len('.psb.m')]
else:
return
# Read in the alldata.bin file if it exists
bin_filename = base_filename + '.bin'
if os.path.isfile(bin_filename):
if not options.quiet:
print("Reading file %s" % bin_filename)
bin_data = bytearray(open(bin_filename, 'rb').read())
# Split the ADB data into each subfile.
# The data is in compressed/encrypted form
mypsb.split_subfiles(bin_data)
return mypsb
def load_from_yaml(yaml_filename):
if not options.quiet:
print("Reading '%s'" % yaml_filename)
yaml_data = open(yaml_filename, 'rt').read()
mypsb = psb.PSB()
mypsb.load_yaml(yaml_data)
# Read in our subfiles
# This will update the PSB.fileinfo[] entries with the new lengths etc
if not options.quiet:
print("Reading subfiles")
mypsb.read_all_subfiles(os.path.dirname(yaml_filename))
# Read in our chunk files
if not options.quiet:
print("Reading chunk files")
mypsb.read_chunks(os.path.dirname(yaml_filename))
return mypsb
# Write out the yaml file
def write_yaml(mypsb):
filename = options.basename + '.yaml'
if os.path.isfile(filename):
print("File '%s' exists, not over-writing" % filename)
return
if not options.quiet:
print("Writing '%s'" % filename)
open(filename, 'wt').write(mypsb.print_yaml())
# Write out our PSB
def write_psb(mypsb):
filename = options.output
if os.path.isfile(filename):
print("File '%s' exists, not over-writing" % filename)
return
if not options.quiet:
print("Writing '%s'" % filename)
# Pack our PSB object into the on-disk format
psb_data0 = mypsb.pack()
if options.debug:
open(filename + '.0', 'wb').write(psb_data0) # raw
if filename.endswith('.psb'):
# Write out the data as-is
open(filename, 'wb').write(psb_data0)
elif filename.endswith('.psb.m'):
# Compress the PSB data
psb_data1 = psb.compress_data(psb_data0)
if options.debug:
open(filename + '.1', 'wb').write(psb_data1) # compressed
# Encrypt the PSB data
if options.key:
psb_data2 = psb.unobfuscate_data(psb_data1, options.key)
else:
psb_data2 = psb.unobfuscate_data(psb_data1, filename)
if options.debug:
open(filename + '.2', 'wb').write(psb_data2) # compressed/encrypted
# Write out the compressed/encrypted PSB data
open(filename, 'wb').write(psb_data2)
# Write out our rom file
def write_rom_file(mypsb):
if not mypsb.fileinfo:
return
filename = options.basename + '.rom'
if os.path.isfile(filename):
print("File '%s' exists, not over-writing" % filename)
return
if not options.quiet:
print("Writing '%s'" % filename)
mypsb.write_rom_file(filename)
# Write out our subfiles
def write_subfiles(mypsb):
if not mypsb.fileinfo:
return
if not options.quiet:
print("Writing subfiles")
base_dir = options.basename + '.files'
mypsb.write_all_subfiles(base_dir)
# Write out our chunks
def write_chunks(mypsb):
if not options.quiet:
print("Writing chunk files")
base_dir = options.basename + '.chunks'
mypsb.write_chunks(base_dir)
# Write out the ADB
def write_bin(mypsb):
# Join the subfiles back into a single ADB
bin_data = mypsb.join_subfiles()
if bin_data is None:
return
filename = options.basename + '.bin'
if os.path.isfile(filename):
print("File '%s' exists, not over-writing" % filename)
return
if not options.quiet:
print("Writing '%s'" % filename)
open(filename, 'wb').write(bytes(bin_data))
def replace_rom_file(mypsb):
if options.rom:
filename = options.rom
if not options.quiet:
print("Reading '%s'" % filename)
fd = open(filename, 'rb').read()
mypsb.replace_rom_file(fd)
def main():
class MyParser(optparse.OptionParser):
def format_epilog(self, formatter):
return self.expand_prog_name(self.epilog)
parser = MyParser(usage='Usage: %prog [options] <filename>', epilog=
"""
-----
To insert a rom:
%prog -r /path/to/new.rom -o workdir/alldata.psb.m originaldir/alldata.psb.m
This will:
* Read in originaldir/alldata{.psb.m, .bin}
* Save the original rom in workdir/alldata.rom
* Replace the original rom with /path/to/new.rom
* Create workdir/alldata{.psb.m, .bin}
The file workdir/alldata.psb.m will be encrypted with 'alldata.psb.m'
-----
Examples:
%prog -o output.psb.m alldata.psb.m
This will:
* Read alldata.psb.m (and alldata.bin)
* Save the original rom in output.rom
* Create output{.psb.m, .bin}
The file output.psb.m will be encrypted with 'output.psb.m'
%prog -y -f -o output.psb.m alldata.psb.m
This will:
* Read alldata.psb.m (and alldata.bin)
* Create output{.psb.m, .bin}
* Create output.yaml
* Save any sub-files in output.files/
* Save any chunks in output.files/
%prog -o output2.psb.m -k mysecretkey output.yaml
This will:
* Read output.yaml (and output.files/* and output.chunks/*)
* Create output2{.psb.m, .bin}
The file output2.psb.m will be encrypted with 'mysecretkey'
""")
parser.add_option('-q', '--quiet', dest='quiet', help='quiet output', action='store_true', default=False)
parser.add_option('-v', '--verbose', dest='verbose', help='verbose output', action='store_true', default=False)
parser.add_option('-d', '--debug', dest='debug', help='debugging output', action='store_true', default=False)
parser.add_option('-f', '--files', dest='files', help='write output.files/*', action='store_true', default=False)
parser.add_option('-y', '--yaml', dest='yaml', help='write output.yaml', action='store_true', default=False)
parser.add_option('-k', '--key', dest='key', help='encrypt output.psb.m using KEY', metavar='KEY')
parser.add_option('-o', '--output', dest='output', help='write new psb to OUTPUT', metavar='OUTPUT')
parser.add_option('-r', '--rom', dest='rom', help='replace the rom file with ROM', metavar='ROM')
(options, args) = parser.parse_args()
if options.verbose:
global_vars.verbose = 1
if not args:
parser.print_help()
# Remove the extension from the output filename
if options.output:
filename = options.output
# '.psb.m' isn't a single extension :(
if filename.endswith('.psb'):
options.basename = filename[:-len('.psb')]
elif filename.endswith('.psb.m'):
options.basename = filename[:-len('.psb.m')]
for filename in args:
if filename.endswith('.psb') or filename.endswith('.psb.m'):
mypsb = load_from_psb(filename)
elif filename.endswith('.yaml'):
mypsb = load_from_yaml(filename)
if options.basename:
# Make sure the directory exists
base_dir = os.path.dirname(options.basename)
if base_dir:
os.makedirs(base_dir, exist_ok = True)
# Write out the existing rom *before* we replace it
write_rom_file(mypsb)
replace_rom_file(mypsb)
# Write out the YAML first for debugging
if options.yaml:
write_yaml(mypsb)
write_psb(mypsb)
write_bin(mypsb)
if options.files:
write_subfiles(mypsb)
write_chunks(mypsb)
if __name__ == "__main__":
main()