Optimize Memory Reading / Writing using C types
qb-0 opened this issue · 7 comments
The current behavior of pymem is to read / write bytes into a buffer and unpacking / packing the binary data with struct. This could be optimized by copying the bytes into a ctype (with memmove) and using the value field. This way the whole unpacking / packing process can be skipped.
def read(handle: int, address: int, c_type, get_py_value=True) -> Any:
size = ctypes.sizeof(c_type)
buff = ctypes.create_string_buffer(size)
if ReadProcessMemory(handle, ctypes.c_void_p(address), ctypes.byref(buff), size, None) == 0:
raise OSError(GetLastError())
ctypes.memmove(ctypes.byref(c_type), ctypes.byref(buff), size)
if get_py_value:
return c_type.value
return c_type
Reading an Integer would then look like:
def read_int(handle, address):
return read(handle, address, ctypes.c_int())
My current tests on Assault Cube show some small speedup's on simple memory operations.
pyMem (Local: {'name': 'unarmed', 'health': 100, 'pos_x': 129.7091827392578, 'pos_y': 192.5406494140625, 'pos_z': 5.5}): Memory: 1.8688466548919678 sec
cTypes Mem (Local: {'name': 'unarmed', 'health': 100, 'pos_x': 129.7091827392578, 'pos_y': 192.5406494140625, 'pos_z': 5.5}): Memory: 1.6034820079803467 sec
The real advantage shows up when you read whole c structures which could be used this way.
cTypes Mem Struct (Local: {'name': 'unarmed', 'health': 100, 'pos_x': 129.7091827392578, 'pos_y': 192.5406494140625, 'pos_z': 5.5}): Memory: 0.3543844223022461 sec
The whole project and the testing file can be reviewed here: https://github.com/qb-0/RW-Mem-C
seems like a good idea to me
made a new branch to track work on this and added the read methods in https://github.com/srounet/Pymem/tree/impl-%23101
I just figured out that you don't even need to create a buffer and move the memory. You can pass the c type directly to ReadProcessMemory
. Increases the performance even more.
def read(handle: int, address: int, c_type, get_py_value=True) -> Any:
size = ctypes.sizeof(c_type)
if ReadProcessMemory(handle, ctypes.c_void_p(address), ctypes.byref(c_type), size, None) == 0:
raise OSError(GetLastError())
if get_py_value:
return c_type.value
return c_type
nice, also did you see a performance improvement when using this method for writing?
I didn't tried yet. I could write up some tests tomorrow. But I guess the result will be pretty much the same as you don't need to create a byte array from the python value:
def write(handle, address: int, data: Any) -> int:
result = ctypes.c_size_t()
WriteProcessMemory(
handle,
ctypes.cast(address, ctypes.c_void_p),
ctypes.cast(ctypes.byref(data), ctypes.c_void_p),
size,
ctypes.byref(result)
)
return result.value
def write_int(handle, address, value):
return write(handle, address, ctypes.c_int(value)) > 0
I'll remove the buffer from the read method and add write tomorrow, then just need to test it and it should be good to go
So this are my results for writing.
[R] pyMem (Local: {'name': 'unarmed', 'health': 100, 'pos_x': 182.0, 'pos_y': 73.0, 'pos_z': 3.5}): Memory: 1.7718544006347656 sec
[W] pyMem 2.9343247413635254 sec
[R] cTypes Mem (Local: {'name': 'unarmed', 'health': 101, 'pos_x': 182.0, 'pos_y': 73.0, 'pos_z': 3.5}): Memory: 1.0322694778442383 sec
[W] cTypes Mem 2.876091957092285 sec
[R] cTypes Mem Struct (Local: {'name': 'unarmed', 'health': 102, 'pos_x': 182.0, 'pos_y': 73.0, 'pos_z': 3.5}): Memory: 0.24591755867004395 sec
[W] cTypes Mem Struct 0.7125911712646484 sec
https://github.com/qb-0/RW-Mem-C/blob/master/compare_speed.py