PyCriCodecsEx 0.0.1__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.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.
- CriCodecsEx.cpython-310-x86_64-linux-gnu.so +0 -0
- PyCriCodecsEx/__init__.py +1 -0
- PyCriCodecsEx/acb.py +100 -0
- PyCriCodecsEx/adx.py +16 -0
- PyCriCodecsEx/awb.py +151 -0
- PyCriCodecsEx/chunk.py +75 -0
- PyCriCodecsEx/cpk.py +732 -0
- PyCriCodecsEx/hca.py +302 -0
- PyCriCodecsEx/usm.py +1266 -0
- PyCriCodecsEx/utf.py +704 -0
- pycricodecsex-0.0.1.dist-info/METADATA +81 -0
- pycricodecsex-0.0.1.dist-info/RECORD +15 -0
- pycricodecsex-0.0.1.dist-info/WHEEL +6 -0
- pycricodecsex-0.0.1.dist-info/licenses/LICENSE +21 -0
- pycricodecsex-0.0.1.dist-info/top_level.txt +2 -0
PyCriCodecsEx/cpk.py
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import BinaryIO
|
|
3
|
+
from io import BytesIO, FileIO
|
|
4
|
+
from PyCriCodecsEx.chunk import *
|
|
5
|
+
from PyCriCodecsEx.utf import UTF, UTFBuilder
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
8
|
+
from tempfile import NamedTemporaryFile
|
|
9
|
+
import CriCodecsEx
|
|
10
|
+
|
|
11
|
+
def worker_do_compression(src : str, dst: str):
|
|
12
|
+
with open(src, "rb") as fsrc, open(dst, "wb") as fdst:
|
|
13
|
+
data = fsrc.read()
|
|
14
|
+
compressed = CriCodecsEx.CriLaylaCompress(data)
|
|
15
|
+
fdst.write(compressed)
|
|
16
|
+
@dataclass
|
|
17
|
+
class _PackFile():
|
|
18
|
+
stream: BinaryIO
|
|
19
|
+
path: str
|
|
20
|
+
offset: int
|
|
21
|
+
size : int
|
|
22
|
+
compressed : bool = False
|
|
23
|
+
|
|
24
|
+
def get_bytes(self) -> bytes:
|
|
25
|
+
self.stream.seek(self.offset)
|
|
26
|
+
data = self.stream.read(self.size)
|
|
27
|
+
if self.compressed:
|
|
28
|
+
data = CriCodecsEx.CriLaylaDecompress(data)
|
|
29
|
+
return data
|
|
30
|
+
|
|
31
|
+
def save(self, path : str):
|
|
32
|
+
with open(path, "wb") as f:
|
|
33
|
+
f.write(self.get_bytes())
|
|
34
|
+
class _TOC():
|
|
35
|
+
magic: bytes
|
|
36
|
+
encflag: int
|
|
37
|
+
packet_size: int
|
|
38
|
+
unk0C: int
|
|
39
|
+
stream: BinaryIO
|
|
40
|
+
table: dict
|
|
41
|
+
def __init__(self, stream: bytes) -> None:
|
|
42
|
+
self.stream = BytesIO(stream)
|
|
43
|
+
self.magic, self.encflag, self.packet_size, self.unk0C = CPKChunkHeader.unpack(
|
|
44
|
+
self.stream.read(CPKChunkHeader.size)
|
|
45
|
+
)
|
|
46
|
+
if self.magic not in [header.value for header in CPKChunkHeaderType]:
|
|
47
|
+
raise ValueError(f"{self.magic} header not supported.")
|
|
48
|
+
self.table = UTF(self.stream.read()).table
|
|
49
|
+
|
|
50
|
+
class CPK:
|
|
51
|
+
magic: bytes
|
|
52
|
+
encflag: int
|
|
53
|
+
packet_size: int
|
|
54
|
+
unk0C: int
|
|
55
|
+
stream: BinaryIO
|
|
56
|
+
tables: dict
|
|
57
|
+
filename: str
|
|
58
|
+
def __init__(self, filename) -> None:
|
|
59
|
+
if type(filename) == str:
|
|
60
|
+
self.filename = filename
|
|
61
|
+
self.stream = FileIO(filename)
|
|
62
|
+
else:
|
|
63
|
+
self.stream = BytesIO(filename)
|
|
64
|
+
self.filename = ''
|
|
65
|
+
self.magic, self.encflag, self.packet_size, self.unk0C = CPKChunkHeader.unpack(
|
|
66
|
+
self.stream.read(CPKChunkHeader.size)
|
|
67
|
+
)
|
|
68
|
+
if self.magic != CPKChunkHeaderType.CPK.value:
|
|
69
|
+
raise ValueError("Invalid CPK file.")
|
|
70
|
+
self.tables = dict(CPK = UTF(self.stream.read(0x800-CPKChunkHeader.size)).table)
|
|
71
|
+
self._load_tocs()
|
|
72
|
+
|
|
73
|
+
def _load_tocs(self) -> None:
|
|
74
|
+
for key, value in self.tables["CPK"].items():
|
|
75
|
+
if key == "TocOffset":
|
|
76
|
+
if value[0]:
|
|
77
|
+
self.stream.seek(value[0], 0)
|
|
78
|
+
self.tables["TOC"] = _TOC(self.stream.read(self.tables['CPK']["TocSize"][0])).table
|
|
79
|
+
elif key == "ItocOffset":
|
|
80
|
+
if value[0]:
|
|
81
|
+
self.stream.seek(value[0], 0)
|
|
82
|
+
self.tables["ITOC"] = _TOC(self.stream.read(self.tables['CPK']["ItocSize"][0])).table
|
|
83
|
+
if "DataL" in self.tables["ITOC"]:
|
|
84
|
+
self.tables["ITOC"]['DataL'][0] = UTF(self.tables["ITOC"]['DataL'][0]).table
|
|
85
|
+
if "DataH" in self.tables["ITOC"]:
|
|
86
|
+
self.tables["ITOC"]['DataH'][0] = UTF(self.tables["ITOC"]['DataH'][0]).table
|
|
87
|
+
elif key == "HtocOffset":
|
|
88
|
+
if value[0]:
|
|
89
|
+
self.stream.seek(value[0], 0)
|
|
90
|
+
self.tables["HTOC"] = _TOC(self.stream.read(self.tables['CPK']["HtocSize"][0])).table
|
|
91
|
+
elif key == "GtocOffset":
|
|
92
|
+
if value[0]:
|
|
93
|
+
self.stream.seek(value[0], 0)
|
|
94
|
+
self.tables["GTOC"] = _TOC(self.stream.read(self.tables['CPK']["GtocSize"][0])).table
|
|
95
|
+
if "AttrData" in self.tables["GTOC"]:
|
|
96
|
+
self.tables["GTOC"]['AttrData'][0] = UTF(self.tables["GTOC"]['AttrData'][0]).table
|
|
97
|
+
if "Fdata" in self.tables["GTOC"]:
|
|
98
|
+
self.tables["GTOC"]['Fdata'][0] = UTF(self.tables["GTOC"]['Fdata'][0]).table
|
|
99
|
+
if "Gdata" in self.tables["GTOC"]:
|
|
100
|
+
self.tables["GTOC"]['Gdata'][0] = UTF(self.tables["GTOC"]['Gdata'][0]).table
|
|
101
|
+
elif key == "HgtocOffset":
|
|
102
|
+
if value[0]:
|
|
103
|
+
self.stream.seek(value[0], 0)
|
|
104
|
+
self.tables["HGTOC"] = _TOC(self.stream.read(self.tables['CPK']["HgtocSize"][0])).table
|
|
105
|
+
elif key == "EtocOffset":
|
|
106
|
+
if value[0]:
|
|
107
|
+
self.stream.seek(value[0], 0)
|
|
108
|
+
self.tables["ETOC"] = _TOC(self.stream.read(self.tables['CPK']["EtocSize"][0])).table
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def mode(self):
|
|
112
|
+
TOC, ITOC, GTOC = 'TOC' in self.tables, 'ITOC' in self.tables, 'GTOC' in self.tables
|
|
113
|
+
if TOC and ITOC and GTOC:
|
|
114
|
+
return 3
|
|
115
|
+
elif TOC and ITOC:
|
|
116
|
+
return 2
|
|
117
|
+
elif TOC:
|
|
118
|
+
return 1
|
|
119
|
+
elif ITOC:
|
|
120
|
+
return 0
|
|
121
|
+
raise ValueError("Unknown CPK mode.")
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def files(self):
|
|
125
|
+
"""Retrieves a list of all files in the CPK archive."""
|
|
126
|
+
if "TOC" in self.tables:
|
|
127
|
+
toctable = self.tables['TOC']
|
|
128
|
+
rel_off = 0x800
|
|
129
|
+
for i in range(len(toctable['FileName'])):
|
|
130
|
+
dirname = toctable["DirName"][i%len(toctable["DirName"])]
|
|
131
|
+
filename = toctable['FileName'][i]
|
|
132
|
+
if len(filename) >= 255:
|
|
133
|
+
filename = filename[:250] + "_" + str(i) # 250 because i might be 4 digits long.
|
|
134
|
+
if toctable['ExtractSize'][i] > toctable['FileSize'][i]:
|
|
135
|
+
self.stream.seek(rel_off+toctable["FileOffset"][i], 0)
|
|
136
|
+
yield _PackFile(self.stream, os.path.join(dirname,filename), self.stream.tell(), toctable['FileSize'][i], compressed=True)
|
|
137
|
+
else:
|
|
138
|
+
self.stream.seek(rel_off+toctable["FileOffset"][i], 0)
|
|
139
|
+
yield _PackFile(self.stream, os.path.join(dirname,filename), self.stream.tell(), toctable['FileSize'][i])
|
|
140
|
+
elif "ITOC" in self.tables:
|
|
141
|
+
toctableL = self.tables["ITOC"]['DataL'][0]
|
|
142
|
+
toctableH = self.tables["ITOC"]['DataH'][0]
|
|
143
|
+
align = self.tables['CPK']["Align"][0]
|
|
144
|
+
offset = self.tables["CPK"]["ContentOffset"][0]
|
|
145
|
+
files = self.tables["CPK"]["Files"][0]
|
|
146
|
+
self.stream.seek(offset, 0)
|
|
147
|
+
for i in sorted(toctableH['ID']+toctableL['ID']):
|
|
148
|
+
if i in toctableH['ID']:
|
|
149
|
+
idx = toctableH['ID'].index(i)
|
|
150
|
+
if toctableH['ExtractSize'][idx] > toctableH['FileSize'][idx]:
|
|
151
|
+
yield _PackFile(self.stream, str(i), self.stream.tell(), toctableH['FileSize'][idx], compressed=True)
|
|
152
|
+
else:
|
|
153
|
+
yield _PackFile(self.stream, str(i), self.stream.tell(), toctableH['FileSize'][idx])
|
|
154
|
+
if toctableH['FileSize'][idx] % align != 0:
|
|
155
|
+
seek_size = (align - toctableH['FileSize'][idx] % align)
|
|
156
|
+
self.stream.seek(seek_size, 1)
|
|
157
|
+
elif i in toctableL['ID']:
|
|
158
|
+
idx = toctableL['ID'].index(i)
|
|
159
|
+
if toctableL['ExtractSize'][idx] > toctableL['FileSize'][idx]:
|
|
160
|
+
yield _PackFile(self.stream, str(i), self.stream.tell(), toctableL['FileSize'][idx], compressed=True)
|
|
161
|
+
else:
|
|
162
|
+
yield _PackFile(self.stream, str(i), self.stream.tell(), toctableL['FileSize'][idx])
|
|
163
|
+
if toctableL['FileSize'][idx] % align != 0:
|
|
164
|
+
seek_size = (align - toctableL['FileSize'][idx] % align)
|
|
165
|
+
self.stream.seek(seek_size, 1)
|
|
166
|
+
class CPKBuilder:
|
|
167
|
+
""" Use this class to build semi-custom CPK archives. """
|
|
168
|
+
mode: int
|
|
169
|
+
# CPK mode dictates (at least from what I saw) the use of filenames in TOC or the use of
|
|
170
|
+
# ITOC without any filenames (Use of ID's only, will be sorted).
|
|
171
|
+
# CPK mode of 0 = Use of ITOC only, CPK mode = 1, use of TOC, ITOC and optionally ETOC?
|
|
172
|
+
Tver: str
|
|
173
|
+
# Seems to be CPKMaker/CPKDLL version, I will put in one of the few ones I found as default.
|
|
174
|
+
# I am not sure if this affects the modding these files.
|
|
175
|
+
# However, you can change it.
|
|
176
|
+
dirname: str
|
|
177
|
+
itoc_size: int
|
|
178
|
+
encrypt: bool
|
|
179
|
+
encoding: str
|
|
180
|
+
fileslen: int
|
|
181
|
+
ITOCdata: bytearray
|
|
182
|
+
TOCdata: bytearray
|
|
183
|
+
CPKdata: bytearray
|
|
184
|
+
ContentSize: int
|
|
185
|
+
EnabledDataSize: int
|
|
186
|
+
EnabledPackedSize: int
|
|
187
|
+
outfile: str
|
|
188
|
+
init_toc_len: int # This is a bit of a redundancy, but some CPK's need it.
|
|
189
|
+
|
|
190
|
+
in_files : list[tuple[str, str, bool]] # (source path, dest filename, compress or not)
|
|
191
|
+
os_files : list[tuple[str, bool]] # (os path, temp or not)
|
|
192
|
+
files: list[tuple[str, int, int]] # (filename, file size, compressed file size).
|
|
193
|
+
|
|
194
|
+
progress_cb : callable # Progress callback taking (task name, current, total)
|
|
195
|
+
|
|
196
|
+
def __init__(self, mode: int = 1, Tver: str = None, encrypt: bool = False, encoding: str = "utf-8", progress_cb : callable = None) -> None:
|
|
197
|
+
"""Setup CPK file building
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
mode (int, optional): CPK mode. 0: ID Only (ITOC), 1: Name Only (TOC), 2: Name + ID (ITOC + TOC), 3: Name + ID + GTOC (GTOC). Defaults to 1.
|
|
201
|
+
Tver (str, optional): CPK version. Defaults to None.
|
|
202
|
+
encrypt (bool, optional): Enable encryption. Defaults to False.
|
|
203
|
+
encoding (str, optional): Filename encoding. Defaults to "utf-8".
|
|
204
|
+
progress_cb (callable, optional): Progress callback taking (task name, current, total). Defaults to None.
|
|
205
|
+
"""
|
|
206
|
+
self.progress_cb = progress_cb
|
|
207
|
+
if not self.progress_cb:
|
|
208
|
+
self.progress_cb = lambda task_name, current, total: None
|
|
209
|
+
self.mode = mode
|
|
210
|
+
if not Tver:
|
|
211
|
+
# Some default ones I found with the matching CpkMode, hope they are good enough for all cases.
|
|
212
|
+
if self.mode == 0:
|
|
213
|
+
self.Tver = 'CPKMC2.18.04, DLL2.78.04'
|
|
214
|
+
elif self.mode == 1:
|
|
215
|
+
self.Tver = 'CPKMC2.45.00, DLL3.15.00'
|
|
216
|
+
elif self.mode == 2:
|
|
217
|
+
self.Tver = 'CPKMC2.49.32, DLL3.24.00'
|
|
218
|
+
elif self.mode == 3:
|
|
219
|
+
self.Tver = 'CPKFBSTD1.49.35, DLL3.24.00'
|
|
220
|
+
else:
|
|
221
|
+
raise ValueError("Unknown CpkMode.")
|
|
222
|
+
else:
|
|
223
|
+
self.Tver = Tver
|
|
224
|
+
if self.mode not in [0, 1, 2, 3]:
|
|
225
|
+
raise ValueError("Unknown CpkMode.")
|
|
226
|
+
|
|
227
|
+
self.encrypt = encrypt
|
|
228
|
+
self.encoding = encoding
|
|
229
|
+
self.EnabledDataSize = 0
|
|
230
|
+
self.EnabledPackedSize = 0
|
|
231
|
+
self.ContentSize = 0
|
|
232
|
+
self.in_files = []
|
|
233
|
+
self.os_files = []
|
|
234
|
+
|
|
235
|
+
def add_file(self, src : str, dst : str = None, compress=False):
|
|
236
|
+
"""Add a file to the bundle.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
src (str): The source file path.
|
|
240
|
+
dst (str): The destination full file name (containing directory). Can be None in ITOC Mode. Defaults to None.
|
|
241
|
+
compress (bool, optional): Whether to compress the file. Defaults to False.
|
|
242
|
+
|
|
243
|
+
NOTE:
|
|
244
|
+
- In ITOC-related mode, the insertion order determines the final integer ID of the files.
|
|
245
|
+
- Compression can be VERY slow with high entropy files (e.g. encoded media). Use at discretion.
|
|
246
|
+
"""
|
|
247
|
+
if not dst and self.mode != 0:
|
|
248
|
+
raise ValueError("Destination filename must be specified in non-ITOC mode.")
|
|
249
|
+
|
|
250
|
+
self.in_files.append((src, dst, compress))
|
|
251
|
+
|
|
252
|
+
def _writetofile(self, header) -> None:
|
|
253
|
+
with open(self.outfile, "wb") as out:
|
|
254
|
+
out.write(header)
|
|
255
|
+
for i, ((path, _), (filename, file_size, pack_size)) in enumerate(zip(self.os_files, self.files)):
|
|
256
|
+
src = open(path, 'rb').read()
|
|
257
|
+
out.write(src)
|
|
258
|
+
out.write(bytes(0x800 - pack_size % 0x800))
|
|
259
|
+
self.progress_cb("Write %s" % os.path.basename(filename), i + 1, len(self.files))
|
|
260
|
+
|
|
261
|
+
def _populate_files(self, parallel : bool):
|
|
262
|
+
self.files = []
|
|
263
|
+
for src, dst, compress in self.in_files:
|
|
264
|
+
if compress:
|
|
265
|
+
tmp = NamedTemporaryFile(delete=False)
|
|
266
|
+
self.os_files.append((tmp.name, True))
|
|
267
|
+
else:
|
|
268
|
+
self.os_files.append((src, False))
|
|
269
|
+
if parallel:
|
|
270
|
+
with ProcessPoolExecutor() as exec:
|
|
271
|
+
futures = []
|
|
272
|
+
for (src, _, _), (dst, compress) in zip(self.in_files,self.os_files):
|
|
273
|
+
if compress:
|
|
274
|
+
futures.append(exec.submit(worker_do_compression, src, dst))
|
|
275
|
+
for i, fut in as_completed(futures):
|
|
276
|
+
try:
|
|
277
|
+
fut.result()
|
|
278
|
+
except:
|
|
279
|
+
pass
|
|
280
|
+
self.progress_cb("Compress %s" % os.path.basename(src), i + 1, len(futures))
|
|
281
|
+
else:
|
|
282
|
+
for i, ((src, _, _), (dst, compress)) in enumerate(zip(self.in_files,self.os_files)):
|
|
283
|
+
if compress:
|
|
284
|
+
worker_do_compression(src, dst)
|
|
285
|
+
self.progress_cb("Compress %s" % os.path.basename(src), i + 1, len(self.in_files))
|
|
286
|
+
for (src, filename, _) , (dst, _) in zip(self.in_files,self.os_files):
|
|
287
|
+
file_size = os.stat(src).st_size
|
|
288
|
+
pack_size = os.stat(dst).st_size
|
|
289
|
+
self.files.append((filename, file_size, pack_size))
|
|
290
|
+
|
|
291
|
+
def _cleanup_files(self):
|
|
292
|
+
self.files = []
|
|
293
|
+
for path, is_temp in self.os_files:
|
|
294
|
+
if not is_temp:
|
|
295
|
+
continue
|
|
296
|
+
try:
|
|
297
|
+
os.unlink(path)
|
|
298
|
+
except:
|
|
299
|
+
pass
|
|
300
|
+
self.os_files = []
|
|
301
|
+
|
|
302
|
+
def save(self, outfile : str, parallel : bool = False):
|
|
303
|
+
"""Build and save the bundle into a file
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
outfile (str): The output file path.
|
|
308
|
+
parallel (bool, optional): Whether to use parallel processing for file compression (if at all used). Defaults to False.
|
|
309
|
+
|
|
310
|
+
NOTE:
|
|
311
|
+
- Temporary files may be created during the process if compression is used.
|
|
312
|
+
- parallel uses multiprocessing. Make sure your main function is guarded with `if __name__ == '__main__'` clause.
|
|
313
|
+
"""
|
|
314
|
+
assert self.in_files, "cannot save empty bundle"
|
|
315
|
+
self.outfile = outfile
|
|
316
|
+
self._populate_files(parallel)
|
|
317
|
+
if self.encrypt:
|
|
318
|
+
encflag = 0
|
|
319
|
+
else:
|
|
320
|
+
encflag = 0xFF
|
|
321
|
+
data = None
|
|
322
|
+
if self.mode == 3:
|
|
323
|
+
self.TOCdata = self._generate_TOC()
|
|
324
|
+
self.TOCdata = bytearray(CPKChunkHeader.pack(b'TOC ', encflag, len(self.TOCdata), 0)) + self.TOCdata
|
|
325
|
+
self.TOCdata = self.TOCdata.ljust(len(self.TOCdata) + (0x800 - len(self.TOCdata) % 0x800), b'\x00')
|
|
326
|
+
assert self.init_toc_len == len(self.TOCdata)
|
|
327
|
+
self.GTOCdata = self._generate_GTOC()
|
|
328
|
+
self.GTOCdata = bytearray(CPKChunkHeader.pack(b'GTOC', encflag, len(self.GTOCdata), 0)) + self.GTOCdata
|
|
329
|
+
self.GTOCdata = self.GTOCdata.ljust(len(self.GTOCdata) + (0x800 - len(self.GTOCdata) % 0x800), b'\x00')
|
|
330
|
+
self.CPKdata = self._generate_CPK()
|
|
331
|
+
self.CPKdata = bytearray(CPKChunkHeader.pack(b'CPK ', encflag, len(self.CPKdata), 0)) + self.CPKdata
|
|
332
|
+
data = self.CPKdata.ljust(len(self.CPKdata) + (0x800 - len(self.CPKdata) % 0x800) - 6, b'\x00') + bytearray(b"(c)CRI") + self.TOCdata + self.GTOCdata
|
|
333
|
+
elif self.mode == 2:
|
|
334
|
+
self.TOCdata = self._generate_TOC()
|
|
335
|
+
self.TOCdata = bytearray(CPKChunkHeader.pack(b'TOC ', encflag, len(self.TOCdata), 0)) + self.TOCdata
|
|
336
|
+
self.TOCdata = self.TOCdata.ljust(len(self.TOCdata) + (0x800 - len(self.TOCdata) % 0x800), b'\x00')
|
|
337
|
+
assert self.init_toc_len == len(self.TOCdata)
|
|
338
|
+
self.ITOCdata = self._generate_ITOC()
|
|
339
|
+
self.ITOCdata = bytearray(CPKChunkHeader.pack(b'ITOC', encflag, len(self.ITOCdata), 0)) + self.ITOCdata
|
|
340
|
+
self.ITOCdata = self.ITOCdata.ljust(len(self.ITOCdata) + (0x800 - len(self.ITOCdata) % 0x800), b'\x00')
|
|
341
|
+
self.CPKdata = self._generate_CPK()
|
|
342
|
+
self.CPKdata = bytearray(CPKChunkHeader.pack(b'CPK ', encflag, len(self.CPKdata), 0)) + self.CPKdata
|
|
343
|
+
data = self.CPKdata.ljust(len(self.CPKdata) + (0x800 - len(self.CPKdata) % 0x800) - 6, b'\x00') + bytearray(b"(c)CRI") + self.TOCdata + self.ITOCdata
|
|
344
|
+
elif self.mode == 1:
|
|
345
|
+
self.TOCdata = self._generate_TOC()
|
|
346
|
+
self.TOCdata = bytearray(CPKChunkHeader.pack(b'TOC ', encflag, len(self.TOCdata), 0)) + self.TOCdata
|
|
347
|
+
self.TOCdata = self.TOCdata.ljust(len(self.TOCdata) + (0x800 - len(self.TOCdata) % 0x800), b'\x00')
|
|
348
|
+
assert self.init_toc_len == len(self.TOCdata)
|
|
349
|
+
self.CPKdata = self._generate_CPK()
|
|
350
|
+
self.CPKdata = bytearray(CPKChunkHeader.pack(b'CPK ', encflag, len(self.CPKdata), 0)) + self.CPKdata
|
|
351
|
+
data = self.CPKdata.ljust(len(self.CPKdata) + (0x800 - len(self.CPKdata) % 0x800) - 6, b'\x00') + bytearray(b"(c)CRI") + self.TOCdata
|
|
352
|
+
elif self.mode == 0:
|
|
353
|
+
self.ITOCdata = self._generate_ITOC()
|
|
354
|
+
self.ITOCdata = bytearray(CPKChunkHeader.pack(b'ITOC', encflag, len(self.ITOCdata), 0)) + self.ITOCdata
|
|
355
|
+
self.ITOCdata = self.ITOCdata.ljust(len(self.ITOCdata) + (0x800 - len(self.ITOCdata) % 0x800), b'\x00')
|
|
356
|
+
self.CPKdata = self._generate_CPK()
|
|
357
|
+
self.CPKdata = bytearray(CPKChunkHeader.pack(b'CPK ', encflag, len(self.CPKdata), 0)) + self.CPKdata
|
|
358
|
+
data = self.CPKdata.ljust(len(self.CPKdata) + (0x800 - len(self.CPKdata) % 0x800) - 6, b'\x00') + bytearray(b"(c)CRI") + self.ITOCdata
|
|
359
|
+
self._writetofile(data)
|
|
360
|
+
self._cleanup_files()
|
|
361
|
+
|
|
362
|
+
def _generate_GTOC(self) -> bytearray:
|
|
363
|
+
# NOTE: Practically useless
|
|
364
|
+
# I have no idea why are those numbers here.
|
|
365
|
+
Gdata = [
|
|
366
|
+
{
|
|
367
|
+
"Gname": (UTFTypeValues.string, ""),
|
|
368
|
+
"Child": (UTFTypeValues.int, -1),
|
|
369
|
+
"Next": (UTFTypeValues.int, 0)
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"Gname": (UTFTypeValues.string, "(none)"),
|
|
373
|
+
"Child": (UTFTypeValues.int, 0),
|
|
374
|
+
"Next": (UTFTypeValues.int, 0)
|
|
375
|
+
}
|
|
376
|
+
]
|
|
377
|
+
Fdata = [
|
|
378
|
+
{
|
|
379
|
+
"Next": (UTFTypeValues.int, -1),
|
|
380
|
+
"Child": (UTFTypeValues.int, -1),
|
|
381
|
+
"SortFlink": (UTFTypeValues.int, 2),
|
|
382
|
+
"Aindex": (UTFTypeValues.ushort, 0)
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"Next": (UTFTypeValues.int, 2),
|
|
386
|
+
"Child": (UTFTypeValues.int, 0),
|
|
387
|
+
"SortFlink": (UTFTypeValues.int, 1),
|
|
388
|
+
"Aindex": (UTFTypeValues.ushort, 0)
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"Next": (UTFTypeValues.int, 0),
|
|
392
|
+
"Child": (UTFTypeValues.int, 1),
|
|
393
|
+
"SortFlink": (UTFTypeValues.int, 2),
|
|
394
|
+
"Aindex": (UTFTypeValues.ushort, 0)
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
Attrdata = [
|
|
398
|
+
{
|
|
399
|
+
"Aname": (UTFTypeValues.string, ""),
|
|
400
|
+
"Align": (UTFTypeValues.ushort, 0x800),
|
|
401
|
+
"Files": (UTFTypeValues.uint, 0),
|
|
402
|
+
"FileSize": (UTFTypeValues.uint, 0)
|
|
403
|
+
}
|
|
404
|
+
]
|
|
405
|
+
payload = [
|
|
406
|
+
{
|
|
407
|
+
"Glink": (UTFTypeValues.uint, 2),
|
|
408
|
+
"Flink": (UTFTypeValues.uint, 3),
|
|
409
|
+
"Attr" : (UTFTypeValues.uint, 1),
|
|
410
|
+
"Gdata": (UTFTypeValues.bytes, UTFBuilder(Gdata, encrypt=False, encoding=self.encoding, table_name="CpkGtocGlink").bytes()),
|
|
411
|
+
"Fdata": (UTFTypeValues.bytes, UTFBuilder(Fdata, encrypt=False, encoding=self.encoding, table_name="CpkGtocFlink").bytes()),
|
|
412
|
+
"Attrdata": (UTFTypeValues.bytes, UTFBuilder(Attrdata, encrypt=False, encoding=self.encoding, table_name="CpkGtocAttr").bytes()),
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
return UTFBuilder(payload, encrypt=self.encrypt, encoding=self.encoding, table_name="CpkGtocInfo").bytes()
|
|
416
|
+
|
|
417
|
+
def _generate_TOC(self) -> bytearray:
|
|
418
|
+
payload = []
|
|
419
|
+
temp = []
|
|
420
|
+
count = 0
|
|
421
|
+
lent = 0
|
|
422
|
+
switch = False
|
|
423
|
+
sf = set()
|
|
424
|
+
sd = set()
|
|
425
|
+
for filename, store_size, full_size in self.files:
|
|
426
|
+
# Dirname management.
|
|
427
|
+
# Must be POSIX path
|
|
428
|
+
dirname = os.path.dirname(filename)
|
|
429
|
+
if dirname not in sd:
|
|
430
|
+
switch = True
|
|
431
|
+
lent += len(dirname) + 1
|
|
432
|
+
sd.update({dirname})
|
|
433
|
+
|
|
434
|
+
# Filename management.
|
|
435
|
+
flname = os.path.basename(filename)
|
|
436
|
+
if flname not in sf:
|
|
437
|
+
lent += len(flname) + 1
|
|
438
|
+
sf.update({flname})
|
|
439
|
+
count += 1
|
|
440
|
+
|
|
441
|
+
# This estimates how large the TOC table size is.
|
|
442
|
+
if switch and len(sd) != 1:
|
|
443
|
+
lent = (lent + (4 + 4 + 4 + 4 + 8 + 4) * count + 0x47 + 0x51) # 0x47 is header len when there are mutiple dirs.
|
|
444
|
+
else:
|
|
445
|
+
lent = (lent + (4 + 4 + 4 + 8 + 4) * count + 0x4B + 0x51) # 0x4B is header len when there is only one dir.
|
|
446
|
+
if lent % 8 != 0:
|
|
447
|
+
lent = 8 + (lent - 8) + (8 - (lent - 8) % 8)
|
|
448
|
+
lent += 0x10
|
|
449
|
+
lent = lent + (0x800 - lent % 0x800)
|
|
450
|
+
# init_toc_len will also be the first file offset.
|
|
451
|
+
# Used to assert that the estimated TOC length is equal to the actual length, just in case the estimating went wrong.
|
|
452
|
+
self.init_toc_len = lent
|
|
453
|
+
|
|
454
|
+
self.fileslen = count
|
|
455
|
+
count = 0
|
|
456
|
+
for filename, store_size, full_size in self.files:
|
|
457
|
+
sz = store_size
|
|
458
|
+
fz = full_size
|
|
459
|
+
if sz > 0xFFFFFFFF:
|
|
460
|
+
raise OverflowError("4GBs is the max size of a single file that can be bundled in a CPK archive of mode 1.")
|
|
461
|
+
self.EnabledDataSize += fz
|
|
462
|
+
self.EnabledPackedSize += sz
|
|
463
|
+
if sz % 0x800 != 0:
|
|
464
|
+
self.ContentSize += sz + (0x800 - sz % 0x800)
|
|
465
|
+
else:
|
|
466
|
+
self.ContentSize += sz
|
|
467
|
+
dirname = os.path.dirname(filename)
|
|
468
|
+
payload.append(
|
|
469
|
+
{
|
|
470
|
+
"DirName": (UTFTypeValues.string, dirname),
|
|
471
|
+
"FileName": (UTFTypeValues.string, os.path.basename(filename)),
|
|
472
|
+
"FileSize": (UTFTypeValues.uint, sz),
|
|
473
|
+
"ExtractSize": (UTFTypeValues.uint, fz),
|
|
474
|
+
"FileOffset": (UTFTypeValues.ullong, lent),
|
|
475
|
+
"ID": (UTFTypeValues.uint, count),
|
|
476
|
+
"UserString": (UTFTypeValues.string, "<NULL>")
|
|
477
|
+
}
|
|
478
|
+
)
|
|
479
|
+
count += 1
|
|
480
|
+
if sz % 0x800 != 0:
|
|
481
|
+
lent += sz + (0x800 - sz % 0x800)
|
|
482
|
+
else:
|
|
483
|
+
lent += sz
|
|
484
|
+
return UTFBuilder(payload, encrypt=self.encrypt, encoding=self.encoding, table_name="CpkTocInfo").bytes()
|
|
485
|
+
|
|
486
|
+
def _generate_ITOC(self) -> bytearray:
|
|
487
|
+
if self.mode == 2:
|
|
488
|
+
payload = []
|
|
489
|
+
for i, (filename, store_size, full_size) in enumerate(self.files):
|
|
490
|
+
payload.append(
|
|
491
|
+
{
|
|
492
|
+
"ID": (UTFTypeValues.int, i),
|
|
493
|
+
"TocIndex": (UTFTypeValues.int, i)
|
|
494
|
+
}
|
|
495
|
+
)
|
|
496
|
+
return UTFBuilder(payload, encrypt=self.encrypt, encoding=self.encoding, table_name="CpkExtendId").bytes()
|
|
497
|
+
else:
|
|
498
|
+
assert len(self.files) < 65535, "ITOC requires less than 65535 files."
|
|
499
|
+
self.fileslen = len(self.files)
|
|
500
|
+
datal = []
|
|
501
|
+
datah = []
|
|
502
|
+
for i, (filename, store_size, full_size) in enumerate(self.files):
|
|
503
|
+
sz = store_size
|
|
504
|
+
fz = full_size
|
|
505
|
+
self.EnabledDataSize += fz
|
|
506
|
+
self.EnabledPackedSize += sz
|
|
507
|
+
if sz % 0x800 != 0:
|
|
508
|
+
self.ContentSize += sz + (0x800 - sz % 0x800)
|
|
509
|
+
else:
|
|
510
|
+
self.ContentSize += sz
|
|
511
|
+
if sz > 0xFFFF:
|
|
512
|
+
dicth = {
|
|
513
|
+
"ID": (UTFTypeValues.ushort, i),
|
|
514
|
+
"FileSize": (UTFTypeValues.uint, sz),
|
|
515
|
+
"ExtractSize": (UTFTypeValues.uint, sz)
|
|
516
|
+
}
|
|
517
|
+
datah.append(dicth)
|
|
518
|
+
else:
|
|
519
|
+
dictl = {
|
|
520
|
+
"ID": (UTFTypeValues.ushort, i),
|
|
521
|
+
"FileSize": (UTFTypeValues.ushort, sz),
|
|
522
|
+
"ExtractSize": (UTFTypeValues.ushort, sz)
|
|
523
|
+
}
|
|
524
|
+
datal.append(dictl)
|
|
525
|
+
datallen = len(datal)
|
|
526
|
+
datahlen = len(datah)
|
|
527
|
+
if len(datal) == 0:
|
|
528
|
+
datal.append({"ID": (UTFTypeValues.ushort, 0), "FileSize": (UTFTypeValues.ushort, 0), "ExtractSize": (UTFTypeValues.ushort, 0)})
|
|
529
|
+
elif len(datah) == 0:
|
|
530
|
+
datah.append({"ID": (UTFTypeValues.uint, 0), "FileSize": (UTFTypeValues.uint, 0), "ExtractSize": (UTFTypeValues.uint, 0)})
|
|
531
|
+
payload = [
|
|
532
|
+
{
|
|
533
|
+
"FilesL" : (UTFTypeValues.uint, datallen),
|
|
534
|
+
"FilesH" : (UTFTypeValues.uint, datahlen),
|
|
535
|
+
"DataL" : (UTFTypeValues.bytes, UTFBuilder(datal, table_name="CpkItocL", encrypt=False, encoding=self.encoding).bytes()),
|
|
536
|
+
"DataH" : (UTFTypeValues.bytes, UTFBuilder(datah, table_name="CpkItocH", encrypt=False, encoding=self.encoding).bytes())
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
return UTFBuilder(payload, table_name="CpkItocInfo", encrypt=self.encrypt, encoding=self.encoding).bytes()
|
|
540
|
+
|
|
541
|
+
def _generate_CPK(self) -> bytearray:
|
|
542
|
+
if self.mode == 3:
|
|
543
|
+
ContentOffset = (0x800+len(self.TOCdata)+len(self.GTOCdata))
|
|
544
|
+
CpkHeader = [
|
|
545
|
+
{
|
|
546
|
+
"UpdateDateTime": (UTFTypeValues.ullong, 0),
|
|
547
|
+
"ContentOffset": (UTFTypeValues.ullong, ContentOffset),
|
|
548
|
+
"ContentSize": (UTFTypeValues.ullong, self.ContentSize),
|
|
549
|
+
"TocOffset": (UTFTypeValues.ullong, 0x800),
|
|
550
|
+
"TocSize": (UTFTypeValues.ullong, len(self.TOCdata)),
|
|
551
|
+
"EtocOffset": (UTFTypeValues.ullong, None),
|
|
552
|
+
"EtocSize": (UTFTypeValues.ullong, None),
|
|
553
|
+
"GtocOffset": (UTFTypeValues.ullong, 0x800+len(self.TOCdata)),
|
|
554
|
+
"GtocSize": (UTFTypeValues.ullong, len(self.GTOCdata)),
|
|
555
|
+
"EnabledPackedSize": (UTFTypeValues.ullong, self.EnabledPackedSize),
|
|
556
|
+
"EnabledDataSize": (UTFTypeValues.ullong, self.EnabledDataSize),
|
|
557
|
+
"Files": (UTFTypeValues.uint, self.fileslen),
|
|
558
|
+
"Groups": (UTFTypeValues.uint, 0),
|
|
559
|
+
"Attrs": (UTFTypeValues.uint, 0),
|
|
560
|
+
"Version": (UTFTypeValues.ushort, 7),
|
|
561
|
+
"Revision": (UTFTypeValues.ushort, 14),
|
|
562
|
+
"Align": (UTFTypeValues.ushort, 0x800),
|
|
563
|
+
"Sorted": (UTFTypeValues.ushort, 1),
|
|
564
|
+
"EnableFileName": (UTFTypeValues.ushort, 1),
|
|
565
|
+
"CpkMode": (UTFTypeValues.uint, self.mode),
|
|
566
|
+
"Tvers": (UTFTypeValues.string, self.Tver),
|
|
567
|
+
"Codec": (UTFTypeValues.uint, 0),
|
|
568
|
+
"DpkItoc": (UTFTypeValues.uint, 0),
|
|
569
|
+
"EnableTocCrc": (UTFTypeValues.ushort, None),
|
|
570
|
+
"EnableFileCrc": (UTFTypeValues.ushort, None),
|
|
571
|
+
"CrcMode": (UTFTypeValues.uint, None),
|
|
572
|
+
"CrcTable": (UTFTypeValues.bytes, b''),
|
|
573
|
+
"FileSize": (UTFTypeValues.ullong, None),
|
|
574
|
+
"TocCrc": (UTFTypeValues.uint, None),
|
|
575
|
+
"HtocOffset": (UTFTypeValues.ullong, None),
|
|
576
|
+
"HtocSize": (UTFTypeValues.ullong, None),
|
|
577
|
+
"ItocOffset": (UTFTypeValues.ullong, None),
|
|
578
|
+
"ItocSize": (UTFTypeValues.ullong, None),
|
|
579
|
+
"ItocCrc": (UTFTypeValues.uint, None),
|
|
580
|
+
"GtocCrc": (UTFTypeValues.uint, None),
|
|
581
|
+
"HgtocOffset": (UTFTypeValues.ullong, None),
|
|
582
|
+
"HgtocSize": (UTFTypeValues.ullong, None),
|
|
583
|
+
"TotalDataSize": (UTFTypeValues.ullong, None),
|
|
584
|
+
"Tocs": (UTFTypeValues.uint, None),
|
|
585
|
+
"TotalFiles": (UTFTypeValues.uint, None),
|
|
586
|
+
"Directories": (UTFTypeValues.uint, None),
|
|
587
|
+
"Updates": (UTFTypeValues.uint, None),
|
|
588
|
+
"EID": (UTFTypeValues.ushort, None),
|
|
589
|
+
"Comment": (UTFTypeValues.string, '<NULL>'),
|
|
590
|
+
}
|
|
591
|
+
]
|
|
592
|
+
elif self.mode == 2:
|
|
593
|
+
ContentOffset = 0x800+len(self.TOCdata)+len(self.ITOCdata)
|
|
594
|
+
CpkHeader = [
|
|
595
|
+
{
|
|
596
|
+
"UpdateDateTime": (UTFTypeValues.ullong, 0),
|
|
597
|
+
"ContentOffset": (UTFTypeValues.ullong, ContentOffset),
|
|
598
|
+
"ContentSize": (UTFTypeValues.ullong, self.ContentSize),
|
|
599
|
+
"TocOffset": (UTFTypeValues.ullong, 0x800),
|
|
600
|
+
"TocSize": (UTFTypeValues.ullong, len(self.TOCdata)),
|
|
601
|
+
"EtocOffset": (UTFTypeValues.ullong, None),
|
|
602
|
+
"EtocSize": (UTFTypeValues.ullong, None),
|
|
603
|
+
"ItocOffset": (UTFTypeValues.ullong, 0x800+len(self.TOCdata)),
|
|
604
|
+
"ItocSize": (UTFTypeValues.ullong, len(self.ITOCdata)),
|
|
605
|
+
"EnabledPackedSize": (UTFTypeValues.ullong, self.EnabledPackedSize),
|
|
606
|
+
"EnabledDataSize": (UTFTypeValues.ullong, self.EnabledDataSize),
|
|
607
|
+
"Files": (UTFTypeValues.uint, self.fileslen),
|
|
608
|
+
"Groups": (UTFTypeValues.uint, 0),
|
|
609
|
+
"Attrs": (UTFTypeValues.uint, 0),
|
|
610
|
+
"Version": (UTFTypeValues.ushort, 7),
|
|
611
|
+
"Revision": (UTFTypeValues.ushort, 14),
|
|
612
|
+
"Align": (UTFTypeValues.ushort, 0x800),
|
|
613
|
+
"Sorted": (UTFTypeValues.ushort, 1),
|
|
614
|
+
"EnableFileName": (UTFTypeValues.ushort, 1),
|
|
615
|
+
"EID": (UTFTypeValues.ushort, None),
|
|
616
|
+
"CpkMode": (UTFTypeValues.uint, self.mode),
|
|
617
|
+
"Tvers": (UTFTypeValues.string, self.Tver),
|
|
618
|
+
"Codec": (UTFTypeValues.uint, 0),
|
|
619
|
+
"DpkItoc": (UTFTypeValues.uint, 0),
|
|
620
|
+
"EnableTocCrc": (UTFTypeValues.ushort, None),
|
|
621
|
+
"EnableFileCrc": (UTFTypeValues.ushort, None),
|
|
622
|
+
"CrcMode": (UTFTypeValues.uint, None),
|
|
623
|
+
"CrcTable": (UTFTypeValues.bytes, b''),
|
|
624
|
+
"FileSize": (UTFTypeValues.ullong, None),
|
|
625
|
+
"TocCrc": (UTFTypeValues.uint, None),
|
|
626
|
+
"HtocOffset": (UTFTypeValues.ullong, None),
|
|
627
|
+
"HtocSize": (UTFTypeValues.ullong, None),
|
|
628
|
+
"ItocCrc": (UTFTypeValues.uint, None),
|
|
629
|
+
"GtocOffset": (UTFTypeValues.ullong, None),
|
|
630
|
+
"GtocSize": (UTFTypeValues.ullong, None),
|
|
631
|
+
"HgtocOffset": (UTFTypeValues.ullong, None),
|
|
632
|
+
"HgtocSize": (UTFTypeValues.ullong, None),
|
|
633
|
+
"TotalDataSize": (UTFTypeValues.ullong, None),
|
|
634
|
+
"Tocs": (UTFTypeValues.uint, None),
|
|
635
|
+
"TotalFiles": (UTFTypeValues.uint, None),
|
|
636
|
+
"Directories": (UTFTypeValues.uint, None),
|
|
637
|
+
"Updates": (UTFTypeValues.uint, None),
|
|
638
|
+
"Comment": (UTFTypeValues.string, '<NULL>'),
|
|
639
|
+
}
|
|
640
|
+
]
|
|
641
|
+
elif self.mode == 1:
|
|
642
|
+
ContentOffset = 0x800 + len(self.TOCdata)
|
|
643
|
+
CpkHeader = [
|
|
644
|
+
{
|
|
645
|
+
"UpdateDateTime": (UTFTypeValues.ullong, 0),
|
|
646
|
+
"FileSize": (UTFTypeValues.ullong, None),
|
|
647
|
+
"ContentOffset": (UTFTypeValues.ullong, ContentOffset),
|
|
648
|
+
"ContentSize": (UTFTypeValues.ullong, self.ContentSize),
|
|
649
|
+
"TocOffset": (UTFTypeValues.ullong, 0x800),
|
|
650
|
+
"TocSize": (UTFTypeValues.ullong, len(self.TOCdata)),
|
|
651
|
+
"TocCrc": (UTFTypeValues.uint, None),
|
|
652
|
+
"EtocOffset": (UTFTypeValues.ullong, None),
|
|
653
|
+
"EtocSize": (UTFTypeValues.ullong, None),
|
|
654
|
+
"ItocOffset": (UTFTypeValues.ullong, None),
|
|
655
|
+
"ItocSize": (UTFTypeValues.ullong, None),
|
|
656
|
+
"ItocCrc": (UTFTypeValues.uint, None),
|
|
657
|
+
"GtocOffset": (UTFTypeValues.ullong, None),
|
|
658
|
+
"GtocSize": (UTFTypeValues.ullong, None),
|
|
659
|
+
"GtocCrc": (UTFTypeValues.uint, None),
|
|
660
|
+
"EnabledPackedSize": (UTFTypeValues.ullong, self.EnabledPackedSize),
|
|
661
|
+
"EnabledDataSize": (UTFTypeValues.ullong, self.EnabledDataSize),
|
|
662
|
+
"TotalDataSize": (UTFTypeValues.ullong, None),
|
|
663
|
+
"Tocs": (UTFTypeValues.uint, None),
|
|
664
|
+
"Files": (UTFTypeValues.uint, self.fileslen),
|
|
665
|
+
"Groups": (UTFTypeValues.uint, 0),
|
|
666
|
+
"Attrs": (UTFTypeValues.uint, 0),
|
|
667
|
+
"TotalFiles": (UTFTypeValues.uint, None),
|
|
668
|
+
"Directories": (UTFTypeValues.uint, None),
|
|
669
|
+
"Updates": (UTFTypeValues.uint, None),
|
|
670
|
+
"Version": (UTFTypeValues.ushort, 7),
|
|
671
|
+
"Revision": (UTFTypeValues.ushort, 1),
|
|
672
|
+
"Align": (UTFTypeValues.ushort, 0x800),
|
|
673
|
+
"Sorted": (UTFTypeValues.ushort, 1),
|
|
674
|
+
"EID": (UTFTypeValues.ushort, None),
|
|
675
|
+
"CpkMode": (UTFTypeValues.uint, self.mode),
|
|
676
|
+
"Tvers": (UTFTypeValues.string, self.Tver),
|
|
677
|
+
"Comment": (UTFTypeValues.string, '<NULL>'),
|
|
678
|
+
"Codec": (UTFTypeValues.uint, 0),
|
|
679
|
+
"DpkItoc": (UTFTypeValues.uint, 0),
|
|
680
|
+
"EnableFileName": (UTFTypeValues.ushort, 1),
|
|
681
|
+
"EnableTocCrc": (UTFTypeValues.ushort, None),
|
|
682
|
+
"EnableFileCrc": (UTFTypeValues.ushort, None),
|
|
683
|
+
"CrcMode": (UTFTypeValues.uint, None),
|
|
684
|
+
"CrcTable": (UTFTypeValues.bytes, b''),
|
|
685
|
+
"HtocOffset": (UTFTypeValues.ullong, None),
|
|
686
|
+
"HtocSize": (UTFTypeValues.ullong, None),
|
|
687
|
+
"HgtocOffset": (UTFTypeValues.ullong, None),
|
|
688
|
+
"HgtocSize": (UTFTypeValues.ullong, None),
|
|
689
|
+
}
|
|
690
|
+
]
|
|
691
|
+
elif self.mode == 0:
|
|
692
|
+
CpkHeader = [
|
|
693
|
+
{
|
|
694
|
+
"UpdateDateTime": (UTFTypeValues.ullong, 0),
|
|
695
|
+
"ContentOffset": (UTFTypeValues.ullong, 0x800+len(self.ITOCdata)),
|
|
696
|
+
"ContentSize": (UTFTypeValues.ullong, self.ContentSize),
|
|
697
|
+
"ItocOffset": (UTFTypeValues.ullong, 0x800),
|
|
698
|
+
"ItocSize": (UTFTypeValues.ullong, len(self.ITOCdata)),
|
|
699
|
+
"EnabledPackedSize": (UTFTypeValues.ullong, self.EnabledPackedSize),
|
|
700
|
+
"EnabledDataSize": (UTFTypeValues.ullong, self.EnabledDataSize),
|
|
701
|
+
"Files": (UTFTypeValues.uint, self.fileslen),
|
|
702
|
+
"Groups": (UTFTypeValues.uint, 0),
|
|
703
|
+
"Attrs": (UTFTypeValues.uint, 0),
|
|
704
|
+
"Version": (UTFTypeValues.ushort, 7), # 7?
|
|
705
|
+
"Revision": (UTFTypeValues.ushort, 0),
|
|
706
|
+
"Align": (UTFTypeValues.ushort, 0x800),
|
|
707
|
+
"Sorted": (UTFTypeValues.ushort, 0),
|
|
708
|
+
"EID": (UTFTypeValues.ushort, None),
|
|
709
|
+
"CpkMode": (UTFTypeValues.uint, self.mode),
|
|
710
|
+
"Tvers": (UTFTypeValues.string, self.Tver),
|
|
711
|
+
"Codec": (UTFTypeValues.uint, 0),
|
|
712
|
+
"DpkItoc": (UTFTypeValues.uint, 0),
|
|
713
|
+
"FileSize": (UTFTypeValues.ullong, None),
|
|
714
|
+
"TocOffset": (UTFTypeValues.ullong, None),
|
|
715
|
+
"TocSize": (UTFTypeValues.ullong, None),
|
|
716
|
+
"TocCrc": (UTFTypeValues.uint, None),
|
|
717
|
+
"EtocOffset": (UTFTypeValues.ullong, None),
|
|
718
|
+
"EtocSize": (UTFTypeValues.ullong, None),
|
|
719
|
+
"ItocCrc": (UTFTypeValues.uint, None),
|
|
720
|
+
"GtocOffset": (UTFTypeValues.ullong, None),
|
|
721
|
+
"GtocSize": (UTFTypeValues.ullong, None),
|
|
722
|
+
"GtocCrc": (UTFTypeValues.uint, None),
|
|
723
|
+
"TotalDataSize": (UTFTypeValues.ullong, None),
|
|
724
|
+
"Tocs": (UTFTypeValues.uint, None),
|
|
725
|
+
"TotalFiles": (UTFTypeValues.uint, None),
|
|
726
|
+
"Directories": (UTFTypeValues.uint, None),
|
|
727
|
+
"Updates": (UTFTypeValues.uint, None),
|
|
728
|
+
"Comment": (UTFTypeValues.string, '<NULL>'),
|
|
729
|
+
}
|
|
730
|
+
]
|
|
731
|
+
return UTFBuilder(CpkHeader, encrypt=self.encrypt, encoding=self.encoding, table_name="CpkHeader").bytes()
|
|
732
|
+
|