"I will not give a stone" or how the resources of the game "Cursed Earth" are arranged


Do you remember a lot of Russian games? Qualitative? Memorable? Yes, they were. If you are over 35 or you are a fan of Russian igroproma, then you probably know the "Cursed Lands".


The story began very prosaically: summer, heat. There is nothing special to do, and at a lazy viewing of the contents of the hard disk of the laptop, the gaze caught on the folder with the familiar dragon icon, lying idle for a couple of years.


What fan of the game will not be interested to know what is inside?


Introduction


Game info


Cursed Lands - or, as they were called outside the CIS, Evil Islands: Curse of Lost Soul , a stealth-RPG game released in 2000. The development of the game involved the studio Nival Interactive, at that time already proven to be a series of games Allods (Rage of Mages abroad). Mainly graduates of Moscow State University worked in it - they were quite capable of implementing one of the first games with a fully three-dimensional world.
In 2010, the title went to Mail.Ru ( information ), but the game is still being sold at the GOG store on behalf of Nival.


Relatively recently, the game turned 18 years old - October 26 is considered a birthday, the release date in the CIS. Despite his age, the official master server is still in service: from time to time someone decides to crawl through the forests of Gipat and knock a dozen or two skeletons with a squad of comrades.


Short article


Initially, my goal was only to write a one-way converter "for myself" in Python 3, and using only standard libraries. However, the process began smoothly writing documentation on formats, attempts to somehow standardize the output. For some of the formats, the structure was described using Kaitai Struct . As a result, everything resulted in the writing of this article and the wiki by formats.


Immediately, I note: for the most part, the game files have already been investigated, fan editors have been written to them. However, the information is extremely fragmented, but there is no more or less holistic description of the formats in the public domain, as well as an adequate set for creating modifications.


... and how to read it


For all formats, diagrams are provided (.ksy files) that can be converted into code in several of the most popular languages ​​in two clicks.


Unfortunately, already at the last stages of writing this article, I discovered that the highly respected Habr does not know how to highlight YAML (and JSON), and all the schemes use it. This should not be a big problem, but if it is inconvenient to read the scheme, I can advise you to copy it into a third-party editor, for example, NPP.


Resources and where they live


The game is a portable application containing the engine with libraries, launcher and, in fact, packaged resources.


This is interesting: the game settings are almost entirely stored in the registry. The bug of the camera in the GOG version is due to the fact that the installer does not prescribe the correct default values.

When you first look at the contents of the game folder, we immediately notice a couple of new file extensions: ASI and REG.
The first is a dynamic library, which we will not consider (this is done by reverse engineering specialists), but the second is the first native file format of the game.


REG


Files of this type are binary serialization of well-known text INI files.
The content is divided into sections that store the keys and their values. The REG file retains this hierarchy, but speeds up reading and parsing data - in 2000, this was apparently critical.


In general terms, you can describe the structure of this diagram:


Structure description
meta:
  id: reg
  title: Evil Islands, REG file (packed INI)
  application: Evil Islands
  file-extension: reg
  license: MIT
  endian: le
doc: Packed INI file
seq:
  - id: magic
    contents: [0xFB, 0x3E, 0xAB, 0x45]
    doc: Magic bytes
  - id: sections_count
    type: u2
    doc: Number of sections
  - id: sections_offsets
    type: section_offset
    doc: Sections offset table
    repeat: expr
    repeat-expr: sections_count
types:
  section_offset:
    doc: Section position in file
    seq:
      - id: order
        type: s2
        doc: Section order number
      - id: offset
        type: u4
        doc: Global offset of section in file
    instances:
      section:
        pos: offset
        type: section
    types:
      section:
        doc: Section representation
        seq:
          - id: keys_count
            type: u2
            doc: Number of keys in section
          - id: name_len
            type: u2
            doc: Section name lenght
          - id: name
            type: str
            encoding: cp1251
            size: name_len
            doc: Section name
          - id: keys
            type: key
            doc: Section's keys
            repeat: expr
            repeat-expr: keys_count
        types:
          key:
            doc: Named key
            seq:
              - id: order
                type: s2
                doc: Key order in section
              - id: offset
                type: u4
                doc: Key offset in section
            instances:
              key_record:
                pos: _parent._parent.offset + offset
                type: key_data
          key_data:
            seq:
              - id: packed_type
                type: u1
                doc: Key value info
              - id: name_len
                type: u2
                doc: Key name lenght
              - id: name
                type: str
                encoding: cp1251
                size: name_len
                doc: Key name
              - id: value
                type: value
                doc: Key value
            instances:
              is_array:
                value: packed_type > 127
                doc: Is this key contain array
              value_type:
                value: packed_type & 0x7F
                doc: Key value type
            types:
              value:
                doc: Key value
                seq:
                  - id: array_size
                    type: u2
                    if: _parent.is_array
                    doc: Value array size
                  - id: data
                    type:
                      switch-on: _parent.value_type
                      cases:
                        0: s4
                        1: f4
                        2: string
                    repeat: expr
                    repeat-expr: '_parent.is_array ? array_size : 1'
                    doc: Key value data
              string:
                doc: Sized string
                seq:
                  - id: len
                    type: u2
                    doc: String lenght
                  - id: value
                    type: str
                    encoding: cp1251
                    size: len
                    doc: String

This is interesting: in 2002, Nival shared some tools with the game community ( snapshot of the site ) - one of them was the INI serializer in REG. As you might guess, the deserializer appeared almost immediately, even if not official.

With the starting folder sorted out, move on to the subdirectories.
The first glance falls on the Cameras folder containing the CAM files.


Cam


A very simple format - just pack the camera positions in time. The camera is described by position and rotation. The other two fields are presumably time and step in the sequence of movements.



Structure description
meta:
  id: cam
  title: Evil Islands, CAM file (cameras)
  application: Evil Islands
  file-extension: cam
  license: MIT
  endian: le
doc: Camera representation
seq:
  - id: cams
    type: camera
    repeat: eos
types:
  vec3:
    doc: 3d vector
    seq:
      - id: x
        type: f4
        doc: x axis
      - id: y
        type: f4
        doc: y axis
      - id: z
        type: f4
        doc: z axis
  quat:
    doc: quaternion
    seq:
      - id: w
        type: f4
        doc: w component
      - id: x
        type: f4
        doc: x component
      - id: y
        type: f4
        doc: y component
      - id: z
        type: f4
        doc: z component
  camera:
    doc: Camera parameters
    seq:
      - id: unkn0
        type: u4
        doc: unknown
      - id: unkn1
        type: u4
        doc: unknown
      - id: position
        type: vec3
        doc: camera's position
      - id: rotation
        type: quat
        doc: camera's rotation

In the next folder - Res, res (unexpectedly!) Res files that are archives are stored.


Res


This format is sometimes hidden under other extensions, but the original is still RES.
The data structure is quite typical for an archive with random access to files: there are tables for storing information about files inside, a table of names, the very contents of files.
The directory structure is contained directly in the names.


