오늘도 끄적끄적

슈퍼 패미컴 · 1996 · 한글패치 v1.1

슈퍼 마리오 RPG 한글판

키노피오 마리오 님, 가지고 계신 일본판 게임 파일을 고르시면 바로 한글판으로 바꿔 저장해 드려요. 따로 프로그램을 설치하지 않아도 돼요.

파일은 이 기기 안에서만 바꾸고 어디에도 보내지 않습니다.

직접 패치하려면

IPS 파일 받기를 누른 뒤 Floating IPS·Lunar IPS·ROM Patcher JS 같은 도구로 헤더 없는 일본판 원본에 적용하세요. 헤더가 있는 파일(4MB+512바이트)은 이 페이지의 패치 상자를 쓰면 알아서 처리합니다.

헤더 없는 파일 기준
구분크기CRC32MD5
일본판 원본4,194,304바이트5527071E59483FEB9EB20207E3241E005FBDDE7E
한글판 v1.14,194,304바이트ABD8AC138E5320631EDCF274BF5C7A62C0417C55
한국어 대사로 '나는 천공의 사자!'라고 외치는 지노

한국어로 바뀐 것

키노피오같은 장면을 같은 입력으로 찍어서 일본판과 한글판을 나란히 놓았어요.

이야기

키노피오마을 사람과 안내 대사까지 모두 한국어예요.
일본판: 마리오를 맞이하는 버섯 대신
한글판: 마리오를 맞이하는 버섯 대신
키노피오버섯 대신님의 정중한 말투도 그대로예요.
일본판: 피치 공주의 납치를 걱정하는 버섯 대신
한글판: 피치 공주의 납치를 걱정하는 버섯 대신
키노피오공주님이 또 납치되셨다는 소식으로 모험이 시작돼요.
일본판: 마로를 꾸짖는 개구리 선인
한글판: 마로를 꾸짖는 개구리 선인
키노피오개구리 선인님의 호통도 한국어로 들을 수 있어요.
일본판: 메뉴 여는 법을 알려 주는 키노피오
한글판: 메뉴 여는 법을 알려 주는 키노피오
키노피오처음이신 분께 메뉴 여는 법을 알려 드리는 저도 한국어로 말해요!

동료

키노피오동료마다 다른 말투를 원작 느낌 그대로 옮겼어요.
일본판: 크로코를 뒤에서 잡자고 말하는 마로
한글판: 크로코를 뒤에서 잡자고 말하는 마로
키노피오마로 님의 공손한 말투예요.
일본판: 울음을 참는 마로
한글판: 울음을 참는 마로
키노피오울보 마로 님의 「울면 안 돼.」도 그대로예요.
일본판: 크게 우는 마로
한글판: 크게 우는 마로
키노피오‥‥결국 울음이 터지는 장면까지요.
일본판: 별을 돌려 달라고 외치는 인형
한글판: 별을 돌려 달라고 외치는 인형
키노피오정체 모를 인형이 별을 돌려 달라며 나타나요.
5장면 더 보기
일본판: 숲속 공터에서 천공의 사자라고 외치는 지노
한글판: 숲속 공터에서 천공의 사자라고 외치는 지노
키노피오「나는 천공의 사자!」 인형이 스스로를 밝히는 순간이에요.
일본판: 지노라는 이름을 얻는 장면
한글판: 지노라는 이름을 얻는 장면
키노피오「지노」라는 이름이 생기는 장면이에요.
일본판: 집도 없다며 한탄하는 쿠파
한글판: 집도 없다며 한탄하는 쿠파
키노피오성을 빼앗긴 쿠파의 쓸쓸한 혼잣말이에요.
일본판: 마리오 일행을 쿠파 군단에 넣어 주겠다는 쿠파
한글판: 마리오 일행을 쿠파 군단에 넣어 주겠다는 쿠파
키노피오「쿠파 군단」에 넣어 주겠다니, 쿠파다운 합류 인사예요.
일본판: 동료가 된 쿠파
한글판: 동료가 된 쿠파
키노피오드디어 쿠파가 동료가 돼요!

개성 있는 적

