第16回  「専用改造ツールの作成(セーブデータ改造)」



初期化処理

「KAIZOU_OreBuster.vbp」をVBで開くと、以下の3つのファイルが右上のウィンドウに表示されます。

frmKAIZOU_OreBuster.frm(パラメータ表示・改造画面のフォーム)
EditEngine.bas
StrCtrl.bas

下の2つのファイルに関しては、MaferLiteの時と全く同じものなので、違うのはフォームだけです。
以降は、このフォームに記述されたプログラムを見ていくことになります。

まずは、変数宣言部分のプロシージャを見ておきましょう。

General_Declarations プロシージャ
'セーブデータのフルパス名を格納するための変数
Dim SaveFileName As String

'データ読み込み時に、アイテムを何種類持っていたか
Dim ItemNumber As Long


この2つは、いくつかのプロシージャ内で利用する変数なので、フォームの共通部分、
「General_Declarations」に記述しておく必要があります。
それぞれが何に使われているかは、コメントの通りなので説明は不要でしょう。

それでは、これ以降、動作する順番に従って、ソースプログラムを追っていきたいと思います。
まずは、プログラムを起動してすぐに実行される部分を見てみましょう。


Form_Load プロシージャ
Private Sub Form_Load()
  cmdWrite.Enabled = False
  txtHP.Enabled = False
  txtMaxHP.Enabled = False
  txtSTR.Enabled = False
  txtDEF.Enabled = False
  txtLUCK.Enabled = False
  txtSPEED.Enabled = False
  txtGOLD.Enabled = False
  For i = 0 To 4
    txtITEM(i).Enabled = False
  Next i
End Sub


メイン画面のフォームがディスプレイに表示される時に、上記の部分が実行されます。
ここには初期設定を記述しておく事が多いのですが、これもそうです。

内容はといいますと、HPやお金などのパラメータを入力するテキストボックスと、
書き換えを実行するためのコマンドボタンを、使用不可にしています。
これは、データを読み込んでいない間は、データの書き込みをできないようにするための処理です。


セーブデータ読み込み処理

次に、読み込みボタンをクリックした時の処理を見てみましょう。


cmdRead_Click プロシージャ1
Private Sub cmdRead_Click()

  'セーブデータを読み込む
  With CommonDialog1
  .DialogTitle = "コードファイルの選択"
  .Filter = "セーブデータ(savedata.dat)|savedata.dat|"
  .Flags = cdlOFNHideReadOnly
  End With
  CommonDialog1.ShowOpen
  If CommonDialog1.FileName <> "" Then
    SaveFileName = CommonDialog1.FileName
  Else
    Exit Sub
  End If


長い処理なので、3つに分割しておきました。
ここでは、セーブデータを読み込むためのダイアログボックスを表示させています。
また、このゲームのセーブデータは「savedata.dat」ですので、それ以外のファイルが表示されないようにしておきます。

そして、選択されたセーブデータのフルパス名を「SaveFileName」に格納します。


cmdRead_Click プロシージャ2
  '各データをセーブデータから読み込み、それぞれのボタンを使用可・不可に
  DataGet

  cmdRead.Enabled = False
  cmdWrite.Enabled = True
  txtHP.Enabled = True
  txtMaxHP.Enabled = True
  txtSTR.Enabled = True
  txtDEF.Enabled = True
  txtLUCK.Enabled = True
  txtSPEED.Enabled = True
  txtGOLD.Enabled = True


セーブデータのフルパス名を取得することができたら、次にそのデータを読み込みます。
その後、フォームのロード時に使用不可にしていたコントロールを使用可能にしておきます。
データを読み込む処理は、「DataGet」というサブルーチンにまとめてあります。


DataGet プロシージャ
Private Sub DataGet()
  lblName.Caption = HexToDecOrAscii(GetFileData(SaveFileName, "0", "16"), "$2")
  txtHP.Text = HexToDecOrAscii(GetFileData(SaveFileName, "30", "2"), "M")
  txtMaxHP.Text = HexToDecOrAscii(GetFileData(SaveFileName, "32", "2"), "M")
  txtSTR.Text = HexToDecOrAscii(GetFileData(SaveFileName, "38", "2"), "M")
  txtDEF.Text = HexToDecOrAscii(GetFileData(SaveFileName, "3C", "2"), "M")
  txtLUCK.Text = HexToDecOrAscii(GetFileData(SaveFileName, "40", "2"), "M")
  txtSPEED.Text = HexToDecOrAscii(GetFileData(SaveFileName, "42", "2"), "M")
  txtGOLD.Text = HexToDecOrAscii(GetFileData(SaveFileName, "46", "2"), "M")
  txtITEM(0).Text = HexToDecOrAscii(GetFileData(SaveFileName, "82", "1"), "S")
  txtITEM(1).Text = HexToDecOrAscii(GetFileData(SaveFileName, "97", "1"), "S")
  txtITEM(2).Text = HexToDecOrAscii(GetFileData(SaveFileName, "AC", "1"), "S")
  txtITEM(3).Text = HexToDecOrAscii(GetFileData(SaveFileName, "C1", "1"), "S")
  txtITEM(4).Text = HexToDecOrAscii(GetFileData(SaveFileName, "D6", "1"), "S")
End Sub


えーと、ほとんど同じ処理が続いていますね。
ですから、上の2行についてのみ解説します。

まず、1行目ですが、キャラクターの名前を読み込んでいます。
キャラクターの名前は、ファイルのアドレス0番地から16バイトに渡って格納されていますので、
これを読み出して、ラベルコントロールに表示しています。

そのために、第14回で説明した、「GetFileData」関数を使っています。
ただ、このままでは、16進数の数値がそのまま表示されてしまいますので、
「HexToDecOrAscii」関数で目的の型に変換してあります。
(キャラクターの名前は全角(2バイト)なので、引数に「$2」を指定しています)

これさえ分かれば、2行目もほとんど同じです。
キャラクターの現在HPは、ファイルのアドレス30番地に2バイトで格納されています。
これを読み込んで、10進数に変換して表示しています。
(2バイトの10進数なので、引数に「M」を指定しています)

これで、データの読み込みは終了しました。


cmdRead_Click プロシージャ3
  '持っていないアイテムの個数を増やすのを避けるための処理

  ItemNumber = 0
  For i = 0 To 4
    If txtITEM(i).Text <> "0" Then
      txtITEM(i).Enabled = True
      ItemNumber = ItemNumber + 1
    Else
      txtITEM(i).Text = ""
    End If
  Next i

End Sub


あとは、最後のひと工夫をしてできあがりです。
このプログラムはアイテムの個数を変更する事も可能なのですが、
まだ持っていないはずのアイテムの数を増やされると不都合がでそうなので、この対処を考えます。
(このゲームは、アイテムを上から順番に並べて使用するようになっています)

とは言っても、特に難しい処理はしていません。
読み込んだアイテムの個数が「0」であったなら、そのアイテムは持っていないとみなすだけです。
持っていないアイテムの部分、例えばアイテムを2種類しか持っていない場合は、
2番目までのテキストボックスを使用可能にし、3番目以降のテキストボックスの値を「””」にします。

また、書き換えを行う時に利用するので、持っていたアイテムの種類数を「ItemNumber」に記録しておきます。


これで、データの読み込み〜表示までが終わりました。
あとは、各パラメータの値を好きなように書き換えて、書き換みボタンをクリックすれば
データの書き換えができるんでしたね。


データ入力チェック処理

さて、パラメータの値を好きなように書き換えると書きましたが、実際にはいくつかの制約があります。
たとえば、数値しか入力できないとか、数値の上限が999とかですね。
これらのチェックはデータの入力時に行っていますので、まずはこちらを確認しておきましょう。
どのテキストボックスでも、処理はほとんど同じですので、現在HPを参考にします。


