2025年05月04日 05:07
しばらく前のことだが、ふとファミコンディスクシステムの互換BIOSを自作したいなと思い、そのためにどうも仕様が分からないところを実物を(エミュレータ上で)叩いて調べるにあたって、ディスクシステム上で動く最低限のソフトを作ろうとしたら案外簡単だったのでメモを残しておく。
バンク切り替え無しのマッパー0のソフトをベースに、ディスクシステムの機能は何も使わない、ただディスクシステムで動くだけの移植を行う。
アセンブラはNESASMをずっと使っているのだが、これには.fdsファイルの出力機能はどうやら無い。ただ「-raw」指定でINESヘッダ無しのファイルを出力することはできるのでこれを使う。
作業の流れとしては、少々変更したソースをアセンブルし、バイナリエディタなどで少々の切り貼りを行うことになる。
コード側で変更が必要なのは、BIOSが使うメモリ領域と、コードを置くアドレスの変更に伴うもの、そしてミラーリング設定の3つである。
・BIOSが使うメモリについて。
参考: https://www.nesdev.org/wiki/FDS_BIOS
$100~$103は、割込み時の処理を決定するために使われるので適切に設定する。といっても今回の用途ではデフォルトのまま触らないようにするだけでよいはず。ここを別の用途に使っているなら空ける必要がある。
この領域はスタックの末端であり、普通はまず使われない場所である。(まず使われない場所であることを理由に使っている場合はあるだろう)
他に$00あたりと$FFあたりにもBIOSで使われるメモリ領域があるが、説明によれば、――このブログサービスの禁止単語のようなので伏せ字にするが――「temp○rary variables」と「Used by controller reading routines」であり、おそらくディスクシステムの機能を使わない(BIOSコールをしない)限り勝手に使われることは無く、たぶんここは普通に使って問題ない。
(このブログサービスの禁止単語については色々と言いたいことがあるが、ここに愚痴を書いても邪魔なので別の記事にしよう…)
・ミラーリングについて。
ディスクシステムは縦ミラーと横ミラーの切り替えがソフト的に可能だ。
ハード的に決めているマッパー0からの移植では忘れずに設定しなければいけない。(後述するが、忘れていた)
これは$4025への書き込みで行う。
https://www.nesdev.org/wiki/Family_Computer_Disk_System#FDS_Control_($4025)
このレジスタには他の設定項目が含まれているので、普通はread-modify-writeをするのが適切なようだ。
でも他を使わないのにわざわざRMWをするのも無駄な気もして、決め打ちの値を書いた。
・アドレスについて。
の前にまずそもそもディスクシステムのファイル構造についてだが、
ディスクシステムのディスク上のファイルは変わった形式で、データだけでなくそれをどのアドレスに読み込むかまでファイルの属性として指定されている。
そしてファイルとは別にディスク先頭のブロックに「起動時に最初に読み込むファイル」が定義されている。
なのでマッパー0移植であれば、最初に全て読み込ませてやるだけで後はコード側ではファイル読み込みを明示的に行うこと無く素のファミコンソフトのつもりで実行できる。
アドレスは、素のファミコンで(マッパー0で)使えるアドレスは$8000~$FFFFであるのに対し、
ディスクシステムではこのうちの$E000~$FFFFにBIOSが置かれておりここはユーザープログラムに使えない。
その代わり、$6000~$7FFFにもRAMが用意されており、都合プログラムを置けるRAM領域は$6000~$DFFFと、マッパー0で見えるのと同量が使える。
割り込みベクタの$FFFA~FFFFはBIOSに取られるので、代わりに(デフォルトでは)$DFFA~DFFFが仮想的な割り込みベクタとして用意されている。
なのでアドレスはこれに合わせてマッパー0で$8000~$FFFFを使っていたものを$6000~$DFFFに$2000だけ平行移動させるのがシンプルでやりやすいと思う。
あるいは、$8000~DFFAをそのまま、$E000~FFFAを$6000~7FFFに移し、割り込みベクタの$FFFA~FFFFをDFFA~DFFFに移動するのも、書き換えるアドレスが少なくてよいかもしれない。
アドレスの扱いに関して、NESASMの.bank指定と.org指定について今までずっと理解が曖昧だったのだが今回調べてやっと理解した(気がする)ので自分なりの理解を備忘録的に書いておく。
.bankは、出力されるROM(イメージファイル)内での、$2000バイト単位の位置を指定する。.bank NはN*$2000バイト目からの$2000バイトに相当する。ここでPRG-ROM・CHR-ROMの区別は無い。
この$2000バイト単位をバンクと呼びたくなるが、マッパーによるPRGバンク切り替えは$4000単位のものが多く、この2つの「バンク」の扱いが紛らわしいので注意が必要だ。
$2000バイト内の位置合わせは.orgによる。
コードが$2000バイト区切りの境界をまたぐとエラー。
.orgは、CPUから見えるアドレス(16bit)を決定する。これには2つの意味があって、
.orgで決定されたコードのアドレスは、その下位13bitがROM上でバンク内の位置を指定する。上位3bitはここでは使われない。
.orgで決定されたラベルのアドレスは、プログラム中でその16bitのアドレスの数値として扱われる
例えば、
どこか別の場所からそれを呼び出すために
一方、その「func」のコードの実体はROM内で先頭から$2000×4=$8000に$A123の下位13bitの$1123を足した$9123バイトの位置にあるので、それを想定された位置に置くコード、例えばバンク4,5を8000~BFFFの位置に置くのだとプログラマーが考えてそのようなバンク切り替えのコードを書くことになる。
プログラム中でアドレスを何らかの計算で求めていたりしなければ、.orgで指定された数値やアドレスとして使う数値を一貫して-$2000するだけでディスクシステム用のコードになるはずである。
マッパー0のソースはバンクとorgの対応は次のようになっているはずだ。
.bank 0 → .org $8000~$9FFF
.bank 1 → .org $A000~$BFFF
.bank 2 → .org $C000~$DFFF
.bank 3 → .org $E000~$FFFF
.bank 4 → CHR-ROM
これに対し、ディスクシステム用に作るべきファイルは次のように$2000だけずらしたものにするのが素直だ。
.bank 0 → .org $6000~$7FFF
.bank 1 → .org $8000~$9FFF
.bank 2 → .org $A000~$BFFF
.bank 3 → .org $C000~$DFFF
.bank 4 → (一応CHR-ROM)
CHR-ROMについては、別の1つのバイナリファイルで扱っていてソースでは.incbinしているだけなら、わざわざ一旦1つのファイルにまとめる必要はない。
もしソース上で組み立てているならそのままbank4に置いておいて後で分割するのがよいだろう。
これを「-raw」指定でアセンブルし、ヘッダなしPRGのみ32kB、またはCHRありなら40kBのファイルができる。
分割するならして、PRG部32k($8000)バイトとCHR部8k($2000)バイトの2ファイルが用意できたら、FDSファイルのフォーマットに合わせ適宜必要な部分を追加する。
FDSファイルのフォーマットはこちら、 https://www.nesdev.org/wiki/FDS_file_format
そしてディスクのフォーマットはこちらにある。 https://www.nesdev.org/wiki/FDS_disk_format
ディスクの物理的なフォーマットも面白いが、エミュレータで動かすファイルを作るだけなら理解しなくてよいので、FDSファイルを作るのに必要な(論理的な?)部分だけ説明する。
今回作ったファイルは次のようになっている。
各部を説明する。
ディスク内容は「ブロック」という単位に分かれている。
ブロックごとにのみ読み書きが可能であり、それのためにブロックの頭と末尾に何かあったりブロック間は隙間があったりするが、FDSイメージファイル上ではその部分は再現されておらず単にブロックのデータ部分のみを隙間なく並べている。
ここで、今回作ろうとしているマッパー0ベースのソフトはファイルサイズは固定なので、上で示したPRG・CHR以外の部分についてソフトごとに変更が必要な箇所は無い。(ゲーム名や日付など書き換えた方がよい部分はあるが)
なので、「先頭からPRG部の前まで」と「PRG部の次からCHR部の前まで」をファイルにしておけば、作ったPRG部・CHR部とファイルとして結合するだけでFDSファイルが完成する。
また、CHR-RAMを使うソフトの場合、32kBのPRG部のファイルだけで済むので扱いが楽だ。「先頭からPRG部の前まで」を用意するだけでよい。(上記から最初に読み込むファイルの指定とファイル個数を変える)
実際に試してみよう。
以前書いた般若心経を変換する。動いた。

CHR-RAMのものも試す。以前書いた全画面表示プログラム。動いた。

(なお実は、「A面B面切り替え」が出ていることから分かるが、誤ってFDSファイルヘッダでの面数の指定を2面にしていた。それでも動いた)
自分で書いたコード(しかもサイズも小さい)を自分で書き換えてもあまり面白みがない。何か公開されているコードは無いか。
40kBのNESゲームで、NESASM向けのソースが手に入り、少なくとも個人的に書き換えて実行することが許可されているもの。
…と探すとよさそうなものが見つかった。
「Lan Master」。Shiru氏によるシンプルなパズルゲーム。
「The game and its source code are released into Public Domain」とあり、心置きなく改変が可能だ。
http://shiru.untergrund.net/software.shtml#nes
ソースを見るとファイルが多くてちょっと尻込みしたが、orgの指定されている箇所はメインのgame.asmのみ。
8000,a000,c000,e000とfffa(とCHR側)があるので、それぞれ-2000する。
CHR部分は1ファイルにまとまったものを.incbinしている。これは不要だ。コメントアウトする。
変数定義を見ると100~103は使っていないようだ。
しかし初期化の部分でゼロクリアしている。
$0100~$01FFの部分のクリアをやめる。
一応104~1FFはゼロクリアしておいた方がいいかとも思ったがまあただのスタックだろうし不要だろうと思いそのまま。
動いた。

せっかくなのでクリアまで遊んだ。なかなか面白いゲームだ。
するとエンディングの文字がどうもおかしい。PPUビューアで見るとミラーリングが間違っているせいで横スクロールなのに横1画面分に書いてしまっており書き換えが画面内に見えている。

ここで初めてミラーリング設定をしていなかったことに気づく。ゲーム画面は1画面だから正常に見えていたんだな。危なかった。
というわけで先述のミラーリング設定をすると、

今度は正常。
最終的なdiffはこんな感じ。
バンク切り替え無しのマッパー0のソフトをベースに、ディスクシステムの機能は何も使わない、ただディスクシステムで動くだけの移植を行う。
アセンブラはNESASMをずっと使っているのだが、これには.fdsファイルの出力機能はどうやら無い。ただ「-raw」指定でINESヘッダ無しのファイルを出力することはできるのでこれを使う。
作業の流れとしては、少々変更したソースをアセンブルし、バイナリエディタなどで少々の切り貼りを行うことになる。
コード側で変更が必要なのは、BIOSが使うメモリ領域と、コードを置くアドレスの変更に伴うもの、そしてミラーリング設定の3つである。
・BIOSが使うメモリについて。
参考: https://www.nesdev.org/wiki/FDS_BIOS
$100~$103は、割込み時の処理を決定するために使われるので適切に設定する。といっても今回の用途ではデフォルトのまま触らないようにするだけでよいはず。ここを別の用途に使っているなら空ける必要がある。
この領域はスタックの末端であり、普通はまず使われない場所である。(まず使われない場所であることを理由に使っている場合はあるだろう)
$0100 | NMIの有効無効と割り込みベクタの選択。BIOSを抜けてユーザーコードに来たときのデフォルトはC0であり、この場合[DFFA]に飛ぶ。DFFAというのは素のファミコンでのFFFAに相当するアドレスなので、普通はそのままでよい。他にDFF8とDFF6が選べる。 |
$0101 | ディスクシステムのタイマーIRQ時の処理を決める。デフォルトの80のとき割込み無効(BIOSが握りつぶす?)、使うならC0にする。ディスクシステムの機能は使わない想定なのでデフォルトでよい。 |
$0102:$0103 | リセット/BRK時の処理に関係するようだがあまり説明が理解できていない。起動時のデフォルトはAC35になっており、ソフトリセットすると5335になる、リセット要因を区別するために使うもの? それとも積極的に書き換えることで何かできる? |
(このブログサービスの禁止単語については色々と言いたいことがあるが、ここに愚痴を書いても邪魔なので別の記事にしよう…)
・ミラーリングについて。
ディスクシステムは縦ミラーと横ミラーの切り替えがソフト的に可能だ。
ハード的に決めているマッパー0からの移植では忘れずに設定しなければいけない。(後述するが、忘れていた)
これは$4025への書き込みで行う。
https://www.nesdev.org/wiki/Family_Computer_Disk_System#FDS_Control_($4025)
このレジスタには他の設定項目が含まれているので、普通はread-modify-writeをするのが適切なようだ。
でも他を使わないのにわざわざRMWをするのも無駄な気もして、決め打ちの値を書いた。
lda #$22 ;V mirror
sta $4025
これを設定していない場合の挙動はよく分からない。Mesenでは横ミラーとして動いているようだった一方、思えば別のプログラムをNintendulatorで動かしたときはネームテーブルへの書き込みが正常に行えなかった。NintendulatorよりMesenの方が挙動は正しそうなので横ミラーがデフォルトだろうか。sta $4025
・アドレスについて。
の前にまずそもそもディスクシステムのファイル構造についてだが、
ディスクシステムのディスク上のファイルは変わった形式で、データだけでなくそれをどのアドレスに読み込むかまでファイルの属性として指定されている。
そしてファイルとは別にディスク先頭のブロックに「起動時に最初に読み込むファイル」が定義されている。
なのでマッパー0移植であれば、最初に全て読み込ませてやるだけで後はコード側ではファイル読み込みを明示的に行うこと無く素のファミコンソフトのつもりで実行できる。
アドレスは、素のファミコンで(マッパー0で)使えるアドレスは$8000~$FFFFであるのに対し、
ディスクシステムではこのうちの$E000~$FFFFにBIOSが置かれておりここはユーザープログラムに使えない。
その代わり、$6000~$7FFFにもRAMが用意されており、都合プログラムを置けるRAM領域は$6000~$DFFFと、マッパー0で見えるのと同量が使える。
割り込みベクタの$FFFA~FFFFはBIOSに取られるので、代わりに(デフォルトでは)$DFFA~DFFFが仮想的な割り込みベクタとして用意されている。
なのでアドレスはこれに合わせてマッパー0で$8000~$FFFFを使っていたものを$6000~$DFFFに$2000だけ平行移動させるのがシンプルでやりやすいと思う。
あるいは、$8000~DFFAをそのまま、$E000~FFFAを$6000~7FFFに移し、割り込みベクタの$FFFA~FFFFをDFFA~DFFFに移動するのも、書き換えるアドレスが少なくてよいかもしれない。
アドレスの扱いに関して、NESASMの.bank指定と.org指定について今までずっと理解が曖昧だったのだが今回調べてやっと理解した(気がする)ので自分なりの理解を備忘録的に書いておく。
.bankは、出力されるROM(イメージファイル)内での、$2000バイト単位の位置を指定する。.bank NはN*$2000バイト目からの$2000バイトに相当する。ここでPRG-ROM・CHR-ROMの区別は無い。
この$2000バイト単位をバンクと呼びたくなるが、マッパーによるPRGバンク切り替えは$4000単位のものが多く、この2つの「バンク」の扱いが紛らわしいので注意が必要だ。
$2000バイト内の位置合わせは.orgによる。
コードが$2000バイト区切りの境界をまたぐとエラー。
.orgは、CPUから見えるアドレス(16bit)を決定する。これには2つの意味があって、
.orgで決定されたコードのアドレスは、その下位13bitがROM上でバンク内の位置を指定する。上位3bitはここでは使われない。
.orgで決定されたラベルのアドレスは、プログラム中でその16bitのアドレスの数値として扱われる
例えば、
.bank 4
.org $A123
func
...
とあった場合、.org $A123
func
...
どこか別の場所からそれを呼び出すために
JSR func
と書いたなら、JSR $A123
として解釈されそのような機械語が出力される。一方、その「func」のコードの実体はROM内で先頭から$2000×4=$8000に$A123の下位13bitの$1123を足した$9123バイトの位置にあるので、それを想定された位置に置くコード、例えばバンク4,5を8000~BFFFの位置に置くのだとプログラマーが考えてそのようなバンク切り替えのコードを書くことになる。
プログラム中でアドレスを何らかの計算で求めていたりしなければ、.orgで指定された数値やアドレスとして使う数値を一貫して-$2000するだけでディスクシステム用のコードになるはずである。
マッパー0のソースはバンクとorgの対応は次のようになっているはずだ。
.bank 0 → .org $8000~$9FFF
.bank 1 → .org $A000~$BFFF
.bank 2 → .org $C000~$DFFF
.bank 3 → .org $E000~$FFFF
.bank 4 → CHR-ROM
これに対し、ディスクシステム用に作るべきファイルは次のように$2000だけずらしたものにするのが素直だ。
.bank 0 → .org $6000~$7FFF
.bank 1 → .org $8000~$9FFF
.bank 2 → .org $A000~$BFFF
.bank 3 → .org $C000~$DFFF
.bank 4 → (一応CHR-ROM)
CHR-ROMについては、別の1つのバイナリファイルで扱っていてソースでは.incbinしているだけなら、わざわざ一旦1つのファイルにまとめる必要はない。
もしソース上で組み立てているならそのままbank4に置いておいて後で分割するのがよいだろう。
これを「-raw」指定でアセンブルし、ヘッダなしPRGのみ32kB、またはCHRありなら40kBのファイルができる。
分割するならして、PRG部32k($8000)バイトとCHR部8k($2000)バイトの2ファイルが用意できたら、FDSファイルのフォーマットに合わせ適宜必要な部分を追加する。
FDSファイルのフォーマットはこちら、 https://www.nesdev.org/wiki/FDS_file_format
そしてディスクのフォーマットはこちらにある。 https://www.nesdev.org/wiki/FDS_disk_format
ディスクの物理的なフォーマットも面白いが、エミュレータで動かすファイルを作るだけなら理解しなくてよいので、FDSファイルを作るのに必要な(論理的な?)部分だけ説明する。
今回作ったファイルは次のようになっている。
0000 46 44 53 1A 02 00 00 00 00 00 00 00 00 00 00 00
0010 01 2A 4E 49 4E 54 45 4E 44 4F 2D 48 56 43 2A 00
0020 4E 59 41 20 00 00 00 00 00 02 FF FF FF FF FF A0
0030 02 09 49 61 00 00 02 00 FF FF FF 00 FF FF FF FF
0040 FF FF FF FF 00 00 00 00 02 03 03 00 00 4B 59 4F
0050 44 41 4B 55 2D 00 28 E0 00 02 04 24 24 24 24 24
0060 24 24 24 24 24 24 17 12 17 1D 0E 17 0D 18 24 28
0070 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
0080 24 24 0F 0A 16 12 15 22 24 0C 18 16 19 1E 1D 0E
0090 1B 24 1D 16 24 24 24 24 24 24 24 24 24 24 24 24
00A0 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
00B0 24 24 24 24 24 24 24 24 24 24 24 24 24 1D 11 12
00C0 1C 24 19 1B 18 0D 1E 0C 1D 24 12 1C 24 16 0A 17
00D0 1E 0F 0A 0C 1D 1E 1B 0E 0D 24 24 24 24 0A 17 0D
00E0 24 1C 18 15 0D 24 0B 22 24 17 12 17 1D 0E 17 0D
00F0 18 24 0C 18 27 15 1D 0D 26 24 24 24 24 18 1B 24
0100 0B 22 24 18 1D 11 0E 1B 24 0C 18 16 19 0A 17 22
0110 24 1E 17 0D 0E 1B 24 24 24 24 24 24 24 15 12 0C
0120 0E 17 1C 0E 24 18 0F 24 17 12 17 1D 0E 17 0D 18
0130 24 0C 18 27 15 1D 0D 26 26 24 24 03 01 01 FF FF
0140 FF FF FF FF FF FF 00 60 00 80 00 04(78 A2 40 8E
… $8000バイトPRG部 …
8140 FF FF FF FF FF FF FA 00 00 60 00 00)03 02 02 FF
8150 FF FF FF FF FF FF FF 00 00 00 20 01 04(00 00 00
… $2000バイトCHR部 …
A150 00 00 00 FF FF 00 00 00 00 00 00 00 00)
0010 01 2A 4E 49 4E 54 45 4E 44 4F 2D 48 56 43 2A 00
0020 4E 59 41 20 00 00 00 00 00 02 FF FF FF FF FF A0
0030 02 09 49 61 00 00 02 00 FF FF FF 00 FF FF FF FF
0040 FF FF FF FF 00 00 00 00 02 03 03 00 00 4B 59 4F
0050 44 41 4B 55 2D 00 28 E0 00 02 04 24 24 24 24 24
0060 24 24 24 24 24 24 17 12 17 1D 0E 17 0D 18 24 28
0070 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
0080 24 24 0F 0A 16 12 15 22 24 0C 18 16 19 1E 1D 0E
0090 1B 24 1D 16 24 24 24 24 24 24 24 24 24 24 24 24
00A0 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
00B0 24 24 24 24 24 24 24 24 24 24 24 24 24 1D 11 12
00C0 1C 24 19 1B 18 0D 1E 0C 1D 24 12 1C 24 16 0A 17
00D0 1E 0F 0A 0C 1D 1E 1B 0E 0D 24 24 24 24 0A 17 0D
00E0 24 1C 18 15 0D 24 0B 22 24 17 12 17 1D 0E 17 0D
00F0 18 24 0C 18 27 15 1D 0D 26 24 24 24 24 18 1B 24
0100 0B 22 24 18 1D 11 0E 1B 24 0C 18 16 19 0A 17 22
0110 24 1E 17 0D 0E 1B 24 24 24 24 24 24 24 15 12 0C
0120 0E 17 1C 0E 24 18 0F 24 17 12 17 1D 0E 17 0D 18
0130 24 0C 18 27 15 1D 0D 26 26 24 24 03 01 01 FF FF
0140 FF FF FF FF FF FF 00 60 00 80 00 04(78 A2 40 8E
… $8000バイトPRG部 …
8140 FF FF FF FF FF FF FA 00 00 60 00 00)03 02 02 FF
8150 FF FF FF FF FF FF FF 00 00 00 20 01 04(00 00 00
… $2000バイトCHR部 …
A150 00 00 00 FF FF 00 00 00 00 00 00 00 00)
各部を説明する。
ディスク内容は「ブロック」という単位に分かれている。
ブロックごとにのみ読み書きが可能であり、それのためにブロックの頭と末尾に何かあったりブロック間は隙間があったりするが、FDSイメージファイル上ではその部分は再現されておらず単にブロックのデータ部分のみを隙間なく並べている。
・FDSファイルヘッダ (物理ディスクには存在しない、イメージファイルのヘッダ)。 | |||||||||||||||||
0000 | 46 | 44 | 53 | 1A | シグネチャ、"FDS",<EOF>。 | ||||||||||||
01 | ディスクの面数。A面だけなのでここでは01。間違っても動きはする。 | ||||||||||||||||
00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | ゼロパディング。 | ||||||
・ディスク内の最初のブロック | |||||||||||||||||
0010 | 01 | Block Code。01はDisk Info Blockを示す。 | |||||||||||||||
2A | 4E | 49 | 4E | 54 | 45 | 4E | 44 | 4F | 2D | 48 | 56 | 43 | 2A | 固定値"*NINTENDO-HVC*"。 | |||
00 | ライセンシーコード。00が未割り当てなのでこれを使った。 | ||||||||||||||||
0020 | 58 | 58 | 58 | ゲーム名。ASCII3文字でいい感じの名前が思いつかなかったので"XXX"とした。 | |||||||||||||
20 | 普通のディスクは20 | ||||||||||||||||
00 | バージョン。0からインクリメント。 | ||||||||||||||||
00 | 面番号。1面目、というか片面しか使わないので00。 | ||||||||||||||||
00 | ディスク番号。1枚目が00。 | ||||||||||||||||
00 | ディスクタイプ(FMC)。FMCの青ディスクとは何かわからないがまあ普通のは00。 | ||||||||||||||||
00 | 不明 | ||||||||||||||||
02 | 最初に読み込むファイルを指定する。02でIDが0から2のファイルを読み込む。CHR無しならここは01。 | ||||||||||||||||
FF | FF | FF | FF | FF | 不明 | ||||||||||||
A0 | 製造年月日。各1バイトのBCDで年は昭和年。2025年は昭和100年なので困る。とりあえずA0年とした。 | ||||||||||||||||
0030 | 02 | 09 | |||||||||||||||
49 | 国コード。日本は49。 | ||||||||||||||||
61 | 不明 | ||||||||||||||||
00 | 不明 | ||||||||||||||||
00 | 02 | 不明 | |||||||||||||||
00 | FF | FF | FF | 00 | 不明 | ||||||||||||
A0 | 02 | 09 | 書き換え年月日。 | ||||||||||||||
FF | 不明 | ||||||||||||||||
0040 | FF | 不明 | |||||||||||||||
FF | FF | ディスクライターのシリアル番号らしい。 | |||||||||||||||
FF | 不明 | ||||||||||||||||
00 | ディスク書き換え回数 | ||||||||||||||||
00 | Actual disk side。よく分からないが上の面番号は両面0になるがこちらは実際のA面B面を示す? | ||||||||||||||||
00 | ディスクタイプ(他)。こちらのディスクタイプも普通のは00 | ||||||||||||||||
00 | ディスクバージョン。不明らしいがとりあえず00で。 | ||||||||||||||||
・2つ目のブロック | |||||||||||||||||
0040 | 02 | Block Code。02はFile amount blockを示す。 | |||||||||||||||
03 | ファイル個数。PRGとCHRに加えて必須の許諾ファイルがあり3個。CHR無しならここは02。 | ||||||||||||||||
・3つ目のブロック | |||||||||||||||||
0040 | 03 | Block Code。03はFile header blockを示す。最初のファイルのヘッダ。 | |||||||||||||||
00 | ファイル番号。0から順に割り当てる。 | ||||||||||||||||
00 | ファイルID。割当ては自由。このIDを指定して読み出す。今回は単純に同じく0にする。 | ||||||||||||||||
4B | 59 | 4F | ファイル名。このファイルは必須の"KYODAKU-"ファイル。 | ||||||||||||||
0050 | 44 | 41 | 4B | 55 | 2D | ||||||||||||
00 | 28 | ファイルの読み込み先アドレス。(PPUの)$2800~、つまりネームテーブルに読み込む指定。 | |||||||||||||||
E0 | 00 | ファイルサイズ。$00E0バイト。 | |||||||||||||||
02 | ファイルタイプ(ファイルの読み込み先の指定)。02はネームテーブル。 | ||||||||||||||||
・4つ目のブロック | |||||||||||||||||
0050 | 04 | Block Code。03はFile data blockを示す。最初のファイルのデータ。 | |||||||||||||||
24 | 24 | 24 | 24 | 24 | ファイル内容。上のファイルヘッダで示された通り、$E0バイトある。 「KYODAKU-」ファイルの内容は読み込み時に表示される許諾メッセージ。 文字コードはゼルダなどに使われているのと同じ、0~9のあとにA~Zが続き以下少々の記号というもの。 | ||||||||||||
0060 | 24 | 24 | 24 | 24 | 24 | 24 | 17 | 12 | 17 | 1D | 0E | 17 | 0D | 18 | 24 | 28 | |
0070 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | |
0080 | 24 | 24 | 0F | 0A | 16 | 12 | 15 | 22 | 24 | 0C | 18 | 16 | 19 | 1E | 1D | 0E | |
0090 | 1B | 24 | 1D | 16 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | |
00A0 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | |
00B0 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 1D | 11 | 12 | |
00C0 | 1C | 24 | 19 | 1B | 18 | 0D | 1E | 0C | 1D | 24 | 12 | 1C | 24 | 16 | 0A | 17 | |
00D0 | 1E | 0F | 0A | 0C | 1D | 1E | 1B | 0E | 0D | 24 | 24 | 24 | 24 | 0A | 17 | 0D | |
00E0 | 24 | 1C | 18 | 15 | 0D | 24 | 0B | 22 | 24 | 17 | 12 | 17 | 1D | 0E | 17 | 0D | |
00F0 | 18 | 24 | 0C | 18 | 27 | 15 | 1D | 0D | 26 | 24 | 24 | 24 | 24 | 18 | 1B | 24 | |
0100 | 0B | 22 | 24 | 18 | 1D | 11 | 0E | 1B | 24 | 0C | 18 | 16 | 19 | 0A | 17 | 22 | |
0110 | 24 | 1E | 17 | 0D | 0E | 1B | 24 | 24 | 24 | 24 | 24 | 24 | 24 | 15 | 12 | 0C | |
0120 | 0E | 17 | 1C | 0E | 24 | 18 | 0F | 24 | 17 | 12 | 17 | 1D | 0E | 17 | 0D | 18 | |
0130 | 24 | 0C | 18 | 27 | 15 | 1D | 0D | 26 | 26 | 24 | 24 | ||||||
・5つ目のブロック | |||||||||||||||||
0130 | 03 | 2つ目のファイルのファイルヘッダブロック。 | |||||||||||||||
01 | ファイル番号と、 | ||||||||||||||||
01 | ファイルIDは01。 | ||||||||||||||||
FF | FF | ファイル名は不要なのでFFで。 | |||||||||||||||
0140 | FF | FF | FF | FF | FF | FF | |||||||||||
00 | 60 | 読み込み先アドレスは(CPUの)$6000から | |||||||||||||||
00 | 80 | ファイルサイズは$8000バイト。 | |||||||||||||||
00 | 読み込み先はプログラムRAM。 | ||||||||||||||||
・6つ目のブロック | |||||||||||||||||
0140 | 04 | 2つ目のファイルのファイルデータブロック。 | |||||||||||||||
XX | XX | XX | XX | $8000バイトのPRG部分。 | |||||||||||||
… | |||||||||||||||||
8140 | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | |||||
・7つ目のブロック | |||||||||||||||||
8140 | 03 | 3つ目のファイルのファイルヘッダブロック。 | |||||||||||||||
02 | ファイル番号と、 | ||||||||||||||||
02 | ファイルIDは02。 | ||||||||||||||||
FF | ファイル名は同じくFFで。 | ||||||||||||||||
8150 | FF | FF | FF | FF | FF | FF | FF | ||||||||||
00 | 00 | 読み込み先アドレスは(PPUの)$0000から | |||||||||||||||
00 | 20 | ファイルサイズは$2000バイト | |||||||||||||||
01 | 読み込み先はキャラクタRAM | ||||||||||||||||
・8つ目のブロック | |||||||||||||||||
8150 | 04 | 3つ目のファイルのファイルデータブロック | |||||||||||||||
XX | XX | XX | $2000バイトのCHR部分。 | ||||||||||||||
… | |||||||||||||||||
A150 | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX | XX |
なので、「先頭からPRG部の前まで」と「PRG部の次からCHR部の前まで」をファイルにしておけば、作ったPRG部・CHR部とファイルとして結合するだけでFDSファイルが完成する。
また、CHR-RAMを使うソフトの場合、32kBのPRG部のファイルだけで済むので扱いが楽だ。「先頭からPRG部の前まで」を用意するだけでよい。(上記から最初に読み込むファイルの指定とファイル個数を変える)
実際に試してみよう。
以前書いた般若心経を変換する。動いた。

