import argparse import sys from typing import Dict, List, Optional from bemani.protocol import EAmuseProtocol, Node def generate_node_name(node: Node, used_names: Dict[str, Node]) -> str: potential_name = node.name if potential_name not in used_names: used_names[potential_name] = node return potential_name loop = 1 while f'{potential_name}_{loop}' in used_names: loop = loop + 1 potential_name = f'{potential_name}_{loop}' used_names[potential_name] = node return potential_name def generate_node_create(node: Node) -> str: dtype = node.data_type if dtype == 'str': method = 'string' elif dtype == 'bin': method = 'binary' elif dtype == 'ip4': method = 'ipv4' elif dtype == '4u8': method = 'fouru8' else: method = dtype if node.is_array: method = f'{method}_array' if dtype != 'void': # Format the type for display if dtype == 'str': value = f', \'{node.value}\'' elif dtype == 'ip4': value = f', \'{node.value[0]}.{node.value[1]}.{node.value[2]}.{node.value[3]}\'' else: value = f', {node.value}' else: value = '' return f'Node.{method}(\'{node.name}\'{value})' def generate_node_link(node_name: str, used_names: Dict[str, Node], parent: Node) -> str: # Find the node that parents this, link to it found_parent = None for parent_name in used_names: if used_names[parent_name] is parent: found_parent = parent_name break if found_parent is None: raise Exception(f'Failed to find parent name for {parent}') return f'{found_parent}.add_child({node_name})' def generate_lines(node: Node, used_names: Dict[str, Node], parent: Optional[Node]=None) -> List[str]: # First, generate node itself create = generate_node_create(node) if not node.children and not node.attributes and parent: # Just directly hook this up to parent return [generate_node_link(create, used_names, parent)] # Print the node generate itself out = [] node_name = generate_node_name(node, used_names) out.append(f'{node_name} = {create}') # Now, generate add to parent if exists if parent is not None: out.append(generate_node_link(node_name, used_names, parent)) # Now generate node attributes for attr in node.attributes: out.append(f'{node_name}.set_attribute(\'{attr}\', \'{node.attributes[attr]}\')') # Now, do the same for all children for child in node.children: out.extend(generate_lines(child, used_names, node)) return out def generate_code(infile: str, outfile: str, encoding: str) -> None: if infile == '-': # Load from stdin packet = sys.stdin.buffer.read() else: with open(infile, mode="rb") as infp: packet = infp.read() infp.close # Add an XML special node to force encoding (will be overwritten if there # is one in the packet). packet = b''.join([ f''.encode(encoding), packet, ]) # Attempt to decode it proto = EAmuseProtocol() req = proto.decode( None, None, packet, ) if req is None: # Can't decode, exit raise Exception("Unable to decode packet!") # Walk through, outputting each node and attaching it to its parent code = '\n'.join(generate_lines(req, {})) if outfile == '-': print(code) else: with open(outfile, mode="a") as outfp: outfp.write(code) outfp.close def main() -> None: parser = argparse.ArgumentParser(description="A utility to generate code that will generate a packet given an example packet from a log or binary dump.") parser.add_argument("-i", "--infile", help="File containing an XML or binary node structure. Use - for stdin.", type=str, default=None, required=True) parser.add_argument("-o", "--outfile", help="File to write python code to. Use - for stdout.", type=str, default=None, required=True) parser.add_argument("-e", "--encoding", help="Encoding for the packet, defaults to UTF-8.", type=str, default='utf-8') args = parser.parse_args() generate_code(args.infile, args.outfile, args.encoding) if __name__ == '__main__': main()