ウィザードリィ エクス 前線の学府

キーワード
相対アドレス
復帰
ベース
レジスタ
ルーチン
論理積(and)


内容
装備制限解除
ステータス確認するとHP全回復
ステータス確認すると年齢変更
購買で全アイテム購入可能


装備制限解除

以下にセーブデータとの比較で相対アドレスがどうなっているかを表にまとめましたので見て下さい。

項目 データ量 相対アドレス
STR b $0004
INT b $0005
LV hw $000a
最大HP hw $000c
現在HP hw $000e
所持金 w $0014
経験値 w $0018
種族 b $0022
学科 b $0023
学位 b $0024
性別 b $0025
性格 善・中・悪 b $0026(下位4ビット)
性格 b $0026(上位4ビット)
経過日数 hw $002e(下位9ビット)
年齢 hw $002e(上位7ビット)
アイテム種類 h $0040
アイテム状態 h $0042
魔法残り回数 b $00d0(下位4ビット)
魔法最大回数 b $00d0(上位4ビット)
注:一部データ量の部分で間違いがあるかもしれません

装備の制限は種族や学科によって変わる為この相対アドレスまたはアイテムの種類から探していきます。

まずはアイテムの種類の相対アドレスから16ビット検索で探していきますが、
$0040なのでかなりの場所でヒットしてしまいます。これだと大体の場所の見当が付いていないと難しいので
別の相対アドレスで検索します。学科がp$0023なのでこれだとヒット数が少なそうです。
試しにやると$0040よりかなりましで、$00110000辺りまではないと仮定し、近くにp$0040がある場所がないかを
16ビット検索。
するとかなり怪しい場所があり、p$0022(種族)もロードしています。
こちらがその場所です。
00131c14 lhu   v1, $0040(s0)              
00131c18 addiu  a0, zero, $0001     a0=$00000001
00131c1c lbu   v0, $0023(s1)              
00131c20 andi  v1, v1, $7fff
00131c24 sllv  v0, a0, v0
00131c28 and   v0, v1, v0
00131c2c bne   v0, zero, $00131c3c
00131c30 daddu  v0, zero, zero
00131c34 beq   zero, zero, $00131c64
00131c38 ld   ra, $0020(sp)
00131c3c lw   v0, $0040(s0)
00131c40 lbu   v1, $0022(s1)
00131c44 dsll32 v0, v0, 7
00131c48 sllv  v1, a0, v1
00131c4c dsrl32 v0, v0, 22
00131c50 and   v0, v0, v1
00131c54 bne   v0, zero, $00131c60
00131c58 andi  v0, a0, $00ff
00131c5c daddu  v0, zero, zero
00131c60 ld   ra, $0020(sp)

学科の値をロードしその分だけ1が左へシフトしたものとアイテムのデータをandし0であれば処理は終わり、
そうでなければ種族の処理へ進むという流れです。
本当に合っているのか若干疑問点がありますが、最終的にsltuでv0に0か1が入るようになっているので適当に
やっても出来ます
andでフラグがかみ合えば0以外の値がv0に入り、sltuで比較してv0が入る筈なのでv0に1が必ず入る様にします。
どこを変えるかは最初のandでの分岐。ここの結果によっては次の処理へまわってしまうのでこの部分をこの様に
変えました。

00131c28 and   v0, v1, v0
00131c2c j    $00131c60
00131c30 daddu  v0, zero, a0       v0=$00000001  
00131c34 beq   zero, zero, $00131c64

これで学科の装備できるか出来ないかのフラグで次へ回し、種族のフラグを省いた状態になります。
コードを使い装備できないはずのものが装備できるか試してみると見事成功しました。
呪われてしまう場合もありましたが…

この辺りに性別のデータをロードし、アイテムのデータと何かをしているものがあるかと思いますが
装備できたらいいかなと思ったのとビット命令が苦手で面倒だっだのでやめました。

ステータス確認するとHP全回復

