Skip to content

Repository files navigation

Splash Editor

StarCraft: Brood War / Remastered 맵 에디터. Windows · macOS · Linux.

장기 목표는 SCMDraft 2 수준의 기능 패리티다. StarEdit 수준에서 멈추지 않는다.

맵을 열고, 지형·유닛·로케이션·두대드를 보고 고치고, 트리거를 편집해 저장할 수 있다. 재저장본이 게임에서 정상으로 열리는 것까지 확인했다. 남은 것은 백로그 참고.


지금 되는 것

  • .scm · .scx · .chk 열기
  • 이름 · 크기 · 타일셋 · 버전 · 유닛/로케이션/트리거/문자열 개수 표시
  • 다시 저장 (저장 · 다른 이름으로 저장)
  • 빈 맵 새로 만들기 (CLI)
  • CHK 바이트를 보존하는 round-trip — 편집하지 않은 섹션은 한 바이트도 바뀌지 않는다
  • 지형 보기 — 게임 설치본의 타일셋으로 실제 지형을 그린다. 스크롤 · 확대/축소
  • 유닛 · 로케이션 보기 — 실제 유닛 스프라이트(그림자·플레이어 색 적용), 로케이션 이름
  • 새 맵 만들기 · 맵 속성 — 이름·설명·크기·타일셋
  • 플레이어 설정 — 종족·슬롯·세력과 세력 이름
  • 문자열 편집기 — 맵의 문자열을 쓰임 여부와 함께 보고 고친다
  • 미니맵 — 클릭으로 이동, 보고 있는 영역 표시
  • 격자 표시 · 최근 파일 · 유닛 복사/붙여넣기
  • 맵 스프라이트 — 팔레트에서 골라 배치 (THG2)
  • 크립 보기 — 저그 건물 주변 크립 (토글 가능)
  • 유닛 편집 — 유닛 팔레트로 배치(배치 소리 포함), 클릭 선택, 드래그 이동, 삭제, 실행 취소/다시 실행
  • 로케이션 편집 — 클릭 선택, 드래그 이동
  • 지형 편집 — 세 가지 방식
    • Isometric: 절벽·해안이 자동으로 이어진다 (ISOM 브러시)
    • Rectangular: 브러시 크기만큼 사각으로 칠한다
    • Subtile: 한 칸씩 정밀하게 칠한다 타일 팔레트, Alt+클릭으로 타일 집기
  • 트리거 편집기 — 목록·실행 플레이어·조건·동작을 보고 고친다 (StarEdit 구성)
  • 트리거 텍스트 — SCMDraft 형식으로 전체를 보고 고쳐 적용
  • 두대드 — 팔레트에서 골라 놓기, 고르기, 복사/붙여넣기, 지형으로 풀기, 어긋난 두대드 찾아 고치기
  • 가리개(MASK) — 칠하기, 일괄 적용, 복사/붙여넣기
  • 설정 창 — 유닛·업그레이드·기술 설정, 스위치 이름, CUWP, 미션 브리핑
  • 소리(WAV) — 넣기·빼기·꺼내기·들어 보기
  • 보호된 맵 풀기 — 다시 편집할 수 있게 되돌린다
  • 시나리오 유틸 — 맵 그림으로 저장, 리빌러 깔기·지우기, 자원량 섞기, 유닛 겹쳐 쌓기, 옵저버 트리거
  • 겹쳐 보기 — 높이·지날 수 있는 곳·지을 수 있는 곳·크립·사거리·시야· 전력 범위·길 영역·AI 타운·타일 번호
  • 한글 맵 — CP949·932·936·1252 를 가려내 읽고 쓴다
  • EUD — 트리거 편집기에서 Memory / Memory Masked 조건·동작을 주소로 바로 고친다. 주소 계산기(주소 ↔ EPD ↔ Deaths 자리, 이름으로 찾기), 맵이 건드리는 메모리 자리 훑기(리마스터에서 되는지까지), 그리고 euddraft 로 epScript 컴파일까지 창 안에서 한다
  • 모드 자료 — 갈아 끼운 MPQ 를 얹어 모드 맵을 원래 모습대로 본다. 설치 폴더와 묶어 프로필로 저장한다
  • 자동 저장 — 고친 맵의 사본을 따로 쌓는다
  • 오브젝트 목록 · 여러 보기 창 · 배율 12.5~800%

