"""
バイナリファイルの読み込みと検索機能を提供するモジュール。
このモジュールは、バイナリファイルから特定のオフセットとサイズでデータを読み込んだり、
特定のデータ型(整数、浮動小数点数、ASCII文字列など)の値をファイル内から検索したりするための
ユーティリティ関数群を提供します。エンディアンの指定も可能です。
関連リンク: :doc:`binparser_usage`
"""
import sys
import struct
import re
__all__ = ['load_bin_file', 'get_data', 'search_data']
type_map = {
'float32': ('f', 4),
'float64': ('d', 8),
'uint16': ('H', 2),
'int16': ('h', 2),
'uint32': ('I', 4),
'int32': ('i', 4),
'uint64': ('Q', 8),
'int64': ('q', 8),
}
def append_data(metadata, varname, vartype, value, eps):
"""
メタデータリストに新しいデータエントリを追加します。
指定された変数名、型、値、許容誤差を辞書としてリストに追加し、
更新されたリストを返します。
:param metadata: 既存のメタデータリスト。
:type metadata: list
:param varname: 変数名。
:type varname: str
:param vartype: 変数のデータ型。
:type vartype: str
:param value: 変数の値。
:type value: any
:param eps: 許容誤差。
:type eps: float
:returns: 更新されたメタデータリスト。
:rtype: list
"""
metadata.append({
'varname': varname,
'vartype': vartype,
'value' : value,
'eps' : eps
})
return metadata
def guess_vartype(str_val):
"""
与えられた文字列のデータ型を推測します。
文字列が整数、浮動小数点数として変換可能かを試し、
それぞれ 'int', 'float' を返します。どちらでもない場合は 'str' を返します。
:param str_val: 型を推測する文字列。
:type str_val: str
:returns: 推測されたデータ型 ('int', 'float', または 'str')。
:rtype: str
"""
try:
int(str_val)
return 'int'
except:
pass
try:
float(str_val)
return 'float'
except:
pass
return 'str'
def find_ascii_strings(in_data, istart, ilast, min_length=4):
"""
バイナリデータ内のASCII文字列を検索します。
指定された範囲内で、最小長以上のASCII印字可能文字のシーケンスを正規表現で検索し、
そのオフセットとデコードされた文字列のリストを返します。
:param in_data: 検索対象のバイナリデータ。
:type in_data: bytes
:param istart: 検索開始オフセット。
:type istart: int
:param ilast: 検索終了オフセット。
:type ilast: int
:param min_length: 検索するASCII文字列の最小長。デフォルトは4。
:type min_length: int
:returns: 発見されたASCII文字列のオフセットと値のタプルのリスト。
:rtype: list[tuple[int, str]]
"""
pattern = rb'[\x20-\x7E]{%d,}' % min_length # ASCII printable characters
results = []
for match in re.finditer(pattern, in_data[istart:ilast+1]):
offset = match.start()
value = match.group().decode('ascii', errors='ignore')
results.append((offset, value))
return results
[ドキュメント]
def load_bin_file(file_path):
"""
指定されたパスのバイナリファイルを読み込み、その内容を返します。
ファイル全体をバイト列として読み込みます。
:param file_path: 読み込むバイナリファイルのパス。
:type file_path: str
:returns: ファイルの内容を表すバイト列。
:rtype: bytes
"""
with open(file_path, 'rb') as f:
return f.read()
def extract_ascii_prefix(bin_data, offset, size):
"""
バイナリデータから指定されたオフセットとサイズでASCII文字列のプレフィックスを抽出します。
指定されたチャンクの先頭からASCII印字可能文字が続く限り抽出し、
非ASCII文字に遭遇した時点で終了します。
:param bin_data: 抽出元のバイナリデータ。
:type bin_data: bytes
:param offset: 抽出開始オフセット。
:type offset: int
:param size: 抽出する最大サイズ。
:type size: int
:returns: 抽出されたASCII文字列。
:rtype: str
"""
chunk = bin_data[offset:offset+size]
result = bytearray()
for b in chunk:
if 0x20 <= b <= 0x7E:
result.append(b)
else:
break # 非ASCII文字が出たら終了
return result.decode('ascii', errors='ignore')
[ドキュメント]
def get_data(bin_data, offset, size, vartype, endian='little', varname = None):
"""
バイナリデータから指定されたオフセット、サイズ、型でデータを読み込みます。
指定されたエンディアン(リトルまたはビッグ)とデータ型(float32、uint16など、または'str')に従って、
バイト列をPythonの対応する型に変換します。
:param bin_data: 読み込み元のバイナリデータ。
:type bin_data: bytes
:param offset: 読み込み開始オフセット。
:type offset: int
:param size: 読み込むデータのバイトサイズ。
:type size: int
:param vartype: 読み込むデータの型 (例: 'float32', 'uint16', 'str')。
:type vartype: str
:param endian: エンディアンの指定 ('little' または 'big')。デフォルトは 'little'。
:type endian: str
:param varname: 読み込むデータの変数名 (エラーメッセージ用)。デフォルトは None。
:type varname: str or None
:returns: 読み込まれたデータ。型は `vartype` に依存。読み込み失敗時は None。
:rtype: int or float or str or None
:raises ValueError: 未対応のエンディアンまたはデータ型が指定された場合、あるいはサイズが不正な場合。
"""
if endian == 'little':
endian_prefix = '<'
elif endian == 'big':
endian_prefix = '>'
else:
raise ValueError(f"未対応のエンディアン指定: {endian}")
if vartype == 'str':
return extract_ascii_prefix(bin_data, offset, size)
if vartype in type_map:
fmt, expected_size = type_map[vartype]
if size != expected_size:
raise ValueError(f"サイズ {size} は型 {vartype} に対して不正です(期待値: {expected_size})")
try:
ret = struct.unpack_from(endian_prefix + fmt, bin_data, offset)[0]
return ret
except:
print(f"Error in binparser.get_data(): Failed to read [{varname}] (type {vartype}) from offset={offset} with size={size}")
return None
raise ValueError(f"未対応の型: {vartype}")
[ドキュメント]
def search_data(bin_data, itop, ilast, size, vartype, endian, value, tolerance=1e-6, varname = 'not given'):
"""
バイナリデータ内で指定された値のデータを検索します。
指定された範囲、サイズ、データ型、エンディアンに従って、特定の値を検索し、
一致するデータのオフセットのリストを返します。
浮動小数点数の場合は `tolerance` を考慮した比較を行います。
:param bin_data: 検索対象のバイナリデータ。
:type bin_data: bytes
:param itop: 検索開始オフセット。
:type itop: int
:param ilast: 検索終了オフセット。
:type ilast: int
:param size: 検索するデータのバイトサイズ。
:type size: int
:param vartype: 検索するデータの型 (例: 'float32', 'int16', 'str')。
:type vartype: str
:param endian: エンディアンの指定 ('little' または 'big')。
:type endian: str
:param value: 検索する値。
:type value: int or float or str
:param tolerance: 浮動小数点数比較における許容誤差。デフォルトは1e-6。
:type tolerance: float
:param varname: 検索対象の変数名 (デバッグ用)。デフォルトは 'not given'。
:type varname: str
:returns: 一致するデータが発見されたオフセットのリスト。
:rtype: list[int]
:raises ValueError: 未対応のエンディアンまたはデータ型が指定された場合、あるいはサイズが不正な場合。
"""
# print("**search:", varname, value, vartype, size, tolerance)
if endian == 'little':
endian_prefix = '<'
elif endian == 'big':
endian_prefix = '>'
else:
raise ValueError(f"未対応のエンディアン指定: {endian}")
matches = []
if vartype == 'str':
# 文字列検索はバイト列全体をデコードしてから部分文字列検索を行う
# これは効率的ではない可能性があるが、既存ロジックを維持
# また、sizeは最大長として扱われるが、現在の実装ではあまり考慮されていない
for offset in range(itop, ilast - size + 1):
chunk = bin_data[offset:offset+size]
try:
# UTF-8デコードを試みるが、エラーは無視
decoded = chunk.decode('utf-8', errors='ignore')
if value in decoded:
matches.append(offset)
except UnicodeDecodeError:
# デコードできない場合はスキップ
continue
return matches
if vartype not in type_map:
raise ValueError(f"未対応の型: {vartype}")
fmt, expected_size = type_map[vartype]
if size != expected_size:
raise ValueError(f"サイズ {size} は型 {vartype} に対して不正です(期待値: {expected_size})")
for offset in range(itop, ilast - size + 1):
try:
val = struct.unpack_from(endian_prefix + fmt, bin_data, offset)[0]
if isinstance(val, float):
if abs(val - value) <= tolerance:
matches.append(offset)
else:
if val == value:
matches.append(offset)
except struct.error:
# 構造体のアンパックに失敗した場合(例:データ範囲外)はスキップ
continue
return matches
def main():
"""
スクリプトの実行エントリポイントです。
コマンドライン引数からバイナリファイルパスとエンディアンを取得し、
`search_data` 関数を使用してサンプル値をファイル内で検索し、結果を表示します。
"""
bin_file = 'test.raw'
endian = 'little'
if len(sys.argv) > 1: bin_file = sys.argv[1]
if len(sys.argv) > 2: endian = sys.argv[2]
bin_data = load_bin_file(bin_file)
ilast = len(bin_data)
eps = 0.002
for val in [8.0, 120.0, 0.02, 514.902, 552.835]:
offsets = search_data(bin_data, 0, ilast, 4, 'float32', endian, val, tolerance=eps)
if not offsets:
print(f"No data found for {val}")
else:
for offset in offsets:
print(f"offset {offset:08x} for {val}")
for val in [5601]:
offsets = search_data(bin_data, 0, ilast, 2, 'int16', endian, val)
if not offsets:
print(f"No data found for {val}")
else:
for offset in offsets:
print(f"offset {offset:08x} for {val}")
if __name__ == '__main__':
main()