txtHP_GotFocus プロシージャ
Private Sub txtHP_GotFocus()
  SelText txtHP
End Sub


txtHP_LostFocus プロシージャ
Private Sub txtHP_LostFocus()
  If txtHP.Text = "" Then
    Exit Sub
  End If
  If IsNumeric(txtHP.Text) = False Then
    MsgBox "数値以外は入力できません", vbOKOnly + vbExclamation, "入力エラー"
    txtHP.SetFocus
    Exit Sub
  End If
  If CLng(txtHP.Text) < 1 Or CLng(txtHP.Text) > 999 Then
    MsgBox "入力できる範囲をは、1 〜 999 までです", vbOKOnly + vbExclamation, "入力エラー"
    txtHP.SetFocus
    Exit Sub
  End If
End Sub


上の処理では、現在HPを示すテキストボックスがフォーカスを受け取ったとき、その内容を選択状態にしています。
これは、データを書き換えるときに、いちいちカーソルを左右に移動するのが面倒なので、
データをすぐに上書きして入力できるようにするための配慮です。

下の処理では逆に、テキストボックスがフォーカスを失ったとき、データのチェックを以下の順で行っています。
1.何も入力されていなければ、チェック終了
2.数値以外が入力されていた場合は、エラーメッセージを出し、フォーカスを移動
3.1〜999以外の数値が入力されていた場合も同様


セーブデータ書き込み処理

では、続いてデータの書き込みボタンをクリックした時の処理を見てみます。


cmdWrite_Click プロシージャ
Private Sub cmdWrite_Click()
  If InputDataCheck <> 0 Then Exit Sub
  Dataset
  cmdEnd.SetFocus
End Sub


データのチェック・データの書き込みは、別途、関数やサブルーチンにしてありますので、 非常にシンプルですね。
データのチェックの結果異常がなければ、書き込みを行い、終了ボタンにフォーカスを移しています。

それでは、データのチェックをしている関数を確認しましょう。


InputDataCheck プロシージャ
Private Function InputDataCheck() As Long
  InputDataCheck = 1

  If Len(txtHP.Text) = 0 Then
    MsgBox "HPの値を入力して下さい", vbOKOnly + vbExclamation, "空値エラー"
    txtHP.SetFocus
    Exit Function
  End If
  If Len(txtMaxHP.Text) = 0 Then
    MsgBox "最大HPの値を入力して下さい", vbOKOnly + vbExclamation, "空値エラー"
    txtMaxHP.SetFocus
    Exit Function
  End If
  If Len(txtSTR.Text) = 0 Then
    MsgBox "STRの値を入力して下さい", vbOKOnly + vbExclamation, "空値エラー"
    txtSTR.SetFocus
    Exit Function
  End If
  If Len(txtDEF.Text) = 0 Then
    MsgBox "DEFの値を入力して下さい", vbOKOnly + vbExclamation, "空値エラー"
    txtDEF.SetFocus
    Exit Function
  End If
  If Len(txtLUCK.Text) = 0 Then
    MsgBox "LUCKの値を入力して下さい", vbOKOnly + vbExclamation, "空値エラー"
    txtLUCK.SetFocus
    Exit Function
  End If
  If Len(txtSPEED.Text) = 0 Then
    MsgBox "SPEEDの値を入力して下さい", vbOKOnly + vbExclamation, "空値エラー"
    txtSPEED.SetFocus
    Exit Function
  End If
  If Len(txtGOLD.Text) = 0 Then
    MsgBox "GOLDの値を入力して下さい", vbOKOnly + vbExclamation, "空値エラー"
    txtGOLD.SetFocus
    Exit Function
  End If

  For i = 0 To 4
    If ItemNumber > i And txtITEM(i).Text = "" Then
      MsgBox "アイテムの数を入力して下さい", vbOKOnly + vbExclamation, "空値エラー"
      txtITEM(i).SetFocus
      Exit Function
    End If
  Next i

  If txtHP.Text > txtMaxHP.Text Then
    MsgBox "HPに、MaxHPより大きい値が指定されています", vbOKOnly + vbExclamation, "範囲エラー"
    txtHP.SetFocus
    Exit Function
  End If

  InputDataCheck = 0
End Function


ここでは、データを書き込む前の最終チェックをしています。
ますは、関数の戻り値にエラーを示す「1」をセットしておきます。
処理が中断してしまった時は、この値が戻り値になるわけですね。

次に、データが未入力の場合はエラーメッセージを出し、
その部分にフォーカスを移動して、データの書き込みを中止します。
このチェックを、現在HP,最大HPなどについて1つ1つ行います。

また、現在HPが最大HPを上回っている場合も、ゲームとしておかしいデータと考えられますので、
これも同様にチェックしています。

チェックに問題が無ければ、関数の戻り値に正常終了を示す「0」をセットします。
関数の最後まで進んだ時が、正常終了という事ですね。


Dataset プロシージャ
Private Sub Dataset()

  Const MAX_CODE = 12
  Dim Code(MAX_CODE - 1) As String  '改造コードを格納する配列

  Code(0) = "30-M" & txtHP.Text
  Code(1) = "32-M" & txtMaxHP.Text
  Code(2) = "38-M" & txtSTR.Text
  Code(3) = "3C-M" & txtDEF.Text
  Code(4) = "40-M" & txtLUCK.Text
  Code(5) = "42-M" & txtSPEED.Text
  Code(6) = "46-M" & txtGOLD.Text

  '持っていないアイテムの個数を増やすのを避けるための処理
  If ItemNumber > 4 Then
    Code(11) = "D6-S" & txtITEM(4).Text
  End If
  If ItemNumber > 3 Then
    Code(10) = "C1-S" & txtITEM(3).Text
  End If
  If ItemNumber > 2 Then
    Code(9) = "AC-S" & txtITEM(2).Text
  End If
  If ItemNumber > 1 Then
    Code(8) = "97-S" & txtITEM(1).Text
  End If
  If ItemNumber > 0 Then
    Code(7) = "82-S" & txtITEM(0).Text
  End If

  Dim ret As Long
  For i = 0 To UBound(Code) - (5 - ItemNumber)
    ret = BinFileEdit(SaveFileName, Code(i))
    If ret <> 0 Then
      MsgBox "エラーです" & vbCrLf & "エラー番号 " & ret
    End If
  Next i

  MsgBox "セーブデータを書き換えました", vbOKOnly, "書き換え完了"
End Sub


データのチェックが正常終了した場合は、データの書き換えを行います。
手順としては、MaferLite用の改造コードと同じものを生成し、実行するという要領です。

例えば、現在HPを書き換える場合、アドレスは30番地、データの大きさは2バイトでしたから、
データを「999」にする場合の改造コードは「30−M999」ですね。

ちなみに、1行目で配列の個数(書き換えるパラメータの数)をわざわざ定数で宣言してあるのは、
直接「12」という数字を書くと、後でプログラムを見直したときに、
何故この数字を設定したのか、すぐに分からないだろうと考えたためです。

後はどのデータも同じように改造コードを作成していくのですが、アイテムの個数については、
持っていない場合を考慮していましたので、その対処をしてあります。

アイテムを2個しか持っていない場合は、5つあるIF文のうち、下の2つだけが実行され、
それ以外の所のIF文にある改造コードは、セットされないようになっていますね?
ただこれは、あまり上手い方法とはいえませんので、何か良い方法があれば差し替えてみて下さい。

改造コードの生成ができたら、実際に書き換えを行います。
この書き換えは第8回の時とほぼ同じです。
違う点があるとすれば、繰り返しの回数を配列の大きさを計算して求めているというぐらいでしょう。


これでセーブデータの書き換えは終了です。
次回は、リアルタイム改造(メモリの書き換え)についての解説をしていきます。



前に戻る     次に進む

講座の初めに戻る