아직 안 되는 것

  • SCMDraft 브러시 파일(.scb) 읽기 — 공개 스펙이 없다. 우리 브러시는 .splashbrush 로 주고받는다.
  • 리마스터(HD) 그래픽 — 클래식(SD) 자산으로 그린다. SCMDraft 2 도 그렇다.
  • 유닛 애니메이션 — 정지 자세만 그린다.
  • 크립 가장자리 모양 — 게임과 경계가 다르다. 퍼지는 규칙이 실행 파일 안에 있다.
  • 편집기 껍데기 일부 — 플러그인, 오브젝트 트리, 창 나누기, 자동 저장, 프로필·모드 데이터.

까닭과 다시 붙을 실마리는 백로그에 적어 두었다.


빌드

macOS (Apple Silicon)

Apple Silicon(M1/M2/M3/M4) 에서 확인한 절차다. 검증 환경: macOS 15 (Sequoia), Apple clang 21, CMake 4.4, Qt 6.11, arm64 네이티브.

# 1. 사전 준비 — Homebrew 가 이미 있다고 가정한다
brew install cmake ninja qt

# 2. 설정
cmake -S . -B build -G Ninja

# 3. 빌드
cmake --build build

# 4. 실행
./build/src/ui/splash-editor

Xcode Command Line Tools 가 필요하다 (xcode-select --install).

ICU 는 따로 설치하지 않아도 된다. Qt 가 의존성으로 끌어오며, Homebrew 의 ICU 는 keg-only 라 CMake 기본 경로에 없으므로 빌드 스크립트가 brew --prefix 로 자동 탐지한다. 자동 탐지가 실패하면 직접 지정할 수 있다:

cmake -S . -B build -G Ninja -DICU_ROOT=$(brew --prefix icu4c@78)

Qt 가 자동으로 안 잡히면:

cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH=$(brew --prefix qt)

Linux

sudo apt install build-essential cmake ninja-build qt6-base-dev libicu-dev
cmake -S . -B build -G Ninja
cmake --build build

Windows

Visual Studio 2022 (C++20) 와 Qt 6 가 필요하다.

cmake -S . -B build -DCMAKE_PREFIX_PATH="C:/Qt/6.11.2/msvc2022_64"
cmake --build build --config RelWithDebInfo

빌드 옵션

옵션 기본값 설명
SPLASH_BUILD_GUI ON Qt GUI 를 빌드한다. OFF 면 Qt 없이 코어+CLI 만 빌드
SPLASH_BUILD_TESTS ON 테스트를 빌드한다

첫 설정 때 의존성을 네트워크로 받으므로 시간이 걸린다. 이후 빌드는 캐시된다.


사용법

GUI

./build/src/ui/splash-editor              # 빈 창으로 시작
./build/src/ui/splash-editor path/to.scx  # 맵을 열고 시작

# 타일셋을 쓰려면 StarCraft 설치 폴더가 필요하다.
# 한 번 지정하면 기억하므로 다음부터는 생략해도 된다.
./build/src/ui/splash-editor path/to.scx --install "/경로/StarCraft"

설치 폴더는 메뉴 파일 › StarCraft 설치 폴더 지정… 으로도 고를 수 있다. 지정하지 않으면 지형 대신 안내 문구가 뜨고, 나머지 기능은 그대로 동작한다.

조작:

스크롤 스크롤바 · 휠
확대 / 축소 ⌘+ / ⌘- · Ctrl+휠
실제 크기 ⌘0
유닛 표시 ⌘1
로케이션 표시 ⌘2
크립 표시 ⌘3
격자 표시 ⌘G
복사 / 붙여넣기 ⌘C / ⌘V
유닛 · 로케이션 선택 클릭 (겹치면 유닛 우선)
이동 드래그
선택 해제 Esc
선택 삭제 Delete
실행 취소 / 다시 실행 ⌘Z / ⇧⌘Z
선택 / 유닛 놓기 / 지형 도구 S / U / T
타일 집기 (지형 도구) Alt+클릭
타일 · 유닛 팔레트 도구 메뉴 (해당 도구를 고르면 자동으로 열림)
트리거 편집기 ⇧⌘T
트리거 텍스트 보기 ⌃T
새 맵 ⌘N
맵 속성 ⌘I
플레이어 설정 ⌘P
문자열 편집기 ⇧⌘S

CLI

CLI 는 GUI 없이 코어를 두드리는 도구이자 테스트 하네스다. 화면에서 되는 편집은 명령으로도 된다. 명령은 갈래로 묶여 있다.

