모든 형식 페이지 ↓
튜토리얼·참고 자료

모딩과 파싱에서 흔히 주의할 점

출처: coreprime/kbot @ 0ab97bf · GitHub에서 편집

kbot 형식 문서 곳곳에 흩어져 있는 주의할 점 가운데 사람들이 가장 자주 걸려드는 것을 한 페이지에 모은 요약표입니다. 도무지 이해할 수 없는 버그를 만났다면 여기서 시작하세요. 각 항목은 해당 형식 페이지의 더 자세한 설명으로 연결됩니다.

Tip

"모드가 로드되지 않는다"는 문제를 찾고 계신가요? TDF 세미콜론 규칙(#tdf--fbi--ota의 첫 항목) 하나 때문에 생기는 "조용한 고장" 보고가 나머지 주의할 점을 모두 합친 것보다 많습니다.


HPI / UFO / CCX / GP3

  • 설치 폴더 루트의 낱개 파일은 모든 아카이브보다 우선합니다. 디스크에 units/MYUNIT.fbi를 두면 엔진은 totala1.hpi의 어떤 파일보다 그것을 먼저 씁니다. 전체 우선순위는 hpi.md의 계층화 도표에 있습니다.
  • 아카이브끼리는 먼저 마운트된 것이 이기며, *.hpi는 열 개까지만 마운트됩니다. TA 3.1c는 rev31.gp3, 그다음 *.ccx, *.ufo, 그리고 열리는 처음 열 개의 *.hpi를 마운트하며, 각 묶음 안에서는 이름을 ASCII 대문자로 바꾼 순서를 따릅니다. zz_mod.hpi로 배포한 모드는 모든 정식판 아카이브보다 순위가 낮고, 이미 그보다 앞에 정렬되는 *.hpi가 열 개 있는 설치본에서는 디스크 검색으로만 마운트되어 모든 것보다 아래에 놓입니다. 모드는 .ufo로 배포하세요.
  • HPI v1 포인터는 디렉터리 시작이 아니라 파일 시작 기준의 절대 오프셋입니다. 아무것도 빼지 마세요. 표준적인 파서의 요령은 DirectorySize바이트 버퍼를 할당하고 디렉터리를 그 버퍼의 오프셋 Start에 복호화해 넣어, 포인터로 버퍼를 바로 인덱싱하는 것입니다. 디렉터리 트리를 참고하세요.
  • HPI v1 암호의 시드는 파일 위치에 따라 바뀝니다. 버퍼만 따로 떼어 복호화할 수는 없습니다 — 그 버퍼가 파일의 어느 절대 오프셋에서 왔는지 알아야 합니다. 암호 키 유도를 참고하세요.
  • DecryptKey == 0이면 XOR 암호를 아예 적용하지 마세요. 일부 커스텀 아카이브는 평문으로 배포됩니다. 그래도 암호를 적용하면 이미 읽을 수 있는 내용이 뒤섞입니다.
  • SQSH 청크 체크섬은 암호화된 바이트로 계산합니다. 암호를 풀기 전에 검증하세요. 압축 청크(SQSH)를 참고하세요.
  • TA: Kingdoms는 구조가 다른 HPI v2(Version == 0x00020000)를 씁니다 — XOR 암호를 쓰지 않고 파일마다 SQSH 청크가 하나입니다. TAK 아카이브에 v1 파서를 써 보기 전에 TA: Kingdoms HPI v2 절을 읽어 보세요.
  • BANK 표식이 있는 저장 게임은 완전히 다른 물건입니다 — HPI의 겉 구조를 재사용하지만 내용은 파일 시스템이 아니라 직렬화된 게임 상태입니다. kbot은 이를 디코딩하지 않습니다.
  • .gp3에는 rev3.1 패치가 필요합니다. 안은 여전히 HPI지만, 패치하지 않은 TA는 이 파일의 마운트를 거부합니다.

TDF / FBI / OTA

  • 모든 값은 ;으로 끝나야 합니다. 세미콜론이 빠지면 값이 여러 줄에 걸쳐 조용히 합쳐져 능력치가 엉망이 됩니다. "유닛의 HP가 이상하다 / 설명이 없다 / 비용이 틀리다"의 가장 흔한 원인입니다.
  • 키는 대소문자를 구분하지 않지만 값은 구분합니다. Side=arm은 대부분의 조회에서 동작하지만, Side를 문자열 일치로 비교하는 모드 코드에서는 제대로 동작하지 않습니다.
  • 목록 값은 쉼표가 아니라 공백으로 구분합니다(Category=COMMANDER MOBILE WEAPON). 예외는 쉼표를 쓰는 OTA의 numplayers=2,4,6,8,10입니다.
  • 불리언은 true/false가 아니라 정수 1/0입니다. 엔진은 정수 형태만 받아들입니다.
  • 모드에서는 유닛을 추가하려고 sidedata.tdf를 고치지 마세요. 대신 download/<UNITNAME>.tdf의 [MENUENTRY] 등록을 쓰세요 — Core Contingency가 쓰는 것과 같은 메커니즘입니다. TDF: [MENUENTRY]와 모딩 튜토리얼을 참고하세요.
  • Bmcode=0은 다른 모든 것이 올바르더라도 유닛을 건설 메뉴에서 숨깁니다. 거의 언제나 Bmcode=1로 두면 됩니다.
  • 어떤 건설 유닛 목록에도 없는 유닛은 건설할 수 없습니다. FBI가 그 밖에는 올바르더라도 마찬가지입니다. "새 유닛은 존재하는데 건설할 수 없다"는 버그의 가장 흔한 원인입니다. 모딩 5단계를 참고하세요.
  • 중첩 섹션의 깊이는 명세가 아니라 관례입니다. 일부 도구는 2단계를 가정하지만, 무기 TDF는 3단계까지 들어갑니다([WEAPON][DAMAGE][category]).

TNT / SCT — 맵 지형

  • TNT의 Width와 Height는 16픽셀 속성 칸 단위이며, 타일도 픽셀도 아닙니다. 2로 나누면 타일 수, 16을 곱하면 픽셀 크기입니다. TNT 헤더를 참고하세요.
  • SeaLevel == 0은 "물 없음"이 아닙니다 — 가장 낮은 높이 값이 딱 경계에 걸리므로 물에 잠기는 곳이 없다는 뜻입니다. 정말로 물이 없는 맵에는 SeaLevel = 1을 쓰세요.
  • TNT 지형지물 워드는 지형지물 테이블 개수보다 작을 때만 지형지물을 배치합니다. 0xFFFC는 공백(void)이고, 0xFFFE(Lava Run, AC02), 0xFFFD, 그리고 테이블 크기 이상인 그 밖의 모든 워드는 아무것도 배치하지 않으며 그 칸에는 여전히 건설할 수 있습니다.
  • TNT 미니맵 패딩은 팔레트 인덱스 0x64를 쓰며, 맵 영역은 패딩을 찾아서가 아니라 맵 크기로 정합니다(긴 변 252, 보이는 맵의 가로세로 비율). 게임은 헤더 0x2c 워드의 비트 0이 설정되어 있을 때만 저장된 미니맵을 읽습니다. TNT를 참고하세요.
  • TA: Kingdoms의 .tnt는 TA의 0x2000 대신 IDVersion == 0x4000을 씁니다(TA는 예전 0x1020 구조도 읽음). kbot tnt image를 TAK TNT에 쓰면 엉터리 이미지가 나옵니다. 타일 디코더가 TA 전용이기 때문입니다. TA:K 맵을 참고하세요.
  • SCT 섹션은 TAE에서 로드하려면 가로세로 모두 4의 배수여야 합니다. 디스크상 형식은 다른 크기도 허용하지만, 에디터는 "section misaligned"라며 거부합니다.
  • SCT V2의 높이 칸은 8바이트, V3는 4바이트입니다. kbot은 둘 다 받아들이며, 에디터용으로 만든 모드에는 V3만 보입니다.

3DO — 모델

  • 위쪽은 Z가 아니라 Y입니다. 3DO를 Z축이 위인 엔진(Blender, 최신 Unreal)에 축을 바꾸지 않고 가져오면 모든 유닛이 옆으로 누워 버립니다.
  • 텍스처가 없는 프리미티브는 IsColored != 0이 아니면 보이지 않습니다 — 의사 코드 visible = (texture != "") || (IsColored != 0)이 두 경우를 모두 다룹니다. 가장 흔한 3DO 렌더링 버그입니다.
  • OffsetToSelectionPrim은 루트 오브젝트에서만 의미가 있습니다. 자식 오브젝트는 이 값을 -1(0xFFFFFFFF)로 설정해야 합니다.
  • 고정소수점 배율은 1 / 65536입니다. 0x00010000으로 쓴 정점은 월드 단위 1.0이며, 하위 16비트가 소수 부분입니다.
  • 정점은 전역이 아니라 오브젝트 기준의 지역 좌표입니다. 두 오브젝트의 정점 배열이 똑같을 수 있는데, 이는 데이터를 공유하는 것이 아니라 대칭인 형상일 뿐입니다.

COB / BOS — 스크립트

  • call-script 옵코드는 3.1 이전의 일부 문서가 주장하는 0x10063000이 아니라 0x10062000입니다. 0x10063000을 쓰는 오래된 디컴파일러는 엉터리 결과를 냅니다 — 옵코드 레퍼런스를 참고하세요.
  • 옵코드 안의 피스 번호는 문자열이 아니라 피스 이름 테이블의 인덱스입니다. COB는 이름을 문자열 풀로 담고, 런타임은 속도를 위해 인덱스를 씁니다.
  • SET_SIGNAL_MASK는 관심을 등록할 뿐, 아무것도 종료하지 않습니다. 마스크에 X가 설정된 스크립트를 멈추는 것은 signal X를 보내는 동작입니다. signal-never-signalled 린트 규칙은 아무도 시그널을 보내지 않는 마스크를 설정하는 스크립트를 잡아냅니다.
  • Cavedog의 Scriptor는 CURRENT_SPEED를 리터럴 29로 인라인해서, 왕복 변환 후 MoveWatcher 방식의 스크립트를 깨뜨립니다. kbot의 디컴파일러는 기호 이름을 되살립니다. Scriptor로 왕복 변환해야 한다면 괄호로 감싸세요: (get CURRENT_SPEED) > 5.
  • 점프 오프셋은 현재 명령어가 아니라 OffsetToScriptCode 기준의 바이트 오프셋입니다. JUMP 0x4C는 "OffsetToScriptCode + 0x4C로 점프"라는 뜻입니다.
  • GET_UNIT_VALUE 포트 번호는 TA와 TA:K에서 다릅니다. TA의 목록(kbot 디컴파일러는 포트 1–20에 이름을 붙임)은 부록 B에 정리되어 있습니다.

GAF — 스프라이트 애니메이션

  • 시퀀스는 +2 워드의 하위 바이트가 설정되어 있을 때만 반복합니다. 0이면 한 번 재생하고 멈춥니다. 모든 기본 시퀀스는 1을 저장하므로, 0을 쓰는 도구는 움직이는 지형지물을 멈춰 버립니다.
  • textures/*.gaf와 anims/vismasks.gaf의 프레임은 비압축(raw)이어야 합니다. 게임은 이를 평범한 픽셀 배열로 읽습니다.
  • 일부 시퀀스는 프레임 길이가 0인 채로 출시되었습니다. 게임은 그런 프레임을 한 틱 동안 보여 줍니다. 기본값으로 "고치지" 마세요.
  • 팔레트 인덱스 0은 그 자체로는 투명하지 않습니다. 투명한 것은 비압축 프레임의 키와 압축 프레임의 건너뛰기 런뿐입니다.
  • 압축된 픽셀 행은 Width보다 짧을 수 있습니다. 게임은 다음 바이트까지 이어서 읽고, kbot은 그 행을 투명 픽셀로 채우고 경고합니다. 그런 행이 있는 기본 파일은 없습니다.
  • 레이어 원점은 핫스팟입니다. 합성 프레임의 각 레이어는 자기 원점이 프레임의 원점에 오도록 놓입니다.
  • anims/terrain.gaf와 anims/vismasks.gaf는 헤더에 Version == 0이 기록되어 있습니다. 게임은 버전 워드를 무시합니다.
  • TA: Kingdoms의 .taf는 두 종류입니다. 팔레트 방식 .taf는 GAF 그 자체로, 디스크상 형식이 같고 다만 TAK가 전역 TA 팔레트 대신 진영별 .pcx에서 팔레트를 가져옵니다. 트루컬러 .taf는 같은 컨테이너를 쓰지만 16비트 ARGB 픽셀을 저장하며 팔레트가 필요 없습니다. 판단 기준은 확장자가 아니라 프레임 형식 바이트(0/1 = 팔레트 방식, 4/5 = 트루컬러)입니다 — TAF / TSF를 참고하세요.

TAF / TSF — 트루컬러 애니메이션

  • .taf라는 확장자는 두 가지로 쓰입니다 — 팔레트 방식(GAF로 읽음)과 트루컬러(kbot taf로 읽음). 대부분의 TAK 트루컬러 파일에는 관례상 _4444 / _1555 접미사가 붙지만, 항상 파일 이름이 아니라 프레임 형식 바이트를 확인하세요.
  • GIF 내보내기는 손실이 있습니다. GIF는 1비트 잘라내기와 255색만 담을 수 있으므로, ARGB4444의 부드러운 알파가 딱딱한 가장자리가 됩니다. 진짜 알파 채널을 유지하려면 APNG(kbot taf export --format apng)를 쓰세요.
  • 프레임 이름 필드와 0x0B 플래그 바이트에는 쓰레기 값이 있을 수 있습니다. 바이트 단위로 정확한 왕복 변환을 위해 그대로 보존하고, 정규화하지 마세요.
  • Delay / Duration이 0이어도 유효합니다 — GAF와 마찬가지로 0틱 프레임은 이벤트로 구동됩니다(정적인 메뉴 배경이 이를 씀). "고치지" 마세요.

PCX — 이미지

  • TA는 BytesPerLine을 무시합니다. 표준 리더는 BytesPerLine > Width일 때 패딩을 버리지만, TA 3.1c는 행마다 정확히 Width바이트를 디코딩하므로 패딩이 있는 파일은 게임에서 밀려 보입니다. BytesPerLine == Width로 쓰고 kbot pcx describe로 확인하세요.
  • TA는 버전 5만 로드하며, 모든 파일을 8비트 단일 플레인 데이터로 디코딩합니다.
  • 상위 두 비트가 설정된 리터럴 바이트는 RLE로 이스케이프해야 합니다. 값이 0xC0–0xFF인 바이트를 그대로 리터럴로 쓸 수는 없으므로, 개수 1인 런으로 내보내세요(0xFF 하나는 0xC1, 0xFF). 이를 잘못 처리하면 한 행이 지난 뒤 디코딩이 어긋납니다.
  • 게임은 0x0C 표식이 있든 없든 끝의 768바이트를 팔레트로 씁니다. 표준 리더에는 표식이 필요합니다.

PAL / ALP / LHT / SHD — 팔레트와 조회 테이블

  • 항목마다 있는 네 번째 바이트는 알파가 아닙니다. Cavedog이 항상 0x00으로 설정한, 쓰이지 않는 패딩 바이트입니다. 이를 알파로 취급하는 오픈 소스 팔레트 도구는 팔레트를 제대로 렌더링하지 못합니다(alpha=0 → 모두 투명).
  • 인덱스 0은 투명이 아니라 검은색입니다. 지형, 미니맵, 배경은 이를 불투명하게 그리고, 스프라이트는 각자의 투명 키를 고릅니다.
  • palette.pal이 비어 있거나 없으면 palette.pcx(마지막 768바이트)로 대신합니다. 1,024바이트보다 긴 .pal은 1,024바이트까지만 읽습니다.
  • .alp / .lht / .shd는 RGB 팔레트가 아닙니다 — .alp는 256×256, .shd / .lht는 32×256 크기의 팔레트 인덱스 테이블이며, 게임은 다른 크기의 테이블을 무시하고 다시 만듭니다. kbot pal describe로 렌더링하면 엉뚱한 결과가 나오므로, 대신 kbot pal lookup을 쓰세요.
  • TA 팔레트에는 중복된 RGB 세 값 조합이 13개 있습니다. 에디터가 가져올 때 중복을 제거하면(일부는 그렇게 함) 항목을 잃고, 팔레트는 조용히 243개 항목짜리가 됩니다.

FNT — 비트맵 글꼴

  • Offsets[c] == 0은 "오프셋 0에 있는 글리프"가 아니라 "글리프 없음"을 뜻합니다. 오프셋 0은 헤더 한가운데에 해당하므로, Cavedog은 0을 감시값으로 재활용합니다.
  • 비트 순서는 바이트마다 MSB 우선입니다. 요즘 비트맵 글꼴에서는 LSB 우선이 더 흔하니 지레짐작하지 마세요.
  • 비트 스트림은 행 사이에서 끊기지 않고 이어집니다 — 각 스캔 라인 끝에 패딩이 없습니다.

AI 프로필

  • TA: Kingdoms AI 파일에는 plan 지시문이 없습니다 — weight/limit 줄만 있습니다. kbot의 파서는 암묵적인 default plan을 엽니다. 리더는 블록이 빠진 것을 허용해야 합니다. TA:K — plan이 없는 프로필을 참고하세요.
  • 검증은 어디에도 없습니다. 철자가 틀린 유닛 이름, 잘못된 가중치 값, plan 바깥의 줄 — 모두 조용히 무시됩니다. 프로필이 망가졌다는 첫 신호는 로그 메시지 하나 없이 "AI가 이상하게 행동한다"는 것입니다.
  • 분류 별칭(PLANT, LEVEL3)은 적어도 한 유닛이 Category=에 선언해야만 "존재"합니다. LEVL3처럼 철자를 틀리면 그 지시문은 조용히 아무 일도 하지 않습니다.

사운드

  • WAV가 없으면 오류가 아니라 무음입니다. select1=cmsdel1처럼 철자를 틀리면(글자 순서가 바뀜) 엔진은 아무것도 재생하지 않습니다 — 로그도, 대체 소리도 없습니다.
  • TDF 값에는 .wav 확장자가 자동으로 붙습니다 — 절대 직접 쓰지 마세요. select1=cmdsel1.wav는 sounds/cmdsel1.wav.wav가 되어 아무것도 찾지 못합니다.

TA: Kingdoms 고유 사항

  • TAK는 고친 파일 대부분을 로드하려면 명령줄 인수 -disablecavedogverification이 필요합니다. 이것이 없으면 엔진은 해시가 허용 목록에 없는 모든 것을 거부합니다.
  • TAK 데이터에는 진영 코드가 4개가 아니라 8개 있습니다: 플레이할 수 있는 네 진영의 ARA / TAR / VER / ZON에 더해 CRE(Creon, Iron Plague), MON(몬스터), LIF(야생 동물), NPC(캠페인 등장인물)가 있습니다. 유닛 목록을 필터링할 때는 이를 모두 처리해야 합니다.
  • TAK 무기는 별도의 weapons/*.tdf 디렉터리가 아니라, 각 FBI 안에 [WEAPON1]–[WEAPON3] 하위 섹션으로 들어 있습니다.
  • TAK 건설 메뉴는 2×3 격자가 아니라 한 줄로 이어집니다. 건설 유닛마다 canbuild/<builder>/<unit>.tdf 디렉터리가 있고, 각 파일에는 [Menu] { Priority = N; } 블록 하나가 들어 있습니다.

함께 보기

  • 모딩 튜토리얼 — TA 유닛 모드를 처음부터 끝까지 따라 하는 안내.
  • 용어집 — 위에서 언급한 여러 형식에 걸친 모든 용어의 정의.
  • 빠른 시작 — 먼저 실행해 볼 kbot 명령 다섯 가지.