ラプソディア

キーワード
相対アドレス
ベース
レジスタ
論理積、排他的論理和


内容
攻撃すると敵即死
獲得経験値x倍その2
名前無しキャラ以外の仲間出現(セーブ反映)


わかっている範囲のセーブアドレス、相対アドレス
いつもの様に最大値検索し特定、後はセーブデータとのアドレス差で出せば大体下のものは見つけられます
基本値は装備等での増加分を除いた値。補正値は増加分を加算した値でLvupすると再計算される

キリル
幼少:青年:相対アドレス
9DB0:2AD8:00:Exp
9DB4:2ADC:04:武器Lv
基本値
9DB8:2AE0:08:現在HP
9DBA:2AE2:0A:最大HP
9DBC:2AE4:0C:力
9DBE:2AE6:0E:技
9DC0:2AE8:10:魔
9DC2:2AEA:12:避
9DC4:2AEC:14:直防
9DC6:2AEE:16:魔防
9DC8:2AF0:18:速
9DCA:2AF2:1A:運
補正値
9DCC:2AF4:1C:最大HP
9DCE:2AF6:1E:力
9DD0:2AF8:20:技
9DD2:2AFA:22:魔
9DD4:2AFC:24:避
9DD6:2AFE:26:直防
9DD8:2B00:28:魔防
9DDA:2B02:2A:速
9DDC:2B04:2C:運

9DDE:2B06:2E:装備体
9DE0:2B08:30:装備手
9DE2:2B0A:32:装備アイテム1
9DE4:2B0C:34:装備アイテム2

9DF0:2B18:40:装備アイテム8
9DF0:2B1A:42:頭装備紋章
9DF0:2B1B:43:右手装備紋章
9DF0:2B1C:44:左手装備紋章
9DF5:2B1D:45:最大MPLv1
9DF6:2B1E:46:MPLv2
9DF7:2B1F:47:MPLv3
9DF8:2B20:48:MPLv4
9DF9:2B21:49:残りMPLv1
9DFA:2B22:4A:MPLv2
9DFB:2B23:4B:MPLv3
9DFC:2B24:4C:MPLv4
9E00:2B28:50:フラグその1 +01存在,+02バトル参加,+10ツノウマ乗,+40バトル強制参加
9E01:2B29:51:フラグその2 +02死亡
9E04:2B2C:54:フラグその3 +02死亡
9E08:2B30:58:?
9E10:2B38:60:好感度?1
9E11:2B39:61:好感度?2
9E1C:2B44:6C:好感度Lv?1
9E1D:2B45:6D:好感度Lv?2
9E30:2B58:80:装備スキル1
9E31:2B59:81:装備スキル2

9E37:2B5F:87:装備スキル8
9E38:2B60:88:スキルLv

9E5B:2B83:AB:スキルLv

主人公の紋章回数制限フラグ除去
C61Dの最下位1ビットを排除

MP=紋章回数


戦闘中のアドレス差も中断データを見るとこの様な並びになってます。
中断データを作り、その時の経験値をバイナリで探せば戦闘中のステータスのアドレスを確認可能で
バトル参加、好感度等は中断データで見つけました

攻撃すると敵即死

このソフトの処理は特徴があり、敵と味方のステータスの処理が明確に分かれています
通常の場合HPの変動等は敵と味方で共通で行われており、
addu v0,v0,v1の様になっている部分を変えると双方で効果が出てしまう為に
効果の切り換えをしなくてはなりませんが、それをする必要がないので楽です

実際にどの様になっているのかは画像を見て下さい

002a55c8 addiu  sp, sp, $fff0            残りHPlh(ラベル)
002a55cc sd   s0, $0000(sp)
002a55d0 sd   ra, $0008(sp)
002a55d4 jal   $002a51a8
002a55d8 daddu  s0, a0, zero
002a55dc beq   v0, zero, $002a55f8
002a55e0 daddu  a0, s0, zero
002a55e4 ld   s0, $0000(sp)
002a55e8 ld   ra, $0008(sp)
002a55ec j    $002ea000
002a55f0 addiu  sp, sp, $0010
002a55f4 nop
002a55f8 ld   s0, $0000(sp)
002a55fc ld   ra, $0008(sp)
002a5600 j    $002ebdd8              
002a5604 addiu  sp, sp, $0010