./build/src/cli/splash-cli help          # 갈래 목록
./build/src/cli/splash-cli unit          # 그 갈래의 명령
갈래 하는 일
unit 놓기·지우기·옮기기·속성·겹쳐 쌓기·애드온 잇기
sprite 놓기·지우기·속성
doodad 놓기·지우기·지형으로 풀기·어긋난 것 찾고 고치기
location 만들기·지우기·이름·크기·높이·안팎 뒤집기
object 유닛·스프라이트·두대드·로케이션을 네모째 오려 붙이기
terrain 칠하기·복사·붙여넣기·대칭·ISOM 브러시
fog 가리개 칠하기·일괄·복사·붙여넣기
map 이름·설명·크기·타일셋
player force 종족·슬롯·세력·색, 동맹·시야 공유
string switch preset 문자열·스위치·CUWP
map encoding 코드 페이지를 보고, --encoding 으로 다시 읽기
unitdef upgrade tech 맵이 정하는 능력치·비용
scenario 리빌러·자원 섞기·맵 밖 치우기·보호 해제·맵 그림
sound 넣기·빼기·꺼내기
trigger briefing 텍스트로 뽑고 되돌려 넣기, 더하기·지우기·베끼기·실행 플레이어, 인자 하나만 고치기
eud 주소 ↔ EPD, 오프셋 표 찾기, 맵 안 EUD 훑기·가려내기, 조건·동작 넣기, euddraft 빌드

고치는 명령은 저장할 곳을 반드시 받는다 — -o <출력맵> 이거나 --in-place. 원본을 말없이 덮어쓰지 않는다. --in-place 는 옆에 먼저 쓰고 바꿔치기하므로, 쓰다 멈춰도 원본이 남는다. 저장한 다음에는 저장본을 다시 열어 확인하고, 그 결과를 한 줄로 찍는다.

# 유닛을 이름으로 놓기 (번호로도 된다). 좌표는 픽셀, --tiles 면 타일
splash-cli unit place map.scx "Terran Marine" 50 30 --tiles --owner 1 -o out.scx

# 속성 바꾸기
splash-cli unit set map.scx 34 --owner 3 --hp 50 --cloaked on -o out.scx

# 로케이션 만들고 높이 조건 주기
splash-cli location add map.scx 10 10 20 20 --tiles --name "시험터" -o out.scx
splash-cli location elevation out.scx 1 저지대,고공 --in-place

# 지형을 베껴 두었다가 다른 자리에 붙이기
splash-cli terrain copy map.scx 10 10 6 3 patch.tiles --no-doodads
splash-cli terrain paste map.scx 40 40 patch.tiles -o out.scx

# 왼쪽 절반을 오른쪽에 거울처럼 베끼기
splash-cli terrain mirror map.scx horizontal -o out.scx

# 본진을 통째로 오려 다른 자리에 붙이기 (애드온·나이더스 연결도 따라온다)
splash-cli object copy map.scx 0 0 20 20 base.objects
splash-cli object paste map.scx 40 40 base.objects --install "/경로/StarCraft" -o out.scx

# 맵 전체를 리빌러로 덮기
splash-cli scenario revealers map.scx --owner 1 --spacing 16 -o out.scx

# 조건·동작을 낱개로 (종류 번호는 trigger types 로 본다)
splash-cli trigger types map.scx condition --install "/경로/StarCraft"
splash-cli trigger set-type map.scx condition 0 1 23 -o out.scx
splash-cli trigger line-enabled map.scx action 0 0 off -o out.scx

# 인자는 수(십진·0x 16진)로도, 이름으로도 넣는다
splash-cli trigger set-arg map.scx action 0 0 0 "Player 3" --install "/경로/StarCraft" -o out.scx
splash-cli trigger set-arg map.scx condition 0 0 3 "at most" --install "/경로/StarCraft" -o out.scx

이름으로 못 찾으면 고를 수 있는 것을 함께 보여 줍니다. 그 목록이 곧 "이 자리에 이름표가 붙은 값" 의 전부입니다 — 이름표가 없는 값(비교의 Set· NotSet 등)은 수로 넣습니다.

지형·유닛을 갈아 끼운 모드 맵은 --mod <mpq> 를 여러 번 주면 설치본보다 먼저 뒤집니다 — 앞에 적은 것이 우선합니다.

글자가 깨져 보이면 코드 페이지 자동 판별이 틀린 것입니다. CHK 에는 코드 페이지 칸이 없어서 맵에 저장해 둘 수 없고, 읽고 쓸 때마다 알려 줘야 합니다.

splash-cli map encoding map.scx --encoding cp949        # 이렇게 읽으면 맞는지 본다
splash-cli string set map.scx 1 "새 글자" --encoding cp949 -o out.scx

진단·검증 명령은 갈래 없이 그대로 쓴다.

# 메타데이터 보기
./build/src/cli/splash-cli info map.scx

# round-trip 검증 — 열고 저장한 뒤 CHK 바이트를 비교한다
./build/src/cli/splash-cli roundtrip map.scx

# 결과를 파일로 남기기 (게임에서 열어 보려면 이쪽)
./build/src/cli/splash-cli roundtrip map.scx /경로/Maps/재저장본.scx

# 시나리오 청크만 꺼내기
./build/src/cli/splash-cli chk map.scx scenario.chk

# 빈 맵 만들기 (확장자로 포맷 결정: .scm=하이브리드, .scx=브루드워)
# --install 을 주면 고른 지형으로 바닥을 채운다. 주지 않으면 타일이 0 으로
# 남아 terrain isom 이 아무것도 놓지 못한다.
./build/src/cli/splash-cli new new.scx 128 128 4 --melee \
    --install "/경로/StarCraft" --terrain Dirt

# 게임 설치본 조사 — 아카이브 종류와 타일셋 데이터 유무를 확인한다
./build/src/cli/splash-cli assets "/경로/StarCraft"

# 지형을 이미지로 뽑기 (타일셋 디코딩 검증용, PPM 출력)
./build/src/cli/splash-cli render map.scx "/경로/StarCraft" out.ppm

# 유닛·로케이션·크립을 겹쳐 그리기
./build/src/cli/splash-cli render map.scx "/경로/StarCraft" out.ppm --units --locations --creep

# 유닛 하나만 검증용으로 뽑기 (격자·경계·중심 표시)
./build/src/cli/splash-cli unit-image "/경로/StarCraft" 176 out.ppm 0 0 1500

# 타일셋 조사 / 타일 시트 뽑기
./build/src/cli/splash-cli tileset-info "/경로/StarCraft" 4

# 램프 타일 찾기 — ISOM 브러시에는 램프가 없어서 타일을 직접 찍어야 한다
./build/src/cli/splash-cli tileset-ramps "/경로/StarCraft" 4
./build/src/cli/splash-cli tile-sheet "/경로/StarCraft" 4 0 16 sheet.ppm

예전 이름(move-unit, place-isom, set-triggers, units, triggers …)도 그대로 받는다. 새 이름은 갈래 쪽이다.


AI 로 맵 만들기

프롬프트 지시로 맵을 만드는 에이전트 스킬이 .agents/skills/ 에 있다. Claude Code 는 .claude/skills/ 만 뒤지므로 .claude 를 .agents 로 잇는 심볼릭 링크를 두었다. 알맹이는 .agents 쪽 한 곳뿐이다.

export SC_INSTALL=/경로/StarCraft

# 밀리맵 뼈대 — 대칭 스타팅, 본진 고지대, 램프, 앞마당, 바깥 멀티
python3 .agents/skills/starcraft-map-melee/scripts/make_melee.py out.scx --players 4 --tileset jungle

# 유즈맵 뼈대 — 장르에 맞는 트리거 고리까지
python3 .agents/skills/starcraft-map-usemap/scripts/make_usemap.py out.scx --config profile.json

# 재기 (자리 밸런스 + 종족 밸런스 기울기) 와 그려 보기
python3 tools/mapgen/verify_map.py out.scx
python3 tools/mapgen/preview.py out.scx look.png

스킬에는 공식 리그 맵 56개와 인기 유즈맵 93개를 실제로 뜯어 잰 값이 함께 들어 있다 — 본진 미네랄 9개·가스 1개, 미네랄 1500·가스 5000, 2인용은 예외 없이 180도 회전 대칭, 유즈맵 트리거 중앙값 153개 같은 것들이다. 종족 밸런스를 어느 손잡이로 기울이는지도 근거와 함께 적어 두었다.

EUD·비표준 크기·unused unit 처럼 판본을 타거나 위험한 것은 쓰기 전에 사용자에게 묻도록 되어 있고, 플레이어 컴퓨터에 해를 끼칠 수 있는 EUD 요구는 거절한다.


EUD

EUD(Extended Unit Death)는 새 트리거가 아니다. Deaths 조건의 플레이어 자리에 플레이어일 수 없는 큰 수를 넣으면 게임이 Deaths 표 밖의 메모리를 읽는다. MappingCore 는 그것을 Memory / Memory Masked 라는 가상 종류로 다루고, 우리도 같은 잣대를 쓴다 — 플레이어 자리가 28 을 넘으면 EUD 다.