키노피오영어가 섞인 말투나 엉뚱한 말버릇도 빠짐없이 옮겼어요.
일본판: 마리오를 놀리는 크로코
한글판: 마리오를 놀리는 크로코
키노피오크로코의 「OH!」처럼 영어가 섞인 말투도 그대로예요.
일본판: 막다른 길에 몰린 크로코
한글판: 막다른 길에 몰린 크로코
키노피오「ROAD다!」까지 살렸어요.
일본판: 화살 1000개째를 세는 유민파
한글판: 화살 1000개째를 세는 유민파
키노피오유민파의 「냐아~」 말버릇도요.
일본판: 신부에게 말을 거는 부키
한글판: 신부에게 말을 거는 부키
키노피오부키의 엉뚱한 말투도요.
1장면 더 보기
일본판: 짜다고 말하는 부키
한글판: 짜다고 말하는 부키
키노피오「짜다.」 한마디면 충분하죠.

전투와 메뉴

키노피오전투 명령, 적 이름, 아이템, 필살기 설명까지 한국어예요.
일본판: 전투 명령을 고르는 화면
한글판: 전투 명령을 고르는 화면
키노피오전투 명령과 적 이름도 한국어예요.
일본판: 필살기 설명이 보이는 메뉴
한글판: 필살기 설명이 보이는 메뉴
키노피오필살기 설명을 읽으면 버튼을 누를 때를 알 수 있어요.

이야기

버섯 대신마리오 씨, 큰일입니다! 공주님을 구하러 쿠파성에 가셨을 때, 하늘에서 거대한 검이 내리꽂혔지요. 그 바람에 마리오 씨는 성 밖으로 튕겨 나가고 말았습니다.
버섯 대신검과 함께 나타난 카지오 군단이 소원을 이루어 주는 스타 로드를 부숴 버렸습니다. 흩어진 스타 피스 7개는 세계 곳곳으로 떨어졌다고 합니다.
버섯 대신스타 피스를 모아 스타 로드를 고치지 않으면 누구의 소원도 이루어지지 않습니다. 마리오 씨, 부탁이네! 서둘러주게!!
1996년, 스퀘어가 개발하고 닌텐도가 발매한 슈퍼 패미컴 RPG. 마리오다운 점프와 장난기 위에, 동료를 모으고 성장하는 RPG의 재미를 얹었다.
피치 공주가 또 납치됐다며 놀라는 버섯 대신
공주의 기운을 느끼며 성 안을 걷는 마리오

진행 방식

바위 언덕 사이로 두더지 마을을 둘러보는 마리오

탐험

키노피오마리오 님, 이곳은 비스듬히 내려다보는 입체 세계예요. 높은 곳에 올라서거나 숨은 상자를 찾으며 다음 마을로 가 보세요.
마리오가 전투 명령을 고르는 화면

전투

키노피오필드에 보이는 적과 부딪히면 전투가 시작돼요. 버튼마다 명령이 하나씩 붙어 있어요.
  • A공격
  • X필살기
  • Y아이템
  • B방어
마리오와 마로의 필살기 설명이 보이는 메뉴

타이밍

키노피오공격이 닿는 순간 버튼을 누르면 피해가 커지고, 맞는 순간 누르면 피해가 줄어요. 필살기마다 누르는 방법이 다르니 메뉴에서 설명을 읽어 보세요.
아이템 이름과 설명이 보이는 상점 화면

성장

키노피오레벨이 오르면 능력치 하나를 골라 키울 수 있어요. 상점에서 장비와 아이템을 갖추고, 동료를 바꿔 가며 싸워 보세요.

주인공과 동료

한글판을 만든 방법

v1.0은 대사와 메뉴는 한글이었지만 아이템·필살기·적 이름은 MUSHROOM, HAMMER 같은 영문이었습니다. 롬 용량이 모자라서가 아니라 메뉴 화면에 한 번에 올려 둘 수 있는 글자가 256개뿐이었기 때문입니다. v1.1은 필요한 글자만 그때그때 빈 칸에 올리는 코드를 게임에 끼워 넣어 이 문제를 풀었습니다. 롬 크기는 원본과 같은 4MB입니다.

위는 v1.0, 아래는 v1.1 상점 화면. 영문 아이템 이름이 한글로 바뀌었다
위 v1.0, 아래 v1.1

슈퍼 패미컴 구조나 65c816 어셈블리를 몰라도 읽을 수 있게 풀어 썼습니다.

