Created
November 5, 2018 00:54
-
-
Save wodim/65b3e0f97a95825a74a01925f0fd54e6 to your computer and use it in GitHub Desktop.
Script to extract AR files from Midtown Madness 2
This file contains hidden or 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
# wodim, 05/11/2018 -- public domain | |
# extracts an AR file from Midtown Madness 2 into the target directory | |
from math import ceil | |
import os | |
import sys | |
from struct import unpack | |
import zlib | |
def round_up(n): return ceil(n / 0x800) * 0x800 | |
def get_int(b): return unpack('<I', b)[0] | |
def extract_s(b, offset): | |
# extracts string from b from offset up to next \0 | |
end = b.decode().find('\0', offset + 1) | |
return b[offset:end] | |
if len(sys.argv) < 2: | |
print('Usage: %s <ar file> [output folder]' % sys.argv[0]) | |
sys.exit(1) | |
ar_fp = open(sys.argv[1], 'rb') | |
# first let's read the header. first the fourcc, then a uint16 with the number | |
# of files, then some stuff I don't know | |
header = ar_fp.read(0x800) | |
if header[:4] != b'DAVE': | |
raise ValueError('Not a valid DAVE AR file') | |
n_entries = get_int(header[4:8]) | |
print('Found %d entries.' % n_entries) | |
# then the mft which will be something n_entries * 0x10 and then padded out to | |
# the nearest 0x800. and then the file list. and then we combine them. | |
mft_size = round_up(n_entries * 0x10) | |
mft = ar_fp.read(mft_size) | |
fn_size = get_int(mft[4:8]) - 0x800 - mft_size | |
fn = ar_fp.read(fn_size) | |
entries = [] | |
for i in range(n_entries): | |
offset = i * 0x10 | |
entry = mft[offset:offset + 0x10] | |
fn_offset, file_offset, real_size, ar_size = \ | |
unpack('<IIII', entry) | |
filename = extract_s(fn, fn_offset) | |
entries.append({ | |
'filename': filename.decode(), | |
'offset': file_offset, | |
'real_size': real_size, # size of the file | |
'ar_size': ar_size # compressed size | |
}) | |
if len(sys.argv) < 3: | |
# done | |
sys.exit(0) | |
files = ar_fp.read() | |
output_dir = sys.argv[2] | |
for entry in entries: | |
# first make sure the tree exists | |
path = '%s/%s' % (output_dir, entry['filename']) | |
print('Extracting: %s' % path) | |
dir = os.path.dirname(path) | |
os.makedirs(dir, exist_ok=True) | |
if os.path.isdir(path): | |
continue | |
with open(path, 'wb') as fp: | |
offset = entry['offset'] - (0x800 + mft_size + fn_size) | |
if entry['real_size'] != entry['ar_size']: | |
d_obj = zlib.decompressobj(-zlib.MAX_WBITS) | |
fp.write(d_obj.decompress(files[offset:offset + entry['ar_size']])) | |
else: | |
fp.write(files[offset:offset + entry['ar_size']]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment