Last active
August 4, 2018 01:12
-
-
Save Wunkolo/78ffc88979bfb455de9c83af9b6abb29 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Wunkolo<[email protected]> - 8/3/2018 | |
# Dumps the archive files for Avalanche's APEX Engine 2 ".tab/.arc" file pairs | |
# Drag this python script into your "theHunter Call of the Wild\archives_win64\" | |
# directory and run the script. It will create some worker threads and dump each | |
# archive into its own folder. Each dumped file will be named using its file | |
# identifier(a numerical hash of the actual filename). Other games such as | |
# Just Cause 3 were mistakingly shipped with a .txt file that paired each hash | |
# with a full filename but they fixed up theHunter so all we got to work | |
# with are numerical file IDs | |
import mmap | |
import os | |
import struct | |
from multiprocessing.dummy import Pool | |
class TABHeader: | |
def __init__(self): | |
self.Magic = 0 | |
self.VersionMajor = 0 | |
self.VersionMinor = 0 | |
self.Alignment = 0 | |
class TABEntry: | |
def __init__(self): | |
self.FileNameCRC = 0 | |
self.FileOffset = 0 | |
self.FileSize = 0 | |
def DumpArc(ArchiveIndex): | |
TABFilename = "./game" + str(ArchiveIndex) + ".tab" | |
ARCFilename = "./game" + str(ArchiveIndex) + ".arc" | |
with open(TABFilename, "r") as TABFile, open(ARCFilename, "r") as ARCFile: | |
TABMap = mmap.mmap(TABFile.fileno(), 0, access=mmap.ACCESS_READ) | |
ARCMap = mmap.mmap(ARCFile.fileno(), 0, access=mmap.ACCESS_READ) | |
CurHeader = TABHeader | |
(CurHeader.Magic, CurHeader.VersionMajor, CurHeader.VersionMinor, CurHeader.Alignment) \ | |
= struct.unpack_from('<IHHI',TABMap,0) | |
if CurHeader.Magic != 0x424154: | |
# Invalid magic, probably not a .tab file | |
return | |
FileCount = (TABMap.size() - 0xC) // 0xC | |
# Extract files | |
CurDumpPath = "./game" + str(ArchiveIndex) + "/" | |
if not os.path.exists(CurDumpPath): | |
os.makedirs(CurDumpPath) | |
# Parse file list | |
for FileIndex in range(FileCount): | |
CurEntry = TABEntry | |
(CurEntry.FileNameCRC, CurEntry.FileOffset, CurEntry.FileSize) \ | |
= struct.unpack_from('<III',TABMap,0xC + FileIndex * 0xC) | |
print("{}: CRC:{:08x} Offset:{:08X} Size:{:08X}".format( | |
ArchiveIndex, | |
CurEntry.FileNameCRC, | |
CurEntry.FileOffset, | |
CurEntry.FileSize | |
) | |
) | |
with open(CurDumpPath + hex(CurEntry.FileNameCRC)[2:],"wb") as CurDumpFile: | |
CurDumpFile.write( | |
ARCMap[CurEntry.FileOffset:CurEntry.FileOffset + CurEntry.FileSize] | |
) | |
workers = Pool(4) | |
workers.map(DumpArc,range(0,23)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment