Skip to content

Instantly share code, notes, and snippets.

@wodim
Created November 5, 2018 00:54
Show Gist options
  • Save wodim/65b3e0f97a95825a74a01925f0fd54e6 to your computer and use it in GitHub Desktop.
Save wodim/65b3e0f97a95825a74a01925f0fd54e6 to your computer and use it in GitHub Desktop.
Script to extract AR files from Midtown Madness 2
# 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