"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:
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.
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:
- The archive is optimized for downloading file information to a linked list with private hashing.
- 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.
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:
- Textures;
- Database;
- Models;
- 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.
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:
fourcc | Description |
---|---|
44 44 00 00 | ARGB4 |
44 58 54 31 | DXT1 |
44 58 54 33 | DXT3 |
50 4E 54 33 | PNT3 - RLE compressed ARGB8 |
50 56 00 00 | R5G5B5 |
51 55 00 00 | A1R5G5B5 |
88 88 00 00 | ARGB8 |
Если формат изображения 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.
обозначение | описание |
---|---|
S | string |
I | 4b int |
U | 4b unsigned |
F | 4b float |
X | bits byte |
f | float array |
i | int array |
B | bool |
b | bool array |
H | unknown hex bytes |
T | time |
0 | not stated |
1 | 0FII |
2 | SUFF |
3 | FFFF |
4 | 0SISS |
5 | 0SISS00000U |
Предметы (.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 |
NPC | SUFFFFbbssssFUB |
Выкрики (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.
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).
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.
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.
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.
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:
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.
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:
Узнать, что нужно Ящеру
Разговор с ящером в его жилище
Ящер-Отшельник: Ты пришел, человек. Это хорошо.
Зак: Это все, что ты хотел мне сказать?
Ящер-Отшельник: Ты опять торопишься. Я помню твои вопросы и буду на них отвечать. Я пришел к людям в железе, чтобы заключить сделку. Но я увидел, как они поступили с тобой. Они не держат слова, я перестал им верить. Ты сдержал слово. Сделка будет предложена тебе.
Ящер-Отшельник: Люди любят золото. Ящерам золото неинтересно. Ты выполнишь мое задание, и я дам тебе золото, которое есть у меня. Этого золота много.
Зак (задумчиво и без особой заинтересованности): Хм… Золото… Оно, конечно, не помешает…
Зак: Было бы лучше, если бы ты помог мне узнать, где живет старый маг, которого я так долго ищу. Ведь ящеры — древний народ, и вы можете это знать!
Ящер-Отшельник: Ты прав. Ящеры — древний народ. Я могу собрать все, что нам известно про старика. Ты согласен выполнить мое задание?
Зак: О чем разговор! Считай, что все уже сделано.
Ящер-Отшельник (серьезно): Уже сделано? Ты хочешь меня обмануть?
Зак: Вообще-то я хотел пошутить, а то ты уж больно серьезен.
Ящер-Отшельник: Понимаю. Это шутка. Наверное, я тоже смогу пошутить. Потом. А сейчас мне надо, чтобы ты вернул воду в Канал. Воду украли у нас орки.
Ящер-Отшельник: Иди на юг вдоль воды. Увидишь плотину и Канал. Плотину надо поднять. Рычагом. Я его дам. Канал нужно завалить. Камнем. Камень я не дам. Он уже лежит на краю Канала. Вверх по течению от плотины. Камень тяжелый. Когда орки копали, они его поднимали долго. Если ты его толкнешь, обратно он будет падать быстро.
Ящер-Отшельник: После этого возвращайся. Я расскажу тебе все, что узнаю про старого Мага.
Зак: По рукам! Но, кстати, если ты добавишь к рассказу немножко монет, я вовсе не обижусь.
Ящер-Отшельник: За монетами отправляйся к моим сородичам, которые живут на отмелях дальше, на юге. Пройди на самый дальний песчаный остров, третий по счету. Сокровища будут твоими!
Ящер-Отшельник (сам себе): Странно. Этот человек любит юмор. Я пошутил. Человек не засмеялся. Очень странно.
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.
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
terrain type | Тип |
---|---|
0 | Базовый ландшафт |
1 | Вода без текстуры |
2 | Текстурированная трава |
3 | Текстурированная вода |
material 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 |
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
.
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.
Распаковка нормали
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;
Получение ландшафта
Вершины идут по 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;
Описание структуры (опять же, неполное)
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
тип данных | размер (обычно) | описание |
---|---|---|
AiGraph | граф проходимости | |
AreaArray | ||
Byte | 1 | 1б беззнаковое целое |
Diplomacy | 4096 | 32x32 матрица из 2б целых |
Dword | 4 | 4б беззнаковое целое |
Float | 4 | 4б вещественное |
LeverStats | 12 | параметры рычага |
Null | 0 | пустая нода |
Plot | 12 | 3 floats (vec3) |
Plot2DArray | ||
Quaternion | 16 | 4 floats (vec4) |
Record | >8 | контейнер нод |
Rectangle | ||
String | строка | |
StringArray | >4 | массив строк |
StringEncrypted | >4 | зашифрованный скрипт уровня |
UnitStats | 180 | параметры существа |
Unknown |
type_id | Тип данных | Имя поля |
---|---|---|
0x00000000 | Record | ROOT |
0x00001E00 | Record | VSS_SECTION |
0x00001E01 | Record | VSS_TRIGER |
0x00001E02 | Record | VSS_CHECK |
0x00001E03 | Record | VSS_PATH |
0x00001E04 | Dword | VSS_ID |
0x00001E05 | Rectangle | VSS_RECT |
0x00001E06 | Dword | VSS_SRC_ID |
0x00001E07 | Dword | VSS_DST_ID |
0x00001E08 | String | VSS_TITLE |
0x00001E09 | String | VSS_COMMANDS |
0x00001E0A | Byte | VSS_ISSTART |
0x00001E0B | Record | VSS_LINK |
0x00001E0C | String | VSS_GROUP |
0x00001E0D | Byte | VSS_IS_USE_GROUP |
0x00001E0E | Record | VSS_VARIABLE |
0x00001E0F | StringArray | VSS_BS_CHECK |
0x00001E10 | StringArray | VSS_BS_COMMANDS |
0x00001E11 | String | VSS_CUSTOM_SRIPT |
0x0000A000 | Record | OBJECTDBFILE |
0x0000AA00 | Null | LIGHT_SECTION |
0x0000AA01 | Record | LIGHT |
0x0000AA02 | Float | LIGHT_RANGE |
0x0000AA03 | String | LIGHT_NAME |
0x0000AA04 | Plot | LIGHT_POSITION |
0x0000AA05 | Dword | LIGHT_ID |
0x0000AA06 | Byte | LIGHT_SHADOW |
0x0000AA07 | Plot | LIGHT_COLOR |
0x0000AA08 | String | LIGHT_COMMENTS |
0x0000ABD0 | Record | WORLD_SET |
0x0000ABD1 | Plot | WS_WIND_DIR |
0x0000ABD2 | Float | WS_WIND_STR |
0x0000ABD3 | Float | WS_TIME |
0x0000ABD4 | Float | WS_AMBIENT |
0x0000ABD5 | Float | WS_SUN_LIGHT |
0x0000B000 | Record | OBJECTSECTION |
0x0000B001 | Record | OBJECT |
0x0000B002 | Dword | NID |
0x0000B003 | Dword | OBJTYPE |
0x0000B004 | String | OBJNAME |
0x0000B005 | Null | OBJINDEX |
0x0000B006 | String | OBJTEMPLATE |
0x0000B007 | String | OBJPRIMTXTR |
0x0000B008 | String | OBJSECTXTR |
0x0000B009 | Plot | OBJPOSITION |
0x0000B00A | Quaternion | OBJROTATION |
0x0000B00B | Null | OBJTEXTURE |
0x0000B00C | Plot | OBJCOMPLECTION |
0x0000B00D | StringArray | OBJBODYPARTS |
0x0000B00E | String | PARENTTEMPLATE |
0x0000B00F | String | OBJCOMMENTS |
0x0000B010 | Null | OBJ_DEF_LOGIC |
0x0000B011 | Byte | OBJ_PLAYER |
0x0000B012 | Dword | OBJ_PARENT_ID |
0x0000B013 | Byte | OBJ_USE_IN_SCRIPT |
0x0000B014 | Byte | OBJ_IS_SHADOW |
0x0000B015 | Null | OBJ_R |
0x0000B016 | String | OBJ_QUEST_INFO |
0x0000C000 | Null | SC_OBJECTDBFILE |
0x0000CC00 | Null | SOUND_SECTION |
0x0000CC01 | Record | SOUND |
0x0000CC02 | Dword | SOUND_ID |
0x0000CC03 | Plot | SOUND_POSITION |
0x0000CC04 | Dword | SOUND_RANGE |
0x0000CC05 | String | SOUND_NAME |
0x0000CC06 | Dword | SOUND_MIN |
0x0000CC07 | Dword | SOUND_MAX |
0x0000CC08 | String | SOUND_COMMENTS |
0x0000CC09 | Null | SOUND_VOLUME |
0x0000CC0A | StringArray | SOUND_RESNAME |
0x0000CC0B | Dword | SOUND_RANGE2 |
0x0000CC0D | Byte | SOUND_AMBIENT |
0x0000CC0E | Byte | SOUND_IS_MUSIC |
0x0000D000 | Null | PR_OBJECTDBFILE |
0x0000DD00 | Null | PARTICL_SECTION |
0x0000DD01 | Record | PARTICL |
0x0000DD02 | Dword | PARTICL_ID |
0x0000DD03 | Plot | PARTICL_POSITION |
0x0000DD04 | String | PARTICL_COMMENTS |
0x0000DD05 | String | PARTICL_NAME |
0x0000DD06 | Dword | PARTICL_TYPE |
0x0000DD07 | Float | PARTICL_SCALE |
0x0000E000 | Record | DIRICTORY |
0x0000E001 | Record | FOLDER |
0x0000E002 | String | DIR_NAME |
0x0000E003 | Dword | DIR_NINST |
0x0000E004 | Dword | DIR_PARENT_FOLDER |
0x0000E005 | Byte | DIR_TYPE |
0x0000F000 | Record | DIRICTORY_ELEMENTS |
0x0000FF00 | Record | SEC_RANGE |
0x0000FF01 | Record | MAIN_RANGE |
0x0000FF02 | Record | RANGE |
0x0000FF05 | Dword | MIN_ID |
0x0000FF06 | Dword | MAX_ID |
0x31415926 | AiGraph | AIGRAPH |
0xACCEECCA | String | SS_TEXT_OLD |
0xACCEECCB | StringEncrypted | SS_TEXT |
0xBBAB0000 | Record | MAGIC_TRAP |
0xBBAB0001 | Dword | MT_DIPLOMACY |
0xBBAB0002 | String | MT_SPELL |
0xBBAB0003 | AreaArray | MT_AREAS |
0xBBAB0004 | Plot2DArray | MT_TARGETS |
0xBBAB0005 | Dword | MT_CAST_INTERVAL |
0xBBAC0000 | Record | LEVER |
0xBBAC0001 | Null | LEVER_SCIENCE_STATS |
0xBBAC0002 | Byte | LEVER_CUR_STATE |
0xBBAC0003 | Byte | LEVER_TOTAL_STATE |
0xBBAC0004 | Byte | LEVER_IS_CYCLED |
0xBBAC0005 | Byte | LEVER_CAST_ONCE |
0xBBAC0006 | LeverStats | LEVER_SCIENCE_STATS_NEW |
0xBBAC0007 | Byte | LEVER_IS_DOOR |
0xBBAC0008 | Byte | LEVER_RECALC_GRAPH |
0xBBBB0000 | Record | UNIT |
0xBBBB0001 | Null | UNIT_R |
0xBBBB0002 | String | UNIT_PROTOTYPE |
0xBBBB0003 | Null | UNIT_ITEMS |
0xBBBB0004 | UnitStats | UNIT_STATS |
0xBBBB0005 | StringArray | UNIT_QUEST_ITEMS |
0xBBBB0006 | StringArray | UNIT_QUICK_ITEMS |
0xBBBB0007 | StringArray | UNIT_SPELLS |
0xBBBB0008 | StringArray | UNIT_WEAPONS |
0xBBBB0009 | StringArray | UNIT_ARMORS |
0xBBBB000A | Byte | UNIT_NEED_IMPORT |
0xBBBC0000 | Record | UNIT_LOGIC |
0xBBBC0001 | Null | UNIT_LOGIC_AGRESSIV |
0xBBBC0002 | Byte | UNIT_LOGIC_CYCLIC |
0xBBBC0003 | Dword | UNIT_LOGIC_MODEL |
0xBBBC0004 | Float | UNIT_LOGIC_GUARD_R |
0xBBBC0005 | Plot | UNIT_LOGIC_GUARD_PT |
0xBBBC0006 | Byte | UNIT_LOGIC_NALARM |
0xBBBC0007 | Byte | UNIT_LOGIC_USE |
0xBBBC0008 | Null | UNIT_LOGIC_REVENGE |
0xBBBC0009 | Null | UNIT_LOGIC_FEAR |
0xBBBC000A | Float | UNIT_LOGIC_WAIT |
0xBBBC000B | Byte | UNIT_LOGIC_ALARM_CONDITION |
0xBBBC000C | Float | UNIT_LOGIC_HELP |
0xBBBC000D | Byte | UNIT_LOGIC_ALWAYS_ACTIVE |
0xBBBC000E | Byte | UNIT_LOGIC_AGRESSION_MODE |
0xBBBD0000 | Record | GUARD_PT |
0xBBBD0001 | Plot | GUARD_PT_POSITION |
0xBBBD0002 | Null | GUARD_PT_ACTION |
0xBBBE0000 | Record | ACTION_PT |
0xBBBE0001 | Plot | ACTION_PT_LOOK_PT |
0xBBBE0002 | Dword | ACTION_PT_WAIT_SEG |
0xBBBE0003 | Dword | ACTION_PT_TURN_SPEED |
0xBBBE0004 | Byte | ACTION_PT_FLAGS |
0xBBBF0000 | Record | TORCH |
0xBBBF0001 | Float | TORCH_STRENGHT |
0xBBBF0002 | Plot | TORCH_PTLINK |
0xBBBF0003 | String | TORCH_SOUND |
0xDDDDDDD1 | Record | DIPLOMATION |
0xDDDDDDD2 | Diplomacy | DIPLOMATION_FOF |
0xDDDDDDD3 | StringArray | DIPLOMATION_PL_NAMES |
0xFFFFFFFF | Unknown | UNKNOWN |
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).
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).
Общая информация об игре
- http://ru.nival.com/games/history/zemli — официальная страница игры
- https://ru.wikipedia.org/wiki/Проклятые_земли — статья на Википедии
- https://allods.gipat.ru/index.php?p=ei — обобщённая информация от "Вселенной Аллодов"
- https://evil-islands.github.io — сборник ссылок, файлов и полезной информации
Инструменты с открытыми исходниками
- http://gipatgroup.org/utilities — EiEdit (.res, .*db), MobSurgeon (.mob)
- http://svn.gipat.org/trac/GGWiki — EiEdit (.res, .*db), MobSurgeon (.mob), информация о форматах .mp, .sec, .*db
- https://github.com/demothorg/eifixer — исправление некоторых проблем игры
- https://github.com/konstvest/ei_figer — аддон для Blender для редактирования .lnk, .fig, .bon, .anm
- https://github.com/demothorg/ei-tools — библиотека для работы с несколькими форматами (.mob, .lnk, .mpr, .res) + набор инструментов
- https://github.com/konstvest/ei_maper — редактирование карты (.mpr, .mp, .sec, .mob)
- https://github.com/chemmalion/EIDBEditor — редактирование баз данных игры (.*db)
- https://gitlab.com/ykurganov/open-evil-islands — проект по разработке открытого альтернативного движка игры, поддержка чтения большей части форматов
Коммьюнити
- https://vk.com/evil.islands — группа ВКонтакте
- http://allods.gipat.ru — "Вселенная Аллодов"
- http://gipat.ru — форум "Город Джунов"
- http://gipatgroup.org/forum — форум GipatGroup (на данный момент — не функционирует)
- http://honestgroup.net/forum — форум HonestGroup