Files
Aislo/tools/stmate-test/conv_win8_ini.py
T
eomsangdonandClaude Opus 5.5 47a04b6df8 feat(stmate-test): REA MCP(Ghidra) 파일 입출력 — JDL 탭 줄·묶음 내보내기·검사 · 구버전→Win8 변환 · 엑셀 역등록 구성 줄 · 자재 집계표 엑셀 · 그림 코드
- JDL 줄 = 탭 구분 + \x05/\x07 덧붙임(검사기 0x504d20 · 고정폭 표는 죽은 고리 — 34 §17.9 바로잡음) · 단가 목록 7종·Main·GongJong 머리·Global.Ini 상수 키 · zip 받기
- 구버전 판 가리기(Chk_Data 0x459280) · 7.x/DOS 표별 필드 대응 · B→X 조건 = 단위 X
- 엑셀 역등록 PRPT「속성」·PRIN「대상」·PROUT「저장」 = PR/RR 줄만 · 원본 BDQTY 4건 일치
- 자재 = 재료비 수량집계표 엑셀 내보내기(주요건설자재현황 시트)
- 그림 코드 = COSTN I 코드 IFORM · SIZE = 그림 클래스 이름
- 지식DB 37번

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 15:58:17 +09:00

227 lines
8.3 KiB
Python

