Help with porting C# to Autohotkey

Ask gaming related questions (AHK v1.1 and older)
mebo
Posts: 14
Joined: 22 Apr 2021, 16:08

Help with porting C# to Autohotkey

Post by mebo » 07 May 2021, 20:44

Hello there! I have quite a specific and complex question that I hope someone can volunteer some time to help me with.

I have a program that enables western players to be able to play a Japanese game in English (https://github.com/jmctune/ahkmon). An external program strictly reads the game's memory for dialog within the game and sends the text to your clipboard, which my autohotkey script then picks up, gets translated and sends the translated text to an overlay. This part all works well as of today and is used by several members of this game's community.

What I'm trying to do is take apart the external program that reads the games memory (there is no writing involved whatsoever - it's just reading the Japanese text) and instead, implement it into autohotkey as I can do some cool stuff with it if I can bring it in my script instead.

The creator of this external program is no longer reachable and we're left with a compiled binary. I managed to get the assembly code for the program so I could see how the memory is being read.

I'm attempting to use RHCP's classMemory library to replicate what's being done, but am quite stuck. The program's assembly code that's relevant is written in C# and the relevant bits to this method are here:

Code: Select all

public MemReader()
{
  this.processHandle = IntPtr.Zero;
  this.closestBlock = 16777216;
  HashSet<int> nums = new HashSet<int>();
  nums.Add(16777216);
  nums.Add(805306368);
  nums.Add(536870912);
  nums.Add(0);
  this.closestBlocks = nums;
  this.pageAddress = -1;
  this.pageSize = -1;
  base();
}

public IntPtr getDialogAddress()
{
  if (this.runCount >= 60)
  {
    this.reset();
    this.runCount = 0;
  }
  MemReader.MEMORY_BASIC_INFORMATION mEMORYBASICINFORMATION = new MemReader.MEMORY_BASIC_INFORMATION();
  MemReader.SYSTEM_INFO sYSTEMINFO = new MemReader.SYSTEM_INFO();
  MemReader.GetSystemInfo(out sYSTEMINFO);
  // Declare regionSize variable
  int regionSize = (int)sYSTEMINFO.minimumApplicationAddress;
  // Delcare num variable
  int num = (int)sYSTEMINFO.maximumApplicationAddress;
  regionSize = 16777216;
  num = 1073741824;
  num = 1342177280;
  int num1 = 0;
  while (regionSize < num)
  {
    MemReader.VirtualQueryEx(this.processHandle, (IntPtr)regionSize, out mEMORYBASICINFORMATION, (uint)Marshal.SizeOf(typeof(MemReader.MEMORY_BASIC_INFORMATION)));
    if (mEMORYBASICINFORMATION.Protect == 4 && mEMORYBASICINFORMATION.State == 4096)
    {
      bool flag = false;
      foreach (Tuple<int, int> module in this.modules)
      {
        if (regionSize >= module.Item1 && regionSize <= module.Item2)
        {
          flag = true;
        }
        if (num < module.Item1 || num > module.Item2)
        {
          continue;
        }
        flag = true;
      }
      foreach (int closestBlock in this.closestBlocks)
      {
        if (regionSize + (int)mEMORYBASICINFORMATION.RegionSize >= closestBlock)
        {
          continue;
        }
        flag = true;
      }
      if (regionSize + (int)mEMORYBASICINFORMATION.RegionSize < this.closestBlock)
      {
        flag = true;
      }
      byte[] numArray = new byte[(int)mEMORYBASICINFORMATION.RegionSize];
      if (MemReader.ReadProcessMemory((int)this.processHandle, regionSize, numArray, (int)mEMORYBASICINFORMATION.RegionSize, ref num1))
      {
        int num2 = numArray.Search(new byte[] { 255, 255, 255, 127, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 253, 255, 168, 153 }, 0);
        if (num2 > 0)
        {
          int num3 = regionSize + num2 + 32 + 4;
          byte[] numArray1 = new byte[4];
          if (MemReader.ReadProcessMemory((int)this.processHandle, num3, numArray1, (int)numArray1.Length, ref num1))
          {
            this.closestBlock = (int)Math.Round((double)BitConverter.ToInt32(numArray1, 0) / 268435456, 0) * 268435456;
            this.closestBlocks.Add((int)Math.Round((double)BitConverter.ToInt32(numArray1, 0) / 268435456, 0) * 268435456);
            return (IntPtr)BitConverter.ToInt32(numArray1, 0);
          }
        }
      }
    }
    regionSize += (int)mEMORYBASICINFORMATION.RegionSize;
  }
  // This is just 0 or null
  return IntPtr.Zero;
}
I've managed to port over just a little bit of this logic, but am struggling with the actual BitConverter.ToInt32 portion. I'm not sure how these values are sent over or converted, but feel it's the last piece of the puzzle to giving me an address that I can use to find the dialog in memory. Here's what I have as far as autohotkey code goes:

