
你还记得很多俄罗斯游戏吗? 定性的? 难忘? 是的,他们是。 如果您年满35岁,或者是俄罗斯游戏产业的忠实拥护者,那么您可能很熟悉《诅咒之地》。
故事开始得很平淡:夏天,炎热。 没什么特别的事情要做,当懒惰地浏览笔记本电脑硬盘的内容时,我的目光抓住了一个带有熟悉的龙图标的文件夹,该文件夹已经闲置了几年。
哪个游戏迷不会知道里面有什么?
引言
被诅咒的土地 -或在独联体之外被称为“ 邪恶的岛屿:失落的灵魂的诅咒”,这是2000年发布的隐身RPG游戏。 该游戏是由Nival Interactive开发的,当时该公司已经将自己确立为一系列的Alloda游戏(国外的Rage of Mages)。 大部分莫斯科国立大学的毕业生都在其中工作-他们完全有能力实现具有完整三维世界的首批游戏之一。
2010年,Mail.Ru( 信息 )转让了标题,但该游戏仍代表Nival在GOG商店中出售。
相对最近,该游戏已经18岁了-生日被认为是10月26日,即CIS中的发布日期。 尽管年代久远,官方主服务器仍在使用:定期,有人决定爬过Gipat的森林,并用同志小队打十二打或两个骷髅。
关于文章的简要介绍
最初,我的目标只是使用专用的标准库在Python 3中“为我自己”编写一个单向转换器。 但是,在此过程中,有关格式的文档开始顺利,试图以某种方式标准化输出。 对于某些格式,使用Kaitai Struct描述了该结构。 结果,一切都导致了本文和Wiki格式的撰写。
我马上注意到:在大多数情况下,已经研究了游戏文件,并为他们编写了粉丝编辑器。 但是,这些信息非常分散,在公共领域中或多或少没有完整的格式描述,也没有足够的集合来创建修改。
...以及如何阅读
提供了所有格式的方案(.ksy文件),可以单击两次即可将其转换为几种最流行语言的代码。
不幸的是,在撰写本文的最后阶段,我发现受人尊敬的Habr不能突出显示YAML(和JSON),并且所有方案都使用它。 这应该不是什么大问题,但是如果不方便阅读该方案,我建议您将其复制到第三方编辑器,例如NPP。
资源及其居住地
该游戏是一个便携式应用程序,其中包含带有库的引擎,启动器以及实际上打包的资源。
这很有趣:游戏的设置几乎全部存储在注册表中。 GOG版本中的相机错误是由于安装程序未注册正确的默认值而导致的。
乍一看游戏文件夹的内容,我们立即注意到几个新的文件扩展名:ASI和REG。
第一个是动态库,我们将不考虑它(这是由逆向工程专家完成的),但是第二个是游戏的第一个本机文件格式。
REG
此类型的文件是众所周知的INI文件的二进制序列化。
内容分为存储键及其值的部分。 REG文件保留了此层次结构,但是加快了读取和解析数据的速度-在2000年,这显然很关键。
通常,您可以描述此图的结构:

结构说明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
这很有趣: 2002年,Nival与游戏社区共享了一些工具( 站点快照 )-其中一个是REG中的INI序列化器。 您可能会猜到,虽然不是官方的,但反序列化器几乎立即出现。
整理好开始文件夹后,让我们继续进入子目录。
最初的外观位于包含CAM文件的Cameras文件夹中。
凸轮
一个非常简单的格式就是随时间打包摄像机的位置。 通过位置和旋转来描述摄像机。 其他两个字段大概是时间和运动顺序。

结构说明 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
在下一个文件夹-Res中,(出乎意料!)存储了存档的RES文件。
RES
这种格式有时会隐藏在其他扩展名下,但原始格式仍然完全是RES。
数据结构对于具有对文件的随机访问权的存档来说非常典型:有一些表用于存储有关内部文件,名称表和文件内容的信息。
目录结构直接包含在名称中。
值得注意两个非常有趣的事实:
- 该归档文件经过优化,可使用封闭式哈希将文件信息加载到链接列表中。
- 您可以存储一次文件的内容,但是可以使用不同的名称来引用它。 据我所知,这个事实被用在了风扇的重新包装中,因此游戏的尺寸大大减小了。 在原始发行版中,未使用档案优化。

结构说明 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
这很有趣:在游戏的俄语版本中,Speech.res归档文件包含两个子目录s和t,它们的内容完全相同,这就是为什么归档文件大小是原来的两倍大的原因-这就是为什么游戏不适合一张CD的原因。
现在您可以解压缩所有归档文件(可以嵌套):
- RES只是一个档案,
- MPR-游戏级别的概况,
- MQ-有关多人游戏任务的信息,
- ANM-一组动画,
- MOD-3D模型,
- BON-模型骨骼的位置。
如果存档中的文件没有扩展名,我们将为BON和ANM存档添加父扩展名。
您还可以将所有收到的文件分为四个组:
- 贴图
- 资料库
- 型号
- 关卡文件。
让我们从简单的-纹理开始。
MMP
其实是质地。 它有一个小标题,指示图像参数,MIP级别数和使用的压缩。 标头后是按大小降序的MIP图像级别。

