-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpack.py
157 lines (149 loc) · 6.07 KB
/
pack.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
import os
import struct
import logger as log
from rcpk import getPaddingAmt
import json
cpkConfig = None
# search through folder to find
# unpacked CPK's to pack
def findCPKS(start, out, recursive):
mirrorOut = out # init var
for filfol in os.listdir(start):
cur = os.path.join(start, filfol)
if os.path.isdir(cur):
# always skip past unpacked CPK data folders;
# there should never be CPK's to pack in there
if (cur == "ExtraData") or (cur == "Files") or (cur == "Headers"):
continue
if os.path.isfile(os.path.join(cur, "Config.json")):
configFile = loadConfigJson(cur)
# unlikely most files named "Config.json" that aren't ours
# would have this key in it
if "NoNullHeader" in configFile:
os.makedirs(mirrorOut, exist_ok=True) # only create dirs when we will write files
packCPK(cur, mirrorOut, loadedConfig=configFile)
else:
mirrorOut = os.path.join(out, filfol) # only add searched folders
# If script is run with recursive argument,
# Search through folders
if recursive:
findCPKS(cur, mirrorOut, recursive)
else:
return
def getPaddingBytes(cnt):
log.verboseLog('Generating file padding! Amt:', cnt)
return b'\x00' * cnt
def createPadding(fileSize, file, exDir):
global cpkConfig
cFilePadding = getPaddingAmt(fileSize)
cExData = b''
if cFilePadding != 0 and os.path.isdir(exDir) and cpkConfig["GenerateExtraData"] == False:
cExPath = os.path.join(exDir, file + '.bin')
if os.path.getsize(cExPath) == cFilePadding:
log.verboseLog("Loading extra file data from", cExPath + '!')
with open(cExPath, "rb") as xDat:
# in this path, the needed amount of padding matches
# the extra data file's size, so we use that instead of
# generating padding to maintain high file accuracy
cExData = xDat.read(cFilePadding)
else:
log.verboseLog("Required padding does not match extra data", cExPath + '\'s file size!')
cExData = getPaddingBytes(cFilePadding)
else:
cExData = getPaddingBytes(cFilePadding)
return cExData
def packFiles(wDir, whDir, wxDir):
cpkBytes = None
cHeader = b''
global cpkConfig
if cpkConfig["AutomaticImport"] == False:
log.verboseLog("AutomaticImport set to false! Iterating over manually-defined files from Config.json!")
fileIter = os.listdir(wDir) if cpkConfig["AutomaticImport"] else cpkConfig["Files"]
for fil in fileIter:
print("Packing", fil, "into CPK...")
if not os.path.isfile(os.path.join(whDir, fil + '.bin')):
print("Can't locate header file for", fil + "! Skipping file!")
continue
if len(fil) > 14:
log.verboseLog("Warning: The file name is longer than 14 characters!\nThis may or may not cause problems with the packed CPK!")
if cpkBytes == None:
# assumes file names can only be 14 characters long
cpkBytes = struct.pack('>14s', fil.encode())
else:
cpkBytes += struct.pack('>14s', fil.encode())
cFile = os.path.join(wDir, fil)
with open(os.path.join(whDir, fil + '.bin'), "rb") as cHed:
# File headers will always be 0xEE bytes long
# if we have one that isn't... too bad!
cpkBytes += cHed.read(0xEE)
# append actual file size to header (as LE)
cFileSize = os.path.getsize(cFile)
cpkBytes += struct.pack('<I', cFileSize)
# copy last file header to append EOF null header
# when NoNullHeader is set to false in the cpk config
if cpkConfig["NoNullHeader"] != True:
cHeader = b'\x00' + cpkBytes[-0xFF:-4] + b'\x00\x00\x00\x00'
else:
log.verboseLog("NoNullHeader is true! Not creating null header for end of file!")
# append file bytes to cpk bytes
with open(cFile, "rb") as cFil:
cpkBytes += cFil.read(cFileSize)
# Pad file as needed
cpkBytes += createPadding(cFileSize, fil, wxDir)
# end of loop, start over with new file
cpkBytes += cHeader
return cpkBytes
def loadConfigJson(basePath):
jsData = None
with open(os.path.join(basePath, "Config.json"), "r") as jsStream:
jsData = json.load(jsStream)
return jsData
def fixConfig(cfg):
if not "NoNullHeader" in cfg:
cfg["NoNullHeader"] = False
log.verboseLog("Config.json missing NoNullHeader key! Using default value! (False)")
if not "GenerateExtraData" in cfg:
cfg["GenerateExtraData"] = False
log.verboseLog("Config.json missing GenerateExtraData key! Using default value! (False)")
if not "AutomaticImport" in cfg:
cfg["AutomaticImport"] = True
log.verboseLog("Config.json missing AutomaticImport key! Using default value! (True)")
if not "Files" in cfg:
cfg["Files"] = []
cfg["AutomaticImport"] = True
log.verboseLog("Config.json missing Files key! Using empty array and Forcing AutomaticImport!")
return cfg
def packCPK(input, outDir, loadedConfig=None):
# Get output file path; output files in all-caps
# since loose CPK's in P3F are all-caps
outputFile = None
if isinstance(outDir, str):
outputFile = os.path.join(outDir, input[input.rindex(os.sep)+len(os.sep):].upper() + '.CPK')
else:
outputFile = os.path.join(input[:input.rindex(os.sep)], input[input.rindex(os.sep)+len(os.sep):].upper() + '.CPK')
if os.path.isfile(outputFile) and log.args.newonly:
log.alreadyExist(outputFile)
return
# get files and headers folder as vars for repeated access
filesDir = os.path.join(input, "Files")
headersDir = os.path.join(input, "Headers")
exDir = os.path.join(input, "ExtraData")
global cpkConfig
if loadedConfig == None:
cpkConfig = loadConfigJson(input)
else:
cpkConfig = loadedConfig
if os.path.isdir(filesDir):
if os.path.isdir(headersDir):
print("\nPacking", input, "into CPK file...")
cpkConfig = fixConfig(cpkConfig)
# pack files into cpk
cpkData = packFiles(filesDir, headersDir, exDir)
# write file data to disk
with open(outputFile, "wb") as cpkOut:
cpkOut.write(cpkData)
else:
# TODO: Add ability to generate generic header, if that's even a good idea
print("\nWarning! Could not locate Headers directory in", headersDir + "! Folder won't be packed into CPK!")
else:
print("\nError! Could not locate Files directory in", filesDir + "! Folder can't be packed into CPK!")