Source code for core.dumps

# ../core/dumps.py

"""Provides dump logging functionalities."""

# =============================================================================
# >> IMPORTS
# =============================================================================
# Python Imports
#   Inspect
from inspect import ismethod

# Source.Python Imports
#   Cvars
from cvars import cvar
from cvars.flags import ConVarFlags
#   Entities
from entities import ServerClassGenerator
from entities.datamaps import FieldType
from entities.entity import BaseEntity
from entities.props import SendPropFlags
from entities.props import SendPropType
#   Memory
from memory import CLASS_INFO
from memory import Pointer
from memory.helpers import MemberFunction
#   Paths
from paths import LOG_PATH
#   Stringtables
from stringtables import string_tables


# =============================================================================
# >> ALL DECLARATION
# =============================================================================
__all__ = ('dump_class_info',
           'dump_convars',
           'dump_datamaps',
           'dump_server_classes',
           'dump_string_tables',
           'dump_weapon_scripts',
           )


# =============================================================================
# >> GLOBAL VARIABLES
# =============================================================================
_convar_types = {
    True: 'CMD',
    False: 'VAR',
}


# =============================================================================
# >> CLASS INFO
# =============================================================================
[docs]def dump_class_info(filename): """Dump the CLASS_INFO dictionary to the given file name.""" # Open/close the file with LOG_PATH.joinpath(filename + '.txt').open('w') as open_file: for classname, class_info in sorted(CLASS_INFO.items()): open_file.write('{0}\n'.format(classname)) for function_name, overloads in sorted(class_info.items()): open_file.write('\t{0}:\n'.format(function_name)) for index, info in enumerate(overloads): open_file.write('\t\tOverload {0}:\n'.format(index)) _dump_function_info_attribute( open_file, 'is_virtual', info.is_virtual) _dump_function_info_attribute( open_file, 'this_pointer_offset', info.this_pointer_offset) _dump_function_info_attribute( open_file, 'vtable_index', info.vtable_index) _dump_function_info_attribute( open_file, 'vtable_offset', info.vtable_offset) _dump_function_info_attribute( open_file, 'return_type', info.return_type) _dump_function_info_attribute( open_file, 'argument_types', tuple(map(str, info.argument_types))) _dump_function_info_attribute( open_file, 'calling_convention', info.calling_convention)
def _dump_function_info_attribute(open_file, attr_name, value): """Dump an attribute of a FunctionInfo object.""" open_file.write('\t\t\t{0}: {1}\n'.format(attr_name.ljust(20), value)) # ============================================================================= # >> CONVARS # =============================================================================
[docs]def dump_convars(filename): """Dump all convars to the given file name.""" # Create a dictionary to store the convars convars = dict() # Get the first convar convar = cvar.commands # Loop through all convars while convar is not None: # Store the convar in the dictionary convars[convar.name] = convar # Move to the next convar convar = convar.next # Get the number of commands command_count = len([ convar_name for convar_name in convars if convars[convar_name].is_command()]) # Open/close the file with LOG_PATH.joinpath(filename + '.txt').open('w') as open_file: # Write the header open_file.write( 'Commands: {0} - Variables: {1} - Total: {2}\n\n'.format( command_count, len(convars) - command_count, len(convars))) # Loop through all convars in alphabetic order for convar_name, convar in sorted(convars.items()): # Get the type (CMD/VAR) of convar convar_type = _convar_types[convars[convar_name].is_command()] # Get the convar's flags convar_flags = [ flag.name for flag in ConVarFlags if flag & convar.flags] # Get the convar's help text convar_text = convar.help_text # Write the convar with its values to file open_file.write('{0} - {1}{2}\n{3}\n\n'.format( convar_name, convar_type, ' - (' + ','.join(convar_flags) + ')' if convar_flags else '', '\t' + convar_text if convar_text else ''))
# ============================================================================= # >> DATA MAPS # =============================================================================
[docs]def dump_datamaps(filename): """Dump all entity data maps to the given file name.""" # Create a dict to get rid of all duplicates datamaps = dict(_get_datamaps()) with LOG_PATH.joinpath(filename + '.txt').open('w') as open_file: for class_name, datamap in sorted(datamaps.items()): _dump_datamap(open_file, class_name, datamap)
def _get_datamaps(): """Create a generator to loop through all entity DataMap objects. The yielded values are two-tuples, which contain the data class name of the data map and the actual DataMap object. """ from entities.factories import factory_dictionary for classname in factory_dictionary: datamap = _get_datamap(classname) while datamap: yield datamap.class_name, datamap datamap = datamap.base def _get_datamap(classname): """Return the DataMap object for the given entity classname.""" # Check existing entities at first entity = BaseEntity.find(classname) if entity is not None: return entity.datamap # We haven't found an entity. Let's create it temporarily entity = BaseEntity.create(classname) datamap = entity.datamap entity.remove() return datamap def _dump_datamap(open_file, class_name, datamap): """Dump a DataMap object to the given file object.""" base = datamap.base open_file.write('{} -> {}\n'.format( class_name, base and base.class_name)) for desc in datamap: _dump_type_description(open_file, desc) open_file.write('\n') def _dump_type_description(open_file, desc, indent=1, offset=0): """Dump a TypeDescription object to the given file object.""" offset += desc.offset open_file.write('{0}{1} {2} (offset {3})'.format( '\t'*indent, desc.type, desc.name, offset)) if desc.type == FieldType.EMBEDDED: open_file.write( ' [{0} properties]:\n'.format(len(desc.embedded_datamap))) # Dump the embedded data map for desc in desc.embedded_datamap: _dump_type_description(open_file, desc, indent+1, offset) else: open_file.write('\n') # ============================================================================= # >> SERVER CLASSES # =============================================================================
[docs]def dump_server_classes(filename): """Dump all server class send properties to the given file name.""" # Open/close the file with LOG_PATH.joinpath(filename + '.txt').open('w') as open_file: # Loop through all server classes for server_class in ServerClassGenerator(): table = server_class.table # Print the server class' name to file open_file.write('{} -> {}\n'.format( server_class.name, _find_base_server_class_name(table))) # Get all items in the server class' table _dump_server_class_table(table, open_file) # Move to the next server class server_class = server_class.next # Was this not the last server class? if server_class is not None: # Write a separator line before the next server class output open_file.write('\n')
def _find_base_server_class_name(table): """Return the name of table's base server class.""" for prop in table: if prop.name != 'baseclass': continue base_name = prop.data_table.name for server_class in ServerClassGenerator(): if server_class.table.name == base_name: return server_class.name return None return None def _dump_server_class_table(table, open_file, level=1, offset=0): """Dump all items in the given table to the given file.""" # Loop through the send props in the table for prop in table: # Skip all baseclasses if (prop.name == 'baseclass' or prop.is_exclude_prop() or prop.flags & SendPropFlags.COLLAPSIBLE ): continue # Get the current offset in case this # property is inside an internal table new_offset = prop.offset + offset # Is the current prop a table? if prop.type == SendPropType.DATATABLE: # Get the new table's instance new_table = prop.data_table # Was there an offset passed? if offset: # Write the property and its values to file open_file.write( '{0}{1} {2} (offset {3} - {4}) [{5} properties]:\n'.format( ' ' * level, prop.type, prop.name, prop.offset, new_offset, len(new_table))) # Was no offset passed? else: # Write the property and its values to file open_file.write( '{0}{1} {2} (offset {3}) [{4} properties]:\n'.format( ' ' * level, prop.type, prop.name, prop.offset, len(new_table))) # Dump all items in the table _dump_server_class_table( new_table, open_file, level + 1, new_offset) # Was there an offset passed? else: real_prop_type = _get_real_prop_type(prop) if offset: # Write the property and its values to file open_file.write('{0}{1} {2} (offset {3} - {4})\n'.format( ' ' * level, real_prop_type, prop.name, prop.offset, prop.offset + offset)) # Was no offset passed? else: # Write the property and its values to file open_file.write('{0}{1} {2} (offset {3})\n'.format( ' ' * level, real_prop_type, prop.name, prop.offset)) def _get_real_prop_type(prop): """Return the real property type name of the given prop.""" if prop.type != SendPropType.INT: return str(prop.type) bit_count = prop.bits if bit_count < 1: return str(prop.type) if bit_count >= 17: return 'INT' if bit_count >= 9: return '{}SHORT'.format('' if prop.is_signed() else 'U') if bit_count >= 2: return '{}CHAR'.format('' if prop.is_signed() else 'U') return 'BOOL' # ============================================================================= # >> STRING TABLES # =============================================================================
[docs]def dump_string_tables(filename): """Dump all string tables to the given file name.""" # Open/close the file with LOG_PATH.joinpath(filename + '.txt').open('w') as open_file: # Loop through the string tables for current_index, string_table in enumerate(string_tables): # Is the current index not zero? if current_index: # If so, Write a separator line before the next string table open_file.write('\n') # Get a filtered list of the table's strings skipping all blank # ones... items = list(filter(None, string_table)) # Write the string table's name and length to file open_file.write('{0} (Length: {1})\n'.format( string_table.name, len(items))) # Loop through all items in the string table for item in items: # Write the item to file open_file.write(' {0}\n'.format(item))
# ============================================================================= # >> WEAPON SCRIPTS # =============================================================================
[docs]def dump_weapon_scripts(filename): """Dump all WeaponInfo instances to the given file name.""" # Import weapon_scripts # This was moved here due to issues with the bms branch from weapons.scripts import weapon_scripts # Open/close the file with LOG_PATH.joinpath(filename + '.txt').open('w') as open_file: # Loop through all WeaponInfo instances... for info in weapon_scripts: # Is the current script not parsed yet? if not info.is_script_parsed: # If so, skip the current weapon... continue # Write the current weapon class name... open_file.write('{0}\n'.format('=' * 80)) open_file.write('{0}\n'.format(info.class_name)) open_file.write('{0}\n'.format('=' * 80)) # Loop through all WeaponInfo's attributes... for attr in dir(info): # Is the current attribute private or inherited from # Pointer? if attr.startswith('_') or hasattr(Pointer, attr): # If so, skip it... continue # Get the current attribute value... value = getattr(info, attr) # Is the current attribute a method or inehrited from Pointer? if ismethod(value) or isinstance( value, (MemberFunction, Pointer)): # If so, skip it... continue # Write the current attribute... open_file.write('{0} = {1}\n'.format(attr, value))