第14回  「ファイル・メモリ・レジストリ読み込み関数」



ファイル等の読み込みってどう使うの?

今までにファイル・メモリ・レジストリの書き込み関数は説明してきましたが、
今度は、読み込み関数についても説明しておきたいと思います。

ところで、ファイルやメモリを読み込む事に何の意義があるのでしょうか?
単にデータを書き換えるだけならば、元の値が何であるかを気にする必要はないので、
読み込みを行う必要はありません。(実際「MaferLite」でも、ここで紹介する関数は使っていません)

例えば、HPを「999」に書き換える場合に、元のHPがいくらであるかは気にしなくて良いという事です。
では逆に、これを気にしなくてはならない場合とはどんな時でしょうか?

第15回で説明する、あるゲームの専用改造ツールを例に挙げてみます。
専用改造ツールの場合は、現時点のデータにおけるHP・MP等の値を読み込んで表示させてやると、
書き換えをする人にとっても、どのデータを書き換えようとしているのか分かりやすいですし、
正しいデータが選択されている事の確認もできますよね。

このように、書き換える前の値を知る必要がある場合に、以降のファイル・メモリ・レジストリの
読み込み関数が役に立つのです。

また、ゲーム改造ツール以外にも、プログラムのパッチを作成する場合にも流用できますので、
これらの関数は、使えるようにしておいて損はないでしょう。


ファイル読み込み関数

まずは、ファイルの読み込み関数から見ていきましょう。


GetFileData ソースプログラム

Function GetFileData(ByVal FileName As String, ByVal Address As String, ByVal Bytes As Long) As String

On Error GoTo ErrSet

Dim i As Long
Dim FileNumber As Long
Dim StartAddress As Long    '書き換えを開始するアドレスを格納する
Dim ReturnData As String    '最終的な戻り値を一時格納する
Dim TempReturnData As String '暫定的な戻り値を一時格納する
Dim TempData() As Byte     '読み込んだデータを一時格納する
ReDim TempData(Bytes - 1)

'もし、引数がセットされていなかったら、即座に終了する
If Len(FileName) = 0 Or Len(Address) = 0 Or Len(Bytes) = 0 Then
  GetFileData = ""
  Exit Function
End If

'ファイルの読み込み
StartAddress = CLng("&h" & Address)
FileNumber = FreeFile
Open FileName For Binary Access Read As #FileNumber
For i = 0 To Bytes - 1
  Get #FileNumber, StartAddress + i + 1, TempData(i)
Next i
Close #FileNumber

'配列に格納したバイナリデータを16進数の文字列に変換し、1バイト(2文字)ずつ繋げていく
ReturnData = ""
For i = 0 To Bytes - 1
  TempReturnData = Hex(TempData(i))
  If Len(TempReturnData) Mod 2 Then
    TempReturnData = "0" & TempReturnData
  End If
  ReturnData = ReturnData & TempReturnData
Next i

GetFileData = ReturnData

Exit Function
ErrSet:
  GetFileData = Err.Number
End Function


変数の宣言や、引数のチェックは解説する必要もないでしょうからパスします。

「ファイルの読み込み」とコメントしている所では、1番目の引数で指定したファイルを
2番目の引数で指定したアドレスから、3番目の引数で指定したバイト数分だけ
1バイトずつ、配列「TempData」に読み込みます。

「配列に格納したバイナリデータを16進数の文字列に変換し、1バイト(2文字)ずつ繋げていく」
とコメントしている所では、そのまんまの処理を行っています。
また、もしも16進数の文字列に変換した結果が奇数桁だった場合は、最上位に「0」を付けています。

これでファイルからのデータの読み込みは完了です。
第10回のファイル書き換え関数も参考にしておいて下さい。


メモリ読み込み関数

では続いて、メモリの読み込み関数です。


GetMemoryData ソースプログラム

Function GetMemoryData(ByVal ProcessName As String, ByVal Address As String, ByVal Bytes As Long) As String

Dim StartAddress As Long  '書き換えを開始するアドレスを格納
Dim ProcessHandle As Long '書き換えるプロセスのハンドルを格納
Dim GetValue As Byte     '読み込んだデータを格納
Dim Count As Long       '読み込んだバイト数を格納
Dim ret As Long         'メモリ読み取り関数の戻り値を格納
Dim rc As Long          'ハンドルのクローズ関数の戻り値を格納
Dim ReturnData As String    '戻り値が確定するまでの値を格納
Dim TempReturnData As String '暫定的な戻り値を一時格納する
Dim i As Long

'もし、引数がセットされていなかったら、即座に終了する
If Len(ProcessName) = 0 Or Len(Address) = 0 Or Len(Bytes) = 0 Then
  GetMemoryData = ""
  Exit Function
End If

'プロセスのハンドルを取得
ProcessHandle = GetProcessHandle(ProcessName)
If ProcessHandle = 0 Then
  GetMemoryData = "Process Open Error !!"
  Exit Function
End If

StartAddress = CLng("&h" & Address)
ReturnData = ""

