Newer
Older
Import / projects / Gameloft / bne_lib / tools / GameloftTools / rki.py
import struct
import re
import tempfile
from collections import OrderedDict
from collections import namedtuple
import os
import shutil

def enum(name, values):
  enums = dict(zip(values, range(len(values))))
  enums['values'] = values
  return type(name, (), enums)

TypeId = enum('TypeId', [
  'RKSERIAL_TYPE_INTEGER',
  'RKSERIAL_TYPE_BOOLEAN',
  'RKSERIAL_TYPE_STRING',
  'RKSERIAL_TYPE_FLOAT',
  'RKSERIAL_TYPE_VECTOR4',
  'RKSERIAL_TYPE_MATRIX44',
  'RKSERIAL_TYPE_POINTER',
  'RKSERIAL_TYPE_ARRAY',
  'RKSERIAL_TYPE_VECTOR2',
  'RKSERIAL_TYPE_CUSTOMDATA',
  'RKSERIAL_TYPE_STRINGPAIR'
])

RKSERIAL_RKAT_ANIMATION   = 0
RKSERIAL_RKAT_FONT        = 1
RKSERIAL_RKAT_LOCALIZATION = 2
RKSERIAL_RKAT_MODEL       = 3
RKSERIAL_RKAT_SCRIPT      = 4
RKSERIAL_RKAT_SHADER      = 5
RKSERIAL_RKAT_SOUND       = 6
RKSERIAL_RKAT_SWF         = 7
RKSERIAL_RKAT_TEXTURE     = 8
RKSERIAL_RKAT_VIDEO       = 9
RKSERIAL_RKAT_XML         = 10
RKSERIAL_RKAT_ANIM_EVENT  = 11
RKSERIAL_RKAT_GLOBAL_EVENT = 12
RKSERIAL_RKAT_CSHARP      = 13
RKSERIAL_RKAT_FBX         = 14
RKSERIAL_RKAT_JSON        = 15


RKSERIAL_FLAG_GLOBAL_LINK = 1<<1
RKSERIAL_FLAG_OBJECT_LINK = 1<<2
RKSERIAL_FLAG_ASSET_LINK  = 1<<3
RKSERIAL_FLAG_TEMPLATE    = 1<<10
RKSERIAL_FLAG_RENDERABLE  = 1<<27

def _objectLink(flags):
  return ((flags & RKSERIAL_FLAG_TEMPLATE) != 0
      or (flags & RKSERIAL_FLAG_GLOBAL_LINK) != 0
      or (flags & RKSERIAL_FLAG_OBJECT_LINK) != 0)

def _assetLink(flags):
  return ((flags & RKSERIAL_FLAG_ASSET_LINK) and (flags >> 32) != RKSERIAL_RKAT_SOUND)

def _assetType(flags):
  if (flags & RKSERIAL_FLAG_ASSET_LINK) == 0: return ''
  t = flags >> 32
  if t == RKSERIAL_RKAT_ANIMATION:    return 'Animations'
  if t == RKSERIAL_RKAT_FONT:         return 'Fonts'
  if t == RKSERIAL_RKAT_LOCALIZATION: return 'Localization'
  if t == RKSERIAL_RKAT_MODEL:        return 'Models'
  if t == RKSERIAL_RKAT_SCRIPT:       return 'Scripts'
  if t == RKSERIAL_RKAT_SHADER:       return 'Shaders'
  if t == RKSERIAL_RKAT_SOUND:        return 'Sounds'
  if t == RKSERIAL_RKAT_SWF:          return 'Swf'
  if t == RKSERIAL_RKAT_TEXTURE:      return 'Textures'
  if t == RKSERIAL_RKAT_VIDEO:        return 'Video'
  if t == RKSERIAL_RKAT_XML:          return 'Xml'
  if t == RKSERIAL_RKAT_ANIM_EVENT:   return 'AnimationEvents'
  if t == RKSERIAL_RKAT_GLOBAL_EVENT: return 'GlobalEvents'
  if t == RKSERIAL_RKAT_CSHARP:       return 'CSharp'
  if t == RKSERIAL_RKAT_FBX:          return 'Fbx'
  if t == RKSERIAL_RKAT_JSON:         return 'Json'
  return 'Assets'

def _extractStringFromOffset(data, offset):
  for i in range(offset, len(data)):
    if data[i] == chr(0):
      return data[offset:i]
  return data[offset:]