CHR-RAMのものも試す。以前書いた全画面表示プログラム。動いた。

(なお実は、「A面B面切り替え」が出ていることから分かるが、誤ってFDSファイルヘッダでの面数の指定を2面にしていた。それでも動いた)
自分で書いたコード(しかもサイズも小さい)を自分で書き換えてもあまり面白みがない。何か公開されているコードは無いか。
40kBのNESゲームで、NESASM向けのソースが手に入り、少なくとも個人的に書き換えて実行することが許可されているもの。
…と探すとよさそうなものが見つかった。
「Lan Master」。Shiru氏によるシンプルなパズルゲーム。
「The game and its source code are released into Public Domain」とあり、心置きなく改変が可能だ。
http://shiru.untergrund.net/software.shtml#nes
ソースを見るとファイルが多くてちょっと尻込みしたが、orgの指定されている箇所はメインのgame.asmのみ。
8000,a000,c000,e000とfffa(とCHR側)があるので、それぞれ-2000する。
CHR部分は1ファイルにまとまったものを.incbinしている。これは不要だ。コメントアウトする。
変数定義を見ると100~103は使っていないようだ。
しかし初期化の部分でゼロクリアしている。
$0100~$01FFの部分のクリアをやめる。
一応104~1FFはゼロクリアしておいた方がいいかとも思ったがまあただのスタックだろうし不要だろうと思いそのまま。
動いた。

せっかくなのでクリアまで遊んだ。なかなか面白いゲームだ。
するとエンディングの文字がどうもおかしい。PPUビューアで見るとミラーリングが間違っているせいで横スクロールなのに横1画面分に書いてしまっており書き換えが画面内に見えている。

ここで初めてミラーリング設定をしていなかったことに気づく。ゲーム画面は1画面だから正常に見えていたんだな。危なかった。
というわけで先述のミラーリング設定をすると、

今度は正常。
最終的なdiffはこんな感じ。
12c12
< .org $8000
---
> .org $6000 ;$8000
166c166
< sta $100,x
---
> ;sta $100,x
174a175,177
>
> lda #$22 ;V mirror
> sta $4025
1942c1945
< .org $a000
---
> .org $8000 ;$a000
1949c1952
< .org $c000
---
> .org $a000; $c000
1955c1958
< .org $e000
---
> .org $c000; $e000
2086c2089
< .org $fffa
---
> .org $dffa ;$fffa
2092,2094c2095,2110
< .bank 4
< .org $0000
< .incbin "patterns.chr"
---
> ; .bank 4
> ; .org $0000
> ; .incbin "patterns.chr"
< .org $8000
---
> .org $6000 ;$8000
166c166
< sta $100,x
---
> ;sta $100,x
174a175,177
>
> lda #$22 ;V mirror
> sta $4025
1942c1945
< .org $a000
---
> .org $8000 ;$a000
1949c1952
< .org $c000
---
> .org $a000; $c000
1955c1958
< .org $e000
---
> .org $c000; $e000
2086c2089
< .org $fffa
---
> .org $dffa ;$fffa
2092,2094c2095,2110
< .bank 4
< .org $0000
< .incbin "patterns.chr"
---
> ; .bank 4
> ; .org $0000
> ; .incbin "patterns.chr"
2023年05月04日 09:25

先日、「Nintendo Switch Online 加入者限定 ファミリーコンピュータ コントローラー」を買った。
これは名目上「ファミリーコンピュータ Nintendo Switch Online」をプレイするための専用コントローラであり、公式ページにも
※Nintendo Switch 本体付属のJoy-Conよりもボタンが少ないため、『ファミリーコンピュータ Nintendo Switch Online』以外のソフトを遊ぶことはできません。とはっきり書かれている。
しかし、他のソフトを遊べない理由は「ボタンが少ないため」だけであり、ゲームが起動しないようなことはなく、ボタンさえ足りればプレイ可能である。
これはそのへんの記事で事前に分かっていた。例えばファミ通のこれ。
2コンのマイクが使える!? Nintendo Switch“ファミコンコントローラー”の各種機能や使えるタイトルなどを実機レビュー
せっかくなのでどのゲームが遊べるか、手持ちのゲーム(体験版含む)を片っ端から試してみよう。
まず、このコントローラのキーだが、ファミコンのボタンに加え、本体への接続部はJoyConと同一なので、LRボタンも付いている。
キー割り当てはStart/Selectが+/-に対応する他は名前通り。非常に残念なことに、本体機能でのキーコンフィグには対応していない。
LRは単体では押しづらいがJoyConストラップがそのまま使えるので付ければだいぶ使いやすくなる。
ただJoyConと違って+/-の目印がないため、(本体への)装着の向きを示す▶▶▶マークと逆向きに装着することになり向きを間違えやすいので注意が必要だ。
また、本体左右に装着した場合のキー割り当ても参考に示そう。
左(I)
十字: 十字
St: +
Se: -
A: (反応なし)
B: キャプチャ
右(II)
A: A
B: B
十字: (4方向すべて)Home
LRが無い分単独に劣り、追加のボタンはゲーム外の機能であるHomeとキャプチャができるだけで、勝る点は無い。この使用法は考えなくてよいだろう。
以下、ゲームごとの評価をしていく。
評価はあまり厳密なものではないので参考程度に見てほしいが、一応意味合いとしては次のようなイメージだ。
良好: 重要な操作が全て賄え、全編通して普通のプレイが可能。
微妙: 重要な操作に不可能なものがあり、プレイの幅は狭まるが、普通のプレイが楽しめる場面も多い。
困難: 普通のプレイに必須の操作が不可能だが、普通でないプレイで楽しめる場面はあるかもしれない。
不可: プレイに必須の操作が不可能で、ゲームを楽しむことはまず無理。
また、
・主要なゲームモードが複数あってモードによって評価が異なる場合はそれぞれ評価する。
・ゲーム中ほとんど触る必要のないメニュー操作は評価とは別扱いとする。(例えばファミコンコントローラにないボタンを押さないとゲームを開始できない場合でも開始してからの操作ができれば高評価)
並び順はなんとなく、「問題なくプレイできる」または「曲がりなりにもプレイできることが意外」なものから順に。
マリオカート8DX
著名な3Dレースゲーム。ドリフトとアイテムが特徴。【良好】
一番意外性があったのがこれ。(まあ事前に上記記事で分かっていたが)
2Dゲームは多くが十字キーに対応しておりボタン数が足りれば操作可能なものは多いのに対し、3Dのゲームは方向指示操作がアナログスティック限定なことが多く、プレイ可能なものはかなり珍しい。
またキーコンフィグもないのに奇跡的にボタンが配置されている。
十字: 移動
A: アクセル
B: ブレーキ
L: アイテム使用
R: ドリフト
使えない機能
Xの視点変更は使えないがまあ無くてもそんなに困らない。
メニュー操作は若干問題があり、マップ表示・ジャイロ操作のOn/Offができなかったり、タイムアタックで150/200ccの切り替えができなかったりする。
なおプレイ中にポーズを掛けることをしなければIIコンでも操作可能である。
横並びのABでAを押しながらBや、LRが若干押しづらく指が疲れるが、一応プレイは可能な程度の操作性。
例えば200ccのスペシャルカップを全1位でクリアできた。
スーパーマリオ3Dコレクション
3Dマリオ3作のオムニバス。【困難・不可・不可】
マリオサンシャイン・マリオギャラクシーは不可。例に漏れず移動操作が左スティックのみ。
意外なことに64のみ十字キーで操作が可能。マリオ64DSで十字キー操作があった関係だろうか。
ただしボタンが非常に残念な割付け。
十字: 移動
A,B: ジャンプ
L,R: カメラ切り替え
キー数はあるのにしゃがみも攻撃もできない。
キーコンフィグさえあればLをZ、BをBで視点変更以外問題なくプレイできたものを。
しかし十字キーで操作可能なのと、いちばん重要なジャンプだけは使えることから、何気にそこそこ遊べる。
Aボタン縛りプレイはなかなかマイナーな縛りプレイだが、最初のスターを取るのがバグ技を使わなければ不可能である点とクッパが倒せない点を除けば取得できるスターはけっこうあり、なかなか楽しいプレイである。(逆のAボタンのみ使用不可の縛りプレイはA Button Challengeと呼ばれ有名だ)
加えて十字キー操作で8方向最高速の移動しかできないのがいい感じに難易度を上げ、一味違った縛りプレイが楽しめる。Nintendo64(というかマリオ64?)の開発時に3D空間を自在に移動するにはアナログスティックが必須だと分かったという話があった気がするが、それを実感する。
冒頭の写真はファミコンコントローラでチックタックロックのえっへんてっぺんドッスンを目指してスター目前で死んで心が折れた時のもの…の録画を流しながらコントローラを持ってあたかもプレイ中のように装っているものだ。プレイしながら写真を撮るのはだいぶ無理があった。
3DスティックではAボタン縛りでもさほど難なくクリアできる場所なので、十字キーの難しさが出た。
Vertical Strike Endless Challenge
戦闘機で敵を倒す3Dシューティングゲーム。【微妙】
圧倒的自由度のキーコンフィグ。
方向操作を十字キーに割り当てられるし、各種操作はStart/Selectにも割り当て可能。
使える操作が多いので6ボタンでは足りないが、上手く選べばそこそこプレイ可能。
ファルコニア ウォーリア エディション
鳥に乗って空を飛んで敵を倒す3Dシューティングゲーム。【微妙】
キーコンフィグあり。方向操作を十字キーに割り当て可能。
しかしプレイに必要な操作が多く、Start/Selectに割り当てられないので割り当てられるABLRではちょっと足りなそう。
プレイ中にキーコンフィグを変えることはできるので、なんとかプレイ可能かもしれない。
Aery - Beyond Time(ザフライーングトラベラージャーニービヨンドタイム)
なんかいい感じの風景の中をただ飛ぶ3Dゲーム。【良好】
ゲームプレイ自体は3Dゲームには珍しく十字キーが効き、LRと合わせて普通に操作可能。
にもかかわらずなんとメニューの項目移動がスティックのみという変わったUI。
アクセス不可
スイッチや表示機のついた機械を3Dでいじりまわす謎解きパズル。【微妙】
大抵の面では右スティックで回して様々な方向から見る必要があるが、LRで横回転しかできない。XYで拡大縮小もできない。
普段は拡大して操作する場面で非常に細かい操作が必要とされ、かなり厳しいものがある。
クリア可能な面はある程度ありそう…というかもしかすると全部可能かもしれないが、使える操作をすべて使って謎解きをするゲームなので、可能な操作だけではクリアできないかもしれない状態でプレイするのはゲームにならない。(答えを知っていてプレイするのもゲームにならない)
海腹川背Fresh
ゴム紐を地形に引っ掛けてステージを跳び回るゲーム。【良好・微妙】
キーコンフィグ: +-以外ボタン自由
デフォルトではBジャンプ・A特殊能力で、ルアーがLRで斜め上にしか打てない。
変な縛りプレイをするのでなければキーコンフィグは必須。ABをジャンプ・ルアーに割り当てれば普通にプレイ可能。ゴールはデフォルトはXボタンだが、上入力でもゴールにできるようにできる。
使用頻度の低い斜め下ルアーは諦めよう。3DSやVitaには無かったのだし。
特殊能力の無い川背さんはそれでよいのだが、特殊能力ありのキャラを使う場合はボタンが足りない。StartはメニューとしてもSelectは余っているのに割当て不可で残念。ジャンプやルアーを外すわけにもいかないし斜め上ルアーの片方を諦めるしかなく、これは微妙。まあスーファミには無かったのだし遊べなくはない。
Celeste
空中ダッシュしたり壁に貼り付いたりするパズル気味な死に覚えプラットフォーマー。【良好】
デフォルトはBがジャンプ、Aがダッシュとやや不便か。
キーコンフィグは充実も、そのキーコンフィグを使うためにはZLが必要。
MuseDash
レーンが2つと少ないのが特徴的な音ゲー。キャラが可愛い。【良好】
キーコンフィグ: 選択式。
上下2レーンに対しデフォルトの
ABXY,R / 十字,L の分割がファミコンでも使いやすい。
他に
LR / 他
AX左上LR / BY右下
AB左下 / XY右上LR
があり、ABが分かれたり十字キーが分かれたり、意外とファミコンでもそれぞれ利用価値のありそうなコンフィグかもしれない。
ゲーム中の操作としては、手動フィーバーモードの発動操作がZL/ZR固定なのが残念なところ。
まあ最高得点を目指すには必須とはいえ、普通のプレイには無くても困らない。
また、(標準の割り当てでは)下レーンに対して使いやすいのがABの2キーしか無く、3連打以上が困難である。
メニュー操作は十字ABStartで大体可能。XYはランキングを見る・リトライ・お気に入り追加といった細かい機能くらいで遊ぶぶんには問題な…かったのだが、いつからかアップデートで曲選択のUIが変わってから、曲のグループ選択がスティック操作のみになった。バグに近い仕様変更。
そしてその後のアップデートで直った。バグだった模様。
SuperCableBoy
ワイヤーアクションや空中ジャンプなどモード切り替えするプラットフォーマー。【良好】
キーコンフィグあり。
ジャンプ・能力使用・能力切り替え(ポーズして選択式)・能力切り替え(順送り)・能力切り替え(逆送り)・移動・カメラ操作
を自由割り当て、ポーズは+/-固定。
カメラ操作は諦め、ジャンプ・カートリッジ使用・切り替え左右をABLRに割り付けるのがよいだろう。試しにノーマルエンディングまでクリアしてみたが十分にプレイ可能である。カメラ操作で先を見ると少し有利なことがたまにあるが、そこまで有利でもないしそこまで頻繁にでもない。
キーコンフィグでは+/-(Start/Select)ボタンにも機能が割り当てられるものの、ゲーム中のポーズと干渉する。
例えばカートリッジ切り替えに割り当てると、カートリッジ切り替えUIが出つつゲームはポーズがかかる。ポーズを解除するとカートリッジ切り替えができる。これがなければカメラ操作以外完全だったのに。
…と思っていたのだが再度試すと+/-はポーズが掛かるだけで機能が発動しなかった(キーコンフィグで割り当てはできるのに)。なんだろう、バージョンで動作が変わったか?
ケイデンス・オブ・ハイラル: クリプト・オブ・ネクロダンサー feat. ゼルダの伝説
リズムに乗ってダンジョン進むやつfeat.ゼルダ【良好】
基本の攻撃操作が「敵の方へ移動」。LRは固定の特殊行動。ABXYは扱いは同等で、アイテムを割り付けて使う。
同時に装備しておけるアイテムが4つから2つになるだけで問題なくプレイ可能。
ガブッチ
足場を食べるアクションパズルゲーム。【良好】
使うボタン:十字, A=B, L=R, +, -(設定により=X)
珍しく+(メニュー)/-(リトライ)まで使い、すべてファミコンコントローラでまかなえる。
なお-はノーマルコントローラでは押しづらいので設定でXにも割り付けられる。
Sega Ages スペースハリアー
昔の非ポリゴンの3D奥スクロールシューティング。【良好】
スタートしてからは問題なくプレイ可能。
ただしメニューには問題があり、キーコンフィグはあるのだが、コイン投入およびスタートがX固定。
ピクロスSシリーズ
ピクロス。【良好】
ABで塗り・×付けは可能。マークは使えない。LでナビゲーションOn/Off、Rでカウント。
と、白黒ピクロスに関しては普通に良好。カラーピクロスでは操作方法が複数選べる中LRで色を選ぶ1種のみ利用可能でちょっと微妙か。まあでも1種とはいえ使えるので評価を落とすほどではない。
けものフレンズピクロス
ピクロス。【良好】
ピクロスSシリーズと同じ。
カラーピクロスは無い。
初音ミクロジックペイントS
【良好】ピクロス(と同一のルールのパズル)。
基本操作は十字ABのみ。
しかしメニュー操作に問題があり、問題の3段階に分かれた「レベル」の変更操作がZL/ZRのみのため、メニューも操作しようとするならLv1の問題群しかプレイできない。
キュービック
四角い主人公が地形ブロックを食べ進むパズルゲーム。【微妙】
食べ操作はできるが、巻き戻しができない。ミスをしなければプレイ可能なものの、面倒。
キーコンフィグなし。
パックマン99
99人対戦パックマン。【微妙】
基本の操作は十字キーのみだが、
戦略・攻撃対象の選択が左右スティック・ABXYに割当てられるが、ABに割当てた2種のみしか使えない。
YouTube
YouTube。【良好】
十字ABで普通に操作可能。
Xで検索ができないがメニューから選択して可能。
再生中のシークも十字キーで可能。
TorqueL
ABXYの4ボタンで主人公の4面が伸びるゲーム。【困難】
ABの2面しか使えないとプレイは困難。
キーコンフィグは無いし、あったところで4面に対応していない配置ではプレイ困難。
マッド・ラット・デッド
音ゲー+プラットフォーマー。【困難】
キーコンフィグ無し。
ABXYがダッシュ・ジャンプ・溜め・落下の4行動に割り当てられており、ダッシュとジャンプしかできない。
たぶん進めない場面が出てくる。
リバーティア
オセロ式に地形を変化させて進むパズルゲーム。【良好】
移動とジャンプ・色変えだけの操作で問題なくプレイ可能。
スーパーファミコンNintendoSwitchOnline
【微妙】個別のゲームについては最後にまとめた。
ここでは全体について。
重要な点として、スーパーファミコンSwitchOnlineではファミコンコントローラのLRボタンはゲーム操作に使えない。
というのも、ファミリーコンピュータSwitchOnlineと同様、L+Rで中断メニュー/巻き戻し、Lがキャプチャ、Rがホームボタンに割り当てられているためだ。メニュー操作は問題ない代わりにゲーム内操作に不自由することになる。
ファミリーコンピュータSwitchOnline以外で使えないと言っておきながら、普通のコントローラと違う専用のキー割り当てを用意しているんだなあ。
ゲームボーイNintendoSwitchOnline
【微妙】こちらはスーファミとは異なり、特殊な割り当てはされていない。
つまり、L・Rボタンは何にも割り当てられておらず、中断メニュー/巻き戻しはZL+ZRなので使えない。
ゲーム内の操作は問題なくできるのに、実に勿体ない。
以下、不可。
プレイがほとんどあるいは全く不可能なゲーム
3Dのゲームでは先述の通り十字キーで移動ができないパターンが多い。
ハコボーイ&ハコガール
主人公が体から箱を出すアクションパズルゲーム。【不可】
移動はできるが必須動作のハコ出しがY、ハコ消しがX。キーコンフィグ無し。
NeonWall
玉や壁の色を変えてなんやかんやするアクションパズルゲーム。【不可】
ジャイロまたは両スティックでポイントする操作が必要。
スマブラSP
スティックの弾き操作が特徴的な格闘ゲーム。【不可】
試合開始までは操作できるが、試合中の移動操作が左スティックのみ。キーコンフィグはあるが移動は十字キーにはできない。
Wiiの時はWiiリモコンでの操作ができたのに残念だ。
一応、攻撃・必殺・シールド・ジャンプ・つかみ(・アピール)はキーコンフィグで自由にできるので、もしかするとすごい人なら何らかのプレイが可能かもしれない。
MotoRushGT
リアルなグラフィックで昔ながらの敵車避けレースゲーム。【不可】
ハンドル操作がスティック(またはジャイロ操作)のみで十字キー不可。
十字キーでメニューは操作でき、ABまたはLRで加減速はできる。
マリオオデッセイ
3Dマリオ。【不可】
移動操作が左スティックのみ。
十字キーにはマップやスナップショットモードなどの機能。
また、ABともにジャンプ。
キーコンフィグ無し。
ポケモンソード
ポケモン。【不可】
十字キーで移動できない
メトロイドドレッド
メトロイドヴァニア。【不可】
十字キーで移動できない。(マップ切り替え)
チョコボGP
FFキャラのレースゲーム。【不可】
十字キーで移動できない。
パイロットスポーツ
パイロットウィングスっぽい飛ぶゲーム。【不可】
十字キーで移動できない
星のカービィ ディスカバリー
ほぼ初の3Dになったカービィ最新作。【不可】
十字キーで移動できない
いっしょにチョキッと スニッパーズ プラス
2人のキャラが互いの体を切って形を変えるパズルゲーム。【不可】
十字キーで移動できない
HUE
世界の色を変えて地形やブロックを消したり出したりするパズルゲーム。【不可】
キーコンフィグはあるのに、必須操作の色を選ぶ操作が右or左スティックのみ。
以下試すまでもなく諦めたやつ
リングフィットアドベンチャー
加速度/ジャイロセンサーが必須Splatoon2および3
十字キーに移動以外の機能が割り当てられているため、移動不可ゼルダ無双厄災の黙示録
同上ゼルダBotW
同上イモータルズフィニクスライジング
同上NintendoLabo 01,04
加速度/ジャイロ・IR・振動あたりが必須ヒューマンリソースマシーン
操作がジャイロ(or画面タッチ)セブンビリオンヒューマンズ
同上Jump Rope Challenge
加速度/ジャイロ必須タイピングクエスト
キーボード専用スーパーファミコンNintendoSwitchOnlineのゲームの個別評価
多いので全部は試していない。ワイルドトラックス【困難】Bアクセル、Aバック。LR急旋回、YブーストX背伸び。一応移動はできるが遅いし曲がりづらい。
スーパーメトロイド【微妙】キーコンフィグあり、Start以外自由。ショット・ジャンプ・ダッシュ・アイテムセレクト・アイテムキャンセル・ななめ上射ち・ななめ下射ち。ショット・ジャンプ・ダッシュ・アイテムセレクトまでは必須で、ボタン足りない。
また、装備の切り替えが必須だがこれにメニュー中にRが必要。
すーぱーぷよぷよ通【良好】ABでぷよ左右回転。キーコンフィグもあり。
マリオのスーパーピクロス【良好】
スターフォックス【困難】キーコンフィグは選択式。AボムBブラスター、減速・ブースト・ローリングが不可
ゼルダの伝説【困難】Yでアイテム使用
スーパーマリオカート【困難】Aアイテム使用とBアクセル。LRドリフトYブレーキが不可。走れるだけマシだがドリフトなしでは遅い。
スーパーマリオワールド【困難】AスピンジャンプBジャンプ、Yダッシュが不可が痛い
ヨッシーアイランド【困難】Aタマゴ投げBジャンプY舌Rタマゴカーソルロック。Y舌不可が痛い
パネルでポン【良好・微妙】ABパネル入れ替え、LRパネル上げ。XYがパズルでのキャンセルなのでパズルモードは微妙。
スーパーマリオコレクション【良好】キーコンフィグ選択式、デフォルトはXYダッシュABジャンプだが、XYBダッシュAジャンプに切り替えれば問題なし。
スーパードンキーコング【困難】A乗り捨てBジャンプY攻撃。
カービィボウル【微妙】フィールドを見渡せない。LRで方向を45度単位で切り替えもできない。
2020年10月21日 21:49
今は別館のサイトの方に昔(2007年)書いた内容を、手直ししたり新たな考察を加えたりしたもの。
元のページはこちらだが、文字コードが指定されていないのでChromeなどの低機能なブラウザでは文字化けして見えないかもしれない。Firefoxをおすすめする。
むかーし、むかし。電子手帳というものがあったそうな。
今でもあるけど、今は「ぴーでーえー」なんて横文字で呼んだりして、過去の「電子手帳」という呼び名の方がカッコよくて自分は好きだ。大体皆PDAが何の略か知らないだろう。なにせ自分も知らないのだ。
(↑と当時書いたのだが、今はPDAも携帯電話に吸収されて無くなってしまった。諸行無常。)
その電子手帳が「ちょっと高価な子供のおもちゃ」になりだした頃の話。
思い出深い電子手帳を紹介しよう。カシオから発売されていた電子手帳だ。
「スーパー電子手帳」シリーズと、何かわからないがたぶん何か違う「スーパー電子手帳Jr.」シリーズ。
男児向け女児向けで様々な種類があり、「PKバトルリーグ」「ツインゴール」「ペットテレパシー」などある。
機能は定番の電話帳・カレンダー(スケジュール帳)・電卓。それに子供向けだから占いやらゲーム。
この電子手帳は7×7ドットのひらがなが4行×12文字表示できる。つまり普通に考えて縦32ドット×横96ドットだ。

