解析帝国时代游戏记录文件(.mgx)

parsing age of empires game record files(.mgx)

我是帝国时代II(AoE)这个过时游戏的粉丝。我想使用 Python.

编写 AoE 游戏记录(.mgx 文件)的解析器

我在 GitHub 上做了一些搜索,发现了一些关于这个的项目,最有用的是 aoc-mgx-format which provide some details of .mgx game record files

问题是:

根据参考,.mgx 文件的结构如下:

| header_len(4byte int) | next_pos(4byte int) | header_data | ... ... |

十六进制数据在 mgx 格式中的字节顺序是 little endian.

header_len存储Header部分的数据长度(header_len + next_post + header_data)

header_data 存储我需要的有用信息,但它是用 zlib

压缩的

我尝试使用 zlib 模块解压 header_data 中的数据,如下所示:

import struct
import zlib

with open('test.mgx', "rb") as fp:
    # read the header_len bytes and covert it to a int reprents length of Header part
    header_len = struct.unpack("<i", fp.read(4))[0]

    # read next_pos (this is not important for me)
    next_pos = struct.unpack("<i", fp.read(4))[0]

    # then I can get data length of header_data part(compressed with zlib)
    header_data_len = header_len - 8

    compressed_data = fp.read(header_data_len)[::-1] # need to be reversed because byte order is little endian?

    try:
        zlib.decompress(compressed_data)
        print "can be decompressed!"
    except zlib.error as e:
        print e.message

但我在 运行 程序之后得到了这个:

Error -3 while decompressing data: incorrect header check

PS:示例 .mgx 文件可在此处找到:https://github.com/stefan-kolb/aoc-mgx-format/tree/master/parser/recs

你的第一个问题是你不应该反转数据;只需摆脱 [::-1].

但是如果你这样做,你会得到一个不同的错误 -3,而不是那个错误 -3,通常是关于未知的压缩方法。

问题是这是 headerless zlib 数据,很像 gzip 使用的数据。理论上,这意味着有关压缩方法、window、开始字典等的信息必须在文件的其他地方提供(在 gzip 的情况下,通过 gzip header 中的信息)。但在实践中,每个人都使用最大 window 大小的 deflate 并且没有开始指令,所以如果我在每个字节都计算在内的日子里为游戏设计一种紧凑的格式,我只会对它们进行硬编码。 (在现代,正是在 RFC 中将其标准化为 "DEFLATE Compressed Data Format",但大多数 90 年代的 PC 游戏在设计上并没有遵循 RFC...)

所以:

>>> uncompressed_data = zlib.decompress(compressed_data, -zlib.MAX_WBITS)
>>> uncompressed_data[:8] # version
b'VER 9.8\x00'
>>> uncompressed_data[8:12] # unknown_const
b'\xf6(<A'

所以,它不仅解压了,看起来像一个版本而且……好吧,我想任何东西看起来都像一个未知常量,但它在规范中是相同的未知常量,所以我认为我们很好。

正如 decompress 文档所解释的那样,MAX_WBITS 是 default/most 常见的 window 大小(也是通常称为 "zlib deflate" 的唯一大小与 "zlib" 相反),传递负值意味着 header 被抑制;我们可以保留默认值的其他参数。

另见 this answer, the Advanced Functions section in the zlib docs, and RFC 1951。 (感谢 OP 找到链接。)

旧但这是我所做的示例:

class GameRecordParser:

def __init__(self, filename):
    self.filename = filename
    f = open(filename, 'rb')

    # Get header size
    header_size = struct.unpack('<I', f.read(4))[0]
    sub = struct.unpack('<I', f.read(4))[0]
    if sub != 0 and sub < os.stat(filename).st_size:
        f.seek(4)
        self.header_start = 4
    else:
        self.header_start = 8

    # Get and decompress header
    header = f.read(header_size - self.header_start)
    self.header_data = zlib.decompress(header, -zlib.MAX_WBITS)

    # Get body
    self.body = f.read()
    f.close()

    # Get players data
    sep = b'\x04\x00\x00\x00Gaia'
    pos = self.header_data.find(sep) + len(sep)
    players = []
    for k in range(0, 8):
        id = struct.unpack('<I', self.header_data[pos:pos+4])[0]
        pos += 4
        type = struct.unpack('<I', self.header_data[pos:pos+4])[0]
        pos += 4
        name_size = struct.unpack('<I', self.header_data[pos:pos+4])[0]
        pos += 4
        name = self.header_data[pos:pos+name_size].decode('utf-8')
        pos += name_size
        if id < 9:
            players.append(Player(id, type, name))

希望对以后的程序员有帮助:)

顺便说一句,我正计划编写这样一个库。