Out of an intense desire to avoid doing anything useful this morning I decided to poke around at mapping / tiles stuff a bit. I couldn't figure out how to compile OP2Archive on linux so I wrote a python script for extracting .vol files, which I've pasted below.
I looked around at the tile set graphics and the well*.bmp files don't seem to follow the file format described in the documentation, but instead start with "BM" and the file length in bytes, followed by data. I didn't see any information on the file virmask.raw, and was unclear on how op2_art.prt worked (it looked similar to the file format described for the well*.bmp files, but not quite a match). There are also mystery files blue.bmp and color.bmp in the art.vol volume. I read on the forum that "fixed" .bmp files not in the OP2 format are floating around somewhere, is there a link pointing to that?
I am under the understanding that some of the tiles are animated, is there information on how that is represented? I assume existing utilities that work with tile image data are just taking one of the frames from the animation, or some such?
I recall reading that someone made progress on analyzing the various .map files to figure out automatically which tiles can be adjacent to which other tiles; is this a done thing or still a work in progress?
import sys
import struct
import os
import os.path
class VolReader:
def __init__(self, data):
self.data = data
self.index = 0
self.N = len(data)
self.read_all()
# static
def read_file(filename):
with open(filename, 'rb') as stream:
data = stream.read()
return VolReader(data)
## Read a .vol file ##
def read_all(self):
self.read_section_heading('VOL ')
assert self.read_section_heading('volh') == 0
self.read_filenames()
self.read_index_table()
## Helper functions for reading a .vol file ##
def next_k(self, k):
b = self.data[self.index : self.index + k]
self.index += k
assert self.index <= self.N
return b
def read_int4(self):
# little endian
int4, = struct.unpack('<i', self.next_k(4))
return int4
def read_byte(self):
return self.next_k(1)[0]
def read_section_heading(self, tag):
assert self.next_k(4) == bytes(tag, 'ascii')
l = self.read_int4()
assert (l & 0x80000000) > 0
return l & 0x7fffffff
def read_filenames(self):
size = self.read_section_heading('vols')
start = self.index + 4
end = self.index + size
assert start == 28
self.filenames = {}
filename_start = start
for i in range(start, end):
# check null-terminated
if self.data[i] == 0:
if i > filename_start:
filename = self.data[filename_start : i].decode('ascii')
self.filenames[filename_start - start] = filename
filename_start = i + 1
self.index = end
def read_index_table(self):
size = self.read_section_heading('voli')
self.numfiles = (size // 14)
assert size == (14 * self.numfiles)
self.files = []
for i in range(self.numfiles):
filename = self.filenames.get(self.read_int4(), '(none)')
fileoffset = self.read_int4()
filesize = self.read_int4()
compression = self.read_byte()
valid = self.read_byte()
assert compression in [0, 1, 2, 3]
assert valid in [0, 1]
self.files.append((filename, fileoffset, filesize, compression, valid))
def print_file_table(self):
compression_name = ['Uncomp.', 'RLE', 'LZ', 'LZH']
valid_name = ['Invalid', 'Valid']
print("{} files, {} bytes".format(len(self.files), self.N))
for filename, fileoffset, filesize, compression, valid in self.files:
print("{: <30s} {: <10d} {: <10d} {: <10s} {: <10s}".format(
filename, fileoffset, filesize,
compression_name[compression], valid_name[valid]))
def extract_files(self, dest_dir):
for filename, fileoffset, filesize, compression, valid in self.files:
if valid and filesize > 0:
assert compression == 0
self.extract_file(fileoffset, filesize, os.path.join(dest_dir, filename))
def extract_file(self, fileoffset, filesize, dest):
assert fileoffset + 8 + filesize <= self.N
filedata = self.data[fileoffset + 8 : fileoffset + 8 + filesize]
with open(dest, 'wb') as stream:
stream.write(filedata)
def run(filename):
assert os.path.isfile(filename)
vol = VolReader.read_file(filename)
vol.print_file_table()
dest_dir = os.path.join(
os.path.dirname(filename),
'extracted_' + os.path.basename(filename))
os.makedirs(dest_dir, exist_ok = True)
vol.extract_files(dest_dir)
if __name__ == "__main__":
filename = sys.argv[1]
run(filename)