It is worth noting two extremely interesting facts:


  1. The archive is optimized for downloading file information to a linked list with private hashing.
  2. You can store the contents of a file once, but refer to it under different names. As far as I know, this fact was used in the fan repack, where due to this the size of the game was greatly reduced. Archive optimization was not used in the original distribution.


Structure description
meta:
  id: res
  title: Evil Islands, RES file (resources archive)
  application: Evil Islands
  file-extension: res
  license: MIT
  endian: le
doc: Resources archive
seq:
  - id: magic
    contents: [0x3C, 0xE2, 0x9C, 0x01]
    doc: Magic bytes
  - id: files_count
    type: u4
    doc: Number of files in archive
  - id: filetable_offset
    type: u4
    doc: Filetable offset
  - id: nametable_size
    type: u4
    doc: Size of filenames
instances:
  nametable_offset:
    value: filetable_offset + 22 * files_count
    doc: Offset of filenames table
  filetable:
    pos: filetable_offset
    type: file_record
    repeat: expr
    repeat-expr: files_count
    doc: Files metadata table
types:
  file_record:
    doc: File metadata
    seq:
      - id: next_index
        type: s4
        doc: Next file index
      - id: file_size
        type: u4
        doc: Size of file in bytes
      - id: file_offset
        type: u4
        doc: File data offset
      - id: last_change
        type: u4
        doc: Unix timestamp of last change time
      - id: name_len
        type: u2
        doc: Lenght of filename
      - id: name_offset
        type: u4
        doc: Filename offset in name array
    instances:
      name:
        io: _root._io
        pos: name_offset + _parent.nametable_offset
        type: str
        encoding: cp1251
        size: name_len
        doc: File name
      data:
        io: _root._io
        pos: file_offset
        size: file_size
        doc: Content of file

This is interesting: in the Russian version of the game, the Speech.res archive contains two subdirectories s and t with completely identical content, which is why the archive size is twice as large - that is why the game does not fit on one CD.

Now you can unpack all archives (can be nested):


  • RES is just an archive
  • MPR - landscape of game levels,
  • MQ - information about tasks multiplayer,
  • ANM - a set of animations
  • MOD - 3d model,
  • BON - the location of the bones of the model.

If the files inside the archive do not have the extension, we will put the extension of the parent - concerns BON and ANM archives.


You can also split all the resulting files into four groups:


  1. Textures;
  2. Database;
  3. Models;
  4. Level files

Let's start with a simple - with textures.


Mmp


Actually, the texture. It has a small header indicating the image parameters, the number of MIP levels and compression used. After the header, MIP image levels are arranged in descending order of size.



Structure description
meta:
  id: mmp
  title: Evil Islands, MMP file (texture)
  application: Evil Islands
  file-extension: mmp
  license: MIT
  endian: le
doc: MIP-mapping texture
seq:
  - id: magic
    contents: [0x4D, 0x4D, 0x50, 0x00]
    doc: Magic bytes
  - id: width
    type: u4
    doc: Texture width
  - id: height
    type: u4
    doc: Texture height
  - id: mip_levels_count
    type: u4
    doc: Number of MIP-mapping stored levels
  - id: fourcc
    type: u4
    enum: pixel_formats
    doc: FourCC label of pixel format
  - id: bits_per_pixel
    type: u4
    doc: Number of bits per pixel
  - id: alpha_format
    type: channel_format
    doc: Description of alpha bits
  - id: red_format
    type: channel_format
    doc: Description of red bits
  - id: green_format
    type: channel_format
    doc: Description of green bits
  - id: blue_format
    type: channel_format
    doc: Description of blue bits
  - id: unused
    size: 4
    doc: Empty space
  - id: base_texture
    type:
      switch-on: fourcc
      cases:
        'pixel_formats::argb4': block_custom
        'pixel_formats::dxt1': block_dxt1
        'pixel_formats::dxt3': block_dxt3
        'pixel_formats::pnt3': block_pnt3
        'pixel_formats::r5g6b5': block_custom
        'pixel_formats::a1r5g5b5': block_custom
        'pixel_formats::argb8': block_custom
        _: block_custom
types:
  block_pnt3:
    seq:
      - id: raw
        size: _root.bits_per_pixel
  block_dxt1:
    seq:
      - id: raw
        size: _root.width * _root.height >> 1
  block_dxt3:
    seq:
      - id: raw
        size: _root.width * _root.height
  block_custom:
    seq:
      - id: lines
        type: line_custom
        repeat: expr
        repeat-expr: _root.height
    types:
      line_custom:
        seq:
          - id: pixels
            type: pixel_custom
            repeat: expr
            repeat-expr: _root.width
        types:
          pixel_custom:
            seq:
              - id: raw
                type:
                  switch-on: _root.bits_per_pixel
                  cases:
                    8: u1
                    16: u2
                    32: u4
            instances:
              alpha:
                value: '_root.alpha_format.count == 0 ? 255 : 255 * ((raw & _root.alpha_format.mask) >> _root.alpha_format.shift) / (_root.alpha_format.mask >> _root.alpha_format.shift)'
              red:
                value: '255 * ((raw & _root.red_format.mask) >> _root.red_format.shift) / (_root.red_format.mask >> _root.red_format.shift)'
              green:
                value: '255 * ((raw & _root.green_format.mask) >> _root.green_format.shift) / (_root.green_format.mask >> _root.green_format.shift)'
              blue:
                value: '255 * ((raw & _root.blue_format.mask) >> _root.blue_format.shift) / (_root.blue_format.mask >> _root.blue_format.shift)'
  channel_format:
    doc: Description of bits for color channel
    seq:
      - id: mask
        type: u4
        doc: Binary mask for channel bits
      - id: shift
        type: u4
        doc: Binary shift for channel bits
      - id: count
        type: u4
        doc: Count of channel bits
enums:
  pixel_formats:
    0x00004444: argb4
    0x31545844: dxt1
    0x33545844: dxt3
    0x33544E50: pnt3
    0x00005650: r5g6b5
    0x00005551: a1r5g5b5
    0x00008888: argb8

Possible pixel packing formats:


fourccDescription
44 44 00 00ARGB4
44 58 54 31DXT1
44 58 54 33DXT3
50 4E 54 33PNT3 - RLE compressed ARGB8
50 56 00 00R5G5B5
51 55 00 00A1R5G5B5
88 88 00 00ARGB8

About PNT3

Если формат изображения PNT3, то структура пикселей после распаковки — ARGB8; bits_per_pixel — размер сжатого изображения в байтах.


Распаковка PNT3


n = 0
destination = b""while src < size:
    v = int.from_bytes(source[src:src + 4], byteorder='little')
    src += 4if v > 1000000or v == 0:
        n += 1else:
        destination += source[src - (1 + n) * 4:src - 4]
        destination += b"\x00" * v
        n = 0

This is interesting: some of the textures are reflected vertically (or some are not reflected?).
And the game is very jealous of transparency - if the image is with an alpha channel, the color of transparent pixels must be exactly black. Or white - then how lucky.

