約5年半ぶりにファミコンの技術情報サイトを更新しました

2016年の更新は大したものではないので実質7年半ぶりでしょうか. ドラクエ4の記事は書いてみたらおもしろくなかったのでそのうち消すと思います.

今回は RAM の不定値とか hotswap (電源を入れたままカセットを交換する行為) について説明してます. RAM の不定値なんてエミュレーションするものではないでしょうし、 FCEUX の default は BR6216 特性(といってもかなり簡単な法則性なのですが)になっているのでそこそこ信憑性があります.
https://seesaawiki.jp/famicomcartridge/d/RAM%20%a4%ce%c9%d4%c4%ea%c3%cd%a4%ce%cd%f8%cd%d1

レビューしてもらいながら思い出したのは Z ガンダムホットスクランブルの MONITOR でも任意のアドレスと値をかけます. ただし MONITOR を有効にするのが手間なので RTA への分野の実用はなさそうです.

seesaawiki がいつまで使えるかわかりませんし、デザインのカスタマイズの限界を感じているので3年以内を目標に自分で管理しているサーバーの pukiwiki に移動しようかと思います.

VRAM A10 と VRAM CS#

dumper/programmer においてあまり重要ではない扱いになっている VRAM A10 と VRAM CS# 端子について最近の考察をお伝えします.

カートリッジ自動検出

既存の dumper soft ではカートリッジ自動検出は ROM data の hash (checksum) を算出してやっていますが、この方式は既存でないソフトにめっぽう弱く、個人的に嫌な思い出があるので絶対に私は実装しません. ほかの方法は模索していたのですがようやく思いつきました.

VRAM A10 の制御がレジスタ経由の場合(初期ハードのようなはんだづけ方式は不可)、はこのレジスタを操作すると自動検出ができるかもしれません.

例えば MMC3 の場合は CPU address $A000 (有効ビット A14:13, A0) で制御、 MMC1 の場合は CPU adress $8000 (有効ビット A14:13)で制御してるので反応がある方がハード検出できます. 基板によって接続 address bit だけを変更している VRC (2,4,6,7)もたぶん検出できそうです.
ただ、VRAM A10 をレジスタ制御アドレス基準なので誤動作もたくさんあるので大まかにハード製造メーカーを手動で指定して詳細は自動でということになりそうです.

(MMC3 系統の亜種チップと亜種基板を調べていて、その判別を自動でやろうと思っていたときに通常の MMC1 と MMC3 を判別するほうが簡単なことに気づきました)

アフターバーナー以外で nametable に ROM を割り振る方式

VRAM CS# をレジスタ制御できるのは SUNSOFT-4 と Namco の 163 と MMC5 ぐらいだと思われます. その中でもこの機能を使うソフトは少ないです.

最近マインドシーカーは互換機で動かないということを聞きました. これはタイトル画面で nametable を ROM にしているのが原因だと思われて、伝統的な互換機では VRAM CS# をちゃんと配線してない可能性は十分にあります. (Linuxの上でエミュレータを動かしてるだけのレトロフリークは話は別でちゃんと動くと思います)

いまでもマインドシーカーの苦行を楽しみたいのは個人的には謎ですが、タイトル画面以外はnametable が RAM なので最初だけが問題だとは思います.

特別な入力端子とする

現在設計中の dumper ではエッジコネクタから MCU への programming をしたいです. それを考えたところ 5V 入力が必須の入出力端子は実装が難しいのですが、出力端子はバススイッチを経由させればおそらく安全に MCU への programming 端子につなぐことができそうです.

VRAM CS# と XOR ゲート

現状 MCU とエッジコネクタのバスはシフトレジスタ経由で読んでいるのですが、そこで溢れてしまったのが VRAM A10 と VRAM CS# の2つです. VRAM A10 のほうは74165の DS 端子(べつの74165を連結する場合にいれるためにある) にねじ込んでいるのですが、 VRAM CS# はそれにも溢れて抵抗経由で MCU の GPIO につないでいます.

一応、その2入力をXOR gate を介して、74165.DS につなぐ方法も思いついてはいるのですが、入力の仕様が未知の場合の解析に弱いことと、他につかうレベルシフト兼反転ゲートが4つで空きがないという状態のため採用できていません.

あと2つぐらい入力端子があふれるのであれば IC を追加してシフトレジスタの入力を切り替えるとか74165を追加ということもできるのですが切替端子にGPIOを使ってたりICを増やすのも絶妙な状態でパズルがとけません.

2022年4月9日付補足: 74165のつもりで(74)161と書いていた部分を訂正しました.

mruby のビルドの手順

