Author | Message | Time |
---|---|---|
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 |