ほとんどの表示は8×8単位(漢字は16×16)なのだが、例外がカレンダー。

こんな感じに3×5の数字を使っている。3×5は数字をまともに描ける最小のドット数と言ってよいだろう。
日曜祝日は白黒反転、数字横の点はスケジュールがあることを示す。
ここで疑問に思うことはないだろうか?
そう、カレンダーの最大行数は6行。数字は縦5ドット。つまり5*6+隙間5ドットで35ドットとなり、32ドットの画面をオーバーしてしまう。

それでどうなっているかというと簡単なことで、その分のドットがきちんと用意されている。
しかし極限まで無駄を切り詰めているので、なかなかけったいな形になっており、

こうである。
もう惚れちゃいますね。
ここに、

こうなるわけだ。
ところで、これでは日付の表示に画面全部を使ってしまい、曜日が表示できない。
分かりづらくないかって?
大丈夫。

本体の方に書いてあるから。
さて、ここからが新たな発見である。
この電子手帳、パッケージで画面の総ドット数を誇っているのを過去に見た覚えがある。
探すとこちらに発売元はツクダオリジナルだが見たところ同シリーズの「似顔絵電子手帳カラーリスト」のパッケージが見つかった。

「3118ドット」とある。
数えてみよう。メイン部が縦32×横96、加えてはみ出し部分が(4×9+3)が2個で、3150ドット。
おや、計算が合わない。差は3150-3118=32。32? 縦のドット数と同じだ。
まさか…と別に見つけた写真で数えてみると、確かに横は95ドットしかない。そこも切り詰めていたか。
しかし32ドット減らすと何が嬉しいのだろう。
切りの良い32×96=3072ドットには足りないし、よくあるアイコンのためにコモン線が1本多いタイプで33×96=3168なら32ドット減らさなくても足りる(ドットマトリクス外にアイコンがあるが見たところ16個であり、足りる)。
しかし、考えてみると追加のドットはすべて個別に制御する必要があるわけではない。
表示する図形は30・31の下の方と右の点だけ。
右の数字部は「31」しか表示しないので、字と地の2セグメント。左も、調べてみると「1」の字形は上で想像で書いたようなセリフ付きのものではなくデジタル数字の直線の「1」であったので、0と1を表示するには2セグメントで、地を合わせた3セグメントで済む。4つの点も足して合計わずか9セグメントで足りる。
実際には隣り合っていないドットを1つのセグメントにまとめることはできないので多少多くなるが、下図の左9セグメント右7セグメントでいけそうだ。

このセグメントを制御する配線を考えてみよう。
…と、考えてみたら普通にはできそうにない。
何が問題かというと、この囲まれたセグメント。

ドットマトリクス部から横を1ドット削ることで使えるようになるセグメントは、1seg×32com分。(マトリックスの縦横のコモン線・セグメント線と個々のセグメントが紛らわしいので前者をcom・segと書く)
つまりこれだけではマトリックスにすることはできず、セグメント側を全体を1seg(使い方としてはコモン)に割り当ててcom側で制御するしかない。
これではセグメントの隙間を通しでもしない限り他のセグメントに囲まれたセグメントを個別に制御することができない。
これがcomがもう1本あればseg線を上から伸ばしてできるのだが…

もしくはsegが2本あれば2×Nのマトリックスにできてどうにかなるのだが…
と思ったが、よく考えたらそんなことをしなくてもできた!
囲まれたセグメントは1個だけ。1本だけならcom線も追加ドットに隣り合った最下段の1本を上から伸ばすことができる。

周囲のアイコン含め、配線にも問題がない。

このパターンだとうまく説明がつくことがある。
実物の表示は、YouTubeに動画があり、分かったのだが、

これを見ると、最下段でない日曜日には下に枠が付いていることが分かる。
最下段の数字が白黒反転した時は、ドット数からして下に枠がつかず、不自然である。つまり、下もう1列のドットは付けたくても付けられないのだと考えられる。
comがもう1本あって1com×複数segのパターンであれば、下もう1列は容易に追加できる。

また、追加のドットを下ではなく上に付ければ、白黒反転時に端が切れるのが「30」「31」から「1」「2」になり、日曜を含まず休日表示が起こりにくい点、文字数が少ない点からこちらの方が好ましいと考えられるが、1と2を表示できるパターンは囲まれるセグメントが多く、この作戦は不可能である。0と1の下4ドットだからできたパターンなのだ。
1com×複数segのパターンであれば上に1と2のセグメントも可能だ。

もうひとつ、「1」の字形がセリフ付きでなく縦棒なのも、セリフ付きの形は不可能だったためとも考えられる。縦棒でも特におかしいわけではないが、3×5ドットの数字では1に上下や上だけにセリフを付ける方がよく見る気がする。
なお、segが2本ある場合では、下もう1列が不可、1と2のパターンが不可は同様だが、1に上だけならセリフを付けることができる。
実機は確認していないので合っているかはわからないが、今のところ観察結果を上手く説明できており、なかなか可能性が高いのではないかなと思っている。
元のページはこちらだが、文字コードが指定されていないのでChromeなどの低機能なブラウザでは文字化けして見えないかもしれない。Firefoxをおすすめする。
むかーし、むかし。電子手帳というものがあったそうな。
今でもあるけど、今は「ぴーでーえー」なんて横文字で呼んだりして、過去の「電子手帳」という呼び名の方がカッコよくて自分は好きだ。大体皆PDAが何の略か知らないだろう。なにせ自分も知らないのだ。
(↑と当時書いたのだが、今はPDAも携帯電話に吸収されて無くなってしまった。諸行無常。)
その電子手帳が「ちょっと高価な子供のおもちゃ」になりだした頃の話。
思い出深い電子手帳を紹介しよう。カシオから発売されていた電子手帳だ。
「スーパー電子手帳」シリーズと、何かわからないがたぶん何か違う「スーパー電子手帳Jr.」シリーズ。
男児向け女児向けで様々な種類があり、「PKバトルリーグ」「ツインゴール」「ペットテレパシー」などある。
機能は定番の電話帳・カレンダー(スケジュール帳)・電卓。それに子供向けだから占いやらゲーム。
この電子手帳は7×7ドットのひらがなが4行×12文字表示できる。つまり普通に考えて縦32ドット×横96ドットだ。

ほとんどの表示は8×8単位(漢字は16×16)なのだが、例外がカレンダー。

こんな感じに3×5の数字を使っている。3×5は数字をまともに描ける最小のドット数と言ってよいだろう。
日曜祝日は白黒反転、数字横の点はスケジュールがあることを示す。
ここで疑問に思うことはないだろうか?
そう、カレンダーの最大行数は6行。数字は縦5ドット。つまり5*6+隙間5ドットで35ドットとなり、32ドットの画面をオーバーしてしまう。

それでどうなっているかというと簡単なことで、その分のドットがきちんと用意されている。
しかし極限まで無駄を切り詰めているので、なかなかけったいな形になっており、

こうである。
もう惚れちゃいますね。
ここに、

こうなるわけだ。
ところで、これでは日付の表示に画面全部を使ってしまい、曜日が表示できない。
分かりづらくないかって?
大丈夫。

本体の方に書いてあるから。
さて、ここからが新たな発見である。
この電子手帳、パッケージで画面の総ドット数を誇っているのを過去に見た覚えがある。
探すとこちらに発売元はツクダオリジナルだが見たところ同シリーズの「似顔絵電子手帳カラーリスト」のパッケージが見つかった。

「3118ドット」とある。
数えてみよう。メイン部が縦32×横96、加えてはみ出し部分が(4×9+3)が2個で、3150ドット。
おや、計算が合わない。差は3150-3118=32。32? 縦のドット数と同じだ。
まさか…と別に見つけた写真で数えてみると、確かに横は95ドットしかない。そこも切り詰めていたか。
しかし32ドット減らすと何が嬉しいのだろう。
切りの良い32×96=3072ドットには足りないし、よくあるアイコンのためにコモン線が1本多いタイプで33×96=3168なら32ドット減らさなくても足りる(ドットマトリクス外にアイコンがあるが見たところ16個であり、足りる)。
しかし、考えてみると追加のドットはすべて個別に制御する必要があるわけではない。
表示する図形は30・31の下の方と右の点だけ。
右の数字部は「31」しか表示しないので、字と地の2セグメント。左も、調べてみると「1」の字形は上で想像で書いたようなセリフ付きのものではなくデジタル数字の直線の「1」であったので、0と1を表示するには2セグメントで、地を合わせた3セグメントで済む。4つの点も足して合計わずか9セグメントで足りる。
実際には隣り合っていないドットを1つのセグメントにまとめることはできないので多少多くなるが、下図の左9セグメント右7セグメントでいけそうだ。

このセグメントを制御する配線を考えてみよう。
…と、考えてみたら普通にはできそうにない。
何が問題かというと、この囲まれたセグメント。

ドットマトリクス部から横を1ドット削ることで使えるようになるセグメントは、1seg×32com分。(マトリックスの縦横のコモン線・セグメント線と個々のセグメントが紛らわしいので前者をcom・segと書く)
つまりこれだけではマトリックスにすることはできず、セグメント側を全体を1seg(使い方としてはコモン)に割り当ててcom側で制御するしかない。
これではセグメントの隙間を通しでもしない限り他のセグメントに囲まれたセグメントを個別に制御することができない。
これがcomがもう1本あればseg線を上から伸ばしてできるのだが…

もしくはsegが2本あれば2×Nのマトリックスにできてどうにかなるのだが…
と思ったが、よく考えたらそんなことをしなくてもできた!
囲まれたセグメントは1個だけ。1本だけならcom線も追加ドットに隣り合った最下段の1本を上から伸ばすことができる。

周囲のアイコン含め、配線にも問題がない。

このパターンだとうまく説明がつくことがある。
実物の表示は、YouTubeに動画があり、分かったのだが、

これを見ると、最下段でない日曜日には下に枠が付いていることが分かる。
最下段の数字が白黒反転した時は、ドット数からして下に枠がつかず、不自然である。つまり、下もう1列のドットは付けたくても付けられないのだと考えられる。
comがもう1本あって1com×複数segのパターンであれば、下もう1列は容易に追加できる。

また、追加のドットを下ではなく上に付ければ、白黒反転時に端が切れるのが「30」「31」から「1」「2」になり、日曜を含まず休日表示が起こりにくい点、文字数が少ない点からこちらの方が好ましいと考えられるが、1と2を表示できるパターンは囲まれるセグメントが多く、この作戦は不可能である。0と1の下4ドットだからできたパターンなのだ。
1com×複数segのパターンであれば上に1と2のセグメントも可能だ。

もうひとつ、「1」の字形がセリフ付きでなく縦棒なのも、セリフ付きの形は不可能だったためとも考えられる。縦棒でも特におかしいわけではないが、3×5ドットの数字では1に上下や上だけにセリフを付ける方がよく見る気がする。
なお、segが2本ある場合では、下もう1列が不可、1と2のパターンが不可は同様だが、1に上だけならセリフを付けることができる。
実機は確認していないので合っているかはわからないが、今のところ観察結果を上手く説明できており、なかなか可能性が高いのではないかなと思っている。
2020年03月10日 09:37
スーパーマリオシリーズの代表的なアイテムにスーパーキノコ(や1UPキノコ)がある。
今は左のようなデザインで定着しているが初代スーパーマリオブラザーズでは右のデザインだった。

初めて初代のキノコを見たときは何だこりゃと思った。
変わりすぎだろ何があった。
…と、ずっと思っていた。
先日マリオ2を初めてプレイして知ったのだが、まず顔が付いたのはマリオ2だったんだな。

マリオコレクション(今風のデザイン)やマリオDXのおまけ(初代のデザイン)でしかマリオ2は見たことがなかったので知らなかった。
スーパーマリオシリーズの様々な物に顔が付いていることについてはこのように言われている。
ただちょっと不思議なのは、初代の時からパッケージイラストにはマリオの手に持つスーパーキノコと思しきキノコにしっかりと顔が書かれていることだ。地面の花をも差し置いて。

スターやジュゲムの雲には初代から顔があるし、これは物には顔を描くというスタイルは初代からあったのを2で徹底したということか。
にしても。

顔以外も2から3で変わりすぎじゃないか。
…と思っていた。
実はそうでもなかったのである。
まず、実は上の画像には1つ嘘がある。
マリオ1・2の斑点は橙~茶色・マリオ3の斑点は赤だと認識していたのだが(そして実際上の画像もそうなっているが)、斑点の色はどれも同じである。ついでにマリオの服や赤ノコノコの甲羅、スーパー木の葉も同じ色である。

今回キノコの色について調べていて初めて気づいた。
スーパーファミコン以降の真っ赤になった色のイメージや、空の色やキノコの地の色の違いで誤認していたものと思われる。
そして同じ条件で撮られた別のソフトの画像はあまり存在しないことから、気づけなかったのだろう。
実際、先の画像はMarioWikiから取ってきたのだが、見ての通り色は一定していなかった。ファミコンの色はかくも曖昧なものである。
次に地の色が白になった事について。これはアートスタイルの変化に対するハードの制限である。
マリオ3ではキャラクターの絵に(大半は黒の)縁取りが付くようになった。
これにより、縁に1色とられることとなり、残り2色でキノコを描くことになった。
地の黄色を残すか柄(え)の白を残すかの選択になるが、1UPキノコとの統一や他のキャラクターとの兼ね合いを考えると、白にするのが自然だろう。
ちなみにマリオ3の通常の(地上面での)スプライトのパレットは
・プレイヤーキャラ用(色はパワーアップなどで変わる。標準マリオはベージュ/赤茶/黒)
・白/赤茶/黒 (スーパーキノコ、スーパー木の葉、赤ノコノコの甲羅、パタクリボーなど)
・白/緑/黒 (1UPキノコ、緑ノコノコの甲羅、蔓/葉など)
・ベージュ/黄土/黒 (クリボー、ノコノコの顔など)
の4種である。
とはいえ縁取りを付けただけで突然3のデザインになるわけでもなかろう。…と思っていた。
試しに2のキノコに縁取りだけ付けてみよう。
ところで、縁取りの付け方には4連結と8連結がある。

不思議なことに、マリオ3ではほとんどのキャラクターのほとんどの部分は8連結で縁取りされているのだが、キノコは4連結である。
キノコだけ最初に描かれたとか、デザイナーが別とかだろうか。何にせよ4連結で描くことにする。
まずは外形をそのままにして描くのが自然だろう。

すると、妙に痩せた印象になってしまった。縁取りが無ければ物と背景との境が外形だと認識するのに対し、縁取りをすると縁の1pxの中央を外形と認識するからだな。
1pxだけ広げよう。

さて、ファミコンにはスプライトの左右反転機能があるので、左右対称な物体は必要なタイル数が半分で済む。
タイル数は節約したいので、左右対称のデザインにしよう。
とりあえずそのまま左右対称にしてみると、

左はあからさまに2つの大きな斑点が並んでしまって不自然だ。右の方が望みがありそうだな。
くっついてしまった上の斑点を切って、中央のスペースが不自然に空いているので、消えてしまった左のものくらいの大きさの斑点を1個置く。

ほぼマリオ3のキノコになってしまった!
比較してみよう。

特に上部の丸みが完全に一致しているのと、斑点の形の大部分が一致しているあたりがポイントが高い。
縁取りを付けてからここまで制限に従って不自然な部分を直していっただけだ。
ここまできたら後は笠を1px下に下ろし、柄を1px太らせ、斑点を自然に調整するだけ。その程度の変化には特に不思議はない。
マリオはハードの制限から生まれたデザインというのは有名だが、キノコもそうだったのだなあ。
次作のワールドで色数やスプライト数の制限が無くなったにも関わらず3のデザインを(地と斑点の色は反転したが)踏襲、更にこの時左右非対称になったのがその後左右対称に戻り、一番制限のきつかった3のデザインに固定されたというのがなかなか面白いところだ。
今は左のようなデザインで定着しているが初代スーパーマリオブラザーズでは右のデザインだった。

初めて初代のキノコを見たときは何だこりゃと思った。
変わりすぎだろ何があった。
…と、ずっと思っていた。
先日マリオ2を初めてプレイして知ったのだが、まず顔が付いたのはマリオ2だったんだな。

マリオコレクション(今風のデザイン)やマリオDXのおまけ(初代のデザイン)でしかマリオ2は見たことがなかったので知らなかった。
スーパーマリオシリーズの様々な物に顔が付いていることについてはこのように言われている。
雲や草には顔があるように見えるが、これは立体感を出すために書き込まれた線が、顔に見えていただけだった。https://www26.atwiki.jp/gcmatome/pages/3410.html
しかし、これが「怖い」と言われたことから、次回作の『2』では、背景の雲や草にはっきりとした顔が書かれた。
ただちょっと不思議なのは、初代の時からパッケージイラストにはマリオの手に持つスーパーキノコと思しきキノコにしっかりと顔が書かれていることだ。地面の花をも差し置いて。

スターやジュゲムの雲には初代から顔があるし、これは物には顔を描くというスタイルは初代からあったのを2で徹底したということか。
にしても。

顔以外も2から3で変わりすぎじゃないか。
…と思っていた。
実はそうでもなかったのである。
まず、実は上の画像には1つ嘘がある。
マリオ1・2の斑点は橙~茶色・マリオ3の斑点は赤だと認識していたのだが(そして実際上の画像もそうなっているが)、斑点の色はどれも同じである。ついでにマリオの服や赤ノコノコの甲羅、スーパー木の葉も同じ色である。

今回キノコの色について調べていて初めて気づいた。
スーパーファミコン以降の真っ赤になった色のイメージや、空の色やキノコの地の色の違いで誤認していたものと思われる。
そして同じ条件で撮られた別のソフトの画像はあまり存在しないことから、気づけなかったのだろう。
実際、先の画像はMarioWikiから取ってきたのだが、見ての通り色は一定していなかった。ファミコンの色はかくも曖昧なものである。
次に地の色が白になった事について。これはアートスタイルの変化に対するハードの制限である。
マリオ3ではキャラクターの絵に(大半は黒の)縁取りが付くようになった。
これにより、縁に1色とられることとなり、残り2色でキノコを描くことになった。
地の黄色を残すか柄(え)の白を残すかの選択になるが、1UPキノコとの統一や他のキャラクターとの兼ね合いを考えると、白にするのが自然だろう。
ちなみにマリオ3の通常の(地上面での)スプライトのパレットは
・プレイヤーキャラ用(色はパワーアップなどで変わる。標準マリオはベージュ/赤茶/黒)
・白/赤茶/黒 (スーパーキノコ、スーパー木の葉、赤ノコノコの甲羅、パタクリボーなど)
・白/緑/黒 (1UPキノコ、緑ノコノコの甲羅、蔓/葉など)
・ベージュ/黄土/黒 (クリボー、ノコノコの顔など)
の4種である。
とはいえ縁取りを付けただけで突然3のデザインになるわけでもなかろう。…と思っていた。
試しに2のキノコに縁取りだけ付けてみよう。
ところで、縁取りの付け方には4連結と8連結がある。

不思議なことに、マリオ3ではほとんどのキャラクターのほとんどの部分は8連結で縁取りされているのだが、キノコは4連結である。
キノコだけ最初に描かれたとか、デザイナーが別とかだろうか。何にせよ4連結で描くことにする。
まずは外形をそのままにして描くのが自然だろう。

すると、妙に痩せた印象になってしまった。縁取りが無ければ物と背景との境が外形だと認識するのに対し、縁取りをすると縁の1pxの中央を外形と認識するからだな。
1pxだけ広げよう。

さて、ファミコンにはスプライトの左右反転機能があるので、左右対称な物体は必要なタイル数が半分で済む。
タイル数は節約したいので、左右対称のデザインにしよう。
とりあえずそのまま左右対称にしてみると、

左はあからさまに2つの大きな斑点が並んでしまって不自然だ。右の方が望みがありそうだな。
くっついてしまった上の斑点を切って、中央のスペースが不自然に空いているので、消えてしまった左のものくらいの大きさの斑点を1個置く。

ほぼマリオ3のキノコになってしまった!
比較してみよう。

特に上部の丸みが完全に一致しているのと、斑点の形の大部分が一致しているあたりがポイントが高い。
縁取りを付けてからここまで制限に従って不自然な部分を直していっただけだ。
ここまできたら後は笠を1px下に下ろし、柄を1px太らせ、斑点を自然に調整するだけ。その程度の変化には特に不思議はない。
マリオはハードの制限から生まれたデザインというのは有名だが、キノコもそうだったのだなあ。
次作のワールドで色数やスプライト数の制限が無くなったにも関わらず3のデザインを(地と斑点の色は反転したが)踏襲、更にこの時左右非対称になったのがその後左右対称に戻り、一番制限のきつかった3のデザインに固定されたというのがなかなか面白いところだ。
2019年04月08日 02:02
ファミコンあたりのゲーム機は画面を8×8ドットのタイルを並べて構成している。よって文字表示は8×8ドット(空白含む)にするとプログラムが簡単だ。
このサイズだとひらがなカタカナ(濁点・半濁点なし)にちょうどいいサイズである。英数字にはやや大きいがさほど不自然でもない。
問題は漢字表示である。
8×8ドットに漢字を描くのはかなりの無理がある。16×16ドットにすれば何不自由なく描けるが、このサイズの文字は大きすぎて画面内に書ける文章量が減る。
そこでその中間あたりのサイズの文字表示をやってみようと思い立った。
まず考えたのが12×12ドット(空白含む)だ。1.5マスで比較的扱いが楽そうだ。
ゲームボーイでプログラムを組んでみて、まあうまくいった。

なお後に知ったのだがゲームボーイで12×12ドットのフォントはいくつか例がある。その1つの「合格ボーイ」シリーズの「常識の書」がこちらだ。


さて、12ドット文字プログラムだが、意外と面倒な面が多かった。
1文字を18バイトで持っていたのだが、18は切りが悪い。縦横に空白をとると11×11ドットであり、11×11=121ビットというのは16バイトに収まる。せっかくだから収めたくなるがなかなか上手くいかない。
半角文字も用意したくなるが、横6ドットとなるとせっかくの1.5マスの切りの良さが無くなってしまう。
12×12ドットはわりと面積が広く、書き込みに意外と時間がかかる。
(なお先述の「常識の書」だが、英数字は全角の半分ではなく横8ドットにして切りの良さを保ち、フォントデータは中央4ドットを重複して持つことで表示を楽にしている。勉強になる)
12ドットでも面倒なら別のサイズでも似たようなものではないかという思いにいたり、別のサイズを模索してみることにした。
すると9×9ドット(空白含まず)がかなり楽そうだと分かった。
一見中途半端だが、9×9ドットはどのように配置しても8×8ドットのマス目の4マスにかかるという重要な性質がある。場合分けが不要というのは楽だ。
8bitCPUのレジスタは8bitだが、キャリーフラグをうまく使えば9bitを保持しておくことができる。
文字を9×9ドットとして1ドットの空白を設ければ10ドット。偶数なので奇数より多少楽だ。
そんなわけでゲームボーイで組んだ。

