-
Notifications
You must be signed in to change notification settings - Fork 1
/
extract.py
153 lines (130 loc) · 6.24 KB
/
extract.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
import os, sys
chunked = []
unchunked = []
generation = 0
with open(sys.argv[1], 'rb') as file:
header = file.read(500)
# Determine if the image is DCT4 or BB5
if header.find(bytes([ord('B'), ord('B'), ord('5')])) != -1:
generation = 5
if header.find(bytes([ord('D'), ord('C'), ord('T'), ord('4')])) != -1:
generation = 4
# Decode image into 16K chunks, procedure is slightly different depending on generation
if generation == 4:
if header.find(bytes([0x14, 0x01])) != -1:
file.seek(header.find(bytes([0x14, 0x01])) + 10)
elif header.find(bytes([0x14, 0x02])) != -1:
file.seek(header.find(bytes([0x14, 0x02])) + 10)
while chunk := file.read(16384):
chunked += chunk
file.seek(10, 1)
elif generation == 5:
file.seek(header.find(bytes([0x54, 0x01, 0x17])) + 19)
while chunk := file.read(16384):
chunked += chunk
file.seek(19, 1)
else:
raise ValueError("Cannot determine hardware generation, try a valid DCT4 or BB5 image")
# Debug
# with open(sys.argv[1] + '.dec', 'wb') as file:
# file.write(bytes(chunked))
i = 0
last_chunk_idx = -1
top_chunk_idx = -1
while True:
if generation == 4:
# (0xFF 0xF0 0xFF 0xFF) (4 bytes: chunk index) (512 bytes: chunk data) - write chunk
if chunked[i:i+4] == [0xff, 0xf0, 0xff, 0xff]:
i += 4
chunk_idx = int.from_bytes(chunked[i:i+4], byteorder='big')
i += 4
# If skipped through chunks, write blank chunks between the last one and the current one's position
while chunk_idx > last_chunk_idx + 1:
unchunked += [0] * 512
last_chunk_idx += 1
top_chunk_idx = -1
if chunk_idx > top_chunk_idx:
# Append new chunk
unchunked += bytes(chunked[i:i+512])
top_chunk_idx = chunk_idx
else:
# if chunk indexes are not in sequential order (some images like 6020, but not 6030), we need to insert the data in between
unchunked = unchunked[:chunk_idx*512] + bytes(chunked[i:i+512]) + unchunked[chunk_idx*512 + 512:]
i += 511
last_chunk_idx = chunk_idx
# PPM file system structure for 2650
# Also used as a separate partition for storing Java content on 3220/6020/7260
#
# Data structure (big endian):
# ...some metadata that we don't care about...
# 10 bytes: 0xFF FF 00 00 00 E8 00 00 00 F8 - we use these bytes to identify a file
# 200 bytes: filename utf16be
# 32 bytes: irrelevant
# ...keep seeking until we find 0xFF FF or 0x01 FF (skip those two bytes)
# 4 bytes: chunk size
# 4 bytes: irrelevant
# ...first chunk data...
# 2 bytes: 0xF0 0xF0 means the start of another chunk
# 22 bytes: irrelevant
# 4 bytes: chunk size
# 4 bytes: irrelevant
# ...second chunk data...
# 2 bytes: 0xFF 0xFF means the end of this file and beginning of another
# There are file system entries for folders and such, so we could preserve
# the original folder structure, but that's not too important to me and it
# requires a bit more work
if chunked[i:i+10] == [0xFF, 0xFF, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0xF8]:
i += 10
filename = bytes.decode(bytes(chunked[i:i+200]), "utf_16_be")
filename = filename.replace("\x00", "") # because bytes.decode apparently doesn't null terminate
i += 200
if chunked[i] == 0x07:
with open(os.path.join(sys.argv[2], filename), 'wb') as java_file:
i += 32
filesize = 0
while filesize <= 16:
while chunked[i:i+3] != [0xFF, 0x00, 0x00]: i += 1
i += 1
filesize = int.from_bytes(chunked[i:i+4], byteorder='big')
while True:
i += 8
java_file.write(bytes(chunked[i:i+filesize]))
i += filesize
# 0xF0 0xF0 means there is another chunk of this file, 0xFF 0xFF is the beginning of the next file
if chunked[i:i+2] == [0xF0, 0xF0]:
# i += 22
while chunked[i:i+3] != [0xFF, 0x00, 0x00]: i += 1
i += 1
filesize = int.from_bytes(chunked[i:i+4], byteorder='big')
else:
i -= 1
break
elif generation == 5:
# (0xF0 0xFF 0xFF 0xFF) (4 bytes: chunk index) (512 bytes: chunk data) - write chunk
# This is basically the same as the DCT4 image format but with different chunk header bytes and endianness
if chunked[i:i+4] == [0xf0, 0xff, 0xff, 0xff]:
i += 4
chunk_idx = int.from_bytes(chunked[i:i+4], byteorder='little')
i += 4
# Occurs on some images like 6085 if the actual chunk data contains 0xF0 0xFF 0xFF 0xFF
# Not really any good easy way to deal with this
if chunk_idx > 100000:
raise ValueError(f"Chunk index is too high at position {i}")
# If skipped through chunks, write blank chunks between the last one and the current one's position
while chunk_idx > last_chunk_idx + 1:
unchunked += [0] * 512
last_chunk_idx += 1
top_chunk_idx = -1
if chunk_idx > top_chunk_idx:
# Append new chunk
unchunked += bytes(chunked[i:i+512])
top_chunk_idx = chunk_idx
else:
# if chunk indexes are not in sequential order (idk which BB5s have/don't have this), we need to insert the data in between
unchunked = unchunked[:chunk_idx*512] + bytes(chunked[i:i+512]) + unchunked[chunk_idx*512 + 512:]
i += 511
last_chunk_idx = chunk_idx
i += 1
if i >= len(chunked): break
with open(sys.argv[1] + ".img", 'wb') as out_file:
out_file.write(bytes(unchunked))