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?
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.
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.
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.
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.
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.
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.
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:
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):
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:
Let's start with a simple - with textures.
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 |
If the image format is PNT3 , then the pixel structure after decompression is ARGB8; bits_per_pixel
- the size of the compressed image in bytes.
n = 0 destination = b"" while src < size: v = int.from_bytes(source[src:src + 4], byteorder='little') src += 4 if v > 1000000 or v == 0: n += 1 else: 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.
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:
unsigned char type_index; unsigned char 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.
designation | description |
---|---|
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 |
one | 0FII |
2 | SUFF |
3 | FFFF |
four | 0SISS |
five | 0SISS00000U |
table | structure |
---|---|
Materials | SSSIFFFIFIFfIX |
Weapons | SSISIIIFFFFIFIXB00000IHFFFfHHFF |
Armor | SSISIIIFFFFIFIXB00000ffBiHH |
Quick items | SSISIIIFFFFIFIXB00000IIFFSbH |
Quest items | SSISIIIFFFFIFIXB00000Is |
Sold items | SSISIIIFFFFIFIXB00000IHI |
table | structure |
---|---|
Switch prototype | SfIFTSSS |
table | structure |
---|---|
Skills | SSI0000000s |
Skills | SSI0000000SSIIIFFFIIIIBI |
table | structure |
---|---|
Traces of blood | 0S11 |
Traces of flame | 0S110000001 |
Footprints | 0S11 |
table | structure |
---|---|
Prototypes | SSSFIFIFFFFIIIIUSSIIbIXFFFFF |
Modifiers | SSFIFFISX |
Templates | 0SssSX |
Armor Templates | 0SssSX |
Weapons templates | 0SssSX |
table | structure |
---|---|
Damaged parts | SffUU |
Races | SUFFUUFfFUUf222222000000000000SssFSsfUUfUUIUSBFUUUU |
Monster Prototypes | SSIUIFFFSFFFFFFFFFUFFFFFFff33sfssSFFFFFUFUSF |
NPC | SUFFFFbbssssFUB |
table | structure |
---|---|
Answers | 0S0000000044444444444444444444445444444444444 |
Screams | 0S0000000044444 |
Other | 0S0000000044 |
table | structure |
---|---|
Tasks | SFIISIIs |
Briefings | 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.
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.
The name is collected from groups of two characters - abbreviations of the logical "level".
For example, the character-woman 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 (ogre), driad (foresters).
Morally having a rest, we will plunge headlong into the model. They are presented in several formats that are linked together.
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.
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.
def trilinear(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 height return 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.
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!
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:
Conversation with a raptor in his home
Hermit Lizard: You came, man. It's good.
Zack: Is that all you wanted to tell me?
Hermit Lizard: You're in a hurry again. I remember your questions and I will answer them. I came to the people in the gland to make a deal. But I saw what they did to you. They do not hold the words, I stopped believing them. You kept your word. A deal will be offered to you.
Hermit Lizard: People love gold. Gold lizards uninteresting. You will complete my task, and I will give you the gold that I have. This is a lot of gold.
Zack (thoughtfully and without much interest) : Hmm ... Gold ... It certainly does not hurt ...
Zack: It would be better if you could help me find out where the old magician, whom I have been looking for so long, lives. After all, the lizards - the ancient people, and you can know it!
Hermit Lizard: You're right. Lizards - the ancient people. I can collect everything we know about the old man. Do you agree to complete my task?
Zack: What are we talking about! Consider that everything has already been done.
Hermit Lizard (seriously) : Already done? Do you want to fool me?
Zack: Actually, I wanted to joke, otherwise you were too serious.
Hermit Lizard: I understand. This is a joke. Probably, I can also joke. Then. And now I need you to return the water to the Canal. The water was stolen from us by the orcs.
Hermit Lizard: Walk south along the water. See the dam and the canal. The dam must be raised. Lever. I will give it. Channel need to fill up. A stone. Stone I will not give. He already lies on the edge of the Channel. Upstream of the dam. The stone is heavy. When the orcs dug, they raised it for a long time. If you push it, it will fall back quickly.
Hermit Lizard: Come back after that. I will tell you everything I find out about the old wizard.
Zack: Hand! But, by the way, if you add a little bit of coins to the story, I will not be offended at all.
Hermit Lizard: For coins, go to my relatives, who live in the shallows further in the south. Go to the farthest sandy island, the third in a row. The treasure will be yours!
Hermit Lizard (to himself) : Strange. This man loves humor. I was joking. The man did not laugh. Very strange.
Now - the most interesting: how the map is stored.
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:
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 | Type of |
---|---|
0 | |
one | |
2 | |
3 |
material type | Type of |
---|---|
0 | grass |
one | ground |
2 | stone |
3 | sand |
four | rock |
five | field |
6 | water |
7 | road |
eight | (empty) |
9 | snow |
ten | ice |
eleven | drygrass |
12 | snowballs |
13 | lava |
14 | swamp |
15 | highrock |
, Res/aiinfo.res/tileDesc.reg
.
: , β .
: .
. For the cause!
β 3232 . , 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
β .
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
unsigned short texture; unsigned char tile_index = f & 63; unsigned char texture_index = (f >> 6) & 255; unsigned char rotation = (f >> 14) & 3;
33 33 , , 3232 . β 1 .
:
x = x + x_offset / 254
y = y + y_offset / 254
z = altitude / 65535 * max_altitude ( .mp )
"", :
0 1 2 *-*-* |\|\| ~ 33 *-*-* |\|\| ~ 66 *-*-* ~ ~ ~
, , 1616 . β 2 . , 90 .
. , , ID , MP .
: MP, , : ID , - .
ID β .
β :
- β , .
( ) , , : . β " ", .
, ( ).
:
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
() | description | |
---|---|---|
AiGraph | ||
AreaArray | ||
Byte | one | 1 |
Diplomacy | 4096 | 32x32 2 |
Dword | four | 4 |
Float | four | 4 |
LeverStats | 12 | |
Null | 0 | |
Plot | 12 | 3 floats (vec3) |
Plot2DArray | ||
Quaternion | sixteen | 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 |
β , , Nival, β , ( , ).
unsigned key; for (size_t i = 0; i < size; i++) { key += (((((key * 13) << 4) + key) << 8) - key) * 4 + 2531011; data[i] ^= key >> 16; }
: , ( ) . , , , .
( , , β Windows 98):
: , . , ( , , " : ", ).
, , - - , , Collada :
. , .
, . - , β - , . , -...
β !
UPD (23.01.2019):
, : github .
, (, "" ).
Source: https://habr.com/ru/post/434802/
All Articles