見ての通り表示できる文字数が増えるのも利点だ。漢字の読みやすさは12ドットには劣るものの、十分だろう。
その後ファミコンに移植してみた。
ファミコンでもカートリッジにメモリを積めばゲームボーイと同様にキャラクタを書き換えることができる。(CHR-RAMと呼ばれる)
(以前はあまりカートリッジ側に複雑なものを載せるのはレトロゲームプログラミングとして反則な気がして敬遠していたのだが、CHR-RAMを使っているソフトは普通にある感じなのでいいかと思い直した)

ゲームボーイでは1文字出力のコードしか書いていなかったのだが(上の画像は1文字出力関数を文字数分呼んでいるのだ)、ファミコン版でやっとテキストデータを呼んで文字列を表示するコードを書いた。
(もっとも現状文字コードが1バイトで文字を256種しか使えないのだが)
これの製作中に1つ思いついて実装したのが、フォントデータの一部をテキストデータに持たせる手法だ。
フォントデータは普通には9×9で81bit、10バイトと余り1bitになる。これを11バイトで持つのは無駄が多く気に食わない。
そこで、例えば文字コードの最上位bitを文字の右上のドットに対応するようにする。するとフォントデータとして持つのは残りの10バイトのみでよくなる。
文字コードの並びは不規則になってしまうが、文字列を表示するだけなら問題は無い。
問題が起こるのは、まず外部とのやり取りで既存の文字コードを使う場合。これはゲームではあまり考えなくてよいだろう。
それと、文字列をソートしたい場合。これはやや問題になるかもしれない。漢字はソートできなくてよいと考えて、仮名のみならテーブルを作るか、文字コードを上手く作れば仮名の順は取れるようにできるかもしれない。
あと文字の送り幅を変えるコードもやっつけで書いた。やっつけなので1文字あたり1バイトを余計に使う無駄なコードだが。文字データも10バイト使って無駄だ。
NESファイルは[こちら]。
コードは汚いので(まだ)公開しない。そのうち整えてCC-BY-NCあたりで公開するつもり。ゲームボーイからファミコンへの移植で変わったとことかも文章にまとめたい。
(2019/07/17追記)
実機動作していただいた!
…そして初期化忘れに気付かされる。
エミュレータはふつう初期状態のメモリは全ゼロになっているので初期化しなくても動いてしまい、実機で動作させて初めて気づくことが多い。PICマイコンでもしょっちゅうやらかした。
これをエミュレータでテストするのはなかなか面倒である。今回はテスト用に乱数生成(Xorshift)のコードを書いてまず最初にRAMをランダム値で埋めることにした。
それでできたのがこちらである。見事に再現できた。

さらに調べているとスタックが3バイト単位の繰り返しで全部埋まっていることにも気づいた。スタックオーバーフローだ。
コードを確認すると、初期化からVBlank割り込みルーチンに流れ込むわ、VBlank割り込みルーチンは最後リターンせずにVBlank待ちに入るわのめちゃくちゃなコードだった。よく動いていたな。
そんなわけで割り込みルーチンを直しメモリのゼロクリア処理を追加して、
確認してみると目的のノイズは消えたのだが、起動時に一瞬別のノイズが見えるようになったのが治らない。

どうやらランダム値をVRAMに書き込む際に出ているようで、表示は切ってあるはずなのに不思議である。
ランダム化を消せば消えると思われたが、理由が分からないのは不安なので調べることにした。
結果、案の定仕様を勘違いしていたことが分かった。PPUADDRレジスタがパレットのあるアドレスを「表示中に」指しているとそのパレットの色が出るという仕様があるのだが、これを非表示中に起こるものと思っていた。
https://wiki.nesdev.com/w/index.php/PPU_palettes#The_background_palette_hack
更に、この仕様に関連して、ファミコンは起動時の一瞬、PPUが操作可能になる前に(条件によって異なる)とある1色を表示するということも知った。そういえばそんなだった気がするな。PPUが操作可能になる前なのでどうしようもないらしい。
理由が分かったので安心してゼロクリア処理を消す。今まで確認に使っていてNintendulatorではノイズが見えなくなったが、再現度の高いらしいMesenでは本来のパレットへの書き込みの分まだわずかに残っている。VBlank中へ持っていけば消せるのかもしれないが面倒なのでひとまずこれでよしとする。
画像を載せようかと思ったが上のと全く同じなのでやめる。
上のNESファイルは更新した…はず。エミュレータで見ても表示は同一なので確認が難しい。
あと全画面任意画像の方にも同じバグがあったので合わせて修正した。
このサイズだとひらがなカタカナ(濁点・半濁点なし)にちょうどいいサイズである。英数字にはやや大きいがさほど不自然でもない。
問題は漢字表示である。
8×8ドットに漢字を描くのはかなりの無理がある。16×16ドットにすれば何不自由なく描けるが、このサイズの文字は大きすぎて画面内に書ける文章量が減る。
そこでその中間あたりのサイズの文字表示をやってみようと思い立った。
まず考えたのが12×12ドット(空白含む)だ。1.5マスで比較的扱いが楽そうだ。
ゲームボーイでプログラムを組んでみて、まあうまくいった。

なお後に知ったのだがゲームボーイで12×12ドットのフォントはいくつか例がある。その1つの「合格ボーイ」シリーズの「常識の書」がこちらだ。


さて、12ドット文字プログラムだが、意外と面倒な面が多かった。
1文字を18バイトで持っていたのだが、18は切りが悪い。縦横に空白をとると11×11ドットであり、11×11=121ビットというのは16バイトに収まる。せっかくだから収めたくなるがなかなか上手くいかない。
半角文字も用意したくなるが、横6ドットとなるとせっかくの1.5マスの切りの良さが無くなってしまう。
12×12ドットはわりと面積が広く、書き込みに意外と時間がかかる。
(なお先述の「常識の書」だが、英数字は全角の半分ではなく横8ドットにして切りの良さを保ち、フォントデータは中央4ドットを重複して持つことで表示を楽にしている。勉強になる)
12ドットでも面倒なら別のサイズでも似たようなものではないかという思いにいたり、別のサイズを模索してみることにした。
すると9×9ドット(空白含まず)がかなり楽そうだと分かった。
一見中途半端だが、9×9ドットはどのように配置しても8×8ドットのマス目の4マスにかかるという重要な性質がある。場合分けが不要というのは楽だ。
8bitCPUのレジスタは8bitだが、キャリーフラグをうまく使えば9bitを保持しておくことができる。
文字を9×9ドットとして1ドットの空白を設ければ10ドット。偶数なので奇数より多少楽だ。
そんなわけでゲームボーイで組んだ。

見ての通り表示できる文字数が増えるのも利点だ。漢字の読みやすさは12ドットには劣るものの、十分だろう。
その後ファミコンに移植してみた。
ファミコンでもカートリッジにメモリを積めばゲームボーイと同様にキャラクタを書き換えることができる。(CHR-RAMと呼ばれる)
(以前はあまりカートリッジ側に複雑なものを載せるのはレトロゲームプログラミングとして反則な気がして敬遠していたのだが、CHR-RAMを使っているソフトは普通にある感じなのでいいかと思い直した)

ゲームボーイでは1文字出力のコードしか書いていなかったのだが(上の画像は1文字出力関数を文字数分呼んでいるのだ)、ファミコン版でやっとテキストデータを呼んで文字列を表示するコードを書いた。
(もっとも現状文字コードが1バイトで文字を256種しか使えないのだが)
これの製作中に1つ思いついて実装したのが、フォントデータの一部をテキストデータに持たせる手法だ。
フォントデータは普通には9×9で81bit、10バイトと余り1bitになる。これを11バイトで持つのは無駄が多く気に食わない。
そこで、例えば文字コードの最上位bitを文字の右上のドットに対応するようにする。するとフォントデータとして持つのは残りの10バイトのみでよくなる。
文字コードの並びは不規則になってしまうが、文字列を表示するだけなら問題は無い。
問題が起こるのは、まず外部とのやり取りで既存の文字コードを使う場合。これはゲームではあまり考えなくてよいだろう。
それと、文字列をソートしたい場合。これはやや問題になるかもしれない。漢字はソートできなくてよいと考えて、仮名のみならテーブルを作るか、文字コードを上手く作れば仮名の順は取れるようにできるかもしれない。
あと文字の送り幅を変えるコードもやっつけで書いた。やっつけなので1文字あたり1バイトを余計に使う無駄なコードだが。文字データも10バイト使って無駄だ。
NESファイルは[こちら]。
コードは汚いので(まだ)公開しない。そのうち整えてCC-BY-NCあたりで公開するつもり。ゲームボーイからファミコンへの移植で変わったとことかも文章にまとめたい。
(2019/07/17追記)
実機動作していただいた!
実機(NEWファミコン)で動かしてみたところ。
— レゲ活Lab (@LabRegekatsu) 2019年5月2日
御覧の通り、画面後半の文字表示外の領域が化けている。
(不定パターンデータが不定ネームテーブルを元に表示されている) pic.twitter.com/dwv6F1yVH8
…そして初期化忘れに気付かされる。
エミュレータはふつう初期状態のメモリは全ゼロになっているので初期化しなくても動いてしまい、実機で動作させて初めて気づくことが多い。PICマイコンでもしょっちゅうやらかした。
これをエミュレータでテストするのはなかなか面倒である。今回はテスト用に乱数生成(Xorshift)のコードを書いてまず最初にRAMをランダム値で埋めることにした。
それでできたのがこちらである。見事に再現できた。

さらに調べているとスタックが3バイト単位の繰り返しで全部埋まっていることにも気づいた。スタックオーバーフローだ。
コードを確認すると、初期化からVBlank割り込みルーチンに流れ込むわ、VBlank割り込みルーチンは最後リターンせずにVBlank待ちに入るわのめちゃくちゃなコードだった。よく動いていたな。
そんなわけで割り込みルーチンを直しメモリのゼロクリア処理を追加して、
確認してみると目的のノイズは消えたのだが、起動時に一瞬別のノイズが見えるようになったのが治らない。

どうやらランダム値をVRAMに書き込む際に出ているようで、表示は切ってあるはずなのに不思議である。
ランダム化を消せば消えると思われたが、理由が分からないのは不安なので調べることにした。
結果、案の定仕様を勘違いしていたことが分かった。PPUADDRレジスタがパレットのあるアドレスを「表示中に」指しているとそのパレットの色が出るという仕様があるのだが、これを非表示中に起こるものと思っていた。
https://wiki.nesdev.com/w/index.php/PPU_palettes#The_background_palette_hack
更に、この仕様に関連して、ファミコンは起動時の一瞬、PPUが操作可能になる前に(条件によって異なる)とある1色を表示するということも知った。そういえばそんなだった気がするな。PPUが操作可能になる前なのでどうしようもないらしい。
理由が分かったので安心してゼロクリア処理を消す。今まで確認に使っていてNintendulatorではノイズが見えなくなったが、再現度の高いらしいMesenでは本来のパレットへの書き込みの分まだわずかに残っている。VBlank中へ持っていけば消せるのかもしれないが面倒なのでひとまずこれでよしとする。
画像を載せようかと思ったが上のと全く同じなのでやめる。
上のNESファイルは更新した…はず。エミュレータで見ても表示は同一なので確認が難しい。
あと全画面任意画像の方にも同じバグがあったので合わせて修正した。
2018年12月22日 17:40
この記事はGame Boy Advent Calendar 2018の22日めです。
最近ゲームボーイのプログラミングがマイブームだ。
ところでポケットプリンタってあるよね。
動く機械って萌えるよね。
というわけでやってみた。
使うのはもちろん使い慣れたPICマイコン。
ちょうど最近電子ペーパーを試すのに使っていたPIC16F1508が手頃だったのでこれを使うことにした。(電子ペーパーについてもブログに書こうと思っているのだが…)
なお固有の機能は特に使っていないのでEnhancedミッドレンジならどれでも容易に移植できるはず。SPI送信のみとかわざわざ機能調べるほうがめんどい。
ポケットプリンタ(海外名はGameboy Printer)の制御方法の情報は下記2サイトによくまとまっている。
In Depth: The Game Boy Printer
https://shonumi.github.io/articles/art2.html
Furrtek.org : Reversing GameBoy Printer
http://furrtek.free.fr/?a=gbprinter (原文フランス語)
http://furrtek.free.fr/?a=gbprinter&i=2 (英語; 未翻訳部あり)
さらに実際の送受信内容のダンプが公開されているのが嬉しい。
http://furrtek.free.fr/noclass/gbprinter/hexdump.txt
チェックサムが「1バイトごとの和を2バイトで」という珍しい形式なのでこれが無ければ理解は困難だっただろう。
これらサイトの情報を元に、「最低限動くプログラム」を作った。
最初のプログラムというものは単純なら単純なほどよい。バグも入りにくいし、他人も参考にしやすい。
具体的には、
・印刷する画像サイズは最小の160×16(ヘッドの1行分)
・上下のマージンは手動でフィードすればよいので0
・データの圧縮はしない(そもそも白黒でないとほとんど圧縮されなそうだし、圧縮しなくても送信に1秒掛からないし)
・データは送信のみ、プリンタからの返答は見ない
・statusコマンドのポーリングによる印刷終了判定もしない
・各種データはプログラムに直書き
・EnhancedミッドレンジPICのデータは14bit中8bitしか使えない簡易な読み方と全部読める読み方があるが、楽な前者
・電源ONで自動で1回だけ送信
といった感じ。
用紙は感熱紙自体は容易に手に入る。サイズが合わないのでノコギリで切って…
http://furrtek.free.fr/?a=gbpcable&i=2
「No paper ? Take a used receipt」
なるほどその手があったか!
不要なレシートならそのへんに落ちている。
電池を用意し(多いなあ…)

以前GBAと通信を試したときに通信ケーブル変換コネクタをばらして作った線でつないで(コネクタが入手できなければ本体をばらして線をつなぐのが早いと思う)

…動かない。
色々チェックし見つけたバグを修正したがやはり動かない。
出力は確かに出ている。クロック線とデータ線も間違っていない。
簡略化のためにプリンタからの応答を見なかったのはまずかったか…とも思ったがそれ以前にプリンタからのSO(Serial Out; 分かりやすくいえばMISO)線に何も出ていない。
何も出ていないというのはつまり、常にHigh-Zになっている。
出力していない時の信号が不定でHigh-Zにするのはまだ分かるとして、[furrtek]によれば何か信号を入れたら00が返ってくるように書かれているのだが。
線にLEDをつないでのデバッグではこのくらいが限度だ。
仕方ない。重い腰を上げオシロスコープを引っ張り出してきて(先日のPCのクラッシュで制御ソフトが消えていたのでインストールもして)確認してみる。

想定どおりである。(電源の都合上この時は電圧が違うが本番は5Vで動かしている)
ポケモンカードGBから印刷して実機の信号も見てみる。

こちらも想定どおりである。困った。
実機と違うところといえば、以下2点。
速度は[furrtek]には「1kHz以下(もっといけるかも)」くらいに書いてあったので、1kHz以下でプログラムが簡単なところとして約650Hz。
実機の速度は約8kHzだが、ふつう同期シリアル信号というものは遅い分には問題が無いものだ。
怪しいのは電源投入時だ。線をつないでから
プリンタ電源ON→PIC電源ON→300msほど待つ→送信開始
というシーケンスで操作しているが、このPIC側電源投入時にクロック線が暴れて不正なデータが入っているのかもしれない。
しかし、先頭のマジックバイト8833というのはこの辺の同期ズレをリセットするためのものではないのか?
つまり、電源投入時から(バイト単位でなく)ビット単位で信号を見て「1000100000110011」のパターンがあったらそこで同期するという仕組みではないのか。
まあ他に打つ手も無いのでこの2つを修正してみることにした。
通信速度は8kHz弱に。念のため上の画像でも見える1バイトごとに半bit分くらい止まる部分も実機に合わせた。
PIC電源投入時の待ち時間を5秒ほどに伸ばし、電源投入シーケンスを
PIC電源ON→(5秒の間に)プリンタ電源ON→送信開始
とした。
すると、

成功!
どちらが効いたのか片方づつ試すと、なんと両方とも必要だった。
650Hzの速度では、全く反応しない。
電源投入時のシーケンスを最初のものに戻すと、まれに成功するもののほとんど反応しない。(20回ほど試し2回成功)
再び両方修正後のコードで何度か試すと、ほぼ確実に成功する。(失敗は接触不良か?)
これは不思議である。
遅い信号を弾くのはノイズを信号と判断しないためではないのだろうか。そうであれば300ms空けたところでリセットしていてほしい。
一方そうでないなら、挿抜時のノイズを無視するためにマジックバイトがあるのではないのだろうか。そうであれば挿抜時にどんなノイズが乗っても正しく信号を受けてほしい。
何か間違っているかもしれないが、おそらく動くようにはなった。
もっとちゃんとした画像表示もそのうちやりたい。
[余談]
・折角買った感熱紙だし切った。

…55分掛かった。間に1時間の休憩を入れて。
学研のふろくじゃなくまともなノコギリを持っておいたほうがいいな。
・画像データは以下のようにして作った。
GIMPで描く

ペイントで8×8ドットごとに並び替える(背景色を変えるとやりやすい)

上下反転・色反転、GIMPでトーンカーブを使って上位bitと下位bitのデータを作る

モノクロビットマップで保存
バイナリエディタで開く
テキストエディタで適宜置換
・何度か試したの図↓

コード:
list p=16F1508
#include p16F1508.inc
radix dec
; Vdd +-v-+ Vss
; RA5 | | RA0/DAT
; RA4 | | RA1/CLK 4_1
;1_0 (RA3)| | RA2 CLC1
; RC5 | | RC0 CLC2
;21/4 RC4 | | RC1
;2_0 RC3 | | RC2
;3_1 RC6 | | RB4 3_0
;1_1 RC7 | | RB5 4_0
;CLC3 RB7 +---+ RB6
__CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOREN_OFF & _CLKOUTEN_OFF
__CONFIG _CONFIG2, _WRT_OFF & _STVREN_OFF & _BORV_LO & _LPBOR_OFF & _LVP_OFF
#define DATPIN 4
#define CLKPIN 5
cblock 0x20
endc
cblock 0x70
cntl, cnth, suml, sumh
dat, bitcnt, waitcnt, portbuf
; temp, temp2
endc
org 0
goto init
org 4
init:
banksel OSCCON
movlw b'01101010'
; ^^^^ || IRCF 1101=4MHz (※クロックを4MHzにすると1命令1μsで計算しやすい)
; ^^ SCS SystemClockSelect 1x = internal
movwf OSCCON
banksel 0
clrf PORTC
clrf PORTB
movlw b'00110000'
movwf PORTA
banksel TRISC
clrf TRISA
clrf TRISB
clrf TRISC
banksel ANSELC
clrf ANSELC
clrf ANSELB
clrf ANSELA
banksel 0
clrf cntl
clrf cnth
clrf suml
clrf sumh
clrf dat
clrf bitcnt
clrf waitcnt
clrf portbuf
;少し待つ
;//65536*5=327 680
;11*65536*7=5046272
movlw .11
movwf waitcnt
decfsz cntl,F
goto $+2
decfsz cnth,F
goto $+2
decfsz waitcnt,F
goto $-5
;init
movlw .4
movwf cntl
movlw .1 ;プログラムの都合で+1
movwf cnth
movlw high(cmd_init)
movwf FSR0H
movlw low(cmd_init)
movwf FSR0L
call sendcmd
;データ
movlw 0x84
movwf cntl
movlw 0x02+1
movwf cnth
movlw high(cmd_data)
movwf FSR0H
movlw low(cmd_data)
movwf FSR0L
call sendcmd
;空データ
movlw .4
movwf cntl
movlw .1
movwf cnth
movlw high(cmd_data0)
movwf FSR0H
movlw low(cmd_data0)
movwf FSR0L
call sendcmd
movlw .8
movwf cntl
movlw .1
movwf cnth
movlw high(cmd_print)
movwf FSR0H
movlw low(cmd_print)
movwf FSR0L
call sendcmd
goto $ ;終了
;コマンド送信
;具体的には、「88,33,本体,チェックサム,ダミー×2」を送信
sendcmd:
clrf suml
clrf sumh
;magic byte 88,33
movlw 0x88
call sendbyte
movlw 0x33
call sendbyte
;本体
sendcmdloop:
moviw FSR0++
addwf suml,F
btfsc STATUS,C
incf sumh,F
call sendbyte
decfsz cntl,F
goto $+2
decfsz cnth,F
goto sendcmdloop
;チェックサム
movf suml,W
call sendbyte
movf sumh,W
call sendbyte
movlw 0
call sendbyte
movlw 0
call sendbyte
return
;1バイト送信
;相手はクロック立ち上がりで読む
sendbyte:
movwf dat
movlw .18 ;タイミングを現物合わせ
movwf waitcnt
decfsz waitcnt,F
goto $-1
movlw .8
movwf bitcnt
clrf waitcnt
;DAT,CLKをlowにした値を用意
movf PORTA,W
andlw ~((1<<DATPIN)|(1<<CLKPIN)) ;b'11001111'
movwf portbuf
sendbyteloop:
movlw .18 ;タイミングを現物合わせ
movwf waitcnt
decfsz waitcnt,F
goto $-1
movf portbuf,W ;読み込み
rlf dat,F
btfsc STATUS,C
iorlw 1<<DATPIN ;データが1ならDATをhigh
movwf PORTA ;書き込み
movlw .20 ;タイミングを現物合わせ
movwf waitcnt
decfsz waitcnt,F
goto $-1
bsf LATA,CLKPIN ;クロック操作
decfsz bitcnt,F
goto sendbyteloop
return
; magic 8833
; cmd comp len L/H sum L/H dummy
cmd_init:
;dt 0x88, 0x33,
dt 0x01, 0x00, 0x00, 0x00; 0x01, 0x00, 0x00, 0x00
cmd_init_len equ $-cmd_init ;やっぱりめんどいので直書きにする
cmd_print:
dt 0x02, 0x00, 0x04, 0x00
dt 0x01, 0x00, 0xE4, 0x40
; ???? margin palt exposure
cmd_status:
dt 0x0F, 0x00, 0x00, 0x00
cmd_data0:
dt 0x04, 0x00, 0x00, 0x00
cmd_data:
dt 0x04, 0x00, 0x80, 0x02
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x01,0x03,0x03,0x03,0x07,0x07,0x07
dt 0x28,0x70,0xFC,0x78,0xFC,0xFE,0xFC,0xFE,0xFC,0xFE,0xFC,0xFE,0xFE,0xFC,0xF8,0xFC
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x3E,0x7C,0x7F,0xFE,0x7F,0x7F
dt 0x00,0x00,0x03,0x00,0x2F,0x1F,0x1F,0x3F,0x1F,0x3F,0x1F,0x1F,0x0F,0x1F,0x07,0x0F
dt 0x03,0x00,0x87,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xEC,0xF0
dt 0x1E,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0x7E,0x7E,0x7E
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x01,0x7F,0x3F,0x7F,0x3F
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x0F,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF
dt 0x03,0x01,0x07,0x0F,0x0F,0x0F,0x8F,0x47,0xCF,0xE7,0xC7,0xE7,0xE7,0xE3,0xE3,0xC3
dt 0xDF,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xF8,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0
dt 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x0F,0x0F,0x1F,0x0F,0x1F,0x0F,0x1F,0x1F
dt 0xC0,0xE0,0xC0,0xE0,0xC0,0xE0,0xE0,0xC0,0xE0,0xC0,0xCE,0xC1,0xFF,0xDF,0x9F,0xDF
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x1F,0xFF,0xFF,0xFF,0xFF,0xFF
dt 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0xF8,0xF8,0xF0,0xF8,0xF0,0xF0,0xF0
dt 0x01,0x03,0x01,0x03,0x01,0x03,0x01,0x7B,0x79,0x7B,0x79,0x7B,0x79,0x7B,0x79,0x7B
dt 0xF8,0xF8,0xF8,0xF8,0xF0,0xF8,0xF8,0xF0,0xF8,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0
dt 0x03,0x01,0x03,0x01,0x0B,0x01,0x11,0x09,0x39,0x19,0x18,0x39,0x78,0x39,0x39,0x78
dt 0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFF,0xFF
dt 0x00,0x01,0x00,0x01,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x0F,0xFF,0xFF,0xFF
dt 0xE7,0xEF,0xE7,0xEF,0xEF,0xE7,0xEF,0xE7,0x00,0x00,0x00,0x00,0x80,0x00,0x80,0x80
dt 0x07,0x0F,0x0F,0x0F,0x0F,0x1F,0x3F,0x1F,0x3F,0x3F,0x3F,0x7F,0xFF,0x7F,0x7F,0xFF
dt 0xFC,0xF8,0xFC,0xF8,0xF0,0xF8,0xF0,0xF8,0xF8,0xF0,0xE0,0xF0,0xE0,0xE0,0xC0,0xE0
dt 0x3F,0x7F,0x7F,0x3F,0x3F,0x3F,0x3F,0x1F,0x0F,0x1F,0x0F,0x0F,0x07,0x0F,0x07,0x07
dt 0x07,0x8F,0xC7,0x87,0xA7,0xC3,0xE3,0xC3,0xE1,0xE3,0xF1,0xF1,0xF9,0xF0,0xF0,0xF8
dt 0xF0,0xE0,0xE0,0xF0,0xF0,0xF0,0xF8,0xF1,0xFF,0xFF,0xFF,0xFF,0xFE,0xFF,0xF8,0x70
dt 0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x7C,0xFE,0xFC,0xFE,0xFC,0xFC,0x60,0x80,0x00,0x00
dt 0x3F,0x3F,0x1F,0x3F,0x3F,0x1F,0x14,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
dt 0xFF,0xFF,0xFC,0xFF,0xE8,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
dt 0xC1,0xC3,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
dt 0x80,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x01,0x0F,0x07,0x1F,0x1F
dt 0x3F,0x1F,0x1F,0x3F,0x3F,0x7F,0xFE,0x7F,0xFE,0xFE,0xFE,0xFC,0xFA,0xFC,0xFC,0xF8
dt 0xDF,0x8F,0x07,0x8F,0x8F,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
dt 0xFF,0xFF,0xFB,0xFC,0x90,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
dt 0xE0,0xF0,0x01,0x00,0x01,0x01,0x01,0x03,0x03,0x07,0x0F,0x07,0x0F,0x3F,0x7F,0x7F
dt 0x79,0xFB,0xF1,0xFB,0xF1,0xFB,0xF9,0xF3,0xF9,0xF3,0xE1,0xF3,0xF1,0xE3,0xE1,0xE3
dt 0xE0,0xF0,0xE1,0xF0,0xE3,0xF1,0xE7,0xF3,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
dt 0x79,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF0,0xC0,0xE0,0x40,0x80
dt 0xFF,0xFF,0xFF,0xFF,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xF8,0xFC,0x78,0xFC
dt 0xFF,0xFF,0xFF,0xFF,0x1F,0x7F,0x03,0x07,0x02,0x03,0x00,0x00,0x00,0x00,0x00,0x00
dt 0x80,0x80,0x00,0x80,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x55,0xAA ;※末尾の55AAはデバッグ時に使ったものの消し忘れ
end
(2019/09/03 修正) コード中のシフト演算子がHTMLタグ扱いで消えていたのを修正しました。
2018年03月25日 03:01
ファミコンやゲームボーイなど昔のゲームソフトにはデータの保持にボタン電池が使われている。
(最初に知った時には驚いた。壊れなければ永遠に使えると思っていたゲームソフトにまさか寿命のある電池が入っているとは。しかもちっぽけなボタン電池。すぐにでも切れてしまいそうではないか。)
古いソフトの電池を交換する上で何が問題かといえば配線の接続方法だ。
元々はスポット溶接で留められている。これなら確実に導通がとれるだろう。しかし家庭にスポット溶接機はない。どうやって留めるか。
まっとうな方法は既にタブが溶接された電池を買うことだ。電池ホルダーを使うのもよいだろう。
しかしなぜか買う気にならない。その辺で売ってないとか割高とかの理由もあるが、たぶん専用品を使うのは負けた気がするという気持ちの問題が大きいと思う。
溶接以外の方法をいろいろ試してみるのだが、なかなかうまくいかない。溶接の素晴らしさが分かる。
リード線をテープで留めただけだと、すぐに接触不良でデータが飛ぶ。
何かしっかりとテンションを掛けられる手段がないだろうか。
リード線を熱収縮チューブで固定してテンションを掛ければどうかと試してみたが、駄目であった。しばらくしてデータが飛んだ。
また厚みでケースに微妙に収まらず、無理やり閉じたらケースが膨らんだ。(もっともこれはCR2032を使ったのが悪かった。1616にすれば大丈夫だったかもしれない)
もっとしっかりとテンションが掛かる物を考えるとクリップが思いつくが、GBソフトの狭いスペースに入るものはなかなかないだろう。
電池に直接ハンダ付けするという最終手段もあるが、アルカリ電解液の詰まった電池に高熱を与えるのは大変危険なのであまりやりたくない。またそもそも材質がハンダをはじくのであまりしっかりと付いてくれない。
そんなわけでなかなかこれといった方法が見つからなかったのだが、ふと導電インクが使えるのではないかと思い立った。
以前何かに使えるかと思って買ったものの何にも使っていなかったものだ。
「Bare Conductive Electric Paint」という名のもので、調べたら同じものがAmazonでも売っている。
試してみると、予想以上によい。
この種のカーボンタイプの導電インクは回路を描くには少々抵抗が大きいのだが、今回導線と電池表面の間のわずかな隙間を埋めるだけなので抵抗は無視できる。
無視できる…と思うが念のため計ってみると30Ω。思ったよりはあるな。
しかし電流はわずかなので抵抗による電圧降下は気にする必要はないだろう。計算したら1mVもないくらいだし実際計っても分からない。
そして接着力だが、結構ある。剥がそうと思って力を入れれば線を引っ張って剥がしたり爪で引っ掻いて剥がしたりはできるが、意図せず剥がれるようなことは無さそうな程度にある。
付きにくいハンダ付けよりはるかに信頼できる。
最初に試したものがこちらだ。