빌드에 쓴 스크립트, 어셈블리 소스, 이름 데이터, v1.0 코드표는빌드 도구(zip, 600KB)로 받을 수 있습니다. 롬, asar, Mesen은 들어 있지 않고, 빌드 순서는 압축 파일 안 README.txt에 있습니다. 아래 「자세히 보기」의 항목마다 그 내용을 다루는 파일을 적어 두었습니다.

자세히 보기

1. 배경지식

게임기 안의 메모리

CPU는 화면에 직접 그리지 않습니다. 그림 조각(타일)을 VRAM에 올려 두고 "이 칸에는 몇 번 타일" 이라는 배치표(타일맵)를 적어 화면을 만듭니다. 글자도 타일입니다.

이름크기비유하는 일
롬4MB책카트리지에 든 프로그램·대사·폰트. 읽기만 합니다.
WRAM128KB작업 책상CPU가 계산하며 값을 적어 두는 곳. HP와 인벤토리도 여기 있습니다.
VRAM64KB스티커판화면에 붙일 8×8 그림 조각(타일)을 올려 두는 곳.
BW-RAM-SA-1 책상카트리지 안의 두 번째 CPU(SA-1)가 쓰는 메모리.

CPU가 둘: S-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 00LDA $00FFAE$00:FFAE 값을 읽습니다. 주소는 거꾸로 적힙니다.
05 80ORA $80$80번지 값과 OR 합니다. 둘 다 0일 때만 0입니다.
F0 01BEQ +1결과가 0이면 다음 1바이트(RTS)를 건너뛰고 디버그 메뉴로 갑니다.
60RTS아니면 그냥 돌아갑니다. 제품판은 늘 여기로 옵니다.

F0(BEQ, 0이면 건너뛰기) 한 바이트를 80(BRA, 무조건 건너뛰기)으로 바꾸면 전투 중 Start로 개발자용 디버그 메뉴가 열립니다. 롬 해킹은 이런 바이트 바꾸기의 연속입니다.

후킹: 원래 코드 중간에 새 코드 끼우기

원래 코드 사이에는 새 기능을 넣을 자리가 없습니다. 그래서 원래 코드의 한 지점을 "새 코드로 가라" 는 명령으로 덮어쓰고, 새 코드는 롬의 빈 공간에 둡니다. 실제 패치 소스는 이렇게 생겼습니다.

org $C37990          ; 원래 아이템 이름을 그리러 가는 자리를
    JMP DrawItemName ; "DrawItemName으로 가라" 로 덮어쓴다

org $C3F000          ; 원본에서 비어 있던(0xFF) 공간
DrawItemName:        ; 한글 이름을 그리는 새 코드
    ...

v1.1은 필드 메뉴, 전투 메뉴, 결과 창, 레벨업 화면, 대사 엔진 등 스무 곳 남짓에 이런 후킹을 걸었습니다.

2. 왜 v1.0은 이름을 영문으로 뒀나

메뉴 폰트의 글자 하나는 가로 8, 세로 12 픽셀 그림이고 한 줄에 2바이트씩 24바이트를 씁니다. 아래는 실제 롬에서 꺼낸 「마」입니다.

메뉴 폰트의 「마」 글자를 8×12 픽셀 격자와 줄마다의 바이트 값으로 나타낸 그림

아이템 이름은 글자 하나를 1바이트 코드로 적습니다. 1바이트로 고를 수 있는 값은 256개이고 0x00–0x1F를 빼면 글자 칸은 224개입니다. 메뉴를 열면 게임은 이 폰트 전체를 VRAM에 통째로 올려 두고, 코드 c를 (c − 0x20) × 2번 타일로 고정해서 그립니다. 아래는 v1.0 롬의 메뉴 폰트 256칸입니다. 회색은 빈 코드, 노란색은 아이콘·영숫자, 파란색은 v1.0이 넣은 한글 165자입니다.

v1.0 메뉴 폰트 256칸. 0x20부터 0xFF까지 빈칸 없이 아이콘·영숫자·한글로 차 있다

빈칸이 없습니다. 이름을 한글로 쓰려면 음절이 226자 필요했는데 이 표에 있는 것은 88자뿐이었습니다. 138자가 들어갈 칸이 없었던 것입니다. 롬을 키워도 칸 수는 늘지 않으니 병목은 용량이 아니라 "1바이트 코드 = 화면에 상주하는 256칸" 이라는 구조였습니다.

3. 필요한 글자만 올리는 임시 칸

