Skip to content

Instantly share code, notes, and snippets.

@Wunkolo
Last active August 4, 2018 01:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Wunkolo/78ffc88979bfb455de9c83af9b6abb29 to your computer and use it in GitHub Desktop.
Save Wunkolo/78ffc88979bfb455de9c83af9b6abb29 to your computer and use it in GitHub Desktop.
# Wunkolo<wunkolo@gmail.com> - 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