導線の太さで厚みが出て、熱収縮チューブのときほどではないものの微妙にケースが膨らんでしまった。
このあたりでそもそもGBソフトに使われている電池はCR2032でなくCR1616であったことに気づいたが、買いに行くのは面倒だし、うまくすれば入りそうなのでそのまま2032を使い続けることにした。
もう1点問題があり、基板上に電池を固定するために斜めにテープを張ったのだが、カートリッジの上蓋は枠が出っ張っているので閉める時引っかかってしまう。
それを踏まえて2個め。
厚みを抑えるために芯線をバラして接着することにした。カプトンテープで絶縁をとってある。
この方が抵抗をより抑えられそうな気がするし。しかし残念ながらこちらの抵抗は測り忘れてしまった…。

基板にはんだ付け。カートリッジのケースに入れてみると問題なく閉まった。

固定はカプトンテープでは上手くできず、グルーガンで留めようかと思っていたのだが、ただ閉じるだけで振ってもガタつくこと無く一応固定されているようなのでそのままにしてしまった。
1個めが2ヶ月半、2個めも1ヶ月ほど経つがどちらもデータを保持できている。
2018/12/22:
ふと思い出して確認したところどちらもまだデータを保持できている。
2020/11/16:
ふと思い出して確認したところ、1個めはデータが消えてしまっていた…。カートリッジコネクタの接触不良で何度か挿し直したのが悪影響を与えたかもしれない。新しくデータを作成し数分後確認すると正常。
2個めはデータを保持できている。
2021/10/21:
ふと思い出して確認したところどちらもデータを保持できている。(1個めのは2020/11/16に消えて作り直したデータ)
何もしなければデータがもつことの確認はもう十分かな。せっかくなので開けて見てみよう。
3年半経ってどう変わったか。


うーん…見てもよく分からない。
テープも取ってみるか…

うむ、特に接着面が剥がれかけてるとかそういうことは無さそう。(テープに付着しているが、乾く前にテープを貼ったので不思議はない)
バックアップ電池の交換にカーボン系導電インク、なかなか良さそうだ。
(最初に知った時には驚いた。壊れなければ永遠に使えると思っていたゲームソフトにまさか寿命のある電池が入っているとは。しかもちっぽけなボタン電池。すぐにでも切れてしまいそうではないか。)
古いソフトの電池を交換する上で何が問題かといえば配線の接続方法だ。
元々はスポット溶接で留められている。これなら確実に導通がとれるだろう。しかし家庭にスポット溶接機はない。どうやって留めるか。
まっとうな方法は既にタブが溶接された電池を買うことだ。電池ホルダーを使うのもよいだろう。
しかしなぜか買う気にならない。その辺で売ってないとか割高とかの理由もあるが、たぶん専用品を使うのは負けた気がするという気持ちの問題が大きいと思う。
溶接以外の方法をいろいろ試してみるのだが、なかなかうまくいかない。溶接の素晴らしさが分かる。
リード線をテープで留めただけだと、すぐに接触不良でデータが飛ぶ。
何かしっかりとテンションを掛けられる手段がないだろうか。
リード線を熱収縮チューブで固定してテンションを掛ければどうかと試してみたが、駄目であった。しばらくしてデータが飛んだ。
また厚みでケースに微妙に収まらず、無理やり閉じたらケースが膨らんだ。(もっともこれはCR2032を使ったのが悪かった。1616にすれば大丈夫だったかもしれない)
もっとしっかりとテンションが掛かる物を考えるとクリップが思いつくが、GBソフトの狭いスペースに入るものはなかなかないだろう。
電池に直接ハンダ付けするという最終手段もあるが、アルカリ電解液の詰まった電池に高熱を与えるのは大変危険なのであまりやりたくない。またそもそも材質がハンダをはじくのであまりしっかりと付いてくれない。
そんなわけでなかなかこれといった方法が見つからなかったのだが、ふと導電インクが使えるのではないかと思い立った。
以前何かに使えるかと思って買ったものの何にも使っていなかったものだ。
「Bare Conductive Electric Paint」という名のもので、調べたら同じものがAmazonでも売っている。
試してみると、予想以上によい。
この種のカーボンタイプの導電インクは回路を描くには少々抵抗が大きいのだが、今回導線と電池表面の間のわずかな隙間を埋めるだけなので抵抗は無視できる。
無視できる…と思うが念のため計ってみると30Ω。思ったよりはあるな。
しかし電流はわずかなので抵抗による電圧降下は気にする必要はないだろう。計算したら1mVもないくらいだし実際計っても分からない。
そして接着力だが、結構ある。剥がそうと思って力を入れれば線を引っ張って剥がしたり爪で引っ掻いて剥がしたりはできるが、意図せず剥がれるようなことは無さそうな程度にある。
付きにくいハンダ付けよりはるかに信頼できる。
最初に試したものがこちらだ。

導線の太さで厚みが出て、熱収縮チューブのときほどではないものの微妙にケースが膨らんでしまった。
このあたりでそもそもGBソフトに使われている電池はCR2032でなくCR1616であったことに気づいたが、買いに行くのは面倒だし、うまくすれば入りそうなのでそのまま2032を使い続けることにした。
もう1点問題があり、基板上に電池を固定するために斜めにテープを張ったのだが、カートリッジの上蓋は枠が出っ張っているので閉める時引っかかってしまう。
それを踏まえて2個め。
厚みを抑えるために芯線をバラして接着することにした。カプトンテープで絶縁をとってある。
この方が抵抗をより抑えられそうな気がするし。しかし残念ながらこちらの抵抗は測り忘れてしまった…。

基板にはんだ付け。カートリッジのケースに入れてみると問題なく閉まった。

固定はカプトンテープでは上手くできず、グルーガンで留めようかと思っていたのだが、ただ閉じるだけで振ってもガタつくこと無く一応固定されているようなのでそのままにしてしまった。
1個めが2ヶ月半、2個めも1ヶ月ほど経つがどちらもデータを保持できている。
2018/12/22:
ふと思い出して確認したところどちらもまだデータを保持できている。
2020/11/16:
ふと思い出して確認したところ、1個めはデータが消えてしまっていた…。カートリッジコネクタの接触不良で何度か挿し直したのが悪影響を与えたかもしれない。新しくデータを作成し数分後確認すると正常。
2個めはデータを保持できている。
2021/10/21:
ふと思い出して確認したところどちらもデータを保持できている。(1個めのは2020/11/16に消えて作り直したデータ)
何もしなければデータがもつことの確認はもう十分かな。せっかくなので開けて見てみよう。
3年半経ってどう変わったか。
うーん…見てもよく分からない。
テープも取ってみるか…
うむ、特に接着面が剥がれかけてるとかそういうことは無さそう。(テープに付着しているが、乾く前にテープを貼ったので不思議はない)
バックアップ電池の交換にカーボン系導電インク、なかなか良さそうだ。
2017年01月16日 22:44
前編の続き。プログラム側について。
まずは単純に読むことを試みる。
手持ちの中でバンク切り替えなし(32kB)のソフトとしてDr.マリオを選択。
バンク切り替えが無ければアドレスを出してデータを読むだけ。
…とはいえCLKとかCSとかRDとかどう制御すればいいのか。
GBのカートリッジの仕様くらいいくらでも見つかると思っていたのだが、ROMの読み出し方法は常識なのか、細かい解説が意外と見つからない。
結局、分かってみると単純で、
/RDがLにアサートされている間、アドレスピンで指定されたアドレスのデータが、データピンに出る
というだけのことだった。
つまり読み出すには、/RDをLにアサートしたままアドレスを順次変え、データピンを読むだけでよい。
これが分かるまでに/RDをH/L切り替えてしていた。
あと/CSはSRAMを読む時アサートするものだった。
プログラミング自体もなんだかんだで苦労した。
やはり合っているか分からない操作を正しく組めているか分からない機械にプログラムするのは疑心暗鬼に陥ってだいぶ精神を消耗する。
まずシリアル通信するところからしてうまくいかない。
シリアルポートが全く反応しなくて焦った挙句Windows10へアップグレードした時にケーブル類全部抜いてたのを戻してなかっただけだったりもした。
何か出るようになったと思えば文字化けしている。これはどうもTeraTermのバグかWindows10との相性のようで、最新版を使ったら正常であった。
新しいPICを使う際には毎回のようにGPIO以外の機能を切っていなくて問題が起こるのだが、今回もまんまとその罠にはまった。
ADコンバータとコンパレータを切ったまでは良かったのだが、それで読もうとしても何も読めない(FFが読める)。
ポートのレジスタを直接インクリメントしてたのがまずかったかと思い、別の変数をインクリメントしてそれを出力するようにしたところ何かは読めるようになったが、全体にわたりほぼ確実に8バイトづつ同じ値が取れる。
つまりアドレスの下3bitが何かおかしい。そのピンを調べると、LCDドライバの電圧生成機能がデフォルトでONであった。
ということはポートのレジスタを直接インクリメントすること自体は問題なく、最下位bitの書き込みが無視されていたせいでインクリメントできていなかったんだな。
というわけでついにDr.マリオが読めた。

次はバンク切り替え…の前に色々なソフトをバンク0だけ読んでみることにした。
するとポケモンYellowやポケモンカードなど読めるものもあるが、ポケモンSilverが読めない。
読めないというのは、ほぼ全てFFが返ってくる。ごく稀にFF以外のものが返ってくることもあるのがまた不可解である。
FFでない箇所のパターンは規則的で、なにかありそうである。2進数で「xxxx xxx1 0000 000x」と「xx00 0110 0000 0001」、つまり0100, 0101, 0300, 0301,…と0601, 4601がFFでない。
不要なはずのクロックだがMBCを積んでいることもあり何か変わるのではないかと入れてみる。当然変わらず。
…散々悩んだ挙句、電圧不足であった。
いつもPICを動かす時はEneloop2本(2.6V程度)を使っていて、使いやすい5V電源を持っていないこともあり、とりあえずそれでやっていたのだが、GBの電圧は5Vなので動かなくてなんの不思議も無いのであった。
Dr.マリオを始めいくつかのソフトで(バンク0は)読めていたので発覚が遅れた。
改めてバンク切り換えだ。
バンクの少ない(最少の4バンク)ものとしてQIXを選択。
バンク切り換えの手順はMBCによって少々違うが、基本的に特定のアドレスにデータを書き込むだけである。
書き込むべきデータを入れていなかったり、PICのIOを入力のまま出力したつもりになっていたり、書き込むアドレスを間違ったりして手間取ったが、まあまあすんなりと読めた。

さて続いて本命のポケモンSilverだ。
128バンクあるが、バンク切り換えのやり方は同じなので、単純に数が多いだけ。難しいことは何もない。
しかしなぜか途中でデータが飛ぶ。読み取ったデータを見るとファイルサイズが想定より小さい。
今までこのような大量のデータをシリアルで受信したことは無かった。シリアル通信の信頼性はこの程度なのだろうか。
だがそれは想定の内。1バンクごとに目印を入れてあるのでどこで抜けたかは分かる。何度か読んで正常な部分を切り貼りすればよいだろう。
…と思っていたのだが、不思議な事に常にエラーが出ている場所がある。
バッファ切れを疑ってバッファ量を変えたりウェイトを入れたりしてもなんとなく変化はあるものの直らず。
データの問題かと思いXOR 0x55したデータを送ってみると抜けの量はほぼ変わらず、抜けの位置が変わった。特定のデータが来ると問題が起こるのだろうか。
速度を落としてみるとだいぶ改善した。抜けが6バンクまで減ったので試しに起動してみると、一応起動はした。


このような分かりやすいバグった表示になるものなんだなあ。
なお部屋に出口がないので進めなかった。その後もう1バンク正常に取れたのでそれと合わせると部屋から出られたがBGMが異常になったりする。
しかしここで何度とってもほぼ同じ場所でエラーを起こす。
やはり速度を落とすだけでは解決しない。特定のデータが問題という線で考えてみよう。
改行の処理に時間が掛かっている可能性を考えてCRの後にウェイトを入れてみる。むしろ悪化。
あとは…エスケープシーケンス。何らかのエスケープシーケンスが来るとそれの処理に時間がかかってデータを取りこぼすのではないか。
ここでTeraTermを調べて、受信した文字をすべて表示するデバッグモードがあることを知る。
デバッグモードで受信するようにしたところ、一切取りこぼさなくなった!

後で調べたところ、制御シーケンスに「1B 63: 端末リセット」というものがあるらしい。
つまりこのバイト列が来るとTeraTermがリセット動作を起こし、その間に来たデータを取りこぼしていたようだ。
調べてみると「1B 63」は最後まで読めなかった5バンク中の3ヶ所にあった。残り2ヶ所やそれ以前のエラー箇所は分からないままだがたぶん他のエスケープシーケンスだろう。
デバッグモード以外にエスケープシーケンスを無視する方法がないか調べたのだが、見つからなかった。
人が読む文字列を出す時は改行は使いたいのだが、致し方ない。
次に困ったのがX(エックス)だ。バンク切り替えができない。
調べてみるとこれに使われているMBC2はバンク切り替え時に書き込むアドレスに制限があり、今まで使っていた0x2000では駄目だった。
と0x2100に変えてみたが、やはりバンクは切り替わらない。
そこで読み取れたバンク0のコードを見てみることにした。この中にバンク切り替えのコードがあるはずである。
するとやはり0x2100に書き込んでいる。
合っているのにおかしいなと思い更に調べると、バンク1に変更する時は0x2100だったのだが、バンク2では0x2101、バンク3では0x2102…と、バンクNに変更する時0x2100+(N-1)に書き込むようになっていた。
1少ないのは書き込み後のインクリメントの関係だろう。ということでバンクNに変更する時は0x2100+Nに書き込むようにコードを書き換えてみると、見事読み取りに成功した。
なんだろう。バスコンフリクトだろうか。ファミコンのMMCでバスコンフリクトを起こすものがあるという情報はあるが、GBで起こるというのは見たことがなかった。

さて次はニンテンドウパワーのGBメモリの読み取りを試みている。
これは複数のMBCの動作を再現する特殊なコントローラを積んでおり、普通のバンク切り換えとは異なるコマンドを入れる必要があるらしい。
色々試しているのだがまだ一切反応がない。一番つらい時期だ。
読み取りができたら、どうも書き込みも出来るらしいのでやりたい。自作ソフトを実機で動かすのは夢である。
ただその前に、どうも読み取りが安定しないのでどうにかしたい。
今まで読めていたソフトでも読めなくなったりしている。
断線しかかっているとかだろうか…。
まずは単純に読むことを試みる。
手持ちの中でバンク切り替えなし(32kB)のソフトとしてDr.マリオを選択。
バンク切り替えが無ければアドレスを出してデータを読むだけ。
…とはいえCLKとかCSとかRDとかどう制御すればいいのか。
GBのカートリッジの仕様くらいいくらでも見つかると思っていたのだが、ROMの読み出し方法は常識なのか、細かい解説が意外と見つからない。
結局、分かってみると単純で、
/RDがLにアサートされている間、アドレスピンで指定されたアドレスのデータが、データピンに出る
というだけのことだった。
つまり読み出すには、/RDをLにアサートしたままアドレスを順次変え、データピンを読むだけでよい。
これが分かるまでに/RDをH/L切り替えてしていた。
あと/CSはSRAMを読む時アサートするものだった。
プログラミング自体もなんだかんだで苦労した。
やはり合っているか分からない操作を正しく組めているか分からない機械にプログラムするのは疑心暗鬼に陥ってだいぶ精神を消耗する。
まずシリアル通信するところからしてうまくいかない。
シリアルポートが全く反応しなくて焦った挙句Windows10へアップグレードした時にケーブル類全部抜いてたのを戻してなかっただけだったりもした。
何か出るようになったと思えば文字化けしている。これはどうもTeraTermのバグかWindows10との相性のようで、最新版を使ったら正常であった。
新しいPICを使う際には毎回のようにGPIO以外の機能を切っていなくて問題が起こるのだが、今回もまんまとその罠にはまった。
ADコンバータとコンパレータを切ったまでは良かったのだが、それで読もうとしても何も読めない(FFが読める)。
ポートのレジスタを直接インクリメントしてたのがまずかったかと思い、別の変数をインクリメントしてそれを出力するようにしたところ何かは読めるようになったが、全体にわたりほぼ確実に8バイトづつ同じ値が取れる。
つまりアドレスの下3bitが何かおかしい。そのピンを調べると、LCDドライバの電圧生成機能がデフォルトでONであった。
ということはポートのレジスタを直接インクリメントすること自体は問題なく、最下位bitの書き込みが無視されていたせいでインクリメントできていなかったんだな。
というわけでついにDr.マリオが読めた。

次はバンク切り替え…の前に色々なソフトをバンク0だけ読んでみることにした。
するとポケモンYellowやポケモンカードなど読めるものもあるが、ポケモンSilverが読めない。
読めないというのは、ほぼ全てFFが返ってくる。ごく稀にFF以外のものが返ってくることもあるのがまた不可解である。
FFでない箇所のパターンは規則的で、なにかありそうである。2進数で「xxxx xxx1 0000 000x」と「xx00 0110 0000 0001」、つまり0100, 0101, 0300, 0301,…と0601, 4601がFFでない。
不要なはずのクロックだがMBCを積んでいることもあり何か変わるのではないかと入れてみる。当然変わらず。
…散々悩んだ挙句、電圧不足であった。
いつもPICを動かす時はEneloop2本(2.6V程度)を使っていて、使いやすい5V電源を持っていないこともあり、とりあえずそれでやっていたのだが、GBの電圧は5Vなので動かなくてなんの不思議も無いのであった。
Dr.マリオを始めいくつかのソフトで(バンク0は)読めていたので発覚が遅れた。
改めてバンク切り換えだ。
バンクの少ない(最少の4バンク)ものとしてQIXを選択。
バンク切り換えの手順はMBCによって少々違うが、基本的に特定のアドレスにデータを書き込むだけである。
書き込むべきデータを入れていなかったり、PICのIOを入力のまま出力したつもりになっていたり、書き込むアドレスを間違ったりして手間取ったが、まあまあすんなりと読めた。

さて続いて本命のポケモンSilverだ。
128バンクあるが、バンク切り換えのやり方は同じなので、単純に数が多いだけ。難しいことは何もない。
しかしなぜか途中でデータが飛ぶ。読み取ったデータを見るとファイルサイズが想定より小さい。
今までこのような大量のデータをシリアルで受信したことは無かった。シリアル通信の信頼性はこの程度なのだろうか。
だがそれは想定の内。1バンクごとに目印を入れてあるのでどこで抜けたかは分かる。何度か読んで正常な部分を切り貼りすればよいだろう。
…と思っていたのだが、不思議な事に常にエラーが出ている場所がある。
バッファ切れを疑ってバッファ量を変えたりウェイトを入れたりしてもなんとなく変化はあるものの直らず。
データの問題かと思いXOR 0x55したデータを送ってみると抜けの量はほぼ変わらず、抜けの位置が変わった。特定のデータが来ると問題が起こるのだろうか。
速度を落としてみるとだいぶ改善した。抜けが6バンクまで減ったので試しに起動してみると、一応起動はした。


このような分かりやすいバグった表示になるものなんだなあ。
なお部屋に出口がないので進めなかった。その後もう1バンク正常に取れたのでそれと合わせると部屋から出られたがBGMが異常になったりする。
しかしここで何度とってもほぼ同じ場所でエラーを起こす。
やはり速度を落とすだけでは解決しない。特定のデータが問題という線で考えてみよう。
改行の処理に時間が掛かっている可能性を考えてCRの後にウェイトを入れてみる。むしろ悪化。
あとは…エスケープシーケンス。何らかのエスケープシーケンスが来るとそれの処理に時間がかかってデータを取りこぼすのではないか。
ここでTeraTermを調べて、受信した文字をすべて表示するデバッグモードがあることを知る。
デバッグモードで受信するようにしたところ、一切取りこぼさなくなった!

後で調べたところ、制御シーケンスに「1B 63: 端末リセット」というものがあるらしい。
つまりこのバイト列が来るとTeraTermがリセット動作を起こし、その間に来たデータを取りこぼしていたようだ。
調べてみると「1B 63」は最後まで読めなかった5バンク中の3ヶ所にあった。残り2ヶ所やそれ以前のエラー箇所は分からないままだがたぶん他のエスケープシーケンスだろう。
デバッグモード以外にエスケープシーケンスを無視する方法がないか調べたのだが、見つからなかった。
人が読む文字列を出す時は改行は使いたいのだが、致し方ない。
次に困ったのがX(エックス)だ。バンク切り替えができない。
調べてみるとこれに使われているMBC2はバンク切り替え時に書き込むアドレスに制限があり、今まで使っていた0x2000では駄目だった。
と0x2100に変えてみたが、やはりバンクは切り替わらない。
そこで読み取れたバンク0のコードを見てみることにした。この中にバンク切り替えのコードがあるはずである。
するとやはり0x2100に書き込んでいる。
合っているのにおかしいなと思い更に調べると、バンク1に変更する時は0x2100だったのだが、バンク2では0x2101、バンク3では0x2102…と、バンクNに変更する時0x2100+(N-1)に書き込むようになっていた。
1少ないのは書き込み後のインクリメントの関係だろう。ということでバンクNに変更する時は0x2100+Nに書き込むようにコードを書き換えてみると、見事読み取りに成功した。
なんだろう。バスコンフリクトだろうか。ファミコンのMMCでバスコンフリクトを起こすものがあるという情報はあるが、GBで起こるというのは見たことがなかった。

