#はじめに
この記事はすぐにゲームコードが書きたいよう、という人には向けではありませんので、環境だけ構築さえできれば、飛ばしてもらっても構いません。
#build.cfgの設定を取り込む
正常にエミュレーターで動作させるためには、ROMで利用されているメモリの使用領域を定義する必要があります。
そのために必要なのが下記のようなコンフィグファイルです。
(※こちらcc65@wikiサイト内、サンプルコードにあるnes-mapper0.cfgを参考にしています)
SYMBOLS {
__STACKSIZE__: type = weak, value = $0200; # 2 pages stack
}
MEMORY {
# INES Cartridge Header
HEADER: start = $0000, size = $0010, file = %O, fill = yes;
# 2 16K ROM Banks
# - startup
# - code
# - rodata
# - data (load)
ROM0: start = $8000, size = $7ffa, type = ro, file = %O ,fill = yes, define = yes;
ROMINFO: start = $fffa, size = $0006, type = ro, file = %O, fill = yes, define = yes;
# standard 2k SRAM (-zeropage)
# $0100-$0200 cpu stack
# $0200-$0400 2 pages for cc65 parameter stack
# $0400-$0680 2.5 pages for data, bss, heap
# $0680-$0700 0.5 pages for ppu memory write buffer
# $0700-$0800 1 pages for ppu dma buffer
ZP: start = $0000, size = $0100, type = rw, define = yes;
STACK: start = $0200, size = __STACKSIZE__, type = rw, define = yes;
RAM: start = $0400, size = $0280, type = rw, define = yes;
CMDBUF: start = $0680, size = $0080, type = rw, define = yes;
DMABUF: start = $0700, size = $0100, type = rw, define = yes;
# additional 8K SRAM Bank
# SRAM: start = $6000, size = $2000, type = rw, define = yes;
}
SEGMENTS {
HEADER: load = HEADER, type = ro;
STARTUP: load = ROM0, type = ro, define = yes;
VECINFO: load = ROMINFO, type = ro, define = yes;
LOWCODE: load = ROM0, type = ro, optional = yes;
INIT: load = ROM0, type = ro, define = yes, optional = yes;
CODE: load = ROM0, type = ro, define = yes;
RODATA: load = ROM0, type = ro, define = yes;
DATA: load = ROM0, run = RAM, type = rw, define = yes;
BSS: load = RAM, type = bss, define = yes;
ZEROPAGE: load = ZP, type = zp;
}
FEATURES {
CONDES: segment = INIT,
type = constructor,
label = __CONSTRUCTOR_TABLE__,
count = __CONSTRUCTOR_COUNT__;
CONDES: segment = RODATA,
type = destructor,
label = __DESTRUCTOR_TABLE__,
count = __DESTRUCTOR_COUNT__;
CONDES: segment = RODATA,
type = interruptor,
label = __INTERRUPTOR_TABLE__,
count = __INTERRUPTOR_COUNT__;
}
おおよそ、重要な情報は下記の5点
HEADER 後述のNESヘッダーの格納位置です。0x0000を指定します。
STARTUP プログラムが格納されているアドレスのスタート位置です。0x8000を指定します。
STACK スタック領域の先頭です。0x200から2page(0x200)分指定しています。
RAM ヒープ領域です。上記では0x400から2.5page(0x280)分指定してあります。
VECINFO 割り込み関連のセグメント情報です。0xfffaから6バイト定義してあります。
他はコメントを参考ください。
##startup.asmの用意
加えて、ヘッダー情報と各セグメントの制御が書かれたアセンブラコードを用意する必要があります。
現状データをバイナリで置かないのでCHR-ROMバンクはありません。
このアセンブラプログラム内にプログラムメインループ用のNesMain処理とNMI割り込み用のNMIProc処理が記載してありますので、ようやくmain.cが記載できるようになります。
(※こちらcc65@wikiサイト内、Tekepen氏、@MIRROR_氏記載のstartup.asmを参考にしています)
; startup.asm
.setcpu "6502"
.autoimport on
.importzp sp
.import __STACK_START__, __STACK_SIZE__
.global _NesMain
.global _NMIProc
;iNESヘッダー(16 Byte)
.segment "HEADER"
.byte $4e, $45, $53, $1a ; "NES" Header
.byte $02 ; PRG-ROMバンク数(16kb x2)
.byte $00 ; CHR-ROMバンク数(8KB x0)
.byte $01 ; 0:Horizontal/1:Vertical/3:Vertical & WRAM Mirror
.byte $00 ; Mapper 0
.byte $00 ; PRG-RAMサイズ
.byte $00,$00,$00,$00,$00,$00,$00
.segment "STARTUP"
; リセット割込み
.proc Reset
sei
ldx #$ff
txs
; cc65パラメータスタック設定
lda #<(__STACK_START__ + __STACK_SIZE__)
sta sp
lda #>(__STACK_START__ + __STACK_SIZE__)
sta sp+1
jsr _NesMain ;メイン関数呼び出し
.endproc
; NMI割り込み
.proc NMI
; レジスタ退避
pha
txa
pha
tya
pha
; NMI割り込み処理呼び出し
jsr _NMIProc
; レジスタ復帰
pla
tay
pla
tax
pla
; 割り込み終了
rti
.endproc
.segment "VECINFO" ;ベクタテーブル
.word NMI ;NMI割り込みアドレス
.word Reset ;リセット割込みアドレス
.word $0000 ;IRQ割り込みアドレス
##MakeFileの書き換え
前の記事の該当部分を下記の通り書き換えてください。
###ファイル位置
LIBDIRを定義してそこにbuild.cfgとstartup.asmを入れておきましょう。
ついでにsrcファイルの場所もフォルダを作って移しておきます、
LIB_DIR := lib
SRC_DIR := src
BIN_DIR := bin
OBJ_DIR := obj
TARGET := $(BIN_DIR)/$(PROJECT).nes
CFGFILE := $(LIB_DIR)/build.cfg
###リンカオプション
ld65にはコマンドラインコンフィグオプションとして、コンフィグを渡すことが可能ですので、コンフィグファイルを指定します。
$(TARGET): $(OBJS)
@ echo Create $@
$(LD) $(LDFLAGS) -m $@.map -o $@ --config $(CFGFILE) --obj $(OBJS) --lib $(LIBS)
@ echo Done.
なお、(何ともわかりづらいですが)下記のようなエラーが出たらコンフィグファイルとターゲットが双方記載があるという事ですので、リンカに渡しているオプションを確認してみてください。
具体的にはld65の-tオプションとは競合するため、該当のオプションは外しましょう。
###fcsub.h,fcsub.cコード
下記のサンプルビルドは日経BP発行「日経ソフトウエア」2021年1月号の特集記事「ファミコンで動くゲームを作ろう 第2部 ライブラリの自作と実験プログラム」(著者:松原拓也氏)に記載されているfcsub.h,fcsub.cを参照していますので、ビルドでエラーを出さない為にはlibフォルダ以下に該当のコードも入れておく必要があります。
###サンプルコード
以上、ここまでのビルド環境が揃ったならば、下記のサンプルをsrcフォルダに入れてビルドしてみてください。
#include "..\lib\fcsub.h"
// パターンテーブル BG用
const unsigned char pattern_tbl[] = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
};
// カラーテーブル
const unsigned char color_tbl[] = {
0x0d,0x00,0x10,0x20
,0x0d,0x36,0x12,0x20
,0x0d,0x26,0x08,0x36
,0x0d,0x21,0x2f,0x20
};
char NesMain()
{
unsigned char y,x,tileno;
unsigned short bgx;
fc_init();
ppu_pattern((unsigned char *)pattern_tbl, 0,2); // BGパターンをPPUに登録
ppu_palette((char *)color_tbl,0,4); // BGカラーパレット設定
ppu_enable(0);
bg_cls();
sp_cls();
//BGにタイル配置
for (y = 0; y < 30; y++) { //0-31
for (x = 0; x < 32; x++) { //0-31
tileno = ((x + y) & 1);
bg_printch(x, y, tileno);
bg_printch(x, y + 32, tileno);
}
}
ppu_enable(1);
bgx=0;
while (1) {
bgx = (bgx + 1) & 511;
ppu_vsync();
sp_dmastart();
bg_scroll(bgx, bgx);
}
return 0;
}
char NMIProc()
{
return 0;
}
#補足
###CC65コンパイラの-tオプションについて
CC65コンパイラの-tオプションはTargetの略となっていますが、機能的には下記の内容となっていました。
####cc65
cc65の-tオプションは該当のコードの通り、コンパイラが認識するマクロにターゲットに記載したハード用(下記はNES)の定義が追加されるというものの様子です。
####ca65
ca65のtオプションは該当のコードの通り、アセンブラが認識するシンボル定義について、ターゲットに記載したハード用(下記はNES)の定義が追加されるみたいです。
####ld65
ld65のtオプションは各プラットフォーム別のCPUターゲットやバイナリフォーマット、文字コード変換テーブルのテンプレートが選ばれるようです。
それぞれ興味がある人はソースコードがCC65のサイトにありますので確認してみてください。
###atmos.libについて
どこのサイトを見に行ってもnes.libと一緒にatoms.libがリンクされている状態が見られました。
このライブラリなんだろうという疑問の元、調べていったところ、Oric-Atomsというゲームハードのライブラリである様子。
何故、多数のサイトでnes.libと一緒に別ターゲットハードのライブラリが一緒にリンクされてるのかという理由はわからなかったのですが、下記の記事を見る限りは必要ではなさそうでしたので、自分の記事の中では外してあります。
Re: [cc65] NES Atmos.lib
https://cc65.github.io/mailarchive/2009-02/6510.html
(※要約すると、C言語上で使えない関数とか出てくるけど、そもそもNESのエミュ上だとクラッシュしたりするから外したほうが賢明という内容っぽいです。コミュニティの一意見ですが、一理あるかなと思いました)
#参考記事一覧
CC65
https://www.cc65.org/
cc65@wiki
https://w.atwiki.jp/cc65/
ギコ猫でもわかるファミコンプログラミング 第2章 NESASM
http://gikofami.fc2web.com/
日経BP発行「日経ソフトウエア」2021年1月号
特集記事「ファミコンで動くゲームを作ろう 第2部 ライブラリの自作と実験プログラム」(著者:松原拓也氏)
cc65コミュニティ
https://github.com/cc65
該当メールアーカイブ記事
https://cc65.github.io/mailarchive/2009-02/6510.html
Oric Atoms
https://gtd27nkicaocctxu4ye6almbu4--en-m-wikipedia-org.translate.goog/wiki/Oric_Atmos#Oric_Atmos
#関連記事一覧
ファミコンROM作ってみた
ファミコンROM作ってみた:開発編(画像コンバーター)
ファミコンROM作ってみた:開発編(環境)
ファミコンROM作ってみた:開発編(ビルド)
ファミコンROM作ってみた:開発編(コード設計)
ファミコンROM作ってみた:開発編(共通関数ライブラリ)
ファミコンROM作ってみた:開発編(プロダクト用関数ライブラリ)
ファミコンROM作ってみた:開発編(mainとフローの処理コード)
ファミコンROM作ってみた:開発編(キャラクター制御コード)