将C语言二进制数据读取函数翻译为Python
(我为了更清楚地表达,稍微修改了问题,并根据EOL的回答调整了一下内容)我正在尝试把下面这个C语言的函数转换成Python,但一直失败(见下面的C代码)。我理解的是,它从from
指向的内存位置开始,取四个1字节的字符,把它们当作无符号长整型来处理,这样每个字符就占用4个字节的空间,然后通过位移操作把它们排列成一个大端32位整数。这个整数接着用于检查文件的有效性。(来自巴别塔条约)
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
的实现上是正确的。然而,我遇到的问题是检查前四个字符是否是“ALAN”。我所有的测试文件都没有通过这个测试。我已经修改了代码,去掉了这个if/else语句,而是利用了提前返回的方式,现在我所有的单元测试都通过了。所以,从实际的角度来看,我已经完成了。然而,我会继续保留这个问题,以解决新的问题:我该如何处理这些位,以从前四个字符中提取出“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
3 个回答
假设1:你正在使用Windows系统,并且你没有以二进制模式打开你的文件。
你的Python版本看起来没问题。
补充说明: 我之前没注意到DSM提到的"memcmp()
捕获",所以Python代码中的if memcmp(…)…
其实应该是`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)
,确实是这样。
你确定这个示例文件是有效的吗?还是说原帖中缺少了计算crc
的一部分内容??
啊,我想我明白了。注意描述中提到
/*
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'