さて次はニンテンドウパワーのGBメモリの読み取りを試みている。
これは複数のMBCの動作を再現する特殊なコントローラを積んでおり、普通のバンク切り換えとは異なるコマンドを入れる必要があるらしい。
色々試しているのだがまだ一切反応がない。一番つらい時期だ。
読み取りができたら、どうも書き込みも出来るらしいのでやりたい。自作ソフトを実機で動かすのは夢である。
ただその前に、どうも読み取りが安定しないのでどうにかしたい。
今まで読めていたソフトでも読めなくなったりしている。
断線しかかっているとかだろうか…。
2017年01月14日 00:00
ファミコンは普通には全画面に自由に画像を表示できないことはよく知られている。
でもモノクロ2値ならできることに気づいたのでやってみた。
とりあえず猫耳。
酉年らしく羽根っ娘。
全画面任意なのが分かりやすい図柄も。
まず前提として、ファミコンのBG(背景)面は32×30=960個のキャラクタで構成されるが、BGの(スプライトも)キャラクタパターンの指定は1バイトなので、一度に256種類からしか選べない。
よって普通にはBGだけでは256キャラ、画面の1/4強しか埋められず、スプライトを8×16ドットモードで最大の64個使って128キャラ用意してやっと384/960=40%にしかならない。
何らかのマッパーを使えば描画中にバンク切り替えで全画面を別のキャラクタで埋められるが、マッパーを使ってできるのは面白くないので今回考えないことにする。
そこで今回の手法だが、パレットの色分けとキャラクタテーブルの描画中切り替えで実質キャラ数を4倍に増やしている。
まずパレット。
ファミコンのキャラごとの色数は4色だが、これを2色しか使わなければ例えば「白黒白黒」と「白白黒黒」の2種類のパレットを左右で使い分けることで1つのキャラで2つの絵を表示することができる。
比較のために単一のパレットで表示したものがこちらだ。

次にキャラクタテーブル。
「一度に256種類からしか選べない」と書いたが、この「一度に」の部分がポイントだ。
仕組みを詳しく言うと、定義できるキャラは256個の領域が2つあり、BGと8×8のスプライトはそれぞれその2つのどちらの領域からキャラを選ぶかを設定できる。同じ領域をBGとスプライトの両方で使うこともできる。
(なお今回関係ないが8×16のスプライトは一度に2キャラを使うので、どちらかの領域から選ぶのではなく、2つの領域を合わせた512キャラを2キャラづつの256組と見たものから選択する)
ここでこの「どちらの領域から選ぶか」の設定は画面の描画中にも切り替えることができる。これにより上半分と下半分で別のテーブルからキャラを選ぶことが可能なのだ。
比較のためにこの切り換えを行わなかったものがこちら。

基本的なアルゴリズムは以上だ。
実際のコードは下に置いたが、少々細かい解説を加えておく。
今回CHR-ROMでなくCHR-RAMを使っている。
そのためまず最初にPRG-ROMに持っている画像データを元にCHR-RAM上にパターンテーブルのデータを生成している。
これは事前に適切な並びにしたデータをCHR-ROMに持っておけば必要ない操作だ。
ただ、この並べ替えのプログラムは、ファミコン自体で行うか外部で事前に行うかの違いでどうせ書かなければならない。
ならば1つにまとめた方が扱いが楽なのでこうした。
下に書いたコードと、任意のモノクロビットマップを用意するだけで、NESASMでビルドできる。
このパターンテーブル書き込みのコードは少々複雑だが、やっていることは単純に、256×240ドットの1bitビットマップを適切な順に並び替えているだけだ。
パターンテーブル前半0x0000~0x0FFFが画像の上128ドット、後半のうち0x1000~0x0DFFが画像の残り。0x1F00~0x1FFFには後述の通り0x0F00~0x0FFFと同じものを重複して配置している。
「どちらの領域から選ぶか」の設定、つまりパターンテーブルのベースの切り換えだが、このような描画中のタイミング合わせはファミコンではふつう0番スプライトヒットフラグをポーリングすることで行う。
だが今回別の方法をとった。
0番スプライトヒットはBGとスプライトが共に不透明な点でしか起こらない。つまり画像の当該箇所が透明なとき使えない。
今回任意の画像を表示できるようにしようとしているのでこれは困る。
代わりにあまり知られていないスプライトオーバーフローフラグを使った。
これはライン上にスプライトが9個以上並んで横並び数制限を越えたときに立つはずだったフラグだ。
このフラグはスプライトが透明でも変わらず立つので、BGの内容に関係なく使えて便利だ。
ただ重大な欠点があって、このフラグ、アドレスの桁上がりの誤りで別のデータを参照するせいでまともに動かないという、致命的かつしょうもないバグが存在する。
使い物にならないと思うかもしれないが、バグの条件は厳密に判明している。
詳しくはNesDevの「PPU sprite evaluation」のページを読むとよいが、
スプライト番号順にライン内に存在するかをチェックし、8つめが見つかった次のチェックまでは正しく、その次から誤ったアドレスを参照しだす。
ここから、
・スプライト数が7つ以下なら、必ずフラグは立たない
・スプライト数が9つ以上の時、8つめと9つめのスプライト番号が隣り合っていれば、必ずフラグが立つ
ということが言える。
なので今回はスプライト番号順に連続した9つを目的の場所に置くことで確実にスプライトオーバーフローフラグを立てることにした。
というか残りも面倒なので同じ位置に置いた。
切り換えタイミングだが、画像を単純に上下に分割した場合、きっちりHBlank中に切り換えを起こすようにしないとこのように画面が乱れてしまう。

そのレベルで合わせるには描画中のスプライトの判定処理の流れをきちんと理解してコードを書かねばならないが、今回面倒なのでそこまではしなかった。
その代わり、1列分(8ライン)のキャラをパターンテーブル前半と後半で重複させておいた。これでこの8ラインの中で切り替えれば画面を乱さないようにできる。
画像データのインクルード部分は「画像データ(生)」と「画像データ(BMP)」の2種類を用意した。(生)は256×240bitの生データを使う時用で、(BMP)はWindowsのMSペイントで出力したBMPファイルをそのまま使えるようにヘッダ分だけずらした位置に配置するものだ。
これを使う時は、まずペイントで適当に描いた絵を上下反転

モノクロビットマップで保存して

できたファイル名をソースに書き込んでビルドするとできあがり。

ぜひ試してみてほしい。
(2019/07/17 追記)
CHR-RAMの初期化忘れで空白のつもりのスプライトがノイズとなって見えていたのを修正しました。
やはり実機で動作させないと初期化忘れは見逃しがちである。
最初に気づいたのは、これをベースに作った9×9ドットフォントの方で、こっちをまず修正した。
しかし全画面表示の方はVRAMのほぼ全域に書き込んでいたので同じ問題は起こらないと思いこんでしまった。
実のところ、スプライト用のメモリは初期化していなかったのだ。
こちらが最初にわざわざ乱数(Xorshift)で初期化するコードを入れて再現したもの。

再現したところで修正して、乱数初期化を消して出来上がり。(エミュレータでの見た目は前と変わりない)

修正箇所はここを
それで直して確認していたところスタックがオーバーフローしていることに気づき、調べてみると
・初期化部分のコードの終わりからVBlank割り込みルーチンに流れ込む
・VBlank割り込みルーチンは最後にリターンせずにVBlank待ちに入る
というめちゃくちゃなコードであった。他にスタックを使う関数呼び出しなどしていなかったので動いてしまっていた。
これも直し、あとついでにCHR-RAMに書き込むとこのラベルがsetChrRom:だったのも修正した。
上のコードは修正済み。
なお、基本的に修正前の文章は残すように心がけているのだが、処理が面倒で適当に編集していたので申し訳ないが今回旧コードは残っていない。
でもモノクロ2値ならできることに気づいたのでやってみた。



まず前提として、ファミコンのBG(背景)面は32×30=960個のキャラクタで構成されるが、BGの(スプライトも)キャラクタパターンの指定は1バイトなので、一度に256種類からしか選べない。
よって普通にはBGだけでは256キャラ、画面の1/4強しか埋められず、スプライトを8×16ドットモードで最大の64個使って128キャラ用意してやっと384/960=40%にしかならない。
何らかのマッパーを使えば描画中にバンク切り替えで全画面を別のキャラクタで埋められるが、マッパーを使ってできるのは面白くないので今回考えないことにする。
そこで今回の手法だが、パレットの色分けとキャラクタテーブルの描画中切り替えで実質キャラ数を4倍に増やしている。
まずパレット。
ファミコンのキャラごとの色数は4色だが、これを2色しか使わなければ例えば「白黒白黒」と「白白黒黒」の2種類のパレットを左右で使い分けることで1つのキャラで2つの絵を表示することができる。
|
|
|

次にキャラクタテーブル。
「一度に256種類からしか選べない」と書いたが、この「一度に」の部分がポイントだ。
仕組みを詳しく言うと、定義できるキャラは256個の領域が2つあり、BGと8×8のスプライトはそれぞれその2つのどちらの領域からキャラを選ぶかを設定できる。同じ領域をBGとスプライトの両方で使うこともできる。
(なお今回関係ないが8×16のスプライトは一度に2キャラを使うので、どちらかの領域から選ぶのではなく、2つの領域を合わせた512キャラを2キャラづつの256組と見たものから選択する)
ここでこの「どちらの領域から選ぶか」の設定は画面の描画中にも切り替えることができる。これにより上半分と下半分で別のテーブルからキャラを選ぶことが可能なのだ。
比較のためにこの切り換えを行わなかったものがこちら。

基本的なアルゴリズムは以上だ。
実際のコードは下に置いたが、少々細かい解説を加えておく。
今回CHR-ROMでなくCHR-RAMを使っている。
そのためまず最初にPRG-ROMに持っている画像データを元にCHR-RAM上にパターンテーブルのデータを生成している。
これは事前に適切な並びにしたデータをCHR-ROMに持っておけば必要ない操作だ。
ただ、この並べ替えのプログラムは、ファミコン自体で行うか外部で事前に行うかの違いでどうせ書かなければならない。
ならば1つにまとめた方が扱いが楽なのでこうした。
下に書いたコードと、任意のモノクロビットマップを用意するだけで、NESASMでビルドできる。
このパターンテーブル書き込みのコードは少々複雑だが、やっていることは単純に、256×240ドットの1bitビットマップを適切な順に並び替えているだけだ。
パターンテーブル前半0x0000~0x0FFFが画像の上128ドット、後半のうち0x1000~0x0DFFが画像の残り。0x1F00~0x1FFFには後述の通り0x0F00~0x0FFFと同じものを重複して配置している。
「どちらの領域から選ぶか」の設定、つまりパターンテーブルのベースの切り換えだが、このような描画中のタイミング合わせはファミコンではふつう0番スプライトヒットフラグをポーリングすることで行う。
だが今回別の方法をとった。
0番スプライトヒットはBGとスプライトが共に不透明な点でしか起こらない。つまり画像の当該箇所が透明なとき使えない。
今回任意の画像を表示できるようにしようとしているのでこれは困る。
代わりにあまり知られていないスプライトオーバーフローフラグを使った。
これはライン上にスプライトが9個以上並んで横並び数制限を越えたときに立つはずだったフラグだ。
このフラグはスプライトが透明でも変わらず立つので、BGの内容に関係なく使えて便利だ。
ただ重大な欠点があって、このフラグ、アドレスの桁上がりの誤りで別のデータを参照するせいでまともに動かないという、致命的かつしょうもないバグが存在する。
使い物にならないと思うかもしれないが、バグの条件は厳密に判明している。
詳しくはNesDevの「PPU sprite evaluation」のページを読むとよいが、
スプライト番号順にライン内に存在するかをチェックし、8つめが見つかった次のチェックまでは正しく、その次から誤ったアドレスを参照しだす。
ここから、
・スプライト数が7つ以下なら、必ずフラグは立たない
・スプライト数が9つ以上の時、8つめと9つめのスプライト番号が隣り合っていれば、必ずフラグが立つ
ということが言える。
なので今回はスプライト番号順に連続した9つを目的の場所に置くことで確実にスプライトオーバーフローフラグを立てることにした。
というか残りも面倒なので同じ位置に置いた。
切り換えタイミングだが、画像を単純に上下に分割した場合、きっちりHBlank中に切り換えを起こすようにしないとこのように画面が乱れてしまう。

そのレベルで合わせるには描画中のスプライトの判定処理の流れをきちんと理解してコードを書かねばならないが、今回面倒なのでそこまではしなかった。
その代わり、1列分(8ライン)のキャラをパターンテーブル前半と後半で重複させておいた。これでこの8ラインの中で切り替えれば画面を乱さないようにできる。
画像データのインクルード部分は「画像データ(生)」と「画像データ(BMP)」の2種類を用意した。(生)は256×240bitの生データを使う時用で、(BMP)はWindowsのMSペイントで出力したBMPファイルをそのまま使えるようにヘッダ分だけずらした位置に配置するものだ。
これを使う時は、まずペイントで適当に描いた絵を上下反転

モノクロビットマップで保存して

できたファイル名をソースに書き込んでビルドするとできあがり。

ぜひ試してみてほしい。
; 全画面に任意のモノクロ2値のビットマップを表示する
; NESASM v3.1でビルド、NNNesterJとNintendulatorで動作確認している。
; INESヘッダー
.inesprg 1 ; PRG-ROM容量(0x4000=16KB単位)
.ineschr 0 ; CHR-ROM容量、CHR-RAMでは0
.inesmir 0 ; Don'tCare(水平ミラーリング)
.inesmap 0 ; マッパーなし
.bank 0
; レジスタ名定義(してたりしてなかったり、してるのに使ってなかったり)
PPUMASK = $2001
PPUADDR = $2006
PPUDATA = $2007
;(グローバル)変数定義
;RAMの連続したアドレスにラベルを付けたいだけなのだがどうもROMに値をとっている。
;どうせ上書きされる場所で実害ないからいいけど、もっとまともな方法があるのだろうか…。
;まあとりあえず今回はこのままでいいや。
.org $0000
chrptr .dw 0 ;CHR-RAM書き込み時のポインタ
cnt1 .db 0 ;カウンタ変数
sprpos .db 0 ;スプライト書き込み時のポインタ
posx2 .db 0 ;何だっけ
;割り込みベクタ
.bank 1 ; 0x2000(8KB)単位。ファイルが全16KBなのでbank3でなく1。
.org $FFFA ; BFFAだけど。
.dw intvb ; VBlank割り込み
.dw init ; リセット割り込み。起動時とリセット時
.dw 0 ; ハードウェア割り込みとソフトウェア割り込み
.bank 0
.org $8000
init:
; PPUレジスタに書き込みできるようになるまで時間がかかるらしいので待つ
ldy #25
.loop:
dex
bne .loop ;5*256=1280 ;30000 30000/1280=23.4375
dey
bne .loop
.loop2:
lda $2002 ; VBlank待ち
bpl .loop2
; VBlankのNMIを無効
lda #%00110000
; ^||||||| VBlank開始時にNMI発生
; ^|||||| PPUマスター/スレーブ
; ^||||| スプライトサイズ 0:8x8 1:8x16
; ^|||| BGパターンテーブル
; ^||| スプライトパターンテーブルアドレス
; ^|| VRAMアドレス増分 0:1 1:32
; ^^ ベースネームテーブルアドレス 0:2000 1:2400 2:2800 3:2C00
sta $2000
lda #%00000110 ; 初期化中はスプライトとBGを表示OFFにする
sta $2001
; パレットRAM=$3F00
lda #$3F
sta $2006
lda #$00
sta $2006
ldx #$00
loadPal:
lda pallet, x
sta $2007
inx
cpx #32
bne loadPal
lda #50
sta posx2
setBg: ;BGのネームテーブルを適宜セット
; 00,01,...0F,00,01,...0F
; 10,11,...1F,10,11,...1F
; ...
; F0,
; 00,
; ...
; D0,
; $2000: BG1 name table
lda #$20
sta $2006 ;PPU_ADDR
lda #$00
sta $2006
ldx $00
lda #30
sta cnt1
ldy #0
bgLoop0:
ldx #16
bgLoop1:
sty PPUDATA
iny
dex
bne bgLoop1
tya
sec
sbc #$10
tay
ldx #16
bgLoop2:
sty PPUDATA
iny
dex
bne bgLoop2
dec cnt1
bne bgLoop0
;CHR-RAM書き込み
setChrRam:
lda #0
sta chrptr
lda #high(chr)
sta chrptr+1
ldy #$00
sty PPUADDR
sty PPUADDR
ldx #30
.loop
lda [chrptr],Y
sta PPUDATA
; 8: タイル縦px数 000yxxxx ~ 111yxxxx
;Y+=20
tya
clc
adc #$20
tay
bcc .loop ; if no carry loop
; 2: 色bit 0000xxxx, 0001xxxx
;Y+=10
tya
eor #$10
tay
and #$10
bne .loop ;if !y&10 loop
; 16: 横タイル数 00000000 ~ 00001111
iny
tya
and #$0F
bne .loop; if neq loop
; 30: 縦タイル数
ldy #$00
inc (chrptr+1)
dex
bne .loop
clearChrRam: ;背景に使わない16キャラ(=256バイト)をゼロクリア (2個スプライトに使う)
; PPUADDR = #$1E00
lda #0
.loop:
sta PPUDATA
dey
bne .loop
setChrRamOverlap: ;BGのベースを変える境目でオーバーラップさせておく
; PPUADDR = $1F00
lda #high(chr)+$0F
sta chrptr+1
ldy #0
.loop
lda [chrptr],Y
sta PPUDATA
; 8:タイル縦 000yxxxx ~ 111yxxxx
;Y+=20
tya
clc
adc #$20
tay
bcc .loop ; if no carry loop
; 2:パレットbit 0000xxxx, 0001xxxx
;Y+=10
tya
eor #$10
tay
and #$10
bne .loop ;if !y&10 loop
; 16: 横タイル数 00000000 ~ 00001111
iny
tya
and #$0F
bne .loop; if neq loop
;属性テーブル (BGパレット)
ldy #$23
sty PPUADDR
ldy #$C0
sty PPUADDR
ldx #8
attrloop:
lda #$00 ; 左半分 00 00 00 00
sta PPUDATA
sta PPUDATA
sta PPUDATA
sta PPUDATA
lda #$55 ; 右半分 01 01 01 01
sta PPUDATA
sta PPUDATA
sta PPUDATA
sta PPUDATA
dex
bne attrloop
;scroll
lda #0
sta $2005
sta $2005
; スプライト配置
lda #$00
sta $2003 ; OAMADDR
; Byte 0-3: Y,TileNo,Attr,X
; Attr:
;76543210
;||||||||
;||||||++- Palette (4 to 7) of sprite
;|||+++--- Unimplemented
;||+------ Priority (0: in front of background; 1: behind background)
;|+------- Flip sprite horizontally
;+-------- Flip sprite vertically
ldx #64
sprLoop:
lda #$7B ; BGのベースの切り替わりあたり
sta $2004
lda #$E1 ; 1:E0 空スプライト
sta $2004
lda #$00
sta $2004
lda #$B0 ; 特に意味はない
sta $2004
dex
bne sprLoop
; 表示開始
lda #%00011110 ; スプライトとBGの表示をONにする
sta $2001
lda #%11110000 ;VBlank割り込みをON
sta $2000
waitInt:
jmp waitInt
intvb:
lda #%11100000 ;BGパターンテーブルベース=0
sta $2000
waitVBlankEnd: ;VBlank終了時点でスプライトオーバーフローフラグが消えるのを待つ
lda $2002
and #$20
bne waitVBlankEnd
waitSprOvr: ;スプライトオーバーフローフラグが立つのを待ってBGパターンを切り替える
lda $2002
and #$20
beq waitSprOvr
lda #%11110000 ;BGパターンテーブルベース=1
sta $2000
rti
;色0・色1
C0 = $0F
C1 = $30
pallet:
.db C0, C1, C0, C1, C0, C0, C1, C1, C0, C0, C0, C0, C0, C0, C0, C0
.db C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0
; .db 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30
; .db 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30
; 画像データ(生)
; .bank 1
; .org $A000
;chr:
; .incbin "image.bin" ; 画像。256×240/8=0x1E00バイト
; 画像データ(BMP)
.bank 1
.org $A0C2
.incbin "image.bmp"
.org $A100
chr:
; NESASM v3.1でビルド、NNNesterJとNintendulatorで動作確認している。
; INESヘッダー
.inesprg 1 ; PRG-ROM容量(0x4000=16KB単位)
.ineschr 0 ; CHR-ROM容量、CHR-RAMでは0
.inesmir 0 ; Don'tCare(水平ミラーリング)
.inesmap 0 ; マッパーなし
.bank 0
; レジスタ名定義(してたりしてなかったり、してるのに使ってなかったり)
PPUMASK = $2001
PPUADDR = $2006
PPUDATA = $2007
;(グローバル)変数定義
;RAMの連続したアドレスにラベルを付けたいだけなのだがどうもROMに値をとっている。
;どうせ上書きされる場所で実害ないからいいけど、もっとまともな方法があるのだろうか…。
;まあとりあえず今回はこのままでいいや。
.org $0000
chrptr .dw 0 ;CHR-RAM書き込み時のポインタ
cnt1 .db 0 ;カウンタ変数
sprpos .db 0 ;スプライト書き込み時のポインタ
posx2 .db 0 ;何だっけ
;割り込みベクタ
.bank 1 ; 0x2000(8KB)単位。ファイルが全16KBなのでbank3でなく1。
.org $FFFA ; BFFAだけど。
.dw intvb ; VBlank割り込み
.dw init ; リセット割り込み。起動時とリセット時
.dw 0 ; ハードウェア割り込みとソフトウェア割り込み
.bank 0
.org $8000
init:
; PPUレジスタに書き込みできるようになるまで時間がかかるらしいので待つ
ldy #25
.loop:
dex
bne .loop ;5*256=1280 ;30000 30000/1280=23.4375
dey
bne .loop
.loop2:
lda $2002 ; VBlank待ち
bpl .loop2
; VBlankのNMIを無効
lda #%00110000
; ^||||||| VBlank開始時にNMI発生
; ^|||||| PPUマスター/スレーブ
; ^||||| スプライトサイズ 0:8x8 1:8x16
; ^|||| BGパターンテーブル
; ^||| スプライトパターンテーブルアドレス
; ^|| VRAMアドレス増分 0:1 1:32
; ^^ ベースネームテーブルアドレス 0:2000 1:2400 2:2800 3:2C00
sta $2000
lda #%00000110 ; 初期化中はスプライトとBGを表示OFFにする
sta $2001
; パレットRAM=$3F00
lda #$3F
sta $2006
lda #$00
sta $2006
ldx #$00
loadPal:
lda pallet, x
sta $2007
inx
cpx #32
bne loadPal
lda #50
sta posx2
setBg: ;BGのネームテーブルを適宜セット
; 00,01,...0F,00,01,...0F
; 10,11,...1F,10,11,...1F
; ...
; F0,
; 00,
; ...
; D0,
; $2000: BG1 name table
lda #$20
sta $2006 ;PPU_ADDR
lda #$00
sta $2006
ldx $00
lda #30
sta cnt1
ldy #0
bgLoop0:
ldx #16
bgLoop1:
sty PPUDATA
iny
dex
bne bgLoop1
tya
sec
sbc #$10
tay
ldx #16
bgLoop2:
sty PPUDATA
iny
dex
bne bgLoop2
dec cnt1
bne bgLoop0
;CHR-RAM書き込み
setChrRam:
lda #0
sta chrptr
lda #high(chr)
sta chrptr+1
ldy #$00
sty PPUADDR
sty PPUADDR
ldx #30
.loop
lda [chrptr],Y
sta PPUDATA
; 8: タイル縦px数 000yxxxx ~ 111yxxxx
;Y+=20
tya
clc
adc #$20
tay
bcc .loop ; if no carry loop
; 2: 色bit 0000xxxx, 0001xxxx
;Y+=10
tya
eor #$10
tay
and #$10
bne .loop ;if !y&10 loop
; 16: 横タイル数 00000000 ~ 00001111
iny
tya
and #$0F
bne .loop; if neq loop
; 30: 縦タイル数
ldy #$00
inc (chrptr+1)
dex
bne .loop
clearChrRam: ;背景に使わない16キャラ(=256バイト)をゼロクリア (2個スプライトに使う)
; PPUADDR = #$1E00
lda #0
.loop:
sta PPUDATA
dey
bne .loop
setChrRamOverlap: ;BGのベースを変える境目でオーバーラップさせておく
; PPUADDR = $1F00
lda #high(chr)+$0F
sta chrptr+1
ldy #0
.loop
lda [chrptr],Y
sta PPUDATA
; 8:タイル縦 000yxxxx ~ 111yxxxx
;Y+=20
tya
clc
adc #$20
tay
bcc .loop ; if no carry loop
; 2:パレットbit 0000xxxx, 0001xxxx
;Y+=10
tya
eor #$10
tay
and #$10
bne .loop ;if !y&10 loop
; 16: 横タイル数 00000000 ~ 00001111
iny
tya
and #$0F
bne .loop; if neq loop
;属性テーブル (BGパレット)
ldy #$23
sty PPUADDR
ldy #$C0
sty PPUADDR
ldx #8
attrloop:
lda #$00 ; 左半分 00 00 00 00
sta PPUDATA
sta PPUDATA
sta PPUDATA
sta PPUDATA
lda #$55 ; 右半分 01 01 01 01
sta PPUDATA
sta PPUDATA
sta PPUDATA
sta PPUDATA
dex
bne attrloop
;scroll
lda #0
sta $2005
sta $2005
; スプライト配置
lda #$00
sta $2003 ; OAMADDR
; Byte 0-3: Y,TileNo,Attr,X
; Attr:
;76543210
;||||||||
;||||||++- Palette (4 to 7) of sprite
;|||+++--- Unimplemented
;||+------ Priority (0: in front of background; 1: behind background)
;|+------- Flip sprite horizontally
;+-------- Flip sprite vertically
ldx #64
sprLoop:
lda #$7B ; BGのベースの切り替わりあたり
sta $2004
lda #$E1 ; 1:E0 空スプライト
sta $2004
lda #$00
sta $2004
lda #$B0 ; 特に意味はない
sta $2004
dex
bne sprLoop
; 表示開始
lda #%00011110 ; スプライトとBGの表示をONにする
sta $2001
lda #%11110000 ;VBlank割り込みをON
sta $2000
waitInt:
jmp waitInt
intvb:
lda #%11100000 ;BGパターンテーブルベース=0
sta $2000
waitVBlankEnd: ;VBlank終了時点でスプライトオーバーフローフラグが消えるのを待つ
lda $2002
and #$20
bne waitVBlankEnd
waitSprOvr: ;スプライトオーバーフローフラグが立つのを待ってBGパターンを切り替える
lda $2002
and #$20
beq waitSprOvr
lda #%11110000 ;BGパターンテーブルベース=1
sta $2000
rti
;色0・色1
C0 = $0F
C1 = $30
pallet:
.db C0, C1, C0, C1, C0, C0, C1, C1, C0, C0, C0, C0, C0, C0, C0, C0
.db C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0
; .db 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30
; .db 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30, 0x03, 0x11, 0x15, 0x30
; 画像データ(生)
; .bank 1
; .org $A000
;chr:
; .incbin "image.bin" ; 画像。256×240/8=0x1E00バイト
; 画像データ(BMP)
.bank 1
.org $A0C2
.incbin "image.bmp"
.org $A100
chr:
(2019/07/17 追記)
CHR-RAMの初期化忘れで空白のつもりのスプライトがノイズとなって見えていたのを修正しました。
やはり実機で動作させないと初期化忘れは見逃しがちである。
最初に気づいたのは、これをベースに作った9×9ドットフォントの方で、こっちをまず修正した。
しかし全画面表示の方はVRAMのほぼ全域に書き込んでいたので同じ問題は起こらないと思いこんでしまった。
実のところ、スプライト用のメモリは初期化していなかったのだ。
こちらが最初にわざわざ乱数(Xorshift)で初期化するコードを入れて再現したもの。

