上に戻る
4.nesasmでデータ改造 後編
概要
 前回の続きです。単に長くなったので2分割しただけなんてことは……
ヒートマンをもう少し書き換えてみる
 ダメージだけではつまらないので、ヒートマン自体の挙動も幾つか弄ってみましょう。やはりここでも解析のようなことはしません。どこをどう弄ったらどうなるということを知っているという前提でコードを書きます。サクッとこんな感じ。

    ROMADDR $2C16E+1
    ;存在確認するオブジェクトの種類
    ROMADDR $2C193+1
    db $38 ;遠くに投げる火の粉の狙う位置の+分
    ROMADDR $2C198+1
    db $1C ;近くに投げる火の粉の狙う位置の-分
    ROMADDR $2C19C+1
    db $08 ;近すぎる時に近くに投げる火の粉の飛翔距離
    ROMADDR $2C1A0+1
    ;火の粉の数-1
    ROMADDR $2C1DA+1
    ;火の粉のフラグ
    ROMADDR $2C1F4+1
    ;点滅時間
    ROMADDR $2C204 ;火の粉のVylo
    db $00,$00,$00
    ROMADDR $2C207 ;火の粉のVyhi
    db $06,$06,$06
    ROMADDR $2C20A ;火の粉の飛翔予想時間(Vxの計算に利用)
    db  48, 48, 48
    ROMADDR $2C239+1
    db $06 ;突進時間のプラス分(変形の時間)
    ROMADDR $2C28B+1
    ;突進後のフラグ
    ROMADDR $2C262+1
    db $1B ;突進が終わった時に飛ぶアドレス(プログラム改造に近いです)
    ROMADDR $2C296+1
    db $02;突進後のボスの状態番号
    ROMADDR $2C29D ;突進までの時間のテーブル
    db 1,40,80
    ROMADDR $2C2D3+1
    ;体当たり予備動作の音
 ことロックマンの改造においては、敵の弾の方を改造するのが効果的なのですが、ここではヒートマンの動きだけを変更しました。特筆しておきたい点は、突進後すぐに火の玉を投げるくらいでしょうか。
 こういう風に、書き換えられそうな点を列挙しておいて、ちょろちょろっと様子を見ながら弄れるのもアセンブラの利点です。バイナリエディタで行う破壊的でカオスな動きを得づらくなるかもしれませんが……
ソースコードの整理
 さて、ここまでで色々書き換えてきたのですが、「DataHack.asm」の長さが相当なことになっています。このまま一つのファイルにコードを書き続けるのはスマートとはいえないでしょう。
 多くのプログラミング言語がサポートしているように、nesasmでもソースファイルを分割させることができます。正確には一つのソースファイルのある場所に、別のソースファイルを含める事ができます。C言語をご存じの方なら、includeと言えばピンとくるでしょう。
 ここでは、ヒートマンに関する部分だけを別のファイルHeatman.asmに追い出してみましょう。「Heatman.asm」を新規作成し、以下のように書き換えます。
 DataHack.asm

    inesprg $10 ;プログラムバンク数
    ineschr $00 ;キャラクタバンク数
    inesmap 1   ;マッパー番号
    inesmir 0   ;ミラーリング0:Horizontal 1:Vertical

    bank 0
    org $0000
    incbin "Original.prg"
;以下アセンブリを打ち込む
ROMADDR .macro
    bank ((\1-$10)/$2000)
    org (((\1-$10)&$1FFF)|$8000)
    .endm

    include "Heatman.asm"

    ;00 シュリンク
    ROMADDR $3E986+$00
    db   3 ;ロックバスター
    ROMADDR $3EA02+$00
    db  20 ;ヒートフルチャージ
    ROMADDR $3EA7A+$00
    db  10 ;エアー
    ROMADDR $3EAF2+$00
    db  20 ;リーフ
    ROMADDR $3EB6A+$00
    db   5 ;バブル
    ROMADDR $3EBE2+$00