자리 셈

Deaths 표는 유닛 바깥, 플레이어 안쪽이다. P1 마린, P2 마린, … P12 마린, P1 고스트 … 꼴로, 한 유닛이 48바이트(12 x 4)를 차지하고 그런 항목이 228개다.

주소 = 0x0058A364 + 4 x (유닛 x 12 + 플레이어)
EPD  = (주소 - 0x0058A364) / 4        (부호 있는 나눗셈)

나눗셈을 부호 없이 하면 안 된다. Deaths 표보다 앞인 자리는 거리가 음수인데, 부호 없이 나누면 같은 주소를 가리키는 다른 답이 나온다. eudplib·EUD Book· SCMDraft 가 모두 부호 있는 쪽을 쓴다 (미네랄은 EPD −11421).

표 밖을 가리킬 때는 유닛을 0 으로 두고 플레이어 자리에 EPD 를 통째로 넣는다. 게임이 유닛 x 12 + 플레이어 를 32비트로 감아 세므로 같은 자리를 가리키고, euddraft·SCMDraft 의 Memory 표기와도 같다.

오프셋 표

붙박이로 165개를 들고 있다. 두 갈래에서 왔다.

  • 자주 쓰는 24개는 우리가 우리말 이름과 설명, 리마스터 지원 여부를 붙여 직접 적었다.
  • 나머지는 eudplib 의 src/eudplib/scdata 에서 뽑았다 — DAT 표(units·weapons·flingy·sprites· images·tech·upgrades·orders)와 플레이어 상태다. eudplib 은 MIT 라 고지문만 지키면 된다. 뽑는 일은 tools/gen_eud_offsets.py 가 하고, 만들어진 표는 src/io/eud_offsets_eudplib.inc 에 있다. 주소가 겹치면 우리말 이름 쪽을 남긴다.

더 필요하면 EUD Book(armoha/eud-book) 의 api.json 을 받아 가리킨다 — 900개가 넘는 자리를 리마스터 지원 여부(Simple Data / Supported / Read Only / Unsupported …)와 함께 쓴다. 그 값은 eudplib 에 없어서, 자리가 리마스터에서 되는지 촘촘히 가려내려면 이 표가 필요하다.

그 파일은 라이선스가 밝혀져 있지 않아 저장소에 넣지 않는다. 쓰려는 사람이 직접 받아 가리킨다. (LICENSE 도 README 도 없고, 담긴 설명문이 EUDDB 계열 문구라 원작자가 armoha 가 아닐 수 있다.)

네 바이트 경계가 아닌 자리

게임의 바이트·워드 값은 네 바이트 경계에 놓여 있지 않은 것이 흔하다 (유닛 색 0x00581D76 처럼). Deaths 는 네 바이트 단위로만 읽고 쓰므로 그런 자리는 담긴 칸을 읽고 비트마스크로 거른다 — Memory Masked 가 있는 까닭이다. 계산기와 eud addr 이 담긴 칸과 마스크를 함께 알려 준다.

$ splash-cli eud addr 0x581D76
  주소      : 0x00581D76
  네 바이트 경계가 아닙니다 — 담긴 칸을 마스크와 함께 읽으세요.
  담긴 칸  : 0x00581D74
  마스크    : 0x00FF0000
  이름      : 플레이어 · unitColor
# 화면: 트리거 › EUD 주소 계산기… › 오프셋 표 불러오기…
#       (한 번 고르면 다음에 켤 때도 그대로 쓴다. '표 비우기' 로 되돌린다)
# CLI:  --db 로 그때그때 가리키거나, 환경 변수로 붙박아 둔다
export SPLASH_EUD_OFFSETS=~/eud-book/api.json

splash-cli eud offsets "hyper"
splash-cli eud addr 0x57F0F0
splash-cli eud list map.scx
splash-cli eud check map.scx --db ~/eud-book/api.json   # 이때만 다른 표를 쓰기

eud check 는 맵이 건드리는 자리 가운데 리마스터에서 안 되는 것과 쓰기가 막힌 것을 가려낸다. 쓰기가 막힌 자리에 쓰는 맵은 리마스터에서 아예 열리지 않는다.

euddraft

epScript 를 컴파일해 맵에 얹는 일은 euddraft 가 한다. 그것을 다시 만들지 않고 바깥 프로그램으로 부른다.

