Valhalla Legends Forums Archive | Battle.net Bot Development | [C#] ReadProcessMemory - Getting life

AuthorMessageTime
Insolence
I found an AutoIt script example that correctly gets my character's life, however I can't get any C# examples to work correctly.  I've tried tweaking it many ways.

AutoIt:
[code]Func GetStat ($InputBoxAnswer)
    Local $Buffer = DllStructCreate('dword')
    Local $dll[2] = [DllOpen('kernel32.dll')]
    Local $Open = DllCall($Dll[0], 'int', 'OpenProcess', 'int', 0x1F0FFF, 'int', 0, 'int', $pid)
    $dll[1] = $Open[0]
    DllCall($dll[0], 'int', 'ReadProcessMemory', 'int', $dll[1], 'int', $GlobalPlayerUnit, 'ptr', DllStructGetPtr($Buffer), 'int', 4, 'int', '')
    $Pointer1 = '0x' & hex(DllStructGetData($Buffer, 1) + 92)
MsgBox("","", '0x' & hex($GlobalPlayerUnit) & " " & '0x' & hex(DllStructGetData($Buffer, 1)) & " " & $Pointer1)
    DllCall($dll[0], 'int', 'ReadProcessMemory', 'int', $dll[1], 'int', $Pointer1, 'ptr', DllStructGetPtr($Buffer), 'int', 4, 'int', '')
    $Pointer2 = '0x' & hex(DllStructGetData($Buffer, 1) + 36)
    DllCall($dll[0], 'int', 'ReadProcessMemory', 'int', $dll[1], 'int', $Pointer2, 'ptr', DllStructGetPtr($Buffer), 'int', 4, 'int', '')
    $Pointer3 = '0x' & hex(DllStructGetData($Buffer, 1) + $InputBoxAnswer + 31)
    DllCall($dll[0], 'int', 'ReadProcessMemory', 'int', $dll[1], 'int', $Pointer3, 'ptr', DllStructGetPtr($Buffer), 'int', 4, 'int', '')
    $Value = DllStructGetData($Buffer, 1)
    DllClose($dll[0])
    Return $Value
EndFunc[/code]

C#:
[code]                int PlayerUnit = 0x6FBCC1E0 + 0x5c;
                AppendText("Initial pointer: " + PlayerUnit.ToString("X") + Environment.NewLine);

                int ptr1 = BigEndianBitConverter.Big.ToInt32(game.Memory.Read(PlayerUnit, 4), 0);
                AppendText("Memory read stage 1: " + ptr1.ToString("X") + Environment.NewLine);

                int ptr2 = BigEndianBitConverter.Big.ToInt32(game.Memory.Read(ptr1 + 0x5C, 4), 0);
                AppendText("Memory read stage 2: " + ptr2.ToString("X") + Environment.NewLine);

                int ptr3 = BigEndianBitConverter.Big.ToInt32(game.Memory.Read(ptr2 + 36, 4), 0);
                AppendText("Memory read stage 3: " + ptr3.ToString("X") + Environment.NewLine);

                int ptr4 = BigEndianBitConverter.Big.ToInt32(game.Memory.Read(ptr3 + 31 + 14, 4), 0);
                AppendText("Memory read stage 4: " + ptr4.ToString("X") + Environment.NewLine);

                AppendText("Memory read life: " + ptr4.ToString() + Environment.NewLine);[/code]

C# gets the value 0 at PlayerUnit + 0x5C, where a pointer to the PlayerStat struct should be.  I looked in OllyDbg and this is correct--how is the AutoIt version getting the correct life, while my C# version just gets 0 on the first pointer?

I'm sure C# is reading the memory right--I looked at it in Olly to be sure.

I assume I'm (stupidly) overlooking some fundamental aspect of lower level programming, and I'd appreciate it if someone would point it out.  Thanks :)


