-
Notifications
You must be signed in to change notification settings - Fork 28
/
unpacker.py
139 lines (121 loc) · 5.06 KB
/
unpacker.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
import gf
import os
import binascii
import re
from ctypes import cdll, c_char_p, create_string_buffer
class OodleDecompressor:
"""
Oodle decompression implementation.
Requires Windows and the external Oodle library.
"""
def __init__(self, library_path: str) -> None:
"""
Initialize instance and try to load the library.
"""
if not os.path.exists(os.getcwd() + library_path):
print(f'Looking in {os.getcwd() + library_path}')
raise Exception("Could not open Oodle DLL, make sure it is configured correctly.")
try:
self.handle = cdll.LoadLibrary(os.getcwd() + library_path)
except OSError as e:
raise Exception(
"Could not load Oodle DLL, requires Windows and 64bit python to run."
) from e
def decompress(self, payload: bytes, output_size) -> bytes:
"""
Decompress the payload using the given size.
"""
output = create_string_buffer(output_size)
try:
self.handle.OodleLZ_Decompress(
c_char_p(payload), len(payload), output, output_size,
0, 0, 0, None, None, None, None, None, None, 3)
except OSError:
return False
return output.raw
class EntryA:
def __init__(self):
self.pk = ''
self.path_length = 0
self.entry_length = 0
self.data_offset = 0
self.data = b''
self.out_data = b''
self.path = ''
self.length_to_next_pk = 0
self.data_length_pre = 0
self.data_length_post = 0
class EntryB:
def __init__(self):
self.pk = ''
self.path_length = 0
self.bitflags = 0
# self.data_length_pre = 0
# self.data_length_post = 0
self.path = ''
self.entrya_offset = 0
skipped = []
def unpack():
global skipped
paks = [x for x in os.listdir(direc) if '.pak' in x]
for pak in paks:
fbin = gf.get_hex_data(direc + pak)
entriesB = []
ret = [m.start() for m in re.finditer(b'\x50\x4B\x01\x02', fbin)]
ret += [m.start() for m in re.finditer(b'\x50\x4B\x03\x04', fbin)]
if not ret:
raise Exception('no ret')
for offset in ret:
entry = EntryB()
entry.pk = fbin[offset:offset+4]
entry.bitflags = gf.get_int16(fbin, offset+4)
entry.path_length = gf.get_int16(fbin, offset+0x1C)
if entry.path_length == 0:
continue # Sometimes ends with a 0 length path for some reason
entry.path = fbin[offset+0x2E:offset+(0x2E+entry.path_length)].decode('ansi')
if entry.bitflags != 0x8 and entry.bitflags != 0x14 and entry.bitflags != 0x9:
print(f'Skipping file {entry.path} as probably wrong, like in middle of chunk. Skipped {skipped}')
skipped.append(entry.path)
continue
# entry.data_length_pre = gf.get_int32(fhex, offset + 0x14 * 2)
# entry.data_length_post = gf.get_int32(fhex, offset + 0x18 * 2)
entry.entrya_offset = gf.get_int32(fbin, offset + 0x2A)
entriesB.append(entry)
for entryb in entriesB:
ot = entryb.entrya_offset
entry = EntryA()
entry.pk = fbin[ot:ot+4]
entry.path_length = gf.get_int32(fbin, ot+0x1A)
entry.path = fbin[ot+0x1E:ot+(0x1E+entry.path_length)].decode('ansi')
if entryb.path != entry.path:
raise Exception('ERROR PATHS DIFFER')
else:
print('Path match', entry.path)
entry.data_offset = ot + (0x1E + entry.path_length)
entry.data_length_pre = gf.get_int32(fbin, ot + 0x12)
entry.data_length_post = gf.get_int32(fbin, ot + 0x16)
entry.data = fbin[entry.data_offset:entry.data_offset+entry.data_length_pre]
# Write
path = f'{out_direc}/{"/".join(entry.path.split("/")[:-1])}'
os.makedirs(path, exist_ok=True)
with open(f'{out_direc}/{entry.path}', 'wb') as f:
decompressor = OodleDecompressor('/oo2core_8_win64.dll')
if entryb.bitflags == 0x8 or entryb.bitflags == 0x9:
entry.out_data = decompressor.decompress(entry.data, entry.data_length_post)
elif entryb.bitflags == 0x14: # 0xA is entryA
# No compression
entry.out_data = entry.data
elif entryb.bitflags & 1:
print("Unk bitflag 1")
else:
raise Exception(f'New bitflag {entryb.bitflags}')
if not entry.out_data:
raise Exception('DECOMP FAILED %%%%%%%%%%')
f.write(entry.out_data)
if __name__ == '__main__':
direc = 'G:/SteamLibrary/steamapps/common/New World Playtest/assets/'
out_direc = 'Z:/RE_OtherGames/NewWorld/unpack2/'
os.makedirs(out_direc, exist_ok=True)
unpack()
print(f'Total skipped: {[x[:36] for x in skipped]}')
input('Unpack done! Press any key to quit...')