필드 메뉴의 VRAM을 덤프해 보니 BG1 타일 $200–$2FF(VRAM $6000–$7FFF)는 이 화면에서 아무도 쓰지 않았습니다. 메뉴를 열 때마다 원래 내용을 다시 올리므로 잠깐 빌려 써도 뒤탈이 없습니다. 글자 하나가 타일 2장이니 128글자가 들어갑니다.

필드 메뉴 VRAM. 타일 $000–$1FF는 상주 폰트, $200–$2FF는 128칸짜리 임시 글자 칸

새 이름 테이블은 롬 $E2:B400의 빈 공간에 만들고 글자를 2바이트로 적었습니다. 0x0000–0x00FF는 원래 메뉴 폰트의 글자이고, 0x8000 | n은 롬에 따로 넣은 글자 248자 중 n번째입니다. 그리는 순서는 이렇습니다.

  1. 이름에서 글자 하나를 읽습니다.
  2. 메뉴 폰트에 있는 글자면 원래 타일을 그대로 씁니다.
  3. 없는 글자면 이미 올려 둔 임시 칸이 있는지 찾고, 없으면 빈 칸을 하나 배정합니다.
  4. 게임의 외곽선 루틴($C3:6869)으로 8×12 글자를 메뉴 글자 모양으로 바꿔 WRAM에 모아 둡니다(최대 12자).
  5. 화면 갱신 틈(NMI)에 DMA로 VRAM에 한꺼번에 옮깁니다.

마지막 단계가 바로 복사가 아닌 이유가 있습니다. 브라운관 TV는 화면을 위에서 아래로 한 줄씩 그리고, 그동안에는 CPU가 VRAM을 건드릴 수 없습니다. 한 화면을 다 그리고 다음 화면을 시작하기 전 짧은 틈(VBlank)에만 쓸 수 있고, 그 틈이 시작될 때 걸리는 신호가 NMI입니다. 그래서 미리 준비해 둔 글자를 NMI 때 DMA(CPU 대신 데이터를 빠르게 옮기는 장치)로 옮깁니다.

만들며 밟은 지뢰

  • 처음에는 게임의 "NMI 때 할 일" 목록(8칸)에 업로드를 등록했습니다. 그런데 목록이 꽉 차면 등록 루틴이 사운드 명령을 보내고 등록을 포기해서, 토드의 메뉴 튜토리얼에서 음악이 바뀌었습니다. 결국 NMI 루틴 자체를 후킹했습니다.
  • 패치 변수를 평소 0인 WRAM $7E:F580 근처에 뒀는데, 아이템을 캐릭터에게 가져갈 때 이름을 그리는 스프라이트 버퍼였습니다. "평소에 0" 이 "아무도 안 쓴다" 는 뜻은 아니었습니다.

4. 전투 메뉴는 SA-1이 따로 그린다

전투 메뉴는 타일 방식이 아니라 SA-1이 BW-RAM에 비트맵을 만들고 글자를 직접 찍습니다. 같은 이름 테이블을 쓰되 글자 읽기, 그림 주소 계산, 이름 복사 세 곳을 후킹했습니다. 그림 주소를 $E4:2B00에 16비트 값을 더해 구하기 때문에 새 글자는 거기서 64KB 안에만 둘 수 있어서, 그 범위의 빈 구간 네 곳에 나눠 넣었습니다.

위는 v1.0, 아래는 v1.1 전투 화면. 적 이름, 아이템 목록, 사용 메시지가 한글이 됐다
위 v1.0, 아래 v1.1

5. 롬을 4MB보다 키우지 않은 이유

SA-1은 4MB 롬을 1MB짜리 창 4개로 CPU에 보여 줍니다. 4MB 너머를 읽으려면 실행 중에 창 하나를 다른 블록으로 돌려야 하는데, 두 CPU가 동시에 돌기 때문에 한쪽이 창을 돌린 순간 다른 쪽이 원래 데이터를 읽으려 하면 엉뚱한 값을 받습니다. 책 한 권을 둘이 같이 읽는데 한 사람이 말없이 페이지를 넘기는 상황입니다.

S-CPU와 SA-1이 $C0, $D0, $E0, $F0 창 4개로 롬 블록 0–3을 읽는다. 창을 블록 4로 돌리면 다른 CPU가 잘못 읽는다

그래서 롬을 늘리지 않고, 일본판 원본에서도 0이나 0xFF로 비어 있던 공간만 찾아 썼습니다.