PS, if I'm unclear, please let me know and I'll elaborate.
June 30, 2007, 3:21 AM
squeegee
I know a lot of members on this forum that would like to see progress on your project

Keep up the good work
June 30, 2007, 3:38 AM
Insolence
Whoa, really?--thanks, I appreciate it.  It's not my work though, I'm just using the tools E.T./AntiRush/Rhin/Sting/Agin/etc. provide me.
June 30, 2007, 3:44 AM
l2k-Shadow
I don't program in .NET but I don't understand why you are using a big endian class when working on a little endian system?
June 30, 2007, 4:00 AM
Insolence
Oh, that's part of the tweaking and such that I was trying to do--I tried little first.  Either way, it shouldn't be coming up with 0, right?

Thanks for reading the code, I appreciate it. :)
June 30, 2007, 6:35 AM
l2k-Shadow
well can you show us the whole code like with the OpenProcess call and everything?
June 30, 2007, 6:38 AM
Insolence
No problem, here's the whole memory class:
[code]using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ProcessMemoryReaderLib
{
/// <summary>
/// ProcessMemoryReader is a class that enables direct reading a process memory
/// </summary>
class ProcessMemoryReaderApi
{
// constants information can be found in <winnt.h>
[Flags]
public enum ProcessAccessType
{
PROCESS_TERMINATE = (0x0001),
PROCESS_CREATE_THREAD = (0x0002),
PROCESS_SET_SESSIONID = (0x0004),
PROCESS_VM_OPERATION = (0x0008),
PROCESS_VM_READ = (0x0010),
PROCESS_VM_WRITE = (0x0020),
PROCESS_DUP_HANDLE = (0x0040),
PROCESS_CREATE_PROCESS = (0x0080),
PROCESS_SET_QUOTA = (0x0100),
PROCESS_SET_INFORMATION = (0x0200),
PROCESS_QUERY_INFORMATION = (0x0400)
}

// function declarations are found in the MSDN and in <winbase.h>

// HANDLE OpenProcess(
// DWORD dwDesiredAccess,  // access flag
// BOOL bInheritHandle,    // handle inheritance option
// DWORD dwProcessId      // process identifier
// );
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, Int32 bInheritHandle, UInt32 dwProcessId);

// BOOL CloseHandle(
// HANDLE hObject  // handle to object
// );
[DllImport("kernel32.dll")]
public static extern Int32 CloseHandle(IntPtr hObject);

// BOOL ReadProcessMemory(
// HANDLE hProcess,              // handle to the process
// LPCVOID lpBaseAddress,        // base of memory area
// LPVOID lpBuffer,              // data buffer
// SIZE_T nSize,                // number of bytes to read
// SIZE_T * lpNumberOfBytesRead  // number of bytes read
// );
[DllImport("kernel32.dll")]
public static extern Int32 ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,[In, Out] byte[] buffer, UInt32 size, out IntPtr lpNumberOfBytesRead);

// BOOL WriteProcessMemory(
// HANDLE hProcess,                // handle to process
// LPVOID lpBaseAddress,          // base of memory area
// LPCVOID lpBuffer,              // data buffer
// SIZE_T nSize,                  // count of bytes to write
// SIZE_T * lpNumberOfBytesWritten // count of bytes written
// );
[DllImport("kernel32.dll")]
public static extern Int32 WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,[In, Out] byte[] buffer, UInt32 size, out IntPtr lpNumberOfBytesWritten);


}

public class ProcessMemoryReader
{
        private Process m_ReadProcess = null;

