2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ファミコンROM作ってみた:開発編(ビルド)

Last updated at Posted at 2021-03-05

#はじめに
この記事はすぐにゲームコードが書きたいよう、という人には向けではありませんので、環境だけ構築さえできれば、飛ばしてもらっても構いません。

#build.cfgの設定を取り込む
正常にエミュレーターで動作させるためには、ROMで利用されているメモリの使用領域を定義する必要があります。
そのために必要なのが下記のようなコンフィグファイルです。
(※こちらcc65@wikiサイト内、サンプルコードにあるnes-mapper0.cfgを参考にしています)

build.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
; 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オプションとは競合するため、該当のオプションは外しましょう。

※画像はld65のmain.cコードです。
image.png

###fcsub.h,fcsub.cコード
下記のサンプルビルドは日経BP発行「日経ソフトウエア」2021年1月号の特集記事「ファミコンで動くゲームを作ろう 第2部 ライブラリの自作と実験プログラム」(著者:松原拓也氏)に記載されているfcsub.h,fcsub.cを参照していますので、ビルドでエラーを出さない為にはlibフォルダ以下に該当のコードも入れておく必要があります。

####libフォルダ中身
image.png

###サンプルコード
以上、ここまでのビルド環境が揃ったならば、下記のサンプルをsrcフォルダに入れてビルドしてみてください。

main.c
#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;
}


#実行結果
image.png

#補足
###CC65コンパイラの-tオプションについて
CC65コンパイラの-tオプションはTargetの略となっていますが、機能的には下記の内容となっていました。

####cc65
cc65の-tオプションは該当のコードの通り、コンパイラが認識するマクロにターゲットに記載したハード用(下記はNES)の定義が追加されるというものの様子です。
image.png

####ca65
ca65のtオプションは該当のコードの通り、アセンブラが認識するシンボル定義について、ターゲットに記載したハード用(下記はNES)の定義が追加されるみたいです。
image.png

####ld65
ld65のtオプションは各プラットフォーム別のCPUターゲットやバイナリフォーマット、文字コード変換テーブルのテンプレートが選ばれるようです。
image.png

それぞれ興味がある人はソースコードが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作ってみた:開発編(キャラクター制御コード)

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?