これは現在HPをロードしている箇所を見つけ最大値のデータをストアさせる。という一般的な方法をやろうと
したのですが、補正がかかっているのか表示の最大値と変わってしまいます。
その為補正をしている処理を見つけなければいけません。
どうやって探すかですが補正で上限を超えた場合に最大値に修正する箇所を見つけるだけです
最大値検索で手当たり次第分岐を潰し、修正値($270f)を適当な数値($0063,$0064,$0065の等に)に書き換えコード化
表示された数値に変えた箇所が補正での上限修正処理だと予測できます。
結果ここが見つかりました。

0012d584 lh   a1, $000c(a0)              
0012d588 sll   v1, v1, 3
0012d58c addu  v0, v0, v1
0012d590 daddu  a0, a1, zero              
0012d594 lb   v0, $0000(v0)
0012d598 jal   $00147090
0012d59c addiu  a1, v0, $0064
0012d5a0 dsll32 v0, v0, 16
0012d5a4 dsra32 v0, v0, 16
0012d5a8 slti  at, v0, $2710
0012d5ac bne   at, zero, $0012d5bc
0012d5b0 nop
0012d5b4 beq   zero, zero, $0012d5c8
0012d5b8 addiu  v0, zero, $270f     v0=$0000270f
0012d5bc bgez  v0, $0012d5c8
0012d5c0 nop
0012d5c4 daddu  v0, zero, zero
0012d5c8 ld   ra, $0000(sp)
0012d5cc jr   ra

灰色の箇所が補正前の最大HP

この処理が復帰する際、補正された数値はv0へ格納されています。
この復帰から空きメモリへジャンプさせてもいいのですが、それだと最大HPのベースの値が青い箇所で
書き換えられているので余計な手間がかかります。
この処理の先頭から参照箇所へ行ってみるとこの様になっていました。

0016d5c8 daddu  a0, s0, zero              
0016d5cc jal   $0012d570
0016d5d0 dsra32 s2, s2, 16
0016d5d4 dsll32 t0, v0, 16               
0016d5d8 lui   v1, $0064        v1=$00640000
0016d5dc lui   v0, $000f        v0=$000f0000
0016d5e0 lui   a2, $0026        a2=$00260000
0016d5e4 dsra32 t0, t0, 16
0016d5e8 daddu  a3, s2, zero
0016d5ec ori   a1, v1, $0280      a1=$00640280
0016d5f0 ori   a0, v0, $c038      a0=$000fc038
0016d5f4 jal   $00162260
0016d5f8 addiu  a2, a2, $c580      a2="%4d/%4d"

青い箇所の次の行が先ほどの処理へのjalです。
lh a1, $000c(a0)のa0レジスタは書き換えられてしまいましたが、その前の青い箇所でs0をa0へそのまま移しています
その後s0は書き換えられていません。s0をベースにし現在HPの$000eへストアさせることでこの処理を通った際に
補正された最大値を現在HPへストア(回復)する事が可能です。

この次に見てもらいたいのが復帰後のv0で、灰色の箇所で左へ32+16ビットシフト、アドレス$0016d5e4で
右へ32+16ビットシフト。
ここまででv0からt0へ数値が移っています。
つまりsh t0, $000e(s0)をこの辺りに割り込ませると最大値が現在値にストアされるようになります。
更に下を見るとjalがあったのでここを
jal $000f7030に変え、

$000f7030 sh t0, $000e(s0)
$000f7034 j $00162260
この様に変えると割り込ませることが出来ます。
これでステータス確認するとHP全回復が出来ました。

ステータス確認すると年齢変更

年齢は経過日数の違うセーブデータを用意し比較して探していく。という方法をとってもいいのですが、
このソフトはメモリーカード1枚につきセーブが1箇所しか出来ず、バックアップは取るなと言わんばかりの
いやらしい仕様をしています。
ジャグラーを使って別のデータを残せばいいだけなんですが、それも面倒だったので別の方法を探しました。
"AGE: %d"というラベルがあり参照箇所もきちんとしたところになっています。