        private IntPtr m_hProcess = IntPtr.Zero;

public ProcessMemoryReader(Process proc)
{
            this.m_ReadProcess = proc;

            this.OpenProcess();
}

private void OpenProcess()
{
// m_hProcess = ProcessMemoryReaderApi.OpenProcess(ProcessMemoryReaderApi.PROCESS_VM_READ, 1, (uint)m_ReadProcess.Id);
ProcessMemoryReaderApi.ProcessAccessType access;
access = ProcessMemoryReaderApi.ProcessAccessType.PROCESS_VM_READ
| ProcessMemoryReaderApi.ProcessAccessType.PROCESS_VM_WRITE
| ProcessMemoryReaderApi.ProcessAccessType.PROCESS_VM_OPERATION;
m_hProcess = ProcessMemoryReaderApi.OpenProcess((uint)access, 1, (uint)m_ReadProcess.Id);
}

public void CloseHandle()
{
int iRetValue;
iRetValue = ProcessMemoryReaderApi.CloseHandle(m_hProcess);
if (iRetValue == 0)
throw new Exception("CloseHandle failed");
}

        public byte[] Read(int MemoryAddress, uint bytesToRead)
        {
            int notSavingThis;
            return Read((IntPtr)MemoryAddress, bytesToRead, out notSavingThis);
        }

        public byte[] Read(int MemoryAddress, uint bytesToRead, out int bytesRead)
        {
            return Read((IntPtr)MemoryAddress, bytesToRead, out bytesRead);
        }

public byte[] Read(IntPtr MemoryAddress, uint bytesToRead, out int bytesRead)
{
byte[] buffer = new byte[bytesToRead];

IntPtr ptrBytesRead;
ProcessMemoryReaderApi.ReadProcessMemory(m_hProcess, MemoryAddress, buffer, bytesToRead, out ptrBytesRead);

bytesRead = ptrBytesRead.ToInt32();

return buffer;
}

public void Write(IntPtr MemoryAddress, byte[] bytesToWrite ,out int bytesWritten)
{
IntPtr ptrBytesWritten;
ProcessMemoryReaderApi.WriteProcessMemory(m_hProcess,MemoryAddress,bytesToWrite,(uint)bytesToWrite.Length,out ptrBytesWritten);

bytesWritten = ptrBytesWritten.ToInt32();
}
}
}
[/code]

