将一个C语言二进制数据读取函数翻译成Python

4
我试图将下面的C函数翻译成Python,但失败了(见下面的C代码)。据我理解,它从from指向的内存位置开始,取四个1字节字符并将它们视为无符号的长整型,以便每个字符占用4个字节的空间,并进行一些位移操作以将它们排列成大端序的32位整数。然后将其用于检查文件的有效性算法。(来自Babel条约
static int32 read_alan_int(unsigned char *from)
{
 return ((unsigned long int) from[3])| ((unsigned long int)from[2] << 8) |
       ((unsigned long int) from[1]<<16)| ((unsigned long int)from[0] << 24);
}
/*
  The claim algorithm for Alan files is:
   * For Alan 3, check for the magic word
   * load the file length in blocks
   * check that the file length is correct
   * For alan 2, each word between byte address 24 and 81 is a
      word address within the file, so check that they're all within
      the file
   * Locate the checksum and verify that it is correct
*/
static int32 claim_story_file(void *story_file, int32 extent)
{
 unsigned char *sf = (unsigned char *) story_file;
 int32 bf, i, crc=0;
 if (extent < 160) return INVALID_STORY_FILE_RV;
 if (memcmp(sf,"ALAN",4))
 { /* Identify Alan 2.x */
 bf=read_alan_int(sf+4);
 if (bf > extent/4) return INVALID_STORY_FILE_RV;
 for (i=24;i<81;i+=4)
 if (read_alan_int(sf+i) > extent/4) return INVALID_STORY_FILE_RV;
 for (i=160;i<(bf*4);i++)
 crc+=sf[i];
 if (crc!=read_alan_int(sf+152)) return INVALID_STORY_FILE_RV;
 return VALID_STORY_FILE_RV;
 }
 else
 { /* Identify Alan 3 */
   bf=read_alan_int(sf+12);
   if (bf > (extent/4)) return INVALID_STORY_FILE_RV;
   for (i=184;i<(bf*4);i++)
    crc+=sf[i];
 if (crc!=read_alan_int(sf+176)) return INVALID_STORY_FILE_RV;

 }
 return INVALID_STORY_FILE_RV;
}

我正在尝试使用Python重新实现这个功能。为了实现read_alan_int函数,我认为导入struct并执行struct.unpack_from('>L', data, offset)应该可以。但是,在有效文件上,该函数始终返回24,即bf的值,这意味着for循环被跳过。

def read_alan_int(file_buffer, i):
    i0 = ord(file_buffer[i]) * (2 ** 24)
    i1 = ord(file_buffer[i + 1]) * (2 ** 16)
    i2 = ord(file_buffer[i + 2]) * (2 ** 8)
    i3 = ord(file_buffer[i + 3])
    return i0 + i1 + i2 + i3

def is_a(file_buffer):
    crc = 0
    if len(file_buffer) < 160:
        return False
    if file_buffer[0:4] == 'ALAN':
        # Identify Alan 2.x
        bf = read_alan_int(file_buffer, 4)
        if bf > len(file_buffer)/4:
            return False
        for i in range(24, 81, 4):
            if read_alan_int(file_buffer, i) > len(file_buffer)/4:
                return False
        for i in range(160, bf * 4):
            crc += ord(file_buffer[i])
        if crc != read_alan_int(file_buffer, 152):
            return False
        return True
    else:
        # Identify Alan 3.x
        #bf = read_long(file_buffer, 12, '>')
        bf = read_alan_int(file_buffer, 12)
        print bf
        if bf > len(file_buffer)/4:
            return False
        for i in range(184, bf * 4):
            crc += ord(file_buffer[i])
        if crc != read_alan_int(file_buffer, 176):
            return False
        return True
    return False


if __name__ == '__main__':
    import sys, struct
    data = open(sys.argv[1], 'rb').read()
    print is_a(data)

...但是该死的东西仍然返回24。不幸的是,我的C语言技能不存在,所以我很难让原始程序打印一些调试输出,以便我知道bf应该是什么。

我做错了什么?


好的,所以显然我正确地执行了read_alan_int。然而,对我失败的是检查前4个字符是否为“ALAN”。我所有的测试文件都未通过此测试。我已更改代码以删除此if/else语句,并利用早期返回,现在我的所有单元测试都通过了。因此,在实际层面上,我完成了。然而,我将保持这个问题开放,以解决新问题:我如何可能操纵位来获取前4个字符中的“ALAN”?

def is_a(file_buffer):
    crc = 0
    if len(file_buffer) < 160:
        return False
    #if file_buffer.startswith('ALAN'):
        # Identify Alan 2.x
    bf = read_long(file_buffer, 4)
    if bf > len(file_buffer)/4:
        return False
    for i in range(24, 81, 4):
        if read_long(file_buffer, i) > len(file_buffer)/4:
            return False
    for i in range(160, bf * 4):
        crc += ord(file_buffer[i])
    if crc == read_long(file_buffer, 152):
        return True
    # Identify Alan 3.x
    crc = 0
    bf = read_long(file_buffer, 12)
    if bf > len(file_buffer)/4:
        return False
    for i in range(184, bf * 4):
        crc += ord(file_buffer[i])
    if crc == read_long(file_buffer, 176):
        return True
    return False

啊,抱歉,我添加了Python文件的有效性检查代码。 - user626998
你还没有添加文件读取代码。 另外,向我们展示 print map(ord, file_buffer[12:16]) 的输出结果。 你能否提供 C 代码和/或示例文件? - John Machin
打印出print map(ord, file_buffer[12:16])的结果是[0, 0, 0, 24] - user626998
另外,不确定这是否重要,但如果您让 foo = lambda n: struct.unpack_from('>L', file_buffer, n),则 foo(12) * 2**24 == foo(13) * 2**16 == foo(14) * 2**8 == foo(15) - user626998
@Brandon:鉴于map(ord, file_buffer[12:16])的内容,你在real_alan_int()函数中得到的结果为24是正确的。 - Eric O. Lebigot
显示剩余2条评论
3个回答

1
啊,我想我明白了。请注意描述中所说的。
/*
  The claim algorithm for Alan files is:
   * For Alan 3, check for the magic word
   * load the file length in blocks
   * check that the file length is correct
   * For alan 2, each word between byte address 24 and 81 is a
      word address within the file, so check that they're all within
      the file
   * Locate the checksum and verify that it is correct
*/

我读到的是Alan 3中有一个神奇的单词,但在Alan 2中没有。然而,您的代码却相反,尽管C代码只假设ALAN存在于Alan 3文件中。

为什么?因为您不会说C语言,所以您猜测 - 自然而然!-- 如果sf的前四个字符与“ALAN”相等,那么memcmp会返回(类似于Python)True..但实际上并不是这样。如果内容相等,则 memcmp返回0,否则返回非零值。

这似乎就是它的工作方式:

>>> import urllib2
>>> 
>>> alan2 = urllib2.urlopen("http://ifarchive.plover.net/if-archive/games/competition2001/alan/chasing/chasing.acd").read(4)
>>> alan3 = urllib2.urlopen("http://mirror.ifarchive.org/if-archive/games/competition2006/alan/enterthedark/EnterTheDark.a3c").read(4)
>>> 
>>> alan2
'\x02\x08\x01\x00'
>>> alan3
'ALAN'

啊哈!太棒了,谢谢!是的,那正是问题所在。到目前为止我已经遇到过几次 memcmp,并且我一直需要主动提醒自己它的工作原理。 - user626998
因此,自然而然地,C的memcmp()等同于Python的cmp()。 :) - Eric O. Lebigot

