OnWeaponReloaded listener?

Please post any questions about developing your plugin here. Please use the search function before posting!
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Re: OnWeaponReloaded listener?

Postby Ayuto » Sun May 29, 2016 2:22 pm

D3CEPTION wrote:[...] maybe find the respective register in memory [...]

Are you sure you mean register?

But yeah, you can hook the RemoveAmmo() method and simply block it from executing. There is no need to do anything more. But again: it requires you to use additional game data, which I would avoid if possible, because it can easily break in the next update and forces you to update your plugin.

D3CEPTION, since you are interested in that solution, here is the code:

Syntax: Select all

import memory

from memory import Convention
from memory import DataType

from entities.hooks import EntityPreHook
from entities.hooks import EntityCondition

def prepare_hook(entity):
# CBaseCombatCharacter::RemoveAmmo(int, int)
return entity.pointer.make_virtual_function(
254,
Convention.THISCALL,
[DataType.POINTER, DataType.INT, DataType.INT],
DataType.VOID
)

@EntityPreHook(EntityCondition.is_player, prepare_hook)
def pre_remove_ammo(args):
return 0
It might reduce the ammo count on the client's HUD for a tenth of a second due to client prediction.

Edit: Just figured out that you can force unlimited ammo using cvars. Though, it will set the ammo count to 999 (but it won't decrement).

Code: Select all

ammo_338mag_max -2
ammo_357sig_max -2
ammo_45acp_max -2
ammo_50AE_max -2
ammo_556mm_box_max -2
ammo_556mm_max -2
ammo_57mm_max -2
ammo_762mm_max -2
ammo_9mm_max -2
ammo_buckshot_max -2
ammo_flashbang_max -2
ammo_hegrenade_max -2
ammo_smokegrenade_max -2
User avatar
D3CEPTION
Senior Member
Posts: 129
Joined: Tue Jan 26, 2016 1:24 pm
Location: Switzerland

Re: OnWeaponReloaded listener?

Postby D3CEPTION » Sun May 29, 2016 2:56 pm

thanks for showing a possible implementation, Ayuto.
Ayuto wrote:Are you sure you mean register?

i think so, my guess was to not hook into an existing engine function and replace the content with 0, but removing the entire function from the machine code on the server. ( = finding the register(?) and setting the "add function" permanently to None, so that no "eax value" or whatver are pushed into the removeammo function at all, because this way it runs over an empty memory sequence.
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Re: OnWeaponReloaded listener?

Postby Ayuto » Sun May 29, 2016 3:16 pm

D3CEPTION wrote:[...] replace the content with 0 [...]

The 0 just tells SP to block the original method from executing. That means the hook will simply jump back to the caller.

Registers are used as temporary storage for single values, passing parameters and returning values. They do not store the implementation of functions.

Which "add function" do you mean? Or do you want to overwrite all calls to RemoveAmmo() with NOP instructions? That would work, but also mean tremendous work. I guess I didn't really understand what you want to do.
User avatar
D3CEPTION
Senior Member
Posts: 129
Joined: Tue Jan 26, 2016 1:24 pm
Location: Switzerland

Re: OnWeaponReloaded listener?

Postby D3CEPTION » Sun May 29, 2016 3:22 pm

Ayuto wrote:Which "add function" do you mean? Or do you want to overwrite all calls to RemoveAmmo() with NOP instructions? I guess I didn't really understand what you want to do.

yes Nop was the correct word, sorry. i was wondering how sp code would handle such a process and now, after what you said, if it differs at all from your method.
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Re: OnWeaponReloaded listener?

Postby Ayuto » Sun May 29, 2016 3:35 pm

You would try to find all calls to that method and use <Pointer object>.set_uchar(0x90) to set the NOP instruction (0x90 is the byte code for a NOP instruction in x86 code). But like I said: this is a tremendous work and quite difficult with virtual functions, because IDA doesn't show you the calls (only the reference in the vtable). Moreover, that method is highly platform dependent, so you would need to replace other locations on Windows than on Linux.

The difference between the hook and NOP method is that the NOP method will be faster, because it has to execute less instructions. But it's not worth the effort.
User avatar
D3CEPTION
Senior Member
Posts: 129
Joined: Tue Jan 26, 2016 1:24 pm
Location: Switzerland

Re: OnWeaponReloaded listener?

Postby D3CEPTION » Sun May 29, 2016 4:05 pm

Ayuto wrote:The difference between the hook and NOP method is that the NOP method will be faster, because it has to execute less instructions. But it's not worth the effort.

yes, im still wondering, how much speed difference this would make in effect, but i guess that depends on the individual usage of the function, that is getting "Nop'ed". for me this is still interesting though, if you know your way around the engine. thanks for sharing the <Pointer object>.set_uchar(0x90) possibility
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Re: OnWeaponReloaded listener?

Postby Ayuto » Sun May 29, 2016 4:22 pm

D3CEPTION wrote:but i guess that depends on the individual usage of the function, that is getting "Nop'ed".

Not the function (RemoveAmmo) is filled with NOP instructions, but all calls to RemoveAmmo.

Imagine this is the reload function, which calls RemoveAmmo() at some point.

Syntax: Select all

; More ASM code...
mov ecx, some_player_pointer
push 10
push 20
call RemoveAmmo
; More ASM code...
Then you would replace that piece of code with NOP instructions.

Syntax: Select all

; More ASM code...
nop
nop
nop
nop
; More ASM code...
I might be able to create a small test DLL today and test out the performance increasement.
User avatar
D3CEPTION
Senior Member
Posts: 129
Joined: Tue Jan 26, 2016 1:24 pm
Location: Switzerland

Re: OnWeaponReloaded listener?

Postby D3CEPTION » Sun May 29, 2016 4:48 pm

Ayuto wrote:Not the function (RemoveAmmo) is filled with NOP instructions, but all calls to RemoveAmmo.

i dont see any contradiction to what i said, but thanks for clarification anyway ;) ( i only said that the performance boost depends on the function, that is getting noped. more complexity/ressources within the noped function -> more performance increase )
Ayuto wrote:I might be able to create a small test DLL today and test out the performance increasement.

cant wait to see the results :)
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Re: OnWeaponReloaded listener?