splash-cli eud build base.scx -o out.scx \
    --script hello.eps \
    --plugin eudTurbo \
    --plugin "SCBank: bank=mybank, size=100"

플러그인은 이름 또는 이름: 키=값, 키=값 이다. 화면에서는 트리거 › EUD 빌드 (euddraft)… 이고 플러그인 상자에 한 줄에 하나씩 같은 표기로 적는다. 스크립트를 새로 만들고 고치는 것까지 창 안에서 된다.

--dry-run 을 주면 돌리지 않고 만들어질 .eds 를 그대로 찍는다.

알아 둘 것:

  • .eds 로 부른다. .edd 로 부르면 euddraft 가 파일을 지켜보는 데몬으로 돌아 끝나지 않는다.
  • euddraft 는 맵 보호(freeze)를 기본으로 켠다. 우리는 기본을 끔으로 두었다 — 보호된 맵은 다시 열어 고칠 수 없다. 켜려면 --freeze.
  • euddraft 가 뱉는 맵은 scenario.chk 를 로케일로 숨긴다. 중립 로케일에는 0바이트짜리 미끼를 두고 영어(1033) 로케일에 진짜를 넣는다. 우리 readScenarioChk 는 중립 쪽이 비었으면 로케일을 훑는다.
  • 산출물은 매번 다르다. eudplib 이 페이로드 자리를 섞는다 — 이 맵들은 CHK 바이트로 견줄 수 없다. 편집용 맵과 배포용 맵을 갈라 두는 편이 맞다.
  • macOS 배포본 0.11.0.1 은 보호를 켜면 맵을 다 쓴 뒤 freezeMpq 안에서 SIGBUS 로 죽는다(산출물은 멀쩡하다). 업스트림 PR #178 이 릴리스 뒤에 고쳤으므로 다음 판에서는 없어질 것이다. 보호를 끄면 겪지 않는다.

찾는 곳은 환경 변수 SPLASH_EUDDRAFT, PATH, 그리고 홈·응용 프로그램 폴더의 euddraft* 순이다. splash-cli eud which 로 확인한다.

환경 변수 정리

변수 쓰임
SPLASH_EUDDRAFT euddraft 실행 파일이나 그것이 든 폴더
SPLASH_EUD_OFFSETS EUD Book api.json 경로 (CLI 에서 --db 대신)

테스트

ctest --test-dir build --output-on-failure

테스트는 두 층으로 나뉜다.

  1. 합성 맵 — 코어가 직접 만든 맵으로 round-trip 을 돈다. 저작권 자료가 없어도 CI 에서 항상 돌아간다.
  2. 실제 맵 — tests/maps/ 에 넣어 둔 맵이 있으면 전부 검증한다. 비어 있으면 건너뛰었다고 분명히 출력한다. 합성 맵만으로 통과한 결과를 실제 맵 검증으로 착각하지 않기 위해서다.

실제 맵을 넣는 방법은 tests/maps/README.md 참고.

실측 결과

실제 맵 컬렉션 966개(1MB 미만)로 검증한 결과다.

맵 종류 CHK 바이트 보존
래더 · 공식 맵 100 / 100 (100%)
Enslavers 캠페인 10 / 10 (100%)
Precursor 캠페인 6 / 6 (100%)
Custom 11 / 11 (100%)
배포 유즈맵 397 / 744 (53%)

규격을 지키는 맵은 전부 바이트 단위로 보존된다. 실패는 모두 배포 유즈맵에 몰려 있으며, 원인은 우리 쪽 버그가 아니라 그 맵들이 의도적으로 CHK 규격을 벗어나 있기 때문이다. 확인된 유형:

  • 부풀린 STR/STRx — 문자열 섹션이 9~15MB. 실제 쓰이는 문자열은 일부뿐
  • 가짜 중복 섹션 — 한 맵에 섹션 항목이 414개(정상은 28개 안팎). CHK 는 같은 섹션이 여러 번 나오면 뒤엣것이 이기므로, 앞쪽에 쓰레기를 깔아 에디터를 혼란시킨다
  • 잘린 MTXM — 지형 데이터를 맵 크기보다 작게 넣는다. 게임은 나머지를 0 으로 채우지만, 다시 쓸 때는 정상 크기가 된다
  • 규격 초과 선언 — STR 에 문자열 65536 개 선언(최대 32766), VER 섹션 누락 등. 이런 맵은 저장 자체를 거부한다