# -*- coding: utf-8 -*-
"""구버전 → Win8 설정 — 판 가리기(Chk_STDir) · 7.x Start.Ini → Start8x.Ini · DOS Start.MEM 읽기.
근거(지식DB REA 분석/37 §2 · ST_U_Archive Chk_Data 0x459280 · Chk_STDir 0x45a78c · ST_U_Convert 0x48e07c · 0x48fea8):
판 = 파일 이름 묶음 규칙(위에서 처음 맞는 것) · 120·130·140 은 그 설정 [PROJECT] PMODE(기본 1)를 더함 · 110 은 111 고정
7.x 설정 키 대응은 0x48e07c ReadString/Write 차례 · DOS MEM = Clipper 변수 머리 32바이트(이름 11 · 형 +11 · 길이 +16·+17)
"""
from __future__ import annotations
import re
import struct
from dbf_memo import ini_items
RULES = [
(140, ("STINI.MEM", "RATE.DBF", "QCODE.DBF")),
(130, ("START8X.INI", "RATE.DBF", "QCODE.DBF")),
(120, ("START.INI", "FORM.DBF", "KANJUB2.DBF", "IMG.DB")),
(110, ("START.MEM", "FORM.DBF", "KANJUB2.DBF", "COSTN1.DBF")),
(150, ("START8X", "COSTA.DBF", "COSTN.SMT", "PRJLST.SMT", "SYSINFO.DBF")),
]
JODAL = (
"GLOBAL.INI",
"M.TXT",
"L.TXT",
"E.TXT",
"W.TXT",
"UNIT.TXT",
"UNITPART.TXT",
"J.TXT",
"JPART.TXT",
)
JODAL += (
"JUNIT.TXT",
"DANGA.TXT",
"DANGA.DBF",
"DANGA.DBT",
"GONGJONG.TXT",
"MAIN.TXT",
"MAINPART.TXT",
)
RULES.append((200, JODAL))
CORE = ("COSTN.DBF", "BDQTY.DBF", "PRJLST.DBF", "GRASS.DBF")
def ini_dict(text: str) -> dict[str, dict[str, str]]:
out: dict[str, dict[str, str]] = {}
for sec, k, v in ini_items(text):
out.setdefault(sec.upper(), {}).setdefault(k.upper(), v)
return out
def _pmode(files: dict, name: str) -> int:
raw = files.get(name)
if raw is None:
return 1
v = ini_dict(raw.decode("cp949", "replace")).get("PROJECT", {}).get("PMODE", "1")
try:
return int(v.strip() or "1")
except ValueError:
return 0
def detect(files: dict) -> int:
"""폴더 파일 이름(대문자) → 판 번호 — 111 DOS · 120+PMODE 7.x · 130/140+PMODE 이미 Win8 · 200 조달청 TEXT."""
names = {n.upper() for n in files}
code = 0
if len(":".join(names)) >= 5:
code = next((c for c, need in RULES if all(n in names for n in need)), 0)
if code == 0:
return 100 if names & {"COSTN.DBF", "BDQTY.DBF", "PRJLST.DBF"} else 0
if code not in (150, 200) and not all(n in names for n in CORE):
return 100
if code == 110:
return 111
ini = {120: "START.INI", 130: "START8X.INI", 140: "STINI.MEM"}.get(code)
return code + _pmode(files, ini) if ini else code
# 7.x [START] 키 — (대상 키, 원본 키, 기본값)
START_7X = [("BMKNM", "BMKNM", "MLST"), ("ACDNM", "ACDNM", "1"), ("BPNNM", "ACDNM", "1")]
START_7X += [
("KCDNM", "KCDNM", "1"),
("ZCDNM", "ZCDNM", "2"),
("CHGNM", "CHGNM", "1"),
("CHBNM", "CHGNM", "1"),
]
START_7X += [
("CHNNM", "CHNNM", "1"),
("BCDNM", "BCDNM", "0"),
("DCDNM", "DCDNM", "0"),
("ICDNM", "ICDNM", "0"),
]
START_7X += [("LCDNM", "LCDNM", "1"), ("MCDNM", "MCDNM", "1"), ("PCDNM", "PCDNM", "10")]
START_7X += [
("SCDNM", "SCDNM", "0"),
("WCDNM", "WCDNM", "0"),
("SUBNM", "SUBNM", ""),
("LMGNM", "LMGNM", "4"),
]
START_7X += [("MKRNM", "MKRNM", "5"), ("TMGNM", "TMGNM", "6"), ("JEMNM", "JEMNM", "123456")]
JMOK = ["가격 정보", "물가 자료", "유통 물가", "견적단가1", "견적단가2", "적용 단가"]
START_7X += [(f"JMOK{i}", f"JMOK{i}", v) for i, v in enumerate(JMOK, 1)]
START_7X += [
(f"JMK{i}0", f"JMK{i}0", v)
for i, v in enumerate(["가.정", "물.자", "유.물", "견적 1", "견적 2"], 1)
]
START_7X += [("JUKNM", "JUKNM", "0")]
EQUIP_7X = [("RXXNM", "START", "RXXNM", "1/8 * 16/12 * 25/20"), ("RXTNM", "START", "RXTNM", "")]
EQUIP_7X += [("EXLNM1", "START", "EX1NM", "1000"), ("EXHNM1", "START", "EX2NM", "1000")]
EQUIP_7X += [("EXLNM2", "START", "EXCNM", "1000"), ("EXHNM2", "START", "EX0NM", "1000")]
EQUIP_7X += [(k, "START", "EX1NM", "1000") for k in ("EXLNM3", "EXHNM3", "EXLNM4", "EXHNM4")]
def write_ini(sections: list[tuple[str, list[tuple[str, str]]]]) -> bytes:
out = []
for sec, items in sections:
if items:
out.append(f"[{sec}]")
out += [f"{k}={v}" for k, v in items]
return ("\r\n".join(out) + "\r\n").encode("cp949", "replace")
def ini_from_7x(start_ini: bytes, conld: str, contd: str) -> bytes:
"""7.x Start.Ini → Start8x.Ini(0x48e07c) — 원본에 없는 키는 기본값."""
src = ini_dict(start_ini.decode("cp949", "replace"))
g = lambda sec, k, d: src.get(sec, {}).get(k, d) # noqa: E731
start = [("ARENM", "1" if g("START", "ARENM", "A->") == "A->" else "2")]
start += [(t, g("START", s, d)) for t, s, d in START_7X]
prt = [
("ORDNM", g("PRINT", "ORDNM", "")),
("FONT", g("PRINT", "CBFONT", "굴림체")),
("F_COL0", "clBlack"),
]
prt += [("F_COL1", g("PRINT", "COLDAN", "clRed")), ("F_COL2", g("PRINT", "COLBYN", "clBlack"))]
prt += [("SH_TYP0", g("PRINT", "FOMNM", "0")), ("SAN_TYP", g("PRINT", "LANNM", "0"))]
prt += [("SAN_AMT", "1"), ("FLT_JAB", "3")]
eq = [(t, g(s, k, d)) for t, s, k, d in EQUIP_7X]
eq += [(k, v) for k, v in (("CONLD", conld), ("CONTD", contd)) if v]
prj = [
("PCODE", "XXXX"),
("PMODE", g("PROJECT", "PMODE", "1")),
("PNAME", g("PROJECT", "PNAME", "프로젝트명")),
]
prj.append(("P_DIR", ""))
nag = [("NAGNM", src["START"]["NAGNM"])] if "NAGNM" in src.get("START", {}) else []
for t, k in (
("ST_MHAL", "PMHAL"),
("EQ_LHAL", "EQ_L"),
("EQ_MHAL", "EQ_M"),
("EQ_SHAL", "EQ_S"),
):
if k in src.get("PROJECT", {}):
nag.append((t, src["PROJECT"][k]))
secs = [("START", start), ("PRINT", prt), ("EQUIPMENT", eq), ("PROJECT", prj), ("NAG_HAL", nag)]
return write_ini(secs)
def read_mem(b: bytes) -> dict[str, object]:
"""Clipper .MEM 변수 — C 글(TrimRight) · N 실수(%-20.6F 뒤 Trim) · L 0/1 · D 율리우스 일수(조건부)."""
out: dict[str, object] = {}
o = 0
while o + 32 <= len(b) and b[o] not in (0x1A, 0):
name = b[o : o + 11].split(b"\x00", 1)[0].decode("ascii", "replace").upper()
typ = chr(b[o + 11] & 0x7F)
ln = b[o + 16] | (b[o + 17] << 8)
o += 32
if typ == "C":
out[name] = b[o : o + ln].split(b"\x00", 1)[0].decode("cp949", "replace").rstrip()
o += ln
elif typ in ("N", "D"):
v = struct.unpack_from("<d", b, o)[0]
out[name] = (f"{v:<20.6f}").strip() if typ == "N" else v
o += 8
elif typ == "L":
out[name] = "1" if b[o] else "0"
o += 1
else:
break
return out
DOS_PRINT = ("ILWNM", "TOGNM", "LANNM")
DOS_EQUIP = ("RXTNM", "RXXNM", "EX1NM", "EX2NM", "EXCNM", "EX0NM")
def ini_from_dos(mem: bytes, conld: str, contd: str) -> tuple[bytes, list[str]]:
"""DOS Start.MEM → Start8x.Ini(0x48fea8 → 0x44d370) — [START] 34키 표는 실행 중 채워져 이름 미확인 →
[PRINT]·[EQUIPMENT] 로 가는 키 밖의 MEM 변수는 [START] 로(조건부)."""
v = read_mem(mem)
start = [(k, str(x)) for k, x in v.items() if k not in DOS_PRINT + DOS_EQUIP]
start = [kv for kv in start if kv[0] not in ("SUBNM", "QCDNM", "XCDNM", "JUKNM")]
start += [("SUBNM", ""), ("QCDNM", "0"), ("XCDNM", "0"), ("JUKNM", "0")]
prt = [(k, str(v[k])) for k in DOS_PRINT if k in v]
prt += [
("ORDNM", " "),
("FONT", "굴림체"),
("F_COL0", "clBlack"),
("F_COL1", "clRed"),
("F_COL2", "clBlack"),
]
eq = [(k, str(v[k])) for k in DOS_EQUIP if k in v]
eq += [(k, x) for k, x in (("CONLD", conld), ("CONTD", contd)) if x]
note = ["DOS [START] 키 이름 표(0x588294)는 실행 중 채워짐 — MEM 변수 이름 그대로 씀(조건부)"]
return write_ini(
[("START", start), ("PRINT", prt), ("EQUIPMENT", eq), ("PROJECT", [("PMODE", "1")])]
), note
def b_list(rows: list[dict]) -> str:
"""CONLD/CONTD 의 BDCODE 중 B 로 시작하는 6자 코드를 겹치지 않게 이음(0x477e4c)."""
out = []
for r in rows:
c = str(r.get("BDCODE", "")).strip()
if c.startswith("B") and c not in out:
out.append(c)
return "".join(out)
def swap_codes(text: str, mapping: dict[str, str]) -> str:
return (
re.sub("|".join(map(re.escape, mapping)), lambda m: mapping[m.group(0)], text)
if mapping
else text
)