我的公司使用遗留文件格式的Electromiography数据,不再生产。但是,有一些兴趣保持复古兼容性,所以我正在研究为该文件格式编写阅读器的可能性。
通过分析用Delphi编写的非常复杂的前源代码,文件读取器/写入器使用ZLIB,并且在HexEditor内部看起来像二进制ASCII中的文件头(像“Player”,“Analyzer”这样的字段很容易可读),后跟包含原始数据的压缩字符串。
我的疑问是:我应该如何进行识别:
来自维基百科:
zlib压缩数据通常使用gzip或zlib编写 包装。包装器通过添加a来封装原始DEFLATE数据 标题和预告片。这提供了流识别和错误 检测
这是相关的吗?
我很乐意发布更多信息,但我不知道什么是最相关的。
感谢任何提示。
编辑:我有工作应用程序,可以用它来记录任何时间长度的实际数据,如果需要,文件甚至小于1kB。
一些示例文件:
新创建的,没有数据流:https://dl.dropbox.com/u/4849855/Mio_File/HeltonEmpty.mio
保存非常短(1秒?)的数据流后,与上面相同:https://dl.dropbox.com/u/4849855/Mio_File/HeltonFilled.mio
另一个患者,来自名为“manco”而不是“Helton”的患者,具有更短的流(非常适合六角观察):https://dl.dropbox.com/u/4849855/Mio_File/manco_short.mio
说明:每个文件应该是患者(一个人)的文件。在这些文件中,保存一个或多个考试,每个考试由一个或多个时间序列组成。提供的文件只包含一个考试,其中包含一个数据系列。
答案 0 :(得分:9)
首先,为什么不扫描所有有效zip流的文件(这对于小文件来说已经足够了,并且可以找出格式):
import zlib
from glob import glob
def zipstreams(filename):
"""Return all zip streams and their positions in file."""
with open(filename, 'rb') as fh:
data = fh.read()
i = 0
while i < len(data):
try:
zo = zlib.decompressobj()
yield i, zo.decompress(data[i:])
i += len(data[i:]) - len(zo.unused_data)
except zlib.error:
i += 1
for filename in glob('*.mio'):
print(filename)
for i, data in zipstreams(filename):
print (i, len(data))
看起来数据流包含little-endian双精度浮点数据:
import numpy
from matplotlib import pyplot
for filename in glob('*.mio'):
for i, data in zipstreams(filename):
if data:
a = numpy.fromstring(data, '<f8')
pyplot.plot(a[1:])
pyplot.title(filename + ' - %i' % i)
pyplot.show()
答案 1 :(得分:8)
zlib是一个使用。压缩的数据的瘦包装器 DEFLATE算法并在RFC1950中定义:
A zlib stream has the following structure: 0 1 +---+---+ |CMF|FLG| (more-->) +---+---+ (if FLG.FDICT set) 0 1 2 3 +---+---+---+---+ | DICTID | (more-->) +---+---+---+---+ +=====================+---+---+---+---+ |...compressed data...| ADLER32 | +=====================+---+---+---+---+
所以它至少增加了两个,可能是6个字节,4个字节用 原始DEFLATE压缩数据后的ADLER32校验和。
第一个字节包含 CMF (压缩方法和标志),它是分开的 进入 CM (压缩方法)(前4位)和 CINFO (压缩信息)(最后 4位)。
从这一点可以很清楚,遗憾的是已经是前两个字节了 根据压缩方法和方法,zlib流的变化很大 设置已被使用。
幸运的是,我偶然发现了ADLER32的作者马克阿德勒的一篇文章 算法,他lists the most common and less common combinations of those two starting bytes。
有了这个,让我们看一下如何使用Python来检查zlib:
>>> import zlib
>>> msg = 'foo'
>>> [hex(ord(b)) for b in zlib.compress(msg)]
['0x78', '0x9c', '0x4b', '0xcb', '0xcf', '0x7', '0x0', '0x2', '0x82', '0x1', '0x45']
因此,Python的zlib
模块(使用默认选项)创建的zlib数据以
的 78 9c
即可。我们将使用它来创建一个编写自定义文件格式的脚本
包括序言,一些zlib压缩数据和页脚。
然后,我们编写第二个脚本,扫描文件中的两个字节模式, 开始解压缩后面的所有内容作为zlib流并计算出来 流结束并且页脚开始的地方。
<强> create.py 强>
import zlib
msg = 'foo'
filename = 'foo.compressed'
compressed_msg = zlib.compress(msg)
data = 'HEADER' + compressed_msg + 'FOOTER'
with open(filename, 'wb') as outfile:
outfile.write(data)
在这里我们采用msg
,用zlib压缩它,然后用标题包围它
在我们将其写入文件之前的页脚。
在此示例中,页眉和页脚的长度是固定的,但它们当然可以 有任意的,未知的长度。
现在尝试在这样的文件中查找zlib流的脚本。因为
这个例子我们确切地知道我只使用一个标记,但是
很明显,列表ZLIB_MARKERS
可以填充所有标记
上面提到的帖子。
<强> ident.py 强>
import zlib
ZLIB_MARKERS = ['\x78\x9c']
filename = 'foo.compressed'
infile = open(filename, 'r')
data = infile.read()
pos = 0
found = False
while not found:
window = data[pos:pos+2]
for marker in ZLIB_MARKERS:
if window == marker:
found = True
start = pos
print "Start of zlib stream found at byte %s" % pos
break
if pos == len(data):
break
pos += 1
if found:
header = data[:start]
rest_of_data = data[start:]
decomp_obj = zlib.decompressobj()
uncompressed_msg = decomp_obj.decompress(rest_of_data)
footer = decomp_obj.unused_data
print "Header: %s" % header
print "Message: %s" % uncompressed_msg
print "Footer: %s" % footer
if not found:
print "Sorry, no zlib streams starting with any of the markers found."
这个想法是这样的:
从文件的开头开始,创建一个双字节搜索 窗口。
以一个字节为增量向前移动搜索窗口。
对于每个窗口,检查它是否与我们的两个字节标记中的任何一个匹配 定义。
如果找到匹配项,请记录起始位置,停止搜索和 尝试解压缩后面的所有内容。
现在,找到流的结尾并不像寻找两个标记那样微不足道 字节。 zlib流既不是由固定的字节序列终止也不是 它们的长度在任何标题字段中指示。相反,它终止了 一个四字节的ADLER32校验和,必须与到目前为止的数据匹配。
它的工作方式是内部C函数inflate()
不断保持
尝试在读取流时解压缩流,如果遇到流
匹配校验和,向其调用者发出信号,指示其余部分
数据不再是zlib流的一部分。
在Python中,使用解压缩对象而不是简单地暴露这种行为
致电zlib.decompress()
。在decompress(string)
对象上调用Decompress
将解压缩string
中的zlib流并返回作为流的一部分的解压缩数据。流后面的所有内容都将存储在unused_data
中,并且可以存储在Start of zlib stream found at byte 6
Header: HEADER
Message: foo
Footer: FOOTER
中
之后检索。
这应该在使用第一个创建的文件上产生以下输出 脚本:
{{1}}
可以轻松修改示例以将未压缩的消息写入文件 而不是打印它。然后你可以进一步分析以前的zlib 压缩数据,并尝试识别元数据中的已知字段 你分开的页眉和页脚。