结构说明 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
可能的像素包装格式:
fourcc | 内容描述 |
---|
44 44 00 00 | ARGB4 |
44 58 54 31 | Dxt1 |
44 58 54 33 | Dxt3 |
50 4E 54 33 | PNT3-RLE压缩ARGB8 |
50 56 00 00 | R5G5B5 |
51 55 00 00 | A1R5G5B5 |
88 88 00 00 | ARGB8 |
关于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 += 4 if v > 1000000 or v == 0: n += 1 else: destination += source[src - (1 + n) * 4:src - 4] destination += b"\x00" * v n = 0
这很有趣:有些纹理是垂直反射的(有些是不反射的?)。
游戏非常喜欢透明性-如果图像带有Alpha通道,则透明像素的颜色必须恰好是黑色。 还是白色-真是幸运。
简单的格式已经结束,让我们继续使用更严格的格式-一次,mod制作人员大胆地为以下格式保留了自己的编辑工具,但没有白费。 我警告过你
数据库(* DB和其他)
这种格式非常难以描述-本质上,这是节点(或记录表)的序列化树。 一个文件由具有指定字段类型的几个表组成。 常规结构:表嵌套在公共的“根”节点中,记录是表内的节点。
在每个节点中,指定其类型和大小:
unsigned char type_index; unsigned char raw_size;
表字段的类型由索引从表的格式字符串中获取,实际类型由获得的值确定。
栏位类型名称 | 说明 |
---|
小号 | 弦 |
我 | 4b int |
ü | 4b未签名 |
˚F | 4B浮球 |
X | 位字节 |
˚F | 浮点数组 |
我 | 整数数组 |
乙 | 布尔 |
b | 布尔数组 |
^ h | 未知的十六进制字节 |
Ť | 时间 |
0 | 没有说明 |
1个 | 0FII |
2 | SUFF |
3 | FFFF |
4 | 0SISS |
5 | 0SISS00000U |
基础说明项目(.idb)
桌子 | 结构 |
---|
用料 | SSSIFFFIFIFfIX |
武器装备 | SSISIIIFFFFIFIXB00000IHFFFfHHFF |
护甲 | SSISIIIFFFFIFIXB00000ffBiHH |
快速物品 | SSISIIIFFFFIFIXB00000IIFFSbH |
任务物品 | SSISIIIFFFFIFIXB00000Is |
销售物品 | SSISIIIFFFFIFIXB00000IHI |
开关(.ldb)
技能与技巧(.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 |
有趣的是: 2002年1月16日,Nival发布了csv格式的多人游戏的源代码库,以及游戏格式的实用程序转换器( 网站快照 )。 自然,逆转换器的出现并不慢。 至少还有两个描述modmakers的字段及其类型的文档,但是很难阅读它们。
亚行
特定单元类型的动画数据库。 与上述* DB相比,它相当“人性化”-它是具有静态字段大小的单级表。

结构说明 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'
这很有趣:对于几个单元,使用了部分截断的数据库格式,这几乎没有被探索。
处理完数据库后,我们宣布暂停广告投放。 但是我们不会做任何广告-不是我们的方法。 更好地表示接下来有用的东西-生物文件的命名方式。
名称是从两个字符组成的组中收集的-逻辑“级别”的缩写。
例如,女性角色将是unhufe
- Unit > Human > Female
,并且initwesp
- Inventory > Item > Weapon > Spear
,即库存中的一支矛(不是后矛,这很好)。
名称元素的完整树: 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
这很有趣:按照这种分类,蘑菇是树木,带有地精的go是兄弟,而Tka-Rick是怪物。 在这里,您还可以看到怪物的“有效”名称,与D&D的可疑名称相似-旁观者(邪恶之眼),调教(竖琴),食人魔(食人族),闪族(森林人)。
在道德上休息之后,我们全力投入模型。 它们以链接在一起的几种格式表示。
伦克
逻辑上-模型的基础。 用现代3D建模描述模型各部分的层次结构-骨骼的层次结构。

结构说明 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
父骨骼的父名称是一个空字符串(长度为0)。
有骨头,但是仅仅命名和组合在一起是不够的-您需要将它们组装成骨架。
邦
如前所述,此格式(如果不是归档文件)设置相对于父零件的模型零件(骨骼)的位置。 仅偏移存储,没有旋转-这是现代格式的差异之一。

结构说明 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
如您所见,一个偏移量的数字太多-事实是,在这里我们首先遇到了游戏引擎的关键特征之一-三线性模型插值。
工作原理:模型具有三个插值参数-有条件,强度,灵巧性,增长。 模型也有8个极端状态。 使用这些参数,我们可以通过三线性插值获得最终模型。
算法本身 def trilinear(val, coefs=[0, 0, 0]):
这很有趣:三线性模型插值用于为某些对象设置动画,例如,打开石门和箱子。
现在是时候看看模型本身的各个部分了。
图
也许这种反弹是无法理解的。 您可以在网上找到他的描述和搅拌机的插件,但是即使有了它们,意识也不会马上消失。 看一下:

结构说明 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
有什么困难? 因此,毕竟,法线和顶点的数据存储在4个块中,并且顶点也排列在8个块中进行插值。
这很有趣:据推测,这样的分组是为了借助自1999年以来出现在英特尔处理器中的SSE指令来加快处理速度。
好了,我们阅读并组成了模型,但是缺少了一些东西。 恰好-动画!
安姆
动画以组件形式存储为关键状态。 一个有趣的事实是,它不仅支持骨骼动画,而且还支持顶点变形。

结构说明 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
就是这样-现在我们有了完整的模型,您可以欣赏一下刚渲染的隐士蜥蜴:

怀旧的时刻找出蜥蜴的需求
与他家中的蜥蜴交谈
隐士蜥蜴:伙计,你来了。 很好
扎克:这就是你想告诉我的吗?
隐士蜥蜴:你又着急了。 我记得您的问题,并会回答。 我来找人做生意。 但是我看到了他们对你的表现。 他们一言不发,我停止相信他们。 你信守诺言 将为您提供交易。
隐士蜥蜴:人们喜欢黄金。 金蜥蜴没意思。 您将完成我的任务,我会给您我拥有的金子。 有很多黄金。
扎克(深思熟虑,没多大兴趣) :嗯……金……肯定不会伤害……
扎克:如果您能帮助我找出我一直在寻找长寿的老魔术师的位置,那会更好。 毕竟,蜥蜴是一个古老的民族,你知道的!
隐士蜥蜴:你是对的。 蜥蜴是古老的民族。 我可以收集我们所知道的有关老人的一切。 您同意完成我的任务吗?
扎克:真是个话题! 考虑到一切都已经完成。
隐士蜥蜴(严重) :已经完成了吗? 你想骗我吗
扎克:实际上,我想开个玩笑,否则你真的很认真。
隐士蜥蜴:我明白了。 这是个玩笑。 我想我也可以开个玩笑。 然后 现在我需要您将水还给运河。 兽人从我们那里偷了水。
隐士蜥蜴:沿着水往南走。 您将看到水坝和运河。 大坝必须抬高。 杠杆作用。 我会给。 该频道需要封锁。 石头。 我不会扔石头。 他已经躺在运河的边缘。 大坝上游。 石头很重。 当兽人挖掘时,他们将他抬了很长时间。 如果您推动他,他将迅速后退。
隐士蜥蜴:之后,回来。 我将告诉您我从旧魔术师那里学到的所有知识。
扎克:手牵手! 但是,顺便说一句,如果您在故事中添加一些硬币,我将不会被冒犯。
隐士蜥蜴:要买硬币,去找我的亲戚,他们住在南部更浅的地方。 前往第三座最远的沙滩岛。 珍宝将属于您!
蜥蜴人隐士(对自己) :奇怪。 这个男人喜欢幽默。 我在开玩笑。 那个人没有笑。 很奇怪
现在-最有趣的是:地图的存储方式。
MP
这是地图头文件。 由于不幸的巧合,扩展名与多人保存文件的扩展名重合,我们将不予考虑。
首先,您需要对景观进行总体描述:
- “块”的数量-卡的32x32米;
- 最大高度(因为顶点的高度以整数比例存储);
- 瓷砖地图集的数量。
, — , .

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
地形类型 | 型式 |
---|
0 | 基础景观 |
1个 | 无纹理的水 |
2 | 纹理的草 |
3 | 质感的水 |
材料类型 | 型式 |
---|
0 | 草 |
1个 | 地面 |
2 | 石头 |
3 | 沙 |
4 | 摇滚乐 |
5 | 场 |
6 | 水 |
7 | 路 |
8 | (空) |
9 | 大雪 |
10 | 冰块 |
11 | 干草 |
12 | 雪球 |
13 | 熔岩 |
14 | 沼泽地 |
15 | 高摇滚 |
, Res/aiinfo.res/tileDesc.reg
.
: , — .
: .
. !
SEC
— 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
— .
正常拆箱
每个z轴10位,每个x和y 11位
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;
3d获得风景
顶点沿着33条线中的33个元素移动,即形成32x32单元。电池的边长为1个常规单位。
顶点位置:
x = x的索引+ x_offset / 254
y = y的索引+ y_offset / 254
z =高度/ 65535 * max_altitude(来自.mp文件)
"", :
0 1 2 *-*-* |\|\| ~ 33 *-*-* |\|\| ~ 66 *-*-* ~ ~ ~
, , 1616 . — 2 . , 90 .
. , , ID , MP .
: MP, , : ID , - .
ID — .
— :

- — , .
MOB
( ) , , : . — " ", .
, ( ).
:
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 |
浮点数 | 4 | 4 |
LeverStats | 12 | |
Null | 0 | |
Plot | 12 | 3 floats (vec3) |
Plot2DArray | | |
Quaternion | 16 | 4 floats (vec4) |
Record | >8 | |
Rectangle | | |
弦乐 | | |
StringArray | >4 | |
StringEncrypted | >4 | |
UnitStats | 180 | |
Unknown | | |
type_idtype_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 | 弦乐 | VSS_TITLE |
0x00001E09 | 弦乐 | VSS_COMMANDS |
0x00001E0A | Byte | VSS_ISSTART |
0x00001E0B | Record | VSS_LINK |
0x00001E0C | 弦乐 | VSS_GROUP |
0x00001E0D | Byte | VSS_IS_USE_GROUP |
0x00001E0E | Record | VSS_VARIABLE |
0x00001E0F | StringArray | VSS_BS_CHECK |
0x00001E10 | StringArray | VSS_BS_COMMANDS |
0x00001E11 | 弦乐 | VSS_CUSTOM_SRIPT |
0x0000A000 | Record | OBJECTDBFILE |
0x0000AA00 | Null | LIGHT_SECTION |
0x0000AA01 | Record | LIGHT |
0x0000AA02 | 浮点数 | LIGHT_RANGE |
0x0000AA03 | 弦乐 | LIGHT_NAME |
0x0000AA04 | Plot | LIGHT_POSITION |
0x0000AA05 | Dword | LIGHT_ID |
0x0000AA06 | Byte | LIGHT_SHADOW |
0x0000AA07 | Plot | LIGHT_COLOR |
0x0000AA08 | 弦乐 | LIGHT_COMMENTS |
0x0000ABD0 | Record | WORLD_SET |
0x0000ABD1 | Plot | WS_WIND_DIR |
0x0000ABD2 | 浮点数 | WS_WIND_STR |
0x0000ABD3 | 浮点数 | WS_TIME |
0x0000ABD4 | 浮点数 | WS_AMBIENT |
0x0000ABD5 | 浮点数 | WS_SUN_LIGHT |
0x0000B000 | Record | OBJECTSECTION |
0x0000B001 | Record | OBJECT |
0x0000B002 | Dword | NID |
0x0000B003 | Dword | OBJTYPE |
0x0000B004 | 弦乐 | OBJNAME |
0x0000B005 | Null | OBJINDEX |
0x0000B006 | 弦乐 | OBJTEMPLATE |
0x0000B007 | 弦乐 | OBJPRIMTXTR |
0x0000B008 | 弦乐 | OBJSECTXTR |
0x0000B009 | Plot | OBJPOSITION |
0x0000B00A | Quaternion | OBJROTATION |
0x0000B00B | Null | OBJTEXTURE |
0x0000B00C | Plot | OBJCOMPLECTION |
0x0000B00D | StringArray | OBJBODYPARTS |
0x0000B00E | 弦乐 | PARENTTEMPLATE |
0x0000B00F | 弦乐 | 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 | 弦乐 | 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 | 弦乐 | SOUND_NAME |
0x0000CC06 | Dword | SOUND_MIN |
0x0000CC07 | Dword | SOUND_MAX |
0x0000CC08 | 弦乐 | 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 | 弦乐 | PARTICL_COMMENTS |
0x0000DD05 | 弦乐 | PARTICL_NAME |
0x0000DD06 | Dword | PARTICL_TYPE |
0x0000DD07 | 浮点数 | PARTICL_SCALE |
0x0000E000 | Record | DIRICTORY |
0x0000E001 | Record | FOLDER |
0x0000E002 | 弦乐 | 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 | 弦乐 | SS_TEXT_OLD |
0xACCEECCB | StringEncrypted | SS_TEXT |
0xBBAB0000 | Record | MAGIC_TRAP |
0xBBAB0001 | Dword | MT_DIPLOMACY |
0xBBAB0002 | 弦乐 | 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 | 弦乐 | 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 | 浮点数 | 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 | 浮点数 | UNIT_LOGIC_WAIT |
0xBBBC000B | Byte | UNIT_LOGIC_ALARM_CONDITION |
0xBBBC000C | 浮点数 | 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 | 浮点数 | TORCH_STRENGHT |
0xBBBF0002 | Plot | TORCH_PTLINK |
0xBBBF0003 | 弦乐 | 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 .
, (, "" ).
- 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 — ,