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()