MHS Scripting Difficulty

Need Help With an Existing Feature in Memory Hacking Software? Ask Here

Moderators: g3nuin3, SpeedWing, WhiteHat, mezzo

MHS Scripting Difficulty

Postby Gumbercules » Sat Jul 14, 2007 9:59 am

Hello,

I'm tremendously impressed with the capabilities of MHS, but I'm having trouble with something I'm trying to accomplish, which may be rather odd. I'll start out with my first step.

In the game in question, all values that I need to adjust are stored in a structure. This structure is always a DWORD of value 2 followed by a 32-bit float with the value I seek. (If I just seek the float itself I'll get >100 results, with the 2, usually 1-2 results. You'll see below why I can't change the value and subsearch.) Using the following script I can easily accomplish this:

Code: Select all
INT CustomSearchIZ3 (LPVOID lpvAddress, LPVOID lpvBuffer, INT iSize)
{
   if ((DWORD)lpvAddress % 0x4 == 0)
   {
      if (iSize >= sizeof(FLOAT))
      {
         DWORD *lpdwTwo = (DWORD *)(LPVOID)((DWORD)lpvBuffer - 4);
         FLOAT *lpfValue = (FLOAT *)(LPVOID)((DWORD)lpvBuffer);

         if (*lpdwTwo == 2)
         {
            if ((*lpfValue >= g_fWanted)  && (*lpfValue < (g_fWanted + 1.000)))
            {
               return sizeof (FLOAT);
            }
         }
      }
   }
   return 0;
}
(Comments and the Setup script which fills the g_fWanted variable omitted for brevity)

This works fine and returns to me the current location of the float to adjust. However, this is not the end result I need. The structure holding this value moves every time it is changed with a "fixed" pointer located elsewhere. It is the location of this pointer that I'm after.

Right now I can just take the result of the above script and then search for a DWORD (or pointer) containing that value in a separate step, but I really want to determine the location of that pointer using one operation. I've tried to write scripts to accomplish this, but they all end up CTDing MHS.

I still don't entirely understand the relationship between lpvAddress and lpvBuffer. I'm guessing that the former is the true address in the program and the latter is a separate address space containing the same data as in lpvAddress but directly accessible from the Script. Thinking this I tried a bunch of different methods including this:
Code: Select all
         DWORD dwOffsetBuffer =   ((DWORD)(lpvBuffer));
         DWORD dwOffsetAddress =   ((DWORD)(lpvAddress));
         long lOffsetDelta =    dwOffsetBuffer - dwOffsetAddress;
         DWORD dwOffsetAddressPointsTo = *((DWORD *)(lpvBuffer));
         if ((dwOffsetAddressPointsTo < 0x00400000) || (dwOffsetAddressPointsTo > 0x7FFFE000))
            return (0);   
         DWORD dwOffsetBufferPointsTo = dwOffsetAddressPointsTo + lOffsetDelta;
         if ((dwOffsetBufferPointsTo < 0x00400000) || (dwOffsetBufferPointsTo > 0x7FFFE000))
            return (0);   
         struct structSought
         {
            DWORD iTwo;
            FLOAT fValue;
         };
         structSought* soughtData = (LPVOID)dwOffsetBufferPointsTo;
But it just crashes MHS. (I realize I'm probably completely misunderstanding how MHS is working here and this code looks foolish, but I've had little choice but to try to trial and error my way into a solution.) I've tried other variations. But the problem is that I need to access the data that the pointer is pointing to, and that usually crashes MHS.

Is there a way to do this?

To be clear, I'm looking for:
The location of a pointer that points to a 8 byte structure with the first 4 bytes being (DWORD)2 and the second 4 bytes being a (FLOAT)X where X is the number I input.

Thank you for any assistance you can provide,
G

Edit: I know I can optimize my code in the first sample more, but I was just experimenting. Once I get a working solution, I'll tweak for performance. Also, sorry for the non-descriptive Subject, I found it hard to sum up this issue in a few words.
Gumbercules
I Have A Few Questions
 
Posts: 5
Joined: Sat Jul 14, 2007 9:39 am

Postby L. Spiro » Sat Jul 14, 2007 11:00 am

lpvAddress is the address in the target process of the value.
lpvBuffer is a copy of that data stored locally for processing, both by MHS and your script.

You can not safely assume that (lpvBuffer - 4) is valid.
You need to change your code to this:

Code: Select all
INT CustomSearchIZ3 (LPVOID lpvAddress, LPVOID lpvBuffer, INT iSize)
{
   if ((DWORD)lpvAddress % 0x4 == 0)
   {
      if (iSize >= sizeof(FLOAT) + sizeof(DWORD))
      {
         DWORD *lpdwTwo = (DWORD *)(LPVOID)((DWORD)lpvBuffer);
         FLOAT *lpfValue = (FLOAT *)(LPVOID)((DWORD)lpvBuffer + 4);

         if (*lpdwTwo == 2)
         {
            if ((*lpfValue >= g_fWanted)  && (*lpfValue < (g_fWanted + 1.000)))
            {
               return sizeof(FLOAT) + sizeof(DWORD);
            }
         }
      }
   }
   return 0;
}

The offset you return is that of the DWORD and not the FLOAT, but this is normal.
(And of course, as you said, it could still be optimized.)



But the problem is that I need to access the data that the pointer is pointing to, and that usually crashes MHS.

Because you are accessing that data from within MHS’s address space.
Change this:
Code: Select all
structSought* soughtData = (LPVOID)dwOffsetBufferPointsTo;

to this:
Code: Select all
extern structSought soughtData = { "", dwOffsetBufferPointsTo };


Then you do the normal processing.

Code: Select all
if ( soughtData.iTwo == 2 && soughtData.fValue >= g_fWanted && soughtData.fValue < g_fWanted + 1.0f ) { return sizeof( LPVOID ); }  // Remember we are returning the pointer to the object and not the object itself.



L. Spiro
User avatar
L. Spiro
L. Spiro
 
Posts: 3129
Joined: Mon Jul 17, 2006 10:14 pm
Location: Tokyo, Japan

Postby Gumbercules » Sat Jul 14, 2007 1:00 pm

Excellent, thank you. I will give this all a try tomorrow. I'm glad you mentioned the "- 4" was unreliable; I suspected it might be. But since it worked on many tests and since it's slightly nicer to get the float and not the DWORD (obviously adding 0x4 manually is pretty trivial so not a big deal) I just left it that way.

I have a follow up question. In ArtMoney, I can take the pointer (the one I'm really looking for) and put that into an entry as a "pointer" (not an "address") offset by +4 and then "cast" that to a float. I would guess that MHS can do that but I haven't figured out how.

IOW, I put an address into an entry in MHS, MHS would then take the address that that pointer points to, add 4 to it, then cast the value at that address to a float.

This is how it is done in ArtMoney (the float cast is hard to see because the dialog draws strangely in a non-English locale, so I drew an arrow to where it goes).
Image
So in this case, 00E18890 points to 00E4A318 to which 4 is added to make 00E4A31C. The value at that address is cast to a float for the wanted value of 82.13.

Thanks again. I'm looking forward to trying your suggestions tomorrow.
-G
Gumbercules
I Have A Few Questions
 
Posts: 5
Joined: Sat Jul 14, 2007 9:39 am

Postby L. Spiro » Sat Jul 14, 2007 9:19 pm

The help file explains how to set addresses for items.
Address Modifications

You want to use a complex address as follows:
[00E4A318]+4


Main Modifications
Set the type to float here.



The complex addresses here can be any valid expression.
Expression Evaluator
Expressions use all standard C operators and precedence.
[ ], b[ ], w[ ], q[ ], f[ ], and d[ ] have been added for easily working with the target process.


L. Spiro
User avatar
L. Spiro
L. Spiro
 
Posts: 3129
Joined: Mon Jul 17, 2006 10:14 pm
Location: Tokyo, Japan

Postby Gumbercules » Sat Jul 14, 2007 11:02 pm

I'm terribly embarrassed. I did read that section of the manual and tried what you said (well I thought so...obviously something must have been different since it didn't work for me. For one I didn't check the obvious "Use Complex Address" checkbox because at the bottom it says complex overrides simple which I read to mean I didn't need to. But when I check it, it all works).

Sorry for the dumb question. :oops: Really, though, there's so little that can go wrong. The "complex" system is really quite elegant and obviously versatile.

And while the expression evaluator is a nice feature, I need to modify the values so I need to do this in the "main" section.

Thanks again,
G

Edit: In my follow up testing, sometimes it would read the exact same spot as 0.0 and also even when it reads it correctly sometimes writing to the float would cause the game to crash (which has never happened with ArtMoney). I'm encountering some other really odd behavior related to this, all usually causing the game to crash and/or unexplainable results to display in MHS. So I'll keep investigating this to see if I'm doing something wrong or if perhaps a bug as at work. I'll post back with my findings.
Gumbercules
I Have A Few Questions
 
Posts: 5
Joined: Sat Jul 14, 2007 9:39 am

Postby Gumbercules » Sun Jul 15, 2007 5:28 am

It looks like the crashes and other mysterious issues were likely due to the fact that pressing OK pushes the value even if you don't want it to. So the game would crash, for example, if I pressed OK when I unchecked the "Use Complex Address" checkbox during testing (and then the 0 was pushed to a spot that was an expected pointer and boom). Once I realized this, I started deleting the value each time I went into the entry's properties and haven't had a problem since. A separate OK button allowing changes to the entry without pushing the value might be a nice addition for some folks.

In case it's of use to anyone, my final script is done and seems to work fine so I thought I'd post it up. It's not well commented. Sorry about that. (Most of the comments that are there are from L. Spiro's typically well commented code, and the few I added when I wrapped things up didn't get saved due to a crash and I didn't bother rewriting them.) See the discussion above to understand what it does. Since it's not going to be generally useful for specific games, and is pretty esoteric, I figured I shouldn't post it to the Script Searches forum and just include it here, although I can move it there if that'd be preferred.
Code: Select all
INT CustomSearchIZ6(LPVOID lpvAddress, LPVOID lpvBuffer, INT iSize)
{
   DWORD dwOffsetAddressPointsTo = *((DWORD *)(lpvBuffer));
   if ((dwOffsetAddressPointsTo >= 0x00400000) && (dwOffsetAddressPointsTo <= 0x7FFFE000))
   {
      extern structSought soughtData = {"", dwOffsetAddressPointsTo};
      if ((soughtData.dwTwo == 2) && (soughtData.fValue >= g_fWantedMin) && (soughtData.fValue < g_fWantedMax))
         return sizeof(LPVOID);
   }
   return 0;
}

