Closed Ayuto closed 3 years ago
What about using the string tables to resolve the class rather than looking for the entity?
from entities import ServerClassGenerator
from stringtables import string_tables
name = string_tables['GameRulesCreation'].get_user_data('classname') + 'Proxy'
for server_class in ServerClassGenerator():
if server_class.name == name:
break
else:
raise Exception(f'{name} class not found.')
for prop in server_class.table:
if prop.name.endswith('gamerules_data'):
break
else:
raise Exception('Data property not found.')
gamerules = prop.data_table_proxy_function(None, None, None, None, 0)
That's a great idea! Though, we wouldn't be able to access the game rules instance during certain events when the string tables aren't loaded yet, right?
That's a great idea! Though, we wouldn't be able to access the game rules instance during certain events when the string tables aren't loaded yet, right?
The game rules pointer is tied to the world and the earliest you can access it is when the world is spawned and has been pre-cached. Although the game rules entity is created right before, accessing the game rules pointer at that point would returns the previous one which will be reallocated shortly after based on the current team play rules and whatnot or the returned pointer won't be initialized because the initialization happens here. The game rules entity is also created and deleted multiple times during map changes for some reasons. The string table is updated at the same time the pointer is installed so I don't think we can get more reliable than at this very moment:
from entities import ServerClassGenerator
from entities.constants import WORLD_ENTITY_INDEX
from listeners import OnNetworkedEntitySpawned
from stringtables import string_tables
@OnNetworkedEntitySpawned
def on_networked_entity_spawned(entity):
if entity.index != WORLD_ENTITY_INDEX:
return
name = string_tables['GameRulesCreation'].get_user_data('classname') + 'Proxy'
for server_class in ServerClassGenerator():
if server_class.name == name:
break
else:
raise Exception(f'{name} class not found.')
for prop in server_class.table:
if prop.name.endswith('gamerules_data'):
break
else:
raise Exception('Data property not found.')
gamerules = prop.data_table_proxy_function(None, None, None, None, 0)
print('>> Current game rules address:', gamerules.address)
Perhaps maintaining a global pointer from that listener would save us the burden of looking it up every times.
If we want to always return a pointer reliably, we can extract it from g_pGameRules. Of course, we need to use a signature...
from memory import find_binary
#CS:GO Linux
def get_game_rules():
server = find_binary("server", srv_check=False)
ptr = server.find_pointer(b"\x55\x89\xE5\x56\x53\x83\xEC\x30\xA1\x2A\x2A\x2A\x2A\x8B\x5D\x08\x85\xC0\x74\x2A", 9, 1)
if ptr:
return ptr.get_pointer()
else:
raise Exception("game rules are currently unavailable.")
If we want to always return a pointer reliably, we can extract it from g_pGameRules. Of course, we need to use a signature...
Indeed, it would be the most reliable to an extent but wouldn't worth the maintainability on the long run. If we were to go that route, we could get it from a function we have access to, for example IServerGameDLL::Status
so we just have to maintain an offset that we could easily update with this snippet:
from engines.server import server_game_dll
from memory import get_virtual_function
from entities import ServerClassGenerator
from stringtables import string_tables
name = string_tables['GameRulesCreation'].get_user_data('classname') + 'Proxy'
for server_class in ServerClassGenerator():
if server_class.name == name:
break
else:
raise Exception(f'{name} class not found.')
for prop in server_class.table:
if prop.name.endswith('gamerules_data'):
break
else:
raise Exception('Data property not found.')
gamerules = prop.data_table_proxy_function(None, None, None, None, 0)
status = get_virtual_function(server_game_dll, 'Status')
for i in range(2048):
offset = i * 4
try:
if status.get_pointer(offset).get_ulong() == gamerules.address:
break
except (RuntimeError, ValueError):
continue
else:
raise Exception('offset not found')
print('>> Offset of g_pGameRules:', offset)
print('>> Address of game rules:', status.get_pointer(offset).get_ulong())
Though, we should avoid using signatures/offsets unless we absolutely have to. Another option could be to store the proxy class in our data so that we can resolve the proxy once and just call it whenever we want the pointer. This means we don't have to lookup for an entity, nor a string table, and can just call the proxy which will returns NULL
if the game rules are not allocated yet.
Another option could be to store the proxy class in our data so that we can resolve the proxy once and just call it whenever we want the pointer. This means we don't have to lookup for an entity, nor a string table, and can just call the proxy which will returns
NULL
if the game rules are not allocated yet.
Indeed, that's the most elegant way!
Alright, I have finished my changes :)
from engines.gamerules import find_game_rules
game_rules = find_game_rules()
print('m_bMapHasBombTarget', game_rules.get_property_bool('cs_gamerules_data.m_bMapHasBombTarget'))
print('m_bMapHasRescueZone', game_rules.get_property_bool('cs_gamerules_data.m_bMapHasRescueZone'))
@jordanbriere @CookStar Do you have anything else to add?
It took me a while to try it out, and I think it all the features work perfectly!
However, there was a compilation error with CS:GO and Blade, so I added a fix. #376
Here is a draft as a base for a discussion. My initial plan was to also expose methods from the CGameRules class, but unfortunately the classes (even the abstract(!) classes) contain many unimplemented methods. That's fine for the Windows build, but Linux can't handle that. Thus, I simply created a fake class (CGameRulesWrapper), which provides methods to access properties via SendTables. The alternative would be to add dummy implementations for every missing implementation. But I'm not sure whether the header is up to date. So, that could also cause issues.
Any opinions?