Simple formats have ended, we are moving to tougher ones - in due time, the rows of mod-makers fiercely kept their own editing tools of the following formats, and for good reason. I warned you.


Databases (* DB and their ilk)


This format is extremely inconvenient to describe - in essence, this is a serialized node tree (or tables of records). The file consists of several tables with specified field types. General structure: the tables are nested in the common root node, the records are the nodes inside the table.


Each node defines its type and size:


unsignedchar type_index;
unsignedchar raw_size; // не используется вне этого блокаunsigned length; // не читается из файла
read(raw_size);
if (raw_size & 1)
{
    length = raw_size >> 1;
    for (int i = 0; i < 3; i++)
        length <<= 8;
        read(raw_size);
        length += raw_size;
}
else
    length = raw_size >> 1;

The type of the field of the table is taken by the index from the format string for the table, the resulting type is determined by the actual type.


Field types
обозначениеописание
Sstring
I4b int
U4b unsigned
F4b float
Xbits byte
ffloat array
iint array
Bbool
bbool array
Hunknown hex bytes
Ttime
0not stated
10FII
2SUFF
3FFFF
40SISS
50SISS00000U

Database description

Предметы (.idb)


таблицаструктура
МатериалыSSSIFFFIFIFfIX
ОружиеSSISIIIFFFFIFIXB00000IHFFFfHHFF
БроняSSISIIIFFFFIFIXB00000ffBiHH
Быстрые предметыSSISIIIFFFFIFIXB00000IIFFSbH
Квестовые предметыSSISIIIFFFFIFIXB00000Is
Продаваемые предметыSSISIIIFFFFIFIXB00000IHI

Переключатели (.ldb)


таблицаструктура
Прототип переключателяSfIFTSSS

Умения и навыки (.pdb)


таблицаструктура
УменияSSI0000000s
НавыкиSSI0000000SSIIIFFFIIIIBI

Следы (prints.db)


таблицаструктура
Следы крови0S11
Следы пламени0S110000001
Следы ног0S11

Заклинания (.sdb)


таблицаструктура
ПрототипыSSSFIFIFFFFIIIIUSSIIbIXFFFFF
МодификаторыSSFIFFISX
Шаблоны0SssSX
Шаблоны для брони0SssSX
Шаблоны для оружия0SssSX

Существа (.udb)


таблицаструктура
Повреждаемые частиSffUU
РасыSUFFUUFfFUUf222222000000000000SssFSsfUUfUUIUSBFUUUU
Прототипы монстровSSIUIFFFSFFFFFFFFFUFFFFFFff33sfssSFFFFFUFUSF
NPCSUFFFFbbssssFUB

Выкрики (acks.db)


таблицаструктура
Ответы0S0000000044444444444444444444445444444444444
Крики0S0000000044444
Прочее0S0000000044

Задания (.qdb)


таблицаструктура
ЗаданияSFIISIIs
БрифингиSFFsSsssssI

This is interesting: 01/16/2002 Nival laid out the original bases for multiplayer in csv format, as well as a utility converter into the game format ( snapshot of the site ). Naturally, the reverse converter was not slow to appear. There are also at least two documents describing the fields and their types from modmeykerov, but it is very difficult to read them.

Adb


Database animation for a specific type of units. In contrast to the above mentioned * DB, it is rather "human" - this is a single-level table with static field sizes.



Structure description
meta:
  id: adb
  title: Evil Islands, ADB file (animations database)
  application: Evil Islands
  file-extension: adb
  license: MIT
  endian: le
doc: Animations database
seq:
  - id: magic
    contents: [0x41, 0x44, 0x42, 0x00]
    doc: Magic bytes
  - id: animations_count
    type: u4
    doc: Number of animations in base
  - id: unit_name
    type: str
    encoding: cp1251
    size: 24
    doc: Name of unit
  - id: min_height
    type: f4
    doc: Minimal height of unit
  - id: mid_height
    type: f4
    doc: Middle height of unit
  - id: max_height
    type: f4
    doc: Maximal height of unit
  - id: animations
    type: animation
    doc: Array of animations
    repeat: expr
    repeat-expr: animations_count
types:
  animation:
    doc: Animation's parameters
    seq:
      - id: name
        type: str
        encoding: cp1251
        size: 16
        doc: Animation's name
      - id: number
        type: u4
        doc: Index in animations array
      - id: additionals
        type: additional
        doc: Packed structure with animation parameters
      - id: action_probability
        type: u4
        doc: Percents of action probability
      - id: animation_length
        type: u4
        doc: Lenght of animation in game ticks
      - id: movement_speed
        type: f4
        doc: Movement speed
      - id: start_show_hide1
        type: u4
      - id: start_show_hide2
        type: u4
      - id: start_step_sound1
        type: u4
      - id: start_step_sound2
        type: u4
      - id: start_step_sound3
        type: u4
      - id: start_step_sound4
        type: u4
      - id: start_hit_frame
        type: u4
      - id: start_special_sound
        type: u4
      - id: spec_sound_id1
        type: u4
      - id: spec_sound_id2
        type: u4
      - id: spec_sound_id3
        type: u4
      - id: spec_sound_id4
        type: u4
    types:
      additional:
        seq:
          - id: packed
            type: u8
        instances:
          weapons:
            value: 'packed & 127'
          allowed_states:
            value: '(packed >> 15) & 7'
          action_type:
            value: '(packed >> 18) & 15'
          action_modifyer:
            value: '(packed >> 22) & 255'
          animation_stage:
            value: '(packed >> 30) & 3'
          action_forms:
            value: '(packed >> 36) & 63'

This is interesting: for several units, a partially trimmed base format is used, which is practically unexplored.

Having dealt with databases, we announce an advertising pause. But we will not advertise anything - not our method. It is better to denote what comes next - as creature files are named.


Model Name Format


The name is collected from groups of two characters - abbreviations of the logical "level".
For example, the female character will be unhufe- Unit > Human > Female, and initwesp- Inventory > Item > Weapon > Spear, that is, a spear in the inventory (not your back, and that is good).


Full name element tree:
un: # unit
  an: # animal
    wi: # wild
      ti # tiger
      ba # bat
      bo # boar
      hy # hyen
      de # deer
      gi # rat
      ra # rat
      cr # crawler
      wo # wolf
    ho: # home
      co # cow
      pi # pig
      do # dog
      ho # horse
      ha # hare
  or: # orc
    fe # female
    ma # male
  mo: # monster
    co # column (menu)
    un # unicorn
    cu # Curse
    be # beholder
    tr # troll
    el # elemental
    su # succub (harpie)
    ba # banshee
    dr # driad
    sh # shadow
    li # lizard
    sk # skeleton
    sp # spider
    go # golem, goblin
    ri # Rick
    og # ogre
    zo # zombie
    bi # Rik's dragon
    cy # cyclope
    dg # dragon
    wi # willwisp
    mi # octopus
    to # toad
  hu: # human
    fe # female
    ma # male