例の如く、残りHPlhは勝手に付けたラベルです

$002a55dcの分岐で分岐すると敵。分岐しないと味方
敵の方は"WMonster_"というラベルへの参照があり、実際にデータを変えてみると
敵だけに効果が出るのでわかります
ちなみにこの画像部分は残りHPのロード部分で、変動、ストアは別の場所となります
攻撃すると瀕死の方が変動、ストア部分なので最初はそこを0にしてみましたが
それでは変動後0になるだけで倒した事にはならないという状況になり、
無意味なので減算する部分の元の値、つまりロード部分を0にする事で一撃で倒せる様になります

もう少し基本部分からの説明
最大値の探し方
$03e7を16ビット検索又は参照箇所の検索で探し、分岐を潰していくと
$002e9e40の分岐を潰し装備を変更するとHP・力〜運が最大値になるという事がわかります

002e9df0 lh   a0, $01d4(s0)
002e9df4 sll   v1, a0, 1
002e9df8 addu  v1, v1, a0
002e9dfc sll   v1, v1, 4
002e9e00 addu  v1, v1, a0
002e9e04 sll   v1, v1, 3
002e9e08 addu  a1, v0, v1
002e9e0c beq   a1, zero, $002e9e54
002e9e10 addiu  v0, zero, $ffff     v0=$ffffffff
002e9e14 bltz  s1, $002e9e58
002e9e18 ld   s0, $0000(sp)
002e9e1c slti  v0, s1, $0009            
002e9e20 bnel  v0, zero, $002e9e30
002e9e24 sll   v0, s1, 1
002e9e28 beq   zero, zero, $002e9e58
002e9e2c addiu  v0, zero, $ffff     v0=$ffffffff
002e9e30 andi  v1, s2, $ffff
002e9e34 addu  v0, v0, a1
002e9e38 sltiu  v1, v1, $03e8
002e9e3c addiu  a1, v0, $001c            
002e9e40 bne   v1, zero, $002e9e50
002e9e44 sh   s2, $0000(a1)
002e9e48 addiu  v0, zero, $03e7     v0=$000003e7
002e9e4c sh   v0, $0000(a1)

しかしこれでは不十分でLvupすると変更前の値に戻ってしまいます
これは上のリストにある様に元となるステータスに装備等での増加部分を加算した値を最大値修正している為。
(この状態でセーブしてデータを見ると$03e7が並んでいますが、その前の部分に元となる値がある事が確認できます)
Lvupしても特定の数値を維持する為にはこの元の値を変更する必要がありますが
今回は関係ないのでアドレス差を出すだけでいいです

次にsh v0, $0000(a1)の辺りを見て下さい
ベースとなるa1レジスタがaddiu a1, v0, $001cで設定されています
この$001cを相対アドレスとして考えます
そうしてセーブデータとのアドレス差で出来た結果が最初のリスト。
この部分では比較命令と1ビットシフト(+2h)の組み合わせで9種類のデータが管理されています

$001cはその9種類のデータの先頭の相対アドレスで現在HPの相対アドレスはセーブデータを見る限り$0008
大体似た様な処理は固まってあるので画像の部分付近でlhu x,$0008(y)となっている部分を探すとこちらが見つかります

002ea058 lh   a0, $01d4(s0)
002ea05c sll   v1, a0, 1
002ea060 addu  v1, v1, a0
002ea064 sll   v1, v1, 4
002ea068 addu  v1, v1, a0
002ea06c sll   v1, v1, 3
002ea070 addu  v1, v0, v1
002ea074 beq   v1, zero, $002ea080
002ea078 addiu  v0, zero, $ffff     v0=$ffffffff
002ea07c lhu   v0, $0008(v1)
002ea080 ld   s0, $0000(sp)
002ea084 ld   ra, $0008(sp)
002ea088 jr   ra

$002ea07cがその味方の現在HPとなりますので、敵用を探す為にこの処理の先頭からF3を押し参照箇所へ行きます

最初の画像の$002a55ecが参照箇所となり、その前の分岐で分岐すると$002a5600の敵用の処理へのジャンプがあるので
ここを→キーで飛び見ると