0

你的Python版本看起来很好。

PS:我错过了DSM发现的"memcmp() catch",所以if memcmp(…)…的Python代码实际上应该是`if file_buffer[0:4] != 'ALAN'。

就我所看到的C代码和原始问题评论中提供的示例文件而言,示例文件确实无效;以下是值:

read_alan_int(sf+12) == 24  # 0, 0, 0, 24 in file sf, big endian
crc = 0
read_alan_int(sf+176) = 46  # 0, 0, 0, 46 in file sf, big endian

所以,crc != read_alan_int(sf+176),确实如此。

你确定样本文件是有效的吗?或者说原始帖子中缺少了计算的部分?


嗯,至少听到我没疯我就放心了。我在电脑上运行了这个类型的8个文件,并且它们所有的都在那个位置有24这个数字。有趣的是(好吧,并不好笑,这也许会让我的问题变得很愚蠢):如果我只使用if file_buffer [0:4] =='ALAN' ... else ...块中的第一个块,那么所有的文件都可以通过。所以这意味着由于某种原因它们没有正确地进入if语句,而是直接跳到了else。现在我正在找出我在这方面做错了什么。 - user626998
print repr(file_buffer[0:4]) 输出 \x02\x08\x01\x00,我认为这几乎肯定不会拼成 "ALAN"。 - user626998
@Brandon Invergo:ALAN确实是\x41\x4c\x41\x4e,所以执行else子句是正常的。你能否在文件上运行C代码并查看它是否说示例文件有效? - Eric O. Lebigot

0
假设1:您正在运行Windows,并且尚未以二进制模式打开文件。

@Brandon Invergo:基于你没有说明使用的操作系统,也没有展示所有代码,假设你使用的是Windows这一点是很合理的。正确的回应应该是“抱歉我没有包含所有相关信息”。你是其中一个downvote吗? - John Machin
@John Machin:点赞,因为您的建议可以说是对原问题的一个潜在解决方案。 - Eric O. Lebigot
@John Machin:你说得对,我最初提出的问题不够清晰,也没有提供足够的信息。当时我想你的回答更适合作为评论来获取更多的信息,但现在我同意,在原始情况下,是可以作为有效答案的。不幸的是,如果你不对回答进行编辑,我只能保留我的负评(系统不允许我取消)。对此我感到很抱歉... - user626998

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接