in: # inventory
  it: # item
    qu # quest
    qi # interactive
    ar: # armor
      pl # plate
      gl # gloves
      lg # leggins
      bt # boots
      sh # shirt
      hl # helm
      pt # pants
    li: # loot
      mt # material
      tr # trade
    we: # weapon
      hm # hammer
      dg # dagger
      sp # spear
      cb # crossbow
      sw # sword
      ax # axe
      bw # bow
  gm # game menu
  fa: # faces
    un: # unit
      an: # animal
        wi: # wild
          ti: # tiger
            face # face
          ba: # bat
            face # face
          bo: # boar
            face # face
          de: # deer
            face # face
          ra: # rat
            face # face
          cr: # crawler
            face # face
          wo: # wolf
            face # face
        ho: # home
          co: # cow
            face # face
          pi: # pig
            face # face
          do: # dog
            face # face
          ho: # horse
            face # face
          ha: # hare
            face # face
      hu: # human
        fe: # female
          fa # 
          me # 
          th # 
        ma: # male
          fa # 
          me # 
          th # 
      mo: # monster
        to: # toad
          face # face
        tr: # troll
          face # face
        or: # orc
          face # face
        sp: # spider
          face # face
        li: # lizard
          face # face
na: # nature
  fl: # flora
    bu # bush
    te # termitary
    tr # tree
    li # waterplant
  wa # waterfall
  sk # sky
  st # stone
ef: # effects
  cu # 
  ar # 
co # components
st: # static
  si # switch
  bu: # building
    to # tower
    ho # house
  tr # trap
  br # bridge
  ga # gate
  we # well (waterhole)
  wa: # wall
    me # medium
    li # light
  to # torch
  st # static

This is interesting: according to this classification, mushrooms are trees, golems with goblins are brothers, and Tka-Rik is a monster. Also here you can see the "working" names of monsters, suspiciously similar to those of D & D - beholder (evil eye), succub (harpy), ogre (cannibal), driad (foresters).

Morally having a rest, we will plunge headlong into the model. They are presented in several formats that are linked together.


Lnk


Logically - the basis of the model. Describes the hierarchy of parts of a model, in terms of modern 3d modeling, a hierarchy of bones.



Structure description
meta:
  id: lnk
  title: Evil Islands, LNK file (bones hierarchy)
  application: Evil Islands
  file-extension: lnk
  license: MIT
  endian: le
doc: Bones hierarchy
seq:
  - id: bones_count
    type: u4
    doc: Number of bones
  - id: bones_array
    type: bone
    repeat: expr
    repeat-expr: bones_count
    doc: Array of bones
types:
  bone:
    doc: Bone node
    seq:
      - id: bone_name_len
        type: u4
        doc: Length of bone's name
      - id: bone_name
        type: str
        encoding: cp1251
        size: bone_name_len
        doc: Bone's name
      - id: parent_name_len
        type: u4
        doc: Length of bone's parent name
      - id: parent_name
        type: str
        encoding: cp1251
        size: parent_name_len
        doc: Bone's parent name

The parent name of the main bone is an empty string (length 0).


There are bones, but it is not enough to name them and put them in a pile - you need to assemble them into a skeleton.


Bon


The previously mentioned, this format (if it is not an archive) specifies the position of the parts (bones) of the model relative to the parent part. Only offset is stored, without rotation - one of the differences from the modern formats.



Structure description
meta:
  id: bon
  title: Evil Islands, BON file (bone position)
  application: Evil Islands
  file-extension: bon
  license: MIT
  endian: le
doc: Bone position
seq:
  - id: position
    type: vec3
    doc: Bone translation
    repeat: eos
types:
  vec3:
    doc: 3d vector
    seq:
      - id: x
        type: f4
        doc: x axis
      - id: y
        type: f4
        doc: y axis
      - id: z
        type: f4
        doc: z axis

As you can see, there are too many numbers for one offset - the fact is that here we first stumbled upon one of the key pieces of the game engine - trilinear interpolation of models.


How it works: the model has three interpolation parameters - conditionally, strength, agility, growth. There are also 8 extreme states of the model. Using parameters, we can get the final model by trilinear interpolation.


Directly, the algorithm
deftrilinear(val, coefs=[0, 0, 0]):# Linear interpolation by str
    t1 = val[0] + (val[1] - val[0]) * coefs[1]
    t2 = val[2] + (val[3] - val[2]) * coefs[1]
    # Bilinear interpolation by dex
    v1 = t1 + (t2 - t1) * coefs[0]
    # Linear interpolation by str
    t1 = val[4] + (val[5] - val[4]) * coefs[1]
    t2 = val[6] + (val[7] - val[6]) * coefs[1]
    # Bilinear interpolation by dex
    v2 = t1 + (t2 - t1) * coefs[0]
    # Trilinear interpolation by heightreturn v1 + (v2 - v1) * coefs[2]

This is interesting: the trilinear interpolation of the model is used to animate some objects, for example, opening a stone door and chests.

Now is the time to look at the parts of the model.


FIG


Perhaps this format is impossible to understand the meeting. You can find its description and plug-in for the blender on the web, but even with them, awareness does not come immediately. Take a look:



Structure description
meta:
  id: fig
  title: Evil Islands, FIG file (figure)
  application: Evil Islands
  file-extension: fig
  license: MIT
  endian: le
doc: 3d mesh
seq:
  - id: magic
    contents: [0x46, 0x49, 0x47, 0x38]
    doc: Magic bytes
  - id: vertex_count
    type: u4
    doc: Number of vertices blocks
  - id: normal_count
    type: u4
    doc: Number of normals blocks
  - id: texcoord_count
    type: u4
    doc: Number of UV pairs
  - id: index_count
    type: u4
    doc: Number of indeces
  - id: vertex_components_count
    type: u4
    doc: Number of vertex components
  - id: morph_components_count
    type: u4
    doc: Number of morphing components
  - id: unknown
    contents: [0, 0, 0, 0]
    doc: Unknown (aligment)
  - id: group
    type: u4
    doc: Render group
  - id: texture_index
    type: u4
    doc: Texture offset
  - id: center
    type: vec3
    doc: Center of mesh
    repeat: expr
    repeat-expr: 8
  - id: aabb_min
    type: vec3
    doc: AABB point of mesh
    repeat: expr
    repeat-expr: 8
  - id: aabb_max
    type: vec3
    doc: AABB point of mesh
    repeat: expr
    repeat-expr: 8
  - id: radius
    type: f4
    doc: Radius of boundings
    repeat: expr
    repeat-expr: 8
  - id: vertex_array
    type: vertex_block
    doc: Blocks of raw vertex data
    repeat: expr
    repeat-expr: 8
  - id: normal_array
    type: vec4x4
    doc: Packed normal data
    repeat: expr
    repeat-expr: normal_count
  - id: texcoord_array
    type: vec2
    doc: Texture coordinates data
    repeat: expr
    repeat-expr: texcoord_count
  - id: index_array
    type: u2
    doc: Triangles indeces
    repeat: expr
    repeat-expr: index_count
  - id: vertex_components_array
    type: vertex_component
    doc: Vertex components array
    repeat: expr
    repeat-expr: vertex_components_count
  - id: morph_components_array
    type: morph_component
    doc: Morphing components array
    repeat: expr
    repeat-expr: morph_components_count
