
이야기


슈퍼 패미컴 · 1996 · 한글패치 v1.1
키노피오메신저 앱 안에서 열린 화면이라 파일을 고르거나 저장하지 못할 수 있습니다. 메뉴에서 "다른 브라우저로 열기"를 눌러 크롬이나 사파리로 열어 주세요.
키노피오
파일은 이 기기 안에서만 바꾸고 어디에도 보내지 않습니다.
IPS 파일 받기를 누른 뒤 Floating IPS·Lunar IPS·ROM Patcher JS 같은 도구로 헤더 없는 일본판 원본에 적용하세요. 헤더가 있는 파일(4MB+512바이트)은 이 페이지의 패치 상자를 쓰면 알아서 처리합니다.
| 구분 | 크기 | CRC32 | MD5 |
|---|---|---|---|
| 일본판 원본 | 4,194,304바이트 | 5527071E | 59483FEB9EB20207E3241E005FBDDE7E |
| 한글판 v1.1 | 4,194,304바이트 | ABD8AC13 | 8E5320631EDCF274BF5C7A62C0417C55 |
















































점프 · 불꽃
이번에도 피치 공주를 구하러 나섰다가, 스타 피스를 모으는 모험을 맡게 됩니다.

날씨 · 회복
자신을 개구리라 믿는 구름 같은 아이. 잘 울지만 날씨를 부리는 힘이 있습니다.

천공의 사자
천공에서 내려와 인형의 몸을 빌린 사자. 스타 로드를 고치려 마리오와 함께합니다.

힘 · 물리 공격
거대한 검에 성을 빼앗긴 라이벌. 성을 되찾으려 이번에는 같은 편이 됩니다.

회복 · 지원
구해야 할 공주이자, 합류한 뒤에는 회복으로 일행을 지탱하는 동료입니다.
v1.0은 대사와 메뉴는 한글이었지만 아이템·필살기·적 이름은 MUSHROOM, HAMMER 같은 영문이었습니다. 롬 용량이 모자라서가 아니라 메뉴 화면에 한 번에 올려 둘 수 있는 글자가 256개뿐이었기 때문입니다. v1.1은 필요한 글자만 그때그때 빈 칸에 올리는 코드를 게임에 끼워 넣어 이 문제를 풀었습니다. 롬 크기는 원본과 같은 4MB입니다.