이런 맵을 다시 쓸 때 바이트가 달라지는 것은 정규화이지 손상이 아니다. 다만 게임에서 열리는지는 별개 문제이므로, 유즈맵을 편집할 계획이라면 저장본을 게임에서 확인해야 한다.

참고로 MappingCore 의 isProtected() 는 이 판별에 쓸 수 없다 — 위 유형의 맵들이 전부 "보호 아님"으로 보고된다. 그래서 정보 표시에만 쓴다.

round-trip 이 검사하는 것

맵 파일 전체가 아니라 그 안의 CHK(시나리오 청크) 바이트를 비교한다. MPQ 컨테이너는 해시 테이블 배치나 압축 결과가 달라질 수 있어 내용이 같아도 바이트가 달라진다. 보존해야 하는 것은 시나리오 데이터다.


구조

src/
  io/    MappingCore 를 감싸는 얇은 I/O 계층 (Qt 없음)
           map_archive     맵 열기/저장, 구역별 읽기·쓰기
           game_assets     설치본 조사 (CASC / MPQ)
           game_graphics   지형·유닛·아이콘 그리기, 게임 자료 읽기
           text_encoding   CHK 문자열의 코드 페이지 가리기
  chk/   MapDocument — 열린 맵 + 고침 표시 + 실행 취소 (Qt 없음)
  ui/    Qt Widgets GUI — 맵 창, 팔레트, 편집기 창들
  cli/   커맨드라인 프런트엔드
tests/   round-trip 테스트
cmake/   의존성 취득, MappingCore 빌드 정의

그래픽

맵 캔버스는 보이는 영역의 타일만 그린다. 맵 전체를 한 장 이미지로 만들면 256x256 맵이 8192x8192 픽셀, RGBA 로 268MB 가 되기 때문이다. 타일 그림은 타일 ID 별로 캐시한다 — 맵 하나가 쓰는 고유 타일은 보통 수백~수천 개다.

타일 하나(32x32)를 그리는 경로는 전부 MappingCore 가 파싱한 자료를 쓴다:

tileId -> CV5 타일 그룹 -> VX4 메가타일 -> VR4 미니타일(8x8) -> WPE 팔레트

유닛 스프라이트도 같은 방식으로 게임 데이터를 따라간다:

unitType -> units.dat(flingy) -> flingy.dat(sprite) -> sprites.dat(image)
         -> images.dat(GRP) -> GRP 프레임 디코딩

GRP 의 행 압축 규약(투명/단색/얼룩 라인)은 MappingCore 의 Sc::Sprite::PixelLine 이 캡슐화한 것을 그대로 쓴다. 플레이어 색은 팔레트 인덱스 8-15 구간을 tunit.pcx 의 플레이어별 8색 그라데이션으로 바꿔 넣어 표현한다.

스프라이트는 (유닛 타입, 소유자, 자원량 구간) 조합으로 캐시한다.

맵 스프라이트(THG2)도 같은 경로로 그린다 — 액터 초기화만 다르고 레이어 합성은 유닛과 공유한다.

유닛 하나는 단일 이미지가 아니라 여러 이미지 오버레이로 구성된다 (본체 + 그림자 + 부가물). 그 조립은 iscript 가 정하므로 MappingCore 의 AnimContext 를 돌려서 얻는다. 이 계층은 OpenGL 에 의존하지 않아 QPainter 경로에서도 그대로 쓸 수 있다.

그림자는 배경을 어둡게 하는 효과다(dark.pcx 는 "배경색 → 어두운 색" 매핑표다). 스프라이트를 따로 그리는 구조에서는 배경을 모르므로 반투명 검정으로 근사하고, 합성하는 쪽에서 알파를 섞는다.

크립 가장자리에 대하여. 타일셋에는 크립 가장자리 전용 타일이 없다. 네 가지 방법으로 확인했다.

  1. images.tbl 929개 항목에 크립 관련 그래픽이 없다 — 별도 스프라이트가 아니다
  2. 크립 메가타일은 13종뿐이고 전부 가득 찬 질감이다 (Jungle 기준 128~140, 앞뒤는 풀·돌이다)
  3. 그 13종에 비어 있는(투명) 미니타일이 하나도 없다 — 지형이 비쳐 보이는 반투명 가장자리 타일이 아니다
  4. 크립의 미니타일을 함께 쓰는 다른 메가타일을 전부 찾아봤지만, 풀·플랫폼·흙 처럼 어두운 텍스처를 재사용한 것들이었다. Jungle 과 Badlands 둘 다 같다

