공통
0장 롬과 패치
롬은 카트리지 칩을 통째로 옮긴 파일이고, 패치는 바뀐 곳만 적은 파일입니다. IPS 형식을 바이트 단위로 읽어 봅니다.
롬은 칩을 통째로 옮긴 파일 쉽게
게임 카트리지 안에는 프로그램과 대사, 그림, 음악이 모두 들어 있는 칩이 있습니다. 이 칩은 공장에서 한 번 내용을 새기면 다시 쓸 수 없어서 롬(ROM, Read-Only Memory: 읽기만 하는 메모리)이라고 부릅니다.
칩 안의 내용은 0부터 255까지의 숫자, 곧 바이트가 한 줄로 길게 늘어선 것입니다. 이 숫자들을 처음부터 끝까지 차례로 읽어 파일에 그대로 적으면 그것이 롬 파일입니다. 슈퍼 패미컴 롬 파일은 흔히 .sfc라는 확장자를 씁니다.
파일 안에서 몇 번째 바이트인지를 오프셋이라고 합니다. 첫 바이트가 오프셋 0입니다. 롬 분석 노트에 나오는 파일 0x7FD5 같은 표기가 바로 이 오프셋이고, 앞에 붙은 0x는 "16진수로 적었다"는 표시입니다. 16진수는 1장에서 다룹니다.
더 깊이카피어 헤더: 앞에 붙은 512바이트
1990년대에는 카트리지 내용을 디스켓으로 옮기는 "카피어"라는 장치가 있었습니다. 이 장치들은 롬 앞에 자기 설정을 적은 512바이트를 붙였고, 그 시절 덤프가 지금도 돌아다닙니다. 이것을 카피어 헤더라고 합니다. 게임 안에 있는 "내부 헤더"(게임 이름·크기를 적은 곳, 4장)와는 다른 것입니다.
롬 크기는 거의 언제나 1024의 배수라서, 파일 크기를 1024로 나눈 나머지가 512면 카피어 헤더가 붙은 파일입니다. 헤더가 붙으면 모든 내용이 512(0x200)바이트씩 뒤로 밀립니다.
그래서 같은 패치라도 헤더 없는 파일용과 헤더 있는 파일용을 따로 만들어야 합니다. 두 패치는 바뀐 내용은 같고 적어 둔 위치만 0x200씩 다릅니다.
패치는 바뀐 곳만 적은 파일 쉽게
한글패치를 만든다는 것은 롬 파일 안의 바이트 일부를 바꾼다는 뜻입니다. 그런데 바꾼 롬을 통째로 나눠 줄 수는 없습니다. 롬은 게임 회사의 저작물이기 때문입니다. 그래서 "몇 번째 바이트부터 무엇으로 바꿔라"만 적은 파일을 나눠 줍니다. 이것이 패치입니다.
책의 정오표를 떠올리면 쉽습니다. 정오표는 책 전체를 다시 찍지 않고 "12쪽 셋째 줄 '가'를 '나'로" 같은 줄만 적습니다. 책(롬)이 있는 사람만 정오표(패치)를 적용해 고친 책을 얻을 수 있습니다.
패치는 원본과 정확히 같은 파일에 적용해야 합니다. 다른 판(북미판, 다른 버전, 이미 한 번 패치한 롬)에 적용하면 엉뚱한 자리가 바뀌어 게임이 멈춥니다. 한글패치 페이지가 파일을 고르면 먼저 CRC32·MD5 같은 지문 값을 확인하는 것도 이 때문입니다.
IPS 패치를 바이트로 읽기 쉽게
오래된 게임 번역 패치는 대부분 IPS라는 형식을 씁니다. 구조가 아주 단순해서 바이트를 직접 읽을 수 있습니다. 위 그림의 "0x61부터 2바이트를 EA EA로"를 IPS로 만들면 15바이트입니다.
50P41A54T43C48H00위치00위치61위치00길이02길이EA내용EA내용45E4FO46F
- 처음 5바이트는 글자
PATCH입니다. "이 파일은 IPS"라는 표시입니다. - 위치 3바이트
00 00 61: 오프셋 0x61부터 바꿉니다. - 길이 2바이트
00 02: 2바이트를 바꿉니다. - 내용
EA EA: 그 자리에 쓸 바이트입니다. 길이만큼 이어집니다. - 위치·길이·내용 묶음(레코드)이 바뀐 곳마다 하나씩 이어지고, 마지막에 글자
EOF로 끝납니다.
위치와 길이는 큰 자리 바이트부터 적습니다(00 00 61 = 0x000061). 이렇게 큰 자리부터 적는 방식을 "빅 엔디안"이라고 하는데, 게임기 CPU는 반대로 적습니다. 2장에서 이 차이를 다룹니다.
더 깊이IPS 형식 전부: RLE 레코드, 크기 늘리기, 한계
레코드 두 종류
- 보통 레코드: 위치 3바이트 + 길이 2바이트 + 내용(길이만큼). 길이는 1–65,535입니다.
- RLE 레코드: 길이 자리에
00 00을 적으면 "같은 바이트 반복"이라는 뜻입니다. 뒤에 반복 횟수 2바이트와 반복할 바이트 1바이트가 옵니다. 예를 들어01 20 00 | 00 00 | 10 00 | FF는 "0x012000부터 FF를 0x1000번"입니다. 빈 공간을 한꺼번에 채울 때 패치가 작아집니다.
파일 크기 바꾸기
원본보다 뒤쪽 위치에 레코드를 쓰면 적용 프로그램이 파일을 그만큼 늘립니다. 롬을 1MB에서 2MB로 키우는 패치가 이런 방식입니다. 그런데 레코드로는 파일을 늘릴 수만 있고 줄일 수는 없습니다. 그래서 결과가 원본보다 작아야 할 때를 위해 EOF 뒤에 "결과 파일 크기" 3바이트를 붙이는 확장이 생겼습니다. 적용 프로그램은 이 값이 있으면 파일을 그 크기로 잘라 줄입니다. kr-patch-toolkit은 결과가 원본보다 클 때만 이 3바이트를 붙여 결과 크기를 함께 적어 둡니다. 늘어난 부분의 바이트는 모두 레코드에 들어 있으므로, 이때는 크기를 기록해 두는 역할만 합니다.
한계
- 위치가 3바이트라 16MB(0xFFFFFF)보다 큰 파일은 다룰 수 없습니다.
- 위치
0x454F46은 글자로 읽으면EOF라 끝 표시와 헷갈립니다. 그래서 이 위치에서 시작하는 레코드는 만들 수 없고, 한 바이트 앞(0x454F45)에서 시작하도록 만듭니다. 툴킷도 이렇게 합니다. - 원본이 맞는지 확인하는 칸이 없습니다. 엉뚱한 파일에도 그냥 적용됩니다. 그래서 패치를 배포할 때 원본의 CRC32나 MD5를 함께 알려 줍니다. 이런 칸을 가진 BPS 같은 새 형식도 있습니다.
레코드를 어디서 끊나
바뀐 바이트 사이에 안 바뀐 바이트가 몇 개 끼어 있으면, 레코드를 둘로 나누는 것보다 안 바뀐 바이트까지 내용에 넣는 편이 짧을 수 있습니다. 레코드 하나에 머리 5바이트가 들기 때문입니다. 툴킷의 --gap(기본값 6)이 "같은 바이트가 몇 개 이어지면 끊을지"를 정합니다. 값이 달라도 결과 롬은 같지만 패치 파일의 바이트는 달라집니다.
실제 패치에서 실제 패치에서
- 돌격편·각성편·제절초는 헤더 없는 파일용과 헤더 있는 파일용 IPS를 함께 배포합니다. 슈퍼 마리오 RPG는 헤더 없는 파일용만 배포합니다. 툴킷의
krpatch ips make … --headered가 한 번에 두 파일을 만듭니다. - 위 그림의 0x61 두 바이트는 실제 예입니다. 돌격편 롬의 파일 0x61에 있는
80 04를EA EA로 바꾸면 개발자용 디버그 메뉴가 열립니다(5장에서 이 바이트를 어셈블리로 읽습니다). 배포한 패치는 이 자리를 바꾸지 않습니다. - 제절초 일본판은 1MB인데, v1.0 한글판은 대사를 넣으려고 롬을 2MB로 늘렸습니다. 그런데 게임 안 헤더의 "롬 크기" 바이트를
0A(1MB)에서0B(2MB)로 고치지 않았고, v1.1이 이를 바로잡았습니다. 파일 크기와 헤더가 따로 논 사례입니다. - 돌격편 패치 소스의
tools/build.sh는 "일본판 + v1.0 IPS → 타이틀 → 텍스트 → … → 체크섬" 순서로 롬을 만들고, 마지막에 IPS를 만듭니다. 패치는 이렇게 만든 결과 롬과 원본의 차이입니다.
직접 해 보기 직접 해 보기
- 롬 파일을 복사해 두고, 헥스 에디터로 복사본의 바이트 하나를 바꿔 저장합니다.
- 두 파일의 차이로 IPS를 만듭니다.
--headered를 주면 헤더 있는 파일용도 함께 만듭니다.krpatch ips make original.sfc patched.sfc out.ips --headered out-headered.ips - 만든 패치를 바이트로 봅니다. 위 그림처럼 0x61의 두 바이트만 바꿨다면 이렇게 나옵니다.
헤더 있는 쪽은 위치가$ xxd out.ips 00000000: 5041 5443 4800 0061 0002 eaea 454f 46 PATCH..a....EOF $ xxd out-headered.ips 00000000: 5041 5443 4800 0261 0002 eaea 454f 46 PATCH..a....EOF00 02 61, 곧 0x200만큼 뒤입니다. - 원본에 패치를 적용해 고친 파일과 같은지 확인합니다.
krpatch diff는 두 파일이 다른 범위를 알려 줍니다. 같으면 아무것도 출력하지 않습니다.krpatch ips apply original.sfc out.ips result.sfc krpatch diff patched.sfc result.sfc krpatch diff original.sfc result.sfc # 000061-000062 2
실제 패치에서 이 개념이 쓰인 롬 분석 노트
노트의 자세한 절은 「자세히 보기」를 펼친 안쪽에 있습니다.
직접 해 보기 kr-patch-toolkit 명령 설명
참고 자료
- ZeroSoft, IPS File Format — IPS 레코드·RLE 레코드·EOF 형식
- SNESdev Wiki, ROM file formats — .sfc·.smc 파일과 카피어 헤더
- fullsnes, SNES Cartridge ROM Header — "이미지 크기를 1024로 나눈 나머지가 512면 카피어 헤더" 판별법
- kr-patch-toolkit, ips.py — 이 장의 IPS 예시를 만든 구현
댓글