슈퍼 패미컴 구조나 65c816 어셈블리를 몰라도 읽을 수 있게 풀어 썼습니다.
빌드에 쓴 스크립트, 어셈블리 소스, 이름 데이터, v1.0 코드표는빌드 도구(zip, 600KB)로 받을 수 있습니다. 롬, asar, Mesen은 들어 있지 않고, 빌드 순서는 압축 파일 안 README.txt에 있습니다. 아래 「자세히 보기」의 항목마다 그 내용을 다루는 파일을 적어 두었습니다.
CPU는 화면에 직접 그리지 않습니다. 그림 조각(타일)을 VRAM에 올려 두고 "이 칸에는 몇 번 타일" 이라는 배치표(타일맵)를 적어 화면을 만듭니다. 글자도 타일입니다.
| 이름 | 크기 | 비유 | 하는 일 |
|---|---|---|---|
| 롬 | 4MB | 책 | 카트리지에 든 프로그램·대사·폰트. 읽기만 합니다. |
| WRAM | 128KB | 작업 책상 | CPU가 계산하며 값을 적어 두는 곳. HP와 인벤토리도 여기 있습니다. |
| VRAM | 64KB | 스티커판 | 화면에 붙일 8×8 그림 조각(타일)을 올려 두는 곳. |
| BW-RAM | - | SA-1 책상 | 카트리지 안의 두 번째 CPU(SA-1)가 쓰는 메모리. |
이 게임 카트리지에는 SA-1이라는 CPU가 하나 더 들어 있습니다. 본체 CPU(S-CPU, 3.58MHz)보다 세 배쯤 빠르고, 두 CPU가 동시에 돕니다. 둘 다 65c816 계열이라 같은 어셈블리로 짭니다. 필드 메뉴는 S-CPU가, 전투 메뉴는 SA-1이 그려서 아이템 이름 하나를 바꾸려 해도 두 길을 따로 고쳐야 했습니다.
0x22B400 같은 파일 오프셋은 롬 파일을 처음부터 센 위치이고, $E2:B400 같은 SNES 주소는 CPU가 보는 뱅크:주소입니다. 뱅크는 64KB짜리 칸 번호입니다. 이 게임은 다음처럼 바꿉니다.
파일 오프셋 = (뱅크 & 0x3F) × 0x10000 + 주소
$E2:B400 → 0x22 × 0x10000 + 0xB400 = 0x22B400어셈블리는 CPU가 실행하는 명령을 사람이 읽는 이름으로 적은 것이고, 롬 안에서는 그냥 바이트입니다. 전투 디버그 메뉴를 막는 코드(파일 0x106A9)를 예로 보겠습니다.
| 바이트 | 어셈블리 | 뜻 |
|---|---|---|
AF AE FF 00 | LDA $00FFAE | $00:FFAE 값을 읽습니다. 주소는 거꾸로 적힙니다. |
05 80 | ORA $80 | $80번지 값과 OR 합니다. 둘 다 0일 때만 0입니다. |
F0 01 | BEQ +1 | 결과가 0이면 다음 1바이트(RTS)를 건너뛰고 디버그 메뉴로 갑니다. |
60 | RTS | 아니면 그냥 돌아갑니다. 제품판은 늘 여기로 옵니다. |
F0(BEQ, 0이면 건너뛰기) 한 바이트를 80(BRA, 무조건 건너뛰기)으로 바꾸면 전투 중 Start로 개발자용 디버그 메뉴가 열립니다. 롬 해킹은 이런 바이트 바꾸기의 연속입니다.
원래 코드 사이에는 새 기능을 넣을 자리가 없습니다. 그래서 원래 코드의 한 지점을 "새 코드로 가라" 는 명령으로 덮어쓰고, 새 코드는 롬의 빈 공간에 둡니다. 실제 패치 소스는 이렇게 생겼습니다.
org $C37990 ; 원래 아이템 이름을 그리러 가는 자리를
JMP DrawItemName ; "DrawItemName으로 가라" 로 덮어쓴다
org $C3F000 ; 원본에서 비어 있던(0xFF) 공간
DrawItemName: ; 한글 이름을 그리는 새 코드
...v1.1은 필드 메뉴, 전투 메뉴, 결과 창, 레벨업 화면, 대사 엔진 등 스무 곳 남짓에 이런 후킹을 걸었습니다.
메뉴 폰트의 글자 하나는 가로 8, 세로 12 픽셀 그림이고 한 줄에 2바이트씩 24바이트를 씁니다. 아래는 실제 롬에서 꺼낸 「마」입니다.
아이템 이름은 글자 하나를 1바이트 코드로 적습니다. 1바이트로 고를 수 있는 값은 256개이고 0x00–0x1F를 빼면 글자 칸은 224개입니다. 메뉴를 열면 게임은 이 폰트 전체를 VRAM에 통째로 올려 두고, 코드 c를 (c − 0x20) × 2번 타일로 고정해서 그립니다. 아래는 v1.0 롬의 메뉴 폰트 256칸입니다. 회색은 빈 코드, 노란색은 아이콘·영숫자, 파란색은 v1.0이 넣은 한글 165자입니다.

빈칸이 없습니다. 이름을 한글로 쓰려면 음절이 226자 필요했는데 이 표에 있는 것은 88자뿐이었습니다. 138자가 들어갈 칸이 없었던 것입니다. 롬을 키워도 칸 수는 늘지 않으니 병목은 용량이 아니라 "1바이트 코드 = 화면에 상주하는 256칸" 이라는 구조였습니다.
필드 메뉴의 VRAM을 덤프해 보니 BG1 타일 $200–$2FF(VRAM $6000–$7FFF)는 이 화면에서 아무도 쓰지 않았습니다. 메뉴를 열 때마다 원래 내용을 다시 올리므로 잠깐 빌려 써도 뒤탈이 없습니다. 글자 하나가 타일 2장이니 128글자가 들어갑니다.
새 이름 테이블은 롬 $E2:B400의 빈 공간에 만들고 글자를 2바이트로 적었습니다. 0x0000–0x00FF는 원래 메뉴 폰트의 글자이고, 0x8000 | n은 롬에 따로 넣은 글자 248자 중 n번째입니다. 그리는 순서는 이렇습니다.
마지막 단계가 바로 복사가 아닌 이유가 있습니다. 브라운관 TV는 화면을 위에서 아래로 한 줄씩 그리고, 그동안에는 CPU가 VRAM을 건드릴 수 없습니다. 한 화면을 다 그리고 다음 화면을 시작하기 전 짧은 틈(VBlank)에만 쓸 수 있고, 그 틈이 시작될 때 걸리는 신호가 NMI입니다. 그래서 미리 준비해 둔 글자를 NMI 때 DMA(CPU 대신 데이터를 빠르게 옮기는 장치)로 옮깁니다.
전투 메뉴는 타일 방식이 아니라 SA-1이 BW-RAM에 비트맵을 만들고 글자를 직접 찍습니다. 같은 이름 테이블을 쓰되 글자 읽기, 그림 주소 계산, 이름 복사 세 곳을 후킹했습니다. 그림 주소를 $E4:2B00에 16비트 값을 더해 구하기 때문에 새 글자는 거기서 64KB 안에만 둘 수 있어서, 그 범위의 빈 구간 네 곳에 나눠 넣었습니다.

