diff --git a/tools/CPropertyDecoder.py b/tools/CPropertyDecoder.py
new file mode 100644
index 0000000..32a2caf
--- /dev/null
+++ b/tools/CPropertyDecoder.py
@@ -0,0 +1,101 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+#
+# module BitStream
+#
+# Copyright (C) 2019 AleaJactaEst
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+#import logging
+#from ctypes import *
+#import sys
+#import inspect
+#import copy
+#import struct
+
+LOGGER='CPropertyDecoder'
+
+class CEntityEntry():
+ def __init__(self):
+ self.Sheet = 0
+ self.AssociationBits = 0
+ self.EntryUsed = False
+ self.PosIsRelative = False
+ self.PosIsInterior = False
+
+class CPropertyDecoder():
+ def __init__(self):
+ # khanat-opennel-code/code/ryzom/client/src/network_connection.cpp:2937 _PropertyDecoder.init (256);
+
+ for i in range(0, 256):
+ self.Entities[i] = CEntityEntry()
+ self._RefPosX = 0
+ self._RefPosY = 0
+ self._RefBitsX = 0
+ self._RefBitsY = 0
+ self._RefBitsZ = 0
+
+ def GetAssociationBits(self, slot):
+ return self.Entities[slot].AssociationBits
+
+ def clear(self):
+ for i in range(0, 256):
+ self.Entities[i] = CEntityEntry()
+
+ def associationBitsHaveChanged(self, slot, _associationBits):
+ res = _associationBits != self.GetAssociationBits(slot)
+ self.Entities[slot].AssociationBits = _associationBits
+ return res
+
+ def addEntity(self, entity, sheet):
+ self.Entities[entity].EntryUsed = True
+ self.Entities[entity].Sheet = sheet
+
+ def removeEntity(self, entity):
+ if self.Entities[entity].EntryUsed:
+ self.Entities[entity].EntryUsed = False
+ self.Entities[entity].Sheet = 0xffff
+
+ def isUsed(self, slot):
+ return self.Entities[slot].EntryUsed
+
+ def getSheet(self, slot):
+ return self.Entities[slot].Sheet
+
+ def decodeAbsPos2D(self, x16, y16):
+ x = self._RefPosX + (x16 - self._RefBitsX) << 4
+ y = self._RefPosY + (y16 - self._RefBitsY) << 4
+
+ return (x, y)
+
+ def receive(self, actionPosition):
+ # if (action->Code == ACTION_POSITION_CODE) # normally is always this type
+ if actionPosition.IsRelative:
+ # Relative position (entity in a ferry...)
+ actionPosition.Position[0] = actionPosition.Position16[0]
+ actionPosition.Position[1] = actionPosition.Position16[1]
+ actionPosition.Position[2] = actionPosition.Position16[2]
+ self.Entities[actionPosition.Slot].PosIsRelative = True
+ self.Entities[actionPosition.Slot].PosIsInterior = False
+ else:
+ # Absolute position
+ # actionPosition.Position[0] , actionPosition.Position[1] = self.decodeAbsPos2D(actionPosition.Position16[0], actionPosition.Position16[1])
+ actionPosition.Position[0] = self._RefPosX + (actionPosition.Position16[0] - self._RefBitsX) << 4
+ actionPosition.Position[1] = self._RefPosY + (actionPosition.Position16[1] - self._RefBitsY) << 4
+ actionPosition.Position[2] = actionPosition.Position16[2] << 4
+ if actionPosition.Interior:
+ actionPosition.Position[2] += 2
+ self.Entities[actionPosition.Slot].PosIsRelative = False
+ self.Entities[actionPosition.Slot].PosIsInterior = actionPosition.Interior
diff --git a/tools/DecodeDatabase.py b/tools/DecodeDatabase.py
new file mode 100644
index 0000000..6152371
--- /dev/null
+++ b/tools/DecodeDatabase.py
@@ -0,0 +1,139 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+#
+# module DecodeDatabase
+#
+# Copyright (C) 2019 AleaJactaEst
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import logging
+from tools import getPowerOf2
+
+LOGGER='DecodeDatabase'
+
+def show_dico(dico, level=1):
+ for ele in dico:
+ if isinstance(dico[ele], dict):
+ print("." * level, ele , ":")
+ if isinstance(dico[ele], dict):
+ show_dico(dico[ele], level+1)
+ else:
+ print("." * level, ele, ':', dico[ele])
+
+def child(ele):
+ ret = {}
+ ret_branch = {}
+ ref_other = {}
+ id_branch = 0
+ id_other = 0
+ min_i = -1
+ max_i = -1
+ for k in ele.keys():
+ ret[k] = ele.get(k)
+ print(k, ele.get(k))
+ for _child in list(ele):
+ x = child(_child)
+ if x['name'] == 'branch':
+ ret_branch.setdefault(id_branch, x)
+ id_branch += 1
+ else:
+ ref_other.setdefault(id_other, x)
+ id_other += 1
+ if ret_branch or ref_other:
+ ret['child'] = {}
+ id = 0
+ for x in ret_branch:
+ min_i = max_i + 1
+ max_i = min_i
+ #show_dico( ret_branch[x])
+ if 'count' in ret_branch[x]:
+ max_i = min_i + int(ret_branch[x]['count']) - 1
+ ret_branch[x]['min'] = min_i
+ ret_branch[x]['max'] = max_i
+ ret['child'].setdefault(id, ret_branch[x])
+ id += 1
+ for x in ref_other:
+ min_i = max_i + 1
+ max_i = min_i
+ #show_dico( ref_other[x])
+ if 'count' in ref_other[x]:
+ max_i = min_i + int(ref_other[x]['count']) - 1
+ ref_other[x]['min'] = min_i
+ ref_other[x]['max'] = max_i
+ ret['child'].setdefault(id, ref_other[x])
+ id += 1
+ return ret
+
+def count_elements(head):
+ try:
+ return head.items()[-1]['max'] + 1
+ except TypeError:
+ return len(head)
+
+def get_element(head, id):
+ print("id:", id)
+ for ele in head:
+ if id <= head[ele]['max'] and id >= head[ele]['min']:
+ return head[ele]
+ return None
+
+class DecodeDatabase():
+ def __init__(self):
+ self.databaseXml = None
+ self.databasePlr = None
+
+ def loadDatabase(self, databaseXml):
+ logging.getLogger(LOGGER).debug("loadDatabase")
+ self.databaseXml = databaseXml
+ id = 0
+ self.databasePlr = {}
+ for ele in self.databaseXml:
+ if ele.tag == 'branch':
+ if ele.get('bank') == "PLR":
+ self.databasePlr[id] = child(ele)
+ self.databasePlr[id]['min'] = id
+ self.databasePlr[id]['max'] = id
+ id += 1
+ print(dir(ele))
+ print("-" * 80)
+ show_dico(self.databasePlr)
+ print("-" * 80)
+ #raise "Decode"
+
+ def execute(self, msgin, world):
+ logging.getLogger(LOGGER).debug("execute")
+ head = self.databasePlr
+ listpath = []
+ while True:
+ logging.getLogger(LOGGER).debug("count_elements:" + str(count_elements(head)))
+ nbBit = getPowerOf2.getPowerOf2(count_elements(head))
+ logging.getLogger(LOGGER).debug("nbBit:" + str(nbBit))
+ id = msgin.readSerial(nbBit, name='DatabaseXML', typeName='Number', emulate=True)
+
+ logging.getLogger(LOGGER).debug("XML DECODE : %3d -> %s" % (nbBit, ':'.join(listpath)) )
+ ele = get_element(head, id)
+ print(ele)
+ show_dico(ele)
+ name = ele['name']
+ listpath.append(name)
+ fullname = ':'.join(listpath)
+ logging.getLogger(LOGGER).debug(fullname)
+ if 'type' in ele:
+ print("+"*80)
+ return True
+ head = ele
+ print("-"*80)
+ return False
+