|
You last visited: Today at 10:21
Advertisement
Memory (Client) Based Pet Pickup Filters
Discussion on Memory (Client) Based Pet Pickup Filters within the SRO Coding Corner forum part of the Silkroad Online category.
09/17/2021, 11:33
|
#1
|
elite*gold: 260
Join Date: Aug 2008
Posts: 560
Received Thanks: 3,751
|
Memory (Client) Based Pet Pickup Filters
In this thread, I'm going to talk about memory (client) based pet pickup filters.
WARNING: This is a super technical thread, with no ready to use files/code. This is basically a behind the scenes look at what all would go into making an end-user client based pet pickup filter. In other words, if I were to make such a tool in the future, I'd reference this thread to show how it was done and how it works. I don't have such tool yet though, outside of my dev prototypes.
Traditionally, pet filters have always been done using packets. You proxy the client, disable the game's pet pickup option, parse packets, then manually send pet pickup packets with the items you want. Pretty straightforward, but by no means simple. If you want more details, DaxterSoul has a great writeup about the process .
By approaching this problem another way, using the client's memory, it is possible to bypass the need to do all that packet parsing, and instead just grab what you need to filter items during runtime. However, this approach is more complex in implementation due to three reasons. First, you need to find what you want to filter by in memory. Second, you need to write some ASM to codecave the client so it calls your filter logic and not theirs. Third, you need to create a way to manage the new filter, either externally, or internally (which is now possible thanks to florian0's )
In this thread, I'll talk about the memory stuff and the asm stuff. However, as it turns out, the VSRO pet pickup logic itself is buggy. I've created a video showing one of the exact, replicable problem with why the pet sometimes doesn't loot items despite it being in the filter. The other known problem is when the pet itself gets blocked by an obstacle and ignores an item, despite you being able to loot it.
VSRO Pickup Bug:
The problem seems to be the pet gets put in a "catch up" state when you move away after it goes to loot an item, and any items that drop during this time end up getting ignored, most likely due to the pick operation failing. The items themselves are marked for looting (as I can see the code check them in my filter), but there's a flaw with the actual client logic for handling this scenario.
That means ultimately, you don't want to just modify the client's pet filter logic like this thread will talk about. Instead, it would be better to replace the pet pickup logic entirety (in which case you'll be coding new filter logic anyways), which is doable thanks to the model SRO_DevKit is using. However, that's outside the scope of this thread, as this thread will just cover the pet filtering logic using their flawed pet pickup logic.
While it is possible to use client memory to tell a packet based pick filter what to loot (which might be the most ideal short term solution), that's even more layers of complexity and requires a bit too much for a thread like this. It is possible though, so if you want to get creative with it, you certainly can.
One last section of notes/disclaimers/warnings. When making threads like this, to talk about some specific topic, the implementation/process used to educate people on various things rarely matches the implementation/process required for a stable/reliable/performant/etc... project. In other words, the purpose of these threads is to show it's possible, one way that is confirmed to be working, but it will never be the best way, or even the right/ideal way, because when it comes to complicated things like this, complexity and "easier to understand" things are mutually exclusive. It's either one or the other, and my idea of "easier to understand" is probably already overly complex anyways.
So please keep that in mind. I focus on identifying/solving problems first using any means that works, and then work on finding better ways to create a usable solution.
With all that said, let's get started! This is for VSRO 188, but you could adapt it to any version conceptually.
First, let's talk about finding pet pickup/filter logic. The pet pickup opcode is 0x70C5. That's been documented over the years, but you could find it with a packet analyzer pretty easily. Knowing that, you'd search the client for all instances of that opcode, and see which code section(s) get called when the pet goes to loot an item. Since you can drop items and have the pet loot them, all you need to do is find a quiet place in town to test.
The main pet pickup function can be found at 0x8AFB50. In this function, the 0x70C5 packet building gets executed when the pet picks up an item, so it was pretty straight forward finding the starting point. Using RTTI information and checking the code that calls this function, you will discover we're executing a function using the context of a CDropItemManager instance.
Just to shout out a few notable lines of code with some comments:
Code:
008AFB7B | 8B0D ECF5EE00 | mov ecx, dword ptr [<g_pCICPlayer>] | [GLOBAL] CICPlayer : CICUser : CICharactor : CIGIDObject : CIObject : CIEntity : CObjChild : CObj : CAnimationCallback : SICharInfo
008AFB81 | E8 EA501200 | call <GetPlayerCCosDataMgr> |
...
008AFC08 | F686 A8630100 80 | test byte ptr [esi+0x163A8], 0x80 | is pet enabled?
008AFC0F | 0F84 D9010000 | je <NextCosLabel> |
...
008AFC46 | A1 ECF5EE00 | mov eax, dword ptr [<g_pCICPlayer>] | [GLOBAL] CICPlayer : CICUser : CICharactor : CIGIDObject : CIObject : CIEntity : CObjChild : CObj : CAnimationCallback : SICharInfo
008AFC4B | 8A88 5C060000 | mov cl, byte ptr [eax+0x65C] |
...
008AFC91 | 68 F0F6EE00 | push <sro_client.GFXRuntimeClass_CICCos> | EEF6F0:&"CICCos"
008AFC96 | 8BCF | mov ecx, edi |
008AFC98 | E8 D34E2E00 | call <GFXRC_IsSame> |
008AFC9D | 84C0 | test al, al |
008AFC9F | 75 1C | jne 0x8AFCBD |
008AFCA1 | 68 44F0DD00 | push sro_client.DDF044 | DDF044:"pObj->IsSame( GFX_RUNTIME_CLASS(CICCos) )"
008AFCA6 | 68 00F0DD00 | push sro_client.DDF000 | DDF000:"D:\\vss-od\\Silkroad\\Client\\client\\DropItemManager.cpp"
008AFCAB | 68 FA000000 | push 0xFA |
008AFCB0 | E8 DBE7BEFF | call 0x49E490 |
008AFCB5 | 83C4 0C | add esp, 0xC |
008AFCB8 | 84C0 | test al, al |
008AFCBA | 75 01 | jne 0x8AFCBD |
...
008AFCF5 | 8B4C24 2C | mov ecx, dword ptr [esp+0x2C] | CDropItemManager
008AFCF9 | E8 F2FAFFFF | call 0x8AF7F0 |
...
008AFD06 | 8B4424 18 | mov eax, dword ptr [esp+0x18] |
008AFD0A | 50 | push eax |
008AFD0B | E8 10351100 | call <IGIDObject_GetById2> |
008AFD10 | 53 | push ebx |
008AFD11 | 68 C5700000 | push 0x70C5 | pet pick
008AFD16 | E8 651AF9FF | call <CanUseOpcode> |
008AFD1B | 83C4 0C | add esp, 0xC |
008AFD1E | 85C0 | test eax, eax |
008AFD20 | 0F84 8F000000 | je 0x8AFDB5 |
008AFD26 | 68 C5700000 | push 0x70C5 |
008AFD2B | 8D4C24 38 | lea ecx, dword ptr [esp+0x38] |
008AFD2F | E8 6CD1C8FF | call <SetOpcode> |
We can understand the big picture logic as follows: The client iterates through the character's cos, looking for a pickup pet, and then checks to make sure your pet filter is enabled. When it finds the pet, it'll call into another class function to get an item to loot. If it has an item to loot, it'll then build the pet pickup packet and send it. I'm skipping over a few details, but that should be enough to get the gist of things.
What we're interested in, is the function call that filters items and reports back the item that gets used in the packet building code. That function (0x8AF7F0) can be found from the call referenced at address 0x8AFCF9. I lost the comments I had for the function during a multiple client editing mishap, but it's ok because the function itself is pretty small:
Code:
008AF7F0 | 83EC 14 | sub esp, 0x14 |
008AF7F3 | 8B41 08 | mov eax, dword ptr [ecx+0x8] |
008AF7F6 | D905 78B8E100 | fld st(0), dword ptr [0xE1B878] |
008AF7FC | 53 | push ebx |
008AF7FD | D95C24 04 | fstp dword ptr [esp+0x4], st(0) |
008AF801 | 55 | push ebp |
008AF802 | 8D59 04 | lea ebx, dword ptr [ecx+0x4] |
008AF805 | 56 | push esi |
008AF806 | 57 | push edi |
008AF807 | 8B38 | mov edi, dword ptr [eax] |
008AF809 | 8BD3 | mov edx, ebx |
008AF80B | C74424 14 00000000 | mov dword ptr [esp+0x14], 0x0 |
008AF813 | 897C24 20 | mov dword ptr [esp+0x20], edi |
008AF817 | 895424 1C | mov dword ptr [esp+0x1C], edx |
008AF81B | 85D2 | test edx, edx |
008AF81D | 8BC3 | mov eax, ebx |
008AF81F | 8B48 04 | mov ecx, dword ptr [eax+0x4] |
008AF822 | 0F84 2E010000 | je 0x8AF956 |
008AF828 | 3BD0 | cmp edx, eax |
008AF82A | 0F85 26010000 | jne 0x8AF956 |
008AF830 | 3BF9 | cmp edi, ecx |
008AF832 | 0F84 10010000 | je 0x8AF948 |
008AF838 | 3B7A 04 | cmp edi, dword ptr [edx+0x4] |
008AF83B | 0F84 02010000 | je 0x8AF943 |
008AF841 | 8B6F 10 | mov ebp, dword ptr [edi+0x10] |
008AF844 | 8B45 04 | mov eax, dword ptr [ebp+0x4] |
008AF847 | A8 06 | test al, 0x6 |
008AF849 | 0F85 AE000000 | jne 0x8AF8FD |
008AF84F | 8A4C24 28 | mov cl, byte ptr [esp+0x28] |
008AF853 | F6C1 40 | test cl, 0x40 |
008AF856 | 75 08 | jne 0x8AF860 |
008AF858 | A8 01 | test al, 0x1 |
008AF85A | 0F84 9D000000 | je 0x8AF8FD |
008AF860 | F6C1 01 | test cl, 0x1 |
008AF863 | 74 04 | je 0x8AF869 |
008AF865 | A8 08 | test al, 0x8 |
008AF867 | 75 16 | jne 0x8AF87F |
008AF869 | F6C1 02 | test cl, 0x2 |
008AF86C | 74 04 | je 0x8AF872 |
008AF86E | A8 10 | test al, 0x10 |
008AF870 | 75 0D | jne 0x8AF87F |
008AF872 | F6C1 04 | test cl, 0x4 |
008AF875 | 0F84 82000000 | je 0x8AF8FD |
008AF87B | A8 18 | test al, 0x18 |
008AF87D | 75 7E | jne 0x8AF8FD |
008AF87F | 8B47 0C | mov eax, dword ptr [edi+0xC] |
008AF882 | 50 | push eax |
008AF883 | E8 98391100 | call <IGIDObject_GetById2> |
008AF888 | 8BF0 | mov esi, eax |
008AF88A | 83C4 04 | add esp, 0x4 |
008AF88D | 85F6 | test esi, esi |
008AF88F | 74 19 | je 0x8AF8AA |
008AF891 | 68 281CEF00 | push <sro_client.GFXRuntimeClass_CIItem> | EF1C28:&"CIItem"
008AF896 | 8BCE | mov ecx, esi |
008AF898 | E8 F3522E00 | call <sub_B94B90> |
008AF89D | 84C0 | test al, al |
008AF89F | 74 09 | je 0x8AF8AA |
008AF8A1 | 83BE 8C020000 00 | cmp dword ptr [esi+0x28C], 0x0 |
008AF8A8 | 75 53 | jne 0x8AF8FD |
008AF8AA | 8B5424 30 | mov edx, dword ptr [esp+0x30] |
008AF8AE | 6A 00 | push 0x0 |
008AF8B0 | 8D4D 0C | lea ecx, dword ptr [ebp+0xC] |
008AF8B3 | 51 | push ecx |
008AF8B4 | 83C5 08 | add ebp, 0x8 |
008AF8B7 | 55 | push ebp |
008AF8B8 | 52 | push edx |
008AF8B9 | 8D4424 3C | lea eax, dword ptr [esp+0x3C] |
008AF8BD | 50 | push eax |
008AF8BE | E8 1DD91300 | call <sub_9ED1E0> |
008AF8C3 | D95C24 2C | fstp dword ptr [esp+0x2C], st(0) |
008AF8C7 | D94424 2C | fld st(0), dword ptr [esp+0x2C] |
008AF8CB | 83C4 14 | add esp, 0x14 |
008AF8CE | DC15 F8EFDD00 | fcom st(0), qword ptr [0xDDEFF8] |
008AF8D4 | DFE0 | fnstsw ax |
008AF8D6 | F6C4 05 | test ah, 0x5 |
008AF8D9 | 7A 20 | jp 0x8AF8FB |
008AF8DB | D94424 10 | fld st(0), dword ptr [esp+0x10] |
008AF8DF | C74424 14 01000000 | mov dword ptr [esp+0x14], 0x1 |
008AF8E7 | D8D1 | fcom st(0), st(1) |
008AF8E9 | DFE0 | fnstsw ax |
008AF8EB | F6C4 41 | test ah, 0x41 |
008AF8EE | 74 23 | je 0x8AF913 |
008AF8F0 | D9EE | fldz |
008AF8F2 | DED9 | fcompp |
008AF8F4 | DFE0 | fnstsw ax |
008AF8F6 | F6C4 41 | test ah, 0x41 |
008AF8F9 | 74 1A | je 0x8AF915 |
008AF8FB | DDD8 | fstp st(0), st(0) |
008AF8FD | 8D4C24 1C | lea ecx, dword ptr [esp+0x1C] |
008AF901 | E8 1AF1FFFF | call <sub_8AEA20> |
008AF906 | 8B7C24 20 | mov edi, dword ptr [esp+0x20] |
008AF90A | 8B5424 1C | mov edx, dword ptr [esp+0x1C] |
008AF90E | E9 08FFFFFF | jmp 0x8AF81B |
008AF913 | DDD8 | fstp st(0), st(0) |
008AF915 | 8B4C24 1C | mov ecx, dword ptr [esp+0x1C] |
008AF919 | 3B79 04 | cmp edi, dword ptr [ecx+0x4] |
008AF91C | 74 23 | je 0x8AF941 |
008AF91E | 8B57 0C | mov edx, dword ptr [edi+0xC] |
008AF921 | D95C24 10 | fstp dword ptr [esp+0x10], st(0) |
008AF925 | 8B4424 34 | mov eax, dword ptr [esp+0x34] |
008AF929 | 8D4C24 1C | lea ecx, dword ptr [esp+0x1C] |
008AF92D | 8910 | mov dword ptr [eax], edx |
008AF92F | E8 ECF0FFFF | call <sub_8AEA20> |
008AF934 | 8B7C24 20 | mov edi, dword ptr [esp+0x20] |
008AF938 | 8B5424 1C | mov edx, dword ptr [esp+0x1C] |
008AF93C | E9 DAFEFFFF | jmp 0x8AF81B |
008AF941 | DDD8 | fstp st(0), st(0) |
008AF943 | E8 B8EFFFFF | call <sub_8AE900> |
008AF948 | 8B4424 14 | mov eax, dword ptr [esp+0x14] |
008AF94C | 5F | pop edi |
008AF94D | 5E | pop esi |
008AF94E | 5D | pop ebp |
008AF94F | 5B | pop ebx |
008AF950 | 83C4 14 | add esp, 0x14 |
008AF953 | C2 1000 | ret 0x10 |
008AF956 | E8 25EFFFFF | call 0x8AE880 |
Basically, the function will iterate a collection of dropped items, perform type filtering based on client settings (gold/equipment/other), do what seems to be a distance check (only a guess, I've not dug into the entirety of all code yet) and then returns (not literally, via a reference parameter) an item that should be looted by the pet.
The following address are the juicy bits when it comes to the type filtering:
Code:
008AF84F | 8A4C24 28 | mov cl, byte ptr [esp+0x28] |
008AF853 | F6C1 40 | test cl, 0x40 |
008AF856 | 75 08 | jne 0x8AF860 |
008AF858 | A8 01 | test al, 0x1 |
008AF85A | 0F84 9D000000 | je 0x8AF8FD |
008AF860 | F6C1 01 | test cl, 0x1 |
008AF863 | 74 04 | je 0x8AF869 |
008AF865 | A8 08 | test al, 0x8 |
008AF867 | 75 16 | jne 0x8AF87F |
008AF869 | F6C1 02 | test cl, 0x2 |
008AF86C | 74 04 | je 0x8AF872 |
008AF86E | A8 10 | test al, 0x10 |
008AF870 | 75 0D | jne 0x8AF87F |
008AF872 | F6C1 04 | test cl, 0x4 |
008AF875 | 0F84 82000000 | je 0x8AF8FD |
008AF87B | A8 18 | test al, 0x18 |
008AF87D | 75 7E | jne 0x8AF8FD |
Your pet filter flags are being loaded from the stack at address 0x8AF84F. I've not created the proper enum for the flags yet, but here are some easily observed values (by looking at packets or debugging memory)
Code:
0x00 - off/my items/nothing
0x80 - on/my items/nothing
0x40 - off/all items/nothing
0xC0 - on/all items/nothing
0x1 - off/my items/gold
0x81 - on/my items/gold
0x41 - off/all items/gold
0xC1 - on/all items/gold
Since this is the logic we want to replace entirety, I'm not going to bother trying to re-code it exactly as they have it. All we needed to know, was that this was the right location for the primitive type matching they use. Doing a bit more debugging, we can figure out where the relevant pointers we care about for the actual item being processed are, and then using previously discovered information, we can throw together a solution for a more complex pet filter.
At this point, we have a good idea of where we need to modify the client, but now the question is what do we modify and why? The approach I use is simply to find all data I want to use, write the asm codecave to call an external function, and then write the external function via an exported function in a C++ DLL. There are other ways to do it of course, but this is a pretty basic way to present a solution to the problem.
If we're going to make a new item filter, what information do we need/care about? For this example, I care about :
- The player name, so I can have player specific item filters
- The item name, so I can filter by easy to understand item names
- The item type name, so I can filter by items in a way that will compatible in different languages/versions
- The item id (as in what's going to be sent via the 0x70C5 packet) for tracking or possibly more complex logic with letting a packet based filter solve the client filter logic issues
- The original item type the client is using for filtering it's basic categories. This is just for debugging or making sure we can still support default filter logic (although I don't do this for this thread)
The last useful thing would be the user's pet filter settings, but I choose to ignore this for this implementation, only because this is a first iteration of a solution that shouldn't be used in practice. As I mentioned before, first find a way to solve the problem, then look into better ways to implement the solution. Right now, the current dev implementation I'm using, is just not good for a number of reasons.
Anyways, we know what we want from memory, but now the question is how to find it? Well, if you don't know how to do anything with memory, you'll want to familiarize with the following two threads:
As I mentioned in those threads, the GFXRC stuff is the cornerstone of future things using client memory By finding relevant classes in the client and dumping pointers, we can track down useful information such as the item name/type, as the pointers will contain information loaded from the PK2s. I'll mention again, but the GFXRC is large portion of important stuff, but the client has plenty more that is separate from that system you'll have to reverse/create layouts for.
Obviously, I'm making a huge jump from what's posted in the GFXRC threads to specific VSRO memory related things, as you'll have to reverse memory layouts much like you would reverse packets. If some new regional SRO version came out using entirely new opcodes and packet layouts, a lot of new work would have to be done to get back to having what we already do for existing SRO versions. Trying to work with memory is like that in a sense, because there's been only a time amount of memory based work (mostly done in SRO_DevKit) vs all the packet based work that's taken place for years.
That inherently means this thread won't be usable for most people, because:
- They aren't familiar with asm and can't do the asm modifications required to expose the client information to an external DLL
- They aren't familiar with working with client memory, and building up the tools and workflow in C# (even though I've provided that already) is still a big hurdle and time investment
- This thread only covers 1/3 of what needs to be done for a proper solution. As mentioned before, VSRO's pet pickup logic is broken and needs to be rewritten, and ideally you'd want a user gui or external gui program to manage filters. You can hack together an improvement using this the ideas in this thread, but you still have quite a bit of work to do if your target is for user-friendly, pserver ready deployment.
Because of these reasons, I'm not going to do a big picture editing guide or even post modified files, because they'd simply not be usable as-is unless you're able to do this stuff on your own (after knowing it can be done, and having a reference of how to do it). However, it's still useful information to know, and for more advanced projects like SRO_DevKit, those are more suitable for stuff like this, since that project is already rewriting large parts of the client for complete customization (which is the ideal solution to the pet filter problem).
What comes next is the ASM I've written to pass client memory information to the external DLL that will contain the new pet filtering logic. The idea behind the ASM is simple: load our filter dll if it's not loaded, get the address of the filter function from the dll, build the function call for the dll using the memory pointers we've found using GFXRC or referenced from other projects, and then call the function and process the result to know where we should return execution to in the client.
Another reason why I'm not doing an editing guide for this stuff, is because you have to make the patches in relation to the address of your newly added code section to the client, and that gets overly cumbersome to explain. As mentioned at the start of the thread, when presenting information like this in a thread, the solution/implementation used, is not the same as the ideal solution/implementation. Normally, I would not manually write a bunch of ASM like this for every little thing, and instead use a framework I've created to do this stuff, but it's not practical to reference complex things like those in already overly complex/long threads like this. Hopefully readers will understand - this stuff is hard to manage when you're trying to share information.
Code:
0116F100 | 60 | pushad |
0116F101 | 9C | pushfd |
0116F102 | 833D 00F01601 00 | cmp dword ptr [0x116F000], 0x0 |
0116F109 | 75 32 | jne 0x116F13D |
0116F10B | 68 10F01601 | push 0x116F010 | 116F010:"BasicPetFilter.Backend.dll"
0116F110 | E8 BB1AE275 | call <kernel32._LoadLibraryAStub@4> |
0116F115 | A3 00F01601 | mov dword ptr [0x116F000], eax |
0116F11A | 83F8 00 | cmp eax, 0x0 |
0116F11D | 75 09 | jne 0x116F128 |
0116F11F | 9D | popfd |
0116F120 | 61 | popad |
0116F121 | 33C0 | xor eax, eax |
0116F123 | E9 D50774FF | jmp [3][pet_filter]sro_client.8AF8FD |
0116F128 | 68 30F01601 | push 0x116F030 | 116F030:&"9{Pt\n¸\x01"
0116F12D | 50 | push eax |
0116F12E | E8 1D04E275 | call <kernel32._GetProcAddressStub@8> |
0116F133 | A3 04F01601 | mov dword ptr [<sub_116F004>], eax |
0116F138 | 83F8 00 | cmp eax, 0x0 |
0116F13B | 74 E2 | je 0x116F11F |
0116F13D | 8B47 0C | mov eax, dword ptr [edi+0xC] |
0116F140 | 50 | push eax |
0116F141 | E8 DA4085FF | call <[3][pet_filter]sro_client.sub_9C3220> |
0116F146 | 83C4 04 | add esp, 0x4 |
0116F149 | 83F8 00 | cmp eax, 0x0 |
0116F14C | 74 D1 | je 0x116F11F |
0116F14E | A3 08F01601 | mov dword ptr [0x116F008], eax |
0116F153 | 8BF0 | mov esi, eax |
0116F155 | 68 281CEF00 | push [3][pet_filter]sro_client.EF1C28 |
0116F15A | 8BCE | mov ecx, esi |
0116F15C | E8 2F5AA2FF | call <[3][pet_filter]sro_client.sub_B94B90> |
0116F161 | 84C0 | test al, al |
0116F163 | 74 BA | je 0x116F11F |
0116F165 | 83BE 8C020000 00 | cmp dword ptr [esi+0x28C], 0x0 |
0116F16C | 75 B1 | jne 0x116F11F |
0116F16E | 90 | nop |
0116F16F | 8B45 04 | mov eax, dword ptr [ebp+0x4] |
0116F172 | 50 | push eax |
0116F173 | 90 | nop |
0116F174 | A1 08F01601 | mov eax, dword ptr [0x116F008] |
0116F179 | 05 20020000 | add eax, 0x220 |
0116F17E | 8B00 | mov eax, dword ptr [eax] |
0116F180 | 83C0 0C | add eax, 0xC |
0116F183 | 8378 14 08 | cmp dword ptr [eax+0x14], 0x8 |
0116F187 | 72 02 | jb 0x116F18B |
0116F189 | 8B00 | mov eax, dword ptr [eax] |
0116F18B | 50 | push eax |
0116F18C | 90 | nop |
0116F18D | A1 08F01601 | mov eax, dword ptr [0x116F008] |
0116F192 | 05 14010000 | add eax, 0x114 |
0116F197 | 8378 14 08 | cmp dword ptr [eax+0x14], 0x8 |
0116F19B | 72 02 | jb 0x116F19F |
0116F19D | 8B00 | mov eax, dword ptr [eax] |
0116F19F | 50 | push eax |
0116F1A0 | 90 | nop |
0116F1A1 | A1 08F01601 | mov eax, dword ptr [0x116F008] |
0116F1A6 | 05 F8000000 | add eax, 0xF8 |
0116F1AB | 8B00 | mov eax, dword ptr [eax] |
0116F1AD | 50 | push eax |
0116F1AE | 90 | nop |
0116F1AF | A1 ECF5EE00 | mov eax, dword ptr [0xEEF5EC] |
0116F1B4 | 05 14010000 | add eax, 0x114 |
0116F1B9 | 8378 14 08 | cmp dword ptr [eax+0x14], 0x8 |
0116F1BD | 72 02 | jb 0x116F1C1 |
0116F1BF | 8B00 | mov eax, dword ptr [eax] |
0116F1C1 | 50 | push eax |
0116F1C2 | 90 | nop |
0116F1C3 | A1 04F01601 | mov eax, dword ptr [<sub_116F004>] |
0116F1C8 | FFD0 | call eax |
0116F1CA | 3C 00 | cmp al, 0x0 |
0116F1CC | 0F84 4DFFFFFF | je 0x116F11F |
0116F1D2 | 9D | popfd |
0116F1D3 | 61 | popad |
0116F1D4 | E9 D10674FF | jmp <[3][pet_filter]sro_client.sub_8AF8AA> |
Before getting into the specifics of that ASM and what everything is doing and why, I need to skip ahead to the implementation of the pet filter function we'll be calling from our external DLL. Since the function is pretty much the entirety of the DLL, I'll just paste the main file itself.
DLLMain.cpp
Code:
#include <windows.h>
#include <codecvt>
#include <locale>
#include <fstream>
#include <sstream>
#include <map>
#include "../extern/jsoncpp/json/json.h"
//-----------------------------------------------------------------------------
HMODULE g_Module;
//-----------------------------------------------------------------------------
/// <remarks>
/// https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain
/// </summary>
BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
{
// Perform actions based on the reason for calling.
switch (reason)
{
case DLL_PROCESS_ATTACH:
g_Module = static_cast<HMODULE>(instance);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
default:
return FALSE;
}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}
//-----------------------------------------------------------------------------
/// <remarks>
/// https://json.nlohmann.me/home/faq/#wide-string-handling
/// </remarks>
std::string to_utf8(const std::wstring& wide_string)
{
static std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
return utf8_conv.to_bytes(wide_string);
}
//-----------------------------------------------------------------------------
extern "C" __declspec(dllexport) bool __stdcall ShouldLoot(
const wchar_t* playerName,
uint32_t itemId,
const wchar_t* itemName,
const wchar_t* itemTypeName,
uint32_t itemDropType)
{
auto shouldDisplay = false;
// NOTE: This is just debugging display logic (for when running the client in a debugger).
// You'd not want to do this in a real filter due to extra overhead/memory issues.
{
static std::map<uint32_t, DWORD64> g_DisplayedIds;
const auto curTime = GetTickCount64();
const auto itr = g_DisplayedIds.find(itemId);
if (itr == g_DisplayedIds.end())
{
shouldDisplay = true;
}
else if (curTime - itr->second > 1000 * 60)
{
// This weird case is to just output the item being looted after 1 minute, which could
// be from the pet getting stuck, or maybe an item id was reused after despawn. In any case,
// we just don't want to spam the debugger with messages each loop.
shouldDisplay = true;
}
if (shouldDisplay)
{
std::wstringstream wss;
wss << L"[ShouldLoot] Player: [" << playerName << L"]" <<
L" ItemName: [" << itemName << L"]" <<
L" ItemTypeName: [" << itemTypeName << L"]" <<
L" ItemDropType: [" << itemDropType << L"]";
OutputDebugStringW(wss.str().c_str());
g_DisplayedIds[itemId] = curTime;
}
}
// TODO: Implement your filter logic here.
{
// This logic is pretty 'yikes', so don't copy it. This is just a simple example of how it could be done,
// ideally you want to find a really performant, flexible solution, which this is not due to
// the string conversion overhead from having to work with Windows unicode (sro) <-> utf8 (jsoncpp)
// You can do ugly hacks, but the reason I've not is to be able to support non-English name filtering,
// so I just can't cast wstring characters into narrow characters. If you only support as the type ids,
// then those have to be in english, and can be processed as normal unicode strings. For simplicity, I'm
// not going to write separate logic for that in this example. Also, you need to work in a better reloading
// system. This is just your typical whitelist filter by type/name design though.
static std::map<std::string, bool> filteredTypeIds;
static std::map<std::string, bool> filteredNames;
static bool loaded = false;
// Simply trick to make reloading easy. User can edit the json first, then create a file to force a reload.
if (GetFileAttributesA("filter/reload.txt") != INVALID_FILE_ATTRIBUTES)
{
// Make sure we can delete the file before we try to reload, otherwise, we'll hammer the client with
// the json reloading logic each frame. Ideally, let the user know the reload file could not be deleted.
if (DeleteFileA("filter/reload.txt"))
{
filteredTypeIds.clear();
filteredNames.clear();
loaded = false;
OutputDebugStringW(L"Now reloading the pet filter!");
}
else
{
auto dwError = GetLastError();
std::wstringstream wss;
wss << L"'DeleteFileA' failed with error (" << dwError << L")";
OutputDebugStringW(wss.str().c_str());
}
}
if (!loaded)
{
try
{
// https://stackoverflow.com/a/2602258
std::wstringstream wss;
wss << "petfilters/" << playerName << ".json";
std::ifstream t(wss.str());
if (!t.is_open())
{
std::wstringstream wss;
wss << L"A pet filter for player \"" << playerName << L"\" was not found.";
OutputDebugStringW(wss.str().c_str());
return false;
}
std::stringstream buffer;
buffer << t.rdbuf();
// https://github.com/open-source-parsers/jsoncpp/wiki#code-example
Json::Value root;
buffer >> root;
// Pray no exceptions are thrown due to user data errors...
const Json::Value itemTypes = root["types"];
for (size_t index = 0; index < itemTypes.size(); ++index)
{
const auto& id = itemTypes[index].asString();
filteredTypeIds[id] = true; // Or we could also support a flag via json to enable/disable
}
const Json::Value itemNames = root["names"];
for (size_t index = 0; index < itemNames.size(); ++index)
{
const auto& id = itemNames[index].asString();
filteredNames[id] = true; // Or we could also support a flag via json to enable/disable
}
loaded = true;
}
catch (const std::exception& ex)
{
OutputDebugStringA(ex.what());
return false;
}
catch (...) // ¯\_(ツ)_/¯
{
OutputDebugStringA("An unhandled exception occurred");
return false;
}
}
// Check by type id
if (!filteredTypeIds.empty())
{
auto utf8 = to_utf8(itemTypeName);
auto itr = filteredTypeIds.find(utf8);
if (itr != filteredTypeIds.end())
{
if (shouldDisplay)
{
OutputDebugStringW(itr->second ? L" => true" : L" => false");
}
return itr->second;
}
}
if (!filteredNames.empty())
{
auto utf8 = to_utf8(itemName);
auto itr = filteredNames.find(utf8);
if (itr != filteredNames.end())
{
if (shouldDisplay)
{
OutputDebugStringW(itr->second ? L" => true" : L" => false");
}
return itr->second;
}
}
}
if (shouldDisplay)
{
OutputDebugStringW(L" => No rule matches, returning false");
}
// Not matched, so don't pick by default.
// Obviously you can change this behavior if you want a blacklist instead.
return false;
}
The 'ShouldLoot' function will be called with already processed client data. All of the interesting things I mentioned before, are being passed into the function for processing. An example filter would look like this:
Code:
{
"types" : [
"ITEM_ETC_GOLD_01",
"ITEM_ETC_GOLD_02",
"ITEM_ETC_GOLD_03",
"ITEM_ETC_AMMO_BOLT_01",
"ITEM_ETC_AMMO_ARROW_01"
],
"names" : [
"Jade tablet of strikes(Lvl.1)",
],
}
The idea behind the shown filter logic, is to simply check the item being looted against the user's settings. As stated before, we check types when we want to have a filter that can be used on different SRO versions/languages (as the base types don't change, only stuff is added or removed the vast majority of the time). However, types are ugly not user-friendly names, and rely on PK2 file data to obtain. Names are more natural for the most part, but case/spacing matters, and can be more problematic when you need to support more than 1 language.
For example, in VSRO the name of arrows is actually "Arrow " (with an extra space) as opposed to "Arrow". Obviously you can do some trimming and stuff, but when it comes to case or punctuation, it's far better to just use item types instead, and make a user gui that simplifies the process of adding/removing categories rather than having users manually type in the items (although they could if they wanted to). This gets into the user gui part I mentioned earlier and why I'm not covering it in this thread, there's a lot of decisions to be made, and different options depending on what works best for each server.
Getting back to the ASM code, if you work your ways from bottom to top, as stack arguments have to be pushed in reverse order, each section of the call building code is simply obtaining the necessary information from the client, based on memory structures found using the GFXRC project and a debugger. I dump memory layouts, check pointers, and then code in the std::n_wstring.c_str() logic to pass a wchar_t* to the DLL. The LoadLibrary/GetProcAddress logic should be pretty obvious. The debugger doesn't show the function string correctly, but "ShouldLoot" is being passed to GetProcAddress.
The other ASM code is just doing what the client did to get the CIItem and verify it's actually a valid CIItem. I will note I did not do one thing the client code does, because it wasn't really needed. In the client, the following line ends up not being done in my codecave, because I restore original registers before jumping back:
Code:
008AF888 | 8BF0 | mov esi, eax |
Typically, you'd want to always match registers when you interrupt original code, but in the cases where you know they aren't needed, you don't have to. I should at least mention it though, in case the register is being used in another version (not that my ASM is usable as-is anyways, but if anyone with keen eyes notices).
Lastly, I need to mention the codecave itself. This is where I jmp into the codecave and nop their existing category matching logic:
Code:
008AF84F | E9 ACF88B00 | jmp [3][pet_filter]sro_client.116F100 |
008AF854 | 90 | nop |
008AF855 | 90 | nop |
008AF856 | 90 | nop |
008AF857 | 90 | nop |
008AF858 | 90 | nop |
008AF859 | 90 | nop |
008AF85A | 90 | nop |
008AF85B | 90 | nop |
008AF85C | 90 | nop |
008AF85D | 90 | nop |
008AF85E | 90 | nop |
008AF85F | 90 | nop |
008AF860 | 90 | nop |
008AF861 | 90 | nop |
008AF862 | 90 | nop |
008AF863 | 90 | nop |
008AF864 | 90 | nop |
008AF865 | 90 | nop |
008AF866 | 90 | nop |
008AF867 | 90 | nop |
008AF868 | 90 | nop |
008AF869 | 90 | nop |
008AF86A | 90 | nop |
008AF86B | 90 | nop |
008AF86C | 90 | nop |
008AF86D | 90 | nop |
008AF86E | 90 | nop |
008AF86F | 90 | nop |
008AF870 | 90 | nop |
008AF871 | 90 | nop |
008AF872 | 90 | nop |
008AF873 | 90 | nop |
008AF874 | 90 | nop |
008AF875 | 90 | nop |
008AF876 | 90 | nop |
008AF877 | 90 | nop |
008AF878 | 90 | nop |
008AF879 | 90 | nop |
008AF87A | 90 | nop |
008AF87B | 90 | nop |
008AF87C | 90 | nop |
008AF87D | 90 | nop |
008AF87E | 90 | nop |
008AF87F | 90 | nop |
008AF880 | 90 | nop |
008AF881 | 90 | nop |
008AF882 | 90 | nop |
008AF883 | 90 | nop |
008AF884 | 90 | nop |
008AF885 | 90 | nop |
008AF886 | 90 | nop |
008AF887 | 90 | nop |
008AF888 | 90 | nop |
008AF889 | 90 | nop |
008AF88A | 90 | nop |
008AF88B | 90 | nop |
008AF88C | 90 | nop |
008AF88D | 90 | nop |
008AF88E | 90 | nop |
008AF88F | 90 | nop |
008AF890 | 90 | nop |
008AF891 | 90 | nop |
008AF892 | 90 | nop |
008AF893 | 90 | nop |
008AF894 | 90 | nop |
008AF895 | 90 | nop |
008AF896 | 90 | nop |
008AF897 | 90 | nop |
008AF898 | 90 | nop |
008AF899 | 90 | nop |
008AF89A | 90 | nop |
008AF89B | 90 | nop |
008AF89C | 90 | nop |
008AF89D | 90 | nop |
008AF89E | 90 | nop |
008AF89F | 90 | nop |
008AF8A0 | 90 | nop |
008AF8A1 | 90 | nop |
008AF8A2 | 90 | nop |
008AF8A3 | 90 | nop |
008AF8A4 | 90 | nop |
008AF8A5 | 90 | nop |
008AF8A6 | 90 | nop |
008AF8A7 | 90 | nop |
008AF8A8 | 90 | nop |
008AF8A9 | 90 | nop |
008AF8AA | 8B5424 30 | mov edx, dword ptr [esp+0x30] |
Keep in mind I return to the client in different locations based on if the item is going to be matched or not. Since I don't do flag checking for all/my items, I'd probably have to add an extra parameter for that, but I don't need it for my dev testing, so I didn't do it (as once again, none of this stuff is ready to use for most people anyways).
An alternative is also possible. Instead of having complex ASM codecave logic like I do, you can opt to just pass in specific pointers to the DLL instead, and then do the memory reading from the external DLL (as it's injected into the client, so it has full access to memory). That approach is also fine, but then ties the filter dll to each client version, as opposed to only needing the custom ASM codecave logic per-client instead. Typically, I'd automate the process of writing that ASM codecave stuff, so making it per version is the better solution and keeping the filter DLL independent, but I can't do that for this thread, so that's why it might seem weird to do things that way.
This is just a development thread that talks about the different things that can be done, but an end user release thread would make a specific decision and go whatever direction was bet for the server. I'm obviously out of the loop on what the SRO playerbase wants for pet filter customization, so I'm not going to choose a solution that would only work for me right now.
The end result of all this stuff, is being able to have a customizable pet filter for the client, that doesn't rely on packet processing. Obviously it's a different type of complexity, and probably overly complex for most if you've never done memory stuff or client editing, but I think it's a viable solution that could most likely fit into a project like SRO_DevKit.
Here's a little dev video of it in action: (The double debugger output is just a side-effect of the way my debugger is configured, the code itself isn't bugging out. Also if the video is blurry, it's because YT is still processing the HD version)
As mentioned before, there's really little point in doing all this work to make pet filters more customizable and not just go ahead and fix their pet pickup logic while you're at it. In that regards, just using a packet sending DLL instead would probably be better, as you'd still want the pet filter logic running (with no items selected) so the DLL has the info to process still (as it doesn't run if you turn off pet pickup!)
But the process of things is crawl, walk, run, so I started out with this specific thing to be able to make my way into the bigger picture problem with the pet pickup issue. There's a glaring flaw to me in their solution that will be really hard to overcome without server changes. The pet itself is a physical cos entity in the world, so it does pathfinding checks and can get stuck, and has somewhat flakey follow logic.
I think the better solution would be for it to simply be a virtual avatar, and whether it can loot an item or not is based on whether the player can loot an item from where they're standing. Ideally, a more advanced pathfinding system (that they don't have) would be best so it could pathfind from loot to loot, and the time it takes to travel is estimated based on that rather than actually moving in straight lines to physically loot an item. Pets getting stuck and being unable to loot just seems so counterintuitive for that type of "premium" feature, but I digress.
Anyways, I know this was long and excessively technical, and probably not usable for most, but hopefully a few people can get some ideas or motivations from it. Happy hacking!
|
|
|
09/17/2021, 12:02
|
#2
|
elite*gold: 0
Join Date: Apr 2017
Posts: 986
Received Thanks: 456
|
Well done bro
|
|
|
09/17/2021, 13:03
|
#3
|
elite*gold: 53
Join Date: Jul 2012
Posts: 538
Received Thanks: 185
|
good bro
|
|
|
09/19/2021, 20:01
|
#4
|
elite*gold: 312
Join Date: Jul 2020
Posts: 160
Received Thanks: 13
|
please boss
create guide for noobs like me how to get the right address for something to hook
for example when i hover on avatar how i can get the address
i see this code in dev_kit
Code:
replaceOffset(0x00682AFC, addr_from_this(&CIFItemComparison::AppendAdvancedInfo)); // set
replaceOffset(0x00682D6E, addr_from_this(&CIFItemComparison::AppendAdvancedInfo)); // accs
replaceOffset(0x00682FBE, addr_from_this(&CIFItemComparison::AppendAdvancedInfo)); // wep
replaceOffset(0x0068320E, addr_from_this(&CIFItemComparison::AppendAdvancedInfo)); // shield
|
|
|
09/20/2021, 08:47
|
#5
|
elite*gold: 260
Join Date: Aug 2008
Posts: 560
Received Thanks: 3,751
|
Quote:
Originally Posted by kotsh23
please boss
create guide for noobs like me how to get the right address for something to hook
|
Unfortunately, no such guide is possible, because figuring out the right address to hook something is a result of just reversing the game. You need to know x86 assembly, how to use a debugger (x32dbg/ollydbg), how C/C++ code gets generated in MSVC, and optionally know how to use a disassembly tool like IDA/GHIDRA. Then, you need to understand how the game itself works, so you can figure out where you need to hook to change features and stuff.
It's years and years of practice and building up experience to get to the point where you can just find whatever you want. I started learning this stuff in 2006 and am still learning stuff and advancing my skills, so it's been a very long time to get to where I am now. There's no real good guides to follow either, you just have to figure out what works for you learning wise, but thankfully a lot of information is on the internet nowadays, so it's not an impossible task.
If you were looking for an explanation of how someone found addresses like the ones you mentioned, the best bet is to usually just ask the person who found them, which if it's from sro_devkit, should be florian0. He's actively posted a lot of useful information about the process he's used to find stuff (check his blog posts too!) so maybe you can message him or post in the devkit thread if you need help understanding how to find something he has, assuming it was him.
Just a note though, if you don't know much about this stuff at all, asking someone who does usually comes off as "solve my problems for me", so getting specific help about something you want is usually hard if it's not something someone in the community wants to do for everyone. I made this thread about client based pet filter only because I saw a lot of threads over the years about people wanting to do it, but only knowing of the packet based way or just using a bot.
After reading this thread, maybe people decide the packet based way is easier/better, but an alternative does exist. If someone asked me a month ago to help them do a client based pet filter, I'd give them general tips on how to go about it, but if I wasn't interested at the time in looking at it, I simply would have declined. That's just the nature of this stuff. So if you need something specific, you can ask around and hope someone is interested, but usually you'll either have to just wait until someone does it, or learn how to do it all yourself (like I did) so you never have to wait on anyone again
That's the main motivation for my posts. Yes, it's super complex and intimidating, but if you're willing to take the time and learn and put in the work over years, you too can do also do stuff like this. A lot of people have spent 5/10/15 years around SRO now. That's a long time. Had they just dedicated the same time to learning how to do things themselves, they'd already be at a point where they wish to be. Time is going to pass regardless, so the best time to start learning this stuff is today. Then, you just have to be willing to stay dedicated to (hopefully) reach your ultimate goals.
|
|
|
10/31/2021, 22:11
|
#6
|
elite*gold: 0
Join Date: Apr 2012
Posts: 263
Received Thanks: 267
|
i have omitted the parts which i think are not necessary , but my client always get crash , can you help me
PHP Code:
std::string wchar2string(const wchar_t* str) { std::string mystring; while( *str ) mystring += (char)*str++; return mystring; } bool __stdcall ShouldLoot(const wchar_t* playerName,uint32_t itemId,const wchar_t* itemName,const wchar_t* itemTypeName,uint32_t itemDropType) {
{
static std::map<std::string, bool> filteredTypeIds; static std::map<std::string, bool> filteredNames; static bool loaded = false;
if (!loaded) { try { filteredTypeIds.insert(std::pair<std::string, bool>("ITEM_ETC_GOLD_01", true)); filteredTypeIds.insert(std::pair<std::string, bool>("ITEM_ETC_GOLD_02", true)); filteredTypeIds.insert(std::pair<std::string, bool>("ITEM_ETC_GOLD_03", true)); filteredTypeIds.insert(std::pair<std::string, bool>("ITEM_ETC_AMMO_BOLT_01", true)); filteredTypeIds.insert(std::pair<std::string, bool>("ITEM_ETC_AMMO_ARROW_01", true)); filteredNames.insert(std::pair<std::string, bool>("Jade tablet of strikes(Lvl.1)", true)); loaded = true; } catch (const std::exception& ex) { OutputDebugStringA(ex.what()); return false; } catch (...) // ¯\_(ツ)_/¯ { OutputDebugStringA("An unhandled exception occurred"); return false; } }
// Check by type id if (!filteredTypeIds.empty()) { std::string utf8 = wchar2string(itemTypeName); static std::map<std::string, bool>::iterator itr = filteredTypeIds.find(utf8); if (itr != filteredTypeIds.end()) { return itr->second; } }
if (!filteredNames.empty()) { std::string utf8 =wchar2string(itemName); static std::map<std::string, bool>::iterator itr = filteredNames.find(utf8); if (itr != filteredNames.end()) { return itr->second; } } } }
|
|
|
11/01/2021, 02:19
|
#7
|
elite*gold: 260
Join Date: Aug 2008
Posts: 560
Received Thanks: 3,751
|
Quote:
Originally Posted by thaidu0ngpr0
i have omitted the parts which i think are not necessary , but my client always get crash , can you help me
|
What are you actually JMP'ing into in your first screenshot? The address the JMP points to is not shown in that second screenshot. If you dynamically load your DLL, you'll have to dynamically obtain the function address like I do to know where to call it. That doesnt seem like a problem since your code execution landed in your DLL, but I'll point it out anyways.
I'd start with that, a JMP is a forceful transfer of execution, with no register or stack changes other than EIP, so if you've made a C++ function in a DLL, and are trying to JMP directly into it, you'll crash because C++ functions expect to be called a certain way. Typically, you'd have a naked C++ function you jmp to, which then calls your normal C++ function inside. This sort of proxy function is how a lot of API hooking works.
If you look at my codecave in the client that jmps into my dll:
Quote:
008AF84F | E9 ACF88B00 | jmp [3][pet_filter]sro_client.116F100 |
|
Then look at the codecave itself (notice the address 0x116F100 matches)
Quote:
0116F100 | 60 | pushad |
0116F101 | 9C | pushfd |
0116F102 | 833D 00F01601 00 | cmp dword ptr [0x116F000], 0x0 |
0116F109 | 75 32 | jne 0x116F13D |
...
0116F1C3 | A1 04F01601 | mov eax, dword ptr [<sub_116F004>] |
0116F1C8 | FFD0 | call eax |
0116F1CA | 3C 00 | cmp al, 0x0 |
0116F1CC | 0F84 4DFFFFFF | je 0x116F11F |
0116F1D2 | 9D | popfd |
0116F1D3 | 61 | popad |
0116F1D4 | E9 D10674FF | jmp <[3][pet_filter]sro_client.sub_8AF8AA> |
|
All that code is doing something like this in C++:
Code:
HMODULE g_hDLL;
FARPROC g_ShouldLoot;
..
if (!g_hDLL)
{
g_hDLL = LoadLibraryA("BasicPetFilter.Backend.dll");
if (!g_hDLL)
return false;
g_ShouldLoot = GetProcAddress(g_hDLL, "ShouldLoot");
if (!g_ShouldLoot )
return false;
}
if (!g_ShouldLoot( .. stuff from client ..))
return false;
return true; // Although not quite return true, since it jumps to a different location to resume than where return false jmps to
So if you forgot about the exact way I did things, what you'd want to do is:
- Get an address to your ShouldLoot function
- Build the function call by getting all the info like I do from the client and pushing it into the stack in the right order
- Call your function and process the result to return to either the loop to check other items or the "should loot item" client code
See if that helps any, you might want to try to replicate my assembly as well (although it'd be in a different address in your client, so you'd have to carefully fix all the addresses) because you can call LoadLibrary on a DLL already loaded, you'll just get a handled to the existing DLL and increment it's reference count. As long as you then exported the ShouldLoot function correctly, GetProcAddress will find it, and the call will be properly executed like mine is because I build the call stack right.
Worse case, start simpler, and just get the client to call your hooked function (with no parameters, so no call building in asm) and make sure that works, then slowly build it up. This stuff is pretty niche and complicated as it's easy to mess up very tiny details expedically when transferring execution across modules. The way your C++ DLL is compiled can matter too, so make sure to use Release to avoid any corruption from debug mode as well.
|
|
|
11/01/2021, 09:09
|
#8
|
elite*gold: 0
Join Date: Apr 2012
Posts: 263
Received Thanks: 267
|
Quote:
Originally Posted by pushedx
What are you actually JMP'ing into in your first screenshot? The address the JMP points to is not shown in that second screenshot. If you dynamically load your DLL, you'll have to dynamically obtain the function address like I do to know where to call it. That doesnt seem like a problem since your code execution landed in your DLL, but I'll point it out anyways.
I'd start with that, a JMP is a forceful transfer of execution, with no register or stack changes other than EIP, so if you've made a C++ function in a DLL, and are trying to JMP directly into it, you'll crash because C++ functions expect to be called a certain way. Typically, you'd have a naked C++ function you jmp to, which then calls your normal C++ function inside. This sort of proxy function is how a lot of API hooking works.
If you look at my codecave in the client that jmps into my dll:
Then look at the codecave itself (notice the address 0x116F100 matches)
All that code is doing something like this in C++:
Code:
HMODULE g_hDLL;
FARPROC g_ShouldLoot;
..
if (!g_hDLL)
{
g_hDLL = LoadLibraryA("BasicPetFilter.Backend.dll");
if (!g_hDLL)
return false;
g_ShouldLoot = GetProcAddress(g_hDLL, "ShouldLoot");
if (!g_ShouldLoot )
return false;
}
if (!g_ShouldLoot( .. stuff from client ..))
return false;
return true; // Although not quite return true, since it jumps to a different location to resume than where return false jmps to
So if you forgot about the exact way I did things, what you'd want to do is:
- Get an address to your ShouldLoot function
- Build the function call by getting all the info like I do from the client and pushing it into the stack in the right order
- Call your function and process the result to return to either the loop to check other items or the "should loot item" client code
See if that helps any, you might want to try to replicate my assembly as well (although it'd be in a different address in your client, so you'd have to carefully fix all the addresses) because you can call LoadLibrary on a DLL already loaded, you'll just get a handled to the existing DLL and increment it's reference count. As long as you then exported the ShouldLoot function correctly, GetProcAddress will find it, and the call will be properly executed like mine is because I build the call stack right.
Worse case, start simpler, and just get the client to call your hooked function (with no parameters, so no call building in asm) and make sure that works, then slowly build it up. This stuff is pretty niche and complicated as it's easy to mess up very tiny details expedically when transferring execution across modules. The way your C++ DLL is compiled can matter too, so make sure to use Release to avoid any corruption from debug mode as well.
|
I will try it tonight. Can you post a tutorial on how to get the character's skill info stored in the _reskill database by memory? like skill id, skill name that the character owns
|
|
|
11/11/2021, 19:52
|
#9
|
elite*gold: 0
Join Date: Apr 2012
Posts: 263
Received Thanks: 267
|
the most important part is how to get drop item info I don't see you talking about
|
|
|
11/17/2021, 19:57
|
#10
|
elite*gold: 0
Join Date: Apr 2012
Posts: 263
Received Thanks: 267
|
thanks ! it work perfectly
|
|
|
02/09/2022, 14:05
|
#11
|
elite*gold: 0
Join Date: Jan 2020
Posts: 132
Received Thanks: 195
|
Thats alot of effort for real.
|
|
|
04/03/2022, 05:09
|
#12
|
elite*gold: 0
Join Date: Oct 2011
Posts: 13
Received Thanks: 1
|
Can i have your discord? want to ask you about some stuff on private it's silkroad related thanks in advance
|
|
|
07/15/2022, 01:29
|
#13
|
elite*gold: 20
Join Date: Apr 2008
Posts: 2,643
Received Thanks: 2,326
|
Just to say good effort writing these guides still, even though the audience for them might be smaller now. I’m just making my first post in a few years after my periodic forum lookup!
Definitely if the day ever comes I decide to pickup coding and/or Assembly as a hobby I will refer back to these. Best way to learn is with something you already know. Still a lot of this game is imprinted in my memory - I spent many hours in the UI of your packet sniffer.
|
|
|
11/13/2022, 13:25
|
#14
|
elite*gold: 0
Join Date: Dec 2021
Posts: 14
Received Thanks: 20
|
thats the reversed of the pickup func
Code:
//
// Created by Kurama on 9/19/2022.
//
#pragma once
#include "BSLib/_internal/custom_stl.h"
#include "Source/GlobalHelpersThatHaveNoHomeYet.h"
#include "d3d9types.h"
#include <Test/Test.h>
enum DROP_ITEM_TYPE_FLAG : int {
DROP_ITEM_TYPE_FLAG_GOLD = 8,
DROP_ITEM_TYPE_FLAG_GOLD_EQUIPITEM = 0x10,
DROP_ITEM_TYPE_FLAG_ARCHEMY = 0x20,
DROP_ITEM_TYPE_FLAG_AMMUNITION = 0x40,
DROP_ITEM_TYPE_FLAG_TRADEGOODS = 0x80
};
struct stDropItemData {
public:
int m_nRemainingItemTime; //0x0000
DROP_ITEM_TYPE_FLAG m_nDroppedItemType; //0x0004
uregion m_wRegionId; //0x0008
D3DVECTOR m_d3dvLocation; //0x000C
char pad_0018[4]; //0x0018
private:
BEGIN_FIXTURE()
ENSURE_SIZE(0x1c)
ENSURE_OFFSET(m_nRemainingItemTime, 0x0000)
ENSURE_OFFSET(m_nDroppedItemType, 0x0004)
ENSURE_OFFSET(m_wRegionId, 0x0008)
ENSURE_OFFSET(m_d3dvLocation, 0x000C)
END_FIXTURE()
RUN_FIXTURE(stDropItemData)
};
Code:
//
// Created by Kurama on 9/20/2022.
//
#pragma once
#include "DropItemData.h"
#define g_CDropItemMgr (*(CDropItemMananger *) 0x0103639c)
class CDropItemManager {
public:
virtual ~CDropItemManager() = 0;
public:
const stDropItemData *GetDroppedItemData(DWORD dwGID);
DWORD OnPickItemAbbilty(BYTE btPickupFlag, WORD wPetRegionId, D3DVECTOR *pd3dvPetPos, DWORD *dwOutItemGID);
private:
std::n_map<DWORD, stDropItemData *> m_mapItemDropData; //0x0004
private:
BEGIN_FIXTURE()
ENSURE_SIZE(0x10)
ENSURE_OFFSET(m_mapItemDropData, 0x0004)
END_FIXTURE()
RUN_FIXTURE(CDropItemManager)
};
Code:
DWORD CDropItemManager::OnPickItemAbbilty(BYTE btPickupFlag, WORD wPetRegionId, D3DVECTOR *pd3dvPetPos, DWORD *dwOutItemGID) {
std::n_map<DWORD, stDropItemData *>::const_iterator it = m_mapItemDropData.begin();
for (; it != m_mapItemDropData.end(); it++) {
const stDropItemData *pItemData = it->second;
// there is checking for the pet mask flags and there is checking too the disctance bettween the item and the pet, check too if the entity "CItem"
if (GetEntityObjectPtr(it->first) == NULL ||
// Ex: make the pet only pick : Arrows
pItemData->m_nDroppedItemType != DROP_ITEM_TYPE_FLAG_AMMUNITION)
continue;
*dwOutItemGID = it->first;
return TRUE;
}
return FALSE;
}
|
|
|
|
Similar Threads
|
[GF][eXLib] - OpenBot | Level/Teleport/Pickup/Pickup
04/03/2024 - Metin2 Hacks, Bots, Cheats, Exploits & Macros - 490 Replies
Hi everyone, I present you the OpenBot.
This is a project that i have been working on for the past year in my free time alongside the eXLib Module.
Was made using my eXLib Module and is completely made in Python and open source.
It was also created from m2kmod, you can see some similarity in the UI, but the core features were completely rebuild.
Features:
PathFinding (Even across maps)
WaitDmg (Including bow)
|
Pickup bot mit großem pickup-Umkreis
11/17/2009 - Metin2 - 1 Replies
Ich habe im Metin2 forum gelesen, dass es einen neuen "Pickup_Bot" gibt.
Mithilfe diesem Hack kannst du Items, die nicht in deiner Reichweite sind, aufheben. Giebt es sowas wirklich, oder haben die metin2 GMs sich das nur ausgedacht?
(es gibt ja den Trick, um etwas mit Speedhack aufzuheben, obwohl es aussieht als wärest du noch ausser Reichweite...)
Meinen die damit den Speedhack Trick? Oder gibt es wirklich so einen wunderbaren Hack?
|
All times are GMT +2. The time now is 10:21.
|
|