#!/usr/bin/env python3
"""
バイナリファイル探索ヘルパー。
このモジュールは、未知のバイナリファイルを探索し、解析するための様々なユーティリティを提供します。
既知の数値や文字列の検索、オフセットの異なる型での検査、表示可能なASCII文字列の抽出、
および候補オフセット周辺のバイト列ダンプなどの機能が含まれています。
必要に応じて、アラインメントを考慮したステップでの検索も可能です。
関連リンク: :doc:`bin_parser_to_be_tested_usage`
例:
--------
# リトルエンディアンのfloat32値 (120.0) を、アライメントを考慮した典型的なスキャンで検索
python bin_parser_to_be_tested.py search test.raw --type float32 --value 120.0 --endian little --step 4 --tolerance 1e-3
# 上記と同じだが、全ブルートフォーススキャン
python bin_parser_to_be_tested.py search test.raw --type float32 --value 120.0 --step 1
# 生のバイト列からテキストフラグメントを直接検索
python bin_parser_to_be_tested.py search test.raw --type str --value DEVICE
# 表示可能なASCII文字列をリストアップ
python bin_parser_to_be_tested.py strings test.raw --min-length 4
# 指定されたオフセットで複数の候補型として値を読み取る
python bin_parser_to_be_tested.py inspect test.raw --offset 0x120 --types float32,int16,uint16,str --sizes 4,2,2,32
# 配列を読み取る
python bin_parser_to_be_tested.py array test.raw --offset 0x200 --type float32 --count 16 --step 4
"""
from __future__ import annotations
import argparse
import math
import os
import re
import struct
import sys
from typing import Iterable, List, Optional, Sequence, Tuple
__all__ = [
"load_bin_file",
"find_ascii_strings",
"search_data",
"get_data",
"read_array",
"hex_dump",
]
"""
このモジュールがエクスポートする公開APIの一覧です。
"""
#: サポートされているデータ型とその `struct` フォーマット文字列、およびバイトサイズのマッピング
TYPE_MAP = {
"float32": ("f", 4),
"float64": ("d", 8),
"uint8": ("B", 1),
"int8": ("b", 1),
"uint16": ("H", 2),
"int16": ("h", 2),
"uint32": ("I", 4),
"int32": ("i", 4),
"uint64": ("Q", 8),
"int64": ("q", 8),
}
#: 表示可能なASCII文字 (0x20-0x7E) を検索するための正規表現
PRINTABLE_ASCII_RE = re.compile(rb"[\x20-\x7E]+")
# -----------------------------------------------------------------------------
# コアヘルパー
# -----------------------------------------------------------------------------
[ドキュメント]
def load_bin_file(file_path: str) -> bytes:
"""指定されたバイナリファイルを読み込み、その内容をバイト列として返します。
:param file_path: 読み込むバイナリファイルのパス。
:type file_path: str
:returns: ファイルの内容を表すバイト列。
:rtype: bytes
"""
with open(file_path, "rb") as f:
return f.read()
def endian_prefix(endian: str) -> str:
"""エンディアン指定に基づいて `struct` モジュールで使用するフォーマットプレフィックスを返します。
:param endian: エンディアンの指定。"little" または "big"。
:type endian: str
:returns: `struct` モジュールで使用するプレフィックス (< または >)。
:rtype: str
:raises ValueError: サポートされていないエンディアンが指定された場合。
"""
if endian == "little":
return "<"
if endian == "big":
return ">"
raise ValueError(f"Unsupported endian: {endian}")
def parse_offset(text: str) -> int:
"""文字列形式のオフセット(16進数または10進数)を整数に変換します。
'0x' プレフィックスが付いている場合は16進数として解釈します。
:param text: パースするオフセットの文字列。
:type text: str
:returns: パースされた整数オフセット。
:rtype: int
"""
text = text.strip().lower()
return int(text, 0)
def default_step_for_type(vartype: str) -> int:
"""指定されたデータ型に対するデフォルトのスキャンステップサイズを返します。
文字列型の場合は1バイト、それ以外の型の場合はその型のバイトサイズを返します。
:param vartype: データ型の名前(例: "float32", "str")。
:type vartype: str
:returns: デフォルトのスキャンステップサイズ(バイト単位)。
:rtype: int
"""
if vartype == "str":
return 1
_, size = TYPE_MAP[vartype]
return size
def guess_scalar_category(text: str) -> str:
"""入力文字列が整数、浮動小数点数、または文字列のいずれであるかを推測します。
まず整数としてパースを試み、次に浮動小数点数、いずれも失敗した場合は文字列と判断します。
:param text: カテゴリを推測する文字列。
:type text: str
:returns: 推測されたカテゴリ ("int", "float", または "str")。
:rtype: str
"""
try:
int(text, 0)
return "int"
except Exception:
pass
try:
float(text)
return "float"
except Exception:
pass
return "str"
def safe_slice(data: bytes, start: int, end: int) -> bytes:
"""指定された範囲でバイト列を安全にスライスします。
開始インデックスは0未満の場合0に、終了インデックスはデータの長さより大きい場合データの長さにクリップされます。
終了インデックスが開始インデックスより小さい場合、空のスライスを返します。
:param data: スライスする元のバイト列。
:type data: bytes
:param start: スライスの開始オフセット。
:type start: int
:param end: スライスの終了オフセット(このインデックスは含まれません)。
:type end: int
:returns: 安全にスライスされたバイト列。
:rtype: bytes
"""
start = max(0, start)
end = min(len(data), end)
if end < start:
end = start
return data[start:end]
# -----------------------------------------------------------------------------
# ASCIIユーティリティ
# -----------------------------------------------------------------------------
[ドキュメント]
def find_ascii_strings(bin_data: bytes, istart: int = 0, ilast: Optional[int] = None,
min_length: int = 4) -> List[Tuple[int, str]]:
"""バイナリデータから表示可能なASCII文字列を検索します。
検索結果は、元のファイル内の絶対オフセットと抽出された文字列のペアのリストとして返されます。
:param bin_data: 検索対象のバイナリデータ。
:type bin_data: bytes
:param istart: 検索を開始するオフセット(インクルーシブ)。デフォルトは0。
:type istart: int
:param ilast: 検索を終了するオフセット(インクルーシブ)。デフォルトはデータの末尾。
:type ilast: Optional[int]
:param min_length: 検出する文字列の最小長さ。
:type min_length: int
:returns: (オフセット, 文字列) のタプルリスト。
:rtype: List[Tuple[int, str]]
"""
if ilast is None:
ilast = len(bin_data) - 1
if ilast < istart:
return []
region = bin_data[istart:ilast + 1]
pattern = rb"[\x20-\x7E]{%d,}" % min_length
results: List[Tuple[int, str]] = []
for match in re.finditer(pattern, region):
offset = istart + match.start() # 絶対オフセット
value = match.group().decode("ascii", errors="ignore")
results.append((offset, value))
return results
def extract_ascii_prefix(bin_data: bytes, offset: int, size: int) -> str:
"""指定されたオフセットから、連続する表示可能な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
return result.decode("ascii", errors="ignore")
def extract_c_string(bin_data: bytes, offset: int, max_size: int = 256,
encoding: str = "ascii") -> str:
"""指定されたオフセットから、NULL終端された文字列を抽出します。
最大サイズに達するか、NULLバイトが見つかるまでデータを読み込みます。
:param bin_data: データを抽出する元のバイナリデータ。
:type bin_data: bytes
:param offset: 抽出を開始するオフセット。
:type offset: int
:param max_size: 抽出する文字列の最大バイト数(NULLバイトを含む可能性あり)。デフォルトは256。
:type max_size: int
:param encoding: 抽出されたバイト列をデコードするためのエンコーディング。デフォルトは"ascii"。
:type encoding: str
:returns: 抽出された文字列。
:rtype: str
"""
chunk = bin_data[offset: offset + max_size]
nul = chunk.find(b"\x00")
if nul >= 0:
chunk = chunk[:nul]
return chunk.decode(encoding, errors="ignore")
def search_raw_string(bin_data: bytes, needle: str, itop: int = 0,
ilast: Optional[int] = None, encoding: str = "ascii") -> List[int]:
"""バイナリデータ内で生の文字列(バイト列)を検索し、そのオフセットのリストを返します。
指定されたエンコーディングで `needle` をバイト列に変換し、 `bin_data` 内を検索します。
:param bin_data: 検索対象のバイナリデータ。
:type bin_data: bytes
:param needle: 検索する文字列。
:type needle: str
:param itop: 検索を開始するオフセット(インクルーシブ)。デフォルトは0。
:type itop: int
:param ilast: 検索を終了するオフセット(インクルーシブ)。デフォルトはデータの末尾。
:type ilast: Optional[int]
:param encoding: `needle` をバイト列にエンコードするためのエンコーディング。デフォルトは"ascii"。
:type encoding: str
:returns: 一致した文字列の開始オフセットのリスト。
:rtype: List[int]
"""
if ilast is None:
ilast = len(bin_data) - 1
if ilast < itop:
return []
haystack = bin_data[itop: ilast + 1]
target = needle.encode(encoding, errors="ignore")
if not target:
return []
offsets: List[int] = []
pos = 0
while True:
found = haystack.find(target, pos)
if found < 0:
break
offsets.append(itop + found)
pos = found + 1
return offsets
# -----------------------------------------------------------------------------
# バイナリデコーディング
# -----------------------------------------------------------------------------
[ドキュメント]
def get_data(bin_data: bytes, offset: int, size: int, vartype: str,
endian: str = "little", varname: Optional[str] = None,
str_mode: str = "ascii_prefix"):
"""バイナリデータ内の指定されたオフセットから特定の型のデータを読み取ります。
数値型の場合は `struct` モジュールを使用し、文字列型の場合は `str_mode` に応じて抽出します。
: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: 読み取るデータの名前(エラーメッセージ用)。オプション。
:type varname: Optional[str]
:param str_mode: `vartype` が "str" の場合の文字列抽出モード。"ascii_prefix", "c_string", "fixed" のいずれか。デフォルトは"ascii_prefix"。
:type str_mode: str
:returns: 読み取られたデータ値。読み取りに失敗した場合はNone。
:rtype: object
:raises ValueError: オフセット/サイズが範囲外、サポートされていない型、または無効な `str_mode` が指定された場合。
"""
prefix = endian_prefix(endian)
if offset < 0 or offset + size > len(bin_data):
raise ValueError(
f"Offset/size out of range: offset={offset}, size={size}, file_size={len(bin_data)}"
)
if vartype == "str":
if str_mode == "ascii_prefix":
return extract_ascii_prefix(bin_data, offset, size)
if str_mode == "c_string":
return extract_c_string(bin_data, offset, max_size=size)
if str_mode == "fixed":
raw = bin_data[offset: offset + size]
return raw.decode("ascii", errors="ignore")
raise ValueError(f"Unsupported str_mode: {str_mode}")
if vartype not in TYPE_MAP:
raise ValueError(f"Unsupported type: {vartype}")
fmt, expected_size = TYPE_MAP[vartype]
if size != expected_size:
raise ValueError(
f"Invalid size {size} for type {vartype} (expected {expected_size})"
)
try:
return struct.unpack_from(prefix + fmt, bin_data, offset)[0]
except Exception as e:
name = varname if varname is not None else "(unnamed)"
print(
f"Error in get_data(): failed to read [{name}] "
f"(type {vartype}) from offset={offset} size={size}: {e}",
file=sys.stderr,
)
return None
[ドキュメント]
def read_array(bin_data: bytes, offset: int, vartype: str, count: int,
endian: str = "little", step: Optional[int] = None) -> List[object]:
"""バイナリデータ内の指定されたオフセットから、連続するデータ要素の配列を読み取ります。
`vartype` が "str" の場合はサポートされません。
`step` が指定されない場合、デフォルトで型のバイトサイズが使用されます。
:param bin_data: 配列を読み取る元のバイナリデータ。
:type bin_data: bytes
:param offset: 配列の読み取りを開始するオフセット。
:type offset: int
:param vartype: 配列要素の型(例: "float32", "uint16")。"str" はサポートされません。
:type vartype: str
:param count: 読み取る要素の数。
:type count: int
:param endian: エンディアンの指定 ("little" または "big")。デフォルトは"little"。
:type endian: str
:param step: 要素間のバイト単位のストライド。指定されない場合、型サイズが使用されます。オプション。
:type step: Optional[int]
:returns: 読み取られた配列要素のリスト。
:rtype: List[object]
:raises ValueError: `vartype` が "str" の場合。
"""
if vartype == "str":
raise ValueError("read_array() does not support vartype='str'")
fmt, size = TYPE_MAP[vartype]
if step is None:
step = size
out = []
for i in range(count):
off = offset + i * step
if off + size > len(bin_data):
break
out.append(get_data(bin_data, off, size, vartype, endian=endian))
return out
# -----------------------------------------------------------------------------
# 検索
# -----------------------------------------------------------------------------
def numeric_match(val, target, atol: float, rtol: float) -> bool:
"""2つの数値が、許容誤差内で一致するかどうかを判定します。
浮動小数点数の場合、絶対許容誤差 (atol) と相対許容誤差 (rtol) を使用して比較します。
NaNや無限大の浮動小数点値は一致とみなされません。
:param val: 比較する最初の値。
:type val: Any
:param target: 比較するターゲット値。
:type target: Any
:param atol: 絶対許容誤差。
:type atol: float
:param rtol: 相対許容誤差。
:type rtol: float
:returns: 値が一致する場合True、そうでない場合False。
:rtype: bool
"""
if isinstance(val, float):
if math.isnan(val) or math.isinf(val):
return False
return abs(val - target) <= (atol + rtol * abs(target))
return val == target
[ドキュメント]
def search_data(bin_data: bytes, itop: int, ilast: int, size: int, vartype: str,
endian: str, value, tolerance: float = 1e-6, rtol: float = 0.0,
step: int = 1, varname: str = "not given",
str_encoding: str = "ascii") -> List[int]:
"""バイナリデータ内で指定された値(数値または文字列)を検索し、一致するオフセットのリストを返します。
数値型の場合、`step` バイトずつスキャンします。
文字列型の場合、生のバイト列のサブストリング検索を実行します。
: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", "str")。
:type vartype: str
:param endian: エンディアンの指定 ("little" または "big")。
:type endian: str
:param value: 検索するターゲット値。
:type value: Any
:param tolerance: 浮動小数点数比較の絶対許容誤差。デフォルトは1e-6。
:type tolerance: float
:param rtol: 浮動小数点数比較の相対許容誤差。デフォルトは0.0。
:type rtol: float
:param step: スキャンステップサイズ(バイト単位)。デフォルトは1。
:type step: int
:param varname: 検索するデータの名前(エラーメッセージ用)。デフォルトは"not given"。
:type varname: str
:param str_encoding: 文字列検索の場合に `value` をバイト列にエンコードするためのエンコーディング。デフォルトは"ascii"。
:type str_encoding: str
:returns: 一致する値が見つかったオフセットのリスト。
:rtype: List[int]
:raises ValueError: `step` が1未満、サポートされていない型、または型と一致しない `size` が指定された場合。
"""
if ilast < itop:
return []
if step <= 0:
raise ValueError("step must be >= 1")
prefix = endian_prefix(endian)
matches: List[int] = []
if vartype == "str":
return search_raw_string(bin_data, str(value), itop, ilast, encoding=str_encoding)
if vartype not in TYPE_MAP:
raise ValueError(f"Unsupported type: {vartype}")
fmt, expected_size = TYPE_MAP[vartype]
if size != expected_size:
raise ValueError(
f"Invalid size {size} for type {vartype} (expected {expected_size})"
)
max_offset = ilast - size + 1
for offset in range(itop, max_offset + 1, step):
try:
val = struct.unpack_from(prefix + fmt, bin_data, offset)[0]
except struct.error:
continue
if numeric_match(val, value, atol=tolerance, rtol=rtol):
matches.append(offset)
return matches
def search_nearby_pattern(matches_a: Sequence[int], matches_b: Sequence[int],
delta: int, tolerance: int = 0) -> List[Tuple[int, int]]:
"""2つのオフセットリスト `matches_a` と `matches_b` の中で、`b` が `a + delta` に近いペアを検索します。
これは、2つのフィールドが固定の相対オフセットで出現することがわかっている場合に有用です。
`tolerance` が0より大きい場合、 `a + delta` の周囲の範囲で `b` を検索します。
:param matches_a: 最初のパターンの一致オフセットのリスト。
:type matches_a: Sequence[int]
:param matches_b: 2番目のパターンの一致オフセットのリスト。
:type matches_b: Sequence[int]
:param delta: 予想されるオフセット差 (b - a)。
:type delta: int
:param tolerance: オフセット差 `delta` に対する許容誤差。デフォルトは0。
:type tolerance: int
:returns: `(a, b)` の形式で、条件を満たすオフセットペアのリスト。
:rtype: List[Tuple[int, int]]
"""
set_b = set(matches_b)
results = []
if tolerance <= 0:
for a in matches_a:
b = a + delta
if b in set_b:
results.append((a, b))
return results
for a in matches_a:
center = a + delta
for b in range(center - tolerance, center + tolerance + 1):
if b in set_b:
results.append((a, b))
return results
# -----------------------------------------------------------------------------
# 表示ヘルパー
# -----------------------------------------------------------------------------
[ドキュメント]
def hex_dump(bin_data: bytes, offset: int, length: int = 64, width: int = 16) -> str:
"""バイナリデータの一部を16進数とASCII文字でダンプします。
指定されたオフセットから `length` バイトを読み取り、`width` バイトごとに整形された文字列として返します。
:param bin_data: ダンプする元のバイナリデータ。
:type bin_data: bytes
:param offset: ダンプを開始するオフセット。
:type offset: int
:param length: ダンプするバイト数。デフォルトは64。
:type length: int
:param width: 1行に表示するバイト数。デフォルトは16。
:type width: int
:returns: 整形された16進数ダンプ文字列。
:rtype: str
"""
start = max(0, offset)
end = min(len(bin_data), start + length)
lines = []
for row_start in range(start, end, width):
row = bin_data[row_start: row_start + width]
hex_part = " ".join(f"{b:02X}" for b in row)
ascii_part = "".join(chr(b) if 32 <= b <= 126 else "." for b in row)
lines.append(f"{row_start:08X} {hex_part:<{width * 3 - 1}} |{ascii_part}|")
return "\n".join(lines)
def format_value(value) -> str:
"""値を人間が読める形式の文字列にフォーマットします。
浮動小数点数の場合、小数点以下12桁までの精度で表示します。それ以外の型は `str()` で変換します。
:param value: フォーマットする値。
:type value: Any
:returns: フォーマットされた文字列。
:rtype: str
"""
if isinstance(value, float):
return f"{value:.12g}"
return str(value)
# -----------------------------------------------------------------------------
# CLI
# -----------------------------------------------------------------------------
def cmd_search(args: argparse.Namespace) -> int:
"""`search` コマンドの処理関数です。
指定されたバイナリファイル内で数値または文字列を検索し、結果を表示します。
:param args: `argparse` によってパースされたコマンドライン引数。
:type args: argparse.Namespace
:returns: 成功時は0、失敗時は1。
:rtype: int
"""
data = load_bin_file(args.infile)
ilast = len(data) - 1 if args.ilast is None else min(args.ilast, len(data) - 1)
itop = max(0, args.itop)
vartype = args.vartype
if vartype != "str":
category = guess_scalar_category(args.value)
if category == "int":
value = int(args.value, 0)
elif category == "float":
value = float(args.value)
else:
raise ValueError(f"Numeric search requires numeric --value, got: {args.value}")
_, size = TYPE_MAP[vartype]
step = args.step if args.step is not None else default_step_for_type(vartype)
else:
value = args.value
size = args.size if args.size is not None else len(value)
step = 1
matches = search_data(
data,
itop=itop,
ilast=ilast,
size=size,
vartype=vartype,
endian=args.endian,
value=value,
tolerance=args.tolerance,
rtol=args.rtol,
step=step,
)
if not matches:
print("No matches found.")
return 1
print(f"Found {len(matches)} match(es).")
for off in matches[:args.max_hits]:
print(f"offset 0x{off:08X} ({off})")
if args.dump > 0:
base = max(0, off - args.dump_before)
print(hex_dump(data, base, length=args.dump_before + args.dump))
print()
if len(matches) > args.max_hits:
print(f"... {len(matches) - args.max_hits} more match(es) omitted")
return 0
def cmd_strings(args: argparse.Namespace) -> int:
"""`strings` コマンドの処理関数です。
指定されたバイナリファイルから表示可能なASCII文字列を抽出し、表示します。
:param args: `argparse` によってパースされたコマンドライン引数。
:type args: argparse.Namespace
:returns: 成功時は0、失敗時は1。
:rtype: int
"""
data = load_bin_file(args.infile)
ilast = len(data) - 1 if args.ilast is None else min(args.ilast, len(data) - 1)
items = find_ascii_strings(data, istart=max(0, args.itop), ilast=ilast,
min_length=args.min_length)
if args.contains:
items = [(off, s) for off, s in items if args.contains in s]
if not items:
print("No ASCII strings found.")
return 1
for off, s in items[:args.max_hits]:
print(f"0x{off:08X} ({off}): {s}")
if len(items) > args.max_hits:
print(f"... {len(items) - args.max_hits} more string(s) omitted")
return 0
def cmd_inspect(args: argparse.Namespace) -> int:
"""`inspect` コマンドの処理関数です。
指定されたオフセットのデータを複数の型で解釈し、結果を表示します。
:param args: `argparse` によってパースされたコマンドライン引数。
:type args: argparse.Namespace
:returns: 成功時は0、失敗時は1。
:rtype: int
"""
data = load_bin_file(args.infile)
offset = args.offset
types = [x.strip() for x in args.types.split(",") if x.strip()]
sizes = [int(x, 0) for x in args.sizes.split(",")] if args.sizes else []
print(f"File: {args.infile}")
print(f"Offset: 0x{offset:08X} ({offset})")
for i, vartype in enumerate(types):
if vartype == "str":
size = sizes[i] if i < len(sizes) else args.str_size
else:
_, size = TYPE_MAP[vartype]
try:
value = get_data(data, offset, size, vartype, endian=args.endian,
str_mode=args.str_mode)
print(f"- {vartype:<8} size={size:<3} value={format_value(value)}")
except Exception as e:
print(f"- {vartype:<8} error: {e}")
if args.dump > 0:
base = max(0, offset - args.dump_before)
print()
print(hex_dump(data, base, length=args.dump_before + args.dump))
return 0
def cmd_array(args: argparse.Namespace) -> int:
"""`array` コマンドの処理関数です。
指定されたオフセットから数値配列を読み取り、その要素を表示します。
:param args: `argparse` によってパースされたコマンドライン引数。
:type args: argparse.Namespace
:returns: 成功時は0、失敗時は1。
:rtype: int
"""
data = load_bin_file(args.infile)
step = args.step if args.step is not None else default_step_for_type(args.vartype)
values = read_array(data, args.offset, args.vartype, args.count,
endian=args.endian, step=step)
for i, value in enumerate(values):
off = args.offset + i * step
print(f"[{i:4d}] offset=0x{off:08X} ({off:8d}) value={format_value(value)}")
return 0
def cmd_dump(args: argparse.Namespace) -> int:
"""`dump` コマンドの処理関数です。
指定されたオフセットからバイナリデータを16進数ダンプとして表示します。
:param args: `argparse` によってパースされたコマンドライン引数。
:type args: argparse.Namespace
:returns: 成功時は0。
:rtype: int
"""
data = load_bin_file(args.infile)
print(hex_dump(data, args.offset, length=args.length, width=args.width))
return 0
def build_parser() -> argparse.ArgumentParser:
"""コマンドライン引数パーサーを構築し、返します。
このパーサーは、`search`, `strings`, `inspect`, `array`, `dump` のサブコマンドを定義します。
:returns: 設定済みの `ArgumentParser` オブジェクト。
:rtype: argparse.ArgumentParser
"""
p = argparse.ArgumentParser(
description="バイナリファイルを探索し、検索、検査、データダンプを行います。"
)
sub = p.add_subparsers(dest="command", required=True)
# search
ps = sub.add_parser("search", help="数値または文字列を検索します")
ps.add_argument("infile", help="入力バイナリファイル")
ps.add_argument("--type", dest="vartype", required=True,
choices=list(TYPE_MAP.keys()) + ["str"],
help="検索するデータの型")
ps.add_argument("--value", required=True,
help="ターゲット値。整数は0x...形式も受け入れます。")
ps.add_argument("--endian", choices=["little", "big"], default="little",
help="エンディアン (little/big) (デフォルト: little)")
ps.add_argument("--itop", type=parse_offset, default=0,
help="開始オフセット (デフォルト: 0)")
ps.add_argument("--ilast", type=parse_offset, default=None,
help="終了オフセット(インクルーシブ) (デフォルト: EOF)")
ps.add_argument("--size", type=lambda x: int(x, 0), default=None,
help="文字列にのみ使用されます。それ以外の場合は型から推測されます。")
ps.add_argument("--step", type=lambda x: int(x, 0), default=None,
help="スキャンステップ(バイト単位) (デフォルト: 数値型サイズ、str=1)")
ps.add_argument("--tolerance", type=float, default=1e-6,
help="浮動小数点数比較の絶対許容誤差")
ps.add_argument("--rtol", type=float, default=0.0,
help="浮動小数点数比較の相対許容誤差")
ps.add_argument("--dump", type=lambda x: int(x, 0), default=64,
help="表示されるウィンドウからNバイトをダンプします")
ps.add_argument("--dump-before", type=lambda x: int(x, 0), default=16,
help="各ヒットの前にこのバイト数を表示します")
ps.add_argument("--max-hits", type=int, default=20,
help="表示する最大ヒット数")
ps.set_defaults(func=cmd_search)
# strings
pst = sub.add_parser("strings", help="表示可能なASCII文字列をリストアップします")
pst.add_argument("infile", help="入力バイナリファイル")
pst.add_argument("--min-length", type=int, default=4,
help="検出する文字列の最小長さ")
pst.add_argument("--contains", default=None,
help="このサブストリングを含む文字列をフィルタリングします")
pst.add_argument("--itop", type=parse_offset, default=0,
help="検索を開始するオフセット (デフォルト: 0)")
pst.add_argument("--ilast", type=parse_offset, default=None,
help="検索を終了するオフセット(インクルーシブ) (デフォルト: EOF)")
pst.add_argument("--max-hits", type=int, default=200,
help="表示する最大文字列数")
pst.set_defaults(func=cmd_strings)
# inspect
pi = sub.add_parser("inspect", help="1つのオフセットを複数の型として解釈します")
pi.add_argument("infile", help="入力バイナリファイル")
pi.add_argument("--offset", type=parse_offset, required=True,
help="検査するオフセット")
pi.add_argument("--types", default="float32,int16,uint16,str",
help="カンマ区切りの型リスト (例: float32,int16,str)")
pi.add_argument("--sizes", default=None,
help="--types に合わせたオプションのカンマ区切りのサイズリスト")
pi.add_argument("--str-size", type=lambda x: int(x, 0), default=32,
help="vartype=str の場合のデフォルトサイズ")
pi.add_argument("--str-mode", choices=["ascii_prefix", "c_string", "fixed"],
default="ascii_prefix",
help="文字列の抽出モード (ascii_prefix/c_string/fixed)")
pi.add_argument("--endian", choices=["little", "big"], default="little",
help="エンディアン (little/big) (デフォルト: little)")
pi.add_argument("--dump", type=lambda x: int(x, 0), default=64,
help="表示されるウィンドウからNバイトをダンプします")
pi.add_argument("--dump-before", type=lambda x: int(x, 0), default=16,
help="検査オフセットの前にこのバイト数を表示します")
pi.set_defaults(func=cmd_inspect)
# array
pa = sub.add_parser("array", help="オフセットから数値配列を読み取ります")
pa.add_argument("infile", help="入力バイナリファイル")
pa.add_argument("--offset", type=parse_offset, required=True,
help="配列の開始オフセット")
pa.add_argument("--type", dest="vartype", required=True,
choices=list(TYPE_MAP.keys()),
help="配列要素の型")
pa.add_argument("--count", type=int, required=True,
help="読み取る要素の数")
pa.add_argument("--step", type=lambda x: int(x, 0), default=None,
help="要素間のバイト単位のストライド")
pa.add_argument("--endian", choices=["little", "big"], default="little",
help="エンディアン (little/big) (デフォルト: little)")
pa.set_defaults(func=cmd_array)
# dump
pd = sub.add_parser("dump", help="オフセットから16進数ダンプを実行します")
pd.add_argument("infile", help="入力バイナリファイル")
pd.add_argument("--offset", type=parse_offset, required=True,
help="ダンプを開始するオフセット")
pd.add_argument("--length", type=lambda x: int(x, 0), default=128,
help="ダンプするバイト数 (デフォルト: 128)")
pd.add_argument("--width", type=int, default=16,
help="1行に表示するバイト数 (デフォルト: 16)")
pd.set_defaults(func=cmd_dump)
return p
def main(argv: Optional[Sequence[str]] = None) -> int:
"""メインエントリーポイント。コマンドライン引数をパースし、対応するコマンドを実行します。
:param argv: コマンドライン引数のリスト。指定されない場合は `sys.argv` が使用されます。オプション。
:type argv: Optional[Sequence[str]]
:returns: コマンド実行の終了コード(0は成功、非0はエラー)。
:rtype: int
"""
parser = build_parser()
args = parser.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
raise SystemExit(main())