From 23f56e701bf4f3f5ab48b1e14ccceec6693bd1b5 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Tue, 12 May 2026 03:47:52 -0600 Subject: [PATCH] programs: AND-record u16 fields are big-endian on disk (verified) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Authored a controlled "AND IF ZONE 5 SECURE" condition in PC Access and diffed against an "AND IF NEVER" capture at the same record position: NEVER: 08 00 00 00 01 00 00 00 ... ZONE 5 SECURE: 08 04 00 00 05 00 00 00 ... The zone number "5" lands in byte 4 (high-offset of the u16 at positions 3-4). If disk were little-endian, the 5 would land at byte 3. So Arg1_IX, Arg2_IX, and CompConst in AND records are big-endian on disk — opposite of the compact-form cond/cond2/pr2, which are LE. Different record families use different byte orders; the C# encoder writes AND records' u16s directly in BE matching in-memory Data[] layout, while compact-form fields go through the Cond/Cond2/Pr2 setters that produce LE on the wire. Update the AND-record comment block to reflect the resolved byte order. The byte-1 semantic role (OP vs family code) remains open and is noted as a future single-experiment follow-up. No code changes — still no AndRecord decoder exposed since byte 1's meaning is one open question away from being settled. --- src/omni_pca/programs.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/omni_pca/programs.py b/src/omni_pca/programs.py index a49c68d..5dd6d2a 100644 --- a/src/omni_pca/programs.py +++ b/src/omni_pca/programs.py @@ -209,16 +209,26 @@ and ``cond2`` would not exist. # ``Arg1_*`` / ``Arg2_*`` / ``CompConst`` fields are only used when # ``OP > 0``. # -# **Open: disk byte order for the three u16 fields.** ``clsProgram.Read`` -# reads u16s little-endian via ``clsPcaCryptFileStream.ReadUInt16`` -# (clsPcaCryptFileStream.cs:159-164), but the accessors index ``Data[]`` -# big-endian (``(Data[3] << 8) + Data[4]``). The single captured -# example (``AND IF NEVER`` → all zero except byte 4 = 0x01) is -# symmetric and doesn't disambiguate. Resolution requires a controlled -# capture of ``AND IF ZONE 5 SECURE`` (or similar asymmetric ix value). -# Until then we don't expose a structured ``AndRecord`` decoder — the -# raw 14 bytes are still accessible via ``Program.raw`` for callers -# who need them. +# **Disk byte order for the three u16 fields: big-endian.** Verified +# empirically by authoring ``AND IF ZONE 5 SECURE`` and observing the +# zone number (5) at byte 4, not byte 3. So ``Arg1_IX``, ``Arg2_IX``, +# and ``CompConst`` are decoded as ``(body[N] << 8) | body[N+1]`` — +# the *opposite* of compact-form ``cond`` / ``cond2`` / ``pr2``, which +# are LE. Different record families use different byte orders. +# +# Still open: byte 1's semantic role. The C# accessor says it's ``OP`` +# (`enuCondOP`), but empirically `0x04` in byte 1 corresponds to the +# ZONE family code (matching ``ProgramCond.ZONE``) rather than +# ``Arg1_GT_Arg2`` (which the C# enum would assign). Most likely byte +# 1 carries the family code when ``OP`` is implicitly Traditional +# (the common case), and only takes structured ``OP`` values when the +# user picks a comparison operator. A future capture of ``AND IF +# TEMPERATURE > 70`` would resolve this. +# +# We don't expose a structured ``AndRecord`` decoder yet — the raw 14 +# bytes are still accessible via ``Program.raw`` for callers who +# need them, and the two open questions on the structured form make +# a partial decoder risk shipping wrong field interpretations. class CondOP(IntEnum):