SA-1은 4MB 롬을 1MB짜리 창 4개로 CPU에 보여 줍니다. 4MB 너머를 읽으려면 실행 중에 창 하나를 다른 블록으로 돌려야 하는데, 두 CPU가 동시에 돌기 때문에 한쪽이 창을 돌린 순간 다른 쪽이 원래 데이터를 읽으려 하면 엉뚱한 값을 받습니다. 책 한 권을 둘이 같이 읽는데 한 사람이 말없이 페이지를 넘기는 상황입니다.
그래서 롬을 늘리지 않고, 일본판 원본에서도 0이나 0xFF로 비어 있던 공간만 찾아 썼습니다.
대사는 4096개이고 ID로 찾습니다. 위치를 두 단계로 찾아가므로 대사를 고치면 이 표들도 다시 만들어야 합니다.
v1.0은 대사에 『MUSHROOM』을 얻었습니다처럼 영문 이름을 직접 적었습니다. v1.1은 이를 한글로 바꾸고 그룹 순서를 지킨 채 뱅크마다 다시 채운 뒤 헤더와 포인터 표를 새로 만들었습니다. 한글 이름이 짧고 쓰레기 바이트도 빼서 세 뱅크 합쳐 약 1만 9천 바이트가 오히려 줄었습니다.
대사 안에는 글자 말고 명령도 섞여 있습니다. 00은 대사 끝, 01은 줄바꿈, 1A는 "여기 아이템 이름을 끼워라" 입니다. 원판에서 아무 일도 안 하던 1B를 1A와 같은 처리로 보내고, 뒤에 붙는 1바이트로 「」 감싸기, 가운데 정렬, 을/를 붙이기를 고르게 했습니다. 이름 길이는 실행 중에야 정해지므로 가운데 정렬도 실행 중에 합니다.

대사 폰트는 1바이트와 2바이트 코드를 섞어 992자를 쓰는데, 2바이트 코드 768개가 v1.0에서 전부 차 있었습니다. 이름에 필요한 캔, 깃, 링, 쑥, 츠 같은 21자는 어떤 텍스트에서도 쓰지 않는 코드를 찾아 그 자리에 그렸습니다. 처음에는 대사만 보고 골랐다가 메뉴 튜토리얼의 「봅」이 깨졌습니다. 같은 인코딩이 전투 대사, 아이템 설명, 메뉴 문구 등에도 쓰였기 때문입니다. 지금은 모든 영역을 검사해서, 쓰이는 코드를 재활용하려 하면 빌드가 멈춥니다.
대사를 다시 채우다 v1.0이 남긴 문제도 찾았습니다. 끝 코드 뒤에 원문 조각이 남아 있었고, 끝 코드가 너무 일찍 들어가 비어 버린 대사도 있었습니다. 일본판과 대조해 복구했습니다.

"레벨3(이)가", "(을)를 배웠습니다" 같은 표기는 이름이 실행 중에 정해져서 생깁니다. v1.1은 빌드할 때 이름 마지막 글자에 받침이 있는지 계산해 이름 앞 1바이트에 표시해 두고, 게임은 그 비트만 보고 을/를을 고릅니다.
받침 번호 = (글자 코드 − 0xAC00) % 28 ; 0이면 받침 없음
버섯 → 「섯」 받침 있음 → 버섯을
회복샤워 → 「워」 받침 없음 → 회복샤워를숫자는 읽는 소리로 정했습니다. 마지막 자리가 2, 4, 5, 9면 「가」, 나머지는 「이」입니다.

전투 결과 창은 아이템 이름을 오른쪽 정렬하고, 5글자 이하면 「아이템」과 같은 줄에 두며 창을 한 줄 줄였습니다. 필살기 화면 안내는 이름을 4칸 고정으로 그려 「マロ が…」처럼 빈칸이 생기던 것을 이름 바로 뒤로 당겼습니다.