def _setStringInData(value, data, header, offsetData):
  memberSize, typeId, flags = struct.unpack('<IIQ', header)
  valueStringOffset, memberNameOffset, editorNameOffset, editorDescrOffset, editorCategoryOffset, classNameOffset = struct.unpack('<HHHHHH', offsetData)
  oldValue = _extractStringFromOffset(data, valueStringOffset)
  dataLength = len(oldValue)
  sizeDifference = len(value) - dataLength

  if memberNameOffset > valueStringOffset: memberNameOffset += sizeDifference
  if editorNameOffset > valueStringOffset: editorNameOffset += sizeDifference
  if editorDescrOffset > valueStringOffset: editorDescrOffset += sizeDifference
  if editorCategoryOffset > valueStringOffset: editorCategoryOffset += sizeDifference
  if classNameOffset > valueStringOffset: classNameOffset += sizeDifference
  memberSize += sizeDifference

  extraData = data[dataLength:]
  data = value + extraData
    
  endData = max(valueStringOffset, memberNameOffset, editorNameOffset, editorDescrOffset, editorCategoryOffset, classNameOffset)
  endData += len(_extractStringFromOffset(data, endData)) + 1
  headerSize = len(header)
  offsetSize = len(offsetData)
  chunkSize = headerSize + offsetSize + endData
  paddedSize = ((chunkSize - 1) / 8 + 1) * 8
  paddedDifference = paddedSize - (len(data) + headerSize + offsetSize)
  memberSize += paddedDifference
  if paddedDifference > 0:
    for i in range (0, paddedDifference):
      data += '\0'
  elif paddedDifference < 0:
    data = data[:paddedDifference]
      
  header = struct.pack('<IIQ', memberSize, typeId, flags)
  offsetData = struct.pack('<HHHHHH', valueStringOffset, memberNameOffset, editorNameOffset, editorDescrOffset, editorCategoryOffset, classNameOffset)

  return data, header, offsetData


class EOFException(Exception):
  pass

class PointerRef():
  def __init__(self, ref):
    self.ref = ref

  def __str__(self):
    return 'Pointer("%s")' % self.ref
    
class AssetRef():
  def __init__(self, ref, kind):
    self.ref = ref
    self.kind = kind

  def __str__(self):
    return '%s("%s")' % (self.kind, self.ref)

class StringPair():
  def __init__(self, arg1, arg2 = None):
    if arg2 == None:
      self.first = _extractStringFromOffset(arg1, 0)
      self.second = _extractStringFromOffset(arg1, len(self.first)+1)
    else:
      self.first = arg1
      self.second = arg2

  def __str__(self):
    return '"%s" : "%s"' % (self.first, self.second)

def _changeChunkValue(f, value, header, offsetData, data):
  memberSize, typeId, flags = struct.unpack('<IIQ', header)
  valueStringOffset, memberNameOffset, editorNameOffset, editorDescrOffset, editorCategoryOffset, classNameOffset = struct.unpack('<HHHHHH', offsetData)

  if   typeId == TypeId.RKSERIAL_TYPE_INTEGER:
    data = struct.pack('<i', value) + data[4:]
  elif typeId == TypeId.RKSERIAL_TYPE_BOOLEAN:
    data = struct.pack('<?', value) + data[1:]
  elif typeId == TypeId.RKSERIAL_TYPE_FLOAT:
    data = struct.pack('<f', value) + data[4:]
  elif typeId == TypeId.RKSERIAL_TYPE_VECTOR4:
    dataValue = struct.pack('<ffff', value[0], value[1], value[2], value[3])
    data = dataValue + data[16:]
  elif typeId == TypeId.RKSERIAL_TYPE_MATRIX44:
    dataValue = struct.pack('<ffffffffffffffff', data, valueStringOffset, value[0], value[1], value[2], value[3],
                value[4], value[5], value[6], value[7],
                value[8], value[9], value[10], value[11],
                value[12], value[13], value[14], value[15])
    data = dataValue + data[64:]
  elif typeId == TypeId.RKSERIAL_TYPE_STRING:
     (data, header, offsetData) =_setStringInData(value, data, header, offsetData)
  elif typeId == TypeId.RKSERIAL_TYPE_POINTER:
     (data, header, offsetData) =_setStringInData(value.ref, data, header, offsetData)
  elif typeId == TypeId.RKSERIAL_TYPE_STRINGPAIR:
     (data, header, offsetData) =_setStringInData(value.first + '\0' + value.second, data, header, offsetData)
  else:
    assert False, 'This data type is not implemented yet: typeId == %s, file == %s' % (typeId, f)

  _writeChunkInternal(f, header, offsetData, data)

