Tweaked some procedural code and re-worked client opcode reader to standardize hex format (i.e., {0x0abc, 0x0ABC, 0xabc} = 0x0abc)

This commit is contained in:
Uleat 2014-08-05 16:34:57 -04:00
parent 32243aa383
commit 9561f841c4

View File

@ -1,12 +1,12 @@
#! /usr/bin/env python #! /usr/bin/env python
# untested on Linux
# This script generates cross-references to show associated (handled) opcodes between the server and client.
# It will generate files for each client found and provide some basic information..such as opcode names and
# values, server handler and whether opcodes are translated on tx/rx.
# #
# It's currently limited to the 'Zone' server..but, can be expounded upon to include other servers, clients # This script generates cross-references to show associated (handled) opcodes
# and other criteria and features. # between the server and client. It will generate files for each client found
# and provide some basic information..such as opcode names and values, server
# handler and whether opcodes are translated on tx/rx.
#
# It's currently limited to the 'Zone' server..but, can be expounded upon to
# include other servers, clients and other criteria and features.
import os import os
@ -15,14 +15,16 @@ import os
from time import time, ctime from time import time, ctime
VERBOSE = True # pipes relativistic information to the console window # pipes relativistic information to the console window
DEBUG = False # setting this to 'True' will pipe more information to the console window than you would ever want! VERBOSE = True
repopath = os.getcwd() # setting this to 'True' will pipe more information than you would ever want!
repopath = repopath[:-14] # '\utils\scripts' - because I'm lazy and don't want to do it the right way... DEBUG = False
clients = [ '6.2', 'Titanium', 'SoF', 'SoD', 'Underfoot', 'RoF' ] repopath = os.getcwd()[:-14] # '/utils/scripts'
servers = [ 'Login', 'World', 'Zone' ]
clients = ['6.2', 'Titanium', 'SoF', 'SoD', 'Underfoot', 'RoF']
servers = ['Login', 'World', 'Zone', 'UCS']
clientops = {} clientops = {}
serverops = {} serverops = {}
@ -34,440 +36,590 @@ handlers = {}
outstreams = {} outstreams = {}
def main() : def main():
print('Loading source data...') print('Loading source data...')
if not LoadServerOpcodes() : if not loadserveropcodes():
print('Error! Could not load server opcodes') print('Error! Could not load server opcodes')
return return
if not LoadClientOpcodes() : if not loadclientopcodes():
print('Error! Could not load client opcodes') print('Error! Could not load client opcodes')
return return
if not LoadClientTranslators() : if not loadclienttranslators():
print('Error! Could not load client translators') print('Error! Could not load client translators')
return return
if not LoadServerHandlers() : if not loadserverhandlers():
print('Error! Could not load server handlers') print('Error! Could not load server handlers')
return return
print('Creating output streams...') print('Creating output streams...')
if not CreateOutputDirectory() : if not createoutputdirectory():
print('Output directory already exists or could not be created...') print('Output directory could not be created...')
#return # existing directory returns a failure..don't exit here..CreateOutputStreams() will catch makdir() problems return
if not CreateOutputStreams() : if not createoutputstreams():
print('Error! Could not open output files') print('Error! Could not open output files')
return return
print('Parsing opcode data...') print('Parsing opcode data...')
if not ParseOpcodeData() : if not parseopcodedata():
print('Error! Could not parse opcode data') print('Error! Could not parse opcode data')
return return
print('Destroying output streams...') print('Destroying output streams...')
if not DestroyOutputStreams() : if not destroyoutputstreams():
print('Error! Could not close output files') print('Error! Could not close output files')
return return
def LoadServerOpcodes() : def loadserveropcodes():
# Server opcodes are auto-enumerated with a starting reference of '1' # Server opcodes are auto-enumerated with a starting reference of '1'
try : try:
filename = '{0}{1}{2}'.format(repopath, '/common', '/emu_oplist.h') value = 0
if VERBOSE or DEBUG : print(filename) filename = '{0}{1}{2}'.format(
repopath,
'/common',
'/emu_oplist.h')
with open(filename, 'r') as datafile : if VERBOSE or DEBUG:
value = 0 print(filename)
serverops['Null'] = value
while True :
begin = 0
end = 0
with open(filename, 'r') as datafile:
while True:
dataline = datafile.readline() dataline = datafile.readline()
if not dataline : break if not dataline:
if not dataline[:1] == 'N' : continue break
while not dataline[begin:(begin + 1)] == '(' : vbegin = dataline.find('OP_', 2)
if begin >= len(dataline) : break vend = dataline.find(')', vbegin)
else : begin = begin + 1
end = begin + 1 if vbegin == -1:
continue
if vend == -1:
continue
while not dataline[(end - 1):end] == ')' : value += 1
if end >= len(dataline) : break
else : end = end + 1
begin = begin + 1 # adjust out the '(' if dataline[:1] == 'N':
end = end - 1 # adjust out the ')' serverops[dataline[vbegin:vend]] = value
if begin >= end or begin >= len(dataline) : continue if DEBUG:
if end >= len(dataline) : continue print('({0}, {1}) = {2}'.format(
'Server',
value = value + 1 dataline[vbegin:vend],
serverops[dataline[vbegin:vend]]))
serverops[dataline[begin:end]] = value
if DEBUG : print('({0}, {1}) = {2}'.format('Server', dataline[begin:end], serverops[dataline[begin:end]]))
datafile.close() datafile.close()
except : return False filename = '{0}{1}{2}'.format(
repopath,
'/common',
'/mail_oplist.h')
if VERBOSE or DEBUG:
print(filename)
with open(filename, 'r') as datafile:
while True:
dataline = datafile.readline()
if not dataline:
break
vbegin = dataline.find('OP_', 2)
vend = dataline.find(')', vbegin)
if vbegin == -1:
continue
if vend == -1:
continue
value += 1
if dataline[:1] == 'N':
serverops[dataline[vbegin:vend]] = value
if DEBUG:
print('({0}, {1}) = {2}'.format(
'Server',
dataline[vbegin:vend],
serverops[dataline[vbegin:vend]]))
datafile.close()
except:
return False
return True return True
def LoadClientOpcodes() : def loadclientopcodes():
badclients = [] badclients = []
for client in clients : for client in clients:
try : try:
filename = '{0}{1}{2}{3}{4}'.format(repopath, '/utils/patches', '/patch_', client, '.conf') filename = '{0}{1}{2}{3}{4}'.format(
repopath,
'/utils/patches',
'/patch_',
client,
'.conf')
if VERBOSE or DEBUG : print(filename) if VERBOSE or DEBUG:
print(filename)
with open(filename, 'r') as datafile :
while True :
kbegin = 0
vbegin = 0
kend = 0
vend = 0
with open(filename, 'r') as datafile:
while True:
dataline = datafile.readline() dataline = datafile.readline()
if not dataline : break if not dataline:
if not dataline[0:3] == 'OP_' : continue break
kend = kbegin + 3 kbegin = dataline.find('OP_')
kend = dataline.find('=', kbegin)
while not dataline[(kend - 1):kend] == '=' : if not kbegin == 0:
if kend >= len(dataline) : break continue
else : kend = kend + 1 if kend == -1:
continue
kend = kend - 1 # adjust out the '=' vbegin = dataline.find('0x', kend)
vbegin = kend + 1
vend = vbegin + 6 vend = vbegin + 6
if dataline[vbegin:vend] == '0x0000' : continue if vbegin == -1:
continue
clientops[client, dataline[kbegin:kend]] = dataline[vbegin:vend] value = int(dataline[(vbegin + 2):vend].lower(), 16)
if DEBUG : print('({0}, {1}) = {2}'.format(client, dataline[kbegin:kend], clientops[client, dataline[kbegin:kend]])) if value == 0:
continue
clientops[client, dataline[kbegin:kend]] = '0x{0}'.format(
hex(value)[2:].zfill(4))
if DEBUG:
print('({0}, {1}) = {2} (int: {3})'.format(
client,
dataline[kbegin:kend],
clientops[client, dataline[kbegin:kend]],
value))
datafile.close() datafile.close()
except:
badclients.append(client)
except : badclients.append(client) if len(badclients) > 0:
if len(badclients) > 0 :
badentries = [] badentries = []
for badclient in badclients : for badclient in badclients:
if VERBOSE or DEBUG : print('Deleting client {0} from search criteria...'.format(badclient)) if VERBOSE or DEBUG:
print('Deleting client {0} from search criteria...'.format(
badclient))
clients.remove(badclient) clients.remove(badclient)
if VERBOSE or DEBUG : print('Deleting any partial entries for client {0}...'.format(badclient)) if VERBOSE or DEBUG:
print('Deleting any partial entries for client {0}...'.format(
badclient))
for entry in clientops.keys() : for entry in clientops.keys():
if entry[0] == client : if entry[0] == badclient:
badentries.append(entry) badentries.append(entry)
for badentry in badentries : for badentry in badentries:
del clientops[badentry] del clientops[badentry]
if len(clients) == 0 : if len(clients) == 0:
return False return False
return True return True
def LoadClientTranslators() : def loadclienttranslators():
for client in clients : for client in clients:
try : if client == '6.2':
if client == '6.2' : shortname = '{0}'.format('/Client62_ops.h') shortname = '{0}'.format(
else : shortname = '{0}{1}{2}'.format('/', client, '_ops.h') '/Client62_ops.h')
else:
shortname = '{0}{1}{2}'.format(
'/',
client,
'_ops.h')
filename = '{0}{1}{2}'.format(repopath, '/common/patches', shortname) try:
filename = '{0}{1}{2}'.format(
repopath,
'/common/patches',
shortname)
if VERBOSE or DEBUG : print(filename) if VERBOSE or DEBUG:
print(filename)
with open(filename, 'r') as datafile : with open(filename, 'r') as datafile:
while True: while True:
begin = 0
end = 0
dataline = datafile.readline() dataline = datafile.readline()
if not dataline : break if not dataline:
if not dataline[:1] == 'E' and not dataline[0:1] == 'D' : continue break
while not dataline[begin:(begin + 1)] == '(' : vbegin = dataline.find('OP_', 2)
if begin >= len(dataline) : break vend = dataline.find(')', vbegin)
else : begin = begin + 1
end = begin + 1 if vbegin == -1:
continue
if vend == -1:
continue
while not dataline[(end - 1):end] == ')' : if dataline[:1] == 'E':
if end >= len(dataline) : break encodes.append((client, dataline[vbegin:vend]))
else : end = end + 1
begin = begin + 1 # adjust out the '(' if DEBUG:
end = end - 1 # adjust out the ')' print('ENCODE({0}, {1}) [floating index: {2}]'.format(
client,
dataline[vbegin:vend],
encodes.index((client, dataline[vbegin:vend]))))
elif dataline[:1] == 'D':
decodes.append((client, dataline[vbegin:vend]))
if begin >= end or begin >= len(dataline) : continue if DEBUG:
if end >= len(dataline) : continue print('DECODE({0}, {1}) [floating index: {2}]'.format(
client,
if dataline[:1] == 'E' : dataline[vbegin:vend],
encodes.append((client, dataline[begin:end])) decodes.index((client, dataline[vbegin:vend]))))
if DEBUG : print('ENCODE({0}, {1}) [floating index: {2}]'.format(client, dataline[begin:end], encodes.index((client, dataline[begin:end]))))
elif dataline[:1] == 'D' :
decodes.append((client, dataline[begin:end]))
if DEBUG : print('DECODE({0}, {1}) [floating index: {2}]'.format(client, dataline[begin:end], decodes.index((client, dataline[begin:end]))))
datafile.close() datafile.close()
except:
# TODO: need to handle
return False
except : return False # TODO: need to handle # this will need to change if we ever have a client with 100% support
if len(encodes) == 0 and len(decodes) == 0:
if len(encodes) == 0 and len(decodes) == 0 :
return False return False
return True return True
def LoadServerHandlers() : def loadserverhandlers():
# TODO: handle remarked out definitions; add other servers, if possible # TODO: handle remarked out definitions; add other servers, if possible
try : try:
filename = '{0}{1}{2}'.format(repopath, '/zone', '/client_packet.cpp') filename = '{0}{1}{2}'.format(
repopath,
'/zone',
'/client_packet.cpp')
if VERBOSE or DEBUG : print(filename) if VERBOSE or DEBUG:
print(filename)
with open(filename, 'r') as datafile : with open(filename, 'r') as datafile:
dataline = datafile.readline() dataline = datafile.readline()
while not dataline[:19] == 'void MapOpcodes() {' : while not dataline[:19] == 'void MapOpcodes() {':
dataline = datafile.readline() dataline = datafile.readline()
if not dataline : break if not dataline:
break
while True :
kbegin = 0
vbegin = 0
kend = 0
vend = 0
while True:
dataline = datafile.readline() dataline = datafile.readline()
if not dataline : break if not dataline:
if dataline[0:1] == '}' : break break
if dataline[0:1] == '}':
break
kbegin = dataline.find('OP_') kbegin = dataline.find('OP_')
kend = dataline.find(']', kbegin)
if kbegin == -1 : continue if kbegin == -1:
continue
if kend == -1:
continue
kend = kbegin + 1 vbegin = dataline.find('Client::', kend)
vend = dataline.find(';', vbegin)
while not dataline[kend:(kend + 1)] == ']' : if vbegin == -1:
if kend >= len(dataline) : break continue
else : kend = kend + 1 if vend == -1:
continue
vbegin = dataline.find('&Client::')
if vbegin == -1 : continue
vbegin = vbegin + 1
vend = vbegin + 9
while not dataline[vend:(vend + 1)] == ';' :
if vend >= len(dataline) : break
else : vend = vend + 1
handlers['Zone', dataline[kbegin:kend]] = dataline[vbegin:vend] handlers['Zone', dataline[kbegin:kend]] = dataline[vbegin:vend]
if DEBUG : print('({0}, {1}) = {2}'.format('Zone', dataline[kbegin:kend], handlers['Zone', dataline[kbegin:kend]])) if DEBUG:
print('({0}, {1}) = {2}'.format(
'Zone',
dataline[kbegin:kend],
handlers['Zone', dataline[kbegin:kend]]))
datafile.close() datafile.close()
except:
except : return False # should probably do the same for this (badservers) as done for badclients above # should probably do the same for this (badservers)
# as done for badclients in the above function
return False
return True return True
def CreateOutputDirectory() : def createoutputdirectory():
try : outputpath = ''
outputpath = '{0}{1}'.format(repopath, '/utils/scripts/opcode_handlers_output')
if VERBOSE or DEBUG : print(outputpath) try:
outputpath = '{0}{1}'.format(
repopath,
'/utils/scripts/opcode_handlers_output')
os.mkdir(outputpath) if VERBOSE or DEBUG:
print(outputpath)
if not os.path.exists(outputpath):
os.mkdir(outputpath)
return True return True
except:
except : return False return False
def CreateOutputStreams() : def createoutputstreams():
try : filename = ''
for client in clients :
filename = '{0}{1}{2}{3}'.format(repopath, '/utils/scripts/opcode_handlers_output/', client, '_opcode_handlers.txt')
if VERBOSE or DEBUG : print(filename) try:
for client in clients:
filename = '{0}{1}{2}{3}'.format(
repopath,
'/utils/scripts/opcode_handlers_output/',
client,
'_opcode_handlers.txt')
if VERBOSE or DEBUG:
print(filename)
outstreams[client] = open(filename, 'w') outstreams[client] = open(filename, 'w')
outstreams[client].write('******************************************************\n') message = \
outstreams[client].write('** Opcode-Handler analysis for \'{0}\' client\n'.format(client)) '------------------------------------------------------\n' \
outstreams[client].write('** script-generated file @ {0}\n'.format(ctime(time()))) '|| Opcode-Handler analysis for \'{0}\' client\n' \
outstreams[client].write('**\n') '|| script-generated file @ {1}\n' \
outstreams[client].write('** (only cross-linked (active) opcodes are listed)\n') '||\n' \
outstreams[client].write('******************************************************\n\n') '|| (only cross-linked (active) opcodes are listed)\n' \
'------------------------------------------------------\n' \
'\n'.format(
client,
ctime(time()))
except : outstreams[client].write(message)
for client in clients :
if client in outstreams.keys() : if DEBUG:
print(message[:-2])
except:
for client in clients:
if client in outstreams.keys():
outstreams[client].close() outstreams[client].close()
del outstreams[client] del outstreams[client]
if DEBUG:
print('->CreatingClientStream(exception): {0}'.format(
client))
return False return False
return True return True
def ParseOpcodeData() : def parseopcodedata():
serveropnames = [] serveropnames = []
for serveropkey in serverops.keys() : for serveropkey in serverops.keys():
if serveropkey == 'Null' : continue
if DEBUG : print('->ServerOpKey: {0}'.format(serveropkey))
serveropnames.append(serveropkey) serveropnames.append(serveropkey)
if len(serveropnames) == 0 : return False if DEBUG:
print('->ServerOpKey: {0}'.format(
serveropkey))
for server in servers : if len(serveropnames) == 0:
if server == 'Login' or server == 'World' : continue # Login, World not implemented yet return False
for server in servers:
# Login, World, UCS not implemented yet
if not server == 'Zone':
if DEBUG:
print('->SkippingServerOpcodeParse: {0}'.format(
server))
continue
handleropnames = [] handleropnames = []
for handlerkey in handlers.keys() : for handlerkey in handlers.keys():
if handlerkey[0] == server : if handlerkey[0] == server:
if DEBUG : print('->HandlerKey: {0}'.format(handlerkey[1]))
handleropnames.append(handlerkey[1]) handleropnames.append(handlerkey[1])
if len(handleropnames) == 0 : return False if DEBUG:
else : handleropnames.sort() # sort to process opcode names in ascending order print('->HandlerKey: {0}'.format(
handlerkey[1]))
for client in clients : if len(handleropnames) == 0:
return False
else:
# sort to process opcode names in ascending order
handleropnames.sort()
for client in clients:
clientopnames = [] clientopnames = []
clientencodes = [] clientencodes = []
clientdecodes = [] clientdecodes = []
notranslation = 0 totalopcodes = 0
encodedecode = 0
encodeonly = 0 encodeonly = 0
decodeonly = 0 decodeonly = 0
encodedecode = 0 notranslation = 0
totalopcodes = 0
for clientopkey in clientops.keys() :
if clientopkey[0] == client :
if DEBUG : print('->ClientOpKey: {0}'.format(clientopkey[1]))
for clientopkey in clientops.keys():
if clientopkey[0] == client:
clientopnames.append(clientopkey[1]) clientopnames.append(clientopkey[1])
if len(clientopnames) == 0 : return False if DEBUG:
print('->ClientOpKey: {0}'.format(
clientopkey[1]))
for encodeentry in encodes : if len(clientopnames) == 0:
if encodeentry[0] == client : return False
if DEBUG : print('->EncodeEntry: {0}'.format(encodeentry[1]))
for encodeentry in encodes:
if encodeentry[0] == client:
clientencodes.append(encodeentry[1]) clientencodes.append(encodeentry[1])
if len(clientencodes) == 0 : return False if DEBUG:
print('->EncodeEntry: {0}'.format(
encodeentry[1]))
for decodeentry in decodes : if len(clientencodes) == 0:
if decodeentry[0] == client : return False
if DEBUG : print('->DecodeEntry: {0}'.format(decodeentry[1]))
for decodeentry in decodes:
if decodeentry[0] == client:
clientdecodes.append(decodeentry[1]) clientdecodes.append(decodeentry[1])
if len(clientdecodes) == 0 : return False if DEBUG:
print('->DecodeEntry: {0}'.format(
decodeentry[1]))
for handleropentry in handleropnames : if len(clientdecodes) == 0:
try : clientopindex = clientopnames.index(handleropentry) return False
except : clientopindex = -1
if clientopindex > -1 : for handleropentry in handleropnames:
try:
clientopindex = clientopnames.index(handleropentry)
except:
clientopindex = -1
if clientopindex > -1:
val0 = clientopnames[clientopindex] val0 = clientopnames[clientopindex]
val1 = serverops[val0] val1 = serverops[val0]
val2 = clientops[(client, val0)] val2 = clientops[(client, val0)]
if DEBUG : print('->Opcode: {0} ({1}: {2} | {3}: {4})'.format(val0, server, val1, client, val2))
outstreams[client].write('Opcode: {0} | {1}: {2} | {3}: {4}\n'.format(val0, server, val1, client, val2))
val3 = handlers[(server, val0)] val3 = handlers[(server, val0)]
if DEBUG : print('->{0} Handler: {1}'.format(server, val3)) try:
val4 = clientencodes.index(val0) > -1
except:
val4 = False
try:
val5 = clientdecodes.index(val0) > -1
except:
val5 = False
outstreams[client].write(' {0} Handler: {1}\n'.format(server, val3)) message = \
'Opcode: {0} | {1}: {2} | {3}: {4}\n' \
' {5} Handler: \'{6}\'\n' \
' Encoded: {7} | Decoded: {8}\n\n'.format(
val0,
server,
val1,
client,
val2,
server,
val3,
val4,
val5)
try : val4 = clientencodes.index(val0) > -1 outstreams[client].write(message)
except : val4 = False
try : val5 = clientdecodes.index(val0) > -1
except : val5 = False
if DEBUG : print('Encoded: {0} | Decoded: {1}'.format(val4, val5)) if DEBUG:
print(message[:-2])
outstreams[client].write(' Encoded: {0} | Decoded: {1}\n\n'.format(val4, val5)) totalopcodes += 1
totalopcodes = totalopcodes + 1 if val4 and val5:
encodedecode += 1
elif val4 and not val5:
encodeonly += 1
elif not val4 and val5:
decodeonly += 1
elif not val4 and not val5:
notranslation += 1
if val4 and val5 : encodedecode = encodedecode + 1 if DEBUG:
elif val4 and not val5 : encodeonly = encodeonly + 1 print('->EndOfOpcodeLoop: {0}'.format(
elif not val4 and val5 : decodeonly = decodeonly + 1 val0))
elif not val4 and not val5 : notranslation = notranslation + 1
if DEBUG : print('->EndOfOpcodeLoop: {0}'.format(val0)) message = \
'Statistics -------------------------------------------\n' \
'|| Server Opcode Type: {0}\n' \
'|| Handled Opcodes: {1}\n' \
'|| Bi-directional: {2}\n' \
'|| Encodes Only: {3}\n' \
'|| Decodes Only: {4}\n' \
'|| No Translations: {5}\n' \
'\n' \
'Notes ------------------------------------------------\n' \
'|| Encodes are Server-to-Client and Decodes are' \
' Server-from-Client in context\n' \
'|| \'Bi-directional\' indicates translations are performed on' \
' tx/rx packets\n' \
'|| \'Encodes Only\' indicates translations only on tx packets' \
' - does not exclude direct packet rx\n' \
'|| \'Decodes Only\' indicates translations only on rx packets' \
' - does not exclude direct packet tx\n' \
'|| \'No Translations\' indicates no translations of tx/rx' \
' packets\n'.format(
server,
totalopcodes,
encodedecode,
encodeonly,
decodeonly,
notranslation)
if DEBUG : print('->OpcodeCount: {0}'.format(totalopcodes)) outstreams[client].write(message)
if DEBUG : print('->Translations: (Bi-directional: {0}, EncodeOnly: {1}, DecodeOnly: {2}, NoTranslation: {3})'.format(encodedecode, encodeonly, decodeonly, notranslation))
outstreams[client].write('Statistics *******************************************\n') if DEBUG:
outstreams[client].write('** Handled Opcodes: {0}\n'.format(totalopcodes)) print(message[:-1])
outstreams[client].write('** Bi-directional Translations: {0}\n'.format(encodeonly)) print('->EndOfClientLoop: {0}'.format(
outstreams[client].write('** Encodes Only: {0}\n'.format(encodeonly)) client))
outstreams[client].write('** Decodes Only: {0}\n'.format(decodeonly))
outstreams[client].write('** No Translations: {0}\n'.format(notranslation))
outstreams[client].write('Notes ************************************************\n')
outstreams[client].write('** \'Bi-directional\' indicates translations are performed on tx/rx packets\n')
outstreams[client].write('** \'Encodes Only\' indicates translations only on tx packets - does not exclude direct packet rx\n')
outstreams[client].write('** \'Decodes Only\' indicates translations only on rx packets - does not exclude direct packet tx\n')
outstreams[client].write('** \'No Translations\' indicates no translations on tx/rx of packets\n')
if DEBUG : print('->EndOfClientLoop: {0}'.format(client)) if DEBUG:
print('->EndOfServerLoop: {0}'.format(
if DEBUG : print('->EndOfServerLoop: {0}'.format(server)) server))
return True return True
def DestroyOutputStreams() : def destroyoutputstreams():
for client in clients : for client in clients:
if client in outstreams.keys() : if client in outstreams.keys():
outstreams[client].close() outstreams[client].close()
del outstreams[client] del outstreams[client]
if DEBUG:
print('->DestroyingClientStream: {0}'.format(
client))
return True return True