00136e84 jal   年齢           ▲$0012cc50
00136e88 daddu  a0, s4, zero
00136e8c andi  a3, v0, $00ff
00136e90 lui   a2, $0022
00136e94 lui   v0, $0006        v0=$00060000
00136e98 addiu  a2, a2, $e6f0      a2="AGE:   %d"
00136e9c ori   a0, v0, $0110      a0=$00060110
00136ea0 lui   v0, $0064        v0=$00640000
00136ea4 jal   $00162260
00136ea8 ori   a1, v0, $0180      a1=$00640180
00136eac jal   経過日数         ▲$0012cc70
00136eb0 daddu  a0, s4, zero
00136eb4 andi  a3, v0, $ffff
00136eb8 lui   a2, $0022
00136ebc lui   v0, $0007        v0=$00070000
00136ec0 addiu  a2, a2, $e700      a2="DAY:   %d"

この周辺のjalへ移動してみるとこの様になっていました。

0012cc50 lhu   v0, $002e(a0)              年齢(ラベル)
0012cc54 dsll32 v0, v0, 0
0012cc58 dsra32 v0, v0, 0
0012cc5c andi  v0, v0, $fe00
0012cc60 dsra  v0, v0, 9
0012cc64 jr   ra
0012cc68 andi  v0, v0, $00ff
0012cc6c nop
0012cc70 lh   v0, $002e(a0)              経過日数(ラベル)
0012cc74 jr   ra
0012cc78 andi  v0, v0, $01ff

年齢・経過日数は私が後からつけたものなので無視して下さい

ロードしているのでここを変えると年齢が変わりそうです。
最初は"AGE: %d"から下を見ていき$00136eacがそれらしかったのでそのまま0をストアさせたものを作りました。
掲示板にも残っていますがそれは間違っていて"AGE: %d"から上の方にある$00136e84のjalが年齢のロードでした。
良く見てみるとジャンプ先の図の上が年齢、下が経過日数になっています。
どうやら日数が下位9ビットにあり、年齢が上位7ビットにあるようです(ハーフワードでの上位・下位)
ビットは嫌いなんですがなんとかコード化に成功。
どうしてビット単位で分けるんでしょうね。バイト、ハーフワードで分けてくれるとわかりやすいのに。めんどくさ。

とりあえずどんな風に作ったかというとジャンプ先の図の付近はLV等のデータをロードしている箇所があり、
そこはステータス確認時にも読み込まれる事がわかっていましたので、そこに割り込ませました。
日数の復帰に割り込ませられるといいのですが、そこはステータス確認時に読み込んでいないのかコードが
効かなかったので仕方なくLVの復帰に割り込ませました。おかげで省略出来そうな分まで入っています。

000f7100 lh   t4, $002e(a0)              
000f7104 andi  t4, t4, $01ff
000f7108 ori   t5, zero, $0001     t5=$00000001
000f710c dsll  t5, t5, 9
000f7110 or   t5, t5, t4
000f7114 jr   ra
000f7118 sh   t5, $002e(a0)

この様な形で経過日数をそのまま残し、年齢のみ変更できる様にしてみました。
$0001の部分がコードでの数値指定部です。

購買で全アイテム購入可能

どの様に探すかというと購入時の所持金の減少処理を探し、そこを遡って各アイテムの価格設定をしている箇所を
特定します。後はその周辺で怪しいところを調べていくとできます。
まず所持金の減少処理は$0012f030から始まる処理になり、a1の値が変動量でした。
そこから参照箇所を探し、jal前のa1を適当な数値に変え、購入した際の変動量がその変えた数値になった箇所が
購入での減少処理になります。

ここがその箇所でv0の±を反転させa1に格納されています

0013ee48 jal   $0017b930
0013ee4c addiu  a0, sp, $0040
0013ee50 subu  a1, zero, v0              
0013ee54 jal   $0012f030                

この図の1番上からジャンプしたのが↓です。v0つまり各アイテムの価格を設定しています

0017b930 addiu  sp, sp, $fff0
0017b934 sd   ra, $0000(sp)
0017b938 jal   $0017bbe0                
0017b93c nop
0017b940 ld   ra, $0000(sp)
0017b944 lw   v0, $005c(v0)              
0017b948 jr   ra

lw v0, $005c(v0)のベースの部分で種類をずらしているはずなので、その上の青い箇所からジャンプ

0017e6e0 andi  a0, a0, $ffff              
0017e6e4 slti  v0, a0, $0280
0017e6e8 bne   v0, zero, $0017e6fc
0017e6ec sll   v1, a0, 1
0017e6f0 lui   v0, $007f        v0=$007f0000
0017e6f4 beq   zero, zero, $0017e718
0017e6f8 addiu  v0, v0, $ca30      v0=$007eca30
0017e6fc lui   v0, $007f        v0=$007f0000
0017e700 addu  v1, v1, a0
0017e704 addiu  v0, v0, $ca30      v0=$007eca30
0017e708 sll   v1, v1, 2
0017e70c addu  v1, v1, a0
0017e710 sll   v1, v1, 4
0017e714 addu  v0, v0, v1
0017e718 jr   ra

次の処理をたどると↑の様になっています。後はこの処理の参照箇所を見ていき、
lb x, $00xx(v0)という様な怪しい箇所を探していけばいいだけ
これは在庫数が記録されるという仕様になっており、多くても最大が255個は超えないだろうという推測で
バイトのロードを探します。

色々と見ていくとループしている処理があり、試しにそこをいじってみると全アイテムが購買に並んでいました。
ここがそのループ処理です。

0013eaf0 jal   $0017e6e0                
0013eaf4 andi  a0, s0, $ffff
0013eaf8 lbu   v1, $0064(v0)
0013eafc beq   v1, zero, $0013eb60           
0013eb00 daddu  a2, zero, zero
0013eb04 daddu  a3, zero, zero
0013eb08 lw   a0, $8438(gp)
0013eb0c lbu   a1, $0065(v0)
0013eb10 sll   v1, a0, 1
0013eb14 lui   v0, $001f        v0=$001f0000
0013eb18 addu  v1, v1, a0
0013eb1c addiu  v0, v0, $ac00      v0=$001eac00
0013eb20 sll   v1, v1, 2
0013eb24 addu  v1, v0, v1
0013eb28 addu  v0, a3, v1
0013eb2c lw   v0, $0000(v0)
0013eb30 bne   a1, v0, $0013eb50
0013eb34 lui   v0, $0028        v0=$00280000
0013eb38 addiu  s1, s1, $0001      s1=$00000001
0013eb3c addiu  v0, v0, $7f10      v0=$00287f10
0013eb40 addu  v0, v0, s2        v0=$00287f10
0013eb44 sh   s0, $0000(v0)      [00287f10]
0013eb48 beq   zero, zero, $0013eb60
0013eb4c addiu  s2, s2, $0002      s2=$00000002
0013eb50 addiu  a2, a2, $0001      a2=$00000001
0013eb54 slti  v0, a2, $0003
0013eb58 bne   v0, zero, $0013eb28
0013eb5c addiu  a3, a3, $0004
0013eb60 addiu  s0, s0, $0001      s0=$00000001
0013eb64 slti  v0, s0, $0280
0013eb68 bne   v0, zero, $0013eaf0

lbu v1, $0064(v0)ロードした値をゼロ拡張しています。128個以上在庫がもてるんでしょう。
とりあえずこのv1が0であれば分岐する=個数が0の場合はそのままループし、次へとなっているので
ここを分岐させなければいいだけです。
この下を見るとlbu a1, $0065(v0)のロードした値によって分岐するかしないかというようになっています。
ループがslti v0, s0, $0280(全種類の個数)となっているので、ここは武器の種類などを見分けているのではないかと
推測し、分岐を潰してみると購入のどの項目を見ても貴重品を含む全種類が表示されるようになりました。
これで全種類購入可能です。

このコードを作ったので所持アイテム18個目変更のコードがいらなくなりましたが、あれはこのコードが出来るまでの
繋ぎだったからまぁいいかな。
スティックの傾きのデータを使ったコードが作りたかったので、Ver2は遊びで作ったバカコードの雰囲気が強いものです
左スティックを使用する事がないという点で都合が良かったので、左スティックを上にした状態で
動作するようにしました。
Ver2の上限下限チェックには雑記メモ書きのpminh,pmaxhを使用しています。

それとちょっとしたバグ技。アイテム変更で18個目に着物などの防具を入れて装備した状態にしておき、
18個目を武器に変えるとその部分がなくなります。マネキンのポリゴンが上半身裸に…

戻る