再現したところで修正して、乱数初期化を消して出来上がり。(エミュレータでの見た目は前と変わりない)

修正箇所はここを
setChrRomOverlap: ;BGのベースを変える境目でオーバーラップさせておく
ldy #$1F
sty PPUADDR
ldy #$00
sty PPUADDR
こう。ldy #$1F
sty PPUADDR
ldy #$00
sty PPUADDR
clearChrRam: ;16キャラをゼロクリア
; PPUADDR = #$1E00
lda #0
.loop:
sta PPUDATA
dey
bne .loop
setChrRamOverlap: ;BGのベースを変える境目でオーバーラップさせておく
; PPUADDR = $1F00 ←アドレスが直前からの続きになったのでセット不要に
lda #high(chr)+$0F
sta chrptr+1
ldy #0
.loop
lda [chrptr],Y
sta PPUDATA
; PPUADDR = #$1E00
lda #0
.loop:
sta PPUDATA
dey
bne .loop
setChrRamOverlap: ;BGのベースを変える境目でオーバーラップさせておく
; PPUADDR = $1F00 ←アドレスが直前からの続きになったのでセット不要に
lda #high(chr)+$0F
sta chrptr+1
ldy #0
.loop
lda [chrptr],Y
sta PPUDATA
それで直して確認していたところスタックがオーバーフローしていることに気づき、調べてみると
・初期化部分のコードの終わりからVBlank割り込みルーチンに流れ込む
・VBlank割り込みルーチンは最後にリターンせずにVBlank待ちに入る
というめちゃくちゃなコードであった。他にスタックを使う関数呼び出しなどしていなかったので動いてしまっていた。
これも直し、あとついでにCHR-RAMに書き込むとこのラベルがsetChrRom:だったのも修正した。
上のコードは修正済み。
なお、基本的に修正前の文章は残すように心がけているのだが、処理が面倒で適当に編集していたので申し訳ないが今回旧コードは残っていない。
2015年04月13日 01:20
アタリショックで有名なAtari2600。
フェアチャイルドチャンネルFに続くカートリッジ交換式ゲーム機であり、ファミコンの前世代機としてアメリカで流行していたらしい。
日本では流行らなかったため日本語での情報が少ないのが残念だ。とりあえず日本語で一番詳しくなるくらいに仕様をまとめてみる。
性能が分かりやすいよう、日本人に馴染みの深いファミコンとの比較を各所に入れた。
6507、1.19MHz
この6507というCPUは6502の機能削減版で、割り込みが存在しないことと、メモリ領域が6502の64KBに対し8KBしかない点が違いである。
ファミコンのCPUもBCD演算が無い他は機能は6502と同一なので単純に比較すると、ファミコンは1.79MHzなので、演算性能はファミコンの2/3と言えそうだ。
が、Atari2600はファミコンと違い画面の描画にCPUを使うため、実質的に使用できるCPU時間は非表示期間中のみである。
Atari2600の一般的な解像度は縦192pxなので、262-192=70と、1/4程度の時間しか演算に使えない。
ファミコンは単純な(ラスタスクロールなどしない)静止画を表示する分にはCPUを使わないので、これと比較すると演算性能はファミコンの1/6程度とも言える。
なおAtari2600も画面の描画を止めればCPU時間を演算に割くことができる。
例えば「Vedeo Chess」ではAIの思考中は画面がチカチカする単色になる。
https://www.youtube.com/watch?v=wdpRDYQgm84
・メモリ
ワーキングRAM128バイト、VRAM数バイト
ファミコンはワーキングRAMが2KB、VRAMが2KBである。
ワーキングRAMが6502の高速なゼロページ領域に収まっているのでちょっと嬉しい。
なおワーキングRAMは6502のスタック用の1ページにも重複してマッピングされているという面白い構造。
128バイトでは少ないということでカートリッジ側に128バイトや256バイトのRAMを積むこともあったようだ。
横解像度は回路で決まっており、160ドットである。
問題は縦で、(NTSCでは)192pxという値がよく見られるが、これはこの値が多くのゲームで使われているとか、この値が推奨されている程度の意味だろう。
Atari2600の画面描画の特徴として、メモリには常に走査線1ライン分の情報しか持っていない。
そこで、1ラインごとに、HBLANKの間にメモリ上の情報を書き換えて画面を作るのが基本の操作となる。描画中に何もしなければ縦線しか描けない。
つまり、縦方向にどれだけの長さ描画するかはプログラムによって決まる。
これより、NTSCの規格上許されている最大値をAtari2600の縦解像度の値とするのが妥当だろう。
(規格外の走査線数を出力することすら可能だが、さすがにそれは考えないことにする)
NTSCの規格上、映像信号を入れられるのは1フィールドあたり242.5ラインだが、NTSCの規格は2フィールドを半ラインずらすことになっているのをAtari2600などのゲーム機はずらしていないので、242pxとなる。
なおこの192pxというのは242の約79%にあたる。
横方向の160pxは44.7μ秒ほどで、描画期間約53.4μ秒の83.5%ほど。
当時のTVで画面内に表示できる範囲はこの程度だったのであろう。
さてではこの160×242ドットに自由に絵が描けるかというとそうではない。
画面構成は後述するが、1ドット単位で絵を作れる構成要素は「オブジェクト」という、今で言うスプライトのみである。
ファミコンのスプライトに横8枚制限があるのと同様、Atari2600もオブジェクトだけで画面全体を覆うことはまず不可能である。(たぶん本当に不可能だと思うが自信がない。後述)
もう1つの構成要素としてPlayFieldという今で言う背景面のようなものがあるが、こちらは横解像度が40px、つまり160pxの画面に対し4pxごとにしか絵を描けない。
これにより、Atari2600の画面はやけに解像度の低い背景の上にキャラクターがぽつぽつと置かれている独特な見た目になる。
ただし、横は4ドット単位だが縦方向には1ドットごとに絵を書き換えられるため、上手いプログラムでは巧妙に横方向の解像度の低さを感じさせない絵柄を作っている。
例えば「Pitfall!」の穴や木の表現が上手い。

・画面構成
BackGround:
以降の全てが無い部分の色
PlayField:
今で言う背景面
単色。(Scoreモードでない場合)
横解像度40px。
拡大縮小やスクロールなどの機能は無い。
「Scoreモード」では左右それぞれがPlayer0/1と同色で表示される。
オブジェクト:
今で言うスプライト。3種類5個のオブジェクトがある。
Player0 / Player1
横8px、それぞれ単色。
一定間隔の2個または3個に増殖したり横幅2倍・4倍で表示する機能あり。
具体的には、以下の8通り。2倍幅2個などといった自由な組み合わせはできない。

4倍幅モードでは横32pxと画面横幅の1/5。2つのPlayerで計2/5。ファミコンではスプライト上限横8個で画面の1/4なので、巨大キャラを動かすことについてはファミコンに優っていると言えなくもない。
(まあファミコンなら背景面を使うところだが)
Missile0 / Missile1
横1px
横幅2・4・8倍表示が可能
対応するPlayerと同色
個数・間隔もそれぞれのPlayerと同じ
Ball
横1px
横幅2・4・8倍表示が可能
色はPlayFieldと同色
増殖はしない
・発色数
扱える色は、オリジナルのNTSC版では
(15色相+無彩色)×明度(?)8段階 = 128色
PAL版では色信号の周波数が約15/12倍である関係か、
(12+無)×8=104色
と少々少ないが、どちらもファミコンの54色と比較すると多い。
SECAMでは仕組みが全く違い明度に対応したデジタル8色のみ。
なお「明度」と書いたが、彩度も連動して変わるかもしれない。(ファミコンがそうであるように)
同時発色数は、1ライン中に同時に出せる色は基本的には以下の4色である。
・BackGround
・PlayField・Ball
・Player0・Missile0
・Player1・Missile1
ただしライン中にレジスタを書き換えればこの限りではない。
例えばAcidDropというゲームがあり、横1列に色違いの7区画+両端の色を表示している。
https://www.youtube.com/watch?v=5Po47y0ALpU
また、頑張れば下図のように横15色も可能である。

なお縦方向には制限はないので、ファミコンと違い画面中同時発色数は扱える全ての色128色が出力可能。縦方向のグラデーションはAtari2600の特徴である。
・オブジェクトの横位置の指定
現代の感覚で考えれば、オブジェクトのX座標をレジスタに指定するとその位置に書いてくれる仕組みがあると思うだろう。
否、そんな軟弱な仕組みではない。
走査線を描画中にここぞというタイミングで各オブジェクトのリセットレジスタに書き込むことで、その地点のX座標に(次ラインから)オブジェクトが表示されるようになるのである。
とはいえCPUの1クロックは画面上の3ドットに相当する。また時間調整のためのループは最短で1周に5クロックが必要であり、都合15ドットごとにしか書込みができない。
そこで微調整機能が用意されている。
5種のオブジェクト(Player0, Player1, Missile0, Missile1, Ball)それぞれについて横位置の差分を登録し、HMOVEレジスタに書き込むことで次ラインでの表示位置がその値だけずらせる。
これが+7~-8まで設定できるので、任意の位置にオブジェクトを表示することが可能となる。
またBallやMissileを表示したままラインごとにHMOVEをすることで斜めの線が表示できる。上のPitfall!の画像にもある。ファミコンにはできない芸当だ。
なおこのHMOVEへの書込みを行うと、次ラインの左端8ドットが黒になるという妙な仕様があり、これによる櫛状の横線がAtari2600の画像の特徴となっている。
・PlayField内容の書き換え
実は上で横40pxと言ったもののデータは20px分しか無く、1ライン中何もしなければ右半分が左半分と同じか、あるいは左半分を左右反転させたもので表示される。
横40pxをフルに使うにはライン中の書き換えが必須である。
なおこれは最初から想定されていた操作だろう。PlayFieldのScoreモードはライン中書き換えを前提としている。
・Player内容の書き換え
Playerを複数表示モードにした状態で、1つ表示された後に内容を書き換えると、2つ目に別の内容を表示できる。
これを最大限に駆使すると、Player0/1を3つづつ交互に並ぶようにして横48pxに自由に絵が描ける。
ただし、このためにはメモリ(レジスタ)へ4回の書込みが必要になる。(CPUのレジスタとメモリにマップされたレジスタが紛らわしいな…)
6502では(CPUの)レジスタからメモリへの書込みが最速で3クロック掛かる。画面のドット数で言うと9ドット分である。
かつ、6502での最速の書込みはレジスタが3つしか無いため3連続が限度である。
よって普通にはこの表示はできないことになる。
この状況を打破するためには裏レジスタを使う必要がある。
裏レジスタを理解するには、Vertical Delay(垂直遅延)機能の理解が必要である。
この機能を有効にすると、Player0/1の書き込みはそれぞれの裏レジスタになされ、他方への書き込みを契機に表レジスタに移される。
つまり、Player0/1の内容の書込みを他方への書込みまで遅延させるという動作をする。
「Vertical」Delayの名は、1ラインごとにPlayer0と1へ交互に書き込む場合に1ライン分縦に遅延することになることによる。
通常の使い道としては、Player0と1を両方1ラインごとに書き換えるのは処理が重いため、1ライン置きで交互に書き換えるようにしたとき、縦位置がずれないように使うものだろう。
これを利用して、P0,P1両方の遅延を有効にし、非表示期間中にPlayer0,1に加えPlayer0の裏レジスタに表示内容を入れておく。
すると、
・オブジェクト増殖
通常はリセットレジスタに書き込んだ次のラインからオブジェクトが表示されるのだが、Playerの2個・3個表示モードでは頭の1つを除いて同ラインに表示できる。またBallも同ラインから表示される。
これを利用して、ライン中に複数回リセットをすることで1ライン中に複数個のPlayerを表示できる…らしい。
これを使えばひょっとして画面を完全にオブジェクトで覆うこともできたりするのだろうか。恐らくは処理時間が足りず不可能だと思うが、この動作についてはいまいち仕様が理解できていないため自信がない。仮にできても意味のある絵を表示するのは困難だろう。
この動作については下記「TIA Hardware Notes」が詳しいので気になる人はこれにあたってほしい。
数種類のノイズと中途半端な音程の矩形波を出せるチャンネルが2チャンネル。
あまり興味が無いので説明は割愛する。
「Tone Toy」というHomebrewなソフトがあるので試してみると楽しいだろう。
https://www.youtube.com/watch?v=Zco1hnyDiVI
・衝突検知
ファミコンにも0番スプライトと背景面の衝突判定があるが、Atari2600ではPlayField, Ball, Player0/1, Missile0/1の6つの物体の全ての組合せ15通りについて衝突判定ができる。
6502の「BIT」命令で読みやすいよう6,7bit目が立つ。
・タイマー
ファミコンにもあれば良かったのに…。
Atari 2600 Specifications http://problemkaputt.de/2k6specs.htm
Atari 2600 TIA Hardware Notes http://www.atarihq.com/danb/files/TIA_HW_Notes.txt
フェアチャイルドチャンネルFに続くカートリッジ交換式ゲーム機であり、ファミコンの前世代機としてアメリカで流行していたらしい。
日本では流行らなかったため日本語での情報が少ないのが残念だ。とりあえず日本語で一番詳しくなるくらいに仕様をまとめてみる。
性能が分かりやすいよう、日本人に馴染みの深いファミコンとの比較を各所に入れた。
スペック
・CPU6507、1.19MHz
この6507というCPUは6502の機能削減版で、割り込みが存在しないことと、メモリ領域が6502の64KBに対し8KBしかない点が違いである。
ファミコンのCPUもBCD演算が無い他は機能は6502と同一なので単純に比較すると、ファミコンは1.79MHzなので、演算性能はファミコンの2/3と言えそうだ。
が、Atari2600はファミコンと違い画面の描画にCPUを使うため、実質的に使用できるCPU時間は非表示期間中のみである。
Atari2600の一般的な解像度は縦192pxなので、262-192=70と、1/4程度の時間しか演算に使えない。
ファミコンは単純な(ラスタスクロールなどしない)静止画を表示する分にはCPUを使わないので、これと比較すると演算性能はファミコンの1/6程度とも言える。
なおAtari2600も画面の描画を止めればCPU時間を演算に割くことができる。
例えば「Vedeo Chess」ではAIの思考中は画面がチカチカする単色になる。
https://www.youtube.com/watch?v=wdpRDYQgm84
・メモリ
ワーキングRAM128バイト、VRAM数バイト
ファミコンはワーキングRAMが2KB、VRAMが2KBである。
ワーキングRAMが6502の高速なゼロページ領域に収まっているのでちょっと嬉しい。
なおワーキングRAMは6502のスタック用の1ページにも重複してマッピングされているという面白い構造。
128バイトでは少ないということでカートリッジ側に128バイトや256バイトのRAMを積むこともあったようだ。
画面表示
・解像度横解像度は回路で決まっており、160ドットである。
問題は縦で、(NTSCでは)192pxという値がよく見られるが、これはこの値が多くのゲームで使われているとか、この値が推奨されている程度の意味だろう。
Atari2600の画面描画の特徴として、メモリには常に走査線1ライン分の情報しか持っていない。
そこで、1ラインごとに、HBLANKの間にメモリ上の情報を書き換えて画面を作るのが基本の操作となる。描画中に何もしなければ縦線しか描けない。
つまり、縦方向にどれだけの長さ描画するかはプログラムによって決まる。
これより、NTSCの規格上許されている最大値をAtari2600の縦解像度の値とするのが妥当だろう。
(規格外の走査線数を出力することすら可能だが、さすがにそれは考えないことにする)
NTSCの規格上、映像信号を入れられるのは1フィールドあたり242.5ラインだが、NTSCの規格は2フィールドを半ラインずらすことになっているのをAtari2600などのゲーム機はずらしていないので、242pxとなる。
なおこの192pxというのは242の約79%にあたる。
横方向の160pxは44.7μ秒ほどで、描画期間約53.4μ秒の83.5%ほど。
当時のTVで画面内に表示できる範囲はこの程度だったのであろう。
さてではこの160×242ドットに自由に絵が描けるかというとそうではない。
画面構成は後述するが、1ドット単位で絵を作れる構成要素は「オブジェクト」という、今で言うスプライトのみである。
ファミコンのスプライトに横8枚制限があるのと同様、Atari2600もオブジェクトだけで画面全体を覆うことはまず不可能である。(たぶん本当に不可能だと思うが自信がない。後述)
もう1つの構成要素としてPlayFieldという今で言う背景面のようなものがあるが、こちらは横解像度が40px、つまり160pxの画面に対し4pxごとにしか絵を描けない。
これにより、Atari2600の画面はやけに解像度の低い背景の上にキャラクターがぽつぽつと置かれている独特な見た目になる。
ただし、横は4ドット単位だが縦方向には1ドットごとに絵を書き換えられるため、上手いプログラムでは巧妙に横方向の解像度の低さを感じさせない絵柄を作っている。
例えば「Pitfall!」の穴や木の表現が上手い。

・画面構成
BackGround:
以降の全てが無い部分の色
PlayField:
今で言う背景面
単色。(Scoreモードでない場合)
横解像度40px。
拡大縮小やスクロールなどの機能は無い。
「Scoreモード」では左右それぞれがPlayer0/1と同色で表示される。
オブジェクト:
今で言うスプライト。3種類5個のオブジェクトがある。
Player0 / Player1
横8px、それぞれ単色。
一定間隔の2個または3個に増殖したり横幅2倍・4倍で表示する機能あり。
具体的には、以下の8通り。2倍幅2個などといった自由な組み合わせはできない。

4倍幅モードでは横32pxと画面横幅の1/5。2つのPlayerで計2/5。ファミコンではスプライト上限横8個で画面の1/4なので、巨大キャラを動かすことについてはファミコンに優っていると言えなくもない。
(まあファミコンなら背景面を使うところだが)
Missile0 / Missile1
横1px
横幅2・4・8倍表示が可能
対応するPlayerと同色
個数・間隔もそれぞれのPlayerと同じ
Ball
横1px
横幅2・4・8倍表示が可能
色はPlayFieldと同色
増殖はしない
・発色数
扱える色は、オリジナルのNTSC版では
(15色相+無彩色)×明度(?)8段階 = 128色
PAL版では色信号の周波数が約15/12倍である関係か、
(12+無)×8=104色
と少々少ないが、どちらもファミコンの54色と比較すると多い。
SECAMでは仕組みが全く違い明度に対応したデジタル8色のみ。
なお「明度」と書いたが、彩度も連動して変わるかもしれない。(ファミコンがそうであるように)
同時発色数は、1ライン中に同時に出せる色は基本的には以下の4色である。
・BackGround
・PlayField・Ball
・Player0・Missile0
・Player1・Missile1
ただしライン中にレジスタを書き換えればこの限りではない。
例えばAcidDropというゲームがあり、横1列に色違いの7区画+両端の色を表示している。
https://www.youtube.com/watch?v=5Po47y0ALpU
また、頑張れば下図のように横15色も可能である。

なお縦方向には制限はないので、ファミコンと違い画面中同時発色数は扱える全ての色128色が出力可能。縦方向のグラデーションはAtari2600の特徴である。
・オブジェクトの横位置の指定
現代の感覚で考えれば、オブジェクトのX座標をレジスタに指定するとその位置に書いてくれる仕組みがあると思うだろう。
否、そんな軟弱な仕組みではない。
走査線を描画中にここぞというタイミングで各オブジェクトのリセットレジスタに書き込むことで、その地点のX座標に(次ラインから)オブジェクトが表示されるようになるのである。
とはいえCPUの1クロックは画面上の3ドットに相当する。また時間調整のためのループは最短で1周に5クロックが必要であり、都合15ドットごとにしか書込みができない。
そこで微調整機能が用意されている。
5種のオブジェクト(Player0, Player1, Missile0, Missile1, Ball)それぞれについて横位置の差分を登録し、HMOVEレジスタに書き込むことで次ラインでの表示位置がその値だけずらせる。
これが+7~-8まで設定できるので、任意の位置にオブジェクトを表示することが可能となる。
またBallやMissileを表示したままラインごとにHMOVEをすることで斜めの線が表示できる。上のPitfall!の画像にもある。ファミコンにはできない芸当だ。
なおこのHMOVEへの書込みを行うと、次ラインの左端8ドットが黒になるという妙な仕様があり、これによる櫛状の横線がAtari2600の画像の特徴となっている。
高等技術
プログラミング技術が成熟するにつれ、走査線の描画中にレジスタ内容を書き換えることで同時表示内容の限界を超えるテクニックが使われるようになる。・PlayField内容の書き換え
実は上で横40pxと言ったもののデータは20px分しか無く、1ライン中何もしなければ右半分が左半分と同じか、あるいは左半分を左右反転させたもので表示される。
横40pxをフルに使うにはライン中の書き換えが必須である。
なおこれは最初から想定されていた操作だろう。PlayFieldのScoreモードはライン中書き換えを前提としている。
・Player内容の書き換え
Playerを複数表示モードにした状態で、1つ表示された後に内容を書き換えると、2つ目に別の内容を表示できる。
これを最大限に駆使すると、Player0/1を3つづつ交互に並ぶようにして横48pxに自由に絵が描ける。
ただし、このためにはメモリ(レジスタ)へ4回の書込みが必要になる。(CPUのレジスタとメモリにマップされたレジスタが紛らわしいな…)
6502では(CPUの)レジスタからメモリへの書込みが最速で3クロック掛かる。画面のドット数で言うと9ドット分である。
[--P0--][--P1--][--P0--][--P1--][--P0--][--P1--]
^P0 ^P1 ^P0 ^P1
このように、猶予は4ドット分しか無く、書込み以外の操作を入れる余裕は無い。^P0 ^P1 ^P0 ^P1
かつ、6502での最速の書込みはレジスタが3つしか無いため3連続が限度である。
よって普通にはこの表示はできないことになる。
この状況を打破するためには裏レジスタを使う必要がある。
裏レジスタを理解するには、Vertical Delay(垂直遅延)機能の理解が必要である。
この機能を有効にすると、Player0/1の書き込みはそれぞれの裏レジスタになされ、他方への書き込みを契機に表レジスタに移される。
つまり、Player0/1の内容の書込みを他方への書込みまで遅延させるという動作をする。
「Vertical」Delayの名は、1ラインごとにPlayer0と1へ交互に書き込む場合に1ライン分縦に遅延することになることによる。
通常の使い道としては、Player0と1を両方1ラインごとに書き換えるのは処理が重いため、1ライン置きで交互に書き換えるようにしたとき、縦位置がずれないように使うものだろう。
これを利用して、P0,P1両方の遅延を有効にし、非表示期間中にPlayer0,1に加えPlayer0の裏レジスタに表示内容を入れておく。
すると、
非表示期間:
lda A
sta P0 ; P0裏=A
lda B
sta P1 ;P0=A, P1裏=B
lda C
sta P0 ;P1=B, P0裏=C
lda D
ldx E
ldy F
描画中:
sta P1 ;P0=C, P1裏=D
stx P0 ;P1=D, P0裏=E
sty P1 ;P0=E, P1裏=F
sta P0 ;P1=F, P0裏=D(表示されないのでstxでもstyでもよい)
このように最速での4連続書込みが可能となる。lda A
sta P0 ; P0裏=A
lda B
sta P1 ;P0=A, P1裏=B
lda C
sta P0 ;P1=B, P0裏=C
lda D
ldx E
ldy F
描画中:
sta P1 ;P0=C, P1裏=D
stx P0 ;P1=D, P0裏=E
sty P1 ;P0=E, P1裏=F
sta P0 ;P1=F, P0裏=D(表示されないのでstxでもstyでもよい)
・オブジェクト増殖
通常はリセットレジスタに書き込んだ次のラインからオブジェクトが表示されるのだが、Playerの2個・3個表示モードでは頭の1つを除いて同ラインに表示できる。またBallも同ラインから表示される。
これを利用して、ライン中に複数回リセットをすることで1ライン中に複数個のPlayerを表示できる…らしい。
これを使えばひょっとして画面を完全にオブジェクトで覆うこともできたりするのだろうか。恐らくは処理時間が足りず不可能だと思うが、この動作についてはいまいち仕様が理解できていないため自信がない。仮にできても意味のある絵を表示するのは困難だろう。
この動作については下記「TIA Hardware Notes」が詳しいので気になる人はこれにあたってほしい。
その他機能
・音源数種類のノイズと中途半端な音程の矩形波を出せるチャンネルが2チャンネル。
あまり興味が無いので説明は割愛する。
「Tone Toy」というHomebrewなソフトがあるので試してみると楽しいだろう。
https://www.youtube.com/watch?v=Zco1hnyDiVI
・衝突検知
ファミコンにも0番スプライトと背景面の衝突判定があるが、Atari2600ではPlayField, Ball, Player0/1, Missile0/1の6つの物体の全ての組合せ15通りについて衝突判定ができる。
6502の「BIT」命令で読みやすいよう6,7bit目が立つ。
・タイマー
ファミコンにもあれば良かったのに…。
参考文献
ネット上でいろいろと探した気がするが、結果的に以下の2つにだいたい全てまとまっていた。Atari 2600 Specifications http://problemkaputt.de/2k6specs.htm
Atari 2600 TIA Hardware Notes http://www.atarihq.com/danb/files/TIA_HW_Notes.txt
2015年03月28日 13:04