Postby Ayuto » Sun May 29, 2016 5:51 pm

My tests have shown that the performance increase is neglegible.

This is my test library code (see attachment for a compiled binary):

Syntax: Select all

#include <stdio.h>

#define EXPORT extern "C" __declspec(dllexport)

EXPORT int f1(int x, int y)
{
puts("f1");
return x * y;
}

EXPORT int f1_caller()
{
puts("f1_caller");
return f1(10, 20);
}

int main()
{
return 0;
}

And this my test plugin to prove that NOP'ing calls to a function is actually working:

Syntax: Select all

import memory

from memory import Convention
from memory import DataType

from path import Path

test = memory.find_binary(Path(__file__).parent / 'test')

f1_caller = test['f1_caller'].make_function(
Convention.CDECL,
[],
DataType.INT
)

# Do a test call without any patches applied
# Expected output:
# f1_caller
# f1
# Result 200
print('Result', f1_caller())

# Now, we need to make f1_caller writable, so we can apply our patches
kernel32 = memory.find_binary('kernel32')

VirtualProtect = kernel32['VirtualProtect'].make_function(
Convention.CDECL,
[DataType.POINTER, DataType.INT, DataType.INT, DataType.POINTER],
DataType.BOOL
)

PAGE_EXECUTE_READWRITE = 0x40

old_protect = memory.alloc(4)
if not VirtualProtect(f1_caller + 17, 12, PAGE_EXECUTE_READWRITE, old_protect):
raise ValueError('Failed to set PAGE_EXECUTE_READWRITE protection flag.')

# Code of f1_caller before the patch
"""
text:10001020 ; int __cdecl f1_caller()
.text:10001020 public _f1_caller
.text:10001020 _f1_caller proc near ; DATA XREF: .rdata:off_10002538o
.text:10001020 55 push ebp
.text:10001021 8B EC mov ebp, esp
.text:10001023 68 CC 20 00 10 push offset aF1_caller_0 ; "f1_caller"
.text:10001028 FF 15 7C 20 00 10 call ds:__imp__puts
.text:1000102E 83 C4 04 add esp, 4
.text:10001031 6A 14 push 14h ; y
.text:10001033 6A 0A push 0Ah ; x
.text:10001035 E8 C6 FF FF FF call _f1
.text:1000103A 83 C4 08 add esp, 8
.text:1000103D 5D pop ebp
.text:1000103E C3 retn
.text:1000103E _f1_caller endp
"""

NOP = 0x90

# Write the NOP instructions. It should change the following code:
# .text:10001031 6A 14 push 14h ; y
# .text:10001033 6A 0A push 0Ah ; x
# .text:10001035 E8 C6 FF FF FF call _f1
# .text:1000103A 83 C4 08 add esp, 8
for offset in range(17, 29):
print('Changing %02X to 90...'% f1_caller.get_uchar(offset))
f1_caller.set_uchar(NOP, offset)

# Code of f1_caller after the patch
"""
text:10001020 ; int __cdecl f1_caller()
.text:10001020 public _f1_caller
.text:10001020 _f1_caller proc near ; DATA XREF: .rdata:off_10002538o
.text:10001020 55 push ebp
.text:10001021 8B EC mov ebp, esp
.text:10001023 68 CC 20 00 10 push offset aF1_caller_0 ; "f1_caller"
.text:10001028 FF 15 7C 20 00 10 call ds:__imp__puts
.text:1000102E 83 C4 04 add esp, 4
.text:10001031 90 90 nop, nop
.text:10001033 90 90 nop, nop
.text:10001035 90 90 90 90 90 nop, nop, nop, nop, nop
.text:1000103A 90 90 90 nop, nop, nop
.text:1000103D 5D pop ebp
.text:1000103E C3 retn
.text:1000103E _f1_caller endp
"""

# Expected output:
# f1_caller
# Result ?

# The result will be the value that is stored in EAX. It can be anything,
# because f1 doesn't get called anymore. So, you will also need to take care
# of that when NOP'ing calls to non-void functions.
print('Result', f1_caller())

And this is my test code to measure the performance:

Syntax: Select all

# NOTE: You need to restart your server between these tests!

import timeit

iterations = 10000

# Result: 0.8610537750431203
print(timeit.Timer(
"""
f1_caller()
""",
"""
import memory

from memory import Convention
from memory import DataType

test = memory.find_binary('F:/Server/css/cstrike/addons/source-python/plugins/test4/test')

f1_caller = test['f1_caller'].make_function(
Convention.CDECL,
[],
DataType.INT
)

kernel32 = memory.find_binary('kernel32')

VirtualProtect = kernel32['VirtualProtect'].make_function(
Convention.CDECL,
[DataType.POINTER, DataType.INT, DataType.INT, DataType.POINTER],
DataType.BOOL
)

PAGE_EXECUTE_READWRITE = 0x40

old_protect = memory.alloc(4)
if not VirtualProtect(f1_caller + 17, 12, PAGE_EXECUTE_READWRITE, old_protect):
raise ValueError('Failed to set PAGE_EXECUTE_READWRITE protection flag.')

NOP = 0x90
add esp, 8
for offset in range(17, 29):
f1_caller.set_uchar(NOP, offset)
""").timeit(iterations))


# Result: 0.9240437253955461
print(timeit.Timer(
"""
f1_caller()
""",
"""
import memory

from memory import Convention
from memory import DataType

from memory.hooks import PreHook

test = memory.find_binary('F:/Server/css/cstrike/addons/source-python/plugins/test4/test')

f1_caller = test['f1_caller'].make_function(
Convention.CDECL,
[],
DataType.INT
)

f1 = test['f1'].make_function(
Convention.CDECL,
[DataType.INT, DataType.INT],
DataType.INT
)

def pre_f1(args):
return 0

f1.add_pre_hook(pre_f1)
""").timeit(iterations))

Benefits of using the NOP method:
  • Small performance increase
Downside:
  • Tremendous work
  • Breaks signatures
  • Highly platform dependent
  • Breaks much faster after an update
Attachments
test.zip
(3.15 KiB) Downloaded 398 times
User avatar
iPlayer
Developer
Posts: 590
Joined: Sat Nov 14, 2015 8:37 am
Location: Moscow
Contact:

Re: OnWeaponReloaded listener?

Postby iPlayer » Sun May 29, 2016 5:53 pm

Meanwhile props to Ayuto for showing the way how to use EntityPreHook (with EntityCondition) with custom virtual functions. Now I don't need to use memory's PreHook for simple entity hooks anymore.
Image /id/its_iPlayer
My plugins: Map Cycle • Killstreaker • DeadChat • Infinite Jumping • TripMines • AdPurge • Bot Damage • PLRBots • Entity AntiSpam

Hail, Companion. [...] Hands to yourself, sneak thief. Image
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Re: OnWeaponReloaded listener?

Postby Ayuto » Sun May 29, 2016 6:05 pm

iPlayer wrote:Meanwhile props to Ayuto for showing the way how to use EntityPreHook (with EntityCondition) with custom virtual functions. Now I don't need to use memory's PreHook for simple entity hooks anymore.

I have already used this a few weeks ago. :tongue:
viewtopic.php?f=20&t=1185&p=7679#p7679
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Re: OnWeaponReloaded listener?

Postby BackRaw » Mon May 30, 2016 6:17 am

Woah!! You guys are taking this a bit too far lol :D But it's awesome to see you play around with this!!

My initial thought for this was that the player will have maxammo again after they reloaded the weapon (meaning the reload animation has completely finished) so it is consistent and "aesthetic" with the reload animation :D
I personally do not want to have 999 as maxammo value which doesn't decrease or anything like that. Just the way CSSDM has implemented it. But I couldn't find where BAILOPAN has done that.
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Re: OnWeaponReloaded listener?

Postby Ayuto » Mon May 30, 2016 9:33 am

If you want to use a hook, try this:
viewtopic.php?f=20&p=7857#p7838

That's exactly the "beautiful" solution you are looking for.
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Re: OnWeaponReloaded listener?

Postby BackRaw » Mon May 30, 2016 10:33 am

Oh, I thought that wasn't the one. Thanks!
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Re: OnWeaponReloaded listener?

Postby satoon101 » Mon May 30, 2016 10:52 pm

BackRaw wrote:But I couldn't find where BAILOPAN has done that.

Having looked at the code for CSSDM, it seems to call CBaseCombatCharacter::GiveAmmo. That is actually a virtual function. Not sure if this offset needs updated, but for my last data gathering, it's the following for CS:GO (Linux):

Code: Select all

    275   _ZN20CBaseCombatCharacter8GiveAmmoEiib
Image
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Re: OnWeaponReloaded listener?

Postby BackRaw » Wed Jun 01, 2016 10:49 am

satoon101 wrote:
BackRaw wrote:But I couldn't find where BAILOPAN has done that.

Having looked at the code for CSSDM, it seems to call CBaseCombatCharacter::GiveAmmo. That is actually a virtual function. Not sure if this offset needs updated, but for my last data gathering, it's the following for CS:GO (Linux):

Code: Select all

    275   _ZN20CBaseCombatCharacter8GiveAmmoEiib

Gonna have to play around with it for sure! :D

Return to “Plugin Development Support”

Who is online

Users browsing this forum: No registered users and 22 guests