Advertisement
Guest User

CXML decompiler v6

a guest
Jan 13th, 2020
305
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.84 KB | None | 0 0
  1. #!python2
  2.  
  3. import sys, os, struct
  4.  
  5. from io import BytesIO
  6. from pprint import pprint
  7.  
  8. def read_cstring(f):
  9.     bytes = []
  10.     while True:
  11.         byte = f.read(1)
  12.         if byte == b'\x00':
  13.             break
  14.         elif byte == '':
  15.             raise EOFError()
  16.         else:
  17.             bytes.append(byte)
  18.     return b''.join(bytes)
  19.  
  20. def check_file_magic(f, expected_magic):
  21.     old_offset = f.tell()
  22.     try:
  23.         magic = f.read(len(expected_magic))
  24.     except:
  25.         return False
  26.     finally:
  27.         f.seek(old_offset)
  28.     return magic == expected_magic
  29.  
  30. script_file_name = os.path.split(sys.argv[0])[1]
  31. script_file_base = os.path.splitext(script_file_name)[0]
  32.  
  33. if len(sys.argv) < 2:
  34.     print('CXML decompiler (c) flatz')
  35.     print('Usage: {0} <cxml file> <xml file>'.format(script_file_name))
  36.     sys.exit()
  37.  
  38. ENDIANNESS = '>'
  39.  
  40. def write_raw(f, data):
  41.     if type(data) == str:
  42.         f.write(data)
  43.     elif type(data) == unicode:
  44.         f.write(data.decode('utf-8'))
  45.     else:
  46.         f.write(data)
  47. def write_indent(f, depth):
  48.     write_raw(f, '\t' * depth)
  49. def write_line(f, data):
  50.     write_raw(f, data)
  51.     write_raw(f, '\n')
  52.  
  53. INT_FMT = ENDIANNESS + 'i'
  54. FLOAT_FMT = ENDIANNESS + 'f'
  55. STRING_FMT = ENDIANNESS + 'ii'
  56. INT_ARRAY_FMT = ENDIANNESS + 'ii'
  57. FLOAT_ARRAY_FMT = ENDIANNESS + 'ii'
  58. FILE_FMT = ENDIANNESS + 'ii'
  59. ID_FMT = ENDIANNESS + 'i'
  60. ID_REF_FMT = ENDIANNESS + 'i'
  61.  
  62. class Attribute(object):
  63.     HEADER_FMT = ENDIANNESS + 'ii'
  64.     HEADER_SIZE = struct.calcsize(HEADER_FMT)
  65.     SIZE = HEADER_SIZE + max(struct.calcsize(INT_FMT), struct.calcsize(FLOAT_FMT), struct.calcsize(STRING_FMT), struct.calcsize(INT_ARRAY_FMT), struct.calcsize(FLOAT_ARRAY_FMT), struct.calcsize(FILE_FMT), struct.calcsize(ID_FMT), struct.calcsize(ID_REF_FMT))
  66.  
  67.     TYPE_NONE = 0
  68.     TYPE_INT = 1
  69.     TYPE_FLOAT = 2
  70.     TYPE_STRING = 3
  71.     TYPE_INT_ARRAY = 4
  72.     TYPE_FLOAT_ARRAY = 5
  73.     TYPE_FILE = 6
  74.     TYPE_ID = 7
  75.     TYPE_ID_REF = 8
  76.  
  77.     def __init__(self, element):
  78.         self.element = element
  79.         self.start = None
  80.         self.name = None
  81.         self.type = None
  82.         self.offset = None
  83.         self.length = None
  84.         self.value = None
  85.  
  86.     def load(self, f):
  87.         self.start = f.tell()
  88.         data = f.read(self.HEADER_SIZE)
  89.         self.name, self.type = struct.unpack(self.HEADER_FMT, data)
  90.         data = f.read(self.SIZE - self.HEADER_SIZE)
  91.         if self.type == self.TYPE_NONE:
  92.             pass
  93.         elif self.type == self.TYPE_INT:
  94.             self.value, = struct.unpack(INT_FMT, data[:struct.calcsize(INT_FMT)])
  95.         elif self.type == self.TYPE_FLOAT:
  96.             self.value, = struct.unpack(FLOAT_FMT, data[:struct.calcsize(FLOAT_FMT)])
  97.         elif self.type == self.TYPE_STRING:
  98.             self.offset, self.length = struct.unpack(STRING_FMT, data[:struct.calcsize(STRING_FMT)])
  99.         elif self.type == self.TYPE_INT_ARRAY:
  100.             self.offset, self.length = struct.unpack(INT_ARRAY_FMT, data[:struct.calcsize(INT_ARRAY_FMT)])
  101.         elif self.type == self.TYPE_FLOAT_ARRAY:
  102.             self.offset, self.length = struct.unpack(FLOAT_ARRAY_FMT, data[:struct.calcsize(FLOAT_ARRAY_FMT)])
  103.         elif self.type == self.TYPE_FILE:
  104.             self.offset, self.length = struct.unpack(FILE_FMT, data[:struct.calcsize(FILE_FMT)])
  105.         elif self.type == self.TYPE_ID:
  106.             self.offset, = struct.unpack(ID_FMT, data[:struct.calcsize(ID_FMT)])
  107.         elif self.type == self.TYPE_ID_REF:
  108.             self.offset, = struct.unpack(ID_REF_FMT, data[:struct.calcsize(ID_REF_FMT)])
  109.         return True
  110.  
  111.     def get_name(self):
  112.         return self.element.document.get_string(self.name)
  113.  
  114.     def get_int(self):
  115.         if self.type != self.TYPE_INT:
  116.             return None
  117.         return self.value
  118.  
  119.     def get_float(self):
  120.         if self.type != self.TYPE_FLOAT:
  121.             return None
  122.         return self.value
  123.  
  124.     def get_string(self):
  125.         if self.type != self.TYPE_STRING:
  126.             return None
  127.         value = self.element.document.get_string(self.offset)
  128.         if len(value) != self.length:
  129.             return None
  130.         return value
  131.  
  132.     def get_int_array(self):
  133.         if self.type != self.TYPE_INT_ARRAY:
  134.             return None
  135.         value = self.element.document.get_int_array(self.offset, self.length)
  136.         if len(value) != self.length:
  137.             return None
  138.         return value
  139.  
  140.     def get_float_array(self):
  141.         if self.type != self.TYPE_FLOAT_ARRAY:
  142.             return None
  143.         value = self.element.document.get_float_array(self.offset, self.length)
  144.         if len(value) != self.length:
  145.             return None
  146.         return value
  147.  
  148.     def get_file(self):
  149.         if self.type != self.TYPE_FILE:
  150.             return None
  151.         value = self.element.document.get_file(self.offset, self.length)
  152.         return value
  153.  
  154.     def get_id(self):
  155.         if self.type != self.TYPE_ID:
  156.             return None
  157.         id = self.element.document.get_id_string(self.offset)
  158.         return id
  159.  
  160.     def get_id_ref(self):
  161.         if self.type != self.TYPE_ID_REF:
  162.             return None
  163.         id = self.element.document.get_id_string(self.offset)
  164.         element = Element(self.element.document)
  165.         return [id, element]
  166.  
  167.     def dump(self, f, depth):
  168.         pass
  169.         #print('  ' * depth + 'Attribute:' + 'name:{0} type:{1}'.format(self.name, self.type), end='\n', file=f)
  170.  
  171. class Element(object):
  172.     HEADER_FMT = ENDIANNESS + 'iiiiiii'
  173.     SIZE = struct.calcsize(HEADER_FMT)
  174.  
  175.     TAG_NAME = 0
  176.     ATTR_NUM = 1
  177.     PARENT = 2
  178.     PREV = 3
  179.     NEXT = 4
  180.     FIRST_CHILD = 5
  181.     LAST_CHILD = 6
  182.  
  183.     def __init__(self, document):
  184.         self.document = document
  185.         self.start = None
  186.         self.name = None
  187.         self.num_attributes = None
  188.         self.parent = None
  189.         self.prev = None
  190.         self.next = None
  191.         self.first_child = None
  192.         self.last_child = None
  193.  
  194.     def load(self, f):
  195.         self.start = f.tell()
  196.         self.name, self.num_attributes, self.parent, self.prev, self.next, self.first_child, self.last_child = struct.unpack(self.HEADER_FMT, f.read(self.SIZE))
  197.         return True
  198.  
  199.     def get_name(self):
  200.         return self.document.get_string(self.name)
  201.  
  202.     def get_attribute(self, index):
  203.         if index < 0 or index >= self.num_attributes:
  204.             return None
  205.         offset = self.start + Element.SIZE + index * Attribute.SIZE
  206.         if not is_valid_attribute(self.document, offset):
  207.             return None
  208.         attribute = Attribute(self)
  209.         f = BytesIO(self.document.tree_bin)
  210.         f.seek(offset)
  211.         attribute.load(f)
  212.         return attribute
  213.  
  214.     def get_parent(self):
  215.         if not is_valid_element(self.document, self.parent):
  216.             return None
  217.         element = Element(self.document)
  218.         f = BytesIO(self.document.tree_bin)
  219.         f.seek(parent)
  220.         element.load(f)
  221.         return element
  222.  
  223.     def get_first_child(self):
  224.         if not is_valid_element(self.document, self.first_child):
  225.             return None
  226.         element = Element(self.document)
  227.         f = BytesIO(self.document.tree_bin)
  228.         f.seek(self.first_child)
  229.         element.load(f)
  230.         return element
  231.  
  232.     def get_last_child(self):
  233.         if not is_valid_element(self.document, self.last_child):
  234.             return None
  235.         element = Element(self.document)
  236.         f = BytesIO(self.document.tree_bin)
  237.         f.seek(self.last_child)
  238.         element.load(f)
  239.         return element
  240.  
  241.     def get_prev_sibling(self):
  242.         if not is_valid_element(self.document, self.prev):
  243.             return None
  244.         element = Element(self.document)
  245.         f = BytesIO(self.document.tree_bin)
  246.         f.seek(self.prev)
  247.         element.load(f)
  248.         return element
  249.  
  250.     def get_next_sibling(self):
  251.         if not is_valid_element(self.document, self.next):
  252.             return None
  253.         element = Element(self.document)
  254.         f = BytesIO(self.document.tree_bin)
  255.         f.seek(self.next)
  256.         element.load(f)
  257.         return element
  258.  
  259.     def dump(self, f, depth):
  260.         write_indent(f, depth)
  261.         name = self.get_name()
  262.         write_raw(f, '<' + name)
  263.         for i in range(self.num_attributes):
  264.             attribute = self.get_attribute(i)
  265.             if attribute is None:
  266.                 return False
  267.             write_raw(f, ' {0}='.format(attribute.get_name()))
  268.             if attribute.type == Attribute.TYPE_NONE:
  269.                 write_raw(f, '\"null\"')
  270.             elif attribute.type == Attribute.TYPE_INT:
  271.                 write_raw(f, '\"{0}\"'.format(attribute.get_int()))
  272.             elif attribute.type == Attribute.TYPE_FLOAT:
  273.                 write_raw(f, '\"{0:3.7}\"'.format(attribute.get_float()))
  274.             elif attribute.type == Attribute.TYPE_STRING:
  275.                 write_raw(f, '\"{0}\"'.format(attribute.get_string()))
  276.             elif attribute.type == Attribute.TYPE_INT_ARRAY:
  277.                 write_raw(f, '\"')
  278.                 array = attribute.get_int_array()
  279.                 array_length = len(array)
  280.                 for j in range(array_length):
  281.                     write_raw(f, '{0}'.format(array[j]))
  282.                     if j + 1 < array_length:
  283.                         write_raw(f, ',')
  284.                 write_raw(f, '\"')
  285.             elif attribute.type == Attribute.TYPE_FLOAT_ARRAY:
  286.                 write_raw(f, '\"')
  287.                 array = attribute.get_float_array()
  288.                 array_length = len(array)
  289.                 for j in range(array_length):
  290.                     write_raw(f, '{0:3.7}'.format(array[j]))
  291.                     if j + 1 < array_length:
  292.                         write_raw(f, ',')
  293.                 write_raw(f, '\"')
  294.             elif attribute.type == Attribute.TYPE_FILE:
  295.                 file_name = '{0}_{1:08X}_{2:08X}.bin'.format(self.document.file_prefix, attribute.offset, attribute.length)
  296.                 file_data = attribute.get_file()
  297.                 with open(file_name, 'wb') as of:
  298.                     of.write(file_data)
  299.                 write_raw(f, '\"{0}\"'.format(file_name))
  300.             elif attribute.type == Attribute.TYPE_ID:
  301.                 write_raw(f, '\"{0}\"'.format(attribute.get_id()))
  302.             elif attribute.type == Attribute.TYPE_ID_REF:
  303.                 id_entity = attribute.get_id_ref()
  304.                 write_raw(f, '\"{0}\"'.format(id_entity[0]))
  305.         child_element = self.get_first_child()
  306.         if not child_element is None:
  307.             write_raw(f, '>\n')
  308.             while not child_element is None:
  309.                 child_element.dump(f, depth + 1)
  310.                 child_element = child_element.get_next_sibling()
  311.             write_indent(f, depth)
  312.             write_raw(f, '</' + name + '>\n')
  313.         else:
  314.             write_raw(f, ' />\n')
  315.  
  316. def is_valid_element(document, offset):
  317.     if offset < 0 or offset + Element.SIZE > document.tree_size:
  318.         return False
  319.     element = Element(document)
  320.     f = BytesIO(document.tree_bin)
  321.     f.seek(offset)
  322.     element.load(f)
  323.     if element.num_attributes < 0 or offset + Element.SIZE + element.num_attributes * Attribute.SIZE > document.tree_size:
  324.         return False
  325.     return True
  326.  
  327. def is_valid_attribute(document, offset):
  328.     if offset < 0 or offset + Attribute.SIZE > document.tree_size:
  329.         return False
  330.     return True
  331.  
  332. class Document(object):
  333.     HEADER_FMT = ENDIANNESS + '4siiiiiiiiiiiii8x'
  334.     HEADER_SIZE = struct.calcsize(HEADER_FMT)
  335.  
  336.     def __init__(self, file_prefix=''):
  337.         self.file_prefix = file_prefix
  338.         self.magic = None
  339.         self.version = None
  340.         self.tree_offset = None
  341.         self.tree_size = None
  342.         self.id_table_offset = None
  343.         self.id_table_size = None
  344.         self.string_table_offset = None
  345.         self.string_table_size = None
  346.         self.int_array_table_offset = None
  347.         self.int_array_table_size = None
  348.         self.float_array_table_offset = None
  349.         self.float_array_table_size = None
  350.         self.file_table_offset = None
  351.         self.file_table_size = None
  352.         self.tree_bin = None
  353.         self.id_table_bin = None
  354.         self.string_table_bin = None
  355.         self.int_array_table_bin = None
  356.         self.float_array_table_bin = None
  357.         self.file_table_bin = None
  358.         self.root = None
  359.  
  360.     def get_document_element(self):
  361.         if not is_valid_element(self, 0):
  362.             return None
  363.         element = Element(self)
  364.         f = BytesIO(self.tree_bin)
  365.         element.load(f)
  366.         return element
  367.  
  368.     def get_id_string(self, offset):
  369.         if offset < 0 or offset >= self.id_table_size:
  370.             return None
  371.         f = BytesIO(self.id_table_bin)
  372.         f.seek(offset)
  373.         entity_offset, = struct.unpack(INT_FMT, f.read(struct.calcsize(INT_FMT)))
  374.         return read_cstring(f)
  375.  
  376.     def get_string(self, offset):
  377.         if offset < 0 or offset >= self.string_table_size:
  378.             return None
  379.         f = BytesIO(self.string_table_bin)
  380.         f.seek(offset)
  381.         return read_cstring(f)
  382.  
  383.     def get_int_array(self, offset, length):
  384.         if offset < 0 or (offset + length) * struct.calcsize(INT_FMT) > self.int_array_table_size:
  385.             return None
  386.         f = BytesIO(self.int_array_table_bin)
  387.         f.seek(offset * struct.calcsize(INT_FMT))
  388.         array = []
  389.         for i in range(length):
  390.             value, = struct.unpack(INT_FMT, f.read(struct.calcsize(INT_FMT)))
  391.             array.append(value)
  392.         return array
  393.  
  394.     def get_float_array(self, offset, length):
  395.         if offset < 0 or (offset + length) * struct.calcsize(FLOAT_FMT) > self.float_array_table_size:
  396.             return None
  397.         f = BytesIO(self.float_array_table_bin)
  398.         f.seek(offset * struct.calcsize(FLOAT_FMT))
  399.         array = []
  400.         for i in range(length):
  401.             value, = struct.unpack(FLOAT_FMT, f.read(struct.calcsize(FLOAT_FMT)))
  402.             array.append(value)
  403.         return array
  404.  
  405.     def get_file(self, offset, length):
  406.         if offset < 0 or offset + length > self.file_table_size:
  407.             return None
  408.         return self.file_table_bin[offset:offset + length]
  409.  
  410.     def load(self, f):
  411.         self.magic, self.version, self.tree_offset, self.tree_size, self.id_table_offset, self.id_table_size, self.string_table_offset, self.string_table_size, self.int_array_table_offset, self.int_array_table_size, self.float_array_table_offset, self.float_array_table_size, self.file_table_offset, self.file_table_size = struct.unpack(self.HEADER_FMT, f.read(self.HEADER_SIZE))
  412.         f.seek(self.tree_offset)
  413.         self.tree_bin = f.read(self.tree_size)
  414.         f.seek(self.id_table_offset)
  415.         self.id_table_bin = f.read(self.id_table_size)
  416.         f.seek(self.string_table_offset)
  417.         self.string_table_bin = f.read(self.string_table_size)
  418.         f.seek(self.int_array_table_offset)
  419.         self.int_array_table_bin = f.read(self.int_array_table_size)
  420.         f.seek(self.float_array_table_offset)
  421.         self.float_array_table_bin = f.read(self.float_array_table_size)
  422.         f.seek(self.file_table_offset)
  423.         self.file_table_bin = f.read(self.file_table_size)
  424.         self.root = self.get_document_element()
  425.         return True
  426.  
  427.     def check(self, f):
  428.         return check_file_magic(f, 'CXML')
  429.  
  430.     def dump(self, f=sys.stdout, depth=0):
  431.         if self.root is None:
  432.             return
  433.         self.root.dump(f, depth)
  434.  
  435. if len(sys.argv) < 3:
  436.     print('error: insufficient options specified')
  437.     sys.exit()
  438.  
  439. cxml_file_path = sys.argv[1]
  440. if not os.path.isfile(cxml_file_path):
  441.     print('error: invalid cxml file specified')
  442.     sys.exit()
  443. xml_file_path = sys.argv[2]
  444. if os.path.exists(xml_file_path) and not os.path.isfile(xml_file_path):
  445.     print('error: invalid xml file specified')
  446.     sys.exit()
  447.  
  448. cxml_file_base = os.path.splitext(cxml_file_path)[0]
  449. document = Document(cxml_file_base)
  450. with open(cxml_file_path, 'rb') as f:
  451.     #if not document.check(f):
  452.     #   print 'error: invalid CXML file format'
  453.     #   sys.exit()
  454.     document.load(f)
  455.  
  456. with open(xml_file_path, 'wb') as f:
  457.     write_raw(f, '<?xml version="1.0" encoding="utf-8"?>\n')
  458.     document.dump(f)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement