このページは、普通の改造よりもっと上級な事がしたい!
って人のためのページです。
このページで書いてある事がわからなくても、
大丈夫!
何度も試行錯誤すれば
だんだんわかってくるでしょう。
では、始めましょう。
65C816とは、スーパーファミコンのプログラム言語の事を示します。
言語と言っても、日本語や英語、中国語等がありますね。
65C816は、スーパーファミコンの機械語って事です。
これを習うと、スーパーファミコンに何でも命令できます。
食事を作れとか、お金を出せとか、そういうのは無理なんですけどね^^
とにかく、次へ参りましょう。
このページでは、直接機械語は習いません。
アセンブラを使って、一般人にもわかりやすいプログラミング言語を
わかりにくい機械語に変換していきます。
そのほうが効率がいいです。
では習い始めましょう・・・・って言っても=
オリジナルROMを作るとか、エミュを作るとかは
専門のページを検索したほうがいいです。
ここでは、気軽にマリオワールドの上級者関連の事をしたい人にお勧めなページです。
カスタムスプライトから始めましょう。
テキストファイルなので簡単に改変できますから、楽です。
では、始めます
このページではスプライトを大文字の英語で書いていますが、小文字でも構いません。
ただし、例外もあります。
ここからが本題です。
いきなり自分でスプライトを作るのは難しいので、サンプルスプライトを改変しましょう。
では、まず始めに、「普通のジャンプをしてもスピンジャンプになる処理」を「ハンマーブロス」
に加えましょう。
簡単な処理なので、指示通りにやっていけばうまくいきます。
始めに、「sprites」フォルダの中の「hammer_bro.asm」を開いてください。
※バックアップは忘れずに!
開くと、色々と意味不明な事が書いてあると思います。
その中で、以下の文字列を探してください。
dcb "MAIN"
HAMMER_BRO_JSL PHB ; \
PHK ; | main sprite function, just calls local subroutine
PLB ; |
JSR DecrementTimers
JSR START_HB_CODE ; |
PLB ; |
RTL ; /
場合によっては
JSR DecrementTimers
が、ないかもしれませんが、バージョンの違いなので気にしなくていいです。
次は、この文字列の中に
LDA #$01 STA $140D
と、書いてください。
と、言っても
ここと→PLB
JSR DecrementTimers
JSR START_HB_CODE ; |
ここ→PLB ; |
の、間に書いてください。
さもないと、バグる可能性があります。
できたら、そのファイルを保存して、挿入してください。
そのスプライトの設置もして、そのマップをプレイしてください。
すると、
普通のジャンプをしてもスピンジャンプになると思います。
こうなるのは、「スピンジャンプかどうか」のフラグを、立てているからです。
それではここで、解説に行きましょう。
さて、なぜこうなってしまったのでしょうか。
それには、命令の上に書かれている、
dcb "INIT" dcb "MAIN"
と、さっき付け足した文字列が関与しています。
LDA #$01 STA $140D
前者は、スプライトを動かすために大切な文字列です。
dcb "INIT"は、イニシャルルーチンと呼ばれます。
dcb "MAIN"は、メインルーチンと呼ばれます。
後者は、ある二つの命令をあらわしています。
それは、
LDA
と
STA
です。
どっちもAがついていますね。
このような命令は、殆どの場合
アキュムレータ
を使う命令です。
アキュムレータとは、CPUの中にあるレジスタのひとつです。
これを使うと、基本的なことがいろいろできます。
一般では、Aレジスタと呼びます。
dcb "INIT"(イニシャルルーチン)は、スプライトが画面に入った時、
一瞬だけ動作します。
この文字列の下に命令を書くと、その命令はスプライトが画面に入った時、一回だけ動作するので、繰り返したくない命令を書くのに使います。
dcb "MAIN"(メインルーチン)は、スプライトが画面に入った時から、何回も繰り返します。
この下に命令を書くと、その命令は画面に入ったときから何回も動作します。
基本的には、こちらに命令を書きます。
ちなみに、INITとMAINは必ず大文字で書いてください。
dcbと"INIT"もしくは"MAIN"の間は必ずスペースを開けてください。
LDA
は、Aレジスタに、数値をロード(格納)します。
数値の種類は、その後の記号によって変わってきます。
LDA $01 - 16進数でメモリ「01」に入ってる数値をAレジスタにロードする。
LDA #$01 - 16進数で数値「01」をAレジスタにロードする。
LDA #%00000001 - 2進数で数値「00000001」をAレジスタにロードする。
$ は、この数値が16進数だという事を表します。
殆どの場合、この記号を必ずつけるといってもいいでしょう。
逆に、これをつけないと、数値を10進数としてロードする事になります。
% は、数値を2進数で表記するという事を表します。
あまり使わないかもしれませんが、使う時がくるかもしれませんので、
しっかり覚えておきましょう。
# は、この数値が直接入れる数値だという事を表します。
逆につけないと、その数値のメモリに入っている数値をロードしてしまうので、注意してください。
これらはアドレッシングモードと呼ばれ、その記述方法によって命令がちょっと変わってきます。
LDA
の派生型として
LDY
や
LDX
がありますが、これはまたの機会に説明します。
例では、数値「01」をAレジスタにロードしています。
次は、
STA
です。
この命令は、Aレジスタの数値を指定したメモリにストア(代入)します。
これもアドレッシングモードによってちょっと変わってきます。
STA $01 - 16進数でメモリ「01」にAレジスタの数値をストアする。
STA $0151 - 16進数でメモリ「0151」にAレジスタの数値をストアする。
STA $200103 - 16進数でメモリ「200103」にAレジスタの数値をストアする。
上記の命令は、ストアするメモリのビットが違ってきます。
$** - 8bit(2進数で8桁)
$**** - 16bit(2進数で16桁)
$****** - 24bit(2進数で24桁)
ビットの桁が違うと、アドレッシングモードが違う事になるので、よく覚えておいてください。
STA
の派生型として
STY
や
STX
がありますが、これもまたの機会に説明することになるでしょう。
例では、メモリ $140DにAレジスタの数値を書き込んでいます。
では、さっきの命令をまとめてみましょう。
LDA
- 指定した数値(メモリの数値)をAにロード(格納)する。
STA
- 指定したメモリにAレジスタの数値をストア(代入)する。
$ - 命令の数値を16進数に直す。
% - 命令の数値を2進数に直す。
# - 数値を直接指定する。入れないとその数値に対応したメモリに。
$** - 8bitのアドレッシングモード。2進数で8桁なので8bit。
$**** - 16bitのアドレッシングモード。2進数で16桁なので16bit。
$****** - 24bitのアドレッシングモード。2進数で24桁なので24bit。
では、先ほどの文字列をまとめてみましょう。
LDA #$01 - 8bit,16進数で数値 01 をAレジスタにロードする。
STA $140D - 16bit,16進数でメモリ 140DにAレジスタに入っている数値(今は 01 が入っている。)をストアする。
このようにすると、メモリ$140DにあるスピンジャンプのフラグがONになり、
普通のジャンプをしてもスピンジャンプ状態になるのです。
はい、これで理解できなかったあなたは、
ここから読み返すといつかは理解できます。
これで理解できたあなたは、
第1の壁を乗り越えました!おめでとうございます!
基本的な命令を覚えたので、あなたもいろいろやってみましょう!
ここから好きなメモリを選んで、好きな数値を入れてみましょう。
下手な所に数値を入れるとバグるかもしれませんが、
うまくいけば面白いことになります。
つまり、チートと同じことですね。
では、次の章へ行きましょう・・・・
さて、ここまでにサンプルスプライトの改変はできましたね。
次は、新たなレジスタを使いましょう。
まずは、このファイルを保存してください。
スプライトのテンプレートみたいなものです。
中のasmファイルとcfgファイルを「sprites」フォルダにいれてください。
そして、そのasmファイルを開いてください。
中は、こうなってる筈です。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; INIT and MAIN JSL targets
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
dcb "INIT"
RTL
dcb "MAIN"
RTL
中身の確認ができたら、次へ参りましょう。
dcb "MAIN"
**ここ**
RTL
に、以下の命令を書き込んでください。
LDY #$01
LDX #$00
LDA #$3C
STA $14AD,y
STA $14AD,x
書き込んだら、挿入し、マップに設置して、ステージをプレイしてみましょう。
すると・・・・
これが、
こうなってる筈です。
挿入時点でエラーを吐いた人は、asmファイルの名前を変えたり、変な命令を書き込んでるかも知れません。
そのような事があった人は、1度第四章からやり直しましょう。
さて、なぜこうなったのでしょう。
それは、先ほどの命令に「青Pスイッチを押してる状態」にする命令と「銀Pスイッチを押してる状態」にする命令を書き込んでいるからです。
それでは、解説に行きましょう。
さて、さっき書き込んだ命令を見てみましょう。
LDY #$01
LDX #$00
LDA #$3C
STA $14AD,y
STA $14AD,x
ふむふむ、
LDY
という命令や
LDX
という命令がありますね。
それと、
STA
のアドレッシングモードに、
,y
という記号や、
,x
というのがついていますね。これはなんでしょうか。
ストアしたメモリをRAM Mapで見てみると、
$7E:14AD Blue POW Timer
と、書いてますね。
その下には、
$7E:14AE Silver POW Timer
と、書いてます。
メモリ$14ADにしか書き込んでないのに、どうやって$14AEに書き込んだのでしょうか。
それは、
Yレジスタ
と、
Xレジスタ
が関係しています。
Yレジスタ
と
Xレジスタ
は、Aレジスタの基本的な事に加え、メモリアクセスに長けています。
その代わり、演算等の機能は使えません。
この二つのレジスタは、双子のような存在です。
どっちも同じ機能なので、好きな方を使うといいでしょう。
以前、解説(1)で、「命令の殆どはその文字に含まれているレジスタを使われる」と、説明しましたね。
LDY
や
LDX
は、その通り
Yレジスタ
と
Xレジスタ
を使います。
基本的にはLDAと一緒です。
LDA
- 指定した数値(メモリの数値)をAにロードする。
LDY
- 指定した数値(メモリの数値)をYにロードする。
LDX
- 指定した数値(メモリの数値)をXにロードする。
このように、使うレジスタが違うだけです。
STY
や
STX
も同様です。
STA
- 指定したメモリにAレジスタの数値をストア(代入)する。
STY
- 指定したメモリにYレジスタの数値をストア(代入)する。
STX
- 指定したメモリにXレジスタの数値をストア(代入)する。
でも、基本的にストアは
Aレジスタ
で行うので、これらの命令はあまり必要ないのかもしれませんね。
例では、 Yレジスタ に 01 、 Xレジスタ に 00 、 Aレジスタ に 3C をロードしています。
「
,y
」や、「
,x
」を使うと、そのレジスタの数値の分だけ使うメモリの番号が増やされます。
つまり、こういうことです。
LDY
#$03 - Yレジスタに03をロード
LDA
$01
,y
- $01にYレジスタを足した数値に値するメモリの数値をAレジスタにロード
=メモリ$04に入ってる数値をAレジスタにロード
このように、、
Yレジスタ
の数値だけメモリの番号がずれます。
かなり重要です。
例では、Aレジスタの数値を$14AD+Xのメモリと$14AD+Yのメモリにストアしてます。
LD*,ST* - 三文字目のレジスタを使う。(A,Y,Xのみ)
,y
&
,x
- Y,Xレジスタの数値をオペランドに足して命令を行う。
ああ、そういえば
指定した数値の事をオペランドと言います。
アドレッシングモードとはまた違ってきます。
これも覚えておいてください。
さて、さっき実行した命令を纏めてみましょう。
LDY #$01 - 8bit,16進数で数値 01 をYレジスタにロードする。
LDX #$00 - 8bit,16進数で数値 00 をXレジスタにロードする。
LDA #$3C - 8bit,16進数で数値 3C をAレジスタにロードする。
STA $14AD,y - 16bit,16進数でAレジスタの数値(今は3C)を14AD+y(今Yには01が入ってるので14AEになる)にストアする。
STA $14AD,x - 16bit,16進数でAレジスタの数値(今は3C)を14AD+x(今Xには00が入ってるので変化無し)にストアする。
こういう訳で、結果的に青Pスイッチと銀Pスイッチが押してる状態になるのです。
言い忘れてましたが、カスタムスプライトでは
Xレジスタ
の値が重要になります。
このゲームのスプライトの枠(席?)は12枠あるのですが、その区別をつけるために
Xレジスタ
にインデックスの数値を入れておくのです。
それでは、次に進みましょう。
それでは、新しい命令を使ったコマンドを、作ってみましょう。
第四章では、スプライトのテンプレートを使いましたね?
そのテンプレートを使ってみましょう。
第四章で追加した命令を消して、ちょっと長いですが以下の命令を書き込んでください。
dcb "MAIN"
LDA #$00 ;A←0
STA $0DC2 ;ストックの中身←A LDA $1490 ;A←スタータイマー BNE STAR ;A=0でないならSTARへ飛ぶ LDA $19 ;A←マリオのパワーアップ状態 BEQ RETURN ;A=0ならRETURNへ飛ぶ CMP #$01 BEQ SUPER ;A=1ならSUPERへ飛ぶ CMP #$02 BEQ CAPE ;A=2ならCAPEへ飛ぶ CMP #$03 BEQ FIRE ;A=3ならFIREへ飛ぶ RTL
STAR LDA #$03 ;A←分岐によって様々な値
BRA BOX ;BOXへ飛ぶ
SUPER LDA #$01 ;A←分岐によって様々な値
BRA BOX ;BOXへ飛ぶ
CAPE LDA #$04 ;A←分岐によって様々な値
BRA BOX ;BOXへ飛ぶ
FIRE LDA #$02 ;A←分岐によって様々な値
BRA BOX ;BOXへ飛ぶ
BOX STA $0DC2 ;ストックの中身←A RETURN RTL
書き込めましたか?
書き込めたら、挿入してください。
そして、マップにそのスプライトを設置し、
できれば
スーパーキノコ
ファイアフラワー
マント羽根
スーパースター
ブラックパックン
を設置してテストプレイしてください。
すると、こうなる筈です。
チビマリオ
新しい命令が4つ出てきました。
CMP
Aレジスタとオペランドを比較する命令
CPY
Yレジスタとオペランドを比較する命令
CPX
Xレジスタとオペランドを比較する命令
BEQ
CMPの結果「A=オペランド」ならラベルへ移動
BNE
CMPの結果「A≠オペランド」ならラベルへ移動
BCS
CMPの結果「A≧オペランド」ならラベルへ移動
BCC
CMPの結果「A<オペランド」ならラベルへ移動
BRA
無条件にラベルへ移動
これらの分岐命令
は符号付き条件分岐命令です。
BEQとBNEだけでは不便なのでもう2つ命令を紹介しています。使い方は一緒です。
ラベルとは、先ほどのソースの「STAR」や「SUPER」などです。
好きな場所に記述でき、好きな時にBRAやBEQなどで移動できます。
処理が進んでいる途中にラベルにぶつかっても処理が停止することはありません。
ラベルはソース内の目印です。
上のソースでは、最初にスターのタイマーをAレジスタに読み込んでそれが0でなければ(=スターの効果が切れてなければ)BNEでSTARに飛ばしています。
マリオがパワーアップしていない状態ならBEQでRETURNに飛ばしています。
次に、スーパーマリオ、マント、ファイアー…と順に判定して、それぞれのラベルに飛ばしています。
飛ばされた先にはLDAでそれぞれ違った値を読み込ませてBRAでBOXに飛ばしています。
BOXでは先ほどLDAでAレジスタに読み込ませた値をストックの中身にSTAで代入しています。
分岐命令を使う時には注意してください。
例えば、
LDA $00 CMP #$50 BCS GET 命令 命令 ・・・ 命令 命令 GET LDA #$FF STA $01 RTL
分岐命令で飛ぶラベルと分岐命令の間に命令が沢山入っているとSpriteToolがエラーを吐きます。
これは、分岐命令で移動できる距離が短いから。符号付で1バイトしか用意されていないんです。
そんな時はこうすれば解決できます。
GOTOGET JMP GET LDA $00 CMP #$50 BCS GOTOGET 命令 命令 ・・・ 命令 命令 GET LDA #$FF STA $01 RTL
分岐命令の近くにラベルを置き、そこにJMP命令で目的のラベルまで飛ばすようにします。
JMP命令は移動できる距離が長いので(符号無し2バイト)余程のことではエラーを吐きません。
前章と同様に前回の命令を消して次の命令を書き込んでください。
dcb "INIT" LDA #$00 ;A←0 STA $0DBF ;コインの数←A LDA #$32 ;A←50 STA $0F48 ;ボーナススターの数←A RTL
dcb "MAIN" INC $0DBF ;コインの数を増加 DEC $0F48 ;ボーナススターの数を減少 LDA $0DBF ;A←コインの数 CMP #$32 BEQ LABEL ;A=50ならLABELへ LDA $0F48 ;A←ボーナススターの数 BEQ LABEL2 ;A=0ならLABEL2へ RTL
LABEL LDA #$00 ;A←0
STA $0DBF ;コインの数←A RTL
LABEL2 LDA #$32 ;A←50
STA $0F48 ;ボーナススターの数←A
RTL
書き込めたら、挿入してください。
マップにそのスプライトを設置しテストプレイしてみましょう。
マリオのコインの数とボーナススターの数に注目してください。
コインの数はだんだん増え、ボーナススターは減っていきます。
なぜ増加と減少が起こったかというと新しい命令
INC
と
DEC
が関係しています。
INC
Aレジスタの増加
INX
Xレジスタの増加
INY
Yレジスタの増加
DEC
Aレジスタの減少
DEX
Xレジスタの減少
DEY
Yレジスタの減少
上のソースでは、イニシャルルーチンでコインの数を0、ボーナススターを50に初期設定しています。
コインのAを増加させていき、16進数で32 即ち50と比較させ50になったらラベル移動、移動先でコインの数を0にします。
ボーナススターも同じく減少させ、0になったらラベル移動、移動先で50に戻しています。
ちなみにLABEL2への分岐時にCMPを用いていません、この場合BEQは=0で分岐、BNEは≠0で分岐します。
この説明ではありませんでしたが、INC,DECは命令の仕方によってはアキュムレーターの値も変化させることができます。
INC A ;アキュムレーターに1を足す DEC A ;アキュムレーターから1引く INC $00 ;$00に1を足す DEC $00 ;$00から1引く INC ;INC Aと全く同じ動作をします DEC ;DEC Aと全く同じ動作をします
説明が不適切なら適当に直しといてください。
今度はasmの前回の命令を消して次の命令を書き込んでください。
dcb "INIT" RTL
dcb "MAIN"
LDA $15
CMP #$08
BEQ LABEL;上を押したらLABELへ
RTL
LABEL LDA #$01
STA $1426;メッセージ1を表示
RTL
これを挿入してプレイ!
すると、上を押せばメッセージが表示されますが、他のボタンと同時押しだと動作しません。
そこで、さっきのasmを
dcb "INIT" RTL
dcb "MAIN"
LDA $15
AND #%00001000
BNE LABEL;上を押したらLABELへ
RTL
LABEL LDA #$01
STA $1426;メッセージ1を表示
RTL
と書き変えて挿入して見ましょう。
すると、今度は同時押しでも動作します。
ではなぜ出来たのでしょうか。
答えは、AND #%00001000←これです。
AND という命令は、2進数で考えて、同じ位で掛け算をします。
例えば、YとBと↑を同時押しした場合、
16進数で40+80+8=C8という結果が出ます。
それを2進数にすると、11001000になります。
結果をANDという命令で計算すると、
00001000
* 11001000
 ̄ ̄ ̄ ̄ ̄ ̄
