공통
2장 엔디안
2바이트 숫자를 메모리에 놓을 때 어느 쪽 바이트를 먼저 적을까요? 롬에서 주소가 거꾸로 보이는 이유입니다.
큰 숫자는 여러 칸에 나눠 적는다 쉽게
바이트 한 칸에는 255까지만 들어갑니다. 그보다 큰 수, 예를 들어 $4575(10진수 17,781)는 두 칸에 나눠 적어야 합니다. 16진수 네 자리를 두 자리씩 나누면 됩니다. 큰 자리 쪽 $45와 작은 자리 쪽 $75입니다.
문제는 이 두 바이트를 메모리의 두 칸(주소 +0, +1)에 놓을 때 어느 쪽을 먼저 놓느냐입니다. 이 순서를 엔디안(endianness)이라고 합니다.
작은 자리부터, 큰 자리부터 쉽게
- 리틀 엔디안: 작은 자리 바이트를 앞 칸에 둡니다. 슈퍼 패미컴의 65c816, PC의 x86이 이 방식입니다.
- 빅 엔디안: 큰 자리 바이트를 앞 칸에 둡니다. 사람이 숫자를 적는 순서와 같습니다.
날짜를 적는 방식과 비슷합니다. 우리는 "2026년 10월 24일"처럼 큰 단위부터 적지만, 어떤 나라는 "24일 10월 2026년"처럼 작은 단위부터 적습니다. 어느 쪽이 맞다기보다 읽는 쪽과 쓰는 쪽이 같은 약속을 쓰는 것이 중요합니다.
리틀 엔디안 바이트를 다시 숫자로 읽을 때는 칸을 뒤에서부터 이어 붙입니다. 75 45를 보면 "45 다음에 75", 곧 $4575입니다. 처음엔 어색하지만 몇 번 하면 눈이 저절로 뒤집어 읽습니다.
롬에서 주소가 거꾸로 보이는 이유 쉽게
프로그램 안에도 숫자가 잔뜩 들어 있습니다. "이 주소로 가라"는 명령이라면 주소가 명령 바로 뒤에 붙는데, 이 주소도 리틀 엔디안입니다. 그래서 롬을 헥스 뷰어로 열면 주소가 거꾸로 보입니다.
더 깊이왜 리틀 엔디안일까, 24비트 포인터, 섞여 쓰이는 순서
6502 계열이 작은 자리부터 적는 까닭
65c816의 조상인 6502는 한 번에 1바이트씩만 읽는 8비트 CPU였습니다. 명령 뒤의 주소를 읽을 때 작은 자리 바이트가 먼저 오면, 높은 자리 바이트를 읽는 동안 작은 자리에 X 레지스터 값을 더하는 계산을 미리 시작할 수 있습니다. 덧셈은 작은 자리부터 해야 올림을 넘길 수 있기 때문입니다. 흔히 드는 이유가 이것이고, 65c816 데이터시트의 사이클 표에서도 주소 피연산자를 언제나 낮은 바이트부터 가져옵니다.
24비트 포인터
65c816은 주소가 24비트(3바이트)라서 "어디든 가리키는" 포인터는 3바이트입니다. 순서는 역시 작은 자리부터, 마지막이 뱅크입니다. 제절초 한글판의 대본 시작 주소 표에는 00 80 20이 있는데, 거꾸로 읽으면 $20:8000입니다.
반대로 같은 뱅크 안만 가리키는 포인터는 2바이트로 줄여 적고, 뱅크는 코드가 따로 정합니다. 바이트를 아끼는 대신 뱅크 밖으로는 못 나갑니다. 4장에서 이 제약이 대사 배치를 어떻게 묶는지 봅니다.
엔디안은 파일이 아니라 읽는 코드가 정한다
16비트 LDA는 이어진 두 칸을 차례로 읽어(슈퍼 패미컴의 데이터 버스는 8비트) 앞 칸을 작은 자리로 삼습니다. 그래서 CPU가 2바이트 값을 다루는 명령은 항상 리틀 엔디안입니다. 하지만 게임 데이터는 게임 프로그래머가 원하는 순서로 적고 코드로 읽습니다. 그래서 한 롬 안에 두 순서가 섞이기도 합니다.
- 제절초 한글판 대본은 글자 하나를 2바이트 빅 엔디안으로 적고(
01 D8= 「여」), 점프할 주소는 3바이트 리틀 엔디안으로 적습니다. 대본을 읽는 코드가 그렇게 짜여 있습니다. - IPS 패치 파일은 위치와 길이를 빅 엔디안으로 적습니다(0장).
모르는 데이터를 볼 때는 두 순서로 다 읽어 보고, 말이 되는 쪽을 고릅니다.
실제 패치에서 실제 패치에서
대사 포인터 표
돌격편은 대사 번호마다 2바이트 포인터를 표에 적어 둡니다. 대사 0x006의 포인터는 파일 0x14000C에 있고, 바이트는 FE 85입니다. 거꾸로 읽으면 $85FE, 곧 같은 뱅크의 $A8:85FE(파일 0x1405FE)에 대사가 있습니다.
FE작은 자리85큰 자리$85FE읽은 값
글자 코드
그 자리에서 시작하는 대사 「어떻게」는 F3 08 B5 06 27 05입니다. 2바이트씩 거꾸로 읽어 $08F3(어), $06B5(떻), $0527(게)가 글자 번호입니다. 5장의 「명령 한 줄씩 실행하기」에서 이 바이트를 CPU가 2바이트씩 옮기는 모습을 볼 수 있습니다.
헤더 체크섬
슈퍼 마리오 RPG 일본판 헤더의 체크섬은 $4575이고, 롬에는 75 45로 들어 있습니다. 「바이트 순서 보기」의 처음 값이 이 숫자입니다.
한 게임 안의 두 순서
제절초 한글판의 첫 문장 「여름의 해질녘.」은 파일 0x100018부터 01 D8(여) 04 85(름) …처럼 글자마다 2바이트 빅 엔디안이고, 대본 시작 주소 표는 3바이트 리틀 엔디안입니다. 일본판 대본은 아예 바이트가 아닌 비트 단위로 압축되어 있었는데, 이것은 10장에서 다룹니다.
직접 해 보기 직접 해 보기
돌격편 v1.1 이후 롬이 있다면 대사 0x006의 포인터를 직접 읽어 봅니다.
$ xxd -s 0x14000C -l 2 game.sfc
0014000c: fe85 ..Python으로는 바이트 순서를 지정해 숫자로 바꿉니다.
>>> hex(int.from_bytes(bytes.fromhex('fe85'), 'little'))
'0x85fe'
>>> hex(int.from_bytes(bytes.fromhex('fe85'), 'big'))
'0xfe85'두 롬에서 어디가 바뀌었는지는 krpatch diff a.sfc b.sfc로 찾고, 찾은 범위를 헥스 뷰어로 열어 두 순서로 읽어 보세요.
실제 패치에서 이 개념이 쓰인 롬 분석 노트
노트의 자세한 절은 「자세히 보기」를 펼친 안쪽에 있습니다.
직접 해 보기 kr-patch-toolkit 명령 설명
참고 자료
- WDC, W65C816S 데이터시트 — 명령의 주소 피연산자를 낮은 바이트(ADL)부터 읽는 사이클 표
- Wikipedia, Endianness — 리틀·빅 엔디안이라는 이름과 여러 CPU의 바이트 순서
- ZeroSoft, IPS File Format — IPS의 위치·길이가 빅 엔디안이라는 것
댓글