002ebe50 lui   a1, $0045        a1=$00450000
002ebe54 addiu  a0, a0, $1200      a0=$00452400
002ebe58 addiu  a1, a1, $1230      a1="WMonster_"
002ebe5c daddu  a2, s1, zero
002ebe60 jal   $001009b8
002ebe64 daddu  a3, s0, zero
002ebe68 beq   zero, zero, $002ebe90
002ebe6c daddu  v1, zero, zero
002ebe70 jal   $00320140
002ebe74 nop
002ebe78 sll   v1, s0, 1
002ebe7c addu  v1, v1, s0
002ebe80 sll   v1, v1, 4
002ebe84 addu  v1, v1, s0
002ebe88 sll   v1, v1, 3
002ebe8c addu  v1, v0, v1
002ebe90 beq   v1, zero, $002ebe9c
002ebe94 addiu  v0, zero, $ffff     v0=$ffffffff
002ebe98 lhu   v0, $0006(v1)            
002ebe9c ld   s0, $0000(sp)
002ebea0 ld   s1, $0008(sp)
002ebea4 ld   ra, $0010(sp)
002ebea8 jr   ra

この様になっているのでこのロード部分のv0を0に変えるとダメージを与えると死亡するかの判定で
すでに0になっている為一撃死させる事が出来ます
これを踏まえボタンを押している時のみ有効になる様なコードはこの様になっています

000f700c lui   v0, $0049        v0=$00490000
000f7010 lhu   v0, $55c2(v0)      v0=$004955c2
000f7014 andi  v0, v0, $0100
000f7018 beq   v0, zero, $000f7028
000f701c nop
000f7020 j    $002ebdd8
000f7024 nop
000f7028 jr   ra

フックを掛ける場所はlhu v0, $0006(v1)の部分を潰すと、潰したロード命令を加えなければならない為
この一つ手前の処理(最初の画像)の$002a5600に掛けています。

L2を押している場合はv0=0となるようにしたかったので押している場合はv0=0で分岐、
jr raで復帰する為ロードの処理は通らないようになり、押していない場合は0以外が入るので分岐せず
ロードの処理へまわります。
$004955c2はマイナス形式のパッドアドレスですのでL2のみを押した場合$FEFFが入っています。
L2を押した場合0にするにはxori v0,v0,$FEFFを入れる。というものが思いつくかと思いますが
これでは他のボタンも同時に押していると0にはならなくなってしまう為、
andi v0,v0,$0100を使い、押していない場合にフラグかかみ合いv0=$0100になるようにしています。
押している場合は$FEFF(例え他のボタンを押していても9ビットの部分は0)と$0100(9ビットの部分が1)の
論理積ですので0になります。これが出来たコードです。

ダメージあたえると敵即死
1C9AF334 2054E75E
1C9AF338 88941AE7
1C9AF33C 2494E6A5
1C9AF340 0496E7A8
1C9AF348 0C4B8093
1C9AF350 15F6E79D
1CBDD928 0C5393A8

追記
パッドコードマイナス形式と論理積の組み合わせですが、新発見かと思ってたら
shinさんがとっくにこの使い方をしていました。アホですね自分

獲得経験値x倍その2

最大値を見つけられると比較的すぐに見付かります。
LvはExp1000毎にupする様に固定されており、セーブデータを見ると1500(10進数)だとした場合Lv2,Exp500となっています
45678だとLv46,Exp678
つまり最大のLv50,Exp999は10進数で49999になります。
後はこれを16進数の$c34fで16ビット検索し比較している所を見つけ、
必ず$c34fが入るようにしてやると経験値獲得すると最大になるコードができます。
後はその部分の処理を遡ればできます。

002e84d4 jal   経験値lw         ▼$002e8590
002e84d8 daddu  s0, v0, zero       s0=$ffffffff
002e84dc addu  s1, v0, s1
002e84e0 ori   v1, zero, $c34f     v1=$0000c34f
002e84e4 sltu  v0, v1, s1
002e84e8 daddu  a0, s4, zero
002e84ec jal   経験値lw         ▼$002e8590
002e84f0 movn  s1, v1, v0             
002e84f4 addiu  v1, zero, $03e8     v1=$000003e8
002e84f8 addiu  a1, zero, $03e8     a1=$000003e8
002e84fc div   v0, v1
002e8500 divu1  s1, v1

上のmovn s1,v1,v0のs1にv1が入るようにしてやる

$002e8590へのjalは経験値のロードでv0に経験値を入れて復帰。
比較命令はこのv0+s1で比較しているのでs1が増加分となります。