ファミコン、Atari2600、GBAに続いて、ゲームボーイにも手を出してみた。
やっと最近の話になった。冒頭の画像ができたのが今年の2/11である。
GBは実機で動作させるのが簡単そうな気がするのが嬉しい点。
たぶんFlashROMを1個用意してカートリッジの線につなげばできると思う。
これがファミコンだと2個要るし、GBAだとなんだか特殊な回路が入ってるようなことを見た記憶がある。
CPUは80系ということで初めて触るCPUだった。
8080→8086→80x86なのでx86と同系統なのだが、x86は触ったことがあったがレジスタ名に多少面影を感じる程度であった。
新しいCPUのアセンブラを触るのは楽しいのでこれはこれでよい。
開発環境はググって見つけたGBDKというものを。
ルートに展開せよと書いてあるがどうせパスの日本語に非対応という事だろうといつもの場所に置いてみたが、どうもパスの指定の関係でルート直下でないと動かないようだ。
C言語コンパイラもついてるけど、やっぱりアセンブラだよねー。
アセンブラのサンプルコードも(1個だけ)ついてるので安心。
アセンブラの起動方法はなんだかんだで手間取ったが分かってみれば簡単だった。
..\bin\lcc -Wa-l -Wl-m -o temp.gb temp.s
ちょっと困ったことに、この開発環境でアセンブルすると先頭のヘッダあたりを自動で生成してくれるのだが、その中の割り込みハンドラ部分が使い方がよく分からない。
このようなことはよくある。
CPU自体の割り込みの扱いはそのCPUを使おうとしている時点で大抵理解しているし、分からなかったらいくらでも資料があるので調べればよいだけなのだが、ある特定の開発環境でだけ使われるコードなど知らないし、調べようにもその付属のドキュメントが不足していたらそれまでである。だいたい、CPUが使いたいのであって開発環境を使いたいわけでもないのに、それを調べなくてはいけないというのはなかなかやる気を殺がれるものである。
まあコードを追って一応使える程度には理解した。
GBの面白いところとして、全画面に任意の画像が「頑張れば」表示できるという点がある。
ファミコンのようにキャラクタ数が足りず明らかに不可能でもなく、
GBAのようにビットマップモードがあって容易に可能でもない。
GBの全画面を覆うには20*18=360キャラクタ必要。
しかしGBで背景面に一度に表示できるキャラクタ数は256個。普通には不可能。
だが、表示する256個のキャラクタは384個の内から2通りの選び方ができる。
これを、画面の中程で切り替えることで、全画面に任意の表示ができるのである。
それで全画面表示をしたのが冒頭の画像なのだが、ユニークタイル数を数えてみたら181。普通にも表示できた画像であった。
単純に減色した画像では芸がないと思って綺麗にベタ塗りにしたのが災いした。
なおGBカラーになると同時表示キャラクタ数が512個になるので全画面に別のキャラクタを表示することが容易に可能になる。その代わり色数の面で全画面任意とはいかなくなるのだが。
あと画像表示の前に試したハローワールド

と、7×7(8×8)ドットでは芸がないので5×7ドットのテキストを表示…してみようとしたが2文字で力尽きた。

あとエミュレータはGBAでも使っていたので何も考えずVisualBoyAdvanceを使ったが、どうもGBエミュレータではBGBなるものが再現度が高いようだ。
2015年03月24日 01:19

ファミコンとGBAに続き、Atari2600の開発を始めてみた。
…と思っていたのだが、Twitterのログやファイルの日付を見ると2011年5月あたり、GBAより前だった。やってすぐに書かないとこういうことになる。
Atari2600と聞いてもピンと来ない人もいるかもしれない、というか自分自身始める前はおぼろげに名前を聞いたことがあるかという程度だった。
日本では流行らなかったけれどアメリカの方ではファミコンの前世代機として一時代を築いた名機らしいのだが。
このAtari2600、当然(総合的な)性能はファミコンを大きく下回るわけだが、画面の描画方法が他のゲーム機には無い独特なもので、挑戦しがいのある機械である。
詳細な仕様については別に書こうかと思うが、ごく簡単にまとめると以下のようになる。 (4/13追記: 書いた→Atari2600詳細)
・画面解像度は横160ドット
・背景面は横4ドット単位でしか描けない
・スプライトは計5個
横8ドットの絵が描けるものが2個
横1ドットが3個
(スプライトは複製or拡大できるが、詳細は割愛)
縦は? と思うだろうが、ここがAtari2600最大のポイント。なんとVRAMを走査線1本分しか持っていないため、何もしなければ縦縞しか描けない。絵を作るには1ラインごとにVRAMを書き換える必要がある。
さらに走査線の描画中でもお構い無しにVRAMを書き換えることができて、スプライトの3倍複製モードで描画するそばから書き換えることで6キャラクタ分48ドットの任意の画像を表示する高等テクニックがある。(実際にはそれだけでは書き換え速度が足りないので裏レジスタを使うというさらにひとひねり必要。詳細は割愛)
そのテクニックを使って描いたのが冒頭の画像。
Atari2600のCPUはファミコンと同じ6502(正確にはその機能削減版の6507)なのでプログラムは楽であった。
なお開発環境はdasm、エミュレータはz26。
dasmでアセンブルするとなぜか出力ファイルの頭に謎の2バイトが付いてそれを消さないとz26で動かなかった。なんだかよく分からないがとりあえず備忘録的に書いておく。
あとつい最近またAtari2600をやりたくなって描いた絵を2枚。


プログラムもちょこちょこ変えているが外見はただ絵が変わっただけ。下の数字はエミュレータの機能。
はちゅねミクの絵で分かる人には分かると思うが、次はアニメーションさせたい。
2015年03月23日 02:04

ファミコンに続き、ゲームボーイアドバンス(以下GBA)の開発を始めてみた。3年前に。
Twitterのログやファイルの更新日時を見るに、2012年5/30~6/6くらいにやっていたようである。
なぜ今頃書くのかというと、そろそろネタを書きためているテキストファイルの見通しが悪くなってきたので整理するためだ。前回のCボタンユニットもそれである。
開発環境はdevkitProというもの。これが環境を整えるのがすごく楽だった。インストーラの指示に従ってあとは環境変数を書くだけ。
言語はとりあえずC言語。まあARMくらいの性能になると敢えてアセンブラを使う理由も薄いだろう。
ARMの命令セットは条件実行機能が面白そうだから使ってみたいんだけどね。
GBAではフルスクリーンの1枚絵を何の制限もなく表示できる。GBカラーやファミコンではできなかった芸当だ。

GBAの画面モードは色々あるが、これはGBAの最大色数である15bitカラー表示。背景数は1枚のみである。
使用した画像データはこちら。

GBAの色データはxBBBBBGGGGGRRRRRであるが、GIMPで出力する15bitカラーはxRRRRRGGGGGBBBBBしか出せず、RとBを入れ替えて書き出しているため、不気味な色になってしまった。上下反転はBMP画像の特徴。
もう1枚、こちらは256色インデックスカラー。このモードでは背景面が2枚持てる。

次はGIF画像を表示したり簡単な落ち物ゲームを作ろうかと思っていたようだが力尽きたようだ。
その後最近になって(14年8月~11月あたり)、1カートリッジプレイで無改造で実機で動作させることを目指してPICで通信実験などしていた。
そのうち再開したい。
2015年03月19日 01:56
「ニンテンドウ64のコントローラの特徴は?」と問えば十人が十人「3Dスティック」と答えるであろう。
アナログスティックを標準搭載したことでまさに「ゲームが変わる、ロクヨンが変える」を実感したものだ。
だが、3Dスティックの陰に隠れて重要な脇役がある。「Cボタンユニット」だ。
今日はCボタンユニットへの愛を語ろうと思う。
Cボタンユニットはただの4つのボタンでしかないのだが、配置の妙によってさまざまな使い方ができる。
よって4つのモノを選択するのに使用できる。
同様のことは十字キーでもできるだろうが、十字キーと違い斜め入力をしてしまう危険がないという利点がある。
またゲームキューブのABXYボタンでも可能だが、4つが整然と並んでいること、主となるABボタンが残せることが優っている。
例: ポケモンスタジアムのバトル中の技選択

ポケモンの4つの技を上下左右に対応付けることで、すべての技を1動作で選択することができる。
これにより、本編での十字キーでカーソルを動かしAで決定する形と異なり、画面上に選択肢を表示する必要がなくなる。1画面でポケモン対戦をするために相手の技選択を見せないことは必須事項である。
ゲームキューブのABXYボタンでも4つの選択は可能だが、並びが整然としていないため画面上の表示が不恰好になってしまうし、技の使用以外の動作がRやLなどの不自然なボタンに飛ばされてしまうだろう。
例: パイロットウィングスのカメラ

3Dスティックで自機を動かし、Cボタンユニットでカメラを動かせる。
十字キーでも同様の操作は可能だが、コントローラー左側には十字キーとLボタンしかないのに対し、右側にはCボタンユニットとRボタンに加えA・Bボタンがあるため、上位互換と言える。
コントローラーの左と真ん中を持つレフトポジションを使うソフトがほとんど存在しないことからその優位性は明白だ。
そして、必要な分だけ切り売りして自由な組み合わせで使える。

左右で回り込み
上下で寄り・引き
これは十字キーでも可能だが、斜めを押してしまう危険が無い分、上下と左右に別の動作を割り振るにはCボタンユニットの方が向いている。

上は視点変更
左右はカメラ操作
下はブレーキ

左下右で3つのアイテムを使用
上は視点変更および妖精の呼び出し
このような細かい分割もお手の物。
さらには、絶妙な配置によりA・Bボタンとも組み合わせることすら可能である。

A、下、右、左、上の5ボタンがこの順で音高に対応している。
Cボタンユニットが無ければこのオカリナ操作は生まれなかっただろう。
実際、のちのシリーズでも音楽を演奏するギミックは毎回のように入っているのだが、
風のタクトの風のタクトも、トワイライトプリンセスの遠吠えも、スカイウォードソードの琴も、皆オカリナのような自由演奏は不可能である。
(夢幻の砂時計の笛はまあ頑張れば演奏できなくもないかな…)
また64版ゼルダの移植版も、ゲームキューブは単純にA+Cスティックに対応させただけで操作しづらい上に音高の並び順が直感に反しており、3DS版では順にLRYXAと広範囲に散らばってしまっている

この作品では当時主流であった「6匹を見せ合い、相手を見た上でバトルに出す3匹を選ぶ」という対戦形式を公式に取り入れた。
このために、Cボタンユニットに加えA・Bボタンを合わせた6つのボタンを6匹のポケモンに割り当て、3匹の選択を行う。
ここでABとCボタンユニットがほぼ2×3の長方形に並んでいるため、順番が分かりやすい。
技の選択と同じく、ここでも相手に見せずに確実に選択することが必要になる。これもCボタンユニットにしかできない芸当だろう。
十字キーやアナログスティックで6方向を選ぶのはきちんと押せているか不安になる。GCのABXYLRでも可能だが並びが不自然である。
ポケモンスタジアムではオプションやメニューを開くのにC右や上が使われたり、
GBの操作でスタート・セレクトボタンをC左・下に割り振ることができる。
スマッシュブラザーズではCボタンはすべてジャンプに割り当てられている。
・6点で点字を打つのに便利そうだ。
アナログスティックを標準搭載したことでまさに「ゲームが変わる、ロクヨンが変える」を実感したものだ。
だが、3Dスティックの陰に隠れて重要な脇役がある。「Cボタンユニット」だ。
今日はCボタンユニットへの愛を語ろうと思う。
Cボタンユニットはただの4つのボタンでしかないのだが、配置の妙によってさまざまな使い方ができる。
単純4ボタン
まず第一に、Cボタンユニットは実体としてあくまで4つの別々のボタンである。よって4つのモノを選択するのに使用できる。
同様のことは十字キーでもできるだろうが、十字キーと違い斜め入力をしてしまう危険がないという利点がある。
またゲームキューブのABXYボタンでも可能だが、4つが整然と並んでいること、主となるABボタンが残せることが優っている。
例: ポケモンスタジアムのバトル中の技選択

ポケモンの4つの技を上下左右に対応付けることで、すべての技を1動作で選択することができる。
これにより、本編での十字キーでカーソルを動かしAで決定する形と異なり、画面上に選択肢を表示する必要がなくなる。1画面でポケモン対戦をするために相手の技選択を見せないことは必須事項である。
ゲームキューブのABXYボタンでも4つの選択は可能だが、並びが整然としていないため画面上の表示が不恰好になってしまうし、技の使用以外の動作がRやLなどの不自然なボタンに飛ばされてしまうだろう。
4方向指示
4つのボタンが正方形に並んでいるため、3Dスティックに次ぐ第2の方向入力として使える。その用途を示すように上下左右のマークも付いている。例: パイロットウィングスのカメラ

3Dスティックで自機を動かし、Cボタンユニットでカメラを動かせる。
十字キーでも同様の操作は可能だが、コントローラー左側には十字キーとLボタンしかないのに対し、右側にはCボタンユニットとRボタンに加えA・Bボタンがあるため、上位互換と言える。
コントローラーの左と真ん中を持つレフトポジションを使うソフトがほとんど存在しないことからその優位性は明白だ。
そして、必要な分だけ切り売りして自由な組み合わせで使える。
2方向指示×2
例: マリオ64のカメラ
左右で回り込み
上下で寄り・引き
これは十字キーでも可能だが、斜めを押してしまう危険が無い分、上下と左右に別の動作を割り振るにはCボタンユニットの方が向いている。
2方向指示+1+1
例: F-Zeroの操作
上は視点変更
左右はカメラ操作
下はブレーキ
3ボタン+1ボタン
例: ゼルダの伝説のフィールド上操作
左下右で3つのアイテムを使用
上は視点変更および妖精の呼び出し
このような細かい分割もお手の物。
さらには、絶妙な配置によりA・Bボタンとも組み合わせることすら可能である。
4ボタン+A
例: ゼルダの伝説のオカリナ(など楽器)操作
A、下、右、左、上の5ボタンがこの順で音高に対応している。
Cボタンユニットが無ければこのオカリナ操作は生まれなかっただろう。
実際、のちのシリーズでも音楽を演奏するギミックは毎回のように入っているのだが、
風のタクトの風のタクトも、トワイライトプリンセスの遠吠えも、スカイウォードソードの琴も、皆オカリナのような自由演奏は不可能である。
(夢幻の砂時計の笛はまあ頑張れば演奏できなくもないかな…)
また64版ゼルダの移植版も、ゲームキューブは単純にA+Cスティックに対応させただけで操作しづらい上に音高の並び順が直感に反しており、3DS版では順にLRYXAと広範囲に散らばってしまっている
4ボタン+A+B
例: ポケモンスタジアムのポケモン選択
この作品では当時主流であった「6匹を見せ合い、相手を見た上でバトルに出す3匹を選ぶ」という対戦形式を公式に取り入れた。
このために、Cボタンユニットに加えA・Bボタンを合わせた6つのボタンを6匹のポケモンに割り当て、3匹の選択を行う。
ここでABとCボタンユニットがほぼ2×3の長方形に並んでいるため、順番が分かりやすい。
技の選択と同じく、ここでも相手に見せずに確実に選択することが必要になる。これもCボタンユニットにしかできない芸当だろう。
十字キーやアナログスティックで6方向を選ぶのはきちんと押せているか不安になる。GCのABXYLRでも可能だが並びが不自然である。
その他
4つ使い切る必要もない。ポケモンスタジアムではオプションやメニューを開くのにC右や上が使われたり、
GBの操作でスタート・セレクトボタンをC左・下に割り振ることができる。
スマッシュブラザーズではCボタンはすべてジャンプに割り当てられている。
余談
・ストリートファイターが64に出てたら弱中強×パンチ・キックの6ボタンがAB+Cボタンユニットに綺麗に収まったろうに。・6点で点字を打つのに便利そうだ。
2015年02月22日 04:49
ファミコンの解像度は横256px×縦240pxだが、縦を224pxとする説が根強い。
中には「内部的には240px」の但し書きをつけたり、「約」224pxとするものもあるが、224という値を書いている時点で五十歩百歩だ。
「内部的」の解釈も曖昧だが、たぶん次のような認識なのだろう。
「メモリ上には240px分の領域が用意されているが、出力されるのはそのうちの224px分のみ」
そのようなことがないのは、Google画像検索で適当なファミコンソフト名を「640×480」や「720×480」の解像度指定で検索すれば分かるだろう。
ただ似たような現象はあって、240px分が出力されても、普通のアナログTVでは実際に画面に表示されるのはその内の85%や90%程度である。
(詳しくは「タイトルセーフ・アクションセーフ」「オーバースキャン」あたりで検索するとよい)
ではそのことを言っているのかというと、2つの点からおかしい。
第1に、どの程度の範囲が表示されるかはTVによって異なるので、224pxという値は出てこない。「約」であっても(10進法では)切りの悪い値にするのは不自然だ。
第2に、TVに映らないという理由で解像度を224pxなどの値にするなら、同様にアナログTVに出力するスーパーファミコンやプレイステーションも240pxや480pxを出力できないことになろうが、これらの解像度には239pxや480pxという値が平気で書かれている。
ではこの224という数字の出処はどこかだが、スーパーファミコンにはどうやら224pxモードがある。
これやこれを見ると、$2133のbit2で縦224pxと239px(448pxと478px)の切り換えができるようだ。
またプレイステーションについても、興味が無いのであまり調べていないが、224pxや448pxのモードがあるような記述が見られる。
もう1つの可能性として、エミュレータのオプションから来ているのかもしれない。
エミュレータには縦240pxのうち224pxのみを表示するモードが付いている物がある。


(というよりどちらもデフォルトが224pxで、240pxモードがある)
まとめると、
・そもそもアナログTVの仕様として映像信号のうち画面には表示されない部分がある
・スーパーファミコンには縦224px出力のモードがある
・ファミコンのエミュレータには出力のうち縦224pxのみしか表示しないモードがあるものがある
このあたりを混同した結果、ファミコンの縦224px説が生まれたのではないだろうか。
さて本題の考察は以上だが、なぜエミュレータには224px設定が付いているのかの説明をしよう。
まずこちらの画像をご覧頂きたい。

ドラゴンクエスト3で縦スクロール中に撮った画像だ。これは互換機だが、ファミコン実機でも同様の表示になるはずである。
一番下の数ドットが、本来下に続くマップではなく、最上段からはみ出した分が下に回りこんでいるのが見て取れるだろう。
これはファミコンの仕様で、内部的に2画面分を縦または横につなげた仮想画面をスクロールしているため、縦か横どちらか、この場合縦は1画面分の幅しかなく、スクロール時に上下にまたがるキャラクタは表示が乱れるのである。
この乱れは、上記の通り通常のアナログTVでは画面外で表示されない部分であり、ユーザーの目にはいることはまずない。
しかしPCのキャプチャカードで全ての出力を取り込んだり、エミュレータで全てを表示すると見えてしまうわけだ。
そこでエミュレータでは、この本来表示されない領域を隠す設定が付けられた。
どれだけ隠せばいいかといえば、背景面を構成するキャラクタは16×16単位で設定できる(正確には色が16×16、キャラが8×8)ので、うまく作られたプログラムなら半分の8pxづつだけ隠せばよいはずである。
(うまく作られたプログラムばかりでもないと思うが)
また、横についても同様の乱れが起こる。さらに縦と違う点として、ファミコンは横方向はもともと映像表示期間の90%強しか使っていないため、少し広めのTVではこの乱れが画面内に見えてしまう場合がありうる。
おそらくそのために、ファミコンには左のみ8pxを表示しない設定がある(両側でないのはなぜだろう)。上のドラクエ3の画像もそうなっている。左右の黒帯の幅が違うことから分かるだろう。
これを考えると、ファミコンの解像度は248×240と256×240の2モードあるとすべきだろう。
追記:
左8pxを表示しないモードについて、スクロール時の乱れを見せないためだろうと推測したが、おそらく違った。
ファミコンのスプライトは横8px幅であり、そのX座標は0-255の値をとり、0で左端、248で右端、249-255では右端からはみ出す位置になる。つまり画面の右端からスプライトの一部分が見える表現はできるが、左端で同じことはできない非対称性がある。
ここで左端8pxを背景・スプライトともに隠すことで、左端からスプライトの一部分が見える表現が可能となる。
両側でない理由もつくのでこれがこの機能を付けた理由で間違いないだろう。乱れを見せないためにも使えたかもしれないが、少なくともメインの目的ではないだろう。
なおこちらによればMSXやSG-1000ではスプライトの表示位置を32pxずらす機能があったらしい。
中には「内部的には240px」の但し書きをつけたり、「約」224pxとするものもあるが、224という値を書いている時点で五十歩百歩だ。
「内部的」の解釈も曖昧だが、たぶん次のような認識なのだろう。
「メモリ上には240px分の領域が用意されているが、出力されるのはそのうちの224px分のみ」
そのようなことがないのは、Google画像検索で適当なファミコンソフト名を「640×480」や「720×480」の解像度指定で検索すれば分かるだろう。
ただ似たような現象はあって、240px分が出力されても、普通のアナログTVでは実際に画面に表示されるのはその内の85%や90%程度である。
(詳しくは「タイトルセーフ・アクションセーフ」「オーバースキャン」あたりで検索するとよい)
ではそのことを言っているのかというと、2つの点からおかしい。
第1に、どの程度の範囲が表示されるかはTVによって異なるので、224pxという値は出てこない。「約」であっても(10進法では)切りの悪い値にするのは不自然だ。
第2に、TVに映らないという理由で解像度を224pxなどの値にするなら、同様にアナログTVに出力するスーパーファミコンやプレイステーションも240pxや480pxを出力できないことになろうが、これらの解像度には239pxや480pxという値が平気で書かれている。
ではこの224という数字の出処はどこかだが、スーパーファミコンにはどうやら224pxモードがある。
これやこれを見ると、$2133のbit2で縦224pxと239px(448pxと478px)の切り換えができるようだ。
またプレイステーションについても、興味が無いのであまり調べていないが、224pxや448pxのモードがあるような記述が見られる。
もう1つの可能性として、エミュレータのオプションから来ているのかもしれない。
エミュレータには縦240pxのうち224pxのみを表示するモードが付いている物がある。


(というよりどちらもデフォルトが224pxで、240pxモードがある)
まとめると、
・そもそもアナログTVの仕様として映像信号のうち画面には表示されない部分がある
・スーパーファミコンには縦224px出力のモードがある
・ファミコンのエミュレータには出力のうち縦224pxのみしか表示しないモードがあるものがある
このあたりを混同した結果、ファミコンの縦224px説が生まれたのではないだろうか。
さて本題の考察は以上だが、なぜエミュレータには224px設定が付いているのかの説明をしよう。
まずこちらの画像をご覧頂きたい。

ドラゴンクエスト3で縦スクロール中に撮った画像だ。これは互換機だが、ファミコン実機でも同様の表示になるはずである。
一番下の数ドットが、本来下に続くマップではなく、最上段からはみ出した分が下に回りこんでいるのが見て取れるだろう。
これはファミコンの仕様で、内部的に2画面分を縦または横につなげた仮想画面をスクロールしているため、縦か横どちらか、この場合縦は1画面分の幅しかなく、スクロール時に上下にまたがるキャラクタは表示が乱れるのである。
この乱れは、上記の通り通常のアナログTVでは画面外で表示されない部分であり、ユーザーの目にはいることはまずない。
しかしPCのキャプチャカードで全ての出力を取り込んだり、エミュレータで全てを表示すると見えてしまうわけだ。
そこでエミュレータでは、この本来表示されない領域を隠す設定が付けられた。
どれだけ隠せばいいかといえば、背景面を構成するキャラクタは16×16単位で設定できる(正確には色が16×16、キャラが8×8)ので、うまく作られたプログラムなら半分の8pxづつだけ隠せばよいはずである。
(うまく作られたプログラムばかりでもないと思うが)
また、横についても同様の乱れが起こる。さらに縦と違う点として、ファミコンは横方向はもともと映像表示期間の90%強しか使っていないため、少し広めのTVではこの乱れが画面内に見えてしまう場合がありうる。
おそらくそのために、ファミコンには左のみ8pxを表示しない設定がある(両側でないのはなぜだろう)。上のドラクエ3の画像もそうなっている。左右の黒帯の幅が違うことから分かるだろう。
これを考えると、ファミコンの解像度は248×240と256×240の2モードあるとすべきだろう。
追記:
左8pxを表示しないモードについて、スクロール時の乱れを見せないためだろうと推測したが、おそらく違った。
ファミコンのスプライトは横8px幅であり、そのX座標は0-255の値をとり、0で左端、248で右端、249-255では右端からはみ出す位置になる。つまり画面の右端からスプライトの一部分が見える表現はできるが、左端で同じことはできない非対称性がある。
ここで左端8pxを背景・スプライトともに隠すことで、左端からスプライトの一部分が見える表現が可能となる。
両側でない理由もつくのでこれがこの機能を付けた理由で間違いないだろう。乱れを見せないためにも使えたかもしれないが、少なくともメインの目的ではないだろう。
なおこちらによればMSXやSG-1000ではスプライトの表示位置を32pxずらす機能があったらしい。