Skip to content

Instantly share code, notes, and snippets.

@ChadSki
Last active August 29, 2015 14:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChadSki/f4b72be6c7d3d6236d85 to your computer and use it in GitHub Desktop.
Save ChadSki/f4b72be6c7d3d6236d85 to your computer and use it in GitHub Desktop.
# ensure plugins are loaded
if len(plugin_classes) == 0:
load_plugins()
def load_map(map_path=None):
"""Loads a map from Halo.exe's memory, or from disk if given a filepath. Loading a
map requires that plugins have already been loaded.
"""
# end result, constructed now (because we need the reference) but init'd at the end
halomap = HaloMap()
if map_path == None:
location='mem'
halomap.ba_builder = WinMemoryByteArrayBuilder('halo') # error if halo.exe is not running
else:
location = 'file'
halomap.ba_builder = FileByteArrayBuilder(map_path)
# class ByteArray:
# -- Denotes a region of bytes.
# -- Provides methods for reading and writing datatypes such as byte[], int, float, and string.
# -- Uses relative internal offsets, so two ByteArrays which wrap the same data at different
# locations will always appear identical.
#
# Use ByteArray(offset, size) as a constructor:
# offset: location within the source medium
# size: number of bytes enclosed
#
ByteArray = halomap.ba_builder.constructor
if location == 'mem':
# Force Halo to render video even when window is deselected
exe_offset = 0x400000
wmkillHandler_offset = exe_offset + 0x142538
ByteArray(wmkillHandler_offset, 4).WriteBytes(0, Array[Byte]((0xe9, 0x91, 0x00, 0x00)))
# fetch the necessary struct layouts
MapHeader = plugin_classes['map_header']
IndexHeader = plugin_classes['index_header']
TagHeader = plugin_classes['tag_header']
# load the map header
map_header_offset = {'file': 0, 'mem': 0x6A8154}[location]
map_header = MapHeader(
ByteArray(
map_header_offset,
MapHeader.struct_size),
halomap)
# load the index header
index_header_offset = {'file': map_header.index_offset, 'mem': 0x40440000}[location]
index_header = IndexHeader(
ByteArray(
index_header_offset,
IndexHeader.struct_size),
halomap)
if location == 'file':
# Usually the tag index directly follows the index header. However, some forms of
# map protection move the tag index to other locations.
index_offset = map_header.index_offset + index_header.primary_magic - 0x40440000
# On disk, we need to use a magic value to convert raw pointers into file offsets.
# This magic value is based on the tag index's location within the file, since the
# tag index always appears at the same place in memory.
halomap.magic = index_header.primary_magic - index_offset
elif location == 'mem':
# Almost always 0x40440028, unless the map has been protected in a specific way.
index_offset = index_header.primary_magic
# In memory, offsets are just raw pointers and require no adjustment.
halomap.magic = 0
# load all tag headers from the index
tag_headers = [TagHeader(
ByteArray(
TagHeader.struct_size * i + index_offset,
TagHeader.struct_size),
halomap) for i in range(index_header.tag_count)]
# tag metadata can recurse to unknown places, but at least we know where they start
meta_offsets = sorted(tag_header.meta_offset_raw for tag_header in tag_headers)
if location == 'file': # the BSP's meta has an offset of 0, skip it
bsp_offset = meta_offsets.pop(0)
elif location == 'mem': # the BSP's meta has a very large, distant offset, skip it
bsp_offset = meta_offsets.pop()
# to calculate sizes, we need the offset to the end (does not point to a tag)
meta_offsets.append(meta_offsets[0] + map_header.metadata_size)
# [0, 10, 40, 60] from location offsets...
# [10, 30, 20] we can calculate sizes...
# but instead of an ordered list, key based on the start offset
meta_sizes = {start: (end - start) for start, end in zip(meta_offsets[:-1], meta_offsets[1:])}
# just give BSP's meta a size of zero for now
meta_sizes.update({bsp_offset: 0})
name_maxlen = 256 # not sure what the actual limit is; just picking some value
# HaloTags can load their own name and metadata, so just give them the starting ByteArrayes
tags = [HaloTag(
tag_header,
ByteArray( # name
tag_header.name_offset_raw - halomap.magic,
name_maxlen),
ByteArray( # meta
tag_header.meta_offset_raw - halomap.magic,
meta_sizes[tag_header.meta_offset_raw]),
halomap) for tag_header in tag_headers]
halomap.init(map_header, index_header, tags)
return halomap
<?xml version="1.0" encoding="utf-8" ?>
<plugin name="index_header" struct_size="40">
<uint32 name="primary_magic" offset="0" />
<uint32 name="base_tag_ident" offset="4" />
<uint32 name="map_id" offset="8" />
<uint32 name="tag_count" offset="12" />
<uint32 name="verticie_count" offset="16" />
<uint32 name="verticie_offset" offset="20" />
<uint32 name="indicie_count" offset="24" />
<uint32 name="indicie_offset" offset="28" />
<uint32 name="model_data_length" offset="32" />
<ascii name="integrity" offset="36" length="4" reverse="true" />
</plugin>
<?xml version="1.0" encoding="utf-8" ?>
<plugin name="map_header" struct_size="132">
<ascii name="integrity" offset="0" length="4" reverse="true" />
<uint32 name="game_version" offset="4" />
<uint32 name="map_size" offset="8" />
<!-- 4 bytes padding -->
<uint32 name="index_offset" offset="16" />
<uint32 name="metadata_size" offset="20" />
<!-- 8 bytes padding -->
<asciiz name="map_name" offset="32" maxlength="32" />
<asciiz name="map_build" offset="64" maxlength="64" />
<uint32 name="map_type" offset="128" />
</plugin>
<?xml version="1.0" encoding="utf-8" ?>
<plugin name="tag_header" struct_size="32">
<ascii name="first_class" offset="0" length="4" reverse="true" />
<ascii name="second_class" offset="4" length="4" reverse="true" />
<ascii name="third_class" offset="8" length="4" reverse="true" />
<uint32 name="ident" offset="12" />
<uint32 name="name_offset_raw" offset="16" />
<uint32 name="meta_offset_raw" offset="20" />
<uint32 name="indexed" offset="24" />
<!-- 4 bytes padding -->
</plugin>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment