모딩과 파싱에서 흔히 주의할 점
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의 파서는 암묵적인defaultplan을 엽니다. 리더는 블록이 빠진 것을 허용해야 합니다. 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; }블록 하나가 들어 있습니다.