I've been lurking here for a while, so I figured it's time I contributed. I've seen several requests for this around this forum, including from the Prophets, so here goes; my guide to finding and traversing chat messages / objects in PWI.
Load up PWI from fresh (don't just relog, physically start a new client because the last chat index isn't reset by a relog)
Open CE before actually logging your character in and attach it to the process. Set up a scan ready to do a search for a 4 byte number between 1 - 20.
Log your character in and wait for at least 1 chat message to appear in the chat box, or post a dummy message in squad chat or something.
Start the first CE scan - You'll get thousands of results.
Wait for another message to appear in the chatbox or type another one yourself, then scan for an increased value.
Keep repeating the increased value scan every time a new chat appears until you're left with 3 green addresses at the top of your list, plus a bunch of junk below them (pic_1)
[Only registered and activated users can see links. Click Here To Register...]
We want the 2nd one, AE962C in my example, so add it to the address list.
Now do a "Find out what accesses this address" scan and wait for another chat to appear in the window. The scan should find a few hits. Choose any of them and click "More information" (pic_2)
[Only registered and activated users can see links. Click Here To Register...]
So, it's [[AE9620]+C].
Open IDA and load up elementclient (see elsewhere for tutorials on IDA if you're unfamiliar with it or have no idea what I'm talking about here).
Hit Alt+I (or Search -> Immediate value), enter 0xAE9620 and check the "Find all occurences" box.
IDA should find 3 hits (pic_3) - Double click the second one to take you to the code.
[Only registered and activated users can see links. Click Here To Register...]
We're now in the section of code that updates the chatbox (pic_4). You can see our searched value, which is the static chatObjectsBase and just above it, we can see the first value we found in CE (0xAE962C) - This is the static lastChatObject offset.
[Only registered and activated users can see links. Click Here To Register...]
For some reason, searching in IDA for the 0xAE962C value didn't work for me even though we can see it there, plain as day. I'm no IDA expert, so if anyone knows why that is, please let me know ^_^.
This pic also shows the size of each object (0x1C), so we can now figure out how to traverse the list of chat objects.
I'll save you the hassle and show you my findings of the chatObject structure:
I haven't investigated all of the info types fully, mainly just the ones I'm interested in.
So, to traverse the chat objects....
Here's some sample c# code that you can play around with to test this (PWI only - Sorry, tutorial might work for other versions too, in which case you can just change the offsets. I don't really fancy downloading all the other clients, so if anyone wants to try this out, be my guest ;)
Some of the code is borrowed from Interest07's excellent thread on [Only registered and activated users can see links. Click Here To Register...]
So thanks Interest, your thread actually convinced me to get into C# hehe. You might be interested in my little resolveNestedPointer function and maybe the way I did the structure definitions and handling in C#... Although you probably have a better way ;)
And guys.... C# Express is free and plenty enough to work with, plus it has a great debugger. Beats this AutoIt rubbish hands down.
Hope my first thread helped someone :)
[Only registered and activated users can see links. Click Here To Register...]
EDIT: As I get asked a lot for help finding these offsets after updates etc, here's a little tool to find them automagically :P
Full AutoIt script to find chat offsets:
Simple test code in C# (be sure to compile with /unsafe)
Form1.cs
Form1.Designer.cs
And for convenience, here's the whole solution
EDIT: [Only registered and activated users can see links. Click Here To Register...]
Load up PWI from fresh (don't just relog, physically start a new client because the last chat index isn't reset by a relog)
Open CE before actually logging your character in and attach it to the process. Set up a scan ready to do a search for a 4 byte number between 1 - 20.
Log your character in and wait for at least 1 chat message to appear in the chat box, or post a dummy message in squad chat or something.
Start the first CE scan - You'll get thousands of results.
Wait for another message to appear in the chatbox or type another one yourself, then scan for an increased value.
Keep repeating the increased value scan every time a new chat appears until you're left with 3 green addresses at the top of your list, plus a bunch of junk below them (pic_1)
[Only registered and activated users can see links. Click Here To Register...]
We want the 2nd one, AE962C in my example, so add it to the address list.
Now do a "Find out what accesses this address" scan and wait for another chat to appear in the window. The scan should find a few hits. Choose any of them and click "More information" (pic_2)
[Only registered and activated users can see links. Click Here To Register...]
So, it's [[AE9620]+C].
Open IDA and load up elementclient (see elsewhere for tutorials on IDA if you're unfamiliar with it or have no idea what I'm talking about here).
Hit Alt+I (or Search -> Immediate value), enter 0xAE9620 and check the "Find all occurences" box.
IDA should find 3 hits (pic_3) - Double click the second one to take you to the code.
[Only registered and activated users can see links. Click Here To Register...]
We're now in the section of code that updates the chatbox (pic_4). You can see our searched value, which is the static chatObjectsBase and just above it, we can see the first value we found in CE (0xAE962C) - This is the static lastChatObject offset.
[Only registered and activated users can see links. Click Here To Register...]
For some reason, searching in IDA for the 0xAE962C value didn't work for me even though we can see it there, plain as day. I'm no IDA expert, so if anyone knows why that is, please let me know ^_^.
This pic also shows the size of each object (0x1C), so we can now figure out how to traverse the list of chat objects.
I'll save you the hassle and show you my findings of the chatObject structure:
Code:
struct chatObj
{
uint uk1; // 0x00 Unknown
char msgScope; // 0x04 I.e., world, private, faction (see messageTypes)
char smileySet; // 0x05
char uk3; // 0x06
char uk4; // 0x07
wchar *p_msg // 0x08 Pointer to actual unicode message string
uint dwItemId // 0x0C ID of an item linked in chat
uint msgId // 0x10 Unique message ID (can be different from index)
uint uk5; // 0x14
uint uk6; // 0x18
}
Code:
messageTypes (see chatObj->msgScope)
Local // 0
World // 1
Squad // 2
Faction // 3
Whisper // 4
Damage // 5
Combat // 6
Trade // 7
Notification // 8
System // 9
Gen. Info // 0xa
Local info(b) // 0xb
Local info(c) // 0xc
Code:
// 0 - 199 max
For(i = 0 ; i < lastChatObject ; i++)
{
p->chatObject = [[chatObjectsBase] + i*0x1C]
// Actual message
p->message = [[[chatObjectsBase] + i*0x1C]+0x8]
}
Some of the code is borrowed from Interest07's excellent thread on [Only registered and activated users can see links. Click Here To Register...]
So thanks Interest, your thread actually convinced me to get into C# hehe. You might be interested in my little resolveNestedPointer function and maybe the way I did the structure definitions and handling in C#... Although you probably have a better way ;)
And guys.... C# Express is free and plenty enough to work with, plus it has a great debugger. Beats this AutoIt rubbish hands down.
Hope my first thread helped someone :)
[Only registered and activated users can see links. Click Here To Register...]
EDIT: As I get asked a lot for help finding these offsets after updates etc, here's a little tool to find them automagically :P
Full AutoIt script to find chat offsets:
Code:
getChatOffsets()
Func getChatOffsets()
$path = "elementclient.exe"
$file = FileOpen($path, 16)
$data = FileRead($file, FileGetSize($path))
FileClose($file)
$search ='.*?' & _
'8B0D(.{8})' & _ ; /MOV ECX,DWORD PTR DS:[elementclient.0B2DE40]
'8B4C0E.{2}' & _ ; |MOV ECX,DWORD PTR DS:[ECX+ESI+0C]
'85C9' & _ ; |TEST ECX,ECX
'74.{2}' & _ ; |JE SHORT elementclient.004F1586
'8B11' & _ ; |MOV EDX,DWORD PTR DS:[ECX]
'6A.{2}' & _ ; |PUSH 1
'FF12' & _ ; |CALL DWORD PTR DS:[EDX]
'A1(.{8})' ; |MOV EAX,DWORD PTR DS:[elementclient.0B2DE4C]
$matches = StringRegExp($data, $search, 2)
ConsoleWrite('$chatBase = 0x'&rev($matches[1])&@CRLF)
ConsoleWrite('$lastChatIndex = 0x'&rev($matches[2])&@CRLF)
EndFunc
Func rev($string)
Local $all
For $i = StringLen($string) + 1 To 1 Step -2
$all = $all & StringMid($string, $i, 2)
Next
While StringLeft($all, 1) = '0'
$all = StringTrimLeft($all, 1)
WEnd
Return $all
EndFunc
Form1.cs
Form1.Designer.cs
And for convenience, here's the whole solution
EDIT: [Only registered and activated users can see links. Click Here To Register...]