C言語でNESアプリを作成(ビルド環境の準備)

通常のC言語での開発と同様の環境を揃えるところから。
Windows環境では、コマンドプロンプト上でCコンパイラとmakeツールが普通に動くようにします。
LinuxやMacOSXなどのUNIXライクな環境では、Cコンパイラのみ導入します。

コンパイラ cc65 <https://cc65.github.io/>
6502ベースCPU向けのコンパイラ。
アセンブラやライブラリアン、リンカーを含むため、ちょっと規模の大きいコードも普通に扱えます。
アセンブラのソースを混ぜるのも楽です。
Windows
ダウンロード
リンク先の「Windows Snapshot」をクリックすると「cc65-snapshot-win64.zip」の自動ダウンロードが始まります。
自動ダウンロードできない場合は、「direct link」をクリックしてみてください。
広告のDownloadはクリックしないように気をつけてください。
インストール
圧縮ファイルを展開するだけです。
以降の説明では、c:\cc65 内に展開していることを想定しています。
c:\cc65
├─asminc  ... アセンブラ用インクルードファイル群
├─bin     ... cc65などの実行モジュール群(後でパスを通すディレクトリ)
├─cfg     ... メモリレイアウト等の定義ファイル群
├─html    ... ドキュメント
├─include ... C言語用ヘッダファイル群
│  ├─em
│  ├─geos
│  ├─joystick
│  ├─mouse
│  ├─sys
│  └─tgi
├─lib     ... ライブラリファイル群
├─samples
└─target
Linux、Macなど
Windows以外のOSの場合はソースコードからコンパイルするか、パッケージからインストールすることができます。
以下のサイトに詳しく記載されています。
NESDEV <http://wiki.nesdev.org/w/index.php/Installing_CC65>

当方が確認した手順は、以下の通り。
(コンパイラは、ユーザホーム直下のcc65のディレクトリに格納した場合。)

コンパイル

cc65のGitHubサイト<https://github.com/cc65/cc65>内の「Download ZIP」よりソースコードをダウンロード。

ユーザホーム直下にcc65のディレクトリを作成して、ダウンロードした圧縮ファイルを展開。
(ダウンロードしたディレクトリ名は各環境に合わせて変更すること。)

$ cd ~
$ mkdir cc65
$ cd cc65
$ unzip ../Downloads/cc65-master.zip
インストール

コンパイル後、所定のディレクトリに配置。

$ cd cc65-master
$ make
$ make prefix=~/cc65 install
MSYS2 <https://www.msys2.org/>
Software Distribution and Building Platform for Windows
Windows向けのGNUコマンドラインツールコレクションMSYSの後継で、シェルを含めてUNIXライクな環境が構築できます。
本稿ではmakeと共にcpやrm、basenameといったファイル関連のコマンドをコマンドプロンプト上で使っていきます。
下記は基本パッケージとmakeをインストールする手順です。
ダウンロード
Webサイトの「Installation」に記載の「Download the installer:」リンク先よりインストーラをダウンロード。
  1. msys2-x86_64-リリース日.exe
インストール
ダウンロードした実行モジュールを起動し、基本パッケージをインストーラの指示に従いインストール。
デフォルト指定で「C:\msys64」にインストールされます。
パッケージの更新
  1. MSYS2 Shellのターミナル(最新版ではMSYS2 UCRT64)が起動しますので、以下のコマンドを入力して基本パッケージ更新後、一旦ターミナルを終了します。
    「exit」しても終了しきれない場合は「Ctrl+D」で切り抜けてください。
  2. $ pacman --needed -Sy bash pacman pacman-mirrors msys2-runtime
    $ exit
    
  3. MSYS2 Shellのターミナル(msys2_shell.bat)を再起動して、パッケージ全体の更新を行います。
    $ pacman -Su
    $ exit
    
  4. 以降は、MSYS2 Shellのターミナルが使えますが、標準ではmakeがインストールされていません。
    $ make
    bash: make: コマンドが見つかりません
    
  5. makeパッケージを以下のコマンドでインストールします。
    $ pacman -S make
    
  6. インストールされたmakeを確認します。
    $ make
    make: *** No targets specified and no makefile found.  Stop.
    
    $ make --version
    GNU Make 4.4.1
    Built for x86_64-pc-msys
    Copyright (C) 1988-2023 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later 
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    
環境変数

コマンドプロンプトやシェル上でコンパイラを簡単に呼び出せるよう環境変数の設定を行います。

Windows
cc65とMSYS2へのパスを環境変数のPATHに追加します。

set MSYS_HOME=c:\msys64
set CC65_HOME=c:\cc65
set PATH=%CC65_HOME%\bin;%MSYS_HOME%\usr\bin

コンピュータの詳細設定内にあるユーザ環境変数に直接設定することも可能ですが、
他のアプリとの競合が怖いので、上記の内容をsetenv.bat内に記載してコマンドプロンプトを開いた際に1回だけ実行することで定義します。
> setenv.bat
Linux、Macなど
cc65へのパスを環境変数のPATHに追加します。
export PATH=~/cc65/bin:$PATH
WindowsでMSYS2 Shellのターミナルを利用する場合は、以下の様に記述します。
export PATH=/C/cc65/bin:$PATH
bashは.bash_profile、tcshは.tcshrc…と、通常利用するシェルの定義に追加します。
Windows同様、他のアプリとの競合を避けるのであれば上記の内容をsetenv.sh内に記載してシェルを開いた際に1回だけ「.」付きで実行することで定義します。
$ . setenv.sh
コンパイラの起動確認
コマンドプロンプトやシェル上からコンパイラが正常に呼び出せることを確認します。
> cc65 --version
> cc65 V2.19 - Git ad7c5a661
バージョン番号とGitのリビジョン番号は、ダウンロードした時の番号になります。
プロジェクトの配置とビルド

makeツールを使ってサンプルプロジェクトがビルドできることを確認します。

Makefile内でプロジェクト名やソースファイル名を自動取得するため、規定のディレクトリ構成を準備します。
(Makefileは様々なところを参考にさせて頂きました。)

プロジェクトディレクトリの作成
プロジェクト用のディレクトリを作成し、ソースコードとMakefileを用意します。
nesapp/               ... 作業用のディレクトリ
│setenv.bat(.sh)     ... 環境変数設定用のバッチファイル(スクリプト)
│
└─hello/            ... プロジェクト用のディレクトリ(実行ファイル名と同じ)
    │Makefile        ... ビルド用のメイクファイル
    │nes.cfg         ... メモリ配置用の定義ファイル(cc65/cfg/nes.cfgよりコピー)
    │
    └─src/          ... ソースプログラム用のディレクトリ
          main.c      ... ソースプログラム

作業用のディレクトリを作成し、setenv.bat(.sh)などプロジェクト共有のファイルを配置します。

プロジェクトは、実行ファイル名と同じディレクトリを作成し、関連するディレクトリやファイルを配置します。
ソースプログラムは、必ず src/ 内に保存します。
ファイル名は任意です。拡張子は .c がCコンパイラ用で、.s と .asm はアセンブラ用です。

サンプルプログラム

cc65の標準ライブラリを使ってメッセージを表示するサンプルを、src/ 内に用意します。
今回は、cc65@wiki様のサイト<http://www34.atwiki.jp/cc65/pages/14.html>にあるコードを拝借して、
少し手を加えてみました。

#include <conio.h>
#include <string.h>

const char message[] = "Hello cc65!";

int main(void)
{
    unsigned int frame = 0;

    // 画面の消去
    clrscr();

    // 表示位置の変更
    gotoxy((32-strlen(message))/2, 30/2);

    // メッセージの表示
    cprintf(message);

    // 処理を終わらせないための無限ループ
    while(1) {
        gotoxy(0, 0);
        cprintf("%8d", frame++);
    }

    return 0;
}

ファイル名は任意です。とりあえず、main.cなどの名前で保存してください。

Makefile

コンパイラやリンカーの呼び出し規則をMakefileに記述して、ビルド手順を簡略化します。
少し自動化した点は以下のとおり。

  1. カレントディレクトリよりプロジェクト名を取得
  2. src/ディレクトリより、コンパイル対象となるファイルを自動取得
  3. 中間ファイルはobj/ディレクトリに格納
  4. 実行モジュールとマップファイルはbin/ディレクトリに格納
  5. コンパイラの機能を使って依存関係の自動取り込み

PROJECT  := $(shell basename $(CURDIR))

SRC_DIR  := src
BIN_DIR  := bin
OBJ_DIR  := obj

TARGET   := $(BIN_DIR)/$(PROJECT).nes

CFILES   := $(wildcard $(SRC_DIR)/*.c)
SFILES   := $(wildcard $(SRC_DIR)/*.s) $(wildcard $(SRC_DIR)/*.asm)
LIBS     := nes.lib atmos.lib

OBJS     := \
	$(foreach file, $(CFILES), $(OBJ_DIR)/$(basename $(notdir $(file))).o) \
	$(foreach file, $(SFILES), $(OBJ_DIR)/$(basename $(notdir $(file))).o)

DEPENDS  := $(wildcard $(OBJ_DIR)/*.dep)

ASFLAGS  := -t nes
CFLAGS   := -t nes -j
LDFLAGS  := -t nes

CC       := cl65
AS       := cl65
LD       := ld65
RM       := rm -rf
MKDIR    := mkdir

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
	$(CC) $(CFLAGS) --create-dep $@.dep -c -o $@ $<

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.s
	$(AS) $(ASFLAGS) --create-dep $@.dep -c -o $@ $<

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.asm
	$(AS) $(ASFLAGS) --create-dep $@.dep -c -o $@ $<

.PHONY: all
all: init $(TARGET)

.PHONY: clean
clean:
	- $(RM) $(BIN_DIR)
	- $(RM) $(OBJ_DIR)

.PHONY: init
init:
	@$(if $(shell ls | grep $(BIN_DIR)) , , $(MKDIR) $(BIN_DIR))
	@$(if $(shell ls | grep $(OBJ_DIR)) , , $(MKDIR) $(OBJ_DIR))

.PHONY: run
run: all
	$(VM_PATH) $(TARGET)

$(TARGET): $(OBJS)
	@ echo Create $@
	$(LD) $(LDFLAGS) -m $@.map -o $@ --obj $(OBJS) --lib $(LIBS)
	@ echo Done.

-include $(DEPENDS)

Makefileの名前で保存してください。

ビルド

プロジェクトディレクトリに移動後、makeを実行します。

> cd hello
> make

ビルドが成功すれば、binディレクトリ内にhello.nesのファイルが作成されます。
任意のエミュレータにて動作を確認してください。

環境変数「VM_PATH」にエミュレータへのパスを指定することで、ビルド成功後にエミュレータでの実行ができます。 エミュレータは、実行ファイル名に続いて.nesのファイル名指定で起動できれば問題ありません。
以下のような一文をsetenv.bat内に追記後、実行しておきます。

set VM_PATH=c:\opt\Mesen\Mesen.exe

makeコマンドを実行します。

> make run

問題点

現状では以下の問題点を確認しています。

  1. 標準ではC言語が使いやすいよう拡張RAM領域を使用するため、厳密なマッパー0ではない。
    cc65/cfg/nes.cfgの定義で作成した実行モジュールを実機で動かすには、0x6000~0x7fffがRAMである必要がある。
  2. main()呼び出し前のppuinitにおいて、VRAM領域の初期化前にPPUを稼働させている。
    未初期化VRAMを処理できないエミュレータでは、改造版ppu.sをsrc/内に保存してビルドすることで回避可能。
サンプルプロジェクト

上記までのファイルをまとめたものはこちら。

  1. nesapp-hello-20230501.zip「Hello cc65」を表示するサンプルプロジェクト