types:
  morph_component:
    doc: Morphing components indeces
    seq:
      - id: morph_index
        type: u2
        doc: Index of morphing data
      - id: vertex_index
        type: u2
        doc: Index of vertex
  vertex_component:
    doc: Vertex components indeces
    seq:
      - id: position_index
        type: u2
        doc: Index of position data
      - id: normal_index
        type: u2
        doc: Index of normal data
      - id: texture_index
        type: u2
        doc: Index of texcoord data
  vec2:
    doc: 2d vector
    seq:
      - id: u
        type: f4
        doc: u axis
      - id: v
        type: f4
        doc: v axis
  vec3:
    doc: 3d vector
    seq:
      - id: x
        type: f4
        doc: x axis
      - id: y
        type: f4
        doc: y axis
      - id: z
        type: f4
        doc: z axis
  vec3x4:
    doc: 3d vector with 4 values per axis
    seq:
      - id: x
        type: f4
        doc: x axis
        repeat: expr
        repeat-expr: 4
      - id: y
        type: f4
        doc: y axis
        repeat: expr
        repeat-expr: 4
      - id: z
        type: f4
        doc: z axis
        repeat: expr
        repeat-expr: 4
  vertex_block:
    doc: Vertex raw block
    seq:
      - id: block
        type: vec3x4
        doc: Vertex data
        repeat: expr
        repeat-expr: _root.vertex_count
  vec4x4:
    doc: 4d vector with 4 values per axis
    seq:
      - id: x
        type: f4
        doc: x axis
        repeat: expr
        repeat-expr: 4
      - id: y
        type: f4
        doc: y axis
        repeat: expr
        repeat-expr: 4
      - id: z
        type: f4
        doc: z axis
        repeat: expr
        repeat-expr: 4
      - id: w
        type: f4
        doc: w axis
        repeat: expr
        repeat-expr: 4

What is the difficulty? So, the data of normals and vertices are stored in blocks of 4, and the vertices are also arranged in 8 blocks for interpolation.


This is interesting: presumably, such a grouping was done to speed up processing using SSE instructions that have appeared in Intel processors since 1999.

Well, we read the model and compiled it, but something is missing. Exactly - animation!


ANM


Animation is stored as key states componentwise. An interesting fact is that support is provided not only for skeletal animation, but also for all-round morphing.



Structure description
meta:
  id: anm
  title: Evil Islands, ANM file (bone animation)
  application: Evil Islands
  file-extension: anm
  license: MIT
  endian: le
doc: Bone animation
seq:
  - id: rotation_frames_count
    type: u4
    doc: Number of rotation frames
  - id: rotation_frames
    type: quat
    repeat: expr
    repeat-expr: rotation_frames_count
    doc: Bone rotations
  - id: translation_frames_count
    type: u4
    doc: Number of translation frames
  - id: translation_frames
    type: vec3
    repeat: expr
    repeat-expr: translation_frames_count
    doc: Bone translation
  - id: morphing_frames_count
    type: u4
    doc: Number of morphing frames
  - id: morphing_vertex_count
    type: u4
    doc: Number of vertices with morphing
  - id: morphing_frames
    type: morphing_frame
    repeat: expr
    repeat-expr: morphing_frames_count
    doc: Array of morphing frames
types:
  vec3:
    doc: 3d vector
    seq:
      - id: x
        type: f4
        doc: x axis
      - id: y
        type: f4
        doc: y axis
      - id: z
        type: f4
        doc: z axis
  quat:
    doc: quaternion
    seq:
      - id: w
        type: f4
        doc: w component
      - id: x
        type: f4
        doc: x component
      - id: y
        type: f4
        doc: y component
      - id: z
        type: f4
        doc: z component
  morphing_frame:
    doc: Array of verteces morphing
    seq:
      - id: vertex_shift
        type: vec3
        repeat: expr
        repeat-expr: _parent.morphing_vertex_count
        doc: Morphing shift per vertex

All - now we have a full-fledged model, you can admire a freshly rendered hermit reptile:



Moment of nostalgia

Узнать, что нужно Ящеру


Разговор с ящером в его жилище


Ящер-Отшельник: Ты пришел, человек. Это хорошо.


Зак: Это все, что ты хотел мне сказать?


Ящер-Отшельник: Ты опять торопишься. Я помню твои вопросы и буду на них отвечать. Я пришел к людям в железе, чтобы заключить сделку. Но я увидел, как они поступили с тобой. Они не держат слова, я перестал им верить. Ты сдержал слово. Сделка будет предложена тебе.


Ящер-Отшельник: Люди любят золото. Ящерам золото неинтересно. Ты выполнишь мое задание, и я дам тебе золото, которое есть у меня. Этого золота много.


Зак (задумчиво и без особой заинтересованности): Хм… Золото… Оно, конечно, не помешает…


Зак: Было бы лучше, если бы ты помог мне узнать, где живет старый маг, которого я так долго ищу. Ведь ящеры — древний народ, и вы можете это знать!


Ящер-Отшельник: Ты прав. Ящеры — древний народ. Я могу собрать все, что нам известно про старика. Ты согласен выполнить мое задание?


Зак: О чем разговор! Считай, что все уже сделано.


Ящер-Отшельник (серьезно): Уже сделано? Ты хочешь меня обмануть?


Зак: Вообще-то я хотел пошутить, а то ты уж больно серьезен.


Ящер-Отшельник: Понимаю. Это шутка. Наверное, я тоже смогу пошутить. Потом. А сейчас мне надо, чтобы ты вернул воду в Канал. Воду украли у нас орки.


Ящер-Отшельник: Иди на юг вдоль воды. Увидишь плотину и Канал. Плотину надо поднять. Рычагом. Я его дам. Канал нужно завалить. Камнем. Камень я не дам. Он уже лежит на краю Канала. Вверх по течению от плотины. Камень тяжелый. Когда орки копали, они его поднимали долго. Если ты его толкнешь, обратно он будет падать быстро.


Ящер-Отшельник: После этого возвращайся. Я расскажу тебе все, что узнаю про старого Мага.


Зак: По рукам! Но, кстати, если ты добавишь к рассказу немножко монет, я вовсе не обижусь.


Ящер-Отшельник: За монетами отправляйся к моим сородичам, которые живут на отмелях дальше, на юге. Пройди на самый дальний песчаный остров, третий по счету. Сокровища будут твоими!


Ящер-Отшельник (сам себе): Странно. Этот человек любит юмор. Я пошутил. Человек не засмеялся. Очень странно.


Now - the most interesting: how the map is stored.


MP