00001000
となり、16進数で8となります。
つまり、YとBが無視されます。
また、ANDの演算結果により、ゼロフラグが変化するためCMPも必要なくなります
dcb "INIT" LDA $0DBF CLC ADC #10 STA $0DBF RTL dcb "MAIN" RTL
ADCは、Aレジスタをオペランド+キャリーフラグの分加算する命令です。
実際にこれを実行してみるとコインが10枚増えるはずです。
CLCは、キャリーフラグをクリア(0にする)命令。これは繰り上がりなどで使われます。詳しくは鬼畜王氏のサイトで。
LDA $0DBF SEC SBC #10 STA $0DBF
減算はSECとSBCを使います。
SBCはAレジスタをオペランド-キャリーフラグの分減算する命令。
SECはキャリーフラグをセット(1にする)命令。繰り下がりなどで使われます。
同じ処理をn回だけ行いたい時はループを使います。
今何回目か数えるカウンタが必要ですが、
Xレジスタがよく使われます。
LDX #$08
Loop
[処理A]
DEX ;Xを1減らす
BPL Loop ;Xがマイナスでなければやり直し
上のようにすると、[処理A]が「08+1」の9回だけ実行されます。
その仕組みを考えてみましょう。
X=8 で処理A X=7 で処理A : : X=0 で処理A Xがマイナスになってしまったので戻らず
注意として、処理AがXを変える物であってはなりません。
なぜかって…数えてる指をぐちゃぐちゃにされるようなものだからです。
下手すると永久にループから抜けられなくなってフリーズします。
さて、そうは言うものの、本当に同じ処理を
何回もやりたい機会なんてほとんどありません。
毎回の処理をちょっとずつ変える、ということが多いです。
つまり、同じ処理ではなく、似ている処理をまとめて行う。
ループってのはそういうものです。
例のループではどの回の処理Aも同じ処理Aですが、
Xの値というシチュエーションが異なっています。
これがヒント。
もし処理Aが、Xによって微妙に違う処理を行うものだとしたらどうでしょう。
「X=0のときの処理A」をA0
「X=1のときの処理A」をA1…と、かっこよく書きますと、
例のループでは単に『処理Aを9回くりかえす』のではなく、
『処理A0、A1、A2、A3、A4、A5、A6、A7、A8を全て行う』
という意味を持つことになります。
「Xによって違う処理」と言うと抽象的ですが、
具体的にはLDA $7ECD00,xとかですね。
X=8〜0のループの中においてこいつは
$7ECD08〜$7ECD00という「一まとまりの配列」を扱う感じです。
配列の内容ははRAMかもしれませんし、単なる用意されたデータ群かもしれません。
$??????,xというのは、「??????から始まる配列の、X+1番目」
という意味合いが強いです。
なぜX+1かって?X=0のときが1番目だからです。
「LDA $aa,x ⇒STA $bb,x」は、
「配列aaのX+1番目を配列bbのX+1番目にコピー」ということになり、
これをXが(配列の長さ分-1⇒0)となるまでループさせれば
「配列aaをまるまる配列bbにコピー」という結果になります。
さて、ここまで来たら実例を見てみましょう。
HDMA入門の実践?をやってみます。
やるべきことをまとめると、
「08 00 06 40 FE 7Fという配列を、$7FFE18以降に書き写す」
「40 AF 40 6F 40 2F 40 00 00という配列を、$7FFE40以降に書き写す」
自前で配列を用意するには配列名ラベルとdcbを使います。
HAIRETU dcb $08,$00,$06,$40,$FE,$7F
とすれば、
LDA HAIRETU,x
は、先に用意したHAIRETUという名の配列のX番目をLDAすることになります。~
完成品を見てみましょう。
LDX #$05 Loop1 LDA.l SETTING,x STA $7FFE18,x DEX BPL Loop1 LDX #$08 Loop2 LDA.l CONTENT,x STA $7FFE40,x DEX BPL Loop2 RTL SETTING dcb $08,$00,$06,$40,$FE,$7F CONTENT dcb $40,$AF dcb $40,$6F dcb $40,$2F ;最初は00なので dcb $40,$00 ;正直最後の2バイトは不要 dcb $00
上のはTRASM用書式で書いています。
注意したいのが、この処理は1回やれば十分だということです。
Sprite toolで使うなら、INITルーチンに書けばいいです。
LevelASMで使うなら、LDA $7FFE18 BNE Return(RTLへ)
を付け加えましょう。
残念ながら、LevelASMで使おうと普通にTRASMでbin化してもだめです。
SETTINGやCONTENTというラベルを使っていますが、
これはLDAにおいては絶対パスなので、
bin化後は配列の位置を正しく指してくれません。
xkasなら一撃で挿入できるので便利です。
あっぷろだXのLevel.asmを(CMry
xkas書式に変えるときは、
「ラベル本体に:をつける(ラベル指定では不要)」「dcbをdbに変える」。
どのような命令があるかはここでも見てください。ある程度のことは載ってます。