Code: Select all

#SingleInstance force
#Include <classMemory>

if (_ClassMemory.__Class != "_ClassMemory") {
  msgbox class memory not correctly installed. Or the (global class) variable "_ClassMemory" has been overwritten
}

dqx := new _ClassMemory("ahk_exe DQXGame.exe", "", hProcessCopy)

if !isObject(dqx) 
{
    msgbox failed to open a handle
  if (hProcessCopy = 0)
    msgbox The program isn't running (not found) or you passed an incorrect program identifier parameter. 
  else if (hProcessCopy = "")
    msgbox OpenProcess failed. If the target process has admin rights, then the script also needs to be ran as admin. Consult A_LastError for more information.
}

;; Base address of where DQX is
baseAddress := dqx.getModuleBaseAddress()

aAOBPattern := [255, 255, 255, 127, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 253, 255, 168, 153]  ;; this is the array of bytes that tells us if a dialog box is open or closed
regionSize := 16777216  ;; this is a fixed size of the memory's region

value := dqx.processPatternScan(baseAddress, endAddress := 0x7FFFFFFF, aAOBPattern*)
value2 := regionSize + value + 32 + 4
msgBox % value2
This autohotkey code does return a valid value from the game and I have been able to reproduce that when there's no text displayed, the script returns 0 as expected. I'm not quite sure how to decipher the rest of the script though as C# isn't my forte. I'd sincerely appreciate any help to push me forward with this. Thank you.

mebo
Posts: 14
Joined: 22 Apr 2021, 16:08

Re: Help with porting C# to Autohotkey

Post by mebo » 08 May 2021, 16:10

I was able to figure this out using Visual Studio to add debug lines to figure out what the different values are.

Here's my finished script:

Code: Select all

#SingleInstance force
#Include <classMemory>

if (_ClassMemory.__Class != "_ClassMemory") {
  msgbox class memory not correctly installed. Or the (global class) variable "_ClassMemory" has been overwritten
}

dqx := new _ClassMemory("ahk_exe DQXGame.exe", "", hProcessCopy)

if !isObject(dqx) 
{
    msgbox failed to open a handle
  if (hProcessCopy = 0)
    msgbox The program isn't running (not found) or you passed an incorrect program identifier parameter. 
  else if (hProcessCopy = "")
    msgbox OpenProcess failed. If the target process has admin rights, then the script also needs to be ran as admin. Consult A_LastError for more information.
}

aAOBPattern := [255, 255, 255, 127, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 253, 255, 168, 153]  ;; this is the array of bytes that tells us if a dialog box is open or closed

loop {
  dialogPatternResult := dqx.processPatternScan(,, aAOBPattern*)
  dialogBaseAddress := dialogPatternResult + 32 + 4
  dialogActualAddress := dqx.read(dialogBaseAddress, "UInt")

  if (dialogActualAddress != dialogLastAddress && dialogActualAddress != "")
  {
    value := dqx.readString(dialogActualAddress, sizeBytes := 0, encoding := "utf-8")
    msgBox % value

    dialogLastAddress := dialogActualAddress
  }
}

Post Reply

Return to “Gaming Help (v1)”