#!/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 . # Ex.: ./client.py --khanat-host 172.17.0.3 -d --size-buffer-file 10241024 # 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 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 import xml.etree.ElementTree as ET class BitStream(): def __init__(self): self._pos = 0 self._read = 0 self._tampon = [] def needRead(self): return self._pos - self._read def sizeData(self): return self._pos def sizeRead(self): return self._read # ------------------------------------ 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('> 32, 32) def pushChar(self, valeur): v = ord(valeur) self.internalSerial(v, 8) def pushString(self, valeur): #size=len(valeur) #self.internalSerial(size, 32) self.pushUint32(len(valeur)) for x in valeur: self.pushChar(x) #y = ord(x) #self.internalSerial(y, 8) def pushArrayChar(self, valeur, size): ' ex.: pushArrayChar([0,1,3,4]) ' for i in valeur: self.pushChar(i) # ------------------------------------ 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(' 0: x = self.readChar() tmp += x _size -= 1 return tmp def readArrayChar(self, size): ret = [] for i in range(0, size): ret.append(self.readChar()) return ret # ------------------------------------ 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 toBytes(self): return bytes( self._tampon ) def fromBytes(self, data): self._read = 0 self._tampon = [int(x) for x in data] self._pos = len(self._tampon) * 8 def showLastData(self): ret = "" readBefore = self._read while self._read < self._pos: if self._pos - self._read >= 8: data = self.readUint8() else: data = self.readSerial(self._pos - self._read) if ret != "": ret += "." ret += hex(data) self._read = readBefore return ret def getTextMD5(dataRawXml): for data in dataRawXml: pass 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('raw:', a) print("-" * 80) 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.toBytes()) print("-" * 80) b = BitStream() b.fromBytes(a.toBytes()) print(b.readBool()) print(b.readBool()) print(b.readBool()) print(b.readBool()) print(b.readUint32()) print(b.readSint32()) print(b.readUint16()) print(b.readSint16()) print(b.readUint8()) print(b.readSint8()) print(b.readFloat()) print(b.readDouble()) print(b.readUint64()) print(b.readSint64()) print(b.readChar()) print(b.readString()) print(b.toBytes()) class CFileChild(): def __init__(self, name, pos, size): self.name = name self.pos = pos self.size = size def __str__(self): return self.name + '(pos:' + str(self.pos) + ', size:' + str(self.size) + ')' class CFileList(): def __init__(self, name, fullpath): self.name = name self.fullpath = fullpath self.child = [] def addchild(self, name, pos, size): child = CFileChild(name, pos, size) self.child.append(child) def __str__(self): return self.name + '[' + ', '.join([str(x) for x in self.child]) + ']' class CFileContainer(): def __init__(self): self.log = logging.getLogger('myLogger') self.list = [] def addSearchPath(self, path): if not path: return self.log.debug("read path:" + str(path)) onlyfiles = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))] self.log.debug("read files:" + ','.join(onlyfiles)) for filename in onlyfiles: extension = os.path.splitext(filename)[1] if extension == '.bnp': # Container for multi file fullpath = os.path.join(path, filename) size = os.path.getsize(fullpath) data = CFileList(filename, fullpath) with open(fullpath, 'rb') as fp: fp.seek(size-4) nOffsetFromBeginning = int.from_bytes(fp.read(4), byteorder='little', signed=False) self.log.debug("[%s] nOffsetFromBeginning:%u" % (filename, nOffsetFromBeginning)) fp.seek(nOffsetFromBeginning) nNbFile = int.from_bytes(fp.read(4), byteorder='little', signed=False) self.log.debug("[%s] nNbFile:%u" % (filename, nNbFile)) for i in range(0, nNbFile): nStringSize = int.from_bytes(fp.read(1), byteorder='little', signed=False) FileName = fp.read(nStringSize).decode() nFileSize2 = int.from_bytes(fp.read(4), byteorder='little', signed=False) nFilePos = int.from_bytes(fp.read(4), byteorder='little', signed=False) self.log.debug("[%s] (%d) sizestring:%d file:%s size2:%d pos:%d" % (filename, i, nStringSize, FileName, nFileSize2, nFilePos)) data.addchild(FileName, nFilePos, nFileSize2) fp.close() self.list.append(data) def search(self, name): for x in self.list: for y in x.child: if y.name == name: self.log.debug("file:%s child:%s pos:%d size:%d", x.name, y.name, y.pos, y.size) return x.fullpath, y.pos, y.size self.log.debug('-'*80) return None, None, None def getdata(self, name): fullpath, pos, size = self.search(name) self.log.debug("file:%s pos:%d size:%d", fullpath, pos, size) data = None with open(fullpath, 'rb') as fp: fp.seek(pos) data = fp.read(size) fp.close() return data 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:' + str(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 # 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.append(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, khanat_host, khanat_port_frontend, LanguageCode="fr"): self.log = logging.getLogger('myLogger') self._CurrentSendNumber = 0 self.LanguageCode = LanguageCode self._QuitId = 0 self._ConnectionState = TConnectionState.NotInitialised self.UserAddr, self.UserKey, self.UserId = None, None, None self.frontend = (khanat_host, khanat_port_frontend) self._sock = socket.socket(socket.AF_INET, # Internet socket.SOCK_DGRAM) # UDP self._CurrentReceivedNumber = 0 self._SystemMode = 0 self._LastReceivedAck = 0 self._LatestProbe = 0 self._LastReceivedNumber = 0 self._LastAckInLongAck = 0 self._MsgXmlMD5 = None self._DatabaseXmlMD5 = None 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.toBytes(), 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(self._QuitId) # _QuitId self._sock.sendto(msg.toBytes(), self.frontend) self._ConnectionState = TConnectionState.Quit def sendSystemAckSync(self): # code/ryzom/client/src/network_connection.cpp # void CNetworkConnection::sendSystemAckSync() if self._sock is None: raise ValueError msg = BitStream() self.buildSystemHeader(msg) msg.pushUint8(2) # SYSTEM_ACK_SYNC_CODE msg.pushSint32(self._LastReceivedNumber) msg.pushSint32(self._LastAckInLongAck) # msg.pushSint32(self._LongAckBitField) # # msg.pushSint32(self._LatestSync) self.log.error("TODO") def readDelta(self, msg): propertyCount = msg.readUint16() self.log.debug("propertyCount:%d" % propertyCount) for _ in range(0, propertyCount): pass def impulseCallBack(self, data): # code/ryzom/common/src/game_share/generic_xml_msg_mngr.h : CNode *select(NLMISC::CBitMemStream &strm) msg = BitStream() msg.fromBytes(data) serverTick = msg.readUint32() self.log.debug("serverTick:%d" % serverTick) #self.readDelta(msg) # khanat-opennel-code/code/ryzom/client/src/network_connection.cpp # bool CNetworkConnection::buildStream( CBitMemStream &msgin ) def buildStream(self, buffersize=65536): data, addr = self._sock.recvfrom(buffersize) return data, addr def decodeHeader(self, msg): self._CurrentReceivedNumber = msg.readSint32() self._SystemMode = msg.readBool() if self._SystemMode: return self._LastReceivedAck = msg.readSint32(); self._LastReceivedNumber = self._CurrentReceivedNumber def receiveSystemProbe(self, msg): self._LatestProbe = msg.readSint32() self.log.debug("LatestProbe: %d" % self._LatestProbe) def receiveSystemStalled(self, msg): self.log.debug("received STALLED") def receiveSystemSync(self, msg): _Synchronize = msg.readUint32() stime = msg.readSint64() _LatestSync = msg.readUint32() self.log.debug("%d %d %d" %(_Synchronize, stime, _LatestSync)) # khanat-opennel-code/code/ryzom/client/src/network_connection.cpp : void CNetworkConnection::receiveSystemSync(CBitMemStream &msgin) MsgData = msg.readArrayChar(16) DatabaseData = msg.readArrayChar(16) self.log.debug("MsgData:" + str(MsgData)) self.log.debug("DatabaseData:" + str(DatabaseData)) self.log.error("TODO") #self.sendSystemAckSync() def disconnect(self): pass def EmulateFirst(self, msgRawXml, databaseRawXml): msgXml = ET.fromstring(msgRawXml) ET.dump(msgXml) databaseXml = ET.fromstring(databaseRawXml) ET.dump(databaseXml) self._MsgXmlMD5 = getTextMD5(msgRawXml) self._DatabaseXmlMD5 = getTextMD5(databaseRawXml) self.log.info("Client Login") self.sendSystemLogin() self.log.info("Receive Message") for _ in range(0, 20): # while True: data, addr = self.buildStream() self.log.debug("received message: %s" % data) msg = BitStream() msg.fromBytes(data) self.decodeHeader(msg) # khanat-opennel-code/code/ryzom/client/src/network_connection.cpp:bool CNetworkConnection::stateSynchronize() message = msg.readUint8() self.log.debug("_CurrentReceivedNumber:%d (mode:%s) %d [%d/%d/%d]" % (self._CurrentReceivedNumber, str(self._SystemMode), message, msg.sizeData(), msg.sizeRead(), msg.needRead())) if message == 1: # SYSTEM_SYNC_CODE self.log.debug("synchronize->synchronize") self.receiveSystemSync(msg) elif message == 3: # SYSTEM_PROBE_CODE self.log.debug("synchronize->probe") self._ConnectionState = TConnectionState.Probe self.receiveSystemProbe(msg) elif message == 6: # SYSTEM_STALLED_CODE self.log.debug("received STALLED") self._ConnectionState = TConnectionState.Stalled self.receiveSystemStalled(msg) elif message == 7: # SYSTEM_SERVER_DOWN_CODE self.disconnect() self.log.warning("BACK-END DOWN") else: self.log.warning("CNET: received system %d in state Synchronize" % message) self.log.debug("_CurrentReceivedNumber:%d (mode:%s) %d [%d/%d/%d] '%s'" % (self._CurrentReceivedNumber, str(self._SystemMode), message, msg.sizeData(), msg.sizeRead(), msg.needRead(), msg.showLastData())) # self.impulseCallBack(data) self.log.info("Client Logout") self.sendSystemQuit() class ClientKhanat: def __init__(self, khanat_host, khanat_port_login = 40916, khanat_port_frontend = 47851, login="tester", password="tester", clientApp="Lirria", LanguageCode="fr", url="/login/r2_login.php", suffix = None, download_patch = False, show_patch_detail=False, size_buffer_file=1024): self.log = logging.getLogger('myLogger') if suffix is None: suffix = str(random.randrange(1, 9999)) self.log.debug("suffix : %s" % suffix) self.download_patch = download_patch self.show_patch_detail = show_patch_detail self.khanat_host = khanat_host self.khanat_port_login = khanat_port_login self.khanat_port_frontend = khanat_port_frontend 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(self.khanat_host, self.khanat_port_frontend) self.size_buffer_file = size_buffer_file self.cFileContainer = CFileContainer() def createAccount(self): conn = http.client.HTTPConnection(host=self.khanat_host, port=self.khanat_port_login) 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': self.khanat_host+':'+ str(self.khanat_port_login), 'Referer': 'http://' + self.khanat_host+':'+ str(self.khanat_port_login) + '/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("((?P.*) Error )(?P[^.]+)", 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): conn = http.client.HTTPConnection(host=self.khanat_host, port=self.khanat_port_login) 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=self.khanat_host, port=self.khanat_port_login) 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 = self.size_buffer_file # 1024 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): # TODO - check where client search file to download 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) os.remove(tmp) # khanat-opennel-code/code/ryzom/client/src/login_patch.cpp # void CCheckThread::run () FilesToPatch = [] for file in self.khanat_idx.CBNPFile: FilesToPatch.append(file) # Here we got all the files to patch in FilesToPatch and all the versions that must be obtained Now we have to get the optional categories OptionalCat = [] for category in self.khanat_idx.Categories: if category._IsOptional: for file in category._Files: bAdded = False for file2 in FilesToPatch: if file2 == file: OptionalCat.append(category._Name) bAdded = True break if bAdded: break # For all categories that required an optional category if the cat required is present the category that reference it must be present for category in self.khanat_idx.Categories: if category._IsOptional and not len(category._CatRequired) == 0: bFound = False for cat in OptionalCat: if category._Name == cat: bFound = True break if bFound: for cat in OptionalCat: if category._CatRequired == cat: OptionalCat.append(category._Name) break # Delete categories optional cat that are hidden for category in self.khanat_idx.Categories: if category._IsOptional and category._Hidden: for cat in OptionalCat: if category._Name == cat: OptionalCat.remove(category._Name) break # Get all extract to category and check files inside the bnp with real files for category in self.khanat_idx.Categories: if len(category._UnpackTo) != 0: for file in category._Files: # TODO # readHeader() pass def DownloadMinimum(self): self.log.debug("-" * 80) for file in self.khanat_idx.CBNPFile: if file.FileName != "kh_server.bnp": continue 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) os.remove(tmp) 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 if self.show_patch_detail: 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) if self.download_patch: self.downloadAllPatch() else: self.DownloadMinimum() self.cFileContainer = CFileContainer() self.cFileContainer.addSearchPath(self.tempdir.name) msgRawXml = self.cFileContainer.getdata("msg.xml").decode() databaseRawXml = self.cFileContainer.getdata("database.xml").decode() self.clientNetworkConnection.EmulateFirst(msgRawXml, databaseRawXml) 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("--khanat-host", help="khanat host to auhtenticate", default='localhost') parser.add_argument("--suffix", help="define suffix") parser.add_argument("-d", "--debug", help="show debug message", action='store_true') parser.add_argument("-p", "--download-patch", help="show debug message", action='store_true') parser.add_argument("-s", "--show-patch-detail", help="show debug message", action='store_true') parser.add_argument("--size-buffer-file", help="size buffer to download file", type=int, default=1024) parser.add_argument("--khanat-port-login", help="port http login", type=int, default=40916) parser.add_argument("--khanat-port-frontend", help="port UDP frontend", type=int, default=47851) args = parser.parse_args() if args.debug: level = logging.getLevelName('DEBUG') else: level = logging.getLevelName('INFO') log.setLevel(level) client = ClientKhanat(args.khanat_host, khanat_port_login=args.khanat_port_login, khanat_port_frontend=args.khanat_port_frontend, suffix=args.suffix, download_patch=args.download_patch, show_patch_detail=args.show_patch_detail, size_buffer_file=args.size_buffer_file) client.Emulate() log.info("End") if __name__ == "__main__": #Test() main()