즉 크립 경계는 타일 단위이며, 게임도 그렇게 퍼뜨린다. 여기서는 건물마다 타원으로 범위를 잡고 테두리만 부드럽게 처리한다 — 게임보다 매끄러운 쪽이다. 게임의 정확한 확산 패턴은 실행 파일 안에 있어 데이터로는 알 수 없다.

직접 확인하려면: splash-cli images-tbl, mega-sheet, find-creep, creep-kin

크립 바닥 타일은 타일셋에서 Creep 플래그가 선 타일 그룹을 찾아 쓴다. 그룹 안에서 실제 메가타일이 배정된 칸만 고르고(남는 칸은 0 으로 채워져 있다), 좌표를 섞어 변형을 골라 격자 줄무늬가 생기지 않게 한다.

코어에는 Qt 타입이 없다. splash_io 와 splash_core 는 Qt 를 링크하지 않으며, 헤더에 표준 라이브러리 타입만 노출한다. UI 는 MapDocument 만 알고, MapDocument 는 UI 를 모른다.

MappingCore 헤더도 마찬가지로 src/io/map_archive.cpp 안에만 존재한다. chk.h 하나가 90KB 를 넘고 리플렉션 매크로를 끌고 오므로, pimpl 뒤에 가둔다.


의존성

전부 CMake FetchContent 로 커밋 해시를 고정해 가져온다. 소스를 복사해 넣지 않으므로 업스트림 이력을 추적할 수 있고, 고정 해시를 쓰므로 업스트림 변경으로 CHK 파싱 동작이 조용히 바뀌는 일을 막는다.

라이브러리 용도 라이선스
MappingCore (Chkdraft) CHK 파싱 · 맵 구조 MIT
StormLib MPQ 아카이브 (맵 파일) MIT
CascLib CASC 아카이브 (리마스터 설치본 에셋) MIT
RareCpp MappingCore 가 쓰는 리플렉션 MIT
ICU UTF-8 / UTF-16 변환 Unicode-DFS-2016
Qt 6 Widgets · Multimedia GUI · 배치 소리 LGPLv3 (동적 링크)

CHK 와 지형 파싱은 MappingCore 만 재사용한다. 파서를 직접 다시 만들지 않는다.


라이선스

Splash Editor 자체는 MIT 다. LICENSE 참고.

Qt 와 LGPLv3

Splash Editor 는 Qt 6 을 LGPLv3 조건으로 사용하며, 그 의무를 다음과 같이 지킨다.

  • 동적 링크만 쓴다. Qt 를 정적 링크하지 않는다. 빌드 스크립트가 정적 Qt 를 감지하면 설정 단계에서 빌드를 중단한다 (src/ui/CMakeLists.txt).
  • Qt 는 수정하지 않는다. 배포판 Qt 를 그대로 쓴다.
  • 사용자는 Qt 를 자신이 고른 다른 버전으로 교체할 수 있다 — 동적 링크이므로 호환되는 Qt 6 공유 라이브러리로 바꿔 넣으면 된다.

Qt 소스 코드 받는 법. Qt 는 LGPLv3 에 따라 소스를 제공받을 권리를 준다.

이 저장소가 배포하는 바이너리에 대응하는 정확한 Qt 버전은 빌드 시점의 qmake --version 출력으로 확인할 수 있다.


알려진 한계

  • 보호된 맵 — 위 실측 결과 참고. 문자열 칸을 한계 너머로 부풀려 둔 맵은 '보호 해제'를 먼저 해야 저장된다. 아카이브를 손질해 둔 맵은 통째로 새로 써서 저장하며, 이름을 알 수 없는 파일은 옮기지 못한다.
  • 포맷 변환 없음 — 저장은 원본 버전을 유지한다. 하이브리드 맵을 브루드워로 바꾸는 식의 변환은 아직 없다(의도적으로 뺐다 — 자동 변환이 섹션을 조용히 바꾸기 때문이다).

참고한 것

  • Chkdraft — 코어(MappingCore)만 재사용한다. UI 는 복사하지 않는다.
  • ChkForge — Qt 와 코어를 잇는 방식에서 아이디어만 참고했다.
  • SCMDraft 2 — 동작과 저장 결과의 오라클. 코드 공급원이 아니며 역공학하지 않는다.

개발 규칙과 지켜야 하는 제약은 AGENTS.md 에 있다.

About

StarCraft: Brood War / Remastered map editor aiming for SCMDraft 2 feature parity

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages