96 lines
3.0 KiB
Python
96 lines
3.0 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
log_decode.py — 将 K1 LOG.BIN(W25Q 日志分区镜像)解码为可读时间线
|
||
|
||
布局:
|
||
[0x0000 .. 0x0FFF] header (magic K1LG ...)
|
||
[0x1000 .. end] 文本环形区,行以 \\n 结束
|
||
|
||
用法:
|
||
python log_decode.py LOG.BIN -o problem.log
|
||
python log_decode.py LOG.BIN --write-off 1234 --wrap 1 -o problem.log
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import struct
|
||
|
||
MAGIC = 0x4B314C47 # 'K1LG'
|
||
HDR_SIZE = 0x1000
|
||
|
||
|
||
def unpack_header(blob: bytes) -> dict:
|
||
if len(blob) < 36:
|
||
return {"valid": False}
|
||
magic = struct.unpack_from("<I", blob, 0)[0]
|
||
ver = struct.unpack_from("<H", blob, 4)[0]
|
||
boot = struct.unpack_from("<I", blob, 8)[0]
|
||
write_off = struct.unpack_from("<I", blob, 12)[0]
|
||
wrap = struct.unpack_from("<I", blob, 16)[0]
|
||
lines = struct.unpack_from("<I", blob, 20)[0]
|
||
overflow = struct.unpack_from("<I", blob, 24)[0]
|
||
seq = struct.unpack_from("<I", blob, 28)[0]
|
||
return {
|
||
"valid": magic == MAGIC,
|
||
"magic": magic,
|
||
"ver": ver,
|
||
"boot_count": boot,
|
||
"write_off": write_off,
|
||
"wrap_count": wrap,
|
||
"line_count": lines,
|
||
"overflow": overflow,
|
||
"seq": seq,
|
||
}
|
||
|
||
|
||
def extract_text(data: bytes, write_off: int, wrapped: bool) -> str:
|
||
"""Linearize ring: if wrapped, [write_off..end) + [0..write_off); else [0..write_off)."""
|
||
if not data:
|
||
return ""
|
||
if write_off > len(data):
|
||
write_off = len(data)
|
||
if wrapped and write_off < len(data):
|
||
raw = data[write_off:] + data[:write_off]
|
||
else:
|
||
raw = data[:write_off] if write_off else data
|
||
text = raw.decode("utf-8", errors="replace")
|
||
text = text.replace("\xff", "")
|
||
return text
|
||
|
||
|
||
def decode_file(bin_path: str, out_path: str, write_off=None, wrap_count=None) -> None:
|
||
with open(bin_path, "rb") as f:
|
||
blob = f.read()
|
||
hdr = unpack_header(blob)
|
||
data = blob[HDR_SIZE:] if len(blob) > HDR_SIZE else b""
|
||
|
||
wo = write_off if write_off is not None else hdr.get("write_off", 0)
|
||
wrap = wrap_count if wrap_count is not None else hdr.get("wrap_count", 0)
|
||
|
||
text = extract_text(data, wo, wrap > 0)
|
||
lines = [ln for ln in text.splitlines() if ln.strip()]
|
||
|
||
with open(out_path, "w", encoding="utf-8", newline="\n") as f:
|
||
f.write(
|
||
f"# K1 log decode magic_ok={hdr.get('valid')} ver={hdr.get('ver')} "
|
||
f"boot={hdr.get('boot_count')} write_off={wo} wrap={wrap} "
|
||
f"line_count={hdr.get('line_count')} decoded_lines={len(lines)}\n"
|
||
)
|
||
for ln in lines:
|
||
f.write(ln.rstrip("\r") + "\n")
|
||
print(f"decoded {len(lines)} lines -> {out_path}")
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("bin")
|
||
ap.add_argument("-o", "--out", default="problem.log")
|
||
ap.add_argument("--write-off", type=int, default=None)
|
||
ap.add_argument("--wrap", type=int, default=None)
|
||
args = ap.parse_args()
|
||
decode_file(args.bin, args.out, args.write_off, args.wrap)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|