……以下略
 Heatman.asm

    ROMADDR $2E933+0
    db   3 ;ロックバスター
    ROMADDR $2E941+0
    db $FF ;ヒートフルチャージ
    ROMADDR $2E94F+0
    db   4 ;エアー
    ROMADDR $2E95D+0
    db   4 ;リーフ
    ROMADDR $2E96B+0
    db  13 ;バブル
    ROMADDR $2E979+0
    db   4 ;クイック
    ROMADDR $2E987+0
    db   4 ;クラッシュ
    ROMADDR $2E995+0
    db   4 ;メタル
    ROMADDR $2E9A3+0
    db $07 ;相手の体当たりダメージ


    ROMADDR $2C16E+1
    ;存在確認するオブジェクトの種類
    ROMADDR $2C193+1
    db $38 ;遠くに投げる火の粉の狙う位置の+分
    ROMADDR $2C198+1
……以下略
 大体はただ分割したに過ぎないのですが、DataHack.asm内の

    include "Heatman.asm"
 ここに注目します。この場所には、「Heatman.asm」が取り込まれます。その結果、両方のasmがアセンブルできることになります。
 「Heatman.asm」は、ヒートマンに関する部分だけが集約されており、容易に武器ダメージや挙動のパラメータを調整することができます。とても見通しが良いです。また、この様にファイルを分割しておくことはデバッグする上でも大きなメリットがあります。ちょっと、includeの行の先頭に「;」を書きましょう。

;    include "Heatman.asm"
 これで、このincludeの行はコメントになります。つまり、このincludeは実行されません。こうして、一部のasmファイルだけを無効にしてデバッグ作業を行うことが出来るのもまた、アセンブラの大きな魅力です。
 ここまでのasmの例をここの[4-10]からDLできます
アセンブラに算術演算させる
コメント:
節に入る前に、勘違いしやすい点を真っ先に補足説明します。ここで述べる算術演算、要するに計算は、アセンブル時に行われるものであり、その結果は定数(ある値)として、アセンブルに利用されます。つまり、ゲーム中の処理でちょっと割り算したいとか言う時に使うことはできません。
 ここまでも「しれーっと」使ってきているのですが、アセンブリには「+,-,*,/,%,^,&,|,~,<<,>>」といった計算を命令の中で使うことが出来ることがあります。
コメント:
それぞれ、以下の意味です。後半の方の物を知らない方は調べてみて下さい。
+ 加算   - 減算
* 乗算   / 除算(小数点以下切り捨て)   % 剰余 
^ xor演算   & and演算
| or演算   ~ not演算
<< 左シフト   >> 右シフト
 先ほどまでも、

    ROMADDR $3EA02+$00
    db  20 ;ヒートフルチャージ
 の「$3EA02+$00」のように説明なく使って来ました。ちなみに、ヒート以降のデータは、「$78」ずつアドレスがずれています。なので以下のように書くことも出来ます。

    ROMADDR $3EA02+$78*0+$00
    db  20 ;ヒートフルチャージ
    ROMADDR $3EA02+$78*1+$00
    db  10 ;エアー
    ROMADDR $3EA02+$78*2+$00
    db  20 ;リーフ
    ROMADDR $3EA02+$78*3+$00
    db   5 ;バブル
    ROMADDR $3EA02+$78*4+$00
    db   4 ;クイック
    ROMADDR $3EA02+$78*5+$00
    db  20 ;クラッシュ
    ROMADDR $3EA02+$78*6+$00
    db   4 ;メタル
    ROMADDR $3EA02+$78*7+$00
    db   1 ;相手の体当たりダメージ
コメント:
なんでロックバスターだけすこしデータが多いの?
 これを見てわかりやすいと思うかはともかく、例えば「ある場所から12バイトずつデータになっている」とかそういう事は稀にあるので、覚えておいて損はないと思います。
コメント:
この節の最初でも述べましたが、あくまでも計算結果を定数として利用しているに他なりません。例えば、

    ROMADDR $3EA02+$78*7+$00
を見ても、単に計算結果の、3ED4Aという定数を指定したに過ぎません。つまり以下の表記でも全く同じ結果になります。

    ROMADDR $3ED4A
では、なぜ敢えて+や*を残すのかと思われるかもしれませんが、この場合は78hおきにデータが与えられているという構造を意識した書き方をしたといえます。それを見やすいと思うかは人それぞれです。また、後述のラベルを使って指定する場合に、+や-で少し位置を調整するテクニックはしばしば使われます。
ラベル(1)
 nesasmは、値にラベルをつけることができます。ロックマン2はザコのHPが20固定であるので、「何発打ち込んだら倒せるか」という意味合いのラベルを付けてみましょう。以下のように「ラベル」を定義できます。