https://na6ko.hatenadiary.jp/entry/2021/06/10/213745 の続きです.

  • 目的: require が必要なのでコンパイルが必要.
  • 環境: msys2-mingw64 でのビルド.

mattn/mruby-require

fts.h が不要. msys2-mingw64 でビルドできる.

git clone https://github.com/mruby/mruby
cd mruby
cp build_config/default.rb build_config/myconfig.rb
(編集後)
rake -v MRUBY_CONFIG=myconfig

myconfig は conf.gembox 'default' を使用する.
mrbgems/default.gembox から不要そうな機能を削除してみたり設定を変えてみたが、コンパイル時にエラーがでたりバイナリができても mirb で未使用変数をタイプするだけで segmentation fault が出たりするので諦めた. これは mattn/mruby-require と無関係である. (比較した)

このためコンパイラも clang ではなく標準設定 の gcc のままにする.

iij/mruby-require

fts.h が必要なこと、 msys2-msys が必要なことがわかっている. そのためのビルド方法は不明.
fts.h をなしで無理やりコンパイルした場合はエラーメッセージがでてしまう.

ROM dumper からの ROM 以外へのアクセス

半年前から集中力が続かずあきらめ気味になっていたのですが、わりと新しい MCUファミコンカセットをつなぐ機器の第1回試作が終わりました. 第5回ぐらいまで試作はある予定なので完成する自信が全くありません. ですので期待しないでください.

今回はPCとMCUは仮想シリアルポートの形をとり teraterm などで簡単なコマンドプロンプトから ROM, RAM, 内部レジスタがみれるようにしています. (専用ソフトは今後作る予定です)

ASIC 内蔵の SRAM

今の所わかったのは旧設計の kazzo でもできていたカセット上の ROM や RAM (例外あり)に関しては新設計でも問題なく読み書きができています. それとは対照的に Namco 163 の内蔵SRAM(兼音源パラメータ)や MMC5 の Exteded RAM (CPU address $5c00-$5fff) はアクセスできていないのもかわりません.

163 のほうは電源投入直後は address regsiter と auto increment が動作しているようなのですがそれが停まってしまうようで RAM として使えていません. ですのでマインドシーカーのセーブデータ書き換えもできません.

MMC5 のほうは宇宙警備隊と同じレジスタをいじってるつもりですがまったく動いている様子がありません.

今回メモリアクセスの波形だけはそこそこマシにしているので以前から波形の再現精度が悪いので動かないと予想していたのですが別の理由のようです. 他のレジスタも全部キレイに初期化となると簡易コマンドではやってられません.

IRQ counter

163 のφ2カウンタはちゃんと動きました. ただこれはロジックアナライザで波形をみればどのタイミングで IRQ の線が L レベルになるのかがわかるのですが、 MCU で検出してタイミングをユーザーに使える方法が思いつきません.

MMC3 の PPU A12 カウンタはまったく動きません. 偶然に1度Lになって IRQ 禁止レジスタで H にしたのを計測できただけです. MMC3 の場合は CPU と PPU のバスを並列にみて動かしてるので、CPU と PPU のバスをつなげてしまっている設計では難しいのかもしれません.

MMC3 の IRQ のためのカウンタの仕組みは nesdev wiki やそこに貼ってあるリンクでゲートレベルでわかるのですが、それをおっかけてもちゃんと動かないのであれば、やはり ROM dumper としては無理なのか... と諦め気味です.

音源 (未着手)

音源はMHz単位のクロックを停めることなく供給し続けてそれを分周して可聴域の信号をつくるわけですから、リクエストが来るまでφ2が停まってる ROM dumper としてはこれも要求される性能が全然足りないと認識しています.

VRC7 なんかはφ2ではなく Xtal で供給しているのですが、音楽としてのリアルタイム性をもった仕組みを作るのは私の知識では大変です.

すいません

2日ぐらい前は USB や ROM アクセスが早く動くのでとても楽しかったのですがそれ以上は高いレベルを要求しすぎたのかちょっと残念になってしまいました. でも後日は気分を切り替えて楽しい紹介をしたいと思います.

くだらない英語の例

wget (ソフト名)を使ってファイルをダウンロードしました

  • I wget'ed a file.
  • I wgot a file.

くだけた英語で Email を出しましたは I emailed と名詞を動詞にすることがある. くだけた英語で wget を使いましたは emailed に従うと矛盾が発生する.

米国人曰く、wget'ed が自然らしい.

水を1杯ください

  • Give me a cup of water.

液体は数えられない名詞として容器1つを単位とするというのは初歩の英語でやる.

野球場でアイスクリームを食べました

  • I ate a helmet of icecream in the baseball stadium.