// Decode the returned values in the result list.
VOID CustomDecoderIZ6 (LPVOID lpvAddress, LPVOID lpvBuffer, DWORD dwLength, CHAR *pcReturn, INT iMaxLength)
{
   DWORD dwOffsetAddressPointsTo = *((DWORD *)(lpvBuffer));
   extern structSought soughtData = {"", dwOffsetAddressPointsTo};
   SNPrintF (pcReturn, iMaxLength, "%0.4f @ 0x%0.8x+4", soughtData.fValue, dwOffsetAddressPointsTo);
}

// Create a function to wrap all of this together.
VOID CustomSearchSetupIZ6 (INT *piDataSize, INT *piAlign, CHAR **ppcCallback, CHAR **ppcDecoder)
{
   // Size of the data is constant (and this allows us to remove the
   //   size check in CustomSearch() to improve speed).
    (*piDataSize) = sizeof(LPVOID);
   // We can specify an 8-byte alignment here which eliminates the need
   //   for the “if ( (DWORD)lpvAddress % 0x8 == 0 ) {” code in
   //   CustomSearch().
    (*piAlign) = 4;
   
   if (InputFloat("Enter Search Value", "Enter floating point value N.  Results will return X where (N <= X < N+1).", "", &g_fWanted) == TRUE)
   {
      g_fWantedMin =       g_fWanted;
      g_fWantedMax =       g_fWanted + 1.0f;
      (*ppcCallback) =   "CustomSearchIZ6";
      (*ppcDecoder) =      "CustomDecoderIZ6";
   }
   else // Aborts the attempt by reporting an error
   {
      (*ppcCallback) =    "";
      (*ppcDecoder) =    "";
   }
}
I keep the globals and struct definition in a separate script file:
Code: Select all
float g_fWanted = 100.000;
float g_fWantedMin = 100.000;
float g_fWantedMax = 100.000;
struct structSought
{
   DWORD dwTwo;
   FLOAT fValue;
};
Thanks, L. Spiro. This was just what I wanted.

