“我不会放弃”或如何安排“诅咒之地”游戏的资源


你还记得很多俄罗斯游戏吗? 定性的? 难忘? 是的,他们是。 如果您年满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。
数据结构对于具有对文件的随机访问权的存档来说非常典型:有一些表用于存储有关内部文件,名称表和文件内容的信息。
目录结构直接包含在名称中。


值得注意两个非常有趣的事实:


  1. 该归档文件经过优化,可使用封闭式哈希将文件信息加载到链接列表中。
  2. 您可以存储一次文件的内容,但是可以使用不同的名称来引用它。 据我所知,这个事实被用在了风扇的重新包装中,因此游戏的尺寸大大减小了。 在原始发行版中,未使用档案优化。


结构说明
 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存档添加父扩展名。


您还可以将所有收到的文件分为四个组:


  1. 贴图
  2. 资料库
  3. 型号
  4. 关卡文件。

让我们从简单的-纹理开始。


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 00ARGB4
44 58 54 31Dxt1
44 58 54 33Dxt3
50 4E 54 33PNT3-RLE压缩ARGB8
50 56 00 00R5G5B5
51 55 00 00A1R5G5B5
88 88 00 00ARGB8

关于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; //      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; 

表字段的类型由索引从表的格式字符串中获取,实际类型由获得的值确定。


栏位类型
名称说明
小号
4b int
ü4b未签名
˚F4B浮球
X位字节
˚F浮点数组
整数数组
布尔
b布尔数组
^ h未知的十六进制字节
Ť时间
0没有说明
1个0FII
2SUFF
3FFFF
40SISS
50SISS00000U

基础说明

项目(.idb)


桌子结构
用料SSSIFFFIFIFfIX
武器装备SSISIIIFFFFIFIXB00000IHFFFfHHFF
护甲SSISIIIFFFFIFIXB00000ffBiHH
快速物品SSISIIIFFFFIFIXB00000IIFFSbH
任务物品SSISIIIFFFFIFIXB00000Is
销售物品SSISIIIFFFFIFIXB00000IHI

开关(.ldb)


桌子结构
开关原型SfIFTSSS

技能与技巧(.pdb)


桌子结构
能力SSI0000000s
技能专长SSI0000000SSIIIFFFIIIIBI

足迹(prints.db)


桌子结构
血迹0S11
火焰痕迹0S110000001
足迹0S11

咒语(.sdb)


桌子结构
样机SSSFIFIFFFFIIIIUSSIIbIXFFFFF
修饰符SSFIFFISX
模式0SssSX
装甲模板0SssSX
武器模式0SssSX

生物(.udb)


桌子结构
零件损坏SffUU
种族SUFFUUFfFUUf222222000000000000SssFSsfUUfUUIUSBFUUUU
怪物原型SSIUIFFFSFFFFFFFFFUFFFFFFff33sfssSFFFFFUFUSF
NPCSUFFFFbbssssFUB

喊声(acks.db)


桌子结构
答案0S0000000044444444444444444444445444444444444
尖叫声0S0000000044444
其他0S0000000044

任务(.qdb)


桌子结构
任务SFIISIIs
简报SFFsSsssssI

有趣的是: 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]): # 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] 

这很有趣:三线性模型插值用于为某些对象设置动画,例如,打开石门和箱子。

现在是时候看看模型本身的各个部分了。



也许这种反弹是无法理解的。 您可以在网上找到他的描述和搅拌机的插件,但是即使有了它们,意识也不会马上消失。 看一下:



结构说明
 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
Byte1个1
Diplomacy409632x32 2
Dword44
浮点数44
LeverStats12
Null0
Plot123 floats (vec3)
Plot2DArray
Quaternion164 floats (vec4)
Record>8
Rectangle
弦乐
StringArray>4
StringEncrypted>4
UnitStats180
Unknown

