I found the coordinates for the marker on the minimap (the little star inside a circle) that shows the destination point and was able to control it's position by hooking the function that normally handles it. However, I didn't have any luck with the dashed line. I'll post my findings and the code for controlling the destination point later (I'm not at home at the moment).
I *think* I've been able to isolate the function that draws the dashed line, i.e., I can disable it so that the line doesn't get drawn, but it's a pretty big function with lots of variables and lots of trigonometry in it lol.
***Edit***
Update
I've not found a 'good' solution for this, but I've spent a good few hours on it and don't really want to probe it any further lol, so I'll post what I've found so far.
Ok, a little primer...
The coordinates of the little marker on the mini map are pretty easy to find. The actual marker (the circle with the star on it) is a GUI dialogue / object / control called mMark6.
If you use my GUI Mapper you can see this is found at:
Code:
baseCall->structures->guiBase0->guiBase1->guiDialogueList->guiDialogue[0xAC]
I.e.,
[[[[[[baseCall] +0x1C] +0x18] +0x8] + 0xC4] +0xAC]
That's the base of the mMark6 object. lets call this mMark6Base.
X and Y coords of this are at mMark6Base + 0x98 and 0x9C respectively.
These values are constantly written to so it's not really practical to simply modify their X,Y coords in memory. I tried to inject the function which writes them, but there seems to be some sort of stack protection on it!?! Either that or I'm mis-interpreting it.
The function I'm talking about is at 0x77C8A0 (Descent).
Code:
0077C8A0 /83EC 20 SUB ESP,20 ; elementclient.0077C8A0(guessed Arg1,Arg2,Arg3,Arg4,Arg5)
0077C8A3 |. |53 PUSH EBP
0077C8A4 |. |55 PUSH EBP
0077C8A5 |. |8BD9 MOV EBX,ECX ** Hook here **
0077C8A7 |. |8B43 34 MOV EAX,DWORD PTR DS:[EBX+34]
0077C8AA |. |8B88 88020000 MOV ECX,DWORD PTR DS:[EAX+288]
0077C8B0 |. |8B90 8C020000 MOV EDX,DWORD PTR DS:[EAX+28C]
0077C8B6 |. |05 88020000 ADD EAX,288
0077C8BB |. |56 PUSH ESI
0077C8BC |. 8B7424 40 MOV ESI,DWORD PTR SS:[ARG.16]
0077C8C0 |. 57 PUSH EDI
0077C8C1 |. 8B78 08 MOV EDI,DWORD PTR DS:[EAX+8]
0077C8C4 |. 8B40 0C MOV EAX,DWORD PTR DS:[EAX+0C]
0077C8C7 |. 33ED XOR EBP,EBP <- Stack protection???
0077C8C9 3BF5 CMP ESI,EBP
So... in the end I just hooked the function at 0x77C8A5, where I then modify the values that were sent as the desired X,Y coords of the mMark6 object.
The detour write looks like this:
Code:
LPVOID makeDetour(LPVOID lpFuncOrig, LPVOID lpFuncDetour)
{
LPVOID lpMallocPtr = NULL;
// Alloc mem for the overwritten bytes
if((lpMallocPtr = (LPVOID)malloc(5)) == NULL)
return NULL;
PBYTE pbFuncOrig = (PBYTE)lpFuncOrig;
BYTE reWrite[5];
for(int i = 0 ; i < 5 ; i++)
{
reWrite[i] = pbFuncOrig[i];
}
BYTE overWrite[5];
overWrite[0] = 0xE8;
*(DWORD*)(overWrite+1) = (DWORD)lpFuncDetour - (DWORD)lpFuncOrig - 5; // CALL PwMapHaxDLL.hSetMiniMapMarker
/* will need to write following instructions back at end of hook:
0077C8A5 8BD9 MOV EBX,ECX
0077C8A7 8B43 34 MOV EAX,DWORD PTR DS:[EBX+34]
*/
// Enable writing to original
DWORD dwProt = NULL;
VirtualProtect(lpFuncOrig, 5, PAGE_READWRITE, &dwProt);
// Write the detour
for(int i = 0; i < 5; i++)
pbFuncOrig[i] = overWrite[i];
// Reset original mem flags
VirtualProtect(lpFuncOrig, 5, dwProt, new DWORD);
return lpFuncOrig;
}
A simple overwrite with a relative call to my hook function.
The hook function then looks like this:
Code:
static DWORD * funcThisTarget;
static DWORD * mMark6Base;
__declspec( naked ) DWORD hSetMiniMapMarker( )
{
_asm
{
PUSHAD
MOV funcThisTarget, ECX
}
mMark6Base = (DWORD*)&baseCall->base->structures->guiBase0->guiBase1->dialogueBase->dialogObj[0xAC/4]->unknown0[0];
if(mMark6Base == funcThisTarget)
{
_asm
{
MOV EAX, 0x375
MOV [ESP+0x50], EAX
MOV EAX, 0x3B
MOV [ESP+0x54], EAX
}
}
_asm
{
POPAD
MOV EBX,ECX
MOV EAX,DWORD PTR DS:[EBX+0x34]
RETN
}
}
Where the structs are defined as: (ignore all the other stuff - I didn't bother removing a lot of unneeded stuff, sorry).
Code:
struct __baseCall
{
public:
__base* base; //0x0000
};
struct __base
{
public:
char uk0[0x1C]; //0x0000
__structures* structures; //0x001C
char uk1[0x78];
__stringConstStructArray* stringConstStructArray; //0x90
char unknown1[0x1D8];
HWND hWnd; //0x0274
};
struct __structures
{
public:
char unknown0[0x18]; //0x0000
__guiBase0* guiBase0; //0x0018
char unknown1[0x18]; //0x001C
__player* player;
};
struct __guiBase0
{
public:
char unknown0[0x8]; //0x0000
__guiBase1* guiBase1; //0x0008
};
struct __guiBase1
{
public:
char unknown0[0xC4]; //0x0000
dialogueBase* dialogueBase; //0x00C4
};
struct dialogueBase
{
public:
dialogueObj* dialogObj[0x6AC];
};
struct dialogueObj
{
public:
char unknown0[0x4C];
char* name; // 0x004C
char unknown1[0x40]; // 0x0050
char show; // 0x0090
char unknown2[0x7]; // 0x0091
DWORD xPos; // 0x0098
DWORD yPos; // 0x009C
};
struct guiControlObject
{
public:
char uk0[0x10];
dialogueObj* parentObj; // 0x0010
char uk1[0x4];
char* pName; // 0x0018
char* pCommand; // 0x001C
char* pSoundFile; // 0x0020
char hasFocus; // 0x0074
char uk3[0xF];
DWORD xPos; // 0x0084
DWORD yPos; // 0x0088
DWORD width; // 0x008C
DWORD height; // 0x0090
char* text; // 0x00B8
char* toolTip; // 0x00BC
item* itemObj; // 0x00F4
char isHovered; // 0x0118
DWORD scrollbarTop; // 0x0124
char scrollMouseDownPos; // 0x0128
char uk5[3];
DWORD selectedIndex; // 0x012C
char uk6[4];
DWORD columnHover; // 0x0134
DWORD rowHover; // 0x0138
char scrollbarMouseDown; // 0x013C
char uk7[3];
DWORD scrollbarIndex; // 0x0140
char maxScrollPos; // 0x0144
char uk4[0xB];
guiListItem* guiListItems; // 0x0150
char uk8[8];
DWORD guiListItemCount; // 0x015C
DWORD imageOffsetInMap; // 0x01FC
imageProperties* imgProp; // 0x0210
};
As you can see, it only modifies the arguments on the stack if the *this pointer is pointing to the mMark6 object (the function we hooked is called in the graphics rendering loop and also handles drawing other stuff).
Ideally, if this was incorporated into a full program, I would have set a flag showing that we want to modify the mMark6 position (e.g., so it only updates when we want to inject the movement function and not when you actually click the minimap ingame). However, I only made this as a proof of concept.
Once it has been identified that the *this pointer is correctly pointing at the mMark6 object, I simply hardcoded some values into the X and Y coords. 0x375 and 0x3B respectively. These will probably differ depending on your window size.
Ideally, you would read the Win_Map dialogue position (the complete container window in the top right corner) and added the Hide_2 position to that. The Hide_2 control is the 7th child control of the Win_Map dialogue and is actually the drawing surface of the map. This is located at:
Code:
[[[[[[[[[[[[[[[baseCall] +0x1C] +0x18] +0x8] +0xC4] +0x5C] +0x1C4] +0xC] +0xC] +0xC] +0xC] +0xC] +0xC] +0xC] +0x8]
The X,Y location of this child object is at the above base + 0x84 and 0x88 respectively. The width and height are at 0x8C and 0x90.
Anyway... with the above hook in place, if you click on the minimap, the marker position will be overridden.
Again, this is not really practical but it's just to demonstrate where the location is written.
This is actually pretty useless - Perhaps you guys can figure out how to hook / inject it better.
As for the dotted line...
If we inspect the map.xml interface file in interfaces.pck, we can see this:
Code:
<IMAGEPICTURE Name="Hide_2" x="309" y="12" Width="128" Height="128">
<Resource>
<FrameImage FileName="Window\对话框_透明色a.bmp"/>
</Resource>
</IMAGEPICTURE>
Yep... it's that Hide_2 thing again (the drawing surface for the map).
However, the frameImage specified, Window\对话框_透明色a.bmp is actually a little 1x1 pixel purple square - I believe this gets redrawn for each dot that constitutes the dotted line. However I had little luck tracking it down.
I did isolate a few possible functions responsible for actually drawing it - I'll go through my notes and try to find them if anyone needs them, however, I think there may be a better solution.
Again, this isn't ideal, but you could possibly simulate a 'click' on the minimap or the main map. This is very simple.
I threw ogether this little AutoShit demo to simulate a minimap click (yes, I know... but I had a similar program already which only needed a little modification lol).
Code:
#include <NomadMemory.au3>
Global $kernel32 = DllOpen('kernel32.dll')
Global $pid = ProcessExists('elementclient.exe')
global $baseCall = 0xA521C0
global $moveViaMiniMapCall = 0x583290
global $moveViaBigMapCall = 0x5D0EB0
$pHandle = _MemoryOpen($pid)
; Mini map container window (Win_Map)
Global $Win_Map_List[6] = [0, 0x1C, 0x18, 0x8, 0xC4, 0x5C]
$Win_Map_Base = _MemoryPointerRead($baseCall, $pHandle, $Win_Map_List)
$Win_Map_Base = $Win_Map_Base[1]
$Win_Map_X = _MemoryRead($Win_Map_Base + 0x98, $pHandle)
$Win_Map_Y = _MemoryRead($Win_Map_Base + 0x9C, $pHandle)
; Mini map image control (Hide_2)
Global $Hide_2_List[15] = [0, 0x1C, 0x18, 0x8, 0xC4, 0x5C, 0x1C4, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x8]
$Hide_2_Base = _MemoryPointerRead($baseCall, $pHandle, $Hide_2_List)
$Hide_2_Base = $Hide_2_Base[1]
$Hide_2_X = _MemoryRead($Hide_2_Base + 0x84, $pHandle)
$Hide_2_Y = _MemoryRead($Hide_2_Base + 0x88, $pHandle)
$Hide_2_Width = _MemoryRead($Hide_2_Base + 0x8C, $pHandle)
$Hide_2_Height = _MemoryRead($Hide_2_Base + 0x90, $pHandle)
; Get absolute screen coords of centre of minimap (accounts for resizing window etc)
$miniMapPlayerX = $Win_Map_X + $Hide_2_X + ($Hide_2_Width / 2)
$miniMapPlayerY = $Win_Map_Y + $Hide_2_Y + ($Hide_2_Height / 2)
; Move 1 unit East and 1 unit South
Global $tarX = $miniMapPlayerX + 1
Global $tarY = $miniMapPlayerY + 1
; coords in YYYYXXXX format
Global $coords = BitOr(BitShift($tarY, -16), $tarX)
;ConsoleWrite(Hex($Win_Map_Base) & ' ' & Hex($Hide_2_Base) & ' ' & Hex($coords) & @CRLF)
_MemoryClose($pHandle)
$funcCall = $moveViaMiniMapCall
moveViaMap($Win_Map_Base, $Hide_2_Base, $coords, $funcCall)
DllClose($kernel32)
Func moveViaMap($outerObj, $innerObj, $coords, $funcAddress)
Local $pRemoteThread, $vBuffer, $loop, $result, $OPcode, $processHandle, $packetAddress
$processHandle = memopen($pid)
$functionAddress = DllCall($kernel32, 'int', 'VirtualAllocEx', 'int', $processHandle, 'ptr', 0, 'int', 0x46, 'int', 0x1000, 'int', 0x40)
;Construct the OpCode for calling the 'SendPacket' function
$OPcode &= '60' ;PUSHAD
$OPcode &= '68'&_hex($innerObj) ;PUSH innerObj
$OPcode &= '68'&_hex($coords) ;PUSH coords
$OPcode &= '6A00' ;PUSH 0
$OPcode &= 'B9'&_hex($outerObj) ;MOV ECX, outerObj
$OPcode &= 'B8'&_hex($funcAddress) ;MOV EAX, moveViaMiniMapCall
$OPcode &= 'FFD0' ;CALL EAX
$OPcode &= '61' ;POPAD
$OPcode &= 'C3' ;RETN
;Put the OpCode into a struct for later memory writing
$vBuffer = DllStructCreate('byte[' & StringLen($OPcode) / 2 & ']')
For $loop = 1 To DllStructGetSize($vBuffer)
DllStructSetData($vBuffer, 1, Dec(StringMid($OPcode, ($loop - 1) * 2 + 1, 2)), $loop)
Next
;Write the OpCode to previously allocated memory
DllCall($kernel32, 'int', 'WriteProcessMemory', 'int', $processHandle, 'int', $functionAddress[0], 'int', DllStructGetPtr($vBuffer), 'int', DllStructGetSize($vBuffer), 'int', 0)
;ConsoleWrite(Hex($functionAddress[0]) & @CRLF)
;Create a remote thread in order to run the OpCode
$hRemoteThread = DllCall($kernel32, 'int', 'CreateRemoteThread', 'int', $processHandle, 'int', 0, 'int', 0, 'int', $functionAddress[0], 'ptr', 0, 'int', 0, 'int', 0)
;Wait for the remote thread to finish
Do
$result = DllCall('kernel32.dll', 'int', 'WaitForSingleObject', 'int', $hRemoteThread[0], 'int', 50)
Until $result[0] <> 258
;Close the handle to the previously created remote thread
DllCall($kernel32, 'int', 'CloseHandle', 'int', $hRemoteThread[0])
;Free the previously allocated memory
DllCall($kernel32, 'ptr', 'VirtualFreeEx', 'hwnd', $processHandle, 'int', $functionAddress[0], 'int', 0, 'int', 0x8000)
;Close the Process
memclose($processHandle)
Return True
EndFunc
Func memopen($pid)
Local $mid = DllCall($kernel32, 'int', 'OpenProcess', 'int', 0x1F0FFF, 'int', 1, 'int', $pid)
Return $mid[0]
EndFunc
Func memclose($mid)
DllCall($kernel32, 'int', 'CloseHandle', 'int', $mid)
EndFunc
Func _hex($Value, $size=8)
Local $tmp1, $tmp2, $i
$tmp1 = StringRight("000000000" & Hex($Value),$size)
For $i = 0 To StringLen($tmp1) / 2 - 1
$tmp2 = $tmp2 & StringMid($tmp1, StringLen($tmp1) - 1 - 2 * $i, 2)
Next
Return $tmp2
EndFunc
Again, it's only for demonstration purposes so is very simplistic.
It calculates the position of the player marker (the arrow) on the mini map, then injects the function which handles moving via the mini map.
For this demonstration it simply autoroutes 1 'unit' East and 1 unit South.
There is a similar function for the large map at 0x5D0EB0 which can be called using the exact same injector, but using different coordinates. I didn't get as far as finding a pointer to the current player position on the big map so I tested it with hardcoded values and it worked.
Oh... one other little snag with the large map function is that it checks for the Alt key being pressed otherwise it won't autoroute - it will simply show the zoomed map at the specified coordinates. For my test, I simple patched the client at 0x5D1099
Code:
005D1099 /0F88 A3000000 JS elementclient.005D1142
Just patch it to a JMP and it will always assume the Alt key is pressed - again only useful for this precise debugging purpose but I'm sure there's a more elegant way to deal with this.
So... Although this itself is potentially pretty useful for navigating around the immediate local area, it's possibly useful in a wider scope...
As the function mentioned a few posts back which injects the autoroute function does not write the map markers / traces, perhaps it would be possible to first simulate a map click which would setup the destination marker and the dashed line, then manually inject the autoroute function to get a more accurate route.
That's about it - Sorry it's not a decent solution but maybe it'll help in some way.
Cheers.