Unfortunately, the game is still giving me troubles to hack. Right now OllyDbg has trouble with it and MHS just crashes when I try to disassemble/debug it, so I'll try MHS again after your next release since I know you are working on the disassembler.

Thanks again. MHS is really something special.
G

Edit: Ninja edits for clarification
Gumbercules
I Have A Few Questions
 
Posts: 5
Joined: Sat Jul 14, 2007 9:39 am

Postby L. Spiro » Mon Jul 16, 2007 2:25 pm

That is just about exacly the way it is supposed to be done.

Using Input*() to get optional extra parameters for a Script Search and using "" to abort the search on invalid input, etc.


Since I have a feeling you will probably be using the scripts more and more often you may feel obliged to point out any missing-but-helpful API functions that could be added.

And although I am working on the Disassembler/Debugger, it will be a while before I release anything as I have really slowed down working on this project in favor of drawing more often.
I do not like drawing so I expect to get back to MHS soon.


L. Spiro
User avatar
L. Spiro
L. Spiro
 
Posts: 3129
Joined: Mon Jul 17, 2006 10:14 pm
Location: Tokyo, Japan

Postby Gumbercules » Tue Jul 17, 2007 1:31 am

L. Spiro wrote:Since I have a feeling you will probably be using the scripts more and more often you may feel obliged to point out any missing-but-helpful API functions that could be added.
I have a couple thoughts, which are related to the above work. Would you prefer I post them to the suggestions forum though?

L. Spiro wrote:And although I am working on the Disassembler/Debugger, it will be a while before I release anything as I have really slowed down working on this project in favor of drawing more often.
I do not like drawing so I expect to get back to MHS soon.
:) Well I'm not in any rush. This particular game (Japanese game from ~2003) isn't even one I play but rather one that has always been insurmountably hard for me to hack, so I use it as a playground for new skills and tools. Someday I hope to figure it out. Admittedly, I do very little game hacking and only recently wrote my first code injection trainer (although I wrote it in such a way that it will be easy to adapt to future projects simply by editing the game-specific DLL, common practice I'm sure).

Thanks very much, once again,
G
Gumbercules
I Have A Few Questions
 
Posts: 5
Joined: Sat Jul 14, 2007 9:39 am

Postby L. Spiro » Tue Jul 17, 2007 2:42 pm

Would you prefer I post them to the suggestions forum though?

Correct.


L. Spiro
User avatar
L. Spiro
L. Spiro
 
Posts: 3129
Joined: Mon Jul 17, 2006 10:14 pm
Location: Tokyo, Japan


Return to Help

Who is online

Users browsing this forum: No registered users and 0 guests