Ubuntu 15 上で NES エミュレータで動く Hello World を作ってみます。

コンパイラ

NES の CPU は 6502 ですので、コンパイラには cc65 を使います。Ubuntu には cc65 のパッケージが用意されていて、apt-get でインストールできる のですが、このバージョン(V2.13.9)には問題があってプログラムのリンクで こけます。

cc65 の github リポジトリから 最新のソースツリーをクローンして、自前でビルドしてインストールしましょ う。

$ make
$ sudo prefix=/usr/local make install

エミュレータ

FCEUX という NES エミュレータを使います。謎めいた名前ですが、Wikipedia によると FCE Ultra というエミュレータの各種派生版をマージしたものだそ うです。apt-get からインストールできます。

$ sudo apt-get install fceux

ランタイム環境

メモリ

  • NESには、メインメモリ は 2KB しか載っていませんが、cc65 では 静的変数をセーブRAMに割り当てることで、8KB のメモリを静的変数として 扱うことができます。1
  • また、6502 は、CPUスタックが 1 ページ(= 256 バイト)しかありませんが、 C関数の引数や自動変数は別の領域に割り当てられますのでもう少し使えま す。
  • malloc(3) によるメモリの動的確保はできません。2

標準関数

  • ファイル操作関数は使えません。

ファイルシステムが無いので当然ですが、printf(3) による画面への出力や abort(3) によるプログラムの停止もそれぞれ、標準出力や標準エラー出力と いう「ファイル」の操作になりますので、使えません。これらの関数を使った プログラムはリンク時にエラーになります。

画面表示

画面への出力には conio.h に定義される関数を使います。例えば、printf の 替わりに cprintf という関数があります。

NES では画面の操作は基本的に VBlank 3中にする必要があるのですが、 conio を使えば出力はいったんバッファにたくわえられて、VBlank 時に割り 込みルーチンが実際にビデオメモリに書き込んでくれます。メインプログラム の都合の良いタイミングで文字の出力ができるわけですね。

conio は画面のスクロールをサポートしていません。画面外の座標に書き込む とビデオメモリの内容が破壊されますので注意する必要があります。

Hello World

ソースコード

#include <conio.h>

int main()
{
    cprintf("Hello World\r\n");
    while (1)
        ;
}

main を抜けてしまうと画面が消えてまたプログラムが最初から実行されてし まうので、最後に無限ループを置いています。

また、cprintf では改行するときに \n の替わりに \r\n を使います。

コンパイル

cc65 コマンドはアセンブリを出力するだけなので、リンクまでしてくれる cl65 コマンドを使います。

$ cl65 -o main.nes -t nes main.c

実行

$ fceux main.nes

main.nesを実行している画面

やったね。

画面をスクロールさせたりしようとすると、conio が邪魔になっていろいろ 大変なのですが、それはまた別のお話。

  1. セーブRAMなので、エミュレータによって .sav ファイルとして書き出されます(笑)。ただし、main 関数に入る前にCランタイムによってメモリがリセットされるので、プログラムの状態を永続化させる為には使えません。 

  2. メモリアロケータは自分で書きましょう ;-p 

  3. CRT が画面右下のピクセルを書き終わって、次に左上のピクセルを書き始めるまでの待ち時間。