This is the map header file. By an unfortunate coincidence, the extension coincides with that of the multiplayer save files, which we will not consider.


First you need to give a general description of the landscape:


  • the number of "chunks" - pieces of the card 32x32 meters;
  • maximum height (since the height of the vertices is stored in an integer scale);
  • number of tile atlases.

Additionally, there is a description of the map materials, as well as animated tiles - for example, water or lava.



Structure description
meta:
  id: mp
  title: Evil Islands, MP file (map header)
  application: Evil Islands
  file-extension: mp
  license: MIT
  endian: le
doc: Map header
seq:
  - id: magic
    contents: [0x72, 0xF6, 0x4A, 0xCE]
    doc: Magic bytes
  - id: max_altitude
    type: f4
    doc: Maximal height of terrain
  - id: x_chunks_count
    type: u4
    doc: Number of sectors by x
  - id: y_chunks_count
    type: u4
    doc: Number of sectors by y
  - id: textures_count
    type: u4
    doc: Number of texture files
  - id: texture_size
    type: u4
    doc: Size of texture in pixels by side
  - id: tiles_count
    type: u4
    doc: Number of tiles
  - id: tile_size
    type: u4
    doc: Size of tile in pixels by side
  - id: materials_count
    type: u2
    doc: Number of materials
  - id: animated_tiles_count
    type: u4
    doc: Number of animated tiles
  - id: materials
    type: material
    doc: Map materials
    repeat: expr
    repeat-expr: materials_count
  - id: id_array
    type: u4
    doc: Tile type
    repeat: expr
    repeat-expr: tiles_count
    enum: tile_type
  - id: animated_tiles
    type: animated_tile
    doc: Animated tiles
    repeat: expr
    repeat-expr: animated_tiles_count
types:
  material:
    doc: Material parameters
    seq:
      - id: type
        type: u4
        doc: Material type by
        enum: terrain_type
      - id: color
        type: rgba
        doc: RGBA diffuse color
      - id: self_illumination
        type: f4
        doc: Self illumination
      - id: wave_multiplier
        type: f4
        doc: Wave speed multiplier
      - id: warp_speed
        type: f4
        doc: Warp speed multiplier
      - id: unknown
        size: 12
    types:
      rgba:
        doc: RGBA color
        seq:
          - id: r
            type: f4
            doc: Red channel
          - id: g
            type: f4
            doc: Green channel
          - id: b
            type: f4
            doc: Blue channel
          - id: a
            type: f4
            doc: Alpha channel
    enums:
      terrain_type:
        0: base
        1: water_notexture
        2: grass
        3: water
  animated_tile:
    doc: Animated tile parameters
    seq:
      - id: start_index
        type: u2
        doc: First tile of animation
      - id: length
        type: u2
        doc: Animation frames count
enums:
  tile_type:
    0: grass
    1: ground
    2: stone
    3: sand
    4: rock
    5: field
    6: water
    7: road
    8: empty
    9: snow
    10: ice
    11: drygrass
    12: snowballs
    13: lava
    14: swamp
    15: highrock

Landscape Type List
terrain typeТип
0Базовый ландшафт
1Вода без текстуры
2Текстурированная трава
3Текстурированная вода

List of material types
material typeТип
0grass
1ground
2stone
3sand
4rock
5field
6water
7road
8(empty)
9snow
10ice
11drygrass
12snowballs
13lava
14swamp
15highrock

The type of material should affect the permeability, according to the information in the file Res/aiinfo.res/tileDesc.reg.


This is interesting: in all public descriptions of the format, an error is made - the fields of the earth and water are confused by type.
And again: you can confuse these files with the multiplayer saves.

Now we are ready to process the parts of the map. For the cause!


SEC


The file stores a single sector of the map - a piece of 32x32 meters. The position on the map is stored in the file name, which has the form ZonenameXXXYYY.



Structure description
meta:
  id: sec
  title: Evil Islands, SEC file (map sector)
  application: Evil Islands
  file-extension: sec
  license: MIT
  endian: le
doc: Map sector
seq:
  - id: magic
    contents: [0x74, 0xF7, 0x4B, 0xCF]
    doc: Magic bytes
  - id: liquids
    type: u1
    doc: Liquids layer indicator
  - id: vertexes
    type: vertex
    doc: Vertex array 33x33
    repeat: expr
    repeat-expr: 1089
  - id: liquid_vertexes
    type: vertex
    doc: Vertex array 33x33
    if: liquids != 0
    repeat: expr
    repeat-expr: 'liquids != 0 ? 1089 : 0'
  - id: tiles
    type: tile
    doc: Tile array 16x16
    repeat: expr
    repeat-expr: 256
  - id: liquid_tiles
    type: tile
    doc: Tile array 16x16
    if: liquids != 0
    repeat: expr
    repeat-expr: 'liquids != 0 ? 256 : 0'
  - id: liquid_material
    type: u2
    doc: Index of material
    if: liquids != 0
    repeat: expr
    repeat-expr: 'liquids != 0 ? 256 : 0'
types:
  vertex:
    doc: Vertex data
    seq:
      - id: x_shift
        type: s1
        doc: Shift by x axis
      - id: y_shift
        type: s1
        doc: Shift by y axis
      - id: altitude
        type: u2
        doc: Height (z position)
      - id: packed_normal
        type: normal
        doc: Packed normal
  normal:
    doc: Normal (3d vector)
    seq:
      - id: packed
        type: u4
        doc: Normal packed in 4b
    instances:
      x:
        doc: Unpacked x component
        value: packed >> 11 & 0x7FF
      y:
        doc: Unpacked y component
        value: packed & 0x7FF
      z:
        doc: Unpacked z component
        value: packed >> 22
  tile:
    doc: Tile parameters
    seq:
      - id: packed
        type: u2
        doc: Tile information packed in 2b
    instances:
      index:
        doc: Tile index in texture
        value: packed & 63
      texture:
        doc: Texture index
        value: packed >> 6 & 255
      rotation:
        doc: Tile rotation (*90 degrees)
        value: packed >> 14 & 3

Here, the developers swung to glory - almost all data is stored in a packed form.


A set of unpacking algorithms

Распаковка нормали


10 бит на ось z, по 11 на x и y


unsigned packed_normal;
float x = ((float)((packed_normal >> 11) & 0x7FF) - 1000.0f) / 1000.0f;
float y = ((float)(packed_normal & 0x7FF) - 1000.0f) / 1000.0f;
float z = (float)(packed_normal >> 22) / 1000.0f;

Информация о текстуре


6 бит на индекс в атласе, 8 на номер текстуры, 2 на вращение


unsignedshort texture;
unsignedchar tile_index = f & 63;
unsignedchar texture_index = (f >> 6) & 255;
unsignedchar rotation = (f >> 14) & 3;

Generation 3d models

Получение ландшафта


Вершины идут по 33 элемента в 33 строки, то есть, образуя 32х32 клетки. Длина клетки по стороне — 1 условная единица.