Any other questions?--thanks for the lightning fast reply :)
June 30, 2007, 6:43 AM
l2k-Shadow
and are you sure OpenProcess is succeeding?
July 1, 2007, 9:43 PM
Insolence
I'm sure it's reading memory correctly, because I look at the bytes in Olly and they're the same.
July 2, 2007, 12:23 AM
dRAgoN
what game you doing this for anyways, i might check into it tomorrow sometime.
July 2, 2007, 4:22 AM
Insolence
Diablo II.
July 2, 2007, 9:59 AM
Smarter
[quote author=Insolence link=topic=16832.msg170563#msg170563 date=1183173711]
I found an AutoIt script example that correctly gets my character's life, however I can't get any C# examples to work correctly.  I've tried tweaking it many ways.

AutoIt:
[code]Func GetStat ($InputBoxAnswer)
    Local $Buffer = DllStructCreate('dword')
    Local $dll[2] = [DllOpen('kernel32.dll')]
    Local $Open = DllCall($Dll[0], 'int', 'OpenProcess', 'int', 0x1F0FFF, 'int', 0, 'int', $pid)
    $dll[1] = $Open[0]
    DllCall($dll[0], 'int', 'ReadProcessMemory', 'int', $dll[1], 'int', $GlobalPlayerUnit, 'ptr', DllStructGetPtr($Buffer), 'int', 4, 'int', '')
    $Pointer1 = '0x' & hex(DllStructGetData($Buffer, 1) + 92)
MsgBox("","", '0x' & hex($GlobalPlayerUnit) & " " & '0x' & hex(DllStructGetData($Buffer, 1)) & " " & $Pointer1)
    DllCall($dll[0], 'int', 'ReadProcessMemory', 'int', $dll[1], 'int', $Pointer1, 'ptr', DllStructGetPtr($Buffer), 'int', 4, 'int', '')
    $Pointer2 = '0x' & hex(DllStructGetData($Buffer, 1) + 36)
    DllCall($dll[0], 'int', 'ReadProcessMemory', 'int', $dll[1], 'int', $Pointer2, 'ptr', DllStructGetPtr($Buffer), 'int', 4, 'int', '')
    $Pointer3 = '0x' & hex(DllStructGetData($Buffer, 1) + $InputBoxAnswer + 31)
    DllCall($dll[0], 'int', 'ReadProcessMemory', 'int', $dll[1], 'int', $Pointer3, 'ptr', DllStructGetPtr($Buffer), 'int', 4, 'int', '')
    $Value = DllStructGetData($Buffer, 1)
    DllClose($dll[0])
    Return $Value
EndFunc[/code]

C#:
[code]                int PlayerUnit = 0x6FBCC1E0 + 0x5c;
                AppendText("Initial pointer: " + PlayerUnit.ToString("X") + Environment.NewLine);

                int ptr1 = BigEndianBitConverter.Big.ToInt32(game.Memory.Read(PlayerUnit, 4), 0);
                AppendText("Memory read stage 1: " + ptr1.ToString("X") + Environment.NewLine);

                int ptr2 = BigEndianBitConverter.Big.ToInt32(game.Memory.Read(ptr1 + 0x5C, 4), 0);
                AppendText("Memory read stage 2: " + ptr2.ToString("X") + Environment.NewLine);

                int ptr3 = BigEndianBitConverter.Big.ToInt32(game.Memory.Read(ptr2 + 36, 4), 0);
                AppendText("Memory read stage 3: " + ptr3.ToString("X") + Environment.NewLine);

                int ptr4 = BigEndianBitConverter.Big.ToInt32(game.Memory.Read(ptr3 + 31 + 14, 4), 0);
                AppendText("Memory read stage 4: " + ptr4.ToString("X") + Environment.NewLine);

                AppendText("Memory read life: " + ptr4.ToString() + Environment.NewLine);[/code]

C# gets the value 0 at PlayerUnit + 0x5C, where a pointer to the PlayerStat struct should be.  I looked in OllyDbg and this is correct--how is the AutoIt version getting the correct life, while my C# version just gets 0 on the first pointer?

I'm sure C# is reading the memory right--I looked at it in Olly to be sure.

I assume I'm (stupidly) overlooking some fundamental aspect of lower level programming, and I'd appreciate it if someone would point it out.  Thanks :)


PS, if I'm unclear, please let me know and I'll elaborate.
[/quote]

Well, I have never gotten into calling outside dlls in C#, I always tried to be sure to use a .NET version of whatever API I wanted to call.... but if I understand correctly, your problem is: PlayerUnit + 0x5c is returning zero, but I don't see where your even doing that, int PlayerUnit = 0x6FBCC1E0 + 0x5c; is all I see. Maybe i'm missing something?
July 5, 2007, 7:17 AM
Insolence
[quote]I don't see where your even doing that, int PlayerUnit = 0x6FBCC1E0 + 0x5c; is all I see. Maybe i'm missing something?[/quote]
[quote]int ptr1 = BigEndianBitConverter.Big.ToInt32(game.Memory.Read(PlayerUnit, 4), 0);[/quote]Bold should be reading that location--I'm pretty sure that's how it should be done.

Thanks for trying :)
July 5, 2007, 10:20 AM
Smarter
Hmmm. So ptr1 simply returns zero? Have you tried stepping through the program, with autos open and seeing exactly what is happening?

Edit: Local $Buffer = DllStructCreate('dword') - Could it be a varible type problem, in the AutoIt scipt, it's creating a DWORD (byte), and your creating an integer. Your also adding two bytes as an integer: nt PlayerUnit = 0x6FBCC1E0 + 0x5c.
July 5, 2007, 10:48 AM
Insolence
What are "autos"?

Like I said, I looked at the memory dump, and 0x6FBCC1E0 + 0x5c is 0x00 00 00 00. 
July 5, 2007, 10:51 AM
Smarter
[quote author=Insolence link=topic=16832.msg170740#msg170740 date=1183632708]
What are "autos"?

Like I said, I looked at the memory dump, and 0x6FBCC1E0 + 0x5c is 0x00 00 00 00. 
[/quote]

Not sure what you mean by "autos", but as I said a second ago, try changing your variable to a byte instead of an integer?
July 5, 2007, 10:57 AM
Insolence
Like I said on AIM, a DWORD isn't a byte, it's 4 bytes. :)
July 5, 2007, 11:20 AM
Smarter
Ah! After hours of Reversing and running around, I found out the RIGHT address:
[code]            int MemA = 0x0012FBC0;
            int MemB = 0x0012FBC8;
            int MemC = 0x0012FBD0;
            int MemD = 0x0012FBD8;
            ProcessMemoryReader pmr = new ProcessMemoryReader(Proc[0]);
            int p1 = BigEndianBitConverter.Big.ToInt32(pmr.Read(MemA, 8), 4);
            int p2 = BigEndianBitConverter.Big.ToInt32(pmr.Read(MemB, 8), 0);
            int p3 = BigEndianBitConverter.Big.ToInt32(pmr.Read(MemC, 8), 3);
            int p4 = BigEndianBitConverter.Big.ToInt32(pmr.Read(MemD, 8), 0);[/code]

Here's the dump of that ;):
[code]0012FBB8  4C 00 69 00 66 00 65 00  L.i.f.e.
0012FBC0  3A 00 20 00 31 00 38 00  :. .1.8.
0012FBC8  30 00 34 00 20 00 2F 00  0.4. ./.
0012FBD0  20 00 31 00 38 00 30 00  .1.8.0.
0012FBD8  34 00 00 00 00 00 00 00  4.......
..
[/code]

As you can see, my characters life is: 1804 / 1804. The life is stored in UNICODE format, and is grabbed by calling:
0012FB98  6F8EF069  RETURN to D2Win.6F8EF069 from D2Lang.?strlen@Unicode@@SIHPBU1@@Z
which is called by:
0012FBA0  6FB65EB8  RETURN to d2client.6FB65EB8 from <JMP.&D2Win.#10128>

:-D However, i'm having trouble converting the Hex to ASCII/Text in C#, it's quite hard :'(. Can you tell I worked hard? .... This required using ollydbg, putting mouse over red ball:
1) Press ALT-E to open the module list of Diablo II (lists all .dll files that Diablo II is using)
2) Find d2lang.dll and select it. Now Press CTRL-N to get a list of imported/exported function names
3) Find the mangled "strlen(UNICODE)" function and set an execution breakpoint (F2)

Then once it breaks you've found the function, select the 2nd one on the right, and right click and click Follow in Dissasembler :-D!!! and you've got the hex on the left! (Now someone just help me convert hex to text in C# and we're set!)
July 5, 2007, 2:07 PM
Insolence
Nice--thanks :D

I see you used Jan's tutorial, that's awesome, however I need to be able to get the actual life value, not a string representation.  I appreciate you putting a ton of work into that, thanks.

I need to be able to interpret structs, here's the UnitAny struct:
[code]struct UNITANY
{
DWORD dwType; //0x00
DWORD dwClassId; //0x04
DWORD _1; //0x08
DWORD dwUnitId; //0x0C
DWORD dwMode; //0x10
union
{
LPPLAYERDATA pPlayerData;
LPITEMDATA pItemData;
LPMONSTERDATA pMonsterData;
LPOBJECTDATA pObjectData;
//LPTILEDATA pTileData doesn't appear to exist anymore
}; //0x14
DWORD dwAct; //0x18
LPACT pAct; //0x1C
DWORD dwSeed[2]; //0x20
DWORD _2; //0x28
union
{
LPPATH pPath;
LPITEMPATH pItemPath;
LPOBJECTPATH pObjectPath;
}; //0x2C
DWORD _3[5]; //0x30
DWORD dwGfxFrame; //0x44
DWORD dwFrameRemain; //0x48
WORD wFrameRate; //0x4C
WORD _4; //0x4E
LPBYTE pGfxUnk; //0x50
LPDWORD pGfxInfo; //0x54
DWORD _5; //0x58
LPSTATLIST pStats; //0x5C
LPINVENTORY pInventory; //0x60
LPLIGHT ptLight; //0x64
DWORD _6[9]; //0x68
WORD wX; //0x8C
WORD wY; //0x8E
DWORD _7; //0x90
DWORD dwOwnerType; //0x94
DWORD dwOwnerId; //0x98
DWORD _8[3]; //0x9C
LPINFO pInfo; //0xA8
DWORD _9[6]; //0xAC
DWORD dwFlags; //0xC4
DWORD dwFlags2; //0xC8
DWORD _10[5]; //0xCC
LPUNITANY pChangedNext; //0xE0
LPUNITANY pRoomNext; //0xE4
LPUNITANY pListNext; //0xE8
};[/code]

If I can find the Player instance of the UnitAny struct, I should be able to do its address + 0x5C and get the address to the LPSTATLIST struct, however that doesn't seem to be working.
July 5, 2007, 9:21 PM
Smarter
It does fucked up things when you add bytes like that, so maybe that's why, maybe you need to be skipping (like in a Packet Buffer), not adding up?
July 5, 2007, 10:07 PM
l2k-Shadow
the struct is stored in memory as a whole. he's adding up the amount of memory each part of the struct takes up.
July 5, 2007, 10:10 PM
Smarter
So then why would it not be working ? .... :'(
July 6, 2007, 8:21 PM
Myndfyr
Have you considered porting the structure itself?
[code]
[StructLayout(LayoutKind.Explicit)]
public struct UnitAny
{
    [FieldOffset(0)]
    uint dwType;
    [FieldOffset(4)]
    uint dwClassId;
    [FieldOffset(8)]
    uint _1;
    [FieldOffset(0x0c)]
    uint dwUnitId;
    [FieldOffset(0x10)]
    uint dwMode;

// first union
    [FieldOffset(0x14)]
    IntPtr pPlayerData;
    [FieldOffset(0x14)]
    IntPtr pItemData;
    [FieldOffset(0x14)]
    IntPtr pMonsterData;
    [FieldOffset(0x14)]
    IntPtr pObjectData

    [FieldOffset(0x18)]
    uint dwAct;
    [FieldOffset(0x1c)]
    IntPtr pAct;
    [FieldOffset(0x20)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=2)]
    uint[] dwSeed;
    [FieldOffset(0x28)]
    uint _2;

// second union
    [FieldOffset(0x2c)]
    IntPtr pPath;
    [FieldOffset(0x2c)]
    IntPtr pItemPath;
    [FieldOffset(0x2c)]
    IntPtr pObjectPath;

    [FieldOffset(0x30)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=5)]
    uint[] _3;
    [FieldOffset(0x44)]
    uint dwGfxFrame;
    [FieldOffset(0x48)]
    uint dwFrameRemain;
    [FieldOffset(0x4c)]
    ushort wFrameRate;
    [FieldOffset(0x4e)]
    ushort _4;
    [FieldOffset(0x50)]
    IntPtr pGfxUnk;
    [FieldOffset(0x54)]
    IntPtr pGfxInfo;
    [FieldOffset(0x58)]
    uint _5;
    [FieldOffset(0x5c)]
    IntPtr pStats;
    [FieldOffset(0x60)]
    IntPtr pInventory;
    [FieldOffset(0x64)]
    IntPtr ptLight;
    [FieldOffset(0x68)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=9)]
    uint[] _6;
    [FieldOffset(0x8c)]
    ushort wX;
    [FieldOffset(0x8e)]
    ushort wY;
    [FieldOffset(0x90)]
    uint _7;
    [FieldOffset(0x94)]
    uint dwOwnerType;
    [FieldOffset(0x98)]
    uint dwOwnerId;
    [FieldOffset(0x9c)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
    uint[] _8;
    [FieldOffset(0xa8)]
    IntPtr pInfo;
    [FieldOffset(0xac)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=6)]
    uint[] _9;
    [FieldOffset(0xc4)]
    uint dwFlags;
    [FieldOffset(0xc8)]
    uint dwFlags2;
    [FieldOffset(0xcc)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=5)]
    uint[] _10;
    [FieldOffset(0xe0)]
    IntPtr pChangedNext;
    [FieldOffset(0xe4)]
    IntPtr pRoomNext;
    [FieldOffset(0xe8)]
    IntPtr pListNext;
}
[/code]

Marshal that structure and then marshal the pointer to your statlist structure too.  Remember that you'll have to read the process's memory for the pStats structure too - simply dereferencing the pointer or using Marshal.PtrToStructure() on it won't work because the pStats pointer points to memory in a different process.
July 6, 2007, 8:50 PM
Insolence
Wow.

I had no idea you could do that--and to go further, you did it for me.  Thank you very much, I'll try that right now.

EDIT:
[code]                byte[] data = game.Memory.Read((int)Chief.DllBase.D2Client + 0x11C1E0, 232);

                IntPtr pnt = Marshal.AllocHGlobal(data.Length);
                Marshal.Copy(data, 0, pnt, data.Length);

                Chief.Structs.UnitAny PlayerUnit = (Chief.Structs.UnitAny)Marshal.PtrToStructure(pnt, typeof(Chief.Structs.UnitAny));[/code]

It seems to be working--however the values I'm getting can't be right (PlayerUnit.dwUnitId, PlayerUnit.wX, etc.).  The PlayerUnit UID is changing whenever I move the character around.
July 6, 2007, 10:11 PM
Myndfyr
Just as an aside, I covered a lot of advanced marshaling topics in this blog post which primarily deals with using the WinVerifyTrust API.  I didn't cover unions (which I have hit here in the .NET forum IIRC), but it should be insightful.
July 7, 2007, 12:52 AM
Insolence
Thanks--I went over it and saw a _lot_ of examples I can use later.
July 7, 2007, 4:26 AM
Smarter
Awww, now I feel out of the loop again ... :'(
July 7, 2007, 1:05 PM
Insolence
The address I had was to a pointer to the PlayerUnit struct, not the base of it.  Thanks to AntiRush for pointing that out.

Works now :)
July 9, 2007, 2:10 AM
TheMinistered
Smart, its more likely that the memory address you found is a string.  The string is built and then passed to a print text function for display.  Its likely the health is actually stored in a different address, some sort of numerical data type, probably a unsigned short.  But, your method works either way I guess ;)
July 9, 2007, 6:39 AM
Insolence
I converted a ton of structs/made a wrapper, here they are:
http://www.edgeofnowhere.cc/viewtopic.php?p=3117432#3117432
July 12, 2007, 11:53 AM
Ringo
Hm, I dont mean to answer somthing thats already been answered, but the life/mana/stamina of your character are stored at:
Life = D2Client + 1035785
mana = D2Client + 1035785 + 16
Stamina = D2Client + 1035785 + 32
Each is stored as 16 bits and location remains the same in a open/closed/single player game.
how ever, I didnt bother to check if it moves when there is more than 1 player in the game. (dont think it will)

Tolk me less than 2min to find this, by reading all of d2client memory, searching it for the value I wanted to find, logging each position it was found at, then did it again but with differnt amounts of life to narrow down the few possible address's.
If the address changes, its just a case of searching the data for the address of the found variable and back track the pointers like that, untill you narrow down the base pointer.

I just thought doing this could have saved you alot of time :)

PS: I have never worked with any memory tools/software before, so no one flame me if there is a simpler way of doing it :P
July 12, 2007, 3:06 PM

Search