PWI - Real Chat Filters - Remove red / system spam etc. AutoIt Code included
You last visited: Today at 17:30
Did you know? elitepvpers has its own image host, epvpimg.com.
PWI - Real Chat Filters - Remove red / system spam etc. AutoIt Code included
This is a discussion on PWI - Real Chat Filters - Remove red / system spam etc. AutoIt Code included within the PW Hacks, Bots, Cheats, Exploits forum part of the Perfect World category; EDIT: Should now work on PWI, PW Russia and Indo
Well, here it is lol... I'm too tired to do ...
PWI / Rus / Indo - Real Chat Filters - Remove red spam etc. AutoIt Code included
EDIT: Should now work on PWI, PW Russia and Indo
Well, here it is lol... I'm too tired to do the writeup right now, but I'll try to update this post tomorrow with ins and outs of how it was all done ^_^
I figure some people will probably want it straight away due to the crazy red spam issue on PWI since the last update, hence I'm posting the code now.
A quick overview:
Can choose which client to attach to
Patches the client so that the filter overrides don't reset your filter settings when you open the game settings console
Can turn any chat type on or off on any chat region tab, i.e., in the image below you can see that world chat and system chat are disabled in the common chat region
Automatically finds all offsets required, so might be update proof :P
Might work on other versions of PW - I'm not sure yet and this will require testing. Please give feedback if it works on your version
I'll explain more tomorrow... All you need to know for now is:
Run your PWI client as normal and log in (must be logged in for the clients to be detected)
Run the PW_ChatFilter.au3 file from AutoIt, or alternatively, compile it first
(I didn't upload it as an exe because it uses some injections and therefore might throw some false positives with some antivirus programs... I can't be arsed to deal with people QQing that it has some virus, hence the code is here for all to see)
When you run the program, it should populate the dropdown list at the top with the character names of all your opened clients
Choose the one you want to fiddle with and hit 'Attach'
Now hit the 'Patch' button. It won't work without this (I'll probably make this automatic when attaching at a later point... can't remember why I did it like this now lol)
After a couple of seconds, it should hopefully tell you that it's successfully attached.
Now, set whatever filters you want on or off and hit the 'Apply' button
Yay! No more red spam!
(By the way, that red text in the screenshot showing the last login IP seems to not come under any of the normal filter categories)
As a side note, I have a feeling (not confirmed) that the last filter option (the third 'other' option) might be a Horn filter hehe.
EDIT:
Ok, finally got some time to explain the process of working this program out
I don't claim to be a great hacker or anything, but I like to explain how I do things and give an insight into my thought process when doing this sort of thing, in the hope that it might teach people something they might not have known before, or to think about things in a slightly different way in their approach to reverse engineering.
I'm sure most people will probably just scroll down and get the files and use them, but for those interested, here goes :P
So, the first thing we need to do is find out what changes when we alter the filters using the in-game settings -> game tab interface, so for this, we fire up cheat engine and attach it to the elementclient.exe process.
Now, there are 6 chat regions (common, squad, faction, etc) and in each region, there are 11 filter options (normal, world, faction, squad, etc). So intuition might tell us that there is an array of 66 values somewhere which store these filter settings. Being that there are so many, intuition might also tell us that they could be stored as bytes for efficiency (well, booleans, i.e., 1 or 0) and that a 1 would mean the filter is enabled and a 0 would mean it's disabled.
So, once CE is attached and we have the game settings tab open, make sure the 'Normal' filter is enabled in the 'Common' chat region and do a memory search in CE for a byte with a value of 1 (see pic 1).
The search will find several million results, although had we started by searching for a 0 there would have been even more... hence we search for non-zero values first.
Now, disable the 'Normal' filter and change the value in the CE search to 0 then hit next scan. Now we just have a few thousand results. Leave the search value as 0 then hit 'next scan' a few more times - The number of results will pretty much be halved.
Now, re-enable the filter and search for 1 again. Rinse and repeat this process until we only have a few results. I managed to get down to 2 results which both clearly changed each time I clicked on the filter checkbox.
Take the lowest address and right click -> browse memory region.
Now we can modify a few different filters in-game and see some bytes changing near the address we found.
So, we know we've found a memory region which reflects the chat filters, however you may notice that they do not appear to be in the same order that they appear in-game.
messageTypes (see chatObj->msgScope)
Local // 0
World // 1
Squad // 2
Faction // 3
Whisper // 4
5 // 5
6 // 6
Trade // 7
Notification // 8
System // 9
Gen. Info // 0xa
Local info(b) // 0xb
Local info(c) // 0xc
This also indicates that there are not 11 filters per region as previously expected, but 13 - The final two are hidden. Merkada has confirmed that the 13th filter value is actually for horns... Not so sure about the 12th.
So, we actually have 13 * 6 = 78 filters and the offsets from here for each chat region are:
Code:
Common -> Normal = 0
Squad -> Normal = 0xD
Faction -> Normal = 0x1A
Whisper -> Normal = 0x27
Trade -> Normal = 0x34
General -> Normal = 0x41
So, now we know we've found something associated with the chat filters, lets try something...
In the 'browse memory region' window, adjust one of the values for which we know which filter it applies to, i.e., the common -> normal filter. Change the value in CE and you will notice it doesn't change in game. However, if you change the value, then click on a different chat region (i.e., squad) then click back on 'Common' you'll see it has changed! If we adjust the next byte after the 'Normal' chat filter (world chat) then switch regions, then switch back to common, we'll see that the world chat filter is unchecked. You can now click apply, and any world chat messages will be removed from the in-game chat window. Yay! Seems we're almost there huh? Well, not quite.
Adjust the world chat filter to 0 (off), switch regions, switch back, click apply... World chat disappears. Awesome... Now click confirm, or close the game settings interface.
Now open the game settings interface again. Bugger... World chat is enabled again. All the previous world chat messages haven't been restored in the chat window though, but any subsequent messages will be displayed.
So, we now know that there must be another storage area for these filters.
There are a couple of ways we could find this... The first way is to do a search for an array of bytes that matches the array we've already found (do this after applying any filters you've adjusted).
Alternatively (and in my opinion, preferrably) we can find out what writes to this address.
For this, I will use OllyDbg simply because the disassembly is more readable and I can copy and paste code from it more easily :P
So, fire up OllyDbg and attach it to the PW client (you will need to detach CE from the process if you have previously attached it). Once Olly has attached, press F9 to unpause the client.
In the "Executable modules" list (Alt+E) double click "ElementClient" twice to take us to the elementclient module.
Now click in the memory dump area then hit Ctrl+G (Goto address) and type in the address of the memory location we found in CE which stores the filter byte for Common->Normal.
Now right click on this address and set a memory breakpoint on write access (see pic_3 - Depending on the version of Olly you have, this interface might look slightly different)
Now open the game settings interface and we should get a hit at:
Then open the game settings interface again, we'll see that EAX contains the value of our baseCall static offset ( &baseCall = 0xAFF144, *baseCall = AFF7E8).
Just for your information, the code above is basically like a block memory copy, which copies our non-static filter values to an area of memory that actually contains the static game settings.
So, from these few lines of code, we can get our complete offset list for the static filters offset, which is:
Code:
[[[baseCall]+0x18]+0x155]
Add this to your CE offset list and set the type to 'Array of bytes' and set the size to 78 bytes (pic 4)
You can also add the address we found earlier for the non-static filters in the same format, i.e., array of 78 bytes so we can compare side by side (pic 5)
But wait a minute... They don't seem to quite correlate. The static ones seem to be offset by 4 bytes, so what are these first 4 bytes? Well if we examine the 4 bytes of memory just before our non-static filters, and then change the 'Basic' settings in the game tab, then hit apply, we'll see that these bytes contain the boolean values for those four checkboxes (Zoom, A,D Keys, Squad Invites, Trade).
We can also find some of our other settings around this area, such as the autoreply status and message, which is somewhere just after our filters.
So to get to our chat filters offset, add 4 to the final offset we had before, i.e.,
Code:
[[[baseCall]+0x18]+0x159]
Now our two arrays of bytes in CE should be aligned (after setting filters and clicking apply).
We've now established where our filters are statically stored, where they are temporarily stored for the gui, and that the normally 'disabled' filters (world chat, whispers, etc) somehow get adjusted back to default when we reopen the game settings tab after successfully disabling them. So, let's find out what evil witchery keeps forcing us to endure that annoying red and yellow spam...
We know that world chat is one of the filters that gets forced back on, so lets set a memory write breakpoint on this in OllyDbg. I'll save us some time here by mentioning that the static filter isn't specifically modified, but the non static one is - The non-static filters are then bulk copied to the static offsets.
So, the address is our non-static filter base + 1. If you've lost the address of your non-static filters, I'll save some more time here and tell you it's at [[[[[baseCall]+0x18]+0x8]+0x4B0]+0x218] so put the breakpoint one byte above this address (for world chat filter) then open the game settings tab. It will break first where it did last time, so when it hits, hit F9 again to run to the next BP... Which hits a few lines down, here:
When you open the game settings tab, what actually happens is something like this:
Static filter settings are copied to the non-static ones (the gui ones)
Some of the non-static ones get forced to different values (i.e., world / system chat)
Non-static filters are copied back to the static ones and the gui is displayed.
So, basically, there is no way we can just modify the filters and have them 'stick' the next time the game settings interface is opened. Bummer, eh? Well this just means we need to do a little patch.
Going back to the code we found a minute ago which modified the world chat filter, below that we can see that a few others get modified too.
Code:
00569186 |. 8895 19020000 MOV BYTE PTR SS:[EBP+219],DL ; Common -> World On
0056918C |. 889D 5A020000 MOV BYTE PTR SS:[EBP+25A],BL ; General -> World off
00569192 |. 889D 62020000 MOV BYTE PTR SS:[EBP+262],BL ; General -> System off (red)
00569198 |. 889D 5D020000 MOV BYTE PTR SS:[EBP+25D],BL ; General -> Whisper off
0056919E |. 889D 65020000 MOV BYTE PTR SS:[EBP+265],BL ; General -> Horn? off
Yeah... the 'General' chat region sounds like heaven. Unfortunately we're not given it as an option in the actual game chat regions lol.
Now, you might notice that those offsets don't cover any of the normal annoying settings, i.e., red system spam in all the other channels. If we scroll up a little bit in OllyDbg, we can see the culprit for this:
This loops through each chat region (hence the ADD EAX, 0D) and sets a few filters. Note that earlier I gave the offset for the non-static filters as [[[[[baseCall]+0x18]+0x8]+0x4B0]+0x218], but a few lines above this loop we can see the line:
Code:
00569167 |. 8D85 1C020000 LEA EAX,[EBP+21C]
So we start at the 5th filter on the Common chat region, which is 'whisper', so this loop above forces some filters on as follows:
The only way we can stop these filters being reset every time we open the game settings interface is to apply a patch. In my program, I do the patch on the already opened client, i.e., in the client's program memory rather than patching the actual .exe file... 'cause that would be naughty :P
In my code, I read the client's memory into a big string, then do a string search for this code:
If you run my code (at the end of this guide) and attach to your client, then hit the patch button, you'll see that any changes you make now do not get changed back when you open the game settings tab. Yay!
So it would seem we're almost done, however, just updating the filter values in memory doesn't apply the filters to the contents of the in game chat box, i.e., say for example you filtered out world chat, the yellow text won't disappear from the current chat box although any subsequent world chat type messages will not be shown. The only way to clear previous messages in the chat box is to hit the apply button in the game filters interface. So, we need to do this automatically with some magic ^_^
I had some trouble finding the specific function in the client which actually propagates the changes to the chat box, so I took a slightly different approach and did a few injections into the client's function that handles all the button clicks within the gui menu system.
Because of the thing I mentioned earlier, whereby the filters are read from static memory into the gui memory, then back to static memory, the solution I came up with was to do the following:
Close the game settings interface if it's already open
Write your preferred filter settings to the static filter array
Call the gui control function to open the game settings interface (this syncs the static filters to the non-static ones, which are used when changes are applied)
Call the gui control function again to simulate hitting the "apply" button
Close the game settings interface
This actually often all happens so quickly that you won't even see the window open and close :P
Finding the gui control function has actually been described before in toxic6666's awesome thread on finding offsets and function addresses - If you're new to fiddling with PW then I highly recommend reading this thread.
However, for continuity, I'll describe here how to actually find this function (termed 'guiCommand()' by toxic6666)
(also updated)
toxic6666's method involved using w32dasm to find a string constant, however, this program is actually pretty difficult to find these days and most downloads you might find for it tend to have infections and/or be from 'questionable' sites. Fortunately, we don't need w32dasm for this as we can do it in OllyDbg.
From within OllyDgb, right click somewhere within the code window, i.e., actually click on some line of code then choose 'Search for' -> 'All referenced strings'
This should pop up a window with a list of all the string constants used in the client. Right click in this window and choose 'Search for text' (or if you're using the newer version of Olly, just hit Ctrl+F)
Search for "Dlg_Building" - there should only be one occurrence of it. When you find it, double click this line and it should take you to the code. At this point, you could open up IDA, enter the address that you just landed at in Olly then find the start of the function. However, the code here isn't too messy so you can just scroll up a few pages until you find a bunch of NOPS. The start of the function is the first line of code after these NOPS. In the current PWI version, this is at 0x604B30 and looks like this:
The 2nd and 4th instructions here will load the gui command and the gui object pointer respectively. So, set a breakpoint at the PUSH EDI line (5th line shown above) then click the apply button in the game settings window.
When the BP hits, we'll see a value in ESI which will be the gui object and a string reference in EBX which will be the command. Fortunately, the commands used for the gui are quite reader friendly lol... The command for the apply button is simply a text string "apply".
If you do this a few times for some other things, i.e., click the game settings tab or click the close (X) button you'll get the command strings for these too. These are "gamesetting" and "Btn_Close" respectively.
Make a note of the value in ESI when you get into this function after hitting the apply button from within the game settings tab.
I won't go into detail about finding the guiBase pointers here as I think toxic6666 explained it all pretty well in his thread. He also explains how to get the offsets for the gui object for this function too, so I'll skip that part and just state that the offset we need is:
Code:
[[[[[baseCall]+0x1C]+0x18]+0x8]+0x4B0]
This is the gamesettings tab object and thankfully, all the commands we need to send are sent to this object.
Basically, this is the pseudo code for what we want to do now...
Assuming the guiCommand() function prototype is something like this:
If gameSettingsTab is open
{
guiCommand("Btn_Close", gamesettingTabObject) // close the tab
Wait until it's closed
}
setChatFilters() // Write our preferred filters to the static filter array
guiCommand("gamesetting", gamesettingTabObject) // Open the tab again
guiCommand("apply", gamesettingTabObject) // Hit apply
guiCommand("Btn_Close", gamesettingTabObject) // close the tab again
As for how to actually inject into this function, just look at the guiCommand() function in my code - I think this explains it
I think I have explained all the relevant parts of the code... The other bits are mostly just fancy trimmings hehe. As for the offset finding stuff, that's kind of beyond the scope of this guide, but I'll be posting a handy tool I made to aid this very time consuming process... maybe tonight.
That's all folks, hope you're not all bored to sleep now!
So, here's the code - You need all three files plus NomadMemory (you should all have this)
PW_ChatFilter.au3 (thanks to t212 for multiclient fix)
Spoiler:
Code:
#include <ButtonConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <EditConstants.au3>
#include <ComboConstants.au3>
#include <StaticConstants.au3>
#include <GUIComboBox.au3>
#include <GDIPlus.au3>
#include <WinAPI.au3>
#include <Color.au3>
#include <NomadMemory.au3>
#include <Array.au3>
#include <filterOffsets.au3>
#Include "MultiAssocArray.au3" ; http://www.autoitscript.com/forum/topic/113182-easy-multidimensional-associative-arrays/
Opt("GUIOnEventMode", 1) ;0=disabled, 1=OnEvent mode enabled
Global $ApW = 300, $ApH = 450
Global Const $iPI = 3.1415926535897932384626433832795
$hGui = GUICreate("PW Real Chat Filter by dumbfck", $ApW, $ApH)
GUISetBkColor(0x192127)
GUISetOnEvent(-3, "_Quit")
$Pic = GUICtrlCreatePic("", 0, 75, $ApW, $ApH)
GUICtrlSetState(-1, $GUI_DISABLE)
$cboCharacter = GUICtrlCreateCombo("", 8, 8, 185, 25, BitOR($CBS_DROPDOWN, $CBS_AUTOHSCROLL))
$btnAttach = GUICtrlCreateButton("Attach", 200, 6, 80, 24)
GUICtrlSetOnEvent($btnAttach, "btnAttach_Click")
$btnApply = GUICtrlCreateButton("Apply", 120, 415, 100, 25)
GUICtrlSetOnEvent($btnApply, "btnApply_Click")
$btnPatch = GUICtrlCreateButton("Patch", 200, 36, 80, 25)
GUICtrlSetOnEvent($btnPatch, "btnPatch_Click")
Global $kernel32 = DllOpen('kernel32.dll')
Global $procName = "elementclient.exe"
Global $processes
Global $pid
Global $pHandle
Global $functionAddress
Global $charNames[1]
; Are assigned in constructOffsets()
Global $baseCall
Global $realBase
Global $playerBase
Global $playerNamePtr
Global $guiBase0
Global $guiBase1
Global $tab_gamesetting
Global $tabVisibleOffset
Global $str_Btn_Close
Global $str_gamesetting
Global $str_apply
Global $guiCommandCall
Global $settingsBase
Global $staticChatFilters
Global $patchSearchExpression = _
"885005" & _ ;00569174 MOV BYTE PTR DS:[EAX+5],DL ; force system chat = enabled
"8810" & _ ;00569177 MOV BYTE PTR DS:[EAX],DL ; force whisper enabled
"885008" & _ ;00569179 MOV BYTE PTR DS:[EAX+8],DL ; force horn enabled? (chat type 0xC)
"83C00D" & _ ;0056917C ADD EAX,0D
"49" & _ ;0056917F DEC ECX
"75F2" & _ ;00569180 JNE SHORT 00569174
"33DB" & _ ;00569182 XOR EBX,EBX
"8BCD" & _ ;00569184 MOV ECX,EBP
"889519020000" & _ ;00569186 MOV BYTE PTR SS:[EBP+219],DL
"889D5A020000" & _ ;0056918C MOV BYTE PTR SS:[EBP+25A],BL
"889D62020000" & _ ;00569192 MOV BYTE PTR SS:[EBP+262],BL
"889D5D020000" & _ ;00569198 MOV BYTE PTR SS:[EBP+25D],BL
"889D65020000" ;0056919E MOV BYTE PTR SS:[EBP+265],BL
Global $alreadyPatchedExpression = _
"9090909090909090" & _ ;8 x NOP
"83C00D" & _ ;ADD EAX,0D
"49" & _ ;DEC ECX
"75F2" & _ ;JNE SHORT 00564D54
"33DB" & _ ;XOR EBX,EBX
"8BCD" & _ ;MOV ECX,EBP
"909090909090909090909090909090909090909090909090909090909090" ; 30 x NOP
If Not StringLen($patchSearchExpression) = StringLen($alreadyPatchedExpression) Then
MsgBox(0, "D'oh!", "Patch and unpatch expression lengths do not match!")
Exit
EndIf
$processes = ProcessList($procName)
If UBound($processes) < 2 Then ; first entry is always empty
MsgBox(16, 'Error', 'No elementclients found')
Exit
Else
; Check first found client, get exe path and check the file to see if we can extract offsets
$pid = $processes[1][1] ; [x][1] = PID of process
$pHandle = _MemoryOpen($pid)
If $pHandle <> 0 Then
$client = GetModuleBaseByName($pid, $procName) ; $client[1]= *modBaseAddr, [2]=modbaseSize, [3]=szExePath
getOffsets($client[3], $search) ; $search is defined in filterOffsets.au3
constructOffsets()
Else
MsgBox(0, "Error", "No elementclient.exe clients were detected. Please run PW first")
EndIf
_MemoryClose($pHandle)
For $i = 1 To $processes[0][0]
$pid = $processes[$i][1] ; PID of process
$pHandle = _MemoryOpen($pid)
constructOffsets()
$charName = _MemoryRead($playerNamePtr, $pHandle, 'wchar[30]')
ReDim $charNames[$i]
$charNames[$i - 1] = $charName
_MemoryClose($pHandle)
Next
$charString = _ArrayToString($charNames, "|")
GUICtrlSetData($cboCharacter, $charString, 0)
ControlCommand("", "", $cboCharacter, "SetCurrentSelection", 0)
EndIf
PicSetGraphics($Pic, $ApW, $ApH); <-- Draw GDIPlus graphics on Picture control.
; Draw the checkbox grid
$startLeft = 93
$startTop = 145
$columns = 6
$rows = 13
Global $chkBox[$columns * $rows]
For $x = 0 To $columns - 1
For $y = 0 To $rows - 1
$id = $x * $rows + $y
$chkBox[$id] = GUICtrlCreateCheckbox("chk" & ($id), $startLeft + $x * 28, $startTop + $y * 20, 20, 20)
Next
Next
GUISetState(@SW_SHOW, $hGui)
; Loop forever
While 1
Sleep(10)
WEnd
; This must be updated whenever new offsets are added in filterOffsets.au3
Func constructOffsets()
; These vars are all globals declared at start of program
$baseCall = x('o.baseCall.offset')
ConsoleWrite('baseCall: ' & Hex($baseCall) & @CRLF)
$realBase = _MemoryRead($baseCall, $pHandle) + x('o.realBase.offset')
ConsoleWrite('realBase: ' & Hex($realBase) & @CRLF)
$playerBase = _MemoryRead($realBase, $pHandle) + x('o.playerBase.offset')
ConsoleWrite('playerBase: ' & Hex($playerBase) & @CRLF)
$playerNamePtr = _MemoryRead(_MemoryRead($playerBase, $pHandle) + x('o.playerName.offset'), $pHandle)
ConsoleWrite('playerNameOffset: ' & Hex($playerNamePtr) & @CRLF)
$guiBase0 = _MemoryRead($realBase, $pHandle) + x('o.guiBase0.offset')
ConsoleWrite('guiBase0: ' & Hex($guiBase0) & @CRLF)
$guiBase1 = _MemoryRead($guiBase0, $pHandle) + x('o.guiBase1.offset')
ConsoleWrite('guiBase1: ' & Hex($guiBase1) & @CRLF)
$tab_gamesetting = _MemoryRead($guiBase1, $pHandle) + x('o.tab_gamesetting.offset')
ConsoleWrite('tab_gamesetting: ' & Hex($tab_gamesetting) & @CRLF)
$tabVisibleOffset = x('o.str_gamesetting.offset')
ConsoleWrite('tabVisibleOffset: ' & Hex($tabVisibleOffset) & @CRLF)
$gamesettingTabVisible = _MemoryRead($tab_gamesetting, $pHandle) + x('o.tabVisible.offset')
ConsoleWrite('gamesettingTabVisible: ' & Hex($gamesettingTabVisible) & @CRLF)
$str_Btn_Close = x('o.str_Btn_Close.offset')
ConsoleWrite('str_Btn_Close: ' & Hex($str_Btn_Close) & @CRLF)
$str_gamesetting = x('o.str_gamesetting.offset')
ConsoleWrite('str_gamesetting: ' & Hex($str_gamesetting) & @CRLF)
$settingsBase = _MemoryRead($baseCall, $pHandle) + x('o.settingsBase.offset')
ConsoleWrite('settingsBase: ' & Hex($settingsBase) & @CRLF)
$staticChatFilters = _MemoryRead($settingsBase, $pHandle) + x('o.staticChatFilters.offset') + 4
ConsoleWrite('staticChatFilters: ' & Hex($staticChatFilters) & @CRLF)
#cs
; This bastard is reaaaally hard to find in the code with a regex search...
; So lets cheat and just write the string to memory somewhere (see memWriteString() function, called during attach process)
$str_apply = x('o.str_apply.offset')
ConsoleWrite('str_apply: ' & Hex($str_apply) & @CRLF)
#ce
$guiCommandCall = x('o.guiCommandCall.offset')
ConsoleWrite('guiCommandCall: ' & Hex($guiCommandCall) & @CRLF)
EndFunc
Func btnAttach_Click()
$selectedIndex = _GUICtrlComboBox_GetCurSel($cboCharacter)
$pid = $processes[$selectedIndex + 1][1] ; PID of process
$pHandle = _MemoryOpen($pid)
constructOffsets()
$charName = _MemoryRead($playerNamePtr, $pHandle, 'wchar[30]')
If Not @error Then
MsgBox(0, "Success", "Attached to " & $charName & @CRLF & "PID: " & Hex($pid))
getChatFilters()
; Dirty hack because the "apply" string constant offset is such a bitch to find with a regex
; This takes the place of where the string offset ideally should have been fetched in the constructConstants() function
$processHandle = memOpen($pid)
$str_apply = DllCall($kernel32, 'int', 'VirtualAllocEx', 'int', $processHandle, 'ptr', 0, 'int', 0x50, 'int', 0x1000, 'int', 0x40)
$str_apply = $str_apply[0]
memClose($processHandle)
; binary string for "apply" ...
_MemoryWrite($str_apply, $pHandle, "0x6170706C7900", "byte[6]")
EndIf
memClose($processHandle)
EndFunc
Func btnPatch_Click()
If GUICtrlRead($btnPatch) = "Patch" Then
patchClient()
Else
unPatchClient()
EndIf
EndFunc
Func btnApply_Click()
$processHandle = memOpen($pid)
;gamesetting tab needs to be closed to allow proper setting of the filters.
If checkTabVisibility($tab_gamesetting, 0x01) Then
guiCommand($str_Btn_Close, $tab_gamesetting) ; "Btn_Close"
While checkTabVisibility($tab_gamesetting, 0x01)
; just wait until closed
WEnd
EndIf
setChatFilters() ; write the new filter settings to memory
guiCommand($str_gamesetting, $tab_gamesetting) ; Open "gamesetting" tab
While checkTabVisibility($tab_gamesetting, 0x00)
; just wait until visible
WEnd
guiCommand($str_apply, $tab_gamesetting) ; "apply"
guiCommand($str_Btn_Close, $tab_gamesetting) ; "Btn_Close"
memClose($processHandle)
EndFunc ;==>btnApply_Click
Func checkTabVisibility($tabObj, $value)
$tabVisibility = _MemoryRead(_MemoryRead($tabObj, $pHandle) + x('o.tabVisible.offset') , $pHandle, 'byte[1]')
If $tabVisibility = $value Then
Return True
Else
Return False
EndIf
EndFunc ;==>checkTabVisibility
Func getChatFilters()
$filters = _MemoryRead($staticChatFilters, $pHandle, "byte[78]")
For $x = 0 To $columns - 1
For $y = 0 To $rows - 1
$id = $x * $rows + $y
If StringMid(Hex($filters), $id * 2 + 1, 2) = "01" Then
GUICtrlSetState($chkBox[$id], $GUI_CHECKED)
Else
GUICtrlSetState($chkBox[$id], $GUI_UNCHECKED)
EndIf
Next
Next
EndFunc ;==>getChatFilters
Func setChatFilters()
Local $stateBytes
For $i = 0 To UBound($chkBox) - 1
If GUICtrlRead($chkBox[$i]) = $GUI_CHECKED Then
$stateBytes &= "01"
Else
$stateBytes &= "00"
EndIf
Next
_MemoryWrite($staticChatFilters, $pHandle, "0x" & $stateBytes, "byte[78]")
EndFunc ;==>setChatFilters
Func patchClient()
Local $patchAddress
If $pHandle <> 0 Then
$client = GetModuleBaseByName($pid, $procName)
$data = _MemoryRead("0x" & Hex($client[1]), $pHandle, 'byte[' & $client[2] & ']')
Else
MsgBox(0, "Error", "No elementsclient.exe clients were detected. Please run PW first")
EndIf
Local $check = StringInStr($data, $patchSearchExpression)
If $check Then
; Patch the client
$patchAddress = $client[1] + $check / 2 - 1
_MemoryWrite($patchAddress, $pHandle, "0x" & $alreadyPatchedExpression, "byte[" & StringLen($alreadyPatchedExpression) / 2 & "]")
MsgBox(0, "Patch status", "Client successfully patched at address: " & Hex($patchAddress))
GUICtrlSetData($btnPatch, "Unpatch")
Else
$check = StringInStr($data, $alreadyPatchedExpression)
If $check Then
$patchAddress = $client[1] + $check / 2 - 1
MsgBox(0, "Patch status", "Client is already patched at address: " & Hex($patchAddress))
GUICtrlSetData($btnPatch, "Unpatch")
Else
MsgBox(0, "Error", "Could not find code pattern to patch" & @CRLF & "...Client update might have broken offsets >.<")
EndIf
EndIf
EndFunc ;==>patchClient
Func unPatchClient()
Local $patchAddress
If $pHandle <> 0 Then
$client = GetModuleBaseByName($pid, $procName)
$data = _MemoryRead("0x" & Hex($client[1]), $pHandle, 'byte[' & $client[2] & ']')
Else
MsgBox(0, "Error", "No elementsclient.exe clients were detected. Please run PW first")
EndIf
Local $check = StringInStr($data, $alreadyPatchedExpression)
If $check Then
; Unpatch the client
$patchAddress = $client[1] + $check / 2 - 1
_MemoryWrite($patchAddress, $pHandle, "0x" & $patchSearchExpression, "byte[" & StringLen($patchSearchExpression) / 2 & "]")
MsgBox(0, "Patch status", "Client successfully unpatched at address: " & Hex($patchAddress))
GUICtrlSetData($btnPatch, "Patch")
Else
$check = StringInStr($data, $patchSearchExpression)
If $check Then
$patchAddress = $client[1] + $check / 2 - 1
MsgBox(0, "Patch status", "Client isn't yet patched at address: " & Hex($patchAddress))
GUICtrlSetData($btnPatch, "Patch")
Else
MsgBox(0, "Error", "Could not find code pattern to patch" & @CRLF & "...Client update might have broken offsets >.<")
EndIf
EndIf
EndFunc ;==>unPatchClient
Func guiCommand($command, $guiObjPtr)
;//Declare local variables
Local $pRemoteThread, $vBuffer, $loop, $result, $OPcode, $processHandle, $stringAddress, $stringSize
$processHandle = memOpen($pid)
;//Allocate memory for the OpCode and retrieve address for this
$functionAddress = DllCall($kernel32, 'int', 'VirtualAllocEx', 'int', $processHandle, 'ptr', 0, 'int', 0x46, 'int', 0x1000, 'int', 0x40)
;//Construct the OpCode for calling the 'guiCommand' function
$OPcode &= '60' ; PUSHAD
$OPcode &= 'A1' & _hex($guiObjPtr) ; MOV EAX, guiObjPtr
;$OPcode &= '8B401C' ; MOV EAX, DWORD PTR DS:[EAX+1C]
;$OPcode &= '8B4018' ; MOV EAX, DWORD PTR DS:[EAX+18]
;$OPcode &= '8B4008' ; MOV EAX, DWORD PTR DS:[EAX+08]
;$OPcode &= '8B80' & _hex($guiObjPtr) ; MOV EAX, DWORD PTR DS:[EAX+XXXXXXXX]
$OPcode &= '50' ; PUSH EAX
$OPcode &= '68' & _hex($command) ; PUSH commandString
$OPcode &= 'B8' & _hex($guiCommandCall) ; MOV EAX, guiCommandCall
$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)
;//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)
memClose($processHandle)
Return True
EndFunc ;==>guiCommand
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 ;==>_hex
Func memopen($pid)
Local $mid = DllCall($kernel32, 'int', 'OpenProcess', 'int', 0x1F0FFF, 'int', 1, 'int', $pid)
Return $mid[0]
EndFunc ;==>memopen
Func memclose($mid)
DllCall($kernel32, 'int', 'CloseHandle', 'int', $mid)
EndFunc ;==>memclose
Func GetModuleBaseByName($pid, $module) ; thx to lolkop for this
;Edited by dumbfck to also return process exe path
Local $hSnapshot, $me32, $bFound, $baseAddress[4]
; Create a MODULEENTRY32 structure, $me32
; http://msdn.microsoft.com/en-us/library/ms684225%28v=VS.85%29.aspx
; typedef struct tagMODULEENTRY32 {
; DWORD dwSize;
; DWORD th32ModuleID;
; DWORD th32ProcessID;
; DWORD GlblcntUsage;
; DWORD ProccntUsage;
; BYTE *modBaseAddr;
; DWORD modBaseSize;
; HMODULE hModule;
; TCHAR szModule[MAX_MODULE_NAME32 + 1];
; TCHAR szExePath[MAX_PATH];
; } MODULEENTRY32, *PMODULEENTRY32;
$me32 = DllStructCreate("int;int;int;int;int;int;int;int;char[256];char[260]")
; Set first element (dwSize) of $me32 structure as the allocated size of the structure itself
DllStructSetData($me32, 1, DllStructGetSize($me32))
; Take a snapshot of the modules used by process $PID (flag=8 specifies modules only)
; If the function succeeds, it returns an open handle to the specified snapshot.
; If the function fails, it returns INVALID_HANDLE_VALUE
; Modules are enumerated by Module32First
$hSnapshot = DllCall("kernel32.dll", "hwnd", "CreateToolhelp32Snapshot", "int", 8, "int", $pid)
; Returned INVALID_HANDLE_VALUE ?
If $hSnapshot[0] = -1 Then Return 0
; Retrieve information about the first module associated with process
; Returns TRUE if the first entry of the module list has been copied to the buffer or FALSE otherwise
; The ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no modules exist
; or the snapshot does not contain module information.
; The calling application must set the dwSize member of MODULEENTRY32 to the size, in bytes, of the structure.
; To retrieve information about other modules associated with the specified process, use the Module32Next function.
$bFound = DllCall("kernel32.dll", "int", "Module32First", "ptr", $hSnapshot[0], "long", DllStructGetPtr($me32))
; Traverse module list....
If $bFound[0] Then
Do
; if module name stored in $me32 structure = filename (elementclient.exe)
If StringLower(DllStructGetData($me32, 9)) = StringLower($module) Then
; *modBaseAddr
$baseAddress[1] = DllStructGetData($me32, 6)
; modbaseSize
$baseAddress[2] = DllStructGetData($me32, 7)
; szExePath
$baseAddress[3] = DllStructGetData($me32, 10)
ExitLoop
EndIf
; Set bFound as next module in module list
$bFound = DllCall("kernel32.dll", "int", "Module32Next", "ptr", $hSnapshot[0], "long", DllStructGetPtr($me32))
; Loop until we get ERROR_NO_MORE_FILES
Until Not $bFound[0]
EndIf
; Destroy the snapshot
DllCall("kernel32.dll", "int", "CloseHandle", "int", $hSnapshot[0])
; Hopefully we have a nice base address array
; [0] = ?
; [1] = Module base address pointer
; [2] = Module base size
; [3] = Module exe path
Return $baseAddress
EndFunc ;==>GetModuleBaseByName
Func PicSetGraphics($cID, $iW, $iH)
Local Const $STM_SETIMAGE = 0x0172
Local Const $IMAGE_BITMAP = 0
Local $hWnd, $hBitmap, $hImage, $hGraphic, $hBrush, $hBrush1, $hbmp, $aBmp
$hWnd = GUICtrlGetHandle($cID)
_GDIPlus_Startup()
$hBitmap = _WinAPI_CreateSolidBitmap($hGui, 0x192127, $iW, $iH) ; or use next command
$hImage = _GDIPlus_BitmapCreateFromHBITMAP($hBitmap)
$hGraphic = _GDIPlus_ImageGetGraphicsContext($hImage)
GDIPlus_SetAngledText($hGraphic, "Common", 116, 31, -60, "", 12, 0xFFA8A9AA)
GDIPlus_SetAngledText($hGraphic, "Squad", 139, 39, -60, "", 12, 0xFFA8A9AA)
GDIPlus_SetAngledText($hGraphic, "Faction", 170, 35, -60, "", 12, 0xFFA8A9AA)
GDIPlus_SetAngledText($hGraphic, "Whisper", 200, 34, -60, "", 12, 0xFFA8A9AA)
GDIPlus_SetAngledText($hGraphic, "Trade", 223, 42, -60, "", 12, 0xFFA8A9AA)
GDIPlus_SetAngledText($hGraphic, "General", 253, 35, -60, "", 12, 0xFFA8A9AA)
GDIPlus_SetAngledText($hGraphic, "Normal", 54, 80, 0, "", 12, 0xFFCED0D2)
GDIPlus_SetAngledText($hGraphic, "World", 59, 100, 0, "", 12, 0xFFCFBB06)
GDIPlus_SetAngledText($hGraphic, "Squad", 55, 120, 0, "", 12, 0xFF03D006)
GDIPlus_SetAngledText($hGraphic, "Faction", 52, 140, 0, "", 12, 0xFF03D0CF)
GDIPlus_SetAngledText($hGraphic, "Whisper", 50, 160, 0, "", 12, 0xFFDF439C)
GDIPlus_SetAngledText($hGraphic, "Damage", 50, 180, 0, "", 12, 0xFFA8A9AA)
GDIPlus_SetAngledText($hGraphic, "Combat", 50, 200, 0, "", 12, 0xFFCFB606)
GDIPlus_SetAngledText($hGraphic, "Trade", 59, 220, 0, "", 12, 0xFFCF6C06)
GDIPlus_SetAngledText($hGraphic, "System", 52, 240, 0, "", 12, 0xFFB2C58B)
GDIPlus_SetAngledText($hGraphic, "System", 52, 260, 0, "", 12, 0xFFEE3402)
GDIPlus_SetAngledText($hGraphic, "Other", 59, 280, 0, "", 12, 0xFF8793E1)
GDIPlus_SetAngledText($hGraphic, "Other", 59, 300, 0, "", 12, 0xFF8793E1)
GDIPlus_SetAngledText($hGraphic, "Other", 59, 320, 0, "", 12, 0xFF8793E1)
; Keeps all GDIPlus graphics visible
$hbmp = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hImage)
$aBmp = DllCall("user32.dll", "hwnd", "SendMessage", "hwnd", $hWnd, "int", $STM_SETIMAGE, "int", $IMAGE_BITMAP, "int", $hbmp)
_WinAPI_RedrawWindow($hGui, "", "", BitOR($RDW_INVALIDATE, $RDW_UPDATENOW, $RDW_FRAME))
; Save Graphics on picture control
If $aBmp[0] <> 0 Then _WinAPI_DeleteObject($aBmp[0])
_GDIPlus_ImageDispose($hImage)
_GDIPlus_BrushDispose($hBrush)
_GDIPlus_GraphicsDispose($hGraphic)
_WinAPI_DeleteObject($hbmp)
_WinAPI_DeleteObject($hBitmap)
_GDIPlus_Shutdown()
EndFunc ;==>PicSetGraphics
Func _Quit()
Exit
EndFunc ;==>_Quit
; #FUNCTION# ================================================================
; Name...........: GDIPlus_SetAngledText
; Description ...: Adds text to a graphic object at any angle.
; Syntax.........: GDIPlus_SetAngledText($hGraphic, $nText, [$iCentreX, [$iCentreY, [$iAngle , [$nFontName , _
; [$nFontSize, [$iARGB, [$iAnchor]]]]]]] )
; Parameters ....: $hGraphic - The Graphics object to receive the added text.
; $nText - Text string to be displayed
; $iCentreX - Horizontal coordinate of horixontal centre of the text rectangle (default = 0 )
; $iCentreY - Vertical coordinate of vertical centre of the text rectangle (default = 0 )
; $iAngle - The angle which the text will be place in degrees. (default = "" or blank = 0 )
; $nFontName - The name of the font to be used (default = "" or Blank = "Arial" )
; $nFontSize - The font size to be used (default = "" or Blank = 12 )
; $iARGB - Alpha(Transparency), Red, Green and Blue color (0xAARRGGBB) (Default= "" = random color
; or Default = Blank = 0xFFFF00FF )
; $iAnchor - If zero (default) positioning $iCentreX, $iCentreY values refer to centre of text string.
; If not zero positioning $iCentreX, $iCentreY values refer to top left corner of text string.
; Return values .: 1
; Author ........: Malkey
; Modified.......:
; Remarks .......: Call _GDIPlus_Startup() before starting this function, and call _GDIPlus_Shutdown()after function ends.
; Can enter calculation for Angle Eg. For incline, -ATan($iVDist / $iHDist) * 180 / $iPI , where
; $iVDist is Vertical Distance, $iHDist is Horizontal Distance, and, $iPI is Pi, (an added Global Const).
; When used with other graphics, call this function last. The MatrixRotate() may affect following graphics.
; Related .......: _GDIPlus_Startup(), _GDIPlus_Shutdown(), _GDIPlus_GraphicsDispose($hGraphic)
; Link ..........;
; Example .......; Yes
; ========================================================================================
Func GDIPlus_SetAngledText($hGraphic, $nText, $iCentreX = 0, $iCentreY = 0, $iAngle = 0, $nFontName = "Arial", _
$nFontSize = 12, $iARGB = 0xFFFF00FF, $iAnchor = 0)
Local $x, $y, $iX, $iY, $iWidth, $iHeight
Local $hMatrix, $iXt, $iYt, $hBrush, $hFormat, $hFamily, $hFont, $tLayout
; Default values
If $iAngle = "" Then $iAngle = 0
If $nFontName = "" Or $nFontName = -1 Then $nFontName = "Arial" ; "Microsoft Sans Serif"
If $nFontSize = "" Then $nFontSize = 12
If $iARGB = "" Then ; Randomize ARGB color
$iARGB = "0xFF" & Hex(Random(0, 255, 1), 2) & Hex(Random(0, 255, 1), 2) & Hex(Random(0, 255, 1), 2)
EndIf
$hFormat = _GDIPlus_StringFormatCreate(0)
$hFamily = _GDIPlus_FontFamilyCreate($nFontName)
$hFont = _GDIPlus_FontCreate($hFamily, $nFontSize, 1, 3)
$tLayout = _GDIPlus_RectFCreate($iCentreX, $iCentreY, 0, 0)
$aInfo = _GDIPlus_GraphicsMeasureString($hGraphic, $nText, $hFont, $tLayout, $hFormat)
$iWidth = Ceiling(DllStructGetData($aInfo[0], "Width"))
$iHeight = Ceiling(DllStructGetData($aInfo[0], "Height"))
;Later calculations based on centre of Text rectangle.
If $iAnchor = 0 Then ; Reference to middle of Text rectangle
$iX = $iCentreX
$iY = $iCentreY
Else ; Referenced centre point moved to top left corner of text string.
$iX = $iCentreX + (($iWidth - Abs($iHeight * Sin($iAngle * $iPI / 180))) / 2)
$iY = $iCentreY + (($iHeight + Abs($iWidth * Sin($iAngle * $iPI / 180))) / 2)
EndIf
;Rotation Matrix
$hMatrix = _GDIPlus_MatrixCreate()
_GDIPlus_MatrixRotate($hMatrix, $iAngle, 1)
_GDIPlus_GraphicsSetTransform($hGraphic, $hMatrix)
;x, y are display coordinates of center of width and height of the rectanglular text box.
;Top left corner coordinates rotate in a circular path with radius = (width of text box)/2.
;Parametric equations for a circle, and adjustments for centre of text box
$x = ($iWidth / 2) * Cos($iAngle * $iPI / 180) - ($iHeight / 2) * Sin($iAngle * $iPI / 180)
$y = ($iWidth / 2) * Sin($iAngle * $iPI / 180) + ($iHeight / 2) * Cos($iAngle * $iPI / 180)
;Rotation of Coordinate Axes formulae - To display at x and y after rotation, we need to enter the
;x an y position values of where they rotated from. This is done by rotating the coordinate axes.
;Use $iXt, $iYt in _GDIPlus_RectFCreate. These x, y values is the position of the rectangular
;text box point before rotation. (before translation of the matrix)
$iXt = ($iX - $x) * Cos($iAngle * $iPI / 180) + ($iY - $y) * Sin($iAngle * $iPI / 180)
$iYt = -($iX - $x) * Sin($iAngle * $iPI / 180) + ($iY - $y) * Cos($iAngle * $iPI / 180)
$hBrush = _GDIPlus_BrushCreateSolid($iARGB)
$tLayout = _GDIPlus_RectFCreate($iXt, $iYt, $iWidth, $iHeight)
_GDIPlus_GraphicsDrawStringEx($hGraphic, $nText, $hFont, $tLayout, $hFormat, $hBrush)
; Clean up resources
_GDIPlus_MatrixDispose($hMatrix)
_GDIPlus_FontDispose($hFont)
_GDIPlus_FontFamilyDispose($hFamily)
_GDIPlus_StringFormatDispose($hFormat)
_GDIPlus_BrushDispose($hBrush)
$tLayout = ""
Return 1
EndFunc ;==>GDIPlus_SetAngledText
Func Color_SetHSL($iHue, $Saturation = 180, $Brightness = 160)
If IsArray($iHue) Then
$aInput = $iHue
Else
Local $aInput[3] = [$iHue, $Saturation, $Brightness]
EndIf
Local $aiRGB = _ColorConvertHSLtoRGB($aInput)
Return "0x" & Hex(Round($aiRGB[0]), 2) & Hex(Round($aiRGB[1]), 2) & Hex(Round($aiRGB[2]), 2)
EndFunc ;==>Color_SetHSL
As a side note, I have a feeling (not confirmed) that the last filter option (the third 'other' option) might be a Horn filter hehe.
yep last "other" is horn, and probably id 11 "2nd other" can be GM talk, but not confirmed, never see GM talk so they write system (id 9) msgs or id 11
here is rip from my source:
yep last "other" is horn, and probably id 11 "2nd other" can be GM talk, but not confirmed, never see GM talk so they write system (id 9) msgs or id 11
Thanks for confirming it And yeah, the other "other" option might well be GM, since it is a hidden option normally and is set to 'on'... Although last time I spoke to a GM they used normal whispers.
Quote:
Originally Posted by Merkada
btw nice to see how u change your opinion about AutoIT :P
I still don't like it, but I like to challenge myself :P
One of the major advantages I've just realised recently is that you can post code in AutoIt here, and most people can compile it, so there is no need to post the compiled .exe which, when using injections, invariably pisses off people's antivirus programs.
Also the whole reading a file to a string thing is quite nice and I am yet to find such a straightforward solution in C#, so offset searching is pretty sweet in AutoIt. (My extremely similar version in C# takes almost 4 times as long :O)
Quote:
Originally Posted by Interest07
Damn, so many regexps, I feel sorry for you
Yep, you might have noticed in the code that there was one offset that just wasn't practical to get from the client, so I kinda botched it a bit >.>.
Aside from that, I love regex's but I got so pissed off with it I actually made a tool which makes it a bit easier... I shall be posting the details on here soon... I think it has potential to be awesome hehe.
Awesome job yet again
Love seeing all the open source stuff Looking forward to the writeup as usual
Thank you kindly, sir
There's been a lot of alcohol and drums on guitar hero tonight, and lots of work to do over the weekend, but hopefully will post the details fairly soon
Last edited by dumbfck; 09-03-2011 at 03:22.
Reason: I say "lol" and "hehe" too much >.<
I still don't like it, but I like to challenge myself :P
One of the major advantages I've just realised recently is that you can post code in AutoIt here, and most people can compile it, so there is no need to post the compiled .exe which, when using injections, invariably pisses off people's antivirus programs.
That's definitely true. It's much easier to copy paste too, since it's all just stuck in one huge file generally (Perhaps with one or two utility files I guess). For smaller tools (codewise I mean) like the ones you're producing it is effective enough.
Quote:
Also the whole reading a file to a string thing is quite nice and I am yet to find such a straightforward solution in C#, so offset searching is pretty sweet in AutoIt. (My extremely similar version in C# takes almost 4 times as long :O)
While I admit regexps aren't the most straightforward in C# (escaping every single byte with \x is a pain and I simply hate using the bloody things), I don't find them to be slow at all. I'd even say time isn't really an issue at all as far as I've noticed (unless you were to regexp the offset every time you wish to use said offset of course ).
Quote:
Yep, you might have noticed in the code that there was one offset that just wasn't practical to get from the client, so I kinda botched it a bit >.>.
Aside from that, I love regex's but I got so pissed off with it I actually made a tool which makes it a bit easier... I shall be posting the details on here soon... I think it has potential to be awesome hehe.
lmao, I'd rather stay away from regexps completely (besides for addresses), they're nasty as shows in your 'hint'
offsets are usually easy enough to change with simple statements like "UPDATE pwi_offsets SET value = value + 0x4 WHERE value > 0xC40" or whatever. Substitute that code with whatever you need for how you store your offsets of course Then again, for real public use you need to bloody offset finders or you get your daily dosage of moaning at every patch
Quote:
Thank you kindly, sir
There's been a lot of alcohol and drums on guitar hero tonight, and lots of work to do over the weekend, but hopefully will post the details fairly soon
I've personally always been bad at GUI related stuff (even programming it myself) so I always find the things you produce impressive
Hope you have a fun weekend without halucinations about regexps
Since you seem to be quite into the whole GUI bit of the game... I was wondering, do you happen to know how to figure out the x, y coords on screen of items in your inventory (and whether or not they are actually visible, in the case of inventories with scroll bar).
I'm working on integrating some little tools into the client via dll injection like this:
and kinda want to be able to drag for example a potion to a slot to indicate i want to use that potion type. Or perhaps something similar with skills and such. I might post the source code for this later if there's any interest.
Ooooh have you coded that in, or just photoshopped that for now? :O
That looks awesome hehe.
I don't know of a way to get the x,y coords based on the actual item as such, but one way you could do it is to read the position of the inventory window relative to the screen
Inventory window object = [[[[[baseCall]+0x1C]+0x18]+0x8]+0x404]
Inventory window X pos = [[[[[[baseCall]+0x1C]+0x18]+0x8]+0x404]+0x98]
Inventory window Y pos = [[[[[[baseCall]+0x1C]+0x18]+0x8]+0x404]+0x9C]
Inv Window Open flag = [[[[[[baseCall]+0x1C]+0x18]+0x8]+0x404]+0x90]
Then you could do a calculation.
Horizontal distance from the left side of the inventory window the left side of the first inventory item = 28 pixels
Vertical distance from top of inventory window to top of first item inventory item = 314 pixels
Inventory icons = 32 x 32 pixels
Horizontal spacing between inventory icons = 1 pixel
Vertical spacing between inventory icons = 2 pixels
So from those values you could calculate the bounds of each element in the inventory something like:
Code:
for i = 0 to maxSlots
{
boxes[i][left] = 28 + (i % 8) * (iconWidth + horizSpacing)
boxes[i][top] = 314 + (int)(i / 8) * (iconHeight + vertSpacing)
}
to get the positions relative to the inventory window, then add the window x,y to get the positions relative to the game window.
As for whether they are visible, I'd suggest finding the offsets that determine whether you are in scrolled or docked mode and if you're in scrolled mode, perhaps try to find where the value of the scrollbar position is stored and do a calculation from that.
I had just started looking for the scrolled / docked / scrollbar offsets but I ran out of lunch break :P
Although I think at one point when changing the scrollbar, I think I did glance upon a changing list of item objects although I didn't get time to investigate.
If you're doing GUI based stuff, here are a few other offsets you will undoubtedly find useful:
Those last two are particularly useful for finding gui object pointers. Just hover over them and read off the values.
The guiHoverInnerObj shows the object pointer for controls within gui windows / objects.
Hover over your inventory items and it looks like it actually returns the item objects? I might be wrong and again I didn't have time to investigate further.
Wow, that certainly looks promising
I have in fact coded the window above in using a D3D8 endscene hook . The dll loads the images in separately though, so i dont use them directly from the game files. Im on my phone right now, but ill definitely have a look at what you posted when i get home, thanks!
Edit; i currently intercept Windows messages to detect whether a user clicked a button but it might be hard to prevent qn item to fall on the ground or staying stuck under the cursor unless i find. The pointer to the object currently. being dragged so i can null it somehow