def _writeChunkInternal(f, header, offsetData, data):
  f.write(header)
  f.write(offsetData)
  f.write(data)


def _parseChunkInternal(f):

  (header, offsetData, data) = _parseChunkRaw(f)
  
  memberSize, typeId, flags = struct.unpack('<IIQ', header)
  valueStringOffset, memberNameOffset, editorNameOffset, editorDescrOffset, editorCategoryOffset, classNameOffset = struct.unpack('<HHHHHH', offsetData)

  memberName = _extractStringFromOffset(data, memberNameOffset)
  className = _extractStringFromOffset(data, classNameOffset)

  if   typeId == TypeId.RKSERIAL_TYPE_INTEGER:
    value = struct.unpack('<i', data[:4])[0]
  elif typeId == TypeId.RKSERIAL_TYPE_BOOLEAN:
    value = data[0] != chr(0)
  elif typeId == TypeId.RKSERIAL_TYPE_STRING:
    if _objectLink(flags):
      value = PointerRef(_extractStringFromOffset(data, 0))
    elif _assetLink(flags):
      value = AssetRef(_extractStringFromOffset(data, 0), _assetType(flags)) 
    else:
      value = _extractStringFromOffset(data, 0)
  elif typeId == TypeId.RKSERIAL_TYPE_FLOAT:
    value = struct.unpack('<f', data[:4])[0]
  elif typeId == TypeId.RKSERIAL_TYPE_VECTOR4:
    value = struct.unpack('<ffff', data[:4*4])
  elif typeId == TypeId.RKSERIAL_TYPE_MATRIX44:
    value = struct.unpack('<ffffffffffffffff', data[:4*16])
  elif typeId == TypeId.RKSERIAL_TYPE_POINTER:
    value = PointerRef(_extractStringFromOffset(data, 0))
  elif typeId == TypeId.RKSERIAL_TYPE_STRINGPAIR:
    value = StringPair(data)
  else:
    assert False, 'This data type is not implemented yet: typeId == %s, file == %s' % (typeId, f)

  # print 'class:%s, member:%s, value:%s, flags:%x, type:%s' % (className, memberName, value, flags, TypeId.values[typeId])

  return className, memberName, value, flags

def _parseChunkRaw(f):
  headerSize = 4 + 4 + 8
  offsetsSize = 2 * 6
  
  header = f.read(headerSize)
  offsetData = f.read(offsetsSize)
  if len(header) == 0: # eof happened
    raise EOFException()
  
  memberSize, typeId, flags = struct.unpack('<IIQ', header)
  remainingSize = memberSize - headerSize - offsetsSize
  data = f.read(remainingSize)

  return header, offsetData, data

def _parseChunk(f):
  className, memberName, value, flags = _parseChunkInternal(f)
  return className, memberName, value

def _quoteString(s):
  if s.__class__ == str:
    return "'%s'" % s
  else:
    return str(s)

class SerializableDef(object):
  def __init__(self):
    self.typeName = None
    self.attrs = OrderedDict()

  def __str__(self):
    return '%s {%s}' % (self.typeName, self.attrs)

castRE  = re.compile(r'\([^)]*\)(.*)$')
arrayRE = re.compile(r'([^\[]*)\[([^\]]*)\]')

def _extractArrayIdx(name):
  arrayMatch = arrayRE.match(name)
  if arrayMatch:
    return arrayMatch.group(1), arrayMatch.group(2)
  else:
    return name, None

def _addMember(o, className, memberName, value):
  # strip casts like (int&)member
  
  castMatch = castRE.match(memberName)
  if castMatch:
    memberName = castMatch.group(1)

  if className == 'RegionConditional' or className == 'RegionEnd' : return

  #print className, memberName, value
  memberParts = memberName.split('.')
  if len(memberParts) == 1:
    if o.typeName == None:
      o.typeName = className

    baseName, baseIdx = _extractArrayIdx(memberParts[0])
    if baseIdx != None:
      if not baseName in o.attrs or type(o.attrs[baseName]) == int: # arrays have it's size stored under array's name
        o.attrs[baseName] = OrderedDict()
      else:
        assert o.attrs[baseName].__class__ == OrderedDict
      o.attrs[baseName][baseIdx] = value
    else:
      o.attrs[memberName] = value
  else:
    subName = '.'.join(memberParts[1:])
    baseName, baseIdx = _extractArrayIdx(memberParts[0])
    if baseIdx != None:
      if not baseName in o.attrs or type(o.attrs[baseName]) == int: # arrays have it's size stored under array's name
        o.attrs[baseName] = OrderedDict()
      else:
        assert o.attrs[baseName].__class__ == OrderedDict

      if not baseIdx in o.attrs[baseName]:
        o.attrs[baseName][baseIdx] = SerializableDef()
      else:
        assert o.attrs[baseName][baseIdx].__class__ == SerializableDef

      _addMember(o.attrs[baseName][baseIdx], className, subName, value)
    else:
      if not baseName in o.attrs:
        o.attrs[baseName] = SerializableDef()
      else:
        assert o.attrs[baseName].__class__ == SerializableDef
      _addMember(o.attrs[baseName], className, subName, value)

def _convertDefIntoInstance(o):
  if o.__class__ == SerializableDef:
    Type = namedtuple(o.typeName or 'UnknownType', o.attrs.keys())

    convertedAttrs = OrderedDict()
    for k, v in o.attrs.items():
      convertedAttrs[k] = _convertDefIntoInstance(v)

    return Type(**convertedAttrs)

  elif o.__class__ == OrderedDict:
    try:
      res = []
      for i in range(len(o)):
        assert str(i) in o, "Invalid dictionary, doesn't containt %s key: %s" % (i, o)
        value = _convertDefIntoInstance(o[str(i)])
        res.append(value)
      return res
    except AssertionError:
      return o

  else:
    return o

def GetReferencedTemplates(f):
  if isinstance(f, basestring):
    #print 'Parsing %s' % f
    f = open(f, 'rb')

  templates = []
  try:
    while True:
      className, memberName, value, valueFlags = _parseChunkInternal(f)
      if value and ((valueFlags & RKSERIAL_FLAG_TEMPLATE) != 0 or memberName == 'm_RKObjectParentName'):
        templates.append(value)
  except EOFException:
    pass
  except AssertionError as e:
    print e

  return templates

def GetValuesMatchingFlag(f, flags):
  if isinstance(f, basestring):
    #print 'Parsing %s' % f
    f = open(f, 'rb')

  values = []
  try:
    while True:
      className, memberName, value, valueFlags = _parseChunkInternal(f)
      if value and (valueFlags & flags) != 0:
        values.append(value)
  except EOFException:
    pass
  except AssertionError as e:
    print e

  return values

# accepts either stream or string
def parseObject(f):
  if isinstance(f, basestring):
    #print 'Parsing %s' % f
    f = open(f, 'rb')

  o = SerializableDef()

  try:
    while True:
      className, memberName, value = _parseChunk(f)
      #print className, memberName, value
      _addMember(o, className, memberName, value)
  except EOFException:
    pass
  
  return _convertDefIntoInstance(o)
  
def parseObjectFriendlyName(f):
	friendlyName = f
	if isinstance(f, basestring):
		#print 'Parsing %s' % f
		f = open(f, 'rb')
		
	o = SerializableDef()
	
	try:
		className, memberName, friendlyName = _parseChunk(f)
		#print className, memberName, friendlyName
	except EOFException:
		print 'Failed to parse friendly name for rki %s' % friendlyName
		pass
		
	return friendlyName

def getFieldFromObject(f, field):
  if isinstance(f, basestring):
    f = open(f, 'rb')

  try:
    while True:
      className, memberName, value = _parseChunk(f)
      if memberName == field:
        return value
  except EOFException:
    pass

  return None

def setFieldInObject(f, field, value):
  friendlyName = f
  fOutLocation = f + ".temp"
  fOut = fOutLocation

  if isinstance(f, basestring):
    f = open(f, 'rb')
    fOut = open(fOut, 'wb')

  o = SerializableDef()

  try:
    while True:
      header, offsetData, data = _parseChunkRaw(f)
    
      valueStringOffset, memberNameOffset, editorNameOffset, editorDescrOffset, editorCategoryOffset, classNameOffset = struct.unpack('<HHHHHH', offsetData)

      memberName = _extractStringFromOffset(data, memberNameOffset)
      castIndex = memberName.rfind(')')
      if castIndex != -1: memberName = memberName[castIndex + 1:]
      if memberName == field:
        memberSize, typeId, flags = struct.unpack('<IIQ', header)
        
        convertedValue = 0
        if typeId == TypeId.RKSERIAL_TYPE_INTEGER:
          convertedValue = int(value[0])
        elif typeId == TypeId.RKSERIAL_TYPE_BOOLEAN:
          convertedValue = (value[0].lower() == 'true')
        elif typeId == TypeId.RKSERIAL_TYPE_FLOAT:
          convertedValue = float(value[0])
        elif typeId == TypeId.RKSERIAL_TYPE_VECTOR4:
          convertedValue = [float(value[0]), float(value[1]), float(value[2]), float(value[3])]
        elif typeId == TypeId.RKSERIAL_TYPE_MATRIX44:
          convertedValue = [float(value[0]), float(value[1]), float(value[2]), float(value[3]),
            float(value[4]), float(value[5]), float(value[6]), float(value[7]),
            float(value[8]), float(value[9]), float(value[10]), float(value[11]),
            float(value[12]), float(value[13]), float(value[14]), float(value[15])]
        elif typeId == TypeId.RKSERIAL_TYPE_STRING:
          if _objectLink(flags):
            convertedValue = PointerRef(value[0])
          elif _assetLink(flags):
            convertedValue = AssetRef(value[0], _assetType(flags)) 
          else:
            convertedValue = value[0]
        elif typeId == TypeId.RKSERIAL_TYPE_POINTER:
          convertedValue = PointerRef(value[0])
        elif typeId == TypeId.RKSERIAL_TYPE_STRINGPAIR:
          convertedValue = StringPair(value[0], value[1])
        else:
          assert False, 'This data type is not implemented yet: typeId == %s, file == %s' % (typeId, f)

        _changeChunkValue(fOut, convertedValue, header, offsetData, data)
      else:
        _writeChunkInternal(fOut, header, offsetData, data)
  except EOFException:
    pass

  f.close()
  fOut.close()
  shutil.copyfile(fOutLocation, friendlyName)  
  os.remove(fOutLocation)

def _tabLines(lines):
  return ['\t' + x for x in lines]

def _formatObjectLines(obj):
  if isinstance(obj, basestring):
    return ['"%s"' % obj]

  if isinstance(obj, list):
    resLines = ['[']
    for item in obj:
      resLines += _tabLines(_formatObjectLines(item))
    resLines.append(']')
    return resLines

  if isinstance(obj, dict):
    resLines = ['{']
    for key, value in obj.items():
      valueLines = _formatObjectLines(value)
      resLines.append('\t%s = %s' % (key, valueLines[0]))
      resLines += _tabLines(valueLines[1:])
    resLines.append('}')
    return resLines

  if not isinstance(obj, tuple) or not hasattr(obj, '__dict__'):
    return [str(obj).replace(',', ',\r\n')]

  resLines = ['%s {' % type(obj).__name__]
  for attrName, attrValue in obj.__dict__.items():
    attrLines = _formatObjectLines(attrValue)
    resLines.append('\t%s = %s' % (attrName, attrLines[0]))
    resLines += _tabLines(attrLines[1:])
  resLines.append('}')

  return resLines

def formatObject(obj):
  return '\n'.join(_formatObjectLines(obj))

def diffObjects(obj1, obj2, diffCmd):
  f1h, f1Path = tempfile.mkstemp('_%s' % type(obj1).__name__)
  with os.fdopen(f1h, 'wb') as f1:
    f1.write(formatObject(obj1))

  f2h, f2Path = tempfile.mkstemp('_%s' % type(obj2).__name__)
  with os.fdopen(f2h, 'wb') as f2:
    f2.write(formatObject(obj2))

  os.system('"%s" %s %s' % (diffCmd, f1Path, f2Path))

if __name__ == '__main__':
  import sys

  if len(sys.argv[1:]) == 1:
    obj = parseObject(sys.argv[1])
    print formatObject(obj)
  elif len(sys.argv[1:]) == 2:
    obj1 = parseObject(sys.argv[1])
    obj2 = parseObject(sys.argv[2])
    diffObjects(obj1, obj2, 'diff')
  elif len(sys.argv[1:]) == 3:
    obj1 = parseObject(sys.argv[1])
    obj2 = parseObject(sys.argv[2])
    diffCmd = sys.argv[3]
    diffObjects(obj1, obj2, diffCmd)
  else:
    print 'Invalid argument count, expected 1, 2 or 3 args'
    sys.exit(1)