롬 분석 노트 · 슈퍼 패미컴
롬 분석 노트: 드래곤볼 Z 초오공전 각성편
드래곤볼 Z 초오공전 각성편 한글패치 v1.3을 만들며 남긴 기록입니다. 개념은 각 절의 「가이드」 링크에서 처음부터 풀어 설명합니다.
이 롬을 다시 분석하거나 패치를 고칠 사람을 위해 남기는 기록입니다. v1.2는 2016년에 낸 v1.1 패치를 일본판에 적용한 롬에서 출발했습니다. 그 위에 v1.1이 만든 진행 버그를 고치고, 압축된 타이틀 그림을 풀어 한글로 다시 그리고, 대사 782줄을 고쳐 넣었습니다. v1.3은 나레이션 줄바꿈을 바로잡고 전투 말풍선을 원문과 다시 대조했습니다. 롬 크기는 원본과 같은 2MB입니다. 장면 확인은 일본판 롬 안에 남아 있던 개발자용 디버그 메뉴로 했습니다.
1. 배경지식
게임기 안의 메모리
CPU는 화면에 직접 그리지 않습니다. 그림 조각(타일)을 VRAM에 올려 두고 "이 칸에는 몇 번 타일" 이라는 배치표(타일맵)를 적어 화면을 만듭니다. 대사 글자도 타일입니다.
| 이름 | 크기 | 비유 | 하는 일 |
|---|---|---|---|
| 롬 | 2MB | 책 | 카트리지에 든 프로그램·대사·폰트·그림. 읽기만 합니다. |
| WRAM | 128KB | 작업 책상 | CPU가 계산하며 값을 적어 두는 곳. 지금 장면 번호도 여기 있습니다. |
| VRAM | 64KB | 스티커판 | 화면에 붙일 8×8 그림 조각(타일)을 올려 두는 곳. |
| SRAM | 8KB | 수첩 | 전원을 꺼도 남는 세이브 데이터. 에뮬레이터의 .srm 파일입니다. |
주소 읽는 법
0xCA9BE 같은 파일 오프셋은 롬 파일을 처음부터 센 위치이고, $99:A9BE 같은 SNES 주소는 CPU가 보는 뱅크:주소입니다. 이 게임은 LoROM이라 뱅크마다 뒤쪽 32KB($8000–$FFFF)에만 롬이 보입니다. 그래서 다음처럼 바꿉니다.
파일 오프셋 = (뱅크 & 0x7F) × 0x8000 + (주소 & 0x7FFF)
$99:A9BE → 0x19 × 0x8000 + 0x29BE = 0xCA9BE어셈블리, 필요한 만큼만
어셈블리는 CPU가 실행하는 명령을 사람이 읽는 이름으로 적은 것이고, 롬 안에서는 그냥 바이트입니다. 전원을 켠 직후 실행되는 코드($80:805C, 파일 0x5C)를 보겠습니다.
| 바이트 | 어셈블리 | 뜻 |
|---|---|---|
80 0C | BRA $806A | 무조건 12바이트 뒤로 건너뜁니다. 제품판은 아래 네 줄을 늘 건너뜁니다. |
AD 33 02 | LDA $0233 | $0233번지 값을 읽습니다. 주소는 거꾸로 적힙니다. |
29 FF 00 | AND #$00FF | 아래 바이트만 남깁니다. |
D0 04 | BNE $806A | 0이 아니면 다음 4바이트를 건너뜁니다. |
22 2B 9F 80 | JSL $809F2B | 디버그 메뉴 루틴을 부릅니다. |
첫 줄 80 0C를 아무 일도 안 하는 명령 두 개 EA EA(NOP NOP)로 바꾸면 건너뛰지 않고 디버그 메뉴로 들어갑니다. 위 화면이 그렇게 연 메뉴입니다. ADVENTURE에서 장면 번호를 넣고 시작하면 맵까지 그 장면에 맞게 불러와서, 처음부터 플레이하지 않고도 원하는 대사를 띄울 수 있습니다. 롬 해킹은 이런 바이트 바꾸기의 연속입니다.
2. 대사가 롬에 들어 있는 모양
대사 글자 하나는 16비트(2바이트) 코드이고, 작은 쪽 바이트를 먼저 적습니다. 0A 00은 줄바꿈, 00 00은 대사 끝입니다. 한 페이지는 4줄이고, 한 줄에는 얼굴 그림이 있는 창은 10자, 나레이션 창은 14자까지 들어갑니다. 크리링의 대사 하나를 롬에서 그대로 꺼내면 이렇습니다.
| 바이트 | A2 06 | 45 09 | 52 09 | 0A 00 | 05 06 | B4 05 | 72 02 | 20 01 | 72 02 | 13 06 | 24 01 | 00 00 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 글자 | 크 | 리 | 링 | 줄바꿈 | 좋 | 은 | 빈칸 | 걸 | 빈칸 | 줄 | 게 | 끝 |
창은 대사를 띄우는 코드($80:B879)가 고릅니다. 대사 ID $200부터는 $98:8000에 ID마다 2바이트가 있고 첫 바이트가 화자 번호인데, 이 값이 0xFF면 8×8 타일 0x15칸 폭의 나레이션 창, 아니면 0x10칸 폭의 얼굴 창을 깝니다. ID $000–$056은 화자 번호가 없는 짧은 감탄사 81개라 늘 얼굴 창입니다.
글자를 찍는 코드($80:9B2C)는 글자마다 12픽셀씩 오른쪽으로 가고, 다음 글자가 창 폭(타일 수 × 8픽셀)을 넘으면 줄을 바꿉니다. 0x10칸은 128픽셀이라 10자, 0x15칸은 168픽셀이라 14자입니다. 시험용 줄을 넣어 띄워 봐도 얼굴 창은 11번째, 나레이션 창은 15번째 글자부터 다음 줄로 떨어지고, 그만큼 줄을 하나 더 씁니다. 일본판에도 이렇게 넘치는 줄이 2개(ID $7C0, $E59) 있어 「‥!」과 「‥」가 혼자 다음 줄에 찍힙니다.
v1.2는 나레이션 창을 15자로 잘못 알고 줄을 나눴습니다. 그래서 대사 3,558개(글자 시험용 대사 제외) 중 얼굴 창은 모두 10자 안에 들어갔지만, 나레이션 대사 8개(ID $600, $83E, $881, $883, $895, $F84, $F86, $F87)의 9줄은 15자라 마지막 글자가 다음 줄로 떨어졌습니다. v1.3은 이 8개를 14자 안으로 다시 나눴고, 빌드 마지막에 모든 줄을 창 폭과 비교해 넘치면 빌드를 멈추는 검사를 넣었습니다. v1.2 롬에 이 검사를 돌리면 대사 8개와 말풍선 4개가 걸립니다.
대사 ID는 $000부터 $F94까지 4천 개 가까이 됩니다. ID 범위마다 뱅크가 정해져 있고, 뱅크 첫머리에 포인터 표가 있습니다.
| 대사 ID | 뱅크 | 포인터 표 |
|---|---|---|
000–3DF | $99 | $99:8000 |
3E0–5B6 | $9A | $9A:8000 |
5B7–7AE | $9B | $9B:8000 |
7AF–97C | $9C | $9C:8000 |
97D–B44 | $9D | $9D:8000 |
B45–D15 | $9E | $9E:8000 |
D16–ED3 | $9F | $9F:8000 |
ED4–F94 | $98 | $98:C1C8 |
- 스크립트의 대사 명령(0x08)이 16비트 대사 ID를 줍니다. 예: $2A7
- ID 범위로 뱅크를 고릅니다. $2A7은 $99입니다.
- 그 뱅크 첫머리 포인터 표에서 ID 순서대로 2바이트를 읽습니다. $99:8000 + $2A7 × 2 = $99:854E에 BE A9가 있습니다.
- 같은 뱅크의 $99:A9BE(파일 0xCA9BE)에 대사 문자열이 있습니다.
전투 중에 캐릭터 옆에 뜨는 말풍선은 따로 있습니다. 대사 ID $1000–$13FF가 $B4:8000 포인터 표를 쓰고, 전투 스크립트(뱅크 $B5)의 명령이 이 ID를 줍니다. 명령마다 창 크기가 정해져 있습니다.
| 명령 | 코드 | 창(8×8 타일) | 글자 | 쓰는 곳 |
|---|---|---|---|---|
01 | $82:D172 | 0x0C × 0x0A | 8자 × 5줄 | 계왕권 설명처럼 긴 안내 |
02, 03 | $82:D1DC | 0x12 × 0x06 | 12자 × 3줄 | 보통 말풍선 |
전투 스크립트 표 4개($B5:BA7E, $B5:C5AC, $B5:CC74, $B5:D141, 각 80개)에서 시작해 분기까지 따라가면 말풍선 ID 592개가 나오고, 한 ID가 두 창 크기로 쓰이는 경우는 없습니다. Mesen 2로 376개 장면을 돌리며 실제로 뜬 말풍선 230개의 창도 모두 이 결과와 같았습니다. 실제 대사 898개 중 나머지 306개는 어느 스크립트에서도 부르지 않습니다. 천하제일무도회 장면의 전투에서 「너 정말로 누구야?」가 이 표에서 나옵니다.
v1.1이 말풍선을 모두 한글로 바꿨지만 v1.2는 이 뱅크를 고치지 않았습니다. v1.3에서 898개(문자열로는 729개)를 일본어 원문과 대조해 문자열 443개를 고쳤고, 창 밖으로 넘치던 줄(ID $1022, $10DC, $1194, $11CE)도 창에 맞게 나눴습니다. 여러 ID가 한 문자열을 같이 쓰므로 고친 ID는 719개입니다. 마지막 ID $137B–$1381의 7줄은 v1.1이 엔딩 인사와 한글화 참여자 이름을 넣은 자리라 그대로 두었습니다.
3. 폰트에 없는 글자
대사 폰트는 16×16 픽셀이고, 8×8 타일 4장으로 나뉘어 들어 있습니다. 롬에 저장된 시트는 한 줄에 타일 16장이라 글자 8개의 위 절반이 한 줄, 아래 절반이 그다음 줄입니다. 그래서 0x108–0x10F처럼 아래 절반 줄에 해당하는 코드는 글자로 쓰지 않습니다.
v1.1 폰트에는 대사를 고치며 필요해진 글자 몇 개가 없었습니다. 그래서 어떤 대사에서도 쓰지 않는 글자 자리를 찾아 Galmuri11 폰트로 깨·꽉·꾼·닿·렁·롭·벅·빡·뻐·셋·씻 11자를 그려 넣었습니다. 아래는 그렇게 넣은 「깨」입니다.
글자 표를 그대로 믿으면 안 된다는 것도 배웠습니다. v1.1 글자 표에는 922번이 「렁」이라고 적혀 있었지만 실제 그림은 「렇」이었습니다. 지금은 표의 글자와 롬의 그림을 대조해서 씁니다.
4. 늘어난 대사는 같은 뱅크 안으로
포인터는 2바이트라 뱅크 번호 없이 같은 뱅크 안의 주소만 가리킵니다. 그래서 고친 대사가 원래보다 길면 다른 뱅크로는 못 보내고, 같은 뱅크의 빈 공간으로 옮긴 뒤 포인터만 고칩니다. 여러 ID가 한 문자열을 같이 가리키는 경우도 있어서, 그런 문자열은 제자리에서 고치지 않고 새 자리에 따로 씁니다.
고친 782줄 중 432줄이 이렇게 옮겨졌습니다(v1.3 빌드 기준). v1.1 대사는 일본어보다 짧아 뱅크마다 끝쪽에 0xFF가 남아 있었고, 옮긴 대사는 그 자리와 다른 대사가 비우고 간 자리에 들어갔습니다.
| 뱅크 | 옮긴 줄 | 빌드 뒤 남은 빈 곳 |
|---|---|---|
$99 | 76 | 2,526바이트 |
$9A | 61 | 4,680바이트 |
$9B | 78 | 3,715바이트 |
$9C | 67 | 4,840바이트 |
$9D | 48 | 3,700바이트 |
$9E | 27 | 3,476바이트 |
$9F | 46 | 3,463바이트 |
$98 | 29 | 3,226바이트 |
일본판, v1.1, v1.2 롬을 바이트 단위로 비교해 뱅크마다 바뀐 곳을 표시하면 이렇습니다. 대사 말고도 v1.1은 메뉴, 지명, 크레딧, 전투 말풍선($B4)까지 한글로 바꿨고, v1.2는 대사 8뱅크와 타이틀, 새 글자, 아래 5절의 두 곳을 고쳤습니다. v1.3은 v1.2에서 대사 뱅크 $98·$9B·$9C, 말풍선 뱅크 $B4, 암호 화면의 표($81·$83·$84), 새 글자 4자($B7), 헤더 체크섬이 바뀌었습니다.
5. 우주선이 맵 밖을 헤매던 이유
v1.1은 길어진 한글 크레딧과 지명 문자열을 0xFF가 이어진 곳에 넣었습니다. 비어 있다고 생각했기 때문입니다. 그런데 0xFF가 시작되는 자리가 사실은 바로 앞 데이터의 끝 표시(FF FF)였습니다. 두 곳이 이렇게 덮였습니다.
$88:FFAE: 맵 이동 경로 레코드의 끝 표시와 그 뒤 레코드 하나. 이동 루틴이 지명 문자열 바이트를 경로로 읽어서 우주선이 맵 밖으로 나갔습니다.$83:F3AF: 컷신 캐릭터 X 좌표 표의 끝 표시. 캐릭터가 엉뚱한 좌표 쪽으로 미끄러져 갔습니다.
v1.2는 원래 바이트를 되살리고, 그 자리에 있던 문자열을 뒤로 민 다음, 그 문자열을 가리키는 포인터를 같은 만큼 고쳤습니다. 위 롬 지도의 빨간 줄이 이 두 곳입니다. 이제 빈 공간을 쓸 때는 앞뒤에 여유를 두고, 일본판 원본에서도 그 자리가 0xFF였는지 확인합니다.
6. 압축된 타이틀 그림
타이틀 로고는 롬에 LZSS로 압축돼 있습니다. 앞에서 나온 것과 같은 내용이 또 나오면 바이트를 다시 적지 않고 "몇 바이트 앞에서 몇 바이트를 베껴라" 라고만 적는 방식입니다. 플래그 바이트 하나가 뒤따르는 항목 8개의 종류를 아래 비트부터 알려 줍니다. 1이면 바이트 1개를 그대로, 0이면 2바이트짜리 "베껴라" 명령입니다. 일본판 타이틀 그림의 첫머리를 실제로 풀어 보면 이렇습니다.
50 13 풀면 0x1350 = 4,944바이트
DC 플래그 11011100 → 아래 비트부터 0 0 1 1 1 0 1 1
2F 01 베끼기: 길이 0xF+3 = 18, 거리 18 → 시작보다 앞이라 0으로 18바이트
F0 00 베끼기: 길이 3, 거리 15 → 0 세 바이트
01 0F 3F 그대로 세 바이트
89 00 베끼기: 길이 12, 거리 8 → 방금 쓴 8바이트를 겹쳐 가며 반복
1F FF 그대로 두 바이트타이틀 그림을 푸는 루틴($80:9DA7)은 다 푼 뒤에 타일마다 바이트 순서를 한 번 더 바꿉니다. 그래서 압축만 풀어서는 왼쪽처럼 줄무늬가 지고, 순서를 바꿔야 오른쪽처럼 제대로 보입니다.
한글 로고를 그린 뒤 다시 압축했더니 원래 3,608바이트가 3,232바이트로 줄어 제자리에 들어갔습니다. 더 길어지면 같은 영역의 빈 공간(파일 0x4C801–0x4FFFF)으로 옮기고 위치 정보를 고치도록 해 두었습니다. 「DRAGON BALL Z」 로고와 저작권 줄은 원본 그대로입니다. 저작권 줄의 © 타일은 두 줄이 같이 쓰기 때문입니다.
7. 엔딩 크레딧과 세이브
엔딩 크레딧도 대사와 같은 16비트 문자열이고, 이름 줄마다 포인터가 있습니다($83:949D). 「MR. 포포」를 「미스터 포포」로 늘리면서 원래 자리에 줄여 넣지 않고 빈 공간으로 옮겨 포인터를 고쳤습니다. 제자리에서 글자 수를 바꾸면 가운데 정렬이 틀어집니다.
v1.2와 v1.3 모두 세이브 구조는 건드리지 않아 v1.1 세이브를 그대로 쓸 수 있습니다. 함께 배포한 100% 세이브 파일에서 깨진 슬롯을 지울 때는 슬롯 본체만이 아니라 요약 정보 두 곳(0x800대, 0x860대)도 같이 지워야 했습니다.
8. 돌격편에서 넘어오는 암호
돌격편 엔딩에 나오는 50자 암호를 각성편의 새 게임 → 암호 화면에 넣으면 돌격편에서 정한 이름과 기록을 이어받습니다. 검사 코드($83:A1F8)는 빈칸을 뺀 글자를 두 자씩 끊어, $83:A60E의 256쌍 표에서 같은 쌍을 찾아 1바이트로 바꿉니다. 23바이트의 합(16비트)이 마지막 두 쌍이 나타내는 값과 같으면 통과입니다.
입력 칸이 넣는 코드는 $81:8D56(히라가나 85칸)과 $81:8E00(가타카나 85칸)에, 화면에 그리는 격자는 $84:A3E0과 $84:A4D0에 따로 있습니다. v1.1은 칸과 격자를 한글 코드로 바꾸면서 쌍 표는 일본어 코드 그대로 두었습니다. 그래서 v1.2까지는 어떤 암호를 넣어도 틀렸다고 나왔습니다. 칸에 보이는 글자도 돌격편 엔딩 화면과 맞지 않아, 암호 글자 156칸 중 같은 글자를 보여 주는 칸은 25칸뿐이었습니다.
v1.3은 돌격편 v1.1 롬의 쌍 표($B7:F09F)와 각성편 쌍 표를 같은 자리끼리 맞춰 암호 글자 152개를 찾았습니다. 각 글자가 돌격편 화면에서 어떤 한글로 보이는지는 돌격편 폰트를 픽셀로 그려 읽었습니다. 그 한글을 각성편 코드 하나로 정해 쌍 표, 입력 칸, 격자에 모두 넣었습니다. 돌격편에서 넘어온 이름을 바꾸는 표($83:AD58 → $83:AEB0)도 v1.1이 37번째 글자부터 한 칸씩 밀려 있어 같은 방식으로 맞췄습니다. 딩·렉·창은 v1.1이 격자용으로 그려 둔 자리(0x2D1, 0x2D2, 0x2D4)를 쓰고, 폰트에 없던 뎀·띠 2자는 새로 그려 넣었습니다. 새 게임의 이름 입력 화면도 같은 칸과 격자를 써서, 이름 입력 글자판의 배치도 함께 바뀌었습니다.
23바이트에 무엇이 들었는지는 돌격편이 암호를 만드는 코드(돌격편 $81:D048)와 각성편이 받는 코드($83:A313)를 읽어 맞췄습니다. 각성편이 실제로 쓰는 값은 이름, 기술 하나, 플레이 시간뿐입니다. 체력과 전투력은 암호와 상관없이 정해진 값으로 시작합니다.
| 바이트 | 돌격편에서 담는 값 | 각성편이 하는 일 |
|---|---|---|
| 0–1 | $0D99·$0D9B·$0D9D의 9비트 | 바이트 0의 비트 2가 켜져 있으면 기술 15번(손오공 파워기술 「거북선인류최종오의」)을 익힘 |
| 2–4 | $0C8B–$0C8D | 읽지 않음 |
| 5 | $0B00 | 읽지 않음 |
| 6–17 | $0B01–$0B0C (이름, 글자마다 2바이트) | 6–15를 이름 5자로 바꿔 씀 |
| 18–20 | $0C91, $0C92, $0C90 | 읽지 않음 |
| 21–22 | $022F–$0230 | $0266–$0267(플레이 시간 분·시)에 씀 |
| 23–24 | 0–22의 합(16비트) | 검사에만 씀 |
같은 화면은 글자 암호도 받습니다. 검사 코드보다 먼저 $83:A408이 입력을 문장 6개($83:B008 등)와 비교합니다. v1.1은 이 문장을 「체력을늘려줘신룡」처럼 한글로 바꿔 두었는데, v1.3이 칸을 다시 배치하면서 룡·션·싶·플·해·회 6자가 격자에서 빠질 뻔해 남는 칸에 넣었습니다. 여섯 문장은 v1.2처럼 그대로 칠 수 있습니다.
v1.3은 여기에 「손오공은사이어인」을 더했습니다. 돌격편 한글판 엔딩이 보여 주는 암호이고, 8자만 치고 입력완료를 누르면 됩니다. $83:A408이 문장을 못 찾고 끝나는 자리($83:A4D0)에서 새 루틴($83:FE00)으로 넘어가 이 문장과 비교하고, 맞으면 데이터 암호를 통과했을 때와 같은 값을 넣습니다. 시작 체력·전투력은 $83:A313과 같은 값입니다. 플레이 시간 0시간 2분은 정할 근거가 없어 임의로 고른 값이고, 돌격편 디버그 엔딩 암호에 든 값을 빌렸습니다. 이름은 암호에서 가져오지 않아 이름 입력 화면에 각성편 기본 이름이 나옵니다.
기술 15번($0D2E 비트 15)은 각성편 기술습득표의 손오공 파워기술 「거북선인류최종오의」입니다. 이 비트만 켜고 기술습득표를 열면 이 줄만 밝게 나옵니다. 돌격편에서 이 기술(돌격편 v1.1 이름 「무천류 최종오의」, $0D99 비트 9)을 켜는 명령은 카린탑에서 초신수를 마시는 장면 스크립트($B3:DB07) 한 곳뿐입니다. 「마신다」를 고르면 유도 에네르기파·초에네르기파·에네르기파 공격을 익히고, 이벤트 플래그 0x96이 켜져 있을 때만 이어서 이 기술을 익힙니다. 플래그 0x96은 펭귄 마을에서 아라레가 내는 퀴즈에서 세 번째 답(일본판 「ほひょ!?」, 돌격편 한글판 「PERFECTACLE」)을 고르면 켜지고, 퀴즈를 시작할 때마다 지워집니다($B6:BEF1, $B6:BF0E). 그래서 돌격편을 끝까지 깨도 이 기술이 없을 수 있습니다. 「손오공은사이어인」은 돌격편을 빠짐없이 마친 상태를 대신하는 암호라 이 기술을 익힌 것으로 두었습니다.
확인은 돌격편 디버그 메뉴의 엔딩에서 나오는 암호로 했습니다. 돌격편 v1.1 화면에 보이는 글자를 그대로 찾아 누르면 v1.3은 이름 입력 화면으로 넘어가고, v1.2는 「건」 칸이 없어 틀린 암호가 됩니다. 이름이 「스서인레다」가 되도록 직접 만든 암호도 v1.3에서 통과했고, 이름 화면에 그대로 「스서인레다」가 나왔습니다. 「손오공은사이어인」은 이름 화면에 기본 이름 「엑소버드」로 넘어가고, 이름을 정한 뒤의 장면 순서는 디버그 엔딩 암호와 같았습니다. 메모리에서 달라진 것은 이름과 기술 15번뿐이었습니다. 한 글자 모자란 「손오공은사이어」는 틀린 암호입니다.
이 암호를 일본판끼리 옮겨도 안 들어간다는 기록이 있었는데, 원작의 어긋남 때문이었습니다. 돌격편은 0x227(ぺ, 히라가나)을 쓰고 각성편 쌍 표는 같은 자리에 가타카나 ペ를 씁니다. 화면에서는 똑같이 보여서 히라가나로 치면 틀립니다. 돌격편이 り를 쓰는 3자리(값 0x4C, 0x73, 0x7B)도 각성편은 リ로 받습니다. 한글판은 v1.3에서 쌍 표를 돌격편 쌍 표와 같은 자리끼리 맞춰 새로 썼기 때문에, 돌격편 화면의 ぺ(성)와 り(디)를 그대로 치면 통과합니다.
돌격편 v1.1 화면에서는 서로 다른 암호 글자가 같은 모양으로 보이는 경우도 있었습니다. つ와 へ는 둘 다 「나」, も와 ら는 둘 다 「등」, ル와 ォ는 둘 다 「지」, わ·を·ん은 모두 「러」입니다. v1.3은 돌격편 글자 표가 원래 정한 대로 へ는 「다」, も는 「동」, ル는 「저」, を는 「레」, ん은 「렉」으로 보여 주고, 돌격편도 v1.2에서 같은 대응으로 고쳐 이 글자를 서로 다르게 그립니다. 두 게임이 함께 쓰는 대응표는 패치 소스의 tables/totsugeki-password.tsv에 있습니다.
9. 확인은 에뮬레이터가
- Mesen 2와 Lua 스크립트로 디버그 메뉴에서 장면을 시작하고, 버튼을 눌러 대사를 넘기며 화면을 저장합니다. 중간의 DBZ 퀴즈도 스크립트가 넘깁니다.
- 대사 포인터 표 8개와 말풍선 표($B4:8000)를 읽는 순간을 감시해 어느 프레임에 어떤 대사 ID가 떴는지, 어느 코드가 읽었는지 기록하고, 그 프레임을 찾아 찍습니다.
- 위 비교 화면은 모두 이렇게 실제 장면에서 찍었습니다. 포인터를 억지로 바꿔 만든 화면은 쓰지 않았습니다.
- 디버그 메뉴의 장면 376개를 하나씩 시작해 12,000프레임 동안 A를 40프레임마다 누르고, 뜬 대사 ID의 순서를 일본판과 비교합니다. 마지막 화면만 보는 것이 아니라 그 장면에서 나온 대사 순서 전체를 봅니다.
- v1.2는 342개 장면이 일본판과 순서가 같았습니다. 다른 34개 중 30개는 전투가 시작된 뒤부터 갈렸습니다. 대사 길이가 달라 버튼이 눌리는 순간이 바뀌면 전투 결과가 달라지기 때문입니다. 나머지는 오반이 공룡에게 쫓기는 장면($045–$047)에서 같은 대사가 반복되는 횟수, 그리고 12,000프레임 안에 v1.2가 대사 4개를 더 본 장면($052) 입니다.
- v1.3은 360개 장면이 v1.2와 같았습니다. 다른 16개는 모두 전투가 시작된 뒤 한쪽이 아직 전투 중이라 대사가 덜 나온 경우였고, 이 16개를 30,000프레임으로 다시 돌리니 13개가 같아졌습니다. 남은 3개(0x01D, 0x01E, 0x11D)는 같은 입력에도 한쪽이 전투를 더 오래 끄는 경우입니다. 일본판과는 348개가 같습니다. 「손오공은사이어인」을 더한 최종 빌드는 6개마다 하나씩 고른 63개 장면을 다시 돌려 직전 결과와 63개 모두 같은 것을 확인했습니다.
- 빌드 결과가 늘 같은 CRC32(B9F14962)인지 확인합니다.
libretro 코어를 직접 불러 쓰는 작은 프로그램을 만들어, Mesen 2와 같은 입력을 여러 에뮬레이터에 넣었습니다. 새 게임(헤더 없는 롬과 헤더 있는 롬), 함께 배포한 100% 세이브로 이어하기, 디버그 메뉴로 띄운 나레이션(장면 $084)과 전투 말풍선(장면 $00A), 돌격편 디버그 엔딩 암호와 「손오공은사이어인」, 틀린 문장, 「체력을늘려줘신룡」 입력을 일본판·v1.2와 비교했습니다. 메모리를 읽을 수 있는 코어에서는 장면 번호가 바뀌는 순서가 일본판과 같았고, 같은 프레임의 화면도 같은 대사였습니다. 코어마다 다른 것은 글자가 찍히는 타이밍 몇 프레임뿐이었습니다.
| 에뮬레이터 | 결과 |
|---|---|
| Mesen 2 | 정상. 새 게임, 헤더 있는 롬, 100% 세이브 이어하기, 디버그 메뉴로 띄운 나레이션과 말풍선, 돌격편 암호, 「손오공은사이어인」 |
| bsnes, snes9x, snes9x2010 | 정상. Mesen 2와 같은 장면, 같은 대사가 나옵니다. |
| bsnes-hd beta | 새 게임, 이어하기, 돌격편 암호, 「손오공은사이어인」은 정상(화면 216줄 출력). 이 코어는 메모리 쓰기도 치트도 받지 않아 디버그 메뉴 장면은 판정하지 못했습니다. |
| Beetle Supafaust | 새 게임, 이어하기, 돌격편 암호, 「손오공은사이어인」은 정상. 치트를 넣으면 코어가 종료돼 디버그 메뉴 장면은 판정하지 못했습니다. |
| Mesen-S 코어 | 판정 불가 (일본판 원본도 테스트 프로그램에서 바로 종료) |
10. 다음 사람에게
아래는 v1.3에 남은 것과, 이어서 작업할 때 알아 둘 것입니다.
- 마지막 말풍선 7줄(ID $137B–$1381)은 v1.1이 일본어 대사 대신 엔딩 인사와 한글화 참여자 이름을 넣은 자리라 원문과 다릅니다. v1.3도 그대로 두었습니다.
- 어느 전투 스크립트도 부르지 않는 말풍선 306개는 실제로 뜨는지 확인하지 못했습니다. 창 크기를 알 수 없어, 일본어 원문이 3줄 이하면 12자 × 3줄, 4줄 이상이면 8자 × 5줄 창에 맞췄습니다. 일본어 원문부터 어느 창에도 맞지 않는 4개(ID 0x10AF, 0x10C6, 0x11CF, 0x11FD)는 폭 검사에서 경고만 냅니다.
- 말풍선 글자는 대사 빌드가 만든 글자 표만 씁니다. 말풍선에만 쓰인 글자 자리는 대사 빌드가 빈 자리로 볼 수 있으니, 새 글자를 더 넣을 때는 그 글자가 말풍선에 쓰였는지 먼저 확인하세요.
- 돌격편에서 초신수를 마시지 않아도 엔딩까지 갈 수 있는지는 확인하지 않았습니다. 기술 15번의 조건은 스크립트와 명령 처리 코드를 읽어 찾은 것이고, 돌격편을 직접 플레이해 확인하지는 않았습니다.
- 376개 장면 회귀는 각 장면을 12,000프레임까지만 돌렸고, 전투는 입력 타이밍에 따라 결과가 달라져 끝까지 같은 순서로 비교하지 못했습니다.
- 남은 빈 공간은 대사 뱅크마다 위 표의 값, 말풍선 뱅크
$B4의 3,669바이트, 타이틀용으로 비워 둔 파일 0x4C801–0x4FFFF(14,335바이트), 크레딧 뒤 파일 0x1F53C–0x1FFFF(2,756바이트)입니다. 뒤의 두 곳은 일본판에서도 0xFF입니다. - 타이틀 로고 폰트(BM 영성체)는 macOS 시스템 폰트 경로에서 읽습니다. 다른 환경에서는
tools/title.py의TITLE_FONT를 바꿔야 합니다. - 쓴 도구는 Python 3.9.6, Pillow 11.3.0, Mesen 2.1.1(macOS)입니다. 패치 소스를 다시 빌드해 CRC32 B9F14962를 확인했습니다.
11. 패치 소스 파일
같은 엔진을 쓰는 초오공전 돌격편을 분석할 때도 주소만 바꾸면 대부분 쓸 수 있습니다. 돌격편에서 찾은 주소는 그 페이지의 롬 분석 노트에 있습니다.
| 파일 | 하는 일 | 설명 |
|---|---|---|
tools/build.sh | 아래 순서대로 전체 빌드 | 11. 패치 소스 파일 |
tools/dialogue.py | 대사 ID로 대사 위치 찾기 | 2. 대사 |
tools/extract.py | 일본어·한국어 대사 덤프(TSV) | 2. 대사 |
tools/font.py | 대사 폰트 글자 읽기 | 3. 폰트 |
tools/usedsheet.py | 대사에 쓰인 글자 목록 | 3. 폰트 |
tools/dialogue_patch.py | 대사 넣기, 옮기기, 새 글자 그리기 | 4. 대사 옮기기 |
tools/bubble_patch.py | 전투 말풍선 넣기, 옮기기 | 2. 대사 |
tools/check_width.py | 창보다 넓은 줄이 있으면 빌드 실패 | 2. 대사 |
tools/fix_overwrites.py | v1.1이 덮어쓴 데이터 되살리기 | 5. 진행 버그 |
krpatch lzss | 공통 도구. LZSS 압축·해제 | 6. 타이틀 |
tools/title.py | 타이틀 그림 그리기·넣기 | 6. 타이틀 |
tools/credits.py | 엔딩 크레딧 고치기 | 7. 크레딧 |
tools/password.py | 돌격편 암호 입력 칸·검사 표 맞추기 | 8. 돌격편 암호 |
tables/totsugeki-password.tsv | 돌격편 v1.1 롬에서 꺼낸 암호 글자와 쌍 표 | 8. 돌격편 암호 |
krpatch snes checksum | 공통 도구. 롬 헤더 체크섬 다시 계산 | 11. 패치 소스 파일 |
krpatch ips | 공통 도구. IPS 적용·생성 | 11. 패치 소스 파일 |
tables/ | 일본판·v1.1·v1.3 글자 표, 고친 대사 782줄, 고친 말풍선 443개 | 2. 대사 |
lua/ | Mesen 2 스크립트: 디버그 메뉴 장면 시작, 크레딧 바로 보기 | 9. 확인 |
compat/ | Mesen 2와 libretro 코어에 같은 입력을 넣어 비교 | 9. 확인 |
krpatch snes run | 공통 도구. libretro 코어를 직접 불러 같은 입력을 넣는 작은 프로그램 | 9. 확인 |
docs/ANALYSIS.md | 롬 분석 문서(주소·형식 전체) | 1. 배경지식 |
패치 소스의 게임 폴더에 일본판 원본을 넣고 빌드 스크립트를 돌리면 아래 순서로 v1.3과 같은 롬이 나옵니다.
- 일본판 원본에 v1.1 IPS를 적용합니다.
- v1.1이 덮어쓴 데이터를 되살립니다.
- 타이틀 그림을 그려 다시 압축해 넣습니다.
- 고친 대사 782줄을 넣고, 늘어난 대사는 옮긴 뒤 포인터를 고칩니다.
- 고친 전투 말풍선 443개를 같은 방식으로 넣습니다.
- 돌격편 암호 입력 칸과 검사 표를 돌격편 화면 글자에 맞춥니다.
- 엔딩 크레딧을 고칩니다.
- 모든 대사와 말풍선이 창 안에 들어가는지 검사하고, 넘치면 빌드를 멈춥니다.
- 롬 헤더 체크섬을 다시 계산하고 IPS를 만듭니다.
이 노트의 개념을 설명하는 장
직접 해 보기 소스와 도구
- 패치 소스 — 이 노트가 가리키는 파일과 빌드 순서(한국어 안내)
- 롬 분석 문서 v1.3 — 주소와 형식을 [실측]·[추정]으로 나눠 적은 한국어 문서
- kr-patch-toolkit v0.2.0 — 공통 명령 krpatch 한국어 설명
- 드래곤볼 Z 초오공전 각성편 한글패치 페이지 — 패치 내려받기와 롬에 바로 적용
댓글