LoginSignup
2
2

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