type_id
type_id
0x00000000RecordROOT
0x00001E00RecordVSS_SECTION
0x00001E01RecordVSS_TRIGER
0x00001E02RecordVSS_CHECK
0x00001E03RecordVSS_PATH
0x00001E04DwordVSS_ID
0x00001E05RectangleVSS_RECT
0x00001E06DwordVSS_SRC_ID
0x00001E07DwordVSS_DST_ID
0x00001E08弦乐VSS_TITLE
0x00001E09弦乐VSS_COMMANDS
0x00001E0AByteVSS_ISSTART
0x00001E0BRecordVSS_LINK
0x00001E0C弦乐VSS_GROUP
0x00001E0DByteVSS_IS_USE_GROUP
0x00001E0ERecordVSS_VARIABLE
0x00001E0FStringArrayVSS_BS_CHECK
0x00001E10StringArrayVSS_BS_COMMANDS
0x00001E11弦乐VSS_CUSTOM_SRIPT
0x0000A000RecordOBJECTDBFILE
0x0000AA00NullLIGHT_SECTION
0x0000AA01RecordLIGHT
0x0000AA02浮点数LIGHT_RANGE
0x0000AA03弦乐LIGHT_NAME
0x0000AA04PlotLIGHT_POSITION
0x0000AA05DwordLIGHT_ID
0x0000AA06ByteLIGHT_SHADOW
0x0000AA07PlotLIGHT_COLOR
0x0000AA08弦乐LIGHT_COMMENTS
0x0000ABD0RecordWORLD_SET
0x0000ABD1PlotWS_WIND_DIR
0x0000ABD2浮点数WS_WIND_STR
0x0000ABD3浮点数WS_TIME
0x0000ABD4浮点数WS_AMBIENT
0x0000ABD5浮点数WS_SUN_LIGHT
0x0000B000RecordOBJECTSECTION
0x0000B001RecordOBJECT
0x0000B002DwordNID
0x0000B003DwordOBJTYPE
0x0000B004弦乐OBJNAME
0x0000B005NullOBJINDEX
0x0000B006弦乐OBJTEMPLATE
0x0000B007弦乐OBJPRIMTXTR
0x0000B008弦乐OBJSECTXTR
0x0000B009PlotOBJPOSITION
0x0000B00AQuaternionOBJROTATION
0x0000B00BNullOBJTEXTURE
0x0000B00CPlotOBJCOMPLECTION
0x0000B00DStringArrayOBJBODYPARTS
0x0000B00E弦乐PARENTTEMPLATE
0x0000B00F弦乐OBJCOMMENTS
0x0000B010NullOBJ_DEF_LOGIC
0x0000B011ByteOBJ_PLAYER
0x0000B012DwordOBJ_PARENT_ID
0x0000B013ByteOBJ_USE_IN_SCRIPT
0x0000B014ByteOBJ_IS_SHADOW
0x0000B015NullOBJ_R
0x0000B016弦乐OBJ_QUEST_INFO
0x0000C000NullSC_OBJECTDBFILE
0x0000CC00NullSOUND_SECTION
0x0000CC01RecordSOUND
0x0000CC02DwordSOUND_ID
0x0000CC03PlotSOUND_POSITION
0x0000CC04DwordSOUND_RANGE
0x0000CC05弦乐SOUND_NAME
0x0000CC06DwordSOUND_MIN
0x0000CC07DwordSOUND_MAX
0x0000CC08弦乐SOUND_COMMENTS
0x0000CC09NullSOUND_VOLUME
0x0000CC0AStringArraySOUND_RESNAME
0x0000CC0BDwordSOUND_RANGE2
0x0000CC0DByteSOUND_AMBIENT
0x0000CC0EByteSOUND_IS_MUSIC
0x0000D000NullPR_OBJECTDBFILE
0x0000DD00NullPARTICL_SECTION
0x0000DD01RecordPARTICL
0x0000DD02DwordPARTICL_ID
0x0000DD03PlotPARTICL_POSITION
0x0000DD04弦乐PARTICL_COMMENTS
0x0000DD05弦乐PARTICL_NAME
0x0000DD06DwordPARTICL_TYPE
0x0000DD07浮点数PARTICL_SCALE
0x0000E000RecordDIRICTORY
0x0000E001RecordFOLDER
0x0000E002弦乐DIR_NAME
0x0000E003DwordDIR_NINST
0x0000E004DwordDIR_PARENT_FOLDER
0x0000E005ByteDIR_TYPE
0x0000F000RecordDIRICTORY_ELEMENTS
0x0000FF00RecordSEC_RANGE
0x0000FF01RecordMAIN_RANGE
0x0000FF02RecordRANGE
0x0000FF05DwordMIN_ID
0x0000FF06DwordMAX_ID
0x31415926AiGraphAIGRAPH
0xACCEECCA弦乐SS_TEXT_OLD
0xACCEECCBStringEncryptedSS_TEXT
0xBBAB0000RecordMAGIC_TRAP
0xBBAB0001DwordMT_DIPLOMACY
0xBBAB0002弦乐MT_SPELL
0xBBAB0003AreaArrayMT_AREAS
0xBBAB0004Plot2DArrayMT_TARGETS
0xBBAB0005DwordMT_CAST_INTERVAL
0xBBAC0000RecordLEVER
0xBBAC0001NullLEVER_SCIENCE_STATS
0xBBAC0002ByteLEVER_CUR_STATE
0xBBAC0003ByteLEVER_TOTAL_STATE
0xBBAC0004ByteLEVER_IS_CYCLED
0xBBAC0005ByteLEVER_CAST_ONCE
0xBBAC0006LeverStatsLEVER_SCIENCE_STATS_NEW
0xBBAC0007ByteLEVER_IS_DOOR
0xBBAC0008ByteLEVER_RECALC_GRAPH
0xBBBB0000RecordUNIT
0xBBBB0001NullUNIT_R
0xBBBB0002弦乐UNIT_PROTOTYPE
0xBBBB0003NullUNIT_ITEMS
0xBBBB0004UnitStatsUNIT_STATS
0xBBBB0005StringArrayUNIT_QUEST_ITEMS
0xBBBB0006StringArrayUNIT_QUICK_ITEMS
0xBBBB0007StringArrayUNIT_SPELLS
0xBBBB0008StringArrayUNIT_WEAPONS
0xBBBB0009StringArrayUNIT_ARMORS
0xBBBB000AByteUNIT_NEED_IMPORT
0xBBBC0000RecordUNIT_LOGIC
0xBBBC0001NullUNIT_LOGIC_AGRESSIV
0xBBBC0002ByteUNIT_LOGIC_CYCLIC
0xBBBC0003DwordUNIT_LOGIC_MODEL
0xBBBC0004浮点数UNIT_LOGIC_GUARD_R
0xBBBC0005PlotUNIT_LOGIC_GUARD_PT
0xBBBC0006ByteUNIT_LOGIC_NALARM
0xBBBC0007ByteUNIT_LOGIC_USE
0xBBBC0008NullUNIT_LOGIC_REVENGE
0xBBBC0009NullUNIT_LOGIC_FEAR
0xBBBC000A浮点数UNIT_LOGIC_WAIT
0xBBBC000BByteUNIT_LOGIC_ALARM_CONDITION
0xBBBC000C浮点数UNIT_LOGIC_HELP
0xBBBC000DByteUNIT_LOGIC_ALWAYS_ACTIVE
0xBBBC000EByteUNIT_LOGIC_AGRESSION_MODE
0xBBBD0000RecordGUARD_PT
0xBBBD0001PlotGUARD_PT_POSITION
0xBBBD0002NullGUARD_PT_ACTION
0xBBBE0000RecordACTION_PT
0xBBBE0001PlotACTION_PT_LOOK_PT
0xBBBE0002DwordACTION_PT_WAIT_SEG
0xBBBE0003DwordACTION_PT_TURN_SPEED
0xBBBE0004ByteACTION_PT_FLAGS
0xBBBF0000RecordTORCH
0xBBBF0001浮点数TORCH_STRENGHT
0xBBBF0002PlotTORCH_PTLINK
0xBBBF0003弦乐TORCH_SOUND
0xDDDDDDD1RecordDIPLOMATION
0xDDDDDDD2DiplomacyDIPLOMATION_FOF
0xDDDDDDD3StringArrayDIPLOMATION_PL_NAMES
0xFFFFFFFFUnknownUNKNOWN

— , , 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/zh-CN434802/


All Articles