세가 새턴 폴리스너츠 한글화(작업 후기 3부작 )를 진행하며 실제로 부딪히고 실측으로 확정한 기술 지식을 정리했습니다. 이 게임을 한글화하려면 무엇을 알아야 했는지에 대한 기록입니다. (폴리스너츠는 코나미 새턴 타이틀이라, 여기 나오는 구조·함정의 패턴 상당수는 비슷한 시기 코나미 새턴 게임에도 그대로 적용될 가능성이 높습니다.)
0. 큰 그림 — 무엇을 각오해야 하나
한글화는 “번역"이 30%, “게임이 글자를 다루는 방식을 역분석하는 일"이 70% 입니다. 순서는 대략 이렇습니다.
- 폰트 엔진 해독 — 게임에게 한글이라는 ‘그림’을 가르친다
- 텍스트 시스템 전수 파악 — 대사가 어디에 어떤 형식으로 사는지 전부 찾는다
- 추출 → 번역 → 인코딩 → 주입 파이프라인 구축
- 길이 초과(잘림)와의 싸움 — 이게 가장 길고 어렵다
- 런타임 검증 — 에뮬레이터로 실제 화면 확인
핵심 마인드셋 하나: “있는 그대로를 존중하라.” 원본에 이상해 보이는 게 있으면 (의도적 오류, 중복 데이터, 비표준 정렬) 십중팔구 이유가 있습니다. 함부로 ‘고치면’ 게임이 죽습니다.
1. 텍스트는 한 군데 있지 않다 — 텍스트 시스템 전수 조사
가장 흔한 실패는 “본문 대사만 번역하면 끝"이라고 생각하는 것입니다. 코나미 게임은 서로 다른 텍스트 시스템이 여러 개 공존합니다. 폴리스너츠는 6종이었습니다.
| 시스템 | 내용 | 형식 | 비고 |
|---|---|---|---|
| 본문(SZ) | 스토리 대사·메뉴 | rank 인코딩(아래 §2) | 가장 큰 덩어리 |
| 음성 자막(SX) | 컷신 음성 위 자막 | 스트림에 섹터 정렬로 박힘 | 컨테이너 매직(SX9% 등) |
| 무비 자막(MOV) | 영상(무비) 자막 | DLOG 헤더 + 텍스트 | 영상 스트림 사이 |
| 하드코딩 시스템 문자열 | 부팅 메뉴·세이브·디스크 교체 안내 | 실행 바이너리에 직접 | rank이지만 complement 안 함 |
| 비트맵 이름표(NAMEID) | 화면에 뜨는 인물 이름 그림 | 4bpp 비트맵 플레이트 | 텍스트가 아니라 그림 |
| 미니게임 평문 자막 | 추격/슈팅 씬 자막 | 평문 Shift-JIS | 추출기가 놓치기 쉬움 |
교훈
- 추출기를 한 형식만 스캔하게 짜면, 다른 형식의 텍스트를 통째로 놓칩니다. 폴리스너츠에서 추격씬 자막(평문 SJIS)이 마지막까지 일본어로 남았던 이유가 이것이었죠.
- 번역이 끝났다고 믿은 뒤에도 실제 플레이로 “아직 일본어인 화면"을 사냥해야 합니다.
- 미번역 판별 자동화: 빌드된 ISO를 디코드해 히라가나/가타카나가 남아 있으면 미번역입니다 (한글에는 가나가 없으니까요).
2. 폰트 엔진 — 게임에 한글을 가르치기
2-1. 비트맵 글꼴 구조
- 글자 = 작은 비트맵. 폴리스너츠는 12×12px, 2bpp(0=투명 1=그림자 2=엣지 3=본체), 36바이트/글자.
- 한글화 = 안 쓰는 한자(또는 빈) 슬롯에 한글 글리프 비트맵을 덮어쓰기. 폴리스너츠는 Galmuri9 글꼴을 12×12 2bpp로 렌더해 주입했습니다.
2-2. 텍스트 인코딩 — “코나미 rank” + complement
폴리스너츠 텍스트의 핵심 규칙입니다. 본문(SZ) 기준:
- 글자는 rank 값(폰트 슬롯 인덱스)으로 저장되는데, 파일에는 complement(보수) 형태로 들어갑니다.
complement = ~rank & 0xFF(바이트별). 디코드는 다시 보수를 취하면 됩니다. - 슬롯 → rank 변환은 글자 종류·페이지별로 다른 베이스를 더합니다(히라가나·가타카나·한자 페이지별 상이).
⚠️ 시스템마다 complement 여부가 다릅니다. 본문(SZ)은 complement를 하지만, 하드코딩 시스템 문자열은 native rank 그대로입니다. 같은 게임 안에서도 텍스트 시스템마다 인코딩 규칙을 따로 확인하세요.
2-3. 슬롯 한계는 실측으로
- 폰트가 받아주는 슬롯에는 상한이 있습니다(폴리스너츠는 1953까지 동작, 그 위는 렌더 실패). 한글 음절 수가 이 한도를 넘으면 희귀 음절을 정리해야 합니다. 빈 슬롯 테스트로 직접 찾으세요.
2-4. 폰트가 여러 개일 수 있다
- 게임 전역 폰트 외에, 특정 씬이 다른 폰트 파일을 쓰기도 합니다. 이 경우 “안 쓰는 한자 1글자 = 한글 1음절"로 carrier 매핑하고 그 셀에 한글 글리프를 덮습니다.
- 함정: 동일 폰트가 여러 아카이브에 번들될 수 있습니다. 게임이 실제로 어느 파일을 로드하는지는 세이브스테이트 RAM과 파일 바이트를 대조해 확정해야 합니다. 엉뚱한 사본을 패치하면 화면은 그대로예요.
3. 아카이브·디스크 구조
3-1. DPK 아카이브
- 폴리스너츠는 파일을 DPK 아카이브(매직
DIRF)로 묶습니다. - CRC 우회: DIRF의 CRC 필드를 0으로 쓰면 게임이 무결성 검사를 건너뜁니다. 패치의 기본기.
- 같은 아카이브가 여러 copy로 존재할 수 있습니다(폴리스너츠 GAME DPK는 3 copy). 모든 copy를 패치하지 않으면 게임이 다른 copy를 읽어 일본어가 튀어나옵니다.
3-2. ISO 섹터 — zero-shift 원칙
- 기존 파일의 섹터 위치(LBA)를 옮기지 마세요. 게임은 특정 파일이 특정 섹터에 있다고 하드코딩으로 가정합니다. 옮기면 자막 전멸·부팅 불능.
- 섹터를 새로 추가할 땐 MODE1 raw 구조(sync + MSF + mode + 유저데이터 + EDC/ECC)를 직접 만들어야 합니다. 유저 데이터만 쓰면 드라이브가 섹터를 인식 못 해 부팅 즉시 멈춥니다.
- 자막 I/O는 2048B 유저영역 매핑으로 읽고 쓰세요. 선형으로 다루면 섹터 경계를 넘는 블록이 깨집니다.
3-3. EDC/ECC — 의도적 오류를 존중하라
- 원본 디스크에 일부러 심어둔 EDC 오류가 있을 수 있습니다(복제 방지). “고치면” 오히려 게임이 망가집니다. edcre류 도구 사용 금지. 에뮬레이터는 무시하므로 재계산 불필요(실기 때만 점검).
4. 가장 큰 적 — 길이 초과(잘림)
문제
일본어는 짧고, 한국어로 옮기면 글자 수가 늘어납니다. 대사 ‘상자’는 일본어 크기에 맞춰져 있어 넘치면 화면에서 끝이 잘립니다("…세이브하시겠습”). 프로젝트 내내 따라다니는 최대 숙제예요.
해법 A — relocate(빈방으로 옮기기) · 본문 텍스트용
게임은 “몇 번째 글자부터 읽어라"라는 포인터 값으로 대사 위치를 찾습니다. 이 포인터 계산 규칙을 100% 역분석하면, 긴 한국어 대사를 파일 끝 빈 공간에 적어두고 포인터만 그쪽으로 돌릴 수 있습니다. (좁은 방에 짐을 우겨넣는 대신, 복도 끝 빈방에 옮기고 “이쪽으로” 푯말을 붙이는 발상)
해법 B — 다이어트(번역 단축)
relocate는 포인터를 돌릴 ‘앵커’가 있는 블록에만 통합니다. 앵커가 없는 블록(메뉴·선택지·요약 화면)은 뜻은 유지하되 군더더기를 덜어 일본어 바이트 예산 안에 앉힙니다(“당신의"를 빼고, “향한다”→“간다”).
옮길 수 없는 텍스트 — SX/MOV는 예산 엄수
음성·영상 자막은 스트림 사이에 섹터 정렬로 박혀 있어 relocate 불가. 한국어는 원본 바이트 안에 들어가야 하고 넘치면 빌드가 잘라냅니다. 글자당 거의 2바이트라 짧은 블록은 압축 의역이 필수입니다.
직접 포인터 확장은 보통 막혀 있다
“포인터 값을 그냥 크게 쓰면?” → 대개 불가능합니다. 텍스트 핸들러가 피연산자 첫 바이트를 타입 태그로 쓰기 때문(값이 일정 크기를 넘으면 명령어로 해석됨). 디스어셈블/로거로 한계를 먼저 확인하고, 안 되는 길에 시간을 쏟지 마세요.
5. 함정 모음 — 미리 알면 몇 주를 아낀다
에뮬레이터의 ‘친절’이 만든 착시 (두 달을 잡아먹은 함정).
게임은 대사를 찾을 때 메모리의 특정 ‘칸’에서 위치표를 읽습니다. 그런데 새턴의 CPU는 이런 표를 반드시 짝수 번지(2의 배수 자리)에서 읽어야 한다는 규칙이 있습니다. 제 계산이 한 칸 어긋나 홀수 번지를 가리키고 있었던 게 진짜 원인이었어요. 이게 이른바 정렬(alignment) 오류 입니다 — 줄을 맞춰 서야 하는데 한 칸 삐져나온 상태죠.
문제는, 실기(진짜 새턴)라면 이런 어긋난 읽기는 즉시 에러를 냅니다. 그런데 제가 쓰던 에뮬레이터는 어긋난 번지를 슬쩍 짝수로 맞춰서 읽어줬습니다(나름의 친절한 보정). 그 바람에 어떤 빌드는 우연히 맞는 값을 집어 정상 동작하고, 어떤 빌드는 바로 옆 칸의 엉뚱한 값을 집어 깨졌습니다. 겉으로는 마치 “같은 포인터가 어떨 땐 이 주소, 어떨 땐 저 주소를 가리키는” 것처럼 보였죠. 실제로는 포인터가 둘이었던 게 아니라, 한 칸 어긋난 읽기를 에뮬이 그때그때 다르게 보정한 착시였습니다. 저는 두 달 동안 “주소가 두 개"라는 잘못된 전제로 코드를 짜고 있었던 거예요.
결국 정렬 계산에 쓰는 기준값 숫자 하나를 바로잡자, 두 달간의 유령이 거짓말처럼 사라졌습니다.
→ 교훈 두 가지. ① 증상이 “같은 게 어떨 땐 되고 어떨 땐 안 되는” 식으로 갈리면, 로직을 파기 전에 정렬·오프셋(짝수/홀수, 한 칸 밀림) 부터 의심하세요. ② 에뮬레이터의 관대함이 실기와 다른 결과를 줄 수 있음을 기억하세요 — 에뮬에선 우연히 되던 게 실기에선 안 될 수 있습니다.
추출기가 못 잡는 텍스트들. 빈 레코드에서 break / 멀티섹터(4096B) 청크의 둘째 섹터 누락 / 평문 SJIS 블록. 연결 버퍼로 파싱하고, 여러 형식을 모두 스캔해야 합니다.
렌더러의 짝(2바이트) 읽기. 일부 렌더러는 본문을 2바이트씩 짝지어 읽습니다. 단바이트 (반각 공백·쉼표)를 끼우면 짝이 어긋나 그 뒤 전체가 깨집니다. → ASCII를 전각으로 변환.
다중 사본 — 한 copy만 고치면 화면이 안 바뀝니다.
마커·제어코드 보존. 루비/방점/
%s·%d/버튼 태그 같은 제어 토큰을 일관 규칙으로 처리하세요. 지우면 메뉴 로직이 깨집니다.
6. 작업 방법론 — 어떻게 일하면 덜 고생하나
- 재현 가능한 자동 파이프라인: 문자매핑 → 라운드트립 검증 → 예산/린트 → 빌드 → 정적검증 → relocate → 재검증. 항상 원본 ISO에서 시작(산출물 위 재빌드 금지).
- 세이브스테이트 분석: 깨진 화면의 RAM을 파일 바이트와 대조해 “실제 로드처/깨진 값"을 확정.
- 시금석 회귀 테스트: 까다로운 장면 하나를 모든 빌드의 첫 확인 지점으로(폴리스너츠는 ‘추리소설/담배’ 두 줄).
- 라운드트립 검증: 원문→디코드→인코드→디코드가 일치하는지 자동 검사.
- AI 협업: 방대한 번역은 AI에게, 사람은 가이드 작성·검수·일관성에 집중. 단 AI는 추출 깨짐을 그대로 옮기므로(예: 깨진 글자 ‘의사’가 실은 ‘군대’) 원작 대조 검수가 필수.
7. 체크리스트 (요약)
- 폰트 구조 해독(비트맵 포맷·슬롯·인코딩 rank/complement)
- 폰트 슬롯 상한 실측
- 모든 텍스트 시스템 전수 조사 + 각 인코딩 규칙 개별 확인
- 추출기가 빈 레코드·멀티섹터·평문 블록을 놓치지 않는지 검증
- DPK CRC=0 우회, 모든 copy 패치
- zero-shift 유지, 섹터 추가 시 MODE1 헤더, 자막은 유저영역 매핑 I/O
- edcre 금지
- 잘림: relocate(앵커 본문) + 다이어트(앵커 없는 것) + SX/MOV 예산 엄수
- 시금석 장면 + 라운드트립 + 미번역 자동탐지로 회귀 방지
- 세이브스테이트로 실제 로드처·깨진 값 확정
- 런타임 통플레이 QA로 렌더 버그 사냥
- 배포는 xdelta 차분 패치(게임 데이터 미포함), 면책·원본 해시 명시
이상은 폴리스너츠 한 작품을 한글화하며 부딪힌 것들이지만, “여러 텍스트 시스템 / 폰트 비트맵 주입 / rank 인코딩 / DPK·zero-shift / 잘림과의 싸움 / 함정들” 이라는 뼈대는 같은 시기 코나미 새턴 게임에도 반복될 가능성이 높습니다. 누군가의 다음 한글화에 이 기록이 한 걸음이라도 보태지길.