おもちゃバコ

主に趣味について

ゲームボーイにおけるHELLO WORLDのメモ

こんにちは

前回は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: 対応
      対応にすると枠が出現しました(楽しい).
      f:id:lambda410:20200815150709j:plain
  • $014D: ヘッダチェックサム
  • $014E~$014F: グローバルチェックサム
    • カートリッジROM全体のチェックサムです.
      ゲームボーイ実機ではチェックしないため,間違えていても起動します.
      (ただし,bgbでは警告が出ます.)

3. チェックサム

ヘッダチェックサムとグローバルチェックサムの計算は,GbdevWiki-The Cartridge Headerに詳しく書いてありました.
提示したプログラムでは既に値を記述していますが,計算前は$00で初期化してrgbasmrgblinkを行い,出力された.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ページにまとめてみたいと思います.