MLB の球場の売店では小さいヘルメットの形をした容器でアイスクリームが提供される. これは英文法上とても正しい英語なのにバカっぽくて笑える.

米国人にとってもバカっぽいのか別の言い回しで helmet of ... は使わないようにしているらしい.

FF3 のメモリ破壊応用例

youtu.be

cheap さんのアイディアを元にウルで戦闘をしたあとサラひめとウルに戻ってお祝いされる内容を組んでみました. 今回自分のアイテム並べ総当りツールに手をいれてたんですが、重要ではないアイテムの数量は1固定だったので、cheap さんの案のようにアイテム#0,同#1に3個以上置いた例は考えておりませんでした.

ですので並べ方はまだ何通りもありこれ(と半年前のTAS)が最適かは別です.

ほかに注意するのは安いかわのたてですが、address $0050 と address $0078 に data 0xc1 を書き込んでしまう恐れがあるのでアイテムの並びで保護するか左手を利用して埋める順番の調整が必要です. ロングソードで埋められればこんな手間はいらないのですがロングソードは高いのでお金が足りなくなりがちです.

Any % no credits warp 部門への問題提議

この部門のルールを厳格化するには現行記録を有効かつ私の案を無効にする線引がとても難しいです. 具体的には CPU address $006c と $0073 の操作を禁止するのが一番ですが、それを操作しているかを判定できる人が少なすぎます. 以下はその他の問題です.

戦闘中のアイテム欄溢れを禁止案

現行記録ではアイテム欄溢れを利用しています. メモリ破壊の原因は不正なジョブ番号 0x1e の戦闘コマンドを表示から始まりますので、手間と時間は増えますがアイテム変化技でそのジョブ番号にできればメモリ破壊ができてしまいます.

不正なジョブの利用を禁止案

現行記録ではアイテム変化技から不正なジョブ番号を利用しています. 仮に禁止にしたら no glitched jobs 部門と同じになってしまいます.

くらやみのくもをちゃんと倒せ案

現行記録では不正なジョブ番号 0xb7 で戦闘を強制終了しています.

FF3 のメモリ破壊とスクリプトポインタ

cheap さんご提案の内容を確認したら、ポインタ上位8bitに 0x64 (ミスリルかぶと)をいれて普通の会話(会話後 0xa3f8) と組わせてスクリプトポインタ(cpu address $0072) を 0x64f8 に設定していました.
そこから cpu address $6506-$650b にあるセーブデータその1の名前欄にスクリプトを埋め込み、アイテム取得イベントを起こして任意のアイテムを手に入れてます.

スクリプトポインタが本来取りうる値は 0x98xx-0xb3xx あたりですので、これを利用するとウルとカズスに行くだけでどこでもイベントを呼び出せる状態です. さらに先述の名前欄経由でゲーム進行上のフラグも立てることができます.

ポインタの値で終盤ばかりですが解説しておきます.

$ae00 あたり, やみのせかいへ移動

$ae00 (めぐすり+resetによる初期化)でよべます.

$af3b あたり. 倒せるくらやみのくもとの戦闘

$aef8 (めぐすり+会話), $af00 (どくけし+resetによる初期化), $af17 (どくけし+宿泊)で呼べます. 本来はダーククリスタル関連のフラグはチェックした後のイベントなので2ヘッドドラゴンたちを倒したことにしなくてもいいです.

$aff8 あたり. サロニアに帰る

$aff8 (どくけし+会話)でできます.

ここまで外の大陸のため CPU address $0078 へ data 3 を設定する必要があります. このアドレスと CPU address $0050 (店やポエム朗読が呼べる)はかわのたてをおくと data 0xc1 (半濁音記号) で破壊されやすいので、埋めるアイテム順番をかえるか、無難なアイテムを先においておくほうがよいです.

$b0f8 あたり. ウルに帰る.

$b0d8 (ラッコのあたま+いきかえり)で飛べます. $b0f8 だとウルの中からウルの入口へ移動できないようなのでちゃんと動きませんでした.

進行上は浮遊大陸に戻っているので CPU address $0078 を設定する必要がありません. data 0 以外だとおかしくなります.

重要な終盤イベントは偶然にめぐすりとどくけしから生成できるわけですが、本当に STAFF CREDIT が始まる直前はラッコのあたま(0xb0)かボムのかけら(0xb1)が必要です.

ラッコのあたまを名前欄から生成しておけばそれも可能です.

$b165 あたり. ウルでの最後の会話

$b148 (ボムのかけら+やまびこそうをもらう)

ここにジャンプすると any % との違いはほとんどないのですが no credits warp の寸前ではあります.

FF3 の Any % no credits warp 部門への問題提議