KO1  = 20 ;一撃で倒せる
KO2  = 10
KO3  =  7
KO4  =  5
KO5  =  4
KO7  =  3
KO10 =  2
KO20 =  1 ;20回も撃たないと倒せない
 ラベルを定義するためには、行の初めから書き始めるというのがnesasmでは必須のようです。ラベルは半角英数と「_」が使えるようです。
コメント:
ラベルの最初の1文字を数字にする事はできません。多くのプログラミング言語でそういうルールになっています。
 今定義したラベルを使って、ケロッグの武器耐性を書き換えてみましょう。

    ;0C ケロッグ
    ROMADDR $3E986+$0C
    db KO7 ;ロックバスター
    ROMADDR $3EA02+$0C
    db KO1 ;ヒートフルチャージ
    ROMADDR $3EA7A+$0C
    db KO3 ;エアー
    ROMADDR $3EAF2+$0C
    db KO1 ;リーフ
    ROMADDR $3EB6A+$0C
    db KO4 ;バブル
    ROMADDR $3EBE2+$0C
    db KO5 ;クイック
    ROMADDR $3EC5A+$0C
    db KO1 ;クラッシュ
    ROMADDR $3ECD2+$0C
    db KO5 ;メタル
    ROMADDR $3ED4A+$0C
    db  10 ;相手の体当たりダメージ
 何発打ち込んだら倒せるかわかりやす……くないなあw むしろごちゃごちゃした印象を受けます。今回は説明のためにこのようなラベルを準備しましたが、この使い方はあまり良くないと思います。このくらいなら直値で打ってしまえば良いかと。
コメント:
nesasmの説明書では、ラベルとは言わずにシンボルとなっています。シンボルのほうが広い意味を持つように、微妙に意味も違うようなので、本当は「ラベル」と言っている場所全て「シンボル」と書くべきだと思うのですが、「ラベル」で慣れてきてしまったので、以下の説明でもずっと「ラベル」と書きます。
ラベルと算術演算を使ってデータを打ち込む
 以上の様な算術演算とラベルを利用して、少ない値から多くのデータを打ち込む例を挙げてみましょう。ロックマンの伝統であるやられた時の爆発パターンの速さを変更してみましょう。
コメント:
ちなみにデータの打ち間違えか、オリジナルのロックマン2の爆発は、内側の左の速さがおかしいです。内側の他の3つは速さ0.C0なのに、左だけ0.E0になっています。実際よく観察すると、左だけ中心から離れるのが早いです。
 以下のようにコードを追加して下さい。

    ROMADDR $3C3FB
Vin  = $00E0 ;内側の爆発の速さ
Vout = $0380 ;外側の爆発の速さ
Fact0 = 0
Fact1 = 46341 ;1/√2*65536
Fact2 = 65536
    db LOW (Vout*Fact0/65536)
    db LOW (Vout*Fact2/65536)
    db LOW (Vout*Fact0/65536)
    db LOW (Vout*Fact2/65536)
    db LOW (Vout*Fact1/65536)
    db LOW (Vout*Fact1/65536)
    db LOW (Vout*Fact1/65536)
    db LOW (Vout*Fact1/65536)
    db LOW (Vin *Fact0/65536)
    db LOW (Vin *Fact2/65536)
    db LOW (Vin *Fact0/65536)
    db LOW (Vin *Fact2/65536)

    db HIGH(Vout*Fact0/65536)
    db HIGH(Vout*Fact2/65536)
    db HIGH(Vout*Fact0/65536)
    db HIGH(Vout*Fact2/65536)
    db HIGH(Vout*Fact1/65536)
    db HIGH(Vout*Fact1/65536)
    db HIGH(Vout*Fact1/65536)
    db HIGH(Vout*Fact1/65536)
    db HIGH(Vin *Fact0/65536)
    db HIGH(Vin *Fact2/65536)
    db HIGH(Vin *Fact0/65536)
    db HIGH(Vin *Fact2/65536)

    db LOW (Vout*Fact2/65536)
    db LOW (Vout*Fact0/65536)
    db LOW (Vout*Fact2/65536*-1)
    db LOW (Vout*Fact0/65536)
    db LOW (Vout*Fact1/65536)
    db LOW (Vout*Fact1/65536*-1)
    db LOW (Vout*Fact1/65536*-1)
    db LOW (Vout*Fact1/65536)
    db LOW (Vin *Fact2/65536)
    db LOW (Vin *Fact0/65536)
    db LOW (Vin *Fact2/65536*-1)
    db LOW (Vin *Fact0/65536)

    db HIGH(Vout*Fact2/65536)
    db HIGH(Vout*Fact0/65536)
    db HIGH(Vout*Fact2/65536*-1)
    db HIGH(Vout*Fact0/65536)
    db HIGH(Vout*Fact1/65536)
    db HIGH(Vout*Fact1/65536*-1)
    db HIGH(Vout*Fact1/65536*-1)
    db HIGH(Vout*Fact1/65536)
    db HIGH(Vin *Fact2/65536)
    db HIGH(Vin *Fact0/65536)
    db HIGH(Vin *Fact2/65536*-1)
    db HIGH(Vin *Fact0/65536)

 ……なんだこの長いコードはと思われたかもしれませんが、とにかく、このコードをコンパイルすれば、爆発の速さが上昇していることがわかると思います。しかし、ここで大事なのは、ラベル「Vout」「Vin」の値を変更すれば、全ての爆発の速さが適当に設定されるという事です。このように、アセンブラを使えば、実際に結果を見ながら、細かく調整が可能になります。
