- DBF 고친 칸만 다시 씀(한글 깨짐 · 2칸을 1칸으로 · 3칸 87.745% 덮어쓰기 없앰) · 단가 → 내역 행 → 소계 → 직공비 다시 셈 · 행 끼우기·지우기 - SIxMemo(COSTN.SMT · PRJLST.SMT) 블록 구조 유지 · 넘치면 뒤에 붙이고 가리키는 값 갱신 - STC 다시 묶기(같은 파일은 원본 바이트) · 검사 · 자동 백업·복원 · 새 공사 · 복사등록(차수 인계) · 해당분 압축 - BDQTY 역변환(쓰기 준비) · 모듈 dbf_rw · dbf_memo · dbf_crypt · stc_archive · stc_save · stc_read · stc_project · api_file · scr_file - 지킴 시험 test_stmate_file_roundtrip · file_project · file_payload — 실무 STC 7건 왕복 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
43 lines
1.5 KiB
Python
43 lines
1.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""암호 DBF(BDQTY.DBF) 자리별 변환 — 복호(bdqty._dec)의 역변환으로 다시 숨김.
|
|
|
|
근거: STC_난독화_상태.md · 29_암호화자료의_뼈대.md · bdqty.py(자리별 표 PARAMS)
|
|
A 암호 = rol(평문, r) + k B 암호 = rol(평문 + k, r) X 암호 = rol(평문, r) ^ k
|
|
표에 없는 자리(글자 칸 등)는 풀 수도 숨길 수도 없음 — 그 자리를 건드리는 쓰기는 오류.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from bdqty import PARAMS, _dec
|
|
|
|
|
|
def _rol(v: int, r: int) -> int:
|
|
return ((v << r) | (v >> (8 - r))) & 0xFF if r else v
|
|
|
|
|
|
def enc_byte(p: int, param: tuple[str, int, int]) -> int:
|
|
kind, r, k = param
|
|
if kind == "A":
|
|
return (_rol(p, r) + k) & 0xFF
|
|
if kind == "B":
|
|
return _rol((p + k) & 0xFF, r)
|
|
return _rol(p, r) ^ k
|
|
|
|
|
|
def covers(off: int, n: int) -> bool:
|
|
return all(j in PARAMS for j in range(off, off + n))
|
|
|
|
|
|
class BdqtyCodec:
|
|
"""DbfTable codec — off = 레코드 안 칸 시작 자리."""
|
|
|
|
def decode(self, cipher: bytes, off: int) -> bytes:
|
|
if not covers(off, len(cipher)):
|
|
raise ValueError(f"BDQTY 자리 {off}~{off + len(cipher) - 1} 은 복호 표에 없음")
|
|
return bytes(_dec(c, PARAMS[off + i]) for i, c in enumerate(cipher))
|
|
|
|
def encode(self, plain: bytes, off: int) -> bytes:
|
|
if not covers(off, len(plain)):
|
|
raise ValueError(f"BDQTY 자리 {off}~{off + len(plain) - 1} 은 암호 표에 없음")
|
|
return bytes(enc_byte(p, PARAMS[off + i]) for i, p in enumerate(plain))
|