Skip to content

Map file

Julian Offenhäuser edited this page Dec 4, 2019 · 15 revisions

For specifics and code, see https://github.com/sourcehold/sourcehold-maps.

The map file format is currently unknown (at least mostly). Parts of the map file are compressed using the PKWARE algorithm, a good resource to check out how it works is https://github.com/madler/zlib/blob/master/contrib/blast/. Here's some stuff I found out, assuming 160x160 map size and no scenario description is given:

File layout (TODO)

Header

The header is very simple. It starts with the following struct:

struct MapHeader {
    uint32_t magic;  // 0xFFFFFFFF
    uint32_t length; // size of the following section in bytes
};

and continues with the compressed preview image.

Map Sections

The file is composed of multiple sections, all of them are compressed. They start with a 12-byte header (3 x uint32_t):

struct SectionHeader {
    uint32_t uncompressedLen;
    uint32_t compressedLen;
    uint32_t crc32;
    // PKWARE-compressed chunk starts here
};

Compressed sections are very easy to identify, just search for the pattern 0x00 0x04, 0x00 0x05 or 0x00 0x06 to find the start of the PKWARE-data.

Scenario description

The scenario description is stored right after the preview image. Following this section, there is a structure as follows:

struct ScenarioDesc {
    uint32_t size;           // The size of this struct + the following compressed section 
    uint32_t useStringTable; // 1 if the index is used instead of the compressed text
    uint32_t index;          // An index into a global string table (from stronghold.mlb, see the "Stronghold asset formats" wiki page)
    uint32_t u1;             // always 0x3E8
    // The section containing the compressed text starts here. It is always
    // present, even if the index is being used instead.
};

Messages

One of the sections (right after the starting goods etc.) contains the list of messages/events, each of them taking up 188 bytes:

struct Event {
    uint32_t startingMonth;
    uint32_t startingYear;
    uint32_t u1;
    uint32_t u2;
    uint32_t index; // may change for selected character etc.
    char data[168]; // probably a list of winning conditions following
};

Random stuff (TODO)

  • At the very end of the file, the filename is encoded for campaign missions, every other defaults to "mission9.map"
  • The map data is stored in two halves, i.e. top-right edge and bottom-left edge
  • After the header and some data (presumably the map preview or description), there's the byte 5D, maybe some marker or ID. 8 bytes after that, there's one byte which seems to be the map type (invasion, etc). 4 bytes after that, there appears to be some kind of locking mechanism (4 bytes), as changing any of them to nonzero will cause the map to be hidden in the loading menu. I presume the structure looks something like this:
struct SomeInfo {
	uint32_t ID; // 0x5D
	uint32_t u1; // 0x02
	uint32_t u2; // 0x03
	uint32_t lock;
	enum MapType : uint8_t {
		SIEGE,
		INVASION,
		ECONOMIC,
		LANDSCAPE
	} type;
}

Since the file changes around in a (so far) seemingly random way, the following offsets are wrong but can be used to calculate the relative offsets.

  • 0x43A77: Popularity (1 byte, 0x00 - 0x64)
  • 0x439C7: Year (2 bytes)
  • 0x439CB: Month (1 byte, January = 0x00)
  • 0x439D7: Starting goods (4 bytes each, signed). Order (incomplete):
    • Wood
    • Hops
    • Stone
    • ?
    • ?
    • Iron
    • ?
    • Tar
    • Grain
    • Bread
    • Cheese
    • Meat
    • Apples
    • Beer
    • Gold
    • ?
    • Bows
    • Crossbows
    • Spears
    • Pikes
    • Maces
    • Swords
    • Leather armor
    • Armor
Clone this wiki locally