'メモリから1バイトずつ読み込みを行う
For i = 0 To Bytes - 1
  ret = ReadProcessMemory(ProcessHandle, StartAddress + i, GetValue, 1, Count)
  If ret <> 0 Then
    TempReturnData = Hex(GetValue)
    If Len(TempReturnData) Mod 2 <> 0 Then
      TempReturnData = "0" & TempReturnData
    End If
    ReturnData = ReturnData & TempReturnData
  Else
    rc = CloseHandle(ProcessHandle)
    GetMemoryData = "Process Read Error !!"
    Exit Function
  End If
Next i

rc = CloseHandle(ProcessHandle)
GetMemoryData = ReturnData

End Function


これは、第12回のメモリ書き換え関数に良く似ていますね。
「プロセスのハンドルを取得」とコメントした所はほぼ同じですし、
「メモリから1バイトずつ読み込みを行う」という所も、
書き換え関数の「メモリ書き換え」とコメントした部分に近いものがあります。

ここを少しだけ解説しておきましょう。
「ReadProcessMemory」によって、1バイトずつ読み込みを行っているという点を除けば
ファイル読み込みの時と同じ処理をしています。

要するに「ReadProcessMemory」で読み込んだバイナリデータを16進数の文字列に変換し、
1バイト(2文字)ずつ繋げているのです。

ただ、ファイルの時と違って、1バイトずつ読み込み関数を実行していますので、
毎回エラーチェックが入っている分、長くなっています。
(ファイルの時は、配列にまとめて読み込んでしまっている)


レジストリ読み込み関数

では最後に、レジストリの読み込み関数を見てみましょう。


GetRegistryData ソースプログラム

Function GetRegistryData(ByVal Key As String, ByVal Name As String) As String

'左から順に、オープンされたキーのハンドル、予約内容(0)、値のデータの種類(REG_BINARY,REG_DWORD,REG_SZ)
'、lpData で指定したバッファサイズが入った、DWORD型の変数へのポインタ
Dim phkResult As Long, lpReserved As Long, lpType As Long, lpcbData As Long

Dim lpValueName As String  調べる値の名前
Dim lpData() As Byte     '値が入る配列
Dim rc As Long         '関数の戻り値
Dim RootKey As Long    'キー名の左端の情報("HKEY_LOCAL_MACHINE")
Dim SubKey As String    'サブキー名("Software\MakerName\SoftName\Parameta")
Dim RegData As String    '取り出した値のデータ

'ルートキーの取得
Select Case UCase(Mid(Key, 1, InStr(1, Key, "\") - 1))
Case Is = "HKEY_CLASSES_ROOT"
  RootKey = HKEY_CLASSES_ROOT
Case Is = "HKEY_CURRENT_CONFIG"
  RootKey = HKEY_CURRENT_CONFIG
Case Is = "HKEY_CURRENT_USER"
  RootKey = HKEY_CURRENT_USER
Case Is = "HKEY_DYN_DATA"
  RootKey = HKEY_DYN_DATA
Case Is = "HKEY_PERFORMANCE_DATA"
  RootKey = HKEY_PERFORMANCE_DATA
Case Is = "HKEY_USERS"
  RootKey = HKEY_USERS
Case Is = "HKEY_LOCAL_MACHINE"
  RootKey = HKEY_LOCAL_MACHINE
Case Else
End Select

'サブキーの取得
SubKey = Mid(Key, InStr(1, Key, "\") + 1)

'求める値の設定
lpValueName = Name

'予約済みの値
lpReserved = 0

rc = RegOpenKeyEx(RootKey, SubKey, lpReserved, KEY_QUERY_VALUE, phkResult)
'phkResult に開いたキーのハンドルが入っている
If rc = ERROR_SUCCESS Then
  '値を受け取るバイト配列
  lpcbData = 256
  ReDim lpData(lpcbData) As Byte

  rc = RegQueryValueEx(phkResult, lpValueName, lpReserved, lpType, lpData(0), lpcbData)
  Call RegCloseKey(phkResult)

  Select Case lpType
  Case REG_SZ
    RegData = StrConv(lpData(), vbUnicode)
  Case REG_DWORD
    RegData = "&H" & Hex(lpData(3)) & Hex(lpData(2)) & Hex(lpData(1)) & Hex(lpData(0))
  Case Else
    RegData = "不明"
  End Select
  GetRegistryData = CutNullChar(RegData)
End If
Exit Function

ErrSet:
  GetRegistryData = "エラーです。 エラー番号 " & CStr(Err.Number)
End Function


これは、上半分は第13回とほぼ同じですね。

下の方では、「RegQueryValueEx」関数によってレジストリから値を読み出し、
データのタイプ毎に適切な値へと変換しています。


以上で、各種読み込み関数の説明は終了です。
最後の方が手抜きのような気もしますが、許してやって下さいな。

次回はいよいよ、1つのゲームを専門に改造する「専用ゲーム改造ツール」について解説していきます。
お楽しみに!!



前に戻る     次に進む

講座の初めに戻る