遡ると$002e843cでdaddu s1, a1, zeroとなっていてこの処理より前の
a1レジスタに増加量が入っています。
獲得時の表示は変わりませんが、x倍にするだけならここをsll s1, a1, xにするだけでもいいです。
x=1=2倍,x=2=4倍
ですが表示まで変えられる場合もある為遡れるだけ遡った方がいいです
経験値x倍その1ではLvup画面がでない為に効果がないと思われた方も結構居た模様

では続けて遡ります
処理の先頭$002e8430から参照箇所を検索する
参照箇所=$002a6174
参照箇所の処理の先頭$002a6140
この処理では
$002a6158 daddu s1, a1, zero
$002a6164 daddu a1, s1, zero
増加量=a1で$002e8430へ続く

処理の先頭$002a6140から参照箇所を検索する
参照箇所=$002bb054
参照箇所の処理の先頭$002bafc8
この処理では
$002baff0 daddu s5, a1, zero
$002bb04c daddu a1, s5, zero
増加量=a1で$002a6140へ続く

処理の先頭$002bafc8から参照箇所を検索する
参照箇所=$002d390cこの処理では
daddu s1, v0, zero
daddu a1, s1, zero
増加量=a1で$002bafc8へ続く

002d38f0 jal   $002baf50
002d38f4 daddu  a0, s2, zero
002d38f8 daddu  a0, s2, zero
002d38fc jal   $002bb170
002d3900 daddu  s1, v0, zero       s1=$002d0000
002d3904 daddu  s0, v0, zero       s0=$002d0000
002d3908 daddu  a0, s2, zero
002d390c jal   $002bafc8              
002d3910 daddu  a1, s1, zero       a1=$002d0000

ここまで参照箇所としてヒットする場所はそれぞれ1箇所のみ

このdaddu s1, v0, zeroの
v0が増加量。遅延スロットである為手前のjal$002baf50に行くと
$002baf8c lh v0, $0106(s1)ここが大体の基礎となり、v0をロードして復帰
daddu s1, v0, zero→sll s1, v0, 1にして増加量2倍にしたものが獲得経験値x倍その1。となりますが
これでは正常にLvup画面がでない場合があるので大体の基礎のlh v0, $0106(s1)のv0を
復帰前に空きメモリへ飛ばし2倍にしています。
このロードがある処理は参照箇所が2箇所あるのでここで復帰する前に変更するとLvupが正常に表示されるようです。

獲得経験値2倍その2
002baf9c j $000f7044
000f7044 jr ra
000f7048 sll v0, v0, 1
もしかするとjal$002baf50でジャンプした先のすぐ上の処理を見るとp$0106のロード、
s2加算、ストアがあるので$002baf14 daddu s2, a1, zeroをsll s2, a1, 1に変えるだけでよかったのかもしれません。
確認が面倒なので確認しませんが

名前無しキャラ以外の仲間出現(セーブ反映)

キャラの存在フラグは固まってあるか、それぞれのステータス周辺にある場合が多く、
ラプソディアはステータスに含まれていました。
見つけ方はわざと死亡させてフラグをたててやり、セーブデータで比較します。
仲間になっている場合となっていない場合で比べてもいいのですが、
このソフトは一度に数人仲間になる場合が多く相違箇所を調べるのに手間がかかる為死亡させて比較します。
死亡した場合はp$0051,p$0054それぞれに+02されると分かります。
仲間になっているかはこの辺りだと目安をつけて仲間になっている、仲間になっていないキャラとの
データを見比べていきます。
そうするとp$0050が$00E1だと仲間になっている。$00E0だと仲間になっていないという事が分かります。
ですからp$0050の最下位1ビットが存在フラグで、p$0050のロードとビット命令が
ある場所を探します。

$0050で16ビット検索をするとヒットする数が多すぎるので
ステータスはlh x, $01d4(y)でxの値が各キャラのナンバーとなり、
アドレスをずらしている。という点から$01d4を16ビット検索し、
それによる数値がベースでp$0050のロード命令を探していくと
若干手間はかかりますが見付かります。

