diff --git a/client.py b/client.py
new file mode 100755
index 0000000..f71985e
--- /dev/null
+++ b/client.py
@@ -0,0 +1,1645 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+#
+# script to emulate client khanat
+#
+#    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 <http://www.gnu.org/licenses/>.
+
+# Ex.: ./client.py --khanaturl="172.17.0.3:40916" -d
+
+# Modifier les droits pour les nouveaux joueurs (accès à tout)
+# mysql -u root -e "use nel_ams_lib;UPDATE settings SET Value = 7 WHERE settings.Setting = 'Domain_Auto_Add';"
+
+import argparse
+import http.client
+import crypt
+import logging
+import os.path
+import sys
+import urllib.request
+import urllib.parse
+import tempfile
+from enum import IntEnum
+from ctypes import *
+import re
+import random
+import lzma
+import socket
+import struct
+
+
+class BitStream():
+    def __init__(self):
+        self._pos = 0
+        self._read = 0
+        self._tampon = []
+
+    # ------------------------------------
+    def internalSerial(self, value, nbits):
+        if nbits == 0:
+            return
+        elif nbits > 32:
+            raise "Out of range"
+        pos = self._pos % 8
+        if pos == 0:
+            self._tampon.append(0)
+        # print(">", pos, value)
+        value = c_uint32(value).value
+        if nbits != 32:
+            mask = (1 << nbits) - 1;
+            v = value & mask;
+        else:
+            v = value;
+        _FreeBits = 8 - (self._pos % 8)
+        if nbits > _FreeBits:
+            #print(">A")
+            self._tampon[-1] |= (v >> ( nbits - _FreeBits))
+            self._pos += _FreeBits
+            self.internalSerial( v , nbits - _FreeBits)
+        else:
+            #print(">B")
+            self._tampon[-1] |= (v << ( _FreeBits - nbits))
+            self._pos += nbits
+
+    def pushBool(self, valeur):
+        if valeur:
+            v = 1
+        else:
+            v = 0
+        self.internalSerial(v, 1)
+
+    def pushUint32(self, valeur):
+        self.internalSerial(valeur, 32)
+
+    def pushSint32(self, valeur):
+        self.internalSerial(valeur, 32)
+
+    def pushUint16(self, valeur):
+        self.internalSerial(valeur, 16)
+
+    def pushSint16(self, valeur):
+        self.internalSerial(valeur, 16)
+
+    def pushUint8(self, valeur):
+        self.internalSerial(valeur, 8)
+
+    def pushSint8(self, valeur):
+        self.internalSerial(valeur, 8)
+
+    def pushUint64(self, valeur):
+        self.internalSerial(valeur, 32)
+        self.internalSerial(valeur >> 32, 32)
+
+    def pushSint64(self, valeur):
+        self.internalSerial(valeur, 32)
+        self.internalSerial(valeur >> 32, 32)
+
+    def pushFloat(self, valeur):
+        v = c_float(valeur).value
+        v1 = struct.pack('f', v)
+        v2 = struct.unpack('<i',v1)[0]
+        self.internalSerial(v2, 32)
+
+    def pushDouble(self, valeur):
+        v = c_double(valeur).value
+        v1 = struct.pack('d', v)
+        v2 = struct.unpack('<Q',v1)[0]
+        #self.internalSerial(v2, 32)
+        self.internalSerial(v2, 32)
+        self.internalSerial(v2 >> 32, 32)
+
+    def pushChar(self, valeur):
+        v = ord(valeur)
+        self.internalSerial(v, 8)
+
+    def pushString(self, valeur):
+        size=len(valeur)
+        print(size)
+        #self.internalSerial(size, 32)
+        self.pushUint32(len(valeur))
+        for x in valeur:
+            self.pushChar(x)
+            #y = ord(x)
+            #self.internalSerial(y, 8)
+
+    # ------------------------------------
+    def readSerial(self, nbits):
+        if nbits == 0:
+            return
+        elif nbits > 32:
+            raise "Out of range"
+        if self._read + nbits > self._pos:
+            raise "Stream Overflow"
+        value = 0
+        pos = self._read // 8
+        _FreeBits = 8 - (self._read % 8)
+        v = self._tampon[pos] & ((1 << _FreeBits) - 1)
+        if nbits > _FreeBits:
+            value |= (v << (nbits-_FreeBits))
+            self._read += _FreeBits
+            value |= self.readSerial(nbits - _FreeBits)
+        else:
+            value |= (v >> (_FreeBits-nbits))
+            self._read += nbits
+        return value
+
+    def readBool(self):
+        v = self.readSerial(1)
+        if v != 0:
+            return True
+        else:
+            return False
+
+    def readUint32(self):
+        v = self.readSerial(32)
+        return v
+
+    def readSint32(self):
+        v = self.readSerial(32)
+        return c_int32(v).value
+
+    def readUint16(self):
+        v = self.readSerial(16)
+        return v
+
+    def readSint16(self):
+        v = self.readSerial(16)
+        return c_int16(v).value
+
+    def readUint8(self):
+        v = self.readSerial(8)
+        return v
+
+    def readSint8(self):
+        v = self.readSerial(8)
+        return c_int8(v).value
+
+    def readUint64(self):
+        v = self.readSerial(32)
+        v1 = self.readSerial(32)
+        v2 = v | (v1 << 32)
+        return v2
+
+    def readSint64(self):
+        v = self.readSerial(32)
+        v1 = self.readSerial(32)
+        v2 = v | (v1 << 32)
+        return c_int64(v2).value
+
+    def readFloat(self):
+        v = self.readSerial(32)
+        v1 = struct.pack('I', v)
+        v2 = struct.unpack('<f',v1)[0]
+        return v2
+
+    def readDouble(self):
+        v = self.readSerial(32)
+        v1 = struct.pack('I', v)
+        w = self.readSerial(32)
+        w1 = struct.pack('I', w)
+        x = v1 + w1
+        x1 = struct.unpack('<d', x)[0]
+        return x1
+
+    def readChar(self):
+        v = self.readUint8()
+        return chr(v)
+
+    def readString(self):
+        tmp = ''
+        _size = self.readUint32()
+        while _size > 0:
+            x = self.readChar()
+            tmp += x
+            _size -= 1
+        return tmp
+
+    # ------------------------------------
+    def __str__(self):
+        return ''.join([ chr(x) for x in self._tampon])
+
+    def message(self):
+        # return str(self._pos) + ':' + str(self._tampon)
+        return str(self._pos) + ':' + '.'.join([ format(x, "02x") for x in self._tampon])
+
+    def bytes(self):
+        return bytes( self._tampon )
+
+def Test():
+    a = BitStream()
+    a.pushBool(True)
+    a.pushBool(False)
+    a.pushBool(True)
+    a.pushBool(True)
+    a.pushUint32(1234567890)
+    a.pushSint32(-1234567890)
+    a.pushUint16(12345)
+    a.pushSint16(-12345)
+    a.pushUint8(123)
+    a.pushSint8(-123)
+    a.pushFloat(-3.3999999521443642e+38) #-3.4E+38) # 1.2339999675750732)
+    a.pushDouble(-1.7E+308)
+    a.pushUint64(16045690709418696365)
+    a.pushSint64(-1)
+    a.pushChar('a')
+    a.pushString("Test A Faire")
+    print("A6:", a)
+    print(a.readBool())
+    print(a.readBool())
+    print(a.readBool())
+    print(a.readBool())
+    print(a.readUint32())
+    print(a.readSint32())
+    print(a.readUint16())
+    print(a.readSint16())
+    print(a.readUint8())
+    print(a.readSint8())
+    print(a.readFloat())
+    print(a.readDouble())
+    print(a.readUint64())
+    print(a.readSint64())
+    print(a.readChar())
+    print(a.readString())
+    print(a.bytes())
+
+
+class TConnectionState(IntEnum):
+    NotInitialised = 0      # nothing happened yet
+    NotConnected = 1        # init() called
+    Authenticate = 2        # connect() called, identified by the login server
+    Login = 3               # connecting to the frontend, sending identification
+    Synchronize = 4         # connection accepted by the frontend, synchronizing
+    Connected = 5           # synchronized, connected, ready to work
+    Probe = 6               # connection lost by frontend, probing for response
+    Stalled = 7             # server is stalled
+    Disconnect = 8          # disconnect() called, or timeout, or connection closed by frontend
+    Quit = 9                # quit() called
+
+
+class Card(IntEnum):
+    BEGIN_TOKEN = 0
+    END_TOKEN = 1
+    SINT_TOKEN = 2
+    UINT_TOKEN = 3
+    FLOAT_TOKEN = 4
+    STRING_TOKEN = 5
+    FLAG_TOKEN = 6
+    EXTEND_TOKEN = 7
+
+
+
+class TType(IntEnum):
+    STRUCT_BEGIN = 0
+    STRUCT_END = 1
+    FLAG = 2
+    SINT32 = 3
+    UINT32 = 4
+    FLOAT32 = 5
+    STRING = 6
+    SINT64 = 7
+    UINT64 = 8
+    FLOAT64 = 9
+    EXTEND_TYPE = 10
+    NB_TYPE = 11
+
+
+class TExtendType:
+    ET_SHEET_ID = 0
+    ET_64_BIT_EXTENDED_TYPES = 0x80000000
+    ET_ENTITY_ID = 0x80000000 # ET_ENTITY_ID = ET_64_BIT_EXTENDED_TYPES
+
+
+class CBNPFileVersion:
+    def __init__(self):
+        self.VersionNumber = None
+        self.FileTime = None
+        self.FileSize = None
+        self.v7ZFileSize = None
+        self.PatchSize = None
+        self.HashKey = []
+
+    def __str__(self):
+        return "VersionNumber:" + str(self.VersionNumber) + ", FileTime:" + str(self.FileTime) + ", FileSize:" + str(self.FileSize) + ", 7ZFileSize:" + str(self.v7ZFileSize) + ", PatchSize:" + str(self.PatchSize) + ", HashKey:" + str(self.HashKey)
+
+
+class CBNPFile:
+    def __init__(self):
+        self.FileName = None
+        self.Versions = []
+        self.IsIncremental = False
+
+    def __str__(self):
+        return str(self.FileName) +' (' + ', '.join( [str(x) for x in self.Versions]) + ')'
+
+    def update(self, FileName):
+        self.FileName = FileName
+
+class CBNPCategorySet:
+    def __init__(self):
+        self._Name = ""
+        self._IsOptional = False
+        self._UnpackTo = ""
+        self._IsIncremental = False
+        self._CatRequired = ""
+        self._Hidden = False
+        self._Files = ""
+
+    def __str__(self):
+        return self._Name + ' (IsOptional:' + str(self._IsOptional) + ', UnpackTo:' + self._UnpackTo + ', IsIncremental:' + str(self._IsIncremental) + ', CatRequired:' + self._CatRequired + ', Hidden:' + str(self._Hidden) + ', Files:' + self._Files + ')'
+
+# #####################################################
+# persistent_data.h:140  #  struct CArg
+# #####################################################
+class CArgV1(Structure):
+    _fields_ = [("i32_1", c_uint),
+                ("i32_2", c_uint)]
+
+
+class CArgV2(Structure):
+    _fields_ = [("ex32_1", c_uint),
+                ("ex32_2", c_uint)]
+
+
+class CArgV3(Union):
+    _fields_ = [("ex32", CArgV2),
+                ("ExData32", c_uint),
+                ("ExData64", c_ulong)]
+
+
+class CArgV4(Structure):
+    _fields_ = [("ExType", c_uint),
+                ("ex", CArgV3)]
+
+
+class CArgV5(Union):
+    _fields_ = [("i", CArgV1),
+                ("ii32", c_int),
+                ("ii64", c_long),
+                ("i32", c_uint),
+                ("i64", c_ulong),
+                ("f32", c_float),
+                ("f64", c_double),
+                ("ex", CArgV4)]
+
+#       union
+#       {
+#           struct
+#           {
+#               uint32  i32_1;
+#               uint32  i32_2;
+#           } i;
+#
+#           sint32  i32;
+#           sint64  i64;
+#           float   f32;
+#           double  f64;
+#
+#           struct
+#           {
+#               uint32  ExType;
+#               union
+#               {
+#                   struct
+#                   {
+#                       uint32  ex32_1;
+#                       uint32  ex32_2;
+#                   };
+#
+#                   uint32  ExData32;
+#                   uint64  ExData64;
+#               } ex;
+#           } ex;
+#       } _Value;
+
+class CArg:
+    def __init__(self):
+        self._value = CArgV5()
+        self._value.ii64 = 0
+        self._value.i64 = 0
+        self._type = 0
+        self._string = 0
+        self._type64 = False
+
+    def read_Type(self):
+        return self._type
+
+    def write_Type(self, value):
+        self._type = value
+
+    def write_Type64(self, value):
+        self._type64 = value
+
+    def read_String(self):
+        return self._string
+
+    def write_String(self, value):
+        self._string = value
+
+    def read_i32_1(self):
+        return self._value.i.i32_1
+
+    def write_i32_1(self, value):
+        self._value.i.i32_1 = value
+
+    def read_i32_2(self):
+        return self._value.i.i32_2
+
+    def write_i32_2(self, value):
+        self._value.i.i32_2 = value
+
+    def read_i32(self):
+        return self._value.i32
+
+    def write_i32(self, value):
+        self._value.i32 = value
+
+    def read_i64(self):
+        return self._value.i64
+
+    def write_i64(self, value):
+        self._value.i64 = value
+
+    def read_f32(self):
+        return self._value.f32
+
+    def write_f32(self, value):
+        self._value.f32 = value
+
+    def read_f64(self):
+        return self._value.f64
+
+    def write_f64(self, value):
+        self._value.f64 = value
+
+    def read_ExType(self):
+        return self._value.ex.ExType
+
+    def write_ExType(self, value):
+        self._value.ex.ExType = value
+
+    def read_ex32_1(self):
+        return self._value.ex.ex.ex32.ex32_1
+
+    def write_ex32_1(self, value):
+        self._value.ex.ex.ex32.ex32_1 = value
+
+    def read_ex32_2(self):
+        return self._value.ex.ex.ex32.ex32_2
+
+    def write_ex32_2(self, value):
+        self._value.ex.ex.ex32.ex32_2 = value
+
+    def read_ExData32(self):
+        return self._value.ex.ex.ExData32
+
+    def write_ExData32(self, value):
+        self._value.ex.ex.ExData32 = value
+
+    def read_ExData64(self):
+        return self._value.ex.ex.ExData64
+
+    def write_ExData64(self, value):
+        self._value.ex.ex.ExData64 = value
+
+    def isExtended(self):
+        if self._type == TType.EXTEND_TYPE:
+            return True
+        elif self._type == TType.STRUCT_BEGIN:
+            self.log.error("Can't extract a value from a structure delimiter")
+            sys.exit(2)
+        elif self._type == TType.STRUCT_END:
+            self.log.error("Can't extract a value from a structure delimiter")
+            sys.exit(2)
+        return False
+
+    def isFlag(self):
+        if self._type == TType.FLAG:
+            return True
+        else:
+            return False
+
+    def asUint(self):
+        if self._type == TType.STRUCT_BEGIN or self._type == TType.STRUCT_END:
+            self.log.error("Can't extract a value from a structure delimiter")
+            sys.exit(2)
+        elif self._type == TType.SINT32:
+            return self.read_i32()
+        elif self._type == TType.UINT32:
+            return self.read_i32()
+        elif self._type == TType.SINT64:
+            return self.read_i64()
+        elif self._type == TType.UINT64:
+            return self.read_i64()
+        elif self._type == TType.FLOAT32:
+            return self.read_i32()
+        elif self._type == TType.FLOAT64:
+            return self.read_i64()
+        elif self._type == TType.STRING:
+            return int(self._string)
+        elif self._type == TType.FLAG:
+            return "1"
+        elif self._type == TType.EXTEND_TYPE:
+            if self.read_ExType() == TExtendType.ET_SHEET_ID:
+                return self.read_ExData32()
+            elif self.read_ExType() == TExtendType.ET_ENTITY_ID:
+                return self.read_ExData64()
+        log = logging.getLogger('myLogger')
+        log.error("This should never happen!")
+        sys.exit(2)
+
+    def __str__(self):
+        log = logging.getLogger('myLogger')
+        log.debug(self._type)
+        if self._type == TType.STRUCT_BEGIN or self._type == TType.STRUCT_END:
+            return ''
+        elif self._type64:
+            # To be confirm for extend
+            return str(self.read_ExData64())
+        elif self._type == TType.SINT32:
+            return str(self.read_i32())
+        elif self._type == TType.UINT32:
+            return str(self.read_i32())
+        elif self._type == TType.SINT64:
+            return str(self.read_i64())
+        elif self._type == TType.UINT64:
+            return str(self.read_i64())
+        elif self._type == TType.FLOAT32:
+            return str(self.read_i32())
+        elif self._type == TType.FLOAT64:
+            return str(self.read_i64())
+        elif self._type == TType.STRING:
+            return self._string
+        elif self._type == TType.FLAG:
+            return "1"
+        return '?'
+
+    def asSint(self):
+        self.log.error("TODO")
+        sys.exit(2)
+
+    def asFloat(self):
+        self.log.error("TODO")
+        sys.exit(2)
+
+    def asDouble(self):
+        self.log.error("TODO")
+        sys.exit(2)
+
+    def asString(self):
+        if self._type == TType.STRUCT_BEGIN or self._type == TType.STRUCT_END:
+            self.log.error("Can't extract a value from a structure delimiter")
+            sys.exit(2)
+        elif self._type == TType.SINT32:
+            return str(self.read_ii32())
+        elif self._type == TType.UINT32:
+            return str(self.read_i32())
+        elif self._type == TType.SINT64:
+            return str(self.read_ii64())
+        elif self._type == TType.UINT64:
+            return str(self.read_i64())
+        elif self._type == TType.FLOAT32:
+            return str(self.read_f32())
+        elif self._type == TType.FLOAT64:
+            return str(self.read_f64())
+        elif self._type == TType.STRING:
+            return self._string
+        elif self._type == TType.FLAG:
+            return "1"
+        elif self._type == TType.EXTEND_TYPE:
+            self.log.error("TODO")
+            sys.exit(2)
+            #       switch(_Value.ExType)
+            #       {
+            #       case ET_SHEET_ID:
+            #           {
+            #               NLMISC::CSheetId sheetId(_Value.ExData32);
+            #               return sheetId.toString(true);
+            #           }
+            #       case ET_ENTITY_ID:
+            #           {
+            #               NLMISC::CEntityId entityId(_Value.ExData64);
+            #               return entityId.toString();
+            #           }
+            #       default:
+            #           break;
+            #       }
+        self.log.error("This should never happen!")
+        sys.exit(2)
+
+    def asUCString(self):
+        self.log.error("TODO")
+        sys.exit(2)
+
+    def asEntityId(self):
+        self.log.error("TODO")
+        sys.exit(2)
+
+    def asSheetId(self):
+        self.log.error("TODO")
+        sys.exit(2)
+
+    def typeName(self):
+        self.log.error("TODO")
+        sys.exit(2)
+
+# #####################################################
+#
+# #####################################################
+
+class CPersistentDataRecord:
+    def __init__(self,  log):
+        self.log = log
+        self.TokenTable = []
+        self.ArgTable = []
+        self.StringTable = [ ]
+        self.ReadingStructStack = []
+        self.offsetToken = 0
+        self.ArgOffset = 0
+        self.version = 0
+        self.totalSize = 0
+        self.tokenCount = 0
+        self.argCount = 0
+        self.stringCount = 0
+        self.stringsSize = 0
+        self.CBNPFile = []
+        self.Categories = []
+
+    def show(self):
+        for x in self.CBNPFile:
+            self.log.debug("File:%s" % str(x))
+        for x in self.Categories:
+            self.log.debug("Categorie:%s" % str(x))
+
+    # ---------------- Manipulate Token ----------------
+
+    # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
+    # |  0 |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 | 13 | 14 | 15 |
+    # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
+    # |   Token ID                                                     | Token Type   |
+    # +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
+
+    def token2Type(self, token, extend): # persistent_data_inline.h:1102  CPersistentDataRecord::CArg::TType CPersistentDataRecord::CArg::token2Type(uint32 token,bool extend)
+        self.log.debug("token:%d, extend:%d" % (token, extend))
+        if token == Card.BEGIN_TOKEN:
+            return TType.STRUCT_BEGIN
+        elif token == Card.END_TOKEN:
+            return TType.STRUCT_END
+        elif token == Card.FLAG_TOKEN:
+            return TType.FLAG
+        elif token == Card.SINT_TOKEN:
+            if extend:
+                return TType.SINT64
+            else:
+                return TType.SINT32
+        elif token == Card.UINT_TOKEN:
+            if extend:
+                return TType.UINT64
+            else:
+                return TType.UINT32
+        elif token == Card.FLOAT_TOKEN:
+            if extend:
+                return TType.FLOAT64
+            else:
+                return TType.FLOAT32
+        elif token == Card.STRING_TOKEN:
+            if extend:
+                return TType.EXTEND_TYPE
+            else:
+                return TType.STRING
+        self.log.error('This should never happen!')
+        sys.exit(2)
+
+    def type2Token(self, type): # persistent_data_inline.h:1118  CPersistentDataRecord::TToken CPersistentDataRecord::CArg::type2Token(uint32 type)
+        self.log.debug("type: %d" %(type))
+        if type == TType.STRUCT_BEGIN:
+            return Card.BEGIN_TOKEN
+        elif type == TType.STRUCT_END:
+            return Card.END_TOKEN
+        elif type == TType.FLAG:
+            return Card.FLAG_TOKEN
+        elif type == TType.SINT32:
+            return Card.SINT_TOKEN
+        elif type == TType.UINT32:
+            return Card.UINT_TOKEN
+        elif type == TType.FLOAT32:
+            return Card.FLOAT_TOKEN
+        elif type == TType.STRING:
+            return Card.STRING_TOKEN
+        elif type == TType.SINT64:
+            return Card.SINT_TOKEN
+        elif type == TType.UINT64:
+            return Card.UINT_TOKEN
+        elif type == TType.FLOAT64:
+            return Card.FLOAT_TOKEN
+        elif type == TType.EXTEND_TYPE:
+            return Card.STRING_TOKEN
+        self.log.error('This should never happen!')
+        sys.exit(2)
+
+    def peekNextToken(self):
+        token = self.TokenTable[self.offsetToken]
+        self.log.debug("[%d] token:%d" %(self.offsetToken, token))
+        return token // 8 # persistent_data_inline.h:308  CPersistentDataRecord::TToken CPersistentDataRecord::peekNextToken()  # _TokenTable[_TokenOffset]>>3;
+
+    def peekNextTokenType(self): # persistent_data_limit.h:308  CPersistentDataRecord::TToken CPersistentDataRecord::peekNextToken() const
+        self.log.debug("peekNextTokenType - old offset token:%d" % self.offsetToken)
+        if self.isEndOfData():
+            self.log.error('Attempt to read past end of input data')
+            sys.exit(2)
+        token = self.TokenTable[self.offsetToken]
+        tokenType = token & 7
+        if tokenType == Card.EXTEND_TOKEN:
+            if self.offsetToken + 1 > self.tokenCount:
+                self.log.error('Attempt to read past end of input data')
+                sys.exit(2)
+            tokenType = self.TokenTable[self.offsetToken+1]
+            self.log.debug("peekNextTokenType [%d] token:%d type:%d" %(self.offsetToken, token, tokenType))
+            return self.token2Type(tokenType, True)
+        self.log.debug("peekNextTokenType [%d] token:%d type:%d" %(self.offsetToken, token, tokenType))
+        return self.token2Type(tokenType, False)
+
+    def isEndOfData(self):
+        if self.offsetToken == self.tokenCount:
+            return True
+        return False
+
+    def isEndOfStruct(self):
+        if self.isEndOfData():
+            self.log.debug("isEndOfData")
+            return True
+        elif len(self.ReadingStructStack) == 0:
+            self.log.debug("ReadingStructStack")
+            return False
+        elif self.peekNextTokenType() != TType.STRUCT_END:
+            self.log.debug("peekNextTokenType != TType.STRUCT_END")
+            return False
+        elif self.ReadingStructStack[-1] != self.peekNextToken():
+            self.log.error("Opening and closing structure tokens don't match")
+            sys.exit(2)
+        self.log.debug("isEndOfStruct")
+        return True
+
+    def isStartOfStruct(self):
+        if self.peekNextTokenType() == TType.STRUCT_BEGIN:
+            return True
+        return False
+
+    def popStructBegin(self, token):
+        if self.peekNextToken() != token:
+            self.log.error('Attempting to enter a structure with the wrong delimiting token')
+            sys.exit(2)
+        if self.peekNextTokenType() != TType.STRUCT_BEGIN:
+            self.log.error('Attempting to leave a structure with the wrong delimiting token type')
+            sys.exit(2)
+        self.ReadingStructStack.append(token)
+        self.offsetToken += 1
+
+    def popStructEnd(self, token):
+        if len(self.ReadingStructStack) == 0:
+            self.log.error('Attempting to pop end of a structure with nothing left in the open structure stack')
+            sys.exit(2)
+        nextToken = self.peekNextToken()
+        topToken = self.ReadingStructStack[-1]
+        if topToken != token:
+            self.log.error('Attempting to pop end of a structure with the wrong delimiting token')
+            sys.exit(2)
+        if nextToken != token:
+            self.log.error('Attempting to pop end of a structure with the wrong delimiting token')
+            sys.exit(2)
+        if self.peekNextTokenType() != TType.STRUCT_END:
+            self.log.error('Attempting to leave a structure with the wrong delimiting token type')
+            sys.exit(2)
+        del self.ReadingStructStack[-1]
+        self.offsetToken += 1
+
+    # ---------------- Manipulate StringTable ----------------
+    def lookupString(self, idx):
+        if idx >= self.stringCount:
+            self.log.error("Attempting to access past end of string table")
+            sys.exit(2)
+        return self.StringTable[idx]
+
+    # ---------------- Manipulate Arg ----------------
+    def peekNextArg(self): # persistent_data_limit.h:339  CPersistentDataRecord::peekNextArg(CPersistentDataRecord::CArg& result) const
+        _type = self.peekNextTokenType()
+        result = CArg()
+        result.write_Type(_type)
+        result.write_Type64(False)
+        self.log.debug("peekNextArg - Type:%d ArgOffset:%d" % (_type, self.ArgOffset))
+        if result.isExtended():
+            self.log.debug("Extended")
+            result.write_i32_1(self.ArgTable[self.ArgOffset])
+            result.write_i32_2(self.ArgTable[self.ArgOffset+1])
+            if result.read_Type() == TType.EXTEND_TYPE and result.read_ExType() == TExtendType.ET_64_BIT_EXTENDED_TYPES:
+                result.write_ex32_2(self.ArgTable[self.ArgOffset+2]);
+                result.write_Type64(True)
+        elif not result.isFlag():
+            # result._Value.i32_1 = _ArgTable[_ArgOffset];
+            result.write_i32_1(self.ArgTable[self.ArgOffset])
+            self.log.debug("peekNextArg - id :%d" % result.read_i32_1())
+        if result.read_Type() == TType.STRING:
+            result.write_String(self.lookupString(result.read_i32_1()))
+            self.log.debug("peekNextArg - String:%s" % result.read_String())
+        return result
+
+    def popNextArg(self, token): # persistent_data_limit.h:414  CPersistentDataRecord::popNextArg(TToken token,CPersistentDataRecord::CArg& result)
+        result = self.peekNextArg()
+        if result.isFlag():
+            self.offsetToken += 1
+        elif result.isExtended():
+            self.ArgOffset += 2
+            self.offsetToken += 2
+            if result.read_Type() == TType.EXTEND_TYPE and result.read_ExType() == TExtendType.ET_64_BIT_EXTENDED_TYPES:
+                self.ArgOffset += 1
+                self.offsetToken += 1
+        else:
+            self.ArgOffset += 1
+            self.offsetToken += 1
+        self.log.debug("popNextArg - Arg:%d", result.read_i32_1())
+        return result
+
+    def popString(self, token):
+        TempArg = self.popNextArg(token)
+        return TempArg.asString()
+
+    def popUint32(self, token):
+        TempArg = self.popNextArg(token)
+        return TempArg.asUint()
+
+    def popBool(self, token):
+        TempArg = self.popNextArg(token)
+        return TempArg.asUint() != 0
+
+    # ---------------- Read Data ----------------
+    def readFromBinFile(self, filename): # persistent_data.cpp:835   # bool CPersistentDataRecord::fromBuffer(const char *src, uint32 bufferSize)
+        self.log.debug('Read Bin File %s' % filename)
+        with open(filename, "rb") as fp:
+            buffer = fp.read()
+            fp.close()
+
+            self.version = int.from_bytes(buffer[0:4], byteorder='little', signed=False)
+            self.totalSize = int.from_bytes(buffer[4:8], byteorder='little', signed=False)
+            self.tokenCount = int.from_bytes(buffer[8:12], byteorder='little', signed=False)
+            self.argCount = int.from_bytes(buffer[12:16], byteorder='little', signed=False)
+            self.stringCount = int.from_bytes(buffer[16:20], byteorder='little', signed=False)
+            self.stringsSize = int.from_bytes(buffer[20:24], byteorder='little', signed=False)
+            offset = 24
+            self.log.debug("version:%d, totalSize:%d, tokenCount:%d, argCount:%d, stringCount:%d, stringsSize:%d" % (self.version, self.totalSize, self.tokenCount, self.argCount, self.stringCount, self.stringsSize))
+            if  len(buffer) != self.totalSize:
+                self.log.error("Failed to parse buffer due to invalid header (file:%s, size:%d, size define:%d)" % (filename, len(buffer), self.totalSize ))
+                sys.exit(2)
+            if self.version > 0:
+                self.log.error("PDR ERROR: Wrong file format version! (file:%s, version:%d)" % (filename, self.version))
+                sys.exit(2)
+            if (self.stringCount != 0 and self.stringsSize == 0) or (self.stringCount == 0 and self.stringsSize != 0):
+                self.log.error("PDR ERROR: Invalid string table parameters! (file:%s, stringCount:%d, stringsSize:%d)" % (filename, self.stringCount, self.stringsSize))
+                sys.exit(2)
+            # i = offset+tokenCount*sizeof(TToken)+argCount*sizeof(uint32)+stringsSize
+            i = offset + self.tokenCount * 2 + self.argCount * 4 + self.stringsSize;
+            if self.totalSize != i:
+                self.log.error("PDR ERROR: Invalid source data (file:%s, totalSize:%d != datasize:%s)" % (filename, self.totalSize, i))
+                sys.exit(2)
+
+            # READ the tokens
+            self.TokenTable = []
+            for i in range(0, self.tokenCount):
+                tmp = int.from_bytes(buffer[offset:offset+2], byteorder='little', signed=False)
+                self.log.debug("token %5d => %3d id:%3d type:%d" %(i,  tmp, tmp // 8, tmp & 7))
+                self.TokenTable.append(tmp)
+                offset += 2
+
+            # READ the arguments
+            self.ArgTable = []
+            for i in range(0, self.argCount):
+                tmp = int.from_bytes(buffer[offset:offset+4], byteorder='little', signed=False)
+                self.ArgTable.append(tmp)
+                offset += 4
+            print(self.ArgTable)
+
+            # READ the string table data
+            if self.stringsSize != 0:
+                chaine = ''
+                self.StringTable = [ ]
+                while offset < self.totalSize:
+                    car = buffer[offset:offset+1].decode()
+                    if car != '\0':
+                        chaine += car
+                    else:
+                        self.StringTable.append(chaine)
+                        chaine = ''
+                    offset += 1
+                self.log.debug(self.StringTable)
+                if chaine != '':
+                    self.log.error("PDR ERROR: Too few strings found in string table (file:%s)" % (filename))
+                    sys.exit(2)
+            self.log.debug("Red %s" % filename)
+
+    def decrypt_token(self):
+        i = 0
+        lvl = 0
+        posArg = 0
+        extend = False
+        extend64 = False
+        result = CArg()
+
+        print("^  Position  ^  Token  ^")
+        for value in self.TokenTable:
+            print("|  %5d |  %3d |" %(i, value))
+            i += 1
+
+        i = 0
+        print("^  Position  ^  Argument  ^")
+        for value in self.ArgTable:
+            print("|  %5d |  %3d |" %(i, value))
+            i += 1
+
+        i = 0
+        print("^  Position  ^  String  ^")
+        for value in self.StringTable:
+            print("|  %5d |  %s |" %(i, value))
+            i += 1
+
+        i = 0
+        print("^  Position  ^  Niveau  ^  Token  ^  Token ID  ^^  Token Type (Card)  ^^^ Result ^")
+        print("^ ^^ (entrée) ^ Valeur ^ Quoi ^ Valeur ^  Card ^  Type ^ ^")
+        for token in self.TokenTable:
+            tokenId = token // 8
+            tokenTypeValue = token & 7
+            result.write_String("-")
+            if tokenTypeValue == 0:
+                tokenCard = 'BEGIN_TOKEN'
+                tokenType =  'STRUCT_BEGIN'
+                result.write_Type(TType.STRUCT_BEGIN)
+                if lvl <= 1:
+                    print("| |||||||")
+                lvl += 1
+            elif tokenTypeValue == 1:
+                tokenCard = 'END_TOKEN'
+                tokenType =  'STRUCT_END'
+                result.write_Type(TType.STRUCT_END)
+                extend = False
+                extend64 = False
+            elif tokenTypeValue == 2:
+                tokenCard = 'SINT_TOKEN'
+                if extend:
+                    tokenType =  'SINT64'
+                    result.write_Type(TType.SINT64)
+                    result.write_i32_1(self.ArgTable[posArg])
+                    result.write_i32_2(self.ArgTable[posArg+1])
+                    if extend64:
+                        result.write_ex32_2(self.ArgTable[posArg+2]);
+                        posArg += 3
+                    else:
+                        posArg += 2
+                else:
+                    tokenType =  'SINT32'
+                    result.write_Type(TType.SINT32)
+                    result.write_i32_1(self.ArgTable[posArg])
+                    posArg += 1
+                extend = False
+                extend64 = False
+            elif tokenTypeValue == 3:
+                tokenCard = 'UINT_TOKEN'
+                if extend:
+                    tokenType =  'UINT64'
+                    result.write_Type(TType.UINT64)
+                    result.write_i32_1(self.ArgTable[posArg])
+                    result.write_i32_2(self.ArgTable[posArg+1])
+                    if extend64:
+                        result.write_ex32_2(self.ArgTable[posArg+2]);
+                        posArg += 3
+                    else:
+                        posArg += 2
+                else:
+                    tokenType =  'UINT32'
+                    result.write_Type(TType.UINT32)
+                    result.write_i32_1(self.ArgTable[posArg])
+                    posArg += 1
+                extend = False
+                extend64 = False
+            elif tokenTypeValue == 4:
+                tokenCard = 'FLOAT_TOKEN'
+                if extend:
+                    tokenType =  'FLOAT64'
+                    result.write_Type(TType.FLOAT64)
+                    result.write_i32_1(self.ArgTable[posArg])
+                    result.write_i32_2(self.ArgTable[posArg+1])
+                    if extend64:
+                        result.write_ex32_2(self.ArgTable[posArg+2]);
+                        posArg += 3
+                    else:
+                        posArg += 2
+                else:
+                    tokenType =  'FLOAT32'
+                    result.write_Type(TType.FLOAT32)
+                    result.write_i32_1(self.ArgTable[posArg])
+                    posArg += 1
+                extend = False
+                extend64 = False
+            elif tokenTypeValue == 5:
+                tokenCard = 'STRING_TOKEN'
+                if extend:
+                    tokenType =  'EXTEND_TYPE'
+                    result.write_Type(TType.EXTEND_TYPE)
+                    result.write_i32_1(self.ArgTable[posArg])
+                    result.write_i32_2(self.ArgTable[posArg+1])
+                    if extend64:
+                        result.write_ex32_2(self.ArgTable[posArg+2]);
+                        posArg += 3
+                    else:
+                        posArg += 2
+                else:
+                    tokenType =  'STRING'
+                    result.write_Type(TType.STRING)
+                    result.write_i32_1(self.ArgTable[posArg])
+                    tmp = result.read_i32_1()
+                    result.write_String(self.StringTable[tmp])
+                    posArg += 1
+                extend = False
+                extend64 = False
+            elif tokenType == 6:
+                tokenCard = 'FLAG_TOKEN'
+                tokenType =  'FLAG'
+                result.write_Type(TType.FLAG)
+                extend = False
+                extend64 = False
+            elif tokenTypeValue == 7:
+                if extend:
+                    extend64 = True
+                tokenCard = 'EXTEND_TOKEN'
+                result.write_Type(TType.EXTEND_TYPE)
+                tokenType =  ''
+                extend = True
+            # print("token %5d => %3d id:%3d [%s] type:%d [%s]" %(i, token, tokenId, self.StringTable[tokenId], tokenType, tokenCard))
+            print("|  %5d |  %3d |  %3d |  %3d | %s |  %d | %s | %s | %s |" %(i, lvl,  token, tokenId, self.StringTable[tokenId], tokenTypeValue, tokenCard , tokenType, result))
+            if tokenTypeValue == 1:
+                lvl -= 1
+            i += 1
+
+    def addString(self, name): # persistent_data.cpp:100  uint16 CPersistentDataRecord::addString(const string& name)
+        for i in range(0, len(self.StringTable)):
+            if self.StringTable[i] == name:
+                return i
+        self.StringTable.append(name)
+        return len(self.StringTable) - 1
+
+    def CProductDescriptionForClient_apply(self): # persistent_data_template.h:459  # void PERSISTENT_CLASS::apply(CPersistentDataRecord &pdr _PERSISTENT_APPLY_ARGS)
+        __Tok__MapKey = self.addString("__Key__")
+        __Tok__MapVal = self.addString("__Val__")
+        __Tok_Files = self.addString("_Files")
+        __Tok_Categories = self.addString("_Categories")
+        self.log.debug("MapKey:%d, MapVal:%d, Files:%d, Categories:%d" %(__Tok__MapKey, __Tok__MapVal, __Tok_Files, __Tok_Categories))
+        while not self.isEndOfStruct():
+            nextToken = self.peekNextToken()
+            self.log.debug("nextToken:%d" % (nextToken))
+            if nextToken == __Tok_Files:
+                self.popStructBegin(__Tok_Files)
+                self.CBNPFileSet_apply()
+                self.popStructEnd(__Tok_Files)
+                continue
+            elif nextToken == __Tok_Categories:
+                self.popStructBegin(__Tok_Categories)
+                # (_Categories).apply(pdr);
+                self.CBNPCategorySet_apply()
+                self.popStructEnd(__Tok_Categories)
+                continue
+            self.log.error("TODO")
+            sys.exit(2)
+
+    def CBNPFileSet_apply(self):
+        __Tok__MapKey = self.addString("__Key__")
+        __Tok__MapVal = self.addString("__Val__")
+        __Tok_Files = self.addString("_Files")
+        self.log.debug("MapKey:%d, MapVal:%d, Files:%d" %(__Tok__MapKey, __Tok__MapVal, __Tok_Files))
+        while not self.isEndOfStruct():
+            nextToken = self.peekNextToken()
+            self.log.debug("nextToken:%d" % (nextToken))
+            if nextToken == __Tok_Files:
+                self.popStructBegin(__Tok_Files)
+                self.CBNPFile.append(CBNPFile())
+                self.CBNPFile_apply(self.CBNPFile[-1])
+                self.popStructEnd(__Tok_Files)
+                continue
+            self.log.error("TODO")
+            sys.exit(2)
+
+    def CBNPFile_apply(self, _CBNPFile):
+        __Tok__MapKey = self.addString("__Key__")
+        __Tok__MapVal = self.addString("__Val__")
+        __Tok_FileName = self.addString("_FileName")
+        __Tok_Versions = self.addString("_Versions")
+        _FileName = None
+        self.log.debug("MapKey:%d, MapVal:%d, Filename:%d, Versions:%d" %(__Tok__MapKey, __Tok__MapVal, __Tok_FileName, __Tok_Versions))
+        while not self.isEndOfStruct():
+            nextToken = self.peekNextToken()
+            self.log.debug("nextToken:%d" % (nextToken))
+            if nextToken == __Tok_FileName:
+                _FileName = self.popString(nextToken)
+                _CBNPFile.FileName = _FileName
+                self.log.debug("filename: %s" % _FileName)
+                continue
+            if nextToken == __Tok_Versions:
+                self.popStructBegin(__Tok_Versions)
+                # vectAppend(_Versions).apply(pdr);
+                _CBNPFile.Versions.append(CBNPFileVersion())
+                self.CBNPFileVersion_apply(_CBNPFile.Versions[-1])
+                self.popStructEnd(__Tok_Versions)
+                continue
+            stack = []
+            while True:
+                if self.isStartOfStruct():
+                    stack.append(self.peekNextToken())
+                    self.popStructBegin(stack)
+                elif self.isEndOfStruct():
+                    self.popStructEnd(stack[-1])
+                    if len(stack) > 0:
+                        del stack[-1]
+                else:
+                    self.popNextArg(self.peekNextToken())
+                if self.isEndOfData() and len(stack) == 0:
+                    break
+        self.log.debug("CBNPFile: %s" % _CBNPFile)
+
+    def CBNPFileVersion_apply(self, _CBNPFileVersion): # persistent_data_template.h:459 # void CBNPFileVersion::apply(CPersistentDataRecord &pdr )
+        __Tok__MapKey = self.addString("__Key__")
+        __Tok__MapVal = self.addString("__Val__")
+        __Tok_VersionNumber = self.addString("_VersionNumber")
+        __Tok_FileSize = self.addString("_FileSize")
+        __Tok_7ZFileSize = self.addString("_7ZFileSize")
+        __Tok_FileTime = self.addString("_FileTime")
+        __Tok_PatchSize = self.addString("_PatchSize")
+        __Tok_HashKey = self.addString("_HashKey")
+        self.log.debug("MapKey:%d, MapVal:%d, VersionNumber:%d, FileSize:%d, 7ZFileSize:%d, FileTime:%d, PatchSize:%d, HashKey:%d" %(__Tok__MapKey, __Tok__MapVal, __Tok_VersionNumber, __Tok_FileSize, __Tok_7ZFileSize, __Tok_FileTime, __Tok_PatchSize, __Tok_HashKey))
+
+        while not self.isEndOfStruct():
+            nextToken = self.peekNextToken()
+            self.log.debug("nextToken:%d" % (nextToken))
+            if nextToken == __Tok_VersionNumber:
+                self.log.debug("__Tok_VersionNumber")
+                _CBNPFileVersion.VersionNumber = self.popUint32(__Tok_VersionNumber)
+                self.log.debug("VersionNumber: %s" % _CBNPFileVersion.VersionNumber)
+                continue
+            elif nextToken == __Tok_FileSize:
+                self.log.debug("__Tok_FileSize")
+                _CBNPFileVersion.FileSize = self.popUint32(__Tok_FileSize)
+                self.log.debug("FileSize: %s" % _CBNPFileVersion.FileSize)
+                continue
+            elif nextToken == __Tok_7ZFileSize:
+                self.log.debug("__Tok_7ZFileSize")
+                _CBNPFileVersion.v7ZFileSize = self.popUint32(__Tok_7ZFileSize)
+                self.log.debug("7ZFileSize: %s" % _CBNPFileVersion.v7ZFileSize)
+                continue
+            elif nextToken == __Tok_FileTime:
+                self.log.debug("__Tok_FileTime")
+                _CBNPFileVersion.FileTime = self.popUint32(__Tok_FileTime)
+                self.log.debug("FileTime: %s" % _CBNPFileVersion.FileTime)
+                continue
+            elif nextToken == __Tok_PatchSize:
+                self.log.debug("__Tok_PatchSize")
+                _CBNPFileVersion.PatchSize = self.popUint32(__Tok_PatchSize)
+                self.log.debug("PatchSize: %s" % _CBNPFileVersion.PatchSize)
+                continue
+            elif nextToken == __Tok_HashKey:
+                self.log.debug("__Tok_HashKey")
+                _CBNPFileVersion.HashKey.append(self.popUint32(__Tok_HashKey))
+                self.log.debug("HashKey: %s" % _CBNPFileVersion.HashKey[-1])
+                continue
+            # Vidage des autres clefs (inconnues)
+            stack = []
+            while True:
+                if self.isStartOfStruct():
+                    stack.append(self.peekNextToken())
+                    self.popStructBegin(stack)
+                elif self.isEndOfStruct():
+                    self.popStructEnd(stack[-1])
+                    if len(stack) > 0:
+                        del stack[-1]
+                else:
+                    self.popNextArg(self.peekNextToken())
+                if self.isEndOfData() and len(stack) == 0:
+                    break
+
+    def CBNPCategorySet_apply(self): # persistent_data_template.h:459 # void CBNPCategorySet::apply(CPersistentDataRecord &pdr )
+        #__Tok__MapKey = self.addString("__Key__")
+        #__Tok__MapVal = self.addString("__Val__")
+        __Tok_Category = self.addString("_Category")
+        while not self.isEndOfStruct():
+            nextToken = self.peekNextToken()
+            self.log.debug("nextToken:%d" % (nextToken))
+            if nextToken == __Tok_Category:
+                self.log.debug("__Tok_Category")
+                self.popStructBegin(__Tok_Category)
+                self.Categories.append(CBNPCategorySet())
+                self.CBNPCategory_apply(self.Categories[-1])
+                self.popStructEnd(__Tok_Category)
+                continue
+            # Vidage des autres clefs (inconnues)
+            stack = []
+            while True:
+                if self.isStartOfStruct():
+                    stack.append(self.peekNextToken())
+                    self.popStructBegin(stack)
+                elif self.isEndOfStruct():
+                    self.popStructEnd(stack[-1])
+                    if len(stack) > 0:
+                        del stack[-1]
+                else:
+                    self.popNextArg(self.peekNextToken())
+                if self.isEndOfData() and len(stack) == 0:
+                    break
+
+
+    def CBNPCategory_apply(self, _CBNPCategory): # persistent_data_template.h:459 # void CBNPCategory::apply(CPersistentDataRecord &pdr )
+        __Tok__MapKey = self.addString("__Key__")
+        __Tok__MapVal = self.addString("__Val__")
+        __Tok_Name = self.addString("_Name")
+        __Tok_IsOptional = self.addString("_IsOptional")
+        __Tok_UnpackTo = self.addString("_UnpackTo")
+        __Tok_IsIncremental = self.addString("_IsIncremental")
+        __Tok_CatRequired = self.addString("_CatRequired")
+        __Tok_Hidden = self.addString("_Hidden")
+        __Tok_Files = self.addString("_Files")
+        self.log.debug("MapKey:%d, MapVal:%d, Name:%d, IsOptional:%d, UnpackTo:%d, IsIncremental:%d, CatRequired:%d, Hidden:%d, Files:%d" %(__Tok__MapKey, __Tok__MapVal, __Tok_Name, __Tok_IsOptional, __Tok_UnpackTo, __Tok_IsIncremental, __Tok_CatRequired, __Tok_Hidden, __Tok_Files))
+        while not self.isEndOfStruct():
+            nextToken = self.peekNextToken()
+            self.log.debug("nextToken:%d" % (nextToken))
+            if nextToken == __Tok_Name:
+                self.log.debug("__Tok_Name")
+                _CBNPCategory._Name = self.popString(nextToken)
+                self.log.debug("_Name: %s" % _CBNPCategory._Name)
+                continue
+            elif nextToken == __Tok_IsOptional:
+                self.log.debug("__Tok_IsOptional")
+                _CBNPCategory._IsOptional = self.popBool(nextToken)
+                self.log.debug("_IsOptional: %s" % str(_CBNPCategory._IsOptional))
+                continue
+            elif nextToken == __Tok_UnpackTo:
+                self.log.debug("__Tok_UnpackTo")
+                _CBNPCategory._UnpackTo = self.popString(nextToken)
+                self.log.debug("_UnpackTo: %s" % str(_CBNPCategory._UnpackTo))
+                continue
+            elif nextToken == __Tok_IsIncremental:
+                self.log.debug("__Tok_IsIncremental")
+                _CBNPCategory._IsIncremental = self.popBool(nextToken)
+                self.log.debug("_IsIncremental: %s" % str(_CBNPCategory._IsIncremental))
+                continue
+            elif nextToken == __Tok_CatRequired:
+                self.log.debug("__Tok_CatRequired")
+                _CBNPCategory._CatRequired = self.popString(nextToken)
+                self.log.debug("_CatRequired: %s" % str(_CBNPCategory._CatRequired))
+                continue
+            elif nextToken == __Tok_Hidden:
+                self.log.debug("__Tok_Hidden")
+                _CBNPCategory._Hidden = self.popBool(nextToken)
+                self.log.debug("_Hidden: %s" % str(_CBNPCategory._Hidden))
+                continue
+            elif nextToken == __Tok_Files:
+                self.log.debug("__Tok_Files")
+                _CBNPCategory._Files = self.popString(nextToken)
+                self.log.debug("_Files: %s" % str(_CBNPCategory._Files))
+                continue
+            # Vidage des autres clefs (inconnues)
+            stack = []
+            while True:
+                if self.isStartOfStruct():
+                    stack.append(self.peekNextToken())
+                    self.popStructBegin(stack)
+                elif self.isEndOfStruct():
+                    self.popStructEnd(stack[-1])
+                    if len(stack) > 0:
+                        del stack[-1]
+                else:
+                    self.popNextArg(self.peekNextToken())
+                if self.isEndOfData() and len(stack) == 0:
+                    break
+
+
+class ClientNetworkConnection:
+    def __init__(self,
+                 khanaturl,
+                 LanguageCode="fr"):
+        self.log = logging.getLogger('myLogger')
+        self._CurrentSendNumber = 0
+        self.LanguageCode = LanguageCode
+        self._QuitId = 0
+        self._ConnectionState = TConnectionState
+        self.UserAddr, self.UserKey, self.UserId = None, None, None
+        self.khanaturl = khanaturl
+        _khanaturl =  self.khanaturl.strip('"').strip("'")
+        try:
+            host, port = _khanaturl.split(':')
+        except:
+            host = _khanaturl
+            port = 47851
+            (1024).to_bytes(2, byteorder='big')
+        self.frontend = (host, port)
+        self._sock = socket.socket(socket.AF_INET, # Internet
+                            socket.SOCK_DGRAM) # UDP
+
+    def cookiesInit(self, UserAddr, UserKey, UserId):
+        self.UserAddr = UserAddr
+        self.UserKey = UserKey
+        self.UserId = UserId
+
+    def reset(self):
+        self._CurrentSendNumber += 0
+
+    def buildSystemHeader(self, msg): # code/ryzom/client/src/network_connection.cpp # void CNetworkConnection::buildSystemHeader(NLMISC::CBitMemStream &msgout)
+        msg.pushSint32(self._CurrentSendNumber)
+        msg.pushBool(True) # systemMode
+
+    def sendSystemLogin(self): # code/ryzom/client/src/network_connection.cpp # void    CNetworkConnection::sendSystemLogin()
+        if self._sock is None:
+            raise ValueError
+        msg = BitStream()
+        self.buildSystemHeader(msg)
+        msg.pushUint8(0) # SYSTEM_LOGIN_CODE
+        msg.pushUint32(self.UserAddr)
+        msg.pushUint32(self.UserKey)
+        msg.pushUint32(self.UserId)
+        msg.pushString(self.LanguageCode)
+
+        self._sock.sendto(msg.bytes(), self.frontend)
+        self._CurrentSendNumber += 1
+
+        self._ConnectionState = TConnectionState.Login
+
+    def sendSystemQuit(self): # code/ryzom/client/src/network_connection.cpp # void CNetworkConnection::sendSystemQuit()
+        # Disconnect
+        if self._sock is None:
+            raise ValueError
+        self._QuitId += 1
+        msg = BitStream()
+        self.buildSystemHeader(msg)
+        msg.pushUint8(8) # SYSTEM_LOGIN_CODE
+        msg.pushSint32(_QuitId) # _QuitId
+        self._sock.sendto(msg.bytes(), self.frontend)
+        self._ConnectionState = TConnectionState.Quit
+
+    def EmulateFirst(self):
+        self.sendSystemLogin()
+
+        while True:
+            data, addr = self._sock.recvfrom(1024) # buffer size is 1024 bytes
+            print( "received message:", data)
+        self.sendSystemQuit()
+
+
+class ClientKhanat:
+    def __init__(self,
+                 khanaturl,
+                 login="tester",
+                 password="tester",
+                 clientApp="Lirria",
+                 LanguageCode="fr",
+                 url="/login/r2_login.php",
+                 suffix = None):
+        self.log = logging.getLogger('myLogger')
+
+        if suffix is None:
+            suffix = str(random.randrange(1, 9999))
+            self.log.debug("suffix : %s" % suffix)
+        self.khanaturl = khanaturl
+        self.login = login + suffix
+        self.password = password
+        self.clientApp = clientApp
+        self.LanguageCode = LanguageCode
+        self.url = url
+        self.cookie, self.fsaddr, self.ringmainurl, self.fartp, self.stat, self.r2serverversion, self.r2backuppatchurl, self.r2patchurl = None, None, None, None, None, None, None, None
+        self.tempdir = tempfile.TemporaryDirectory(".khanat")
+        self.log.debug("Temporary directory:%s" % self.tempdir)
+        self.khanat_idx = CPersistentDataRecord(self.log)
+        self.UserAddr, self.UserKey, self.UserId = None, None, None
+        self.clientNetworkConnection = ClientNetworkConnection(khanaturl)
+
+    def createAccount(self):
+        khanaturl =  self.khanaturl.strip('"').strip("'")
+        try:
+            host, port = khanaturl.split(':')
+        except:
+            host = khanaturl
+            port = 40916
+
+        conn = http.client.HTTPConnection(host=host, port=port)
+        cmd = "/ams/index.php?page=register"
+        headers = {'Accept':  'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+                            'Accept-Language' : 'en-US',
+                            'Connection': 'keep-alive',
+                            'DNT': '1',
+                            'Cookie': 'PHPSESSID=lsoumn9f0ljgm3vo3hgjdead03',
+                            'Host': khanaturl+':'+ str(port),
+                            'Referer': 'http://' + khanaturl+':'+ str(port) + '/ams/index.php?page=register',
+                            'Upgrade-Insecure-Requests': '1',
+                            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/6.0',
+                            'Content-Type': 'application/x-www-form-urlencoded'}
+
+        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+        params = urllib.parse.urlencode({'Username': self.login, 'Password': self.password, 'ConfirmPass': self.password,  'Email': self.login+'@khaganat.net',  'TaC': 'on',  'function': 'add_user'})
+        conn.request("POST", cmd,  params, headers)
+        response = conn.getresponse()
+        if ( int(response.status) == 302 ):
+            conn.close()
+            self.log.info("Account : %s" % self.login)
+            return
+        elif ( int(response.status) != 200 ):
+            self.log.error("Impossible to create account (return code:" + str(response.status) + ")")
+            sys.exit(2)
+        ret = response.read()
+        conn.close()
+
+        ret2 = ret.decode()
+        try:
+            state, comment = ret2.split(":", 2)
+        except:
+            state = 1
+            comment = ""
+        if int(state) != 1:
+            self.log.error("Impossible to create account (state:" + state + ", comment:" + comment.strip() + ")")
+            sys.exit(2)
+
+        errordetected = False
+        for line in ret2.split('\n'):
+            m = re.search("(<strong>(?P<type>.*) Error</strong> )(?P<comment>[^.]+)",  line)
+            if m:
+                if m.group('comment') == 'Username ' + self.login + ' is in use':
+                    continue
+                if m.group('comment') == 'Email is in use':
+                    continue
+                self.log.error('Impossible to create account: field:%s (%s)' % (m.group('type'),  m.group('comment')))
+                errordetected = True
+        if errordetected:
+            sys.exit(2)
+        self.log.info("Reuse account : %s" % self.login)
+
+    def connectR2(self):
+        khanaturl =  self.khanaturl.strip('"').strip("'")
+        try:
+            host, port = khanaturl.split(':')
+        except:
+            host = khanaturl
+            port = 40916
+
+        conn = http.client.HTTPConnection(host=host, port=port)
+        cmd = self.url + "?cmd=ask&cp=2&login=" + self.login + "&lg=" + self.LanguageCode
+        conn.request("GET", cmd)
+        response = conn.getresponse()
+        if ( int(response.status) != 200 ):
+            self.log.error("Impossible to get salt (return code:" + str(response.status) + ")")
+            sys.exit(2)
+        ret = response.read()
+        conn.close()
+
+        try:
+            state, salt = ret.decode().split(":", 1)
+        except UnicodeDecodeError:
+            try:
+                state, salt = ret.decode(encoding='cp1252').split(":", 1)
+            except UnicodeDecodeError:
+                self.log.error("Impossible to read output login")
+                sys.exit(2)
+        if int(state) != 1:
+            self.log.error("Impossible to get salt (state:" + state + ")")
+
+        cryptedPassword = crypt.crypt(self.password, salt)
+
+        conn = http.client.HTTPConnection(host=host, port=port)
+        cmd = self.url + "?cmd=login&login=" + self.login + "&password=" + cryptedPassword + "&clientApplication=" + self.clientApp + "&cp=2" + "&lg=" + self.LanguageCode
+        conn.request("GET", cmd)
+        response = conn.getresponse()
+        self.log.debug("%s %s" %(response.status,  response.reason))
+        ret = response.read()
+        self.log.debug(ret)
+        try:
+            line = ret.decode().split('\n')
+        except UnicodeDecodeError:
+            try:
+                line = ret.decode(encoding='cp1252').split('\n')
+            except UnicodeDecodeError:
+                self.log.error("Impossible to read output login")
+                sys.exit(2)
+
+        self.log.debug(line[0])
+        self.log.debug("line 0 '%s'" % line[0])
+        self.log.debug("line 1 '%s'" % line[1])
+        try:
+            state, self.cookie, self.fsaddr, self.ringmainurl, self.fartp, self.stat = line[0].split("#", 6)
+        except:
+            try:
+                state, self.cookie, self.fsaddr, self.ringmainurl, self.fartp = line[0].split("#", 5)
+                self.stat = 0
+            except:
+                state, error = line[0].split(":", 1)
+
+        if int(state) != 1:
+            self.log.error(error)
+            sys.exit(2)
+
+        self.r2serverversion, self.r2backuppatchurl, self.r2patchurl = line[1].split("#")
+        self.log.debug("%s %s %s %s %s %s %s %s %s" % (state, self.cookie, self.fsaddr, self.ringmainurl, self.fartp, self.stat, self.r2serverversion, self.r2backuppatchurl, self.r2patchurl))
+        self.UserAddr, self.UserKey, self.UserId = [ int(x,  16) for x in self.cookie.split('|') ]
+
+        conn.close()
+        self.log.info("Login Ok")
+        self.clientNetworkConnection.cookiesInit(self.UserAddr, self.UserKey, self.UserId)
+
+    def downloadFileUrl(self, source, dest):
+        self.log.info("Download %s (destination:%s)" % (source, dest))
+        with urllib.request.urlopen(source) as conn :
+            header = conn.getheaders()
+            file_size = 0
+            for key, value in header:
+                if key == 'Content-Length':
+                    file_size = int(value)
+                    break
+            self.log.debug("size:%d", file_size)
+            file_size_dl = 0
+            block_size = 1024  # 8192
+
+            with open(dest, 'wb') as fp:
+                while True:
+                    buffer = conn.read(block_size)
+                    if not buffer:
+                        break
+                    file_size_dl += len(buffer)
+                    fp.write(buffer)
+                    self.log.debug("Download %s %10d [%6.2f%%]" % (source, file_size_dl, file_size_dl * 100. / file_size))
+                fp.close()
+            self.log.debug("Downloaded %s (%d)" % (source, file_size))
+
+    def getServerFile(self, name, bZipped = False, specifyDestName = None):
+        srcName = name
+        if specifyDestName:
+            dstName = specifyDestName
+        else:
+            dstName = os.path.basename(name)
+        if bZipped:
+            srcName += ".ngz"
+            dstName += ".ngz"
+        self.log.info("Download %s (destination:%s, zip:%d)" % (srcName, dstName, bZipped))
+        dstName = os.path.join(self.tempdir.name, dstName)
+        self.downloadFileUrl( 'http://' + self.r2patchurl + '/' + srcName, dstName)
+        return dstName
+
+    def downloadAllPatch(self):
+        for file in self.khanat_idx.CBNPFile:
+            tmp = self.getServerFile("%05d/%s.lzma" % (int(self.r2serverversion), file.FileName), False, "")
+            with lzma.open(tmp) as fin:
+                dstName = os.path.join(self.tempdir.name, file.FileName)
+                with open(dstName, "wb") as fout:
+                    data = fin.read()
+                    fout.write(data)
+                self.log.info("%s" % dstName)
+
+    def Emulate(self):
+        self.createAccount()
+        self.connectR2()
+        # download patch
+        self.ryzomidx = self.getServerFile("%05d/ryzom_%05d.idx" % (int(self.r2serverversion), int(self.r2serverversion)), False, "")
+        self.khanat_idx.readFromBinFile(self.ryzomidx)
+        self.khanat_idx.CProductDescriptionForClient_apply()
+        # Show detail patch
+        self.khanat_idx.decrypt_token()
+        self.khanat_idx.show()
+        # Todo analyze patch and download if necessary or update if incremental - see category
+        # Download all file in patch - login_patch.cpp:2578 # void CPatchThread::processFile (CPatchManager::SFileToPatch &rFTP)
+        #self.downloadAllPatch()
+        self.clientNetworkConnection.EmulateFirst()
+
+
+def main():
+    FORMAT = '%(asctime)-15s %(levelname)s %(filename)s:%(lineno)d %(message)s'
+    logging.basicConfig(format=FORMAT)
+    log = logging.getLogger('myLogger')
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--khanaturl", help="khanat URL to auhtenticate",  default='localhost')
+    parser.add_argument("--suffix", help="define suffix")
+    parser.add_argument("-d", "--debug", help="show debug message", action='store_true')
+    args = parser.parse_args()
+
+    if args.debug:
+        level = logging.getLevelName('DEBUG')
+    else:
+        level = logging.getLevelName('INFO')
+    log.setLevel(level)
+
+    client = ClientKhanat(args.khanaturl,  suffix=args.suffix)
+    client.Emulate()
+    log.info("End")
+
+if __name__ == "__main__":
+    main()
+    #Test()