롬 분석 노트 · 슈퍼 패미콤
롬 분석 노트: 드래곤볼 Z 초오공전 돌격편
드래곤볼 Z 초오공전 돌격편 한글패치 v1.2을 만들며 남긴 기록입니다. 개념은 각 절의 「가이드」 링크에서 처음부터 풀어 설명합니다.
이 롬을 다시 분석하거나 패치를 이어 고칠 사람을 위해, 확인한 주소와 형식을 적어 둔 기록입니다. v1.0은 한자와 가나가 있던 폰트 자리 1,010칸에 한글을 다시 그려 썼고, 그 칸은 거의 다 차 있었습니다. 대사를 원문에 맞춰 다시 옮기니 꽃·쫓·흙처럼 그 칸에 없는 글자가 필요해졌습니다. v1.1은 게임이 글자 그림을 찾는 코드에 새 길을 하나 끼워 넣어 512자를 더 넣을 수 있게 했습니다. 아래는 v1.1에서 새로 그린 213자를 롬에서 꺼낸 것입니다.
1. 이 노트에서 쉽게
한글패치는 일본판 롬의 바이트 일부를 바꾼 것입니다. 이 노트는 그 바뀐 곳을 롬 주소와 실제 바이트로 하나씩 따라갑니다. 돌격편 한글패치는 세 번에 걸쳐 쌓였고, 빌드도 이 순서대로 일본판에 v1.0 IPS를 먼저 적용한 다음 v1.1·v1.2의 도구를 차례로 돌립니다.
각 버전이 화면에서 무엇을 바꿨는지는 패치 페이지의 v1.2·v1.1 패치 노트에 비교 화면으로 있습니다. 이 노트는 그 밑의 롬 이야기입니다. 처음 읽는다면 아래 장을 먼저 보면 따라가기 쉽습니다.
- 0장 롬과 패치 — IPS 패치가 무엇을 적는지. 이 패치는 v1.0 IPS 위에 쌓입니다.
- 4장 주소와 뱅크 — $A8:800C 같은 주소를 파일 위치로 바꾸는 법.
- 8장 대사는 어떻게 저장되나 — 글자 코드와 포인터 표. 3절이 이 장의 돌격편 예입니다.
- 7장 그림과 글자 — 2bpp 타일과 가변폭 폰트. 4절의 바탕입니다.
주소는 $B7:97BC처럼 쓰면 CPU가 보는 뱅크:주소이고, 0x1B97BC처럼 쓰면 헤더 없는 롬 파일의 위치입니다. 패치 소스의 롬 분석 문서는 롬에서 다시 확인한 것을 [실측], 짐작한 것을 [추정]으로 나눠 적었고, 이 노트도 확인한 것만 옮겼습니다.
2. 롬 지도 쉽게
돌격편 롬은 2MB이고, 32KB짜리 뱅크 64개($80–$BF)로 나뉩니다. 아래는 v1.0 롬과 v1.1 롬을 바이트 단위로 비교해 바뀐 뱅크를 칠한 지도입니다. 대사와 텍스트는 원래 있던 뱅크 안에서 다시 채웠고, 롬의 빈 곳에 새로 쓴 것은 글자 블록 3곳과 $B7:FA00의 새 코드뿐입니다. 롬 크기는 늘리지 않았습니다.
롬 헤더: 파일 0x7FC0의 64바이트
롬 파일의 0x7FC0부터 64바이트에는 게임 이름과 롬 크기 같은 정보가 정해진 자리에 있습니다. 16진수 두 자리가 1바이트이고(1장 16진수), 2바이트 값은 작은 자리부터 적힙니다(2장 롬에서 주소가 거꾸로 보이는 이유).
| 파일 | 바이트 | 항목 | 뜻 |
|---|---|---|---|
0x7FC0 | 53 46 58 … 20 | 이름 | "SFX DBZ TOTSUGEKIHEN " (21바이트) |
0x7FD5 | 30 | 맵 모드 | LoROM, FastROM |
0x7FD6 | 02 | 카트리지 | 롬 + RAM + 배터리 |
0x7FD7 | 0B | 롬 크기 | 2^11 KB = 2MB |
0x7FD8 | 03 | SRAM 크기 | 2^3 KB = 8KB |
0x7FD9 | 00 | 지역 | 일본 |
0x7FDC | 0F EF F0 10 | 체크섬 보수·체크섬 | 일본판 $EF0F / $10F0 |
맵 모드 $30은 2진수로 00110000입니다. 오른쪽 네 칸 0000은 LoROM, 비트 4는 FastROM을 뜻합니다(1장 헤더 바이트 하나에 정보 두 개). 아래 칸을 눌러 비트를 켜고 끄면 값이 바뀝니다.
체크섬은 롬 전체 바이트를 더한 값의 아래 16비트이고, 보수는 그 값을 뒤집은 것입니다. 일본판은 $10F0, v1.1은 $2B67(보수 $D498)입니다. 한 바이트라도 고치면 값이 바뀌므로 빌드 마지막에 다시 계산해 넣습니다.
주소 읽는 법
CPU는 롬을 뱅크:주소로 봅니다. 이 게임은 LoROM이라 뱅크마다 뒤쪽 32KB($8000–$FFFF)에만 롬이 보입니다. 그래서 파일 위치는 다음처럼 계산합니다(4장 CPU 주소와 파일 오프셋은 다르다).
파일 오프셋 = (뱅크 & 0x7F) × 0x8000 + (주소 & 0x7FFF)
$A8:800C → 0x28 × 0x8000 + 0x000C = 0x14000C아래 위젯은 3절에 나오는 대사 포인터 주소 $A8:800C에서 시작합니다. 다른 주소를 넣어 볼 수도 있습니다.
게임기 안의 메모리
| 이름 | 크기 | 하는 일 |
|---|---|---|
| 롬 | 2MB | 프로그램·대사·폰트·그림. 뱅크 $80–$BF의 $8000–$FFFF에 32KB씩 보입니다. |
| WRAM | 128KB | CPU가 계산하며 값을 적어 두는 곳. 지금 띄울 대사 번호($4D) 같은 것이 여기 있습니다. |
| VRAM | 64KB | 화면에 붙일 8×8 타일과 타일맵을 올려 두는 곳. |
| SRAM | 8KB | 전원을 꺼도 남는 세이브. 헤더 0x7FD8의 값이 크기입니다. |
더 깊이이 노트에 나오는 WRAM 변수
| 주소 | 뜻 |
|---|---|
$4D | 지금 띄울 대사 ID. 대사 뱅크 분기 $81:AC4C가 읽습니다. |
$0208 | 지금 줄에서 이번 글자가 시작하는 x 위치(px) |
$020A | 창 폭(px) = $020E × 8 |
$020E | 창 폭(8px 칸 수) |
$0210 | 창 높이(8px 줄 수). 대사 창은 6 = 글자 3줄 |
$32 | 지금 줄의 위치(8px 줄 수, 한 줄마다 2씩 늚) |
$7E:0263 | 디버그 메뉴의 시나리오 번호 |
$7E:0A00 | 엔딩 암호 문자열이 만들어지는 곳 |
$7E:0B01 | 주인공 이름(16비트 글자 코드 5개) |
$4D는 그래픽을 읽는 루틴($81:A703, $81:A8B0)도 잠깐 씁니다. 대사를 기록할 때 이 값을 그대로 믿으면 엉뚱한 번호가 섞입니다(9절).
새로 쓴 빈 공간
늘어난 코드와 글자는 롬 안의 빈 곳에 넣었습니다. 다만 0xFF가 길게 이어진 곳이라고 다 빈 공간은 아닙니다. 바로 앞의 자료가 0xFFFF를 끝 표시로 쓰고 있을 수 있습니다. 같은 엔진의 각성편 v1.1이 이것을 놓쳐 우주선이 맵 밖을 헤매는 버그를 냈습니다. 돌격편은 일본판 원본에서도 0xFF였는지, 앞의 자료가 그 값을 읽지 않는지 확인한 곳만 썼습니다(6장 새 코드를 둘 빈 공간).
| 자리 | 넣은 것 | 버전 |
|---|---|---|
$B7:FA00–$B7:FA77 | 글자 블록을 찾는 새 코드와 표 | v1.1 |
$81:EF00–$81:FEFF | 글자 블록 0 | v1.1 |
$84:EE00–$84:FDFF | 글자 블록 1 | v1.1 |
$86:E000–$86:EBFF | 글자 블록 2 (3KB만 씀) | v1.1 |
$86:EC00–$86:ED7F 일부 | 「펴」「플」「딩」「렉」 | v1.2 |
$B7:FB00–$B7:FB6B | 기술습득표·이벤트 표의 표시 루틴 | v1.2 |
롬을 늘리지 않고 이렇게 빈 곳을 찾아 쓰는 방법은 11장 늘리지 않고 자리를 만드는 법에서, 반대로 1MB 롬을 2MB로 늘린 사례는 제절초 노트에서 볼 수 있습니다.
3. 대사 쉽게
롬 안의 대사는 글자가 아니라 숫자입니다. 돌격편은 글자 하나가 2바이트 코드이고, 작은 쪽 바이트를 먼저 적습니다. 0A 00은 줄바꿈, 00 00은 대사 끝입니다. 대사 $006 「어떻게/잡아 볼까‥」를 v1.1 롬에서 그대로 꺼내면 이렇습니다.
F3 08어B5 06떻27 05게0A 00줄바꿈A1 09잡C6 08아C5 02빈칸E5 07볼85 05까6B 01‥00 00끝
어느 숫자가 어느 글자인지는 게임마다 다르고, 한글패치는 v1.0이 만든 글자 표(tables/kr-v1.0.txt)를 이어 씁니다. 글자 표와 제어 코드는 8장 글자표와 제어 코드에서 다룹니다.
대사 번호에서 문자열까지
대사는 5,370줄이고 게임은 대사 ID로 찾습니다. 대사를 띄울 때 분기 루틴($81:AC4C)이 ID를 큰 범위부터 비교해 뱅크를 고르고, 그 뱅크의 포인터 표에서 문자열 주소를 읽습니다. 위 대사는 이렇게 찾습니다.
- 대사 ID $006은 $000–$25B 범위라 뱅크 $A8입니다.
- 그 뱅크의 포인터 표에서 ID 순서대로 2바이트를 읽습니다. $A8:8000 + $006 × 2 = $A8:800C(파일 0x14000C)에 FE 85가 있습니다.
- 같은 뱅크의 $A8:85FE(파일 0x1405FE)에 위 문자열이 있습니다.
- 창 정보는 $8E:C9BA + $006 × 2 = $8E:C9C6에 있는 19 06입니다. 첫 바이트가 FF가 아니라 얼굴 창입니다.
이 과정을 그림으로 따라가는 예가 8장 포인터 표에 있고, 주소를 파일 위치로 바꾸는 계산은 위 2절의 위젯으로 해 볼 수 있습니다.
대사는 뱅크 밖으로 못 나간다
포인터는 2바이트라 뱅크 번호가 없고, 표가 있는 뱅크 안의 주소만 가리킵니다. 그래서 대사가 길어져도 다른 뱅크로 넘길 수 없습니다. 대사가 들어 있는 뱅크는 9곳입니다. v1.1은 뱅크마다 일본어 문자열이 있던 영역 전체를 처음부터 다시 채웠고, 똑같은 문자열은 하나만 두고 포인터 여러 개가 같이 가리키게 해서 공간을 아꼈습니다.
| 대사 ID | 뱅크 | 포인터 표 | v1.1 사용량(바이트) |
|---|---|---|---|
000–25B | $A8 | $A8:8000 | 27,164 / 31,520 |
25C–570 | $A9 | $A9:87D6 | 25,284 / 29,144 |
571–81B | $AB | $AB:8000 | 27,742 / 31,328 |
81C–A64 | $9D | $9D:8000 | 28,376 / 31,484 |
A65–CC6 | $9E | $9E:8000 | 27,582 / 31,512 |
CC7–F57 | $B2 | $B2:8000 | 27,452 / 31,380 |
F58–121E | $B4 | $B4:8000 | 27,184 / 31,312 |
121F–14AC | $B5 | $B5:8000 | 28,058 / 31,430 |
14AD–14F9 | $8E | $8E:B67A | 4,284 / 4,774 |
같은 문자열을 여러 ID가 함께 가리키므로, 한 문자열을 제자리에서 고치면 다른 대사까지 같이 바뀔 수 있습니다. 같은 엔진의 각성편은 다시 채우지 않고 안 들어가는 줄만 같은 뱅크 끝으로 옮겼습니다. 두 방법의 차이는 8장 번역이 길어지면 어디에 두나에 정리했습니다.
창 폭과 페이지
대사마다 창 정보 2바이트가 $8E:C9BA + ID × 2에 있고, 첫 바이트가 창 모양을 정합니다. 창 높이는 모두 8px 줄 6개, 곧 글자 3줄입니다.
| 첫 바이트 | 창 | 폭 | 일본판 대사 수 |
|---|---|---|---|
FF | 나레이션 창 | 22칸 = 176px | 38줄 |
FE | 얼굴 그림 없는 창 | 16칸 = 128px | 347줄 |
FD | 창을 열지 않음 | - | 0줄 |
그 밖 | 얼굴 창 | 16칸 = 128px | 4,985줄 |
이 게임은 글자마다 폭이 다른 가변폭 폰트를 씁니다(7장 폰트: 고정폭과 가변폭). 줄이 창 폭을 넘으면 엔진이 알아서 줄을 바꾸는데, 그러면 한 줄이 밀려 페이지가 엉뚱한 곳에서 넘어갑니다. v1.0 롬의 대사를 창마다 실제 폭으로 재 보니 이렇게 넘치는 줄이 13줄 있었습니다.
v1.0 첫 페이지
v1.0 다음 페이지
v1.1
글자 폭은 표가 아니라 글자를 그리는 코드 안의 비교문($B7:97DC부터)이 정합니다. 위 대사는 셋째 줄이 122px까지 차 있어서 9px인 「!」를 더하면 128px을 넘었고, 「!」만 다음 페이지에 혼자 나왔습니다.
| 글자 코드 | 폭 | 무엇 |
|---|---|---|
0x500 이상 | 13px | 한자 자리 = 한글 16×16 글자 |
0x100–0x4FF (아래 예외 말고) | 9px | 가나 자리. 빈칸 0x2C5, 「!」 0x16C |
0x8000을 켠 작은 글자 | 8px / 12px | 0x500 미만 8px, 이상 12px |
0x2C7, 0x2A2 / 0x2C6 | 5px / 4px | 좁은 기호 |
0xBE4 / 0xBE5 / 0xBE6 / 0xBE7 | 2 / 4 / 6 / 7px | 자리 맞춤용 빈칸 |
v1.1은 이 규칙대로 글자마다 폭을 더해 모든 줄을 검사했고(tools/layout1.py), 넘치는 줄이 0이 될 때까지 고쳤습니다. v1.1 롬에서 가장 긴 줄은 나레이션 174px, 얼굴 창 128px입니다. 줄을 나눌 때는 이렇게 정했습니다.
- 단어 중간에서 끊지 않습니다.
- 위아래 줄 길이를 비슷하게 맞춥니다.
- 필요한 것보다 줄을 많이 쓰지 않습니다.
- 더듬는 말(「주‥죽어」)은 한 줄에 둡니다.
더 깊이넘침을 판정하는 코드와 Mesen 2로 잰 한계
창 크기는 $81:AD51–$81:ADDD가 창 정보 첫 바이트를 보고 정합니다. 나레이션이면 $020E = 0x16(22칸), 그 밖이면 0x10(16칸)이고, 이어서 $B7:9729가 $020A = $020E × 8을 넣고 x 위치와 줄 위치를 0으로 만듭니다. 글자마다 아래 코드가 돕니다.
$B7:9846 CLC / ADC $0208 ; 이번 글자 폭 + 지금 x = 이번 글자의 오른쪽 끝
$B7:984A CMP $020A ; 창 폭과 비교
$B7:984D BEQ / BCC $B79890 ; 같거나 작으면 그대로 그림
$B7:9851 INC $32 ×2 ; 크면 다음 줄로 ($32 += 2)
$B7:9857 CMP $0210 ; $32가 $0210 이상이면 페이지 대기 뒤 새 페이지그래서 글자 폭 합이 창 폭과 같은 줄까지는 한 줄에 들어갑니다. v1.1 롬의 첫 나레이션(대사 $000)을 시험 줄로 바꿔 Mesen 2에서 띄워 확인했습니다. 「어」×8 + 「!」×8 = 176px 줄은 한 줄에 다 나왔고, 「어」×6 + 「!」×11 = 177px 줄은 마지막 「!」가 셋째 줄로 넘어갔습니다. 창 정보를 얼굴 창으로 바꾸면 128px까지 들어가고 129px에서 넘어갔습니다.
일본판은 첫 나레이션 첫 줄이 딱 176px입니다. 같은 엔진인 각성편은 나레이션 창이 21칸(168px)이라 돌격편보다 한 칸 좁습니다(각성편 노트의 대사 절).
4. 글자 쉽게
게임은 글자 코드를 받아 폰트에서 그 번호의 그림을 찾아 화면에 찍습니다. 돌격편은 코드 범위에 따라 폰트를 읽는 곳이 다릅니다. 일본판의 한자 자리는 16×16, 가나 자리는 8×16 그림입니다.
| 코드 | 크기 | 폭 | 원래 글자 | 롬 위치 |
|---|---|---|---|---|
0x100–0x4FF | 8×16 | 9px | 가나 | $83:DE00 |
0x500–0x7EF | 16×16 | 13px | 한자 | $83:8000 |
0x7F0–0xBEF | 16×16 | 13px | 한자 | $BF:8000 |
0xC00–0xFFF | 16×16 | 13px | v1.1이 새로 만든 칸 | 블록 8곳 |
글자 그림은 2bpp 타일입니다. 한 픽셀에 비트가 두 개(플레인 0·1)라 네 가지 값을 가집니다. 돌격편 폰트는 글자 픽셀을 플레인 0만 1(색 1)로, 그 오른쪽·아래·오른쪽 아래의 빈 칸을 두 플레인 모두 1(색 3)로 그려 그림자를 붙입니다. 아래 위젯은 이 규칙으로 그린 「가」에서 시작합니다. 칸을 눌러 바꾸면 타일 바이트가 따라 바뀝니다(7장 픽셀 하나에 비트 몇 개).
폰트 칸이 모자랐다
v1.0은 한자 자리 850개와 가나 자리 160개에 한글을 그려 넣었습니다. 아래는 v1.0 롬의 16×16 칸 전체입니다. 남색은 한글이 들어간 칸, 노란색은 한글이 아닌 칸(기호와 남은 한자)입니다.
한글 음절은 자주 쓰는 것만 해도 2,350자입니다. 1,010칸에는 v1.0 번역문에 쓴 글자만 골라 넣을 수 있었고, 대사를 원문에 맞춰 다시 옮기니 꽃·쫓·흙처럼 칸에 없는 글자가 필요해졌습니다. 한글을 좁은 폰트 칸에 넣는 여러 방법은 7장 한글 수천 자를 제한된 폰트 칸에 넣기에 있습니다.
글자 칸을 512개 늘리기
글자를 그리는 코드($B7:9763)는 코드 범위를 보고 어느 폰트를 읽을지 고릅니다. v1.1은 그중 0x7F0 이상 코드를 맡는 갈림길($B7:97BC, 파일 0x1B97BC)의 4바이트를 "새 코드로 가라" 는 명령으로 덮어썼습니다. 원래 코드 중간에 새 코드를 끼워 넣는 이 방법을 후킹이라고 합니다(6장 JSL과 RTL).
38 E9 F0 07일본판: SEC / SBC #$07F05C 00 FA B7v1.1: JML $B7FA00
새 코드는 일본판에서 0xFF였던 $B7:FA00부터 0x78바이트입니다. 코드가 0xC00보다 작으면 원래 처리를 그대로 하고, 크면 블록 표를 보고 새 글자 블록 8곳 가운데 하나를 읽습니다. 블록 하나는 4KB이고 글자 64개(코드 128개)가 들어갑니다.
| 블록 | 코드 | 위치 | 파일 | v1.2 |
|---|---|---|---|---|
| 0 | C00–C7F | $81:EF00 | 0x0EF00 | 사용 |
| 1 | C80–CFF | $84:EE00 | 0x26E00 | 사용 |
| 2 | D00–D7F | $86:E000 | 0x36000 | 사용 (v1.1 D57까지, v1.2 D63까지) |
| 3 | D80–DFF | $86:F000 | 0x37000 | 비어 있음 |
| 4 | E00–E7F | $A6:E600 | 0x136600 | 비어 있음 |
| 5 | E80–EFF | $AE:EA00 | 0x176A00 | 비어 있음 |
| 6 | F00–F7F | $B1:E500 | 0x18E500 | 비어 있음 |
| 7 | F80–FFF | $B6:E600 | 0x1B6600 | 비어 있음 |
8곳 모두 일본판과 v1.0에서 4KB 전부 0xFF였습니다. 글자 폭은 원래부터 0x500 이상 코드를 모두 13px로 계산하므로 따로 고칠 필요가 없었습니다. 새로 그린 213자는 먼저 v1.1에서 더 쓰지 않게 된 칸 37개에 넣고, 나머지 176자를 블록 0–2(코드 0xC00–0xD57)에 넣었습니다. 글자 모양은 픽셀 폰트 Galmuri11로 그리고, 그림자는 위 규칙으로 붙였습니다.
새 블록의 글자는 이렇게 찾습니다. 「쫓」은 코드 0xCE5입니다. 0xC00을 빼면 0xE5이고, 블록 하나의 코드 수 0x80으로 나누면 몫 1, 나머지 0x65입니다. 1번 블록은 $84:EE00이고 코드 하나가 32바이트이므로 $84:EE00 + 0x65 × 32 = $84:FAA0(파일 0x27AA0)에 글자가 있습니다. 16×16 글자는 위 타일 둘과 0x100바이트 뒤의 아래 타일 둘로 나뉩니다.
같은 엔진의 각성편은 폰트 형식이 달라 다른 방법으로 글자를 늘렸습니다(각성편 노트의 「폰트에 없는 글자」).
더 깊이새 코드와 블록 표의 실제 바이트
$B7:FA00 CMP #$0C00 ; 코드가 0xC00 이상이면
BCS ext ; 새 블록으로 간다
... ; 아니면 원래 $BF 폰트 처리 뒤 JML $B797D6
$B7:FA23 ext:
SEC
SBC #$0C00 ; 0xC00을 빼고 0x80으로 나눠 블록 번호를 구한다
... ; 나머지 × 32 = 블록 안 위치
LDA $B7FA60,X ; 블록 표에서 그 블록의 주소를 읽고
JML $B797D6 ; 원래 글자를 그리는 코드로 돌아간다v1.1 롬의 블록 표는 이렇습니다. 주소는 2바이트씩 작은 자리부터, 뱅크는 1바이트씩입니다.
$B7:FA60 00 EF 00 EE 00 E0 00 F0 00 E6 00 EA 00 E5 00 E6 ; $EF00 $EE00 $E000 $F000 $E600 $EA00 $E500 $E600
$B7:FA70 81 84 86 86 A6 AE B1 B6 ; 뱅크글자를 더 넣을 때는 빈 블록 3–7(320자)을 그대로 쓰면 됩니다. 블록 표는 이미 여덟 곳을 다 가리키고 있어 고칠 필요가 없습니다.
v1.2에서 더 그린 글자
「플」「딩」「렉」은 16px 글자가 없어서 가나 자리의 8px 글자(0x24B, 0x148, 0x14D)를 대사와 메뉴에 빌려 쓰고 있었습니다. 16px 글자 사이에서 반 칸으로 보였습니다. v1.2는 블록 2의 빈 칸(0xD61–0xD63)에 16px로 새로 그리고, 8px 글자는 이름 입력과 엔딩 암호에만 남겼습니다. 「펴」도 새 16px 글자(0xD60)로 그렸습니다(7절).
v1.1
v1.2
5. 그림·압축 쉽게
타이틀 화면은 그림 여러 겹을 겹쳐서 만듭니다. 오공 그림, 배경 그라데이션, 로고와 글자가 있는 층(BG3)입니다. 한글패치는 이 중 글자 층만 고쳤습니다. v1.1은 「超悟空伝 突激編」을 한글로 다시 그리고, 로고 아래 가타카나는 지웠습니다.
v1.0
v1.1
압축된 글자 층
글자 층의 타일과 배치표는 LZSS로 압축되어 있습니다. 앞에 나온 무늬가 다시 나오면 "몇 칸 앞에서 몇 바이트 복사" 로 줄여 쓰는 방식입니다(9장 LZSS: 플래그 바이트로 둘을 섞기). 일본판 타일 자료($B7:9EB1, 파일 0x1B9EB1)의 첫 14바이트를 게임이 푸는 순서대로 읽으면 이렇습니다.
| 읽은 바이트 | 종류 | 뜻 |
|---|---|---|
60 13 | 크기 | 처음 2바이트는 풀었을 때의 크기입니다: $1360 = 4,960바이트. |
DC | 플래그 | 플래그 바이트 DC = 11011100. 아래 비트(비트 0)부터 뒤따르는 항목 8개의 종류를 알려 줍니다. |
2F 01 | 복사 | 비트 0 = 0 → 복사: 워드 $012F에서 거리 = $012 = 18, 길이 = 15 + 3 = 18. 출력 0번 칸부터 18칸 앞(시작보다 18칸 앞)에서 18바이트(00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00)를 가져옵니다. 시작보다 앞은 0으로 읽습니다. |
F0 00 | 복사 | 비트 1 = 0 → 복사: 워드 $00F0에서 거리 = $00F = 15, 길이 = 0 + 3 = 3. 출력 18번 칸부터 15칸 앞(3번 칸)에서 3바이트(00 00 00)를 가져옵니다. |
01 | 그대로 | 비트 2 = 1 → 그대로 1바이트 01. 출력 21번 칸에 그대로 씁니다. |
0F | 그대로 | 비트 3 = 1 → 그대로 1바이트 0F. 출력 22번 칸에 그대로 씁니다. |
3F | 그대로 | 비트 4 = 1 → 그대로 1바이트 3F(?). 출력 23번 칸에 그대로 씁니다. |
89 00 | 복사 | 비트 5 = 0 → 복사: 워드 $0089에서 거리 = $008 = 8, 길이 = 9 + 3 = 12. 출력 24번 칸부터 8칸 앞(16번 칸)에서 12바이트(00 00 00 00 00 01 0F 3F 00 00 00 00)를 가져옵니다. 가져올 곳과 쓸 곳이 겹쳐서, 방금 쓴 바이트를 다시 읽으며 반복합니다. |
1F | 그대로 | 비트 6 = 1 → 그대로 1바이트 1F. 출력 36번 칸에 그대로 씁니다. |
FF | 그대로 | 비트 7 = 1 → 그대로 1바이트 FF. 출력 37번 칸에 그대로 씁니다. |
14바이트가 38바이트로 풀렸습니다. 아래 위젯에서 「돌격편 타이틀 그림 첫머리」를 고르면 같은 바이트를 한 단계씩 풀어 볼 수 있습니다. 압축 형식과 그림 배치가 각성편과 같아서, 각성편 v1.2를 만들 때 쓴 도구를 주소만 바꿔 썼습니다(각성편 노트의 「압축된 타이틀 그림」).
롬에서 자료를 찾아가는 길
두 자료의 위치는 롬의 5바이트 항목(주소 2바이트, 뱅크 1바이트, 크기 2바이트)에 적혀 있습니다. 타일 항목은 파일 0xA752에 있고 앞 3바이트가 자료 주소입니다.
B1 9E주소 $9EB1B7뱅크$B7:9EB1타일 자료
배치표는 $81:A8B0이 표 $81:A8EC의 39번 항목(파일 0xA9AF, 자료 $B7:BE7F)으로 부릅니다. 새로 그린 그림을 다시 압축하니 타일은 3,613바이트에서 3,237바이트로, 배치표는 781바이트에서 779바이트로 줄어 제자리에 들어갔고, 항목은 일본판 그대로입니다. 더 길어지면 빈 공간(파일 0x13F300, 0x17FB40)으로 옮기고 항목을 고치도록 도구에 넣어 두었습니다.
「DRAGON BALL Z」 로고와 「© BANDAI 1995」는 원본 그대로입니다. 이 © 타일은 위쪽 저작권 줄과 함께 쓰기 때문에 건드리면 두 줄이 같이 바뀝니다.
더 깊이부팅 뒤 그래픽을 푸는 순서와 해제 루틴
해제 루틴은 $81:DF4F입니다. A에 주소, X(8비트)에 뱅크를 받아 WRAM $7E:8000에 풀고 크기를 $F0에 남깁니다. $81:DFE7은 이 루틴을 부른 뒤 $81:DFEB에서 16바이트 타일마다 플레인 순서를 바꿉니다. 일본판에서 JSL $81DF4F는 15곳, JSL $81DFE7은 26곳입니다. Mesen 2로 부팅 뒤를 기록하니 일본판과 v1.1 모두 아래 순서였습니다.
| 프레임 | 부른 곳 | 자료 |
|---|---|---|
| 114 | $81:A703 4번 항목 | $B7:ACCF |
| 126 | $81:A703 3번 항목 | $B7:9EB1 (타이틀 타일) |
| 135·137·139 | $81:A8B0 | $B7:BAB9, $B7:BD47, $B7:B94F |
| 536 | $81:A8B0 39번 항목 | $B7:BE7F (타이틀 배치표) |
각성편 도구의 머리말에 적힌 $80:9CFA는 각성편의 해제 루틴 주소입니다. 돌격편에서는 메뉴 커서 코드 중간이라, 압축된 곳을 찾을 때는 이 게임에서 해제 루틴을 다시 찾아야 합니다(9장 롬에서 압축된 곳과 푸는 코드 찾기).
6. 메뉴·이름·전투 실제 패치에서
대사 말고도 글자가 나오는 곳은 많습니다. 전투 대사, 선택지, 장소 이름, 기술명이 저마다 포인터 표를 가지고 있고, 형식은 대사와 같습니다. 모두 2바이트 포인터라 표가 있는 뱅크 안에서만 다시 채웠습니다.
| 내용 | 뱅크 | 표 | 개수 | v1.1 사용량(바이트) |
|---|---|---|---|---|
| 전투 대사 | $B1 | $B1:9C34 | 769 | 15,338 / 19,820 |
| 스토리 현황 이벤트 목록 | $AD | $AD:8000 | 204 | 4,706 / 8,770 |
| 선택지 | $A9 | $A9:8000 | 117 | 1,826 / 5,632 |
| 미니게임 | $98 | $98:8000 | 56 | 1,422 / 3,456 |
| 장소 이름 | $87 | $87:8000 | 100 | 1,210 / 3,166 |
| 맵 이름 | $82 | $82:B306 | 54 | 바꾸지 않음 |
| 기술명 | $84 | $84:B605 | 자동 | 1,394 / 6,053 |
| 동료 코멘트 | $9F | $9F:8000 | 자동 | 8,704 / 11,642 |
| 전투 중 적 이름 | $AE | $AE:C910 | 55 | 462 / 2,108 |
| 세이브 장면 대사 | $81 | 코드 속 12곳 | 12 | 544 / 840 |
「자동」은 표 길이가 따로 없어 첫 문자열 주소 앞까지를 표로 본다는 뜻입니다. 사용량의 분모는 일본판 문자열이 차지하던 영역입니다.
세이브 장면 대사 12개와 전투 외침 7개는 표가 없습니다. $81 뱅크 코드의 LDA #$xxxx 피연산자가 곧 문자열 주소라, 문자열을 옮기면 그 명령의 둘째·셋째 바이트를 고칩니다. 도구에는 피연산자 위치(파일 0xD85A, 0xE300 등)를 목록으로 적어 두었습니다. 이런 포인터는 8장 포인터 표 아래 「더 깊이」에서도 다룹니다.
한 칸에 눌러 그린 두 음절
스토리 현황 화면의 「기술」「습득」「완료」「클리」는 v1.0이 16px 한 칸(0xB97·0xBA0·0xBA1·0xBA2)에 두 음절을 눌러 그린 글자였습니다. 일본판에서는 한자 한 자가 들어가던 칸입니다. v1.2는 이 글자를 쓰지 않고 16px 글자 두 개로 씁니다.
v1.1
v1.2
오른쪽 메뉴 창은 폭이 6칸, 48px입니다. 16px 글자는 한 자에 13px씩 나아가므로 네 자면 52px이라 넘칩니다. 그래서 코드에 0x8000을 켜 같은 그림을 12px 간격(작은 글자)으로 찍었습니다. 일본판도 이 창의 한자를 같은 간격으로 썼습니다.
기술습득표와 이벤트 표의 줄 머리에 붙는 「습득」「완료」 표시는 표를 조립하는 코드($80:40BC, $80:3D83)가 코드 하나만 쓰게 되어 있었습니다. v1.2는 두 곳을 $B7:FB00·$B7:FB32의 새 루틴으로 JSL하게 바꿔, 16px 두 글자(아니면 같은 폭 빈칸)와 그 뒤 빈칸을 쓰게 했습니다. 두 목록은 줄 폭이 224px이고, 가장 긴 줄은 기술습득표 224px, 이벤트 표 223px입니다.
v1.1
v1.2
엔딩 크레딧과 그 밖의 메뉴 문자열
- 포인터 표 밖의 메뉴 문자열은 파일 위치와 새 문자열을
tables/ui-fixes.tsv에 모아 두고 빌드 때 덮어씁니다. 타이틀 메뉴는 파일0x6F17(NEW GAME / OPTION)과0x6F3D(CONTINUE / NEW GAME / OPTION)에 있습니다. - 엔딩 크레딧은 줄마다
$B7뱅크의 고정 칸에 있고, 칸 크기가 일본어 줄 길이로 정해져 있어 그보다 길게 쓸 수 없습니다. 출연진은 열 가운데(3열 48/128/208px, 2열 88/168px)에 맞춰 다시 줄을 세웠습니다. - 줄을 WRAM에 이어 붙이는 화면에는 다음 문자열을 앞 문자열 바로 뒤에서 읽는 곳이 있습니다. 달성치 화면 라벨(
$80:BAB2, 19코드)과 상태 화면 아래 줄 앞 라벨($80:C9C5, 5코드)은 코드 수를 바꾸지 않았습니다. - 이름 입력 글자판(파일
0x7005, 160칸)은 v1.0이 쓴 16비트 문자열입니다. 엔딩 암호와 같은 8px 글자를 쓰므로, 7절에서 다시 그린 다섯 칸이 함께 바뀝니다.
7. 세이브·암호 실제 패치에서
세이브는 카트리지의 SRAM 8KB에 있습니다. 한글패치는 세이브 구조를 바꾸지 않았습니다. v1.0에서 만든 세이브를 v1.1에서 불러와 이어서 진행되는 것을 확인했습니다. 저장 루틴은 $81:D210(슬롯 번호 $0119), 불러오기는 $81:D3A9이고, 두 주소를 부르는 JSL이 롬에 있습니다.
각성편으로 넘기는 암호
돌격편 엔딩의 마지막 화면(TO BE CONTINUED)에는 원래 가나 50자 암호가 나옵니다. 2편 각성편에서 새 게임 → 「암호」를 고르고 이 암호를 넣으면 주인공 이름과 기록을 이어받습니다. v1.2는 이 자리에 각성편 v1.3이 받는 고정 암호 「손오공은사이어인」을 보여 줍니다.
v1.1
v1.2
원래 암호는 WRAM $7E:0A00에 작은 가나 코드(0x8000이 켜진 코드)로 만들어집니다. 두 글자가 1바이트이고, 쌍 표 $B7:F09F(256쌍, 일본판 그대로)에서 순번이 바이트 값입니다. 25쌍은 23바이트 + 합계 2바이트이고, 그 안에 주인공 이름이 들어 있습니다. 각성편이 이 바이트를 어떻게 읽는지는 각성편 노트의 「돌격편에서 넘어오는 암호」에 바이트별 표로 있습니다.
v1.2는 $81:D169부터 쌍을 바꾸고 옮기던 코드 자리에 짧은 복사 루프와 문자열을 넣었습니다. 문자열은 빈 줄 하나, 작은 빈칸 다섯 개, 아래 여덟 글자, 빈 줄 둘이고, 이어서 원래 코드가 「TO BE CONTINUED」를 다섯째 줄에 그립니다. 창은 18칸(144px)이고 작은 글자는 8px라, 여덟 글자 64px 앞에 빈칸 다섯(40px)을 두면 가운데에 옵니다.
8182손81A7오8106공81AF은8221사81C1이81A2어81C2인
이 여덟 글자를 각성편 v1.3(CRC32 B9F14962)에 넣으면 통과해 이름 화면으로 가고, 이름은 각성편 기본 이름 「엑소버드」입니다(Mesen 2).
헷갈리던 암호 글자 다섯
v1.0이 가나 자리에 8px 한글을 그려 넣어서 한글패치는 암호를 한글로 보여 줍니다. 그런데 v1.1까지는 서로 다른 가나 다섯 개가 다른 글자와 똑같거나 거의 같은 모양이었습니다. 암호를 화면에서 옮겨 적으면 틀린 글자를 고르게 됩니다. 아래는 v1.1 롬에서 꺼낸 글자 그림과 v1.2가 다시 그린 그림입니다.
| 가나 자리 | v1.1에서 보인 글자 | v1.2 |
|---|---|---|
へ(0x12C) = つ(0x121) | 나 | 다 |
も(0x142) = ら(0x146) | 등 | 동 |
を(0x14C) ≈ わ(0x14B) | 러 | 레 |
ん(0x14D) ≈ わ(0x14B) | 러 | 렉 |
ル(0x1C8) ≈ ォ(0x1E2) | 지 | 저 |
글자 표(tables/kr-v1.0.txt)는 이 다섯 자리를 다·동·레·렉·저로 적고 있어서, v1.2는 그 글자로 다시 그렸습니다. 폭 8px, 1px 획, 그림자 없음으로 이웃 글자와 같은 방식입니다. 같은 자리를 쓰는 이름 입력 글자판에서 같은 모양이 두 칸 나오지 않게 됐고, 엔딩 스태프 「디러터」는 원래 뜻대로 「디렉터」가 됐습니다. 그림을 바꾸는 방법은 7장 화면은 8×8 타일을 이어 붙인 그림과 같습니다.
v1.1
v1.2
더 깊이표와 다른 글자 넷, 빌드 검사, 데이터 암호 확인
- 글자 표와 픽셀이 다른 자리는 다섯 말고도 넷 있었습니다(0x120 께, 0x1A4 건, 0x246 띠, 0x248 떠). v1.1은 0x248을 「펴」로 알고 대사 두 줄(
$5D5「펴‥평범한」,$101B「살펴봐」)에 써서 「떠」로 보였습니다. v1.2는 표를 픽셀대로 고치고 「펴」를 새 16px 글자(0xD60)로 그렸습니다. - 빌드는 다섯 자리가 정해 둔 그림과 다르거나, 암호 글자 163자 가운데 같은 모양이 있거나, 글자 표에서 이름이 다른 두 코드가 같은 그림을 그리거나, 이름 입력 글자판에 같은 모양이 두 칸 있으면 멈춥니다(
tools/pwglyph1.py). - 대사·전투·메뉴 문자열 7,899개를 v1.1과 코드 단위로 비교해, 바뀐 것은 「펴」를 쓰는 대사 두 줄뿐임을 확인했습니다.
- 원래 데이터 암호도 쓸 수 있는지 확인했습니다. v1.2에서 엔딩 암호 바꾸기만 뺀 실험용 롬으로 디버그 엔딩 암호, 주인공 이름을 「다동레렉저」로 바꾼 암호를 화면 한글대로 각성편 v1.3에 넣으면 모두 통과했고, 각성편 이름 화면에 같은 이름이 나왔습니다(Mesen 2).
- 돌격편 안에도 가나 입력 화면 코드(
$80:99D6,$82:9D7C에서 부름)가 있습니다. 부팅 직후 이곳으로 가게 고친 롬에서는 글자판이 반쯤 깨진 채 열렸습니다. 정상 플레이에서 열리는 길은 찾지 못했습니다.
8. 게임 고유 문제 실제 패치에서
돌격편에서 따로 다룰 문제는 스토리 현황의 이벤트 표입니다. 일본판에도 있던 동작이 v1.0에서 글자 코드가 바뀌며 실제로 화면을 닫지 못하는 문제가 됐습니다.
이벤트 표에서 못 나오던 문제
- 대사가 떠 있을 때 START를 누르면 스토리 현황 화면이 열립니다.
- 오른쪽 메뉴에서 「클리어 표」를 고르고, 기술습득표에서 B를 누르면 이벤트 표가 나옵니다.
- 목록을 맨 아래까지 내리면 204개 뒤에 숨은 줄 하나를 더 그립니다.
v1.0
v1.1
이벤트는 204개(0–203번)인데, 목록을 내리는 조건($80:BC8A의 CMP #$00CD)이 205줄을 기준으로 되어 있어서 맨 아래에서 없는 204번 줄을 그립니다. 그 줄의 포인터 자리($AD:8198, 파일 0x168198)에는 포인터가 아니라 첫 이벤트 이름의 글자 코드가 있습니다. 게임은 이 값을 주소로 알고 WRAM의 그 자리를 문자열로 읽습니다.
그 자리 값은 게임 상황마다 다릅니다. 끝 표시가 늦게 나오면 줄이 넘치고, B로 목록을 닫을 수 없게 됩니다. v1.0은 첫 글자 코드가 바뀌면서 읽는 주소가 $7E:0686이 됐고, 디버그 메뉴로 장면을 0x00–0xF8까지 8 간격으로 바꿔 가며 재현해 보니 0xC0번 장면에서 실제로 목록이 닫히지 않았습니다. v1.1은 204번 자리에 빈 문자열을 가리키는 포인터를 넣었습니다.
A5 05일본판 → $05A586 06v1.0 → $0686FA 93v1.1 → $AD:93FA
v1.1이 가리키는 $AD:93FA에는 끝 표시 00 00만 있습니다. 같은 0xC0번 장면에서 B를 두 번 눌러 게임으로 돌아오는 것을 확인했습니다. 표 끝의 숨은 항목처럼 포인터가 아닌 값을 포인터로 읽는 곳은 8장 포인터 표를 읽을 때도 조심할 점입니다. 장면마다 같은 입력을 넣어 재현한 방법은 12장 Lua로 확인을 자동으로에 있습니다.
9. 확인 방법 직접 해 보기
대사 5,370줄을 사람이 다 띄워 볼 수는 없습니다. 그래서 게임에 숨어 있는 개발자용 디버그 메뉴로 장면을 바로 시작하고, 에뮬레이터 Mesen 2와 Lua 스크립트로 입력과 기록을 자동으로 했습니다.
디버그 메뉴를 여는 두 바이트
리셋 뒤 계속 도는 루프의 한 부분($80:8061, 파일 0x61)을 보겠습니다. 어셈블리는 CPU가 실행하는 명령을 사람이 읽는 이름으로 적은 것이고, 롬 안에서는 그냥 바이트입니다(5장 분기와 반복).
| 바이트 | 어셈블리 | 뜻 |
|---|---|---|
80 04 | BRA $8067 | 무조건 4바이트 뒤로 건너뜁니다. 제품판은 다음 줄을 늘 건너뜁니다. |
22 B0 86 82 | JSL $8286B0 | 디버그 메뉴 루틴을 부릅니다. 주소는 거꾸로 적힙니다. |
22 00 80 82 | JSL $828000 | 건너뛴 뒤 제품판이 실행하는 곳입니다. |
80 04제품판: BRAEA EANOP NOP
첫 줄 80 04를 아무 일도 안 하는 명령 두 개 EA EA로 바꾸면 건너뛰지 않고 디버그 메뉴로 들어갑니다. 배포한 패치는 이 두 바이트를 바꾸지 않습니다.
ADVENTURE의 SCENARIO SELECT로 장면 번호($00–$FF)를 고르면 그 장면을 실제 플레이와 같은 상태로 시작합니다. DATA의 ENDING으로는 엔딩을 처음부터 볼 수 있습니다.
대사 순서를 일본판과 맞대 보기
- 디버그 메뉴로 256개 시나리오를 하나씩 시작하고, A 버튼을 일정 간격으로 눌러 8000프레임씩 진행합니다.
- 게임은 대사를 띄울 때마다 대사 ID로 창 정보($8E:C9BA + ID×2)를 읽습니다. 이 자리를 누가 읽는지 감시하면 어떤 대사가 몇 번째로 나왔는지 기록할 수 있습니다.
- 일본판과 v1.1의 대사 ID 순서를 시나리오마다 비교했습니다. 순서가 다르거나 v1.1에서 먼저 멈춘 시나리오는 없었습니다. 한글 대사가 짧아 같은 시간에 더 진행한 경우만 있었습니다.
- 패치 노트의 비교 화면도 이 방법으로 찍었습니다. 3프레임마다 화면을 저장하고, 아래 절반의 흰 글자가 지워지기 직전 화면을 그 페이지의 완성 화면으로 골랐습니다.
- A 버튼을 여러 프레임 누르고 있으면 글자 출력 완료와 페이지 넘김이 한꺼번에 일어나 페이지가 화면에 다 나오지 않습니다. 촬영할 때는 1프레임만 눌렀습니다.
"어디서 읽나" 를 잡는 감시와 같은 입력을 되풀이하는 스크립트는 12장 중단점과 Lua로 확인을 자동으로에서 다룹니다. 각성편도 같은 방법으로 확인했습니다(각성편 노트의 확인 절).
여러 에뮬레이터에서 같은 입력
libretro 코어를 직접 불러 쓰는 작은 프로그램으로 같은 입력을 여러 에뮬레이터에 넣었습니다(12장 여러 에뮬레이터에서 같은 입력 돌리기). 새 게임은 이름 입력부터 첫 장면과 필드까지 1만 6천 프레임, 이어하기는 v1.0에서 만든 세이브를 불러 대사가 이어지는 데까지 봤습니다. 롬은 일본판, v1.0, v1.1, v1.2와 헤더 붙은 v1.1·v1.2 여섯 가지입니다. v1.2는 이름 입력 글자판 다섯 칸이 바뀐 것 말고는 모든 코어에서 v1.1과 같은 화면이었습니다.
| 에뮬레이터 | 결과 |
|---|---|
| Mesen 2 | 정상. 이어하기에서 나온 대사 ID 순서가 일본판·v1.0·v1.1·v1.2(헤더 있음/없음) 모두 같음 |
| MesenCE 2.2.1 | 정상. 새 게임·이어하기, 디버그 시나리오 64개, 이벤트 표 문제 재현, 엔딩 암호에서 대사 ID 기록이 Mesen 2와 같음 |
| bsnes, bsnes-hd beta | 정상 |
| snes9x, snes9x2010 | 정상. snes9x2010도 이어하기 대사 ID 순서가 여섯 롬 모두 같음 |
| Beetle Supafaust | 정상. 새 게임은 다른 코어와 몇 프레임 어긋나 필드에서 다른 길로 감(일본판도 코어마다 같은 정도로 갈라짐) |
| Mesen-S 코어 | 판정 불가 (일본판 원본도 테스트 프로그램에서 바로 종료) |
Mesen-S를 뺀 일곱 개 모두 멈추거나 화면이 깨진 곳은 없었습니다. 헤더 있는 롬과 없는 롬은 libretro 코어 다섯 개에서 찍은 화면이 픽셀까지 같았습니다. 이어하기는 대사가 끝나면 검은 화면이 이어지는데, 일본판도 모든 에뮬레이터에서 같은 곳에서 같은 화면이 나와 패치와는 관계없습니다.
더 깊이직접 돌려 보기: 명령
디버그 메뉴를 연 롬과 패치 소스의 Lua 스크립트로 시나리오 $10을 돌리고 이벤트 표 문제를 재현하는 명령입니다.
export LIBDIR=$(krpatch sfc lua)
TARGET=10 SHOTS=1 SHOT_DIR=out Mesen --testrunner debug.smc lua/scenario.lua
TARGET=c0 SHOT_DIR=out Mesen --testrunner debug.smc lua/eventlist.lua
python3 tools/pages1.py out 0142 # 대사 0142의 페이지마다 완성된 프레임
python3 tools/rgcmp1.py runs_jp runs_kr # 두 롬의 대사 ID 순서 비교디버그 메뉴를 부르는 코드를 툴킷 역어셈블러로 읽으면 위 표가 그대로 나옵니다: krpatch sfc dis super-gokuden-jp.smc 808061 3 --mapper lorom.
10. 다음 사람에게 실제 패치에서
이 롬을 이어서 고칠 분이 알아 두면 좋을 것입니다. 주소와 형식 전체는 패치 소스의 롬 분석 문서(docs/ANALYSIS.md)에 있습니다.
확인하지 않은 것
- 실제 슈퍼 패미콤과 플래시 카트리지에서 돌려 본 기록은 없습니다. 에뮬레이터 결과는 9절에 있고, 실기에서만 드러나는 문제는 12장 실기에서만 알 수 있는 것을 보세요.
- 창 정보 첫 바이트가 FE인 대사 347줄은 얼굴 창과 같은 128px로 잡히지만, 얼굴 그림을 부르는 루틴을 건너뛴다는 것만 코드로 봤고 화면은 보지 않았습니다.
- 각성편으로 넘기는 원래 데이터 암호는 디버그 엔딩 암호와 이름만 바꾼 암호로만 확인했고, 끝까지 플레이한 세이브의 암호로는 확인하지 못했습니다. v1.2 엔딩은 고정 암호만 보여 줍니다.
- 대사 $5D5를 부르는 스크립트는
$AC:E22D부근에 있고 장면 $58(피라후 성)에서 이어지는 장면으로 보이지만, 자동 입력으로는 성 안 갈림길 선택지를 넘지 못해 실제 장면까지 가지 못했습니다. 패치 노트의 $5D5 화면은 실험용 롬에서 새 게임 첫 대사 자리가 $5D5의 글자 코드를 가리키게 해서 찍었습니다. - 원래 암호를 만드는 루틴의 주소는 찾지 않았습니다.
알려진 한계
- v1.0 글꼴에서 가로 모음과 받침이 있는 음절 46쌍(돌/들/둘, 울/올/을 등)은 1px 차이로만 갈립니다. Galmuri11로 다시 그려도 41쌍이 그대로였고, 세로 획을 2px로 늘리면 그 200자만 글자 높이가 1px 커져서 고치지 않았습니다.
- 엔딩 크레딧은 줄마다 칸 크기가 고정이라 일본어 줄보다 길게 쓸 수 없습니다.
- LZSS 형식은 각성편과 같지만 해제 루틴 주소는 다릅니다. 돌격편은
$81:DF4F와$81:DFE7이고, 각성편 주소$80:9CFA는 돌격편에서는 메뉴 커서 코드 중간입니다. - WRAM
$4D는 대사 ID 자리이지만 그래픽 읽기 루틴도 잠깐 씁니다. 대사를 기록할 때는 창 정보 표를 읽는 순간을 감시하세요. - 새 글자 블록 3–7의 5곳(320자)이 통째로 비어 있습니다. 글자를 더 넣을 때 주소 표를 고치지 않고 쓸 수 있습니다.
쓴 도구
- Python 3.9.6(Pillow 11.3.0, numpy 2.0.2), macOS
- Mesen 2.1.1(Lua 스크립트, --testrunner)
- libretro 코어: bsnes, bsnes-hd beta, snes9x, snes9x2010, Beetle Supafaust, Mesen-S
- 새 글자: Galmuri11 2.404, 타이틀 글꼴: BM영성체
- kr-patch-toolkit의 IPS·체크섬·LZSS·역어셈블러·libretro 실행 명령
11. 패치 소스 파일 직접 해 보기
위에서 설명한 파일은 모두 패치 소스의 게임 폴더에 있습니다. 그 폴더에 일본판 원본(헤더 없음, CRC32 D531289B)을 super-gokuden-jp.smc로 두고 zsh tools/build.sh를 실행하면 배포판과 같은 롬(CRC32 A939C4F8)이 나옵니다. Python 3(Pillow, numpy)과 kr-patch-toolkit이 필요합니다. 빌드는 이 순서로 돕니다.
krpatch ips apply일본판에 v1.0 IPS를 적용합니다.tools/title.py타이틀 글자 층을 다시 그려 압축해 넣습니다.tools/text_patch1.py대사와 그 밖의 텍스트를 뱅크마다 다시 채우고, 새 글자를 그리고, 메뉴 문자열을 고칩니다.tools/statusmenu1.py스토리 현황 목록의 습득·완료 표시를 두 글자로 바꿉니다.tools/pwglyph1.py암호 글자 다섯 자리를 다시 그리고 글자 표와 대조합니다.tools/endpw1.py엔딩 암호 자리에 「손오공은사이어인」을 넣습니다.krpatch sfc checksum --fix헤더 체크섬을 다시 계산합니다.krpatch ips make --headered헤더 없는 롬용과 512바이트 헤더 롬용 IPS를 만듭니다.
패치 소스와 일본판 원본만으로 빌드해 배포판과 같은 CRC32가 나오는 것을 확인했습니다. IPS가 무엇을 적는지는 0장 IPS 패치를 바이트로 읽기에 있습니다.
이 노트의 개념을 설명하는 장
직접 해 보기 소스와 도구
- 패치 소스 — 이 노트가 가리키는 파일과 빌드 순서(한국어 안내)
- 롬 분석 문서 v1.2 — 주소와 형식을 [실측]·[추정]으로 나눠 적은 한국어 문서
- kr-patch-toolkit v0.4.0 — 공통 명령 krpatch 한국어 설명
- 드래곤볼 Z 초오공전 돌격편 한글패치 페이지 — 패치 내려받기와 롬에 바로 적용
댓글