002ea5a8 lh   a0, $01d4(s0)            
002ea5ac sll   v1, a0, 1
002ea5b0 addu  v1, v1, a0
002ea5b4 sll   v1, v1, 4
002ea5b8 addu  v1, v1, a0
002ea5bc sll   v1, v1, 3
002ea5c0 addu  v1, v0, v1
002ea5c4 beq   v1, zero, $002ea5d4
002ea5c8 daddu  v0, zero, zero
002ea5cc lw   v0, $0050(v1)            
002ea5d0 and   v0, v0, s1
002ea5d4 ld   s0, $0000(sp)
002ea5d8 ld   s1, $0008(sp)
002ea5dc ld   ra, $0010(sp)
002ea5e0 jr   ra
002ea5e4 addiu  sp, sp, $0020
002ea5e8 addiu  sp, sp, $ffe0
002ea5ec sd   s0, $0000(sp)
002ea5f0 sd   s1, $0008(sp)
002ea5f4 daddu  s1, a1, zero
002ea5f8 sd   s2, $0010(sp)
002ea5fc sd   ra, $0018(sp)
002ea600 jal   $002e6a60
002ea604 daddu  s2, a2, zero
中略
002ea660 addiu  v0, zero, $ffff     v0=$ffffffff
002ea664 beq   s2, zero, $002ea678         
002ea668 lw   v0, $0050(a0)
002ea66c beq   zero, zero, $002ea680
002ea670 or   v0, v0, s1
002ea674 nop
002ea678 nor   v1, zero, s1            
002ea67c and   v0, v0, v1
002ea680 sw   v0, $0050(a0)
002ea684 addiu  v0, zero, $0001     v0=$00000001

$002ea5cc lw v0, $0050(v1)をaddiu v0,0,$00e1にしてやると仲間全員出現(セーブ未反映)ができます。
セーブに反映させるにはストアさせなければならないので空きメモリにとばすか、
フラグ追加の際の処理へ無理矢理まわすかになります。
このフラグチェックの付近を見ると下の方にorかnorでp$0050にストアさせています。
これを利用し、無理矢理フラグチェック時にフラグ追加させてやればセーブ反映となります。
やり方は復帰を潰し、必要なデータを入れるだけ。

$002ea5e0を潰し復帰させずにフラグ追加・除去処理へ続けてまわす。
addiu sp,sp,$xxxxで処理の先頭、復帰前それぞれでspの値を元に戻している為
ここの復帰を潰しても大丈夫です
正確には大丈夫とは言えませんが、フラグのチェックでの復帰後使用されるレジスタはv0で
フラグ追加・除去処理ではv0=1かv0=ffffffffで復帰します。
その為チェック時に問題が発生し、仲間が全員表示されない状態になりますが、チェック時に記録されるフラグは
下に記載した様に固定されている為、セーブ後使用をやめると一応大丈夫なのですが、
そんな中途半端な物は使いたくないという場合はこちらを使用して下さい
1C9A0328 3846E7A6
1C9A032C 1486D7C2
1C9A0330 B074E775
1C9A0334 0C4B7E92
1C9A0338 1485D7C1
1CB926F8 0C53C7A5
この様に長くなるのであの状態になりました
上のコードはこの様になっています
002ea5d0 j $000f8000
000f8000 addiu s0,0,$0001
000f8004 or v0,v0,s0
000f8008 sw v0, $0050(v1)
000f800c j $002ea5d4
000f8010 and v0,v0,s1

元に戻り復帰を潰した後
$002ea664のs2が0だとnorでフラグ除去、s2が0以外だとorでフラグ追加となるため
$002ea604をaddiu s2, zero, $0001にして追加にする。

or v0, v0, s1のv0がlw v0, $0050(a0)である為s1が追加するビットとなる
$002ea5f4をaddiu s1, zero, $0001にして最下位1ビットに設定する

これで仲間になっているかフラグをチェックする際にフラグを追加させる事ができます。
但し本来は論理積での復帰である為常に動作させる訳にはいかず、セーブ後使用を止める
という流れになります。

名前無しキャラ以外の仲間出現(セーブ反映)
1CB92608 1456E7A5
1CB9261C 3845E7A6
1CB9292C 3844E7A6

戯れ言
これを機会にPSのSRPGのコードを見直したんですが
PSには遅延スロットの様にロード待ちというものがあったんですね。r5900になってからなくなってよかったです。
分岐、ジャンプの遅延スロットは使えるけど、ロード待ちはスペースを考えるとない方がいい

戻る