Позиция вершины:
x = индекс по x + x_offset / 254
y = индекс по y + y_offset / 254
z = altitude / 65535 * max_altitude (из .mp файла)


Вершины объединяются в полигоны "гребёнкой", при этом четыре вершины образуют два полигона:


 0   1 2
   *-*-*
   |\|\| ~
33 *-*-*
   |\|\| ~
66 *-*-*
    ~ ~  ~

Текстура накладывается на сразу четыре таких клетки, то есть, 16х16 тайлов. Длина тайла — 2 условные единицы. Тайл может быть повёрнут на угол, кратный 90 градусам.


A sector may contain information about fluids at a level. In this case, in addition to vertices and texture information, at the end of the file the water material ID is indicated, which is an index in the table of materials from the MP file.


This is interesting: as for MP, an error was made in the descriptions of the format, but here it is much more weighty: the indication of the material ID was considered an indication of the visibility of the tile, which would have caused the mesh to be constructed incorrectly.
Also, the ID splits the fluid level into several groups - raising the water after applying the lever just uses it.

Great - now we have a ready landscape:



It remains quite a bit to add objects to it, and at the same time consider the last format in this article.


MOB


If you liked (or vice versa) the format of databases, here we use a slightly different, but purely theoretically similar principle: the serialized tree of records. Moreover, the difference from the database format is huge and in a good way - there is no clear structure “by pattern”, but there is a rule for compiling fields.
A field can either be a record and contain other fields, or it can be a key (store the value in a specific format).


Very brief presentation:


typedef structure
{
    unsigned type_id;
    unsigned size;
    byte data[size - 8];
} node;

Graphic representation (incomplete, not nervous to watch!)


Описание структуры (опять же, неполное)


meta:
  id: mob
  title: Evil Islands, MOB file (map entities)
  application: Evil Islands
  file-extension: mob
  license: MIT
  endian: le
doc: Map entities tree
seq:
  - id: root_node
    type: node
    doc: Root node
types:
  node:
    doc: Entity node
    seq:
      - id: type_id
        type: u4
        doc: Node children type ID
      - id: size
        type: u4
        doc: Node full size
      - id: data
        type: node_data
        size: size - 8
        doc: Node stored data
  node_data:
    doc: Node data
    seq:
      - id: value
        type:
          switch-on: _parent.type_id
          cases:
            0xA000: node
            0x00001E00: node
            0x00001E01: node
            0x00001E02: node
            0x00001E03: node
            0x00001E0B: node
            0x00001E0E: node
            0x0000A000: node
            0x0000AA01: node
            0x0000ABD0: node
            0x0000B000: node
            0x0000B001: node
            0x0000CC01: node
            0x0000DD01: node
            0x0000E000: node
            0x0000E001: node
            0x0000F000: node
            0x0000FF00: node
            0x0000FF01: node
            0x0000FF02: node
            0xBBAB0000: node
            0xBBAC0000: node
            0xBBBB0000: node
            0xBBBC0000: node
            0xBBBD0000: node
            0xBBBE0000: node
            0xBBBF0000: node
            0xDDDDDDD1: node
            _: u1
        doc: Node elements
        repeat: eos

Full list of data types
тип данныхразмер (обычно)описание
AiGraphграф проходимости
AreaArray
Byte11б беззнаковое целое
Diplomacy409632x32 матрица из 2б целых
Dword44б беззнаковое целое
Float44б вещественное
LeverStats12параметры рычага
Null0пустая нода
Plot123 floats (vec3)
Plot2DArray
Quaternion164 floats (vec4)
Record>8контейнер нод
Rectangle
Stringстрока
StringArray>4массив строк
StringEncrypted>4зашифрованный скрипт уровня
UnitStats180параметры существа
Unknown