コメント:
 確かにバイナリエディタでも同じ事は出来ると思いますが、1回の試行に時間がかかり過ぎます。パラメータをちょっとずついじりながら調整するなんてことをバイナリエディタでやっていたら、16進数が嫌いになること請け合いです。
 尚、上のコードにあるLOWHIGHは、続く()内の16ビット値の、下位、上位の8ビットを取り出す命令(ディレクティブ)です。
 ここまでのasmの例を、ここの[4-20]に置いておきます
音楽を打ち込む(1)
 アセンブラで音楽を打ち込むこともできます。そりゃまあ、データを打ち込めるので当たり前ですが……繰り返しになりますが、このコンテンツでは、どこをどう書き換えたら期待の結果が出る、という解析は行いません。よってここでも、とりあえずアセンブラで音楽を変更するという話に終始します。
 「フラッシュマン」のステージのBGMの主旋律を、ドレミファソラシドを延々とループするように改造しましょう。この、しょっぱさ満点の改造をするためには、以下のように書き換……?

    ROMADDR $30AF1
    db $00,$06 ;テンポ
    db $03,$3F ;音量上限
    db $07,$82,$10 ;音量エンベロープ
    db $05,$23 ;基本キー
    db $02,$40 ;音色(波形)
    db $A1,$A3,$A5,$A6 ;音符(ドレミファ)
    db $A8,$AA,$AC,$AD ;音符(ソラシド)
    db $04,$00, ……?
 この後は、「ドレミファソラシド」が終わった後に、再び最初の「ド」にジャンプするために、最初の「ド」(データ上で$A1)のアドレスを書き込まなければいけません。どのように書けばよいのでしょうか。
ラベル(2)
 前述のとおり、値にラベルをつけることができます。ここで、ラベルを定義する時に、=以下を省略して以下のように書きます

LabelDesuyo
 まあ好きな(わかりやすい)単語を単に書くだけです。こうすると、このラベルには、ここの「PC位置」が自動で割り当てられます。式の中で、LabelDesuyoと書けば、偶然割り当てられたこの場所の値を取得出来ます。なお、ラベルとしてこのような使い方をする場合は、

LabelDesuyo:
 のように、最後に:をつけることもできます。いくつかのプログラミング言語においては、こういう風にラベルの最後に:をつけるのがお約束です。ですがnesasmでは必須ではないようです。私はつけますが……
音楽を打ち込む(2)
 ラベルを使って、続きを打ち込みます。

    ROMADDR $30AF1
    db $00,$06 ;テンポ
    db $03,$3F ;音量上限
    db $07,$82,$10 ;音量エンベロープ
    db $05,$23 ;基本キー
    db $02,$40 ;音色(波形)
Loop:
    db $A1,$A3,$A5,$A6 ;音符(ドレミファ)
    db $A8,$AA,$AC,$AD ;音符(ソラシド)
    db $04,$00,LOW(Loop),HIGH(Loop) ;無限ループ
 注意すべき点は、ラベルの値がほしい時は:を付けない点でしょうか。ラベルLoopの値を、LOW、HIGHを使って、低位→高位バイトの順番に、1バイトずつ書き込んでいます。これで、「フラッシュマン」のステージの主旋律が、気持ち悪いドレミファソラシドのループになりました。
 こういう風にラベルを使ってアドレスを指定すれば、後になって音符を幾つか追加したくなり、それによりアドレスがずれてしまう場合でも、何一つ対応の手間をかけずに気軽に書き足すことができます。
ラベル(3)
 ここまで、ラベルはグローバルラベルを利用してきました。グローバルラベルは、全てのソースファイルの全ての場所から参照することができます。一方、これから説明するローカルラベルでは、決まった場所からしか参照することができません。
 プログラミング言語に全く通じていない方は、「ローカルラベル」の存在意義がすぐには理解できないかもしれません。改造の規模が大きくなってくると、ラベルの名前を考えるのも面倒になり、似たような名前のラベルを大量につけていた結果、偶然ラベルの名前が衝突してしまい、色々面倒になるなんていう未来が待っています。
 ある程度、ラベルの名前のルール付け(命名規則)を行えばこの現象はかなり回避出来ます。例えば、ヒートマンに関する改造でつけるラベルは、Heatman_から始める、といった具合です。しかしそれだけではやはり限度が出てくるかもしれません。そこで「ローカルラベル」の出番です。ローカルラベルは、以下のように、先頭に.をつけたラベルです。

Global1:

.hogehoge

Global2:
 nesasmの説明書によると、「ローカルラベル」は、2つの「グローバルラベル」の間にのみ存在できるそうです。上のような配置の場合、ラベル.hogehogeは、Global1とGlobal2の間でしか利用できません。
コメント:
ちなみに、.hogehogeより上でも使えます。Global1より下なら。
 また、

Global1:

.hogehoge

Global2:

.hogehoge

Global3:
 以上のように、.hogehogeを2つ定義した場合、.hogehogeと書く場所に応じて、値が異なることになります。また、Global3より後ろで.hogehogeと書くとアセンブルエラーが発生するはずです。
 グローバルラベルの衝突さえ気をつければ、ある程度雑にローカルラベルをつけても良いので、ラベル命名の気持ちが非常に楽になります。
コメント:
まあ、ある程度以上改造していくと、ローカルラベルが衝突することは頻繁にあるんですが。大量のラベルを抱えることが確定している場合、面倒がらず、ラベルはやや長めの名前にし、また前述のようなルール付けを適時行なっていくと良いでしょう。
若干、今言った事に反しますが、数文字の短いラベルを使い捨て出来る点でも、ローカルラベルの利用価値はかなり高いです。計算のために一時的に利用する値などに使いやすいです。これについてはすぐ下で触れます。
 先程は解説のために全てグローバルラベルでやりましたが、やられた時の爆発の速度の設定なんていう局所的な改造にグローバルラベルをいくつも使うなんて事は避けるべきです。

Data_TiwnVelocity:
.Vin  = $00E0 ;内側の爆発の速さ
.Vout = $0380 ;外側の爆発の速さ
.Fact0 = 0
.Fact1 = 46341 ;1/√2*65536
.Fact2 = 65536
(以下略、適時ラベル名を置き換える必要がある)
 このようにすれば、グローバルラベルは一つで済みます。
 同様にドレミファソラシドで使ったラベル名Loopなんかもローカルラベルにすべきでしょう。それも含めたここまでの例をここの[4-30]に置いておきます
オチ
 「慣れれば」データ打ち込みにも、アセンブラは大いに役に立つという事をご理解いただけたでしょうか。データの打ち込みに関してはここで終わりです。アセンブラの魅力を少しでも理解して頂けたでしょうか。このコンテンツでは「どんなことができるのか」を焦点にしており、実際にどう使っていくかは殆ど述べていません。もしアセンブラに興味を持たれたなら、ここから先は、自分でNESASM.EXEの同梱テキスト(Usage.txt)を読んで調べたり、実際に使ってみたりして慣れていくと良いと思います。
 次回からはプログラム改造にアセンブラを使う話となります。nesのプログラムについて全くわからないという方はここでお別れとなりますが、ここまででも、少しでも参考になった事があったら幸いです。
(245)2013年5月25日 プレさ兵衛
inserted by FC2 system