こんにちは
前回はRGBDSでゲームボーイの開発環境を構築して,HELLO WORLDのプログラムをエミュレータで実行しました. lambda00.hatenablog.com
今回は,HELLO WORLDのプログラムを自分なりに理解してまとめたことについての記事です.
(主に,カートリッジヘッダについてですが...)
間違えている可能性がありますので,ご了承ください.
1. HELLO WORLDプログラム
前回のプログラムにコメントと改良を加えたものです.
; ヘッダ構成 SECTION "Header", rom0[$0100] ; ROMバンク0のカートリッジヘッダ EntryPoint: di ; 割り込み無効 jp Start ; スタート処理へ ; nintendoロゴ: $0104~$0133 db $CE, $ED, $66, $66, $CC, $0D, $00, $0B db $03, $73, $00, $83, $00, $0C, $00, $0D db $00, $08, $11, $1F, $88, $89, $00, $0E db $DC, $CC, $6E, $E6, $DD, $DD, $D9, $99 db $BB, $BB, $67, $63, $6E, $0E, $EC, $CC db $DD, $DC, $99, $9F, $BB, $B9, $33, $3E ; タイトル: $0134~$013E ; 古いカートリッジだと$0134~0143までタイトル(15バイト) db "HELLO_WORLD" ; 適当に決めた ; メーカーコード: $013F~$0142 db "LMDA" ; 適当に決めた ; CGBフラグ: $0143 db $00 ; 新しいライセンスコード: $0144~$0145 db "XX" ; 適当に決めた ; SGBフラグ: $0146 (非対応: 00h,対応: 03h) db $03 ; カートリッジタイプ: $0147 db $00 ; ROMサイズ: $0148 db $00 ; RAMサイズ: $0149 db $00 ; 発売国コード: $014A db $00 ; 古いライセンスコード: $014B db $33 ; マスクROMバージョン: $014C db $00 ; チェックサム: $014D db $88 ; グローバルチェックサム: $014E~$014F dw $ae01 SECTION "Game Code", rom0[$150] Start: ; VRAMにアクセスするため,LCDをオフにする .waitVBlank ; ゲームボーイ画面サイズは「160*144」 ; LCDC-Yは「0~143」 ld a, [$FF44] ; $FF44は「LCDC Y座標」 cp a, 144 ; LCDC-Y座標が144ならば,VBLANK中 jr c, .waitVBlank xor a, a ; アキュームレータをリセット ; $FF40は「LCD制御(LCDC)」 ld [$FF40], a ; $FF40の7bit目を0にする(LCD表示オフ) ; フォント取得処理 ld hl, $9000 ; CHR-ROM位置? ld de, FontTiles ; フォント格納番地 ld bc, FontTilesEnd - FontTiles ; フォント数 .copyFont ld a, [de] ; メモリから1バイト取得 ld [hli], a ; VRAMに配置して,インクリメント inc de ; 次のメモリへ dec bc ; 残り文字数のカウント ld a, b ; decはフラグを更新しないので,bcのZフラグチェックを行う or a, c ; a = b | c jr nz, .copyFont ; Zフラグがセットされるまで格納処理 ; $9800~$9BFFはBG Map Data1 ; 「32*32=1024」,「$9800-$9BFF=$3FF=1023」より1024バイト分ある ld hl, $9800 ; 画面左上を指定 ld de, HelloWorldStr ; 文字列格納番地を指定 .copyString ; $9800番地に文字列を転送 ld a, [de] ld [hli], a inc de and a, a ; コピーした文字列のバイトが「0」であるかチェック jr nz, .copyString ; 各種レジスタの初期化 ; 色指定 ld a, %11100100 ; 黒,濃グレー,薄グレー,白 ld [$FF47], a ; $FF47は「背景パレットデータ(BGP)」 ; 画面位置 xor a, a ; アキュームレータのクリア ld [$FF42], a ; LCDC-Yを0 ld [$FF43], a ; LCDC-Xを0 ; サウンド設定 ; $FF26は「サウンドオン・オフ切り替え」 ld [$FF26], a ; サウンドオフ(bit-7が0) ; LCDC ld a, %10000001 ; LCD表示有効,背景表示 ld [$FF40], a .lockup ; 無限ループ jr .lockup SECTION "Font", rom0 FontTiles: INCBIN "font.chr" FontTilesEnd: SECTION "Hello World string", rom0 HelloWorldStr: db "HELLO, WORLD!", 0 ; 表示文字列+終端文字(今回は0) ; 残りのROMを0埋めする SECTION "PADDING", romx rept $7fff - $4000 db 0 endr
2. カートリッジヘッダ情報
前回のプログラムでは,メモリマップ$0104~$0150のカートリッジヘッダ領域を0で初期化していました.
正直な所,カートリッジヘッダ情報を記述しなくてもrgbfixを利用すれば大丈夫なのですが,理解を深めるためにあえて記述しました.
基本はプログラムコメントの通りなので,気になった箇所だけ簡単に説明します.
- $0104~$0133: Nintendoロゴ
- この内容が間違えていると起動に失敗します.
(海賊版への対策だと勝手に思っています?)
- この内容が間違えていると起動に失敗します.
- $0146: SGBフラグ
- スーパーゲームボーイに対応しているかの判定です.
$00: 非対応
$03: 対応
対応にすると枠が出現しました(楽しい).
- スーパーゲームボーイに対応しているかの判定です.
- $014D: ヘッダチェックサム
- $014E~$014F: グローバルチェックサム
3. チェックサム
ヘッダチェックサムとグローバルチェックサムの計算は,GbdevWiki-The Cartridge Headerに詳しく書いてありました.
提示したプログラムでは既に値を記述していますが,計算前は$00で初期化してrgbasmとrgblinkを行い,出力された.gbファイルからチェックサムの計算を行いました.
チェックサムの計算にはC++で作成したプログラムを使用しました.
(エンディアンに注意してください.)
#include <iostream> #include <fstream> #include <vector> int main(int argc, char *argv[]) { if(argc < 2) { std::cerr << "ERROR " << __LINE__ << std::endl; return 1; } std::ifstream rom_file(argv[1], std::ios::in | std::ios::binary); if(!rom_file) { std::cerr << "ERROR " << __LINE__ << std::endl; return 1; } std::vector<char> rom_data; char data; while(!rom_file.eof()) { rom_file.read(&data, sizeof(data)); rom_data.emplace_back(data); } rom_file.close(); std::int16_t x = 0; for(int i = 0x0134;i <= 0x014C; ++i) { x = x - static_cast<int>(rom_data[i]&0xff) - 1; } std::cout << "Header CheckSum: $" << std::hex << (x & 0xff) << std::endl; std::uint16_t y = 0; for(int i = 0x0000;i < rom_data.size();++i) { if(i == 0x014e || i == 0x014f) continue; y += static_cast<int>(rom_data[i]&0xff); } std::cout << "Global CheckSum: $" << std::hex << (y & 0x00ff) << ((y & 0xff00) >> 8) << std::endl; return 0; }
4. パディング処理
ここまでの処理で作成した.gbファイルならエミュレータに警告されないだろうと思っていたら,ROMサイズが違うと警告されました.
ROMデータを見てみると16KBしかなく,$0148で指定した32KBになっていないことがわかりました.
そこで,バンク1以降のROMデータを0でパディングする処理を記述したところ,警告なしで起動できました.
ただ,rgblinkのコマンドでROMサイズを指定できるらしい(わかりませんでした...)のでこの処理は必要ないかもしれません.
5. アセンブラについて
ざっくりですが,andやorなどのアキュームレータを使用する処理は,アキュームレータを省いて記述することが出来ます.
以下は同じ処理です(aを0クリア).
xor a xor a, a
詳しく知りたい方は
ISSOtm's GB programming -Interlude-
で解説されていますので,ご確認ください.
6. まとめ
すっっごく簡単に前回のプログラムの解説メモを書きました.
次は,開発しやすいようにメモリマップやレジスタ関係のメモを1ページにまとめてみたいと思います.