본문으로 건너뛰기

닌텐도 DS모르는 용어는 용어집에서

17장 그림과 글자

DS의 2D 그림도 타일과 팔레트지만 픽셀을 바이트에 붙여 담습니다. 게임이 정한 그림 형식과 폰트를 읽고, 2비트 픽셀 넷의 순서를 그려 보며 찾습니다.

DS의 2D 그림도 타일과 팔레트 쉽게

DS에는 2D 엔진(2D 그림을 그리는 회로)이 A·B 둘 있어 화면을 하나씩 맡습니다(어느 쪽이 위 화면인지는 바꿀 수 있습니다). 원리는 슈퍼 패미콤과 같습니다(7장). 화면은 주로 8×8픽셀 타일을 이어 붙여 그리고(타일 없이 픽셀을 줄줄이 놓는 비트맵 화면 모드도 있습니다), 타일의 픽셀은 색 번호를, 팔레트는 그 번호가 실제로 무슨 색인지를 정합니다. 색 하나는 2바이트로 빨강·초록·파랑을 5비트씩 적고, 팔레트의 0번 색은 투명입니다.

다른 점은 픽셀을 바이트에 담는 방법입니다. 슈퍼 패미콤은 비트평면이라 한 픽셀의 비트가 여러 바이트에 흩어집니다. DS는 한 픽셀의 비트를 한 바이트 안에 붙여 담습니다. 4bpp(16색)면 바이트 하나에 픽셀 둘, 아래 4비트가 왼쪽 픽셀입니다. 8bpp(256색)면 바이트 하나가 픽셀 하나입니다. 그래서 타일 한 줄을 헥스 에디터로 보면 색 번호가 거의 그대로 보입니다.

DS 4bpp 타일 한 줄색 번호 1, 2, 3, 4, 5, 6, 7, 8인 픽셀 여덟 개가 바이트 넷(0x21, 0x43, 0x65, 0x87)으로 담긴다. 앞 픽셀이 바이트의 아래 4비트, 뒤 픽셀이 위 4비트다.픽셀(색 번호)12345678바이트21436587예: 색 1·2인 첫 두 픽셀 → 위 4비트 2, 아래 4비트 1 → 0x21
색 번호 1–8인 픽셀 여덟 개를 DS 4bpp로 담으면 4바이트입니다. 바이트마다 왼쪽 픽셀이 아래 4비트입니다.

게임이 정한 그림 형식 쉽게

롬 안의 그림이 늘 하드웨어 타일 그대로 들어 있지는 않습니다. 게임은 그림을 자기 형식으로 담아 두고, 화면에 낼 때 VRAM에 맞게 옮겨 놓습니다. 그래서 그림 파일을 고치려면 그 게임의 형식부터 알아야 합니다.

고스트 트릭의 타이틀 위 화면은 그래픽 묶음 cpac_3d.bin 안의 한 항목입니다. LZ11(16장)을 풀면 sdt.로 시작하는 그림이 나옵니다. 헤더 0x0C부터 4바이트씩 픽셀 위치(0x24), 픽셀 크기(0xC000), 팔레트 위치(0xC024), 팔레트 크기(0x200), 가로 256, 세로 192가 적혀 있습니다. 픽셀 하나가 1바이트(8bpp)라 256 × 192 = 0xC000바이트이고, 팔레트는 256색 × 2바이트 = 0x200바이트입니다. 타일로 자르지 않고 한 줄씩 늘어놓은 그림입니다(노트).

폰트: 글자 하나 = 그림 한 장 쉽게

대사의 2바이트 값(16장)은 폰트 파일에서 몇 번째 그림인지를 가리킵니다. 고스트 트릭에는 대사 폰트가 세 개 있습니다. 일본판에서 셋은 0x00–0x3F(숫자와 영문)만 같고, 그 뒤는 다릅니다. 대화창·말풍선 폰트는 0x40부터 히라가나인데 font_db.imb는 악센트 붙은 라틴 문자입니다. 한글패치 v1.1은 세 폰트의 0x12C–0xA59에 같은 한글을 같은 순서로 그려, 한글 번호는 어느 폰트에서나 같은 글자가 되게 했습니다.

파일쓰는 곳글자 모양일본판한글패치
fonts2bpp_new.imb대화창16×24, 2bpp, 글자당 96바이트1,350자(129,600바이트)3,203자(307,488바이트)
font_db.imb모름(확인하지 않음)16×16, 4bpp, 글자당 128바이트256자(32,768바이트)3,203자(409,984바이트)
balloon_font.imb말풍선16×16, 4bpp, 글자당 128바이트256자(32,768바이트)3,203자(409,984바이트)

세 폰트 모두 8×8 타일이 아니라 글자 하나를 한 줄씩 늘어놓은 비트맵(선형 비트맵)입니다. 대화창 폰트는 16픽셀 한 줄이 2bpp로 4바이트, 24줄이라 글자 하나가 96바이트입니다. 글자 번호 × 96이 그 글자의 위치라 「가」(0x12C)는 0x7080에 있습니다. 대사를 읽던 글자표와 폰트 그림 순서가 같은 번호를 쓰므로, 둘 중 하나만 바꾸면 엉뚱한 글자가 나옵니다.

2비트 픽셀 넷의 순서 찾기 쉽게

2bpp면 바이트 하나에 픽셀 넷이 들어갑니다. 어느 2비트가 몇 번째 픽셀인지는 게임이 정하기 나름입니다. DS 하드웨어의 4bpp처럼 아래 비트부터일 수도, 반대일 수도 있습니다. 고스트 트릭 대화창 폰트는 둘 다 아닙니다. 위 4비트가 앞 두 픽셀이고, 4비트 안에서는 아래 2비트가 먼저라, 픽셀 0–3이 비트 4, 6, 0, 2에서 시작합니다.

순서는 그려 보면 압니다. 글자는 굵은 획이라 순서가 맞으면 가로로 이웃한 픽셀 값이 비슷하게 이어지고, 틀리면 획이 세로 줄무늬로 갈라집니다. 아래에서 순서를 바꿔 보세요. 처음 순서(위 비트부터)는 툴킷 krpatch linear --order msb와 같습니다.

2비트 픽셀 순서 맞추기

바이트 안 2비트 칸을 고른 순서대로 픽셀에 놓아, 고스트 트릭 한글패치 대화창 폰트의 「고」「스」「트」「릭」 네 글자를 그립니다. 픽셀마다 어느 2비트를 쓸지 골라 보세요. 가로로 이웃한 픽셀 값의 차이를 모두 더한 값이 작을수록 획이 매끄럽게 이어집니다.

고 0x178
스 0x60B
트 0x94D
릭 0x42A

순서 6, 4, 2, 0: 가로 이웃 픽셀 차이의 합 472, 24가지 가운데 15번째로 작음

24가지 순서의 점수
순위픽셀 0–3의 비트 위치차이의 합
14, 6, 0, 2299
24, 0, 6, 2349
32, 0, 6, 4378
40, 2, 6, 4422
52, 6, 0, 4428
66, 0, 2, 4432
74, 6, 2, 0448
80, 6, 2, 4450
92, 4, 6, 0452
106, 2, 0, 4454
112, 0, 4, 6459
126, 4, 0, 2461
134, 2, 0, 6463
140, 6, 4, 2467
156, 4, 2, 0472
162, 6, 4, 0474
170, 2, 4, 6475
184, 2, 6, 0476
194, 0, 2, 6485
202, 4, 0, 6487
210, 4, 6, 2489
226, 0, 4, 2489
236, 2, 4, 0522
240, 4, 2, 6525
더 깊이24가지를 모두 그려 고르는 법

2비트 칸 넷을 픽셀 넷에 놓는 방법은 4! = 24가지입니다. 사람이 보고 고를 수도 있지만, 「가로로 이웃한 픽셀 값 차이의 합」처럼 매끄러움을 숫자로 재면 폰트 전체로 확인할 수 있습니다. 한글 2,350자 전체로 재도 (4, 6, 0, 2)가 가장 작고, 다음 순서(2, 0, 6, 4)보다 약 5% 작습니다. 위젯의 네 글자만으로 재면 2위는 (4, 0, 6, 2)로 다릅니다. 찾은 순서로 픽셀을 읽는 식은 이렇습니다.

pixel(x, y) = (data[글자 × 96 + y × 4 + x ÷ 4] >> SHIFT[x mod 4]) & 3
SHIFT = (4, 6, 0, 2)

2비트는 네 단계라 바탕(0)과 글자(3) 사이에 중간 농도 둘(1, 2)을 써서 획 가장자리를 부드럽게 합니다. 16×16 4bpp인 font_db.imb와 balloon_font.imb는 DS 하드웨어처럼 아래 4비트가 앞 픽셀이라 --order lsb로 바로 그려집니다.

실제 패치에서 실제 패치에서

고스트 트릭 v1.1: 폰트 세 개를 3,203자로 늘리기

일본판 대화창 폰트는 1,350자, 나머지 두 폰트는 256자였습니다. v1.1은 세 폰트를 모두 3,203자로 맞추고 0x12C부터 한글 2,350자를 KS X 1001 순서로 그렸습니다(노트). 폰트 파일이 커졌으니 롬을 다시 묶을 때 자리를 옮겨야 했습니다(15장). 0xA5A부터의 553자는 v1.2 대본에서는 쓰지 않습니다.

고스트 트릭 v1.2: 그림은 하나만

v1.2는 폰트를 v1.1 그대로 두고, 그림은 타이틀 위 화면 하나만 고쳤습니다. v1.1이 137–177줄에 그려 넣은 안내 문구를 일본판 같은 그림의 픽셀로 되돌렸습니다. v1.1이 팔레트를 바꾸지 않아서 팔레트는 그대로 두고 픽셀의 색 번호만 바꿨습니다(노트).

직접 해 보기 직접 해 보기

4bpp 폰트는 툴킷으로 바로 그립니다. 대화창 폰트는 lsb·msb 어느 쪽으로 그려도 줄무늬가 생깁니다.

krpatch nds unpack game.nds work/
krpatch linear work/data/font_db.imb font_db.png --size 16x16 --bpp 4 --order lsb --count 256
krpatch linear work/data/fonts2bpp_new.imb wrong.png --size 16x24 --bpp 2 --order msb --count 256

대화창 폰트는 찾은 순서로 직접 읽습니다. 한글패치 롬이면 0x12C는 「가」입니다.

>>> data = open('work/data/fonts2bpp_new.imb', 'rb').read()
>>> SHIFT = (4, 6, 0, 2)
>>> glyph = 0x12C
>>> for y in range(24):
...     print(''.join(' .:#'[(data[glyph * 96 + y * 4 + x // 4] >> SHIFT[x % 4]) & 3] for x in range(16)))

이 장에서

  1. DS의 2D 그림도 타일과 팔레트
  2. 게임이 정한 그림 형식
  3. 폰트: 글자 하나 = 그림 한 장
  4. 2비트 픽셀 넷의 순서 찾기
  5. 실제 패치에서
  6. 고스트 트릭 v1.1: 폰트 세 개를 3,203자로 늘리기
  7. 고스트 트릭 v1.2: 그림은 하나만
  8. 직접 해 보기