List of possible type_id
type_idТип данныхИмя поля
0x00000000RecordROOT
0x00001E00RecordVSS_SECTION
0x00001E01RecordVSS_TRIGER
0x00001E02RecordVSS_CHECK
0x00001E03RecordVSS_PATH
0x00001E04DwordVSS_ID
0x00001E05RectangleVSS_RECT
0x00001E06DwordVSS_SRC_ID
0x00001E07DwordVSS_DST_ID
0x00001E08StringVSS_TITLE
0x00001E09StringVSS_COMMANDS
0x00001E0AByteVSS_ISSTART
0x00001E0BRecordVSS_LINK
0x00001E0CStringVSS_GROUP
0x00001E0DByteVSS_IS_USE_GROUP
0x00001E0ERecordVSS_VARIABLE
0x00001E0FStringArrayVSS_BS_CHECK
0x00001E10StringArrayVSS_BS_COMMANDS
0x00001E11StringVSS_CUSTOM_SRIPT
0x0000A000RecordOBJECTDBFILE
0x0000AA00NullLIGHT_SECTION
0x0000AA01RecordLIGHT
0x0000AA02FloatLIGHT_RANGE
0x0000AA03StringLIGHT_NAME
0x0000AA04PlotLIGHT_POSITION
0x0000AA05DwordLIGHT_ID
0x0000AA06ByteLIGHT_SHADOW
0x0000AA07PlotLIGHT_COLOR
0x0000AA08StringLIGHT_COMMENTS
0x0000ABD0RecordWORLD_SET
0x0000ABD1PlotWS_WIND_DIR
0x0000ABD2FloatWS_WIND_STR
0x0000ABD3FloatWS_TIME
0x0000ABD4FloatWS_AMBIENT
0x0000ABD5FloatWS_SUN_LIGHT
0x0000B000RecordOBJECTSECTION
0x0000B001RecordOBJECT
0x0000B002DwordNID
0x0000B003DwordOBJTYPE
0x0000B004StringOBJNAME
0x0000B005NullOBJINDEX
0x0000B006StringOBJTEMPLATE
0x0000B007StringOBJPRIMTXTR
0x0000B008StringOBJSECTXTR
0x0000B009PlotOBJPOSITION
0x0000B00AQuaternionOBJROTATION
0x0000B00BNullOBJTEXTURE
0x0000B00CPlotOBJCOMPLECTION
0x0000B00DStringArrayOBJBODYPARTS
0x0000B00EStringPARENTTEMPLATE
0x0000B00FStringOBJCOMMENTS
0x0000B010NullOBJ_DEF_LOGIC
0x0000B011ByteOBJ_PLAYER
0x0000B012DwordOBJ_PARENT_ID
0x0000B013ByteOBJ_USE_IN_SCRIPT
0x0000B014ByteOBJ_IS_SHADOW
0x0000B015NullOBJ_R
0x0000B016StringOBJ_QUEST_INFO
0x0000C000NullSC_OBJECTDBFILE
0x0000CC00NullSOUND_SECTION
0x0000CC01RecordSOUND
0x0000CC02DwordSOUND_ID
0x0000CC03PlotSOUND_POSITION
0x0000CC04DwordSOUND_RANGE
0x0000CC05StringSOUND_NAME
0x0000CC06DwordSOUND_MIN
0x0000CC07DwordSOUND_MAX
0x0000CC08StringSOUND_COMMENTS
0x0000CC09NullSOUND_VOLUME
0x0000CC0AStringArraySOUND_RESNAME
0x0000CC0BDwordSOUND_RANGE2
0x0000CC0DByteSOUND_AMBIENT
0x0000CC0EByteSOUND_IS_MUSIC
0x0000D000NullPR_OBJECTDBFILE
0x0000DD00NullPARTICL_SECTION
0x0000DD01RecordPARTICL
0x0000DD02DwordPARTICL_ID
0x0000DD03PlotPARTICL_POSITION
0x0000DD04StringPARTICL_COMMENTS
0x0000DD05StringPARTICL_NAME
0x0000DD06DwordPARTICL_TYPE
0x0000DD07FloatPARTICL_SCALE
0x0000E000RecordDIRICTORY
0x0000E001RecordFOLDER
0x0000E002StringDIR_NAME
0x0000E003DwordDIR_NINST
0x0000E004DwordDIR_PARENT_FOLDER
0x0000E005ByteDIR_TYPE
0x0000F000RecordDIRICTORY_ELEMENTS
0x0000FF00RecordSEC_RANGE
0x0000FF01RecordMAIN_RANGE
0x0000FF02RecordRANGE
0x0000FF05DwordMIN_ID
0x0000FF06DwordMAX_ID
0x31415926AiGraphAIGRAPH
0xACCEECCAStringSS_TEXT_OLD
0xACCEECCBStringEncryptedSS_TEXT
0xBBAB0000RecordMAGIC_TRAP
0xBBAB0001DwordMT_DIPLOMACY
0xBBAB0002StringMT_SPELL
0xBBAB0003AreaArrayMT_AREAS
0xBBAB0004Plot2DArrayMT_TARGETS
0xBBAB0005DwordMT_CAST_INTERVAL
0xBBAC0000RecordLEVER
0xBBAC0001NullLEVER_SCIENCE_STATS
0xBBAC0002ByteLEVER_CUR_STATE
0xBBAC0003ByteLEVER_TOTAL_STATE
0xBBAC0004ByteLEVER_IS_CYCLED
0xBBAC0005ByteLEVER_CAST_ONCE
0xBBAC0006LeverStatsLEVER_SCIENCE_STATS_NEW
0xBBAC0007ByteLEVER_IS_DOOR
0xBBAC0008ByteLEVER_RECALC_GRAPH
0xBBBB0000RecordUNIT
0xBBBB0001NullUNIT_R
0xBBBB0002StringUNIT_PROTOTYPE
0xBBBB0003NullUNIT_ITEMS
0xBBBB0004UnitStatsUNIT_STATS
0xBBBB0005StringArrayUNIT_QUEST_ITEMS
0xBBBB0006StringArrayUNIT_QUICK_ITEMS
0xBBBB0007StringArrayUNIT_SPELLS
0xBBBB0008StringArrayUNIT_WEAPONS
0xBBBB0009StringArrayUNIT_ARMORS
0xBBBB000AByteUNIT_NEED_IMPORT
0xBBBC0000RecordUNIT_LOGIC
0xBBBC0001NullUNIT_LOGIC_AGRESSIV
0xBBBC0002ByteUNIT_LOGIC_CYCLIC
0xBBBC0003DwordUNIT_LOGIC_MODEL
0xBBBC0004FloatUNIT_LOGIC_GUARD_R
0xBBBC0005PlotUNIT_LOGIC_GUARD_PT
0xBBBC0006ByteUNIT_LOGIC_NALARM
0xBBBC0007ByteUNIT_LOGIC_USE
0xBBBC0008NullUNIT_LOGIC_REVENGE
0xBBBC0009NullUNIT_LOGIC_FEAR
0xBBBC000AFloatUNIT_LOGIC_WAIT
0xBBBC000BByteUNIT_LOGIC_ALARM_CONDITION
0xBBBC000CFloatUNIT_LOGIC_HELP
0xBBBC000DByteUNIT_LOGIC_ALWAYS_ACTIVE
0xBBBC000EByteUNIT_LOGIC_AGRESSION_MODE
0xBBBD0000RecordGUARD_PT
0xBBBD0001PlotGUARD_PT_POSITION
0xBBBD0002NullGUARD_PT_ACTION
0xBBBE0000RecordACTION_PT
0xBBBE0001PlotACTION_PT_LOOK_PT
0xBBBE0002DwordACTION_PT_WAIT_SEG
0xBBBE0003DwordACTION_PT_TURN_SPEED
0xBBBE0004ByteACTION_PT_FLAGS
0xBBBF0000RecordTORCH
0xBBBF0001FloatTORCH_STRENGHT
0xBBBF0002PlotTORCH_PTLINK
0xBBBF0003StringTORCH_SOUND
0xDDDDDDD1RecordDIPLOMATION
0xDDDDDDD2DiplomacyDIPLOMATION_FOF
0xDDDDDDD3StringArrayDIPLOMATION_PL_NAMES
0xFFFFFFFFUnknownUNKNOWN

This file contains all the information about the level - diplomacy, location and parameters of units, information for the legendary Nival editor, and the most interesting is the level script, and in encrypted form (the key is nearby, do not worry).


String decode
unsigned key;
for (size_t i = 0; i < size; i++) 
{
    key += (((((key * 13) << 4) + key) << 8) - key) * 4 + 2531011;
    data[i] ^= key >> 16;
}

This is interesting: this format is very important for mod-makers, however, it was a very difficult task to reverse the files and decrypt the script (and then encrypt it back). In ancient times, one of the teams wrote its utility, in which encryption was performed with one permanent key, in order to identify the fact of use by a competing team.

The same legendary level editor (taken from the forums, the exact date is unknown, but in the screenshot - Windows 98):



This is interesting: a screenshot of the editor appeared several times on the forums where he was eager to get it. Naturally, it was never given to anyone (except, perhaps, to the developers of "Damned Earth: Lost in Astral," but I have no information).

Now, having received all the information we need, we can finally convert everything into a more or less known file format, for example, Collada and make the final render as a memory:



Epilogue


Our brief tour of the files of the Cursed Lands has come to an end. We reviewed the structure of most formats, and those who were particularly interested probably already used the diagrams and wrote their own converter or viewer.


I hope that this article will be useful to fans of the game or attract new people in the community. Now, the chances of making something new even a little, but have increased - someone wants to write a map editor, this will attract even more interested people. Ah, dreams, dreams ...


At this I say goodbye - see you in the open spaces of Cania!


UPD (01/23/2019):
Since Habr's policy has changed for the better, I can leave a link to the repository with the code and the updated version of the format descriptions: github .
I would also like to thank all the indifferent readers who pointed out to me a few inaccuracies in the description of the formats (for example, the maps of the “comb” of polygons were in the wrong direction).


Resources used and links

Общая информация об игре



Инструменты с открытыми исходниками



Коммьюнити



Also popular now: