ロゴ ロゴ

ポケモンで芝浦工大を表示する

⁂この記事ではROMの改造行為は一切行っておりません。ご注意ください。

まえがき

GBA(ゲームボーイアドバンス)の仕様を調べていたらビットマップモードをサポートしていると知りました。
普通に表示させてもいいのですが、せっかくブログのネタになるのでポケモンエメラルドで電算の画像を表示してみようと思った。

まずはGBAの仕様について説明します。
ビットマップとは簡単に言うとピクセルごとに色を決めて画像を描画する方法です。
画像処理では当たり前の事ですが、ゲームでは処理速度が大事なのでこれよりも早いタイルモードというのを使っています。
しかし3D表示や細かい画像の表示はタイルモードだと厳しいためこのモードが用意されています。

続いてポケモンエメラルドの説明
そもそもポケモンでどうやって任意の画像を表示するかというと任意コード/スクリプト実行です。
実はこのゲーム相当壊されていて今ではバイナリエディタなども考案されています。
それを使ってゲームの処理を好き勝手いじくりまわすことで目的を達成していきます。

動画

今回やった事

1.表示したい画像をGBAで使えるように変換
2.バイナリエディタでIWRAM領域にスクリプトとASMコードを書き込む
3.スクリプトを実行して、その後thumbで任意コード実行
4.VRAMにメモリを書き込み画像を表示
…以上の4つです。
上からの順に説明していきます。

処理1:画像の変換

GBAで使える画像形式(モード3の場合)は以下の通りです。
サイズ: 240*160ピクセル
色  : RGBをそれぞれ5bitで表現し、結合したu16型 [0BBB BBGG GGGR RRRR]

以下のpythonコードでテキストに出力しました。

import cv2

#ファイル名
image_filename="logo.png"   #読み込む画像 サイズ:240*160[pix]まで
output_filename="img.out.txt"   #出力ファイル

#ファイル読み込み
image = cv2.imread(image_filename)
f = open(output_filename,"w")

# 色をGBA用に変換する (RGB -> U16)
def colorToHex(color):
    result = 0
    for i in range(len(color)):
        result |= (color[i]//8 << (2-i)*5)
    return (result)

# 画像の大きさを取得
height, width, channels = image.shape[:3]

# ファイル出力
f.write("width: " + str(width)+'\n')
f.write("height: " + str(height)+'\n')
for y in range(0,height):
    for x in range(0,width):
        f.write(str(hex(colorToHex(image[y][x])))+',')

さすCV2

処理2:命令の打ち込み

バイナリエディタでちまちまと打ち込みます。
スクリプトや機械語命令は以下の通りです。

任意コードで実行した命令:

movs r1,0x43
ldr r2,=0x04000000 #実際には他の番地のアドレスを参照
strb r1,[r2]
bx r14

ldr命令は記述の上では即値をとれるようですが、実際の処理では他のアドレスを参照しています。
こう言った事情からバイナリデータの打込み、特に任意コードのような場合は書き込むサイズとアライメントが極めて重要です。

スクリプトの処理:

はい/いいえの選択肢で分岐
・メッセージ表示:DEN3 に [改行コード] はいりませんか ?
はい:
 ・BGM一時停止
 ・SEを鳴らす(ここでBGMが再開する)
 ・メッセージ表示: それは すばらしい !!! [改ページ] ギャラリーへようこそ
 ・BGM再生(海の博物館のBGM)
 ・プレイヤーの操作ロック
 ・thumb形式機械語を実行:[GBAのスクリーンモードを3に変更する]
 ・スクリプト終了
いいえ:未実装

ここで言うスクリプトとはゲーム上で実際に使用しているイベントスクリプトを指します。
第三世代のポケモンではイベントなどを設定する時などに、予め頻出する処理をスクリプトととして定義し、順番に呼び出すことでプログラムを簡単にしています。
今回はこれを悪用していきます。

注意点

thumbでの実行(callasm)では実行アドレスを+1する点に注意。arm形式では無いです。
またメッセージ表示や命令のアドレスはリトルエンディアン、終了コードがしっかりあるかなどにも注意しましょう。
そうしなければあっという間にバグります。

処理3:スクリプトの実行

スクリプト実行はとても簡単
バイナリエディタで下記の通り打ち込むだけです。

0x03000E40  :<-0x01
0x03000E41  :<-0x02
0x03000E44  :<-0x00000000
0x03000E48  :<-スクリプトのアドレス(リトルエンディアン)
0x03000E38  :<-0x00を代入する

これらはアドレス順ではなく上の順番通り書き込みをしましょう。

処理4:VRAMの書き込み

本当は命令に画像データを組み込めればいいのですが0x1F7000バイトも手打ちするのは絶対にやりたくないです。
なので今回はluaを使ってVRAMへの書き込みを行うという非行に走ってしまいました。
根気と時間がある方は挑戦してみてください。

画面のノイズについて

ノイズ

画面にノイズが載ってしまっていますがこれは正常な処理による画面更新のせいです。
故にフリーズさせたり無限ループに入らせれば綺麗な画像を移すことが出来ます。

コメント入力

関連サイト