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