Files
altium-render-engine/backend/app/parser/pcb_binary.py
T
2026-11-09 12:34:50 +03:30

107 خطوط
3.5 KiB
Python

"""Checked Altium PCB framing. No embedded content is decompressed or executed."""
import math
import struct
from .record_decoder import ParseError, properties
MAX_ITEMS = 100_000
MAX_BLOCK = 16 * 1024 * 1024
class Reader:
def __init__(self, data, label="PCB"):
self.data = memoryview(data)
self.offset = 0
self.label = label
@property
def remaining(self):
return len(self.data) - self.offset
def take(self, length):
if length < 0 or length > self.remaining:
raise ParseError(
f"{self.label}: truncated data at byte {self.offset}, need {length}"
)
start = self.offset
self.offset += length
return self.data[start : self.offset]
def read(self, fmt):
result = struct.unpack("<" + fmt, self.take(struct.calcsize("<" + fmt)))
if any(isinstance(v, float) and not math.isfinite(v) for v in result):
raise ParseError(f"{self.label}: non-finite binary number")
return result[0] if len(result) == 1 else result
def block(self):
length = self.read("I")
if length > MAX_BLOCK:
raise ParseError(f"{self.label}: subrecord exceeds {MAX_BLOCK} bytes")
return self.take(length)
def pascal(self):
return bytes(self.take(self.read("B"))).decode("cp1252", errors="replace")
def text_records(data, name):
reader = Reader(data, name)
result = []
while reader.remaining:
if len(result) >= MAX_ITEMS:
raise ParseError(f"{name}: excessive record count")
result.append(properties(bytes(reader.block())))
return result
def binary_records(data, name, tag, blocks=1):
reader = Reader(data, name)
index = 0
while reader.remaining:
if index >= MAX_ITEMS:
raise ParseError(f"{name}: excessive record count")
actual = reader.read("B")
if actual != tag:
raise ParseError(
f"{name}: unsupported tag {actual} at record {index}, expected {tag}"
)
yield index, [bytes(reader.block()) for _ in range(blocks)]
index += 1
def wide_strings(data):
reader = Reader(data, "WideStrings6")
result = {}
while reader.remaining:
if len(result) >= MAX_ITEMS:
raise ParseError("Excessive wide string count")
index = reader.read("I")
length = reader.read("I")
# Altium writes empty strings with length=2 but no payload in some exports.
if (
length == 2
and bytes(reader.data[reader.offset : reader.offset + 2]) != b"\0\0"
):
payload = b""
else:
if length > MAX_BLOCK:
raise ParseError("Wide string exceeds size limit")
payload = reader.take(length)
if len(payload) % 2:
raise ParseError("Odd UTF-16 string length")
result[index] = (
bytes(payload).decode("utf-16le", errors="replace").rstrip("\x00")
)
return result
def at(data, offset, fmt, default=None):
size = struct.calcsize("<" + fmt)
if offset < 0 or offset + size > len(data):
if default is not None:
return default
raise ParseError(f"Truncated PCB field at offset {offset}, need {size} bytes")
values = struct.unpack_from("<" + fmt, data, offset)
if any(isinstance(v, float) and not math.isfinite(v) for v in values):
raise ParseError("Non-finite PCB field")
return values[0] if len(values) == 1 else values