4MB 롬 지도. v1.1은 원본의 빈 공간 몇 곳에만 코드와 데이터를 넣었다

6. 대사 속 아이템 이름

대사는 4096개이고 ID로 찾습니다. 위치를 두 단계로 찾아가므로 대사를 고치면 이 표들도 다시 만들어야 합니다.

  1. 대사 ID(0x000–0xFFF)를 512로 나눠 그룹(0–7)을 구합니다.
  2. 뱅크 $E2–$E4 첫머리 헤더에서 그 그룹의 시작 주소를 찾습니다.
  3. 포인터 표 $F7:E000에서 ID마다 적힌 2바이트(그룹 안 위치)를 더합니다.
  4. 그 자리에 대사 문자열이 있습니다.

v1.0은 대사에 『MUSHROOM』을 얻었습니다처럼 영문 이름을 직접 적었습니다. v1.1은 이를 한글로 바꾸고 그룹 순서를 지킨 채 뱅크마다 다시 채운 뒤 헤더와 포인터 표를 새로 만들었습니다. 한글 이름이 짧고 쓰레기 바이트도 빼서 세 뱅크 합쳐 약 1만 9천 바이트가 오히려 줄었습니다.

안 쓰던 제어 코드 되살리기

대사 안에는 글자 말고 명령도 섞여 있습니다. 00은 대사 끝, 01은 줄바꿈, 1A는 "여기 아이템 이름을 끼워라" 입니다. 원판에서 아무 일도 안 하던 1B를 1A와 같은 처리로 보내고, 뒤에 붙는 1바이트로 「」 감싸기, 가운데 정렬, 을/를 붙이기를 고르게 했습니다. 이름 길이는 실행 중에야 정해지므로 가운데 정렬도 실행 중에 합니다.

획득 메시지. 위부터 일본판, v1.0, v1.1이고 노란 선이 화면 가운데다
노란 선이 화면 가운데. 위부터 일본판, v1.0, v1.1

대사 폰트에 없는 21자

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

대사를 다시 채우다 v1.0이 남긴 문제도 찾았습니다. 끝 코드 뒤에 원문 조각이 남아 있었고, 끝 코드가 너무 일찍 들어가 비어 버린 대사도 있었습니다. 일본판과 대조해 복구했습니다.

두더지 마을 양동이 소녀에게 카보쿠키를 주면 v1.0에서 빠졌던 개구리 코인 획득 메시지가 나온다

7. 조사 자동 선택

"레벨3(이)가", "(을)를 배웠습니다" 같은 표기는 이름이 실행 중에 정해져서 생깁니다. v1.1은 빌드할 때 이름 마지막 글자에 받침이 있는지 계산해 이름 앞 1바이트에 표시해 두고, 게임은 그 비트만 보고 을/를을 고릅니다.

받침 번호 = (글자 코드 − 0xAC00) % 28   ; 0이면 받침 없음
버섯 → 「섯」 받침 있음 → 버섯을
회복샤워 → 「워」 받침 없음 → 회복샤워를

숫자는 읽는 소리로 정했습니다. 마지막 자리가 2, 4, 5, 9면 「가」, 나머지는 「이」입니다.

레벨업 화면에서 필살기 이름과 레벨 숫자 뒤의 조사가 받침에 맞게 나온다

8. 원판부터 어색했던 화면

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

위는 일본판, 아래는 v1.1 전투 결과 창
위 일본판, 아래 v1.1
위는 일본판, 아래는 v1.1 필살기 화면의 다음 기술 안내
위 일본판, 아래 v1.1

9. 확인은 에뮬레이터가

  • Mesen 2와 Lua 스크립트로 세이브스테이트에서 버튼 입력을 재생하고, 원하는 순간의 화면과 메모리를 저장합니다.
  • 인벤토리에 아이템을 직접 넣거나 경험치를 레벨업 직전으로 바꿔 보고 싶은 화면을 바로 만듭니다.
  • 상점 주인 대사 자리에 원하는 대사를 띄우는 테스트 전용 롬으로 v1.0과 v1.1을 같은 조건에서 찍습니다.
  • 고치기로 한 목록 밖에서 바뀐 대사가 0건인지, 메뉴 화면이 기준과 픽셀 단위로 같은지 비교합니다.

한글판 만들러 가기