Final Fantasy III - speedrun.com
先日たまたまそのあたりを見ていたら重要なことに気づきました. この部門の名目は「なにをやってもいいがクレジットに(直接)飛ばない」というもので実際にはアイテム溢れなりアイテム変化のバグを駆使した10年前のピロ彦さんのTASをなぞっているだけでした. なんでもいいからたおせるくらやみのくもの戦闘を終わらせた時点で時間計測終了です.

アイテム溢れから発端するメモリ破壊の仕組みを理解した半年前に、倒せるくらやみのくもの戦闘前後のイベントを無理やり実行できることはわかっていました. つまり、ウルの村でメモリ破壊をしてくらやみのくもを呼べば現行記録より大幅な短縮が可能です.

実際に試したところ、現行の Credit に直接飛ぶ Any % とやることがほとんど同じで目的が達成できてしまいました. くらやみのくもは強制終了できないのでパラメータをいじって倒すか倒したことにする必要があると予想していたのですが、それも不要でした.

ルールはなにも逸脱していないのに、ゲームとして遊ぶところが皆無になってしまったため実験成功と同時に困ってしまいました. Any %部門との違いはアイテムの並べ方が若干変わることくらやみのくも関連の演出の待ち時間が長いだけです. やり方も動画も資料はそろえてあるのですが公開するのにためらっています.

FF3 のスクリプトデータの解析

cheap さんの掲示板に投稿しようとおもったのですが、.pl ファイルが not found で投稿できませんでした. (直してくださったらこの件はそっちに書きます)

[引数なし]
00-bf (未調査) FF2 と同じで町人などの移動や方向制御だと思う
c0-cf プレイヤーキャラの移動や方向制御
d0 ゆれる
d1 雷鳴
d2 暗くなる
d3 明るくなる
d4 HP回復
d5 MP回復
d6 隠し通路解錠関連その1
d7 隠し通路解錠関連その2
d8 reset game
d9 アムル西に船を停泊
da dance
db opening
dc poem
dd ending
de なにかのフラグセットと浮上後(寸前)の外の大陸にいく.; b@6011 |= 0x80; b@0078 = 3
df いきかえり
e0 PC $b64f; 外マップでは止まらないが詳細不明
e1 PC $b668; 同上
e2 ひかる
fd 1つ前に移動(デジョンx1).
fe 繰り返し(?)
ff script 終了

[引数1つ]
e3-e6 xx 効果なし; rts のみ
e7 xx アイテム1つ消費. 引数はアイテムID
e8 xx b@6102 &= xx; 状態異常解除(全員), 0x00 で全部解除
e9 xx b@6102 |= xx; 状態異常付加(全員), 0xff で全部付加
ea xx w@601d += xx; 所持金を256ぎる単位で加算
eb xx w@601d -= xx; 上記の減算版
ec xx b@601b += xx; キャパシティ加算
ed xx b@601b -= xx; 上記の減算版
ee xx b@600b = xx; NPC 設定
ef xx 外で起こすようなスプライト制御系統のイベント
f0 xx セリフを出すその1
f1 xx セリフを出すその2
f2 xx address $6020-$603f の RAM へ OR 演算で bit を立てる. ゲーム進行のフラグを立てている. 引数の bit2:0 が対象 data bit で、 bit7:3 が address offset.
f3 xx 上記の AND 演算で bit を下げる版
f4 xx script data 0xf2 から 対象アドレスを $6080-$609f に変えたもの.
f5 xx 上記の AND 演算で bit を下げる版
f6 xx script data 0xe7 の消費を取得に変えたもの
f7 xx 戦闘開始. 引数は敵のグループ番号.
f8 xx BGM 変更. 引数は曲番号.
f9 xx 特定の外の場所へ移動
fa xx 特定の屋内の場所へ移動
fb xx 用途不明; b@0045 = xx; b@0044 = $80
fc xx xx frames 待機

注)

  • 移動,戦闘,演出は滞在中の大陸や屋内屋外で挙動が変わるものが結構ある
  • b@6102 は address $6102 を byte 単位で扱っての意味. w@601d は little endian 16 bit 単位で扱う.
  • &, |, ^ 演算子の意味は C と同じ
  • セリフコマンドが2種類ある理由は未調査

(2022年1月19日追記)
command 0xf3 と 0xf5 が XOR 演算版と書いていましたが誤りでした. 該当部は訂正しました. さらに cheap さんにその間違いをアドバイスするという失態. すいませんでした.

言い訳なんですが FF2 は XOR 演算だった先入観と $b9a6 eor #$ff; $and $6020,y を $b9a6 eor $6020,y とななめ読みして、早合点してました.

f:id:na6ko:20220119005012p:plain