async def handle_connection(self, reader: StreamReader, writer: StreamWriter): temp_ref = TempRef() world_packet_manager = WorldPacketManager(temp_ref=temp_ref, reader=reader, writer=writer) peername = writer.get_extra_info('peername') Logger.debug('[World Server]: Accept connection from {}'.format(peername)) Logger.info('[World Server]: trying to process auth session') auth = AuthManager(reader, writer, temp_ref=temp_ref, world_packet_manager=world_packet_manager) await auth.process(step=AuthStep.SECOND) self._register_tasks() while True: try: request = await asyncio.wait_for(reader.read(4096), timeout=1.0) if request: response = await asyncio.wait_for(world_packet_manager.process(request), timeout=1.0) if response: for packet in response: writer.write(packet) await writer.drain() except TimeoutError: continue except Exception as e: Logger.error('[World Server]: exception, {}'.format(e)) traceback.print_exc() break writer.close()
async def process(self): Logger.debug('[Login Challenge]: processing') self._parse_data() try: with AccountManager() as account_mgr: account = account_mgr.get(name=self.account_name).account if account is None: raise Exception('Account \'{}\' is not found'.format(self.account_name)) account.os = self.os account.ip = '.'.join([str(i) for i in self.ip_addr]) account.platform = self.platform account.timezone = self.timezone account.locale = self.locale account_mgr.update() self.account = account self.temp_ref.account = account # TODO: define account exceptions except Exception as e: Logger.error('[Login Challenge]: error = {}'.format(e)) return None finally: return self._get_response()
async def authenticate_on_login_server(self): while True: try: request = await asyncio.wait_for(self.reader.read(1024), timeout=0.01) if request: opcode, packet = request[0], request[1:] try: handler = AuthManager.AUTH_HANDLERS[LoginOpCode( opcode)] except ValueError: Logger.error( '[AuthManager]: Incorrect request, check the opcode' ) pass else: response = await handler( packet=packet, srp=self.srp, temp_ref=self.temp_ref).process() if response: self.writer.write(response) except TimeoutError: pass finally: await asyncio.sleep(0.01)
def _get_response(self): try: response = pack('<2B20sQ2B', LoginOpCode.LOGIN_PROOF.value, LoginResult.SUCCESS.value, self.srp.server_proof, 0x00800000, 0x00, 0x00) except Exception as e: Logger.error('[Login Proof]: {}'.format(e)) else: return response
async def process(self, packet: bytes): if not self.header_crypt: raise Exception('Cannot decrypt packet') # this is workaround cause one-time decryption do not works correctly for some opcodes # so I need decrypt some packets for multiple times def decrypt(packet: bytes): result = packet for index in range(20): enc = self.header_crypt.decrypt(packet) try: # TODO: add has_key for Enum WorldOpCode(int.from_bytes(enc[2:6], 'little')).value except ValueError: continue else: result = enc break return result packet = decrypt(packet) size = unpack('>H', packet[:2])[0] opcode = WorldOpCode(unpack('<I', packet[2:6])[0]) if opcode in HANDLERS: Logger.debug( '[World Packet]: processing {} opcode ({} bytes)'.format( WorldOpCode(opcode).name, size)) handlers = HANDLERS[opcode] packets = list() for handler in handlers: opcode, response = await handler( packet, temp_ref=self.temp_ref, reader=self.reader, writer=self.writer, header_crypt=self.header_crypt).process() if opcode and response: packets.append( WorldPacketManager.generate_packet( opcode, response, self.header_crypt)) return packets else: try: Logger.warning( '[World Packet]: no handler for opcode = {} ({} bytes)'. format(WorldOpCode(opcode).name, size)) except ValueError: Logger.error( '[World Packet]: no handler for unknown opcode = {} ({} bytes)' .format(opcode, size)) finally: return None
def _parse_data(self): try: parsed_data = unpack(LoginProof.LOGIN_PROOF_FORMAT, self.packet) self.client_ephemeral = int.from_bytes(parsed_data[0], 'little') self.client_proof = parsed_data[1] self.checksum = parsed_data[2] self.unk = parsed_data[3] except StructError as e: Logger.error('[Login Proof]: on unpacking data(len={}), error={}'.format(len(self.packet), StructError))
def delete(self, **kwargs): if kwargs: try: self.session.query(Account).filter_by(**kwargs).delete() except Exception as e: Logger.error( '[AccountManager]: Error has occured on account delete, {}' .format(e)) return self
def set_default_equipment(self): self.session.expunge(self.player.region) self.session.expunge(self.player.account) self.session.expunge(self.player) with EquipmentManager() as equipment_mgr: try: self.equipment = equipment_mgr.set_default_equipment( player=self.player).get_items() except Exception as e: Logger.error('[PlayerMgr]: equipment error = {}'.format(e))
def get(self, **kwargs): if kwargs: try: self.account = self.session.query(Account).filter_by( **kwargs).first() except Exception as e: Logger.error( '[AccountManager]: Error has occured, account will be None, error: {}' .format(e)) return self
async def handle_connection(self, reader: StreamReader, writer: StreamWriter): self._register_tasks() temp_ref = TempRef() world_packet_manager = WorldPacketManager(temp_ref=temp_ref, reader=reader, writer=writer) Logger.info('[World Server]: trying to process auth session') auth = AuthManager(reader, writer, temp_ref=temp_ref, world_packet_manager=world_packet_manager, session_keys=self.session_keys) is_authenticated = await auth.process(step=AuthStep.SECOND) if is_authenticated: peer_name = writer.get_extra_info('peername') Logger.success( '[World Server]: Accept connection from {}'.format(peer_name)) while True: try: request = await asyncio.wait_for(reader.read(4096), timeout=0.01) if request: response = await asyncio.wait_for( world_packet_manager.process(request), timeout=0.01) if response: for packet in response: writer.write(packet) await writer.drain() except TimeoutError: pass except BrokenPipeError: pass except Exception as e: Logger.error('[World Server]: exception, {}'.format(e)) traceback.print_exc() break finally: await asyncio.sleep(0.01) writer.close()
async def process(self): self._load_player() if not self.temp_ref.player: Logger.error('[Player Init]: player not exists') raise PlayerNotExists await QueuesRegistry.connections_queue.put( (self.temp_ref.player.name, self.reader, self.writer, self.header_crypt)) await QueuesRegistry.players_queue.put(self.temp_ref.player) return None, None
def _check_session_key(self): Logger.info('[Auth Session Manager]: checking session key') key = '#{}-session-key'.format(self.account_name) try: session_key = self.session_keys[key] except KeyError: Logger.error('[AuthMgr]: session with this key does not exists') self.writer.close() else: if not session_key: raise Exception('[AuthMgr]: Session key does not exists') self.session_key = b64decode(session_key)
def _get_verify_login_packet(self): player = self.temp_ref.player if not player: Logger.error('[Login Verify]: player not exists') return None return pack( '<I4f', player.map_id, # map id player.x, # x player.y, # y player.z, # z player.orientation # orientation )
def load(self): current_dir = path.dirname(__file__) main_config = 'Config.yml' with open(path.join(current_dir, main_config), 'r') as stream: try: data = yaml.load(stream, Loader=yaml.Loader) self.config = json.loads( json.dumps(data), object_hook=lambda d: namedtuple('Configs', d.keys()) (*d.values())) except yaml.YAMLError as error: Logger.error('[Config Manager]: {}'.format(error)) else: return self
async def process(self): test_item1 = session.player.equipment.get_object_field(CharacterEquipSlot.BODY) test_item2 = session.player.equipment.get_object_field(CharacterEquipSlot.CHEST) test_item3 = session.player.equipment.get_object_field(CharacterEquipSlot.LEGS) test_item4 = session.player.equipment.get_object_field(CharacterEquipSlot.FEET) spawned_item = UpdatePacket(test_item1, ObjectUpdateType.CREATE_OBJECT.value, False) for field in self.SPAWN_FIELDS: spawned_item.add_field(field, test_item1.get_object_field(field)) response = spawned_item.update(send_packed_guid=True) Logger.error('[ItemSpawn]: {}'.format(response)) return WorldOpCode.SMSG_UPDATE_OBJECT, response
async def refresh_creatures(self): for region in self.regions: try: region_units = region.units.copy() except DetachedInstanceError as e: Logger.error('[Region Manager]: {}'.format(e)) else: units = region_units.copy() # finally building packet for player that contains unit list movement_flags = ( UpdateObjectFlags.UPDATEFLAG_HIGHGUID.value | UpdateObjectFlags.UPDATEFLAG_LIVING.value | UpdateObjectFlags.UPDATEFLAG_HAS_POSITION.value) spawn_dist = Config.World.Gameplay.spawn_dist online_players = region.get_online_players() if not spawn_dist == 0: for player_name in online_players: player = online_players[player_name] # list of unit managers each ready to build the update packet update_packets = [] if spawn_dist > 0: units = [ unit for unit in units if RegionManager._is_unit_in_spawn_radius( unit, player) ] for unit in units: with UnitManager() as unit_mgr: unit_mgr.set(unit) unit_mgr.movement.set_update_flags( movement_flags) batch_builder = unit_mgr.prepare() \ .build_update_packet(RegionManager.UNIT_SPAWN_FIELDS) update_packets.append(batch_builder) asyncio.ensure_future( QueuesRegistry.update_packets_queue.put( (player.name, update_packets)))
async def authenticate_on_world_server(self): self.send_auth_challenge() try: await self._parse_data() await self._check_session_key() self._generate_server_hash() # after this step next packets will be encrypted self._setup_encryption() if self.server_hash != self.client_hash: raise Exception('[Auth Manager]: Server hash is differs from client hash') else: self._send_addon_info() self._send_auth_response() except TimeoutError: Logger.error('[Auth Manager]: Timeout on step2')
def _parse_account_name(self, buffer: BytesIO): Logger.info('[Auth Session Manager]: parsing account name') result = bytes() while True: char = buffer.read(1) if char and char != b'\x00': result += char else: break try: result = result.decode('utf-8') except UnicodeDecodeError: Logger.error('[Auth Session Manager]: decode error, wrong name = {}'.format(result)) else: return result
async def handle_connection(self, websocket: WebSocketCommonProtocol, path): self._register_tasks(websocket=websocket) while True: try: response = {'type': 'REGIONS_FETCH_LIST_RESPONSE', 'payload': self.web_data} await websocket.send(json.dumps(response)) except ConnectionClosedOK: Logger.error('[Websocket Server]: Connection was closed') return except Exception as e: Logger.error('[Websocket Server]: {}'.format(e)) Logger.warning(self.web_data) traceback.print_exc() continue finally: await asyncio.sleep(1)
def add(self, field, value, offset=0): try: field_type = FIELD_TYPE_MAP[field] except KeyError: Logger.error( '[UpdatePacket Block Builder]: no type associated with {}'. format(str(field))) return else: field_struct = self.FIELD_BIN_MAP[field_type] index = offset and (field.value + offset) or field field_index = UpdateBlocksBuilder._get_field_index(index) self._set_field_mask_bits(field_index, field_struct) try: self._set_field_value(field_index, field_struct, value) except StructError: Logger.warning( 'Field with index {} should be set'.format(field_index)) pass
def to_json(self): exclude_keys = ['_sa_instance_state'] # TODO: I think this can be refactored in the future, # this check need to avoid RecursionError if type(self).__name__ in ['Unit', 'Player', 'Item']: exclude_keys += ['region'] result = {} try: for key in self.__dict__: if key not in exclude_keys: if isinstance(self.__dict__[key], InstrumentedList): result[key] = [ obj.to_json() for obj in self.__dict__[key] ] elif isinstance(self.__dict__[key], BaseModel): result[key] = self.__dict__[key].to_json() elif isinstance(self.__dict__[key], dict): sub_result = {} for _key in self.__dict__[key]: item = self.__dict__[key][_key] if isinstance(item, BaseModel): sub_result[_key] = item.to_json() else: sub_result[_key] = item result[key] = sub_result elif isinstance(self.__dict__[key], (bytes, bytearray)): pass else: result[key] = self.__dict__[key] except AttributeError as e: Logger.error('[BaseModel][Player]: {}'.format(e)) raise e except RecursionError as e: Logger.error('[BaseModel]: {}'.format(e)) raise e except Exception as e: raise e else: return result
def _parse_data(self): def _decode_cstring(cstring, encoding='ascii'): if type(cstring) is bytes: return cstring.decode(encoding).strip('\x00')[::-1] else: return cstring try: # remaining part of packet, that contains account name and byte with size of it packet_part_with_acc = (len(self.packet) - LoginChallenge.PACKET_SIZE_WITHOUT_ACC_NAME) parsed_data = unpack(LoginChallenge.LOGIN_CHAL_PACKET_FORMAT % packet_part_with_acc, self.packet) self.unk_code = parsed_data[0] self.size = parsed_data[1] self.game_name = _decode_cstring(parsed_data[2]) self.version_major = parsed_data[4] self.version_minor = parsed_data[5] self.version_patch = parsed_data[6] self.version_build = parsed_data[7] self.platform = _decode_cstring(parsed_data[8]) self.os = _decode_cstring(parsed_data[10]) self.locale = _decode_cstring(parsed_data[11]) self.timezone = parsed_data[12] self.ip_addr = parsed_data[15:19] self.account_name_size, account_name = parsed_data[19:] self.account_name = account_name.decode('ascii') except UnicodeDecodeError as e: Logger.error('[Login Challenge]: UnicodeDecodeError: {}'.format(e)) except StructError as e: Logger.error('[Login Challenge]: StructError: {}'.format(e)) except Exception as e: Logger.error('[Login Challenge]: exception({})'.format(e.__class__))
def set_default_skills(self, player: Player): try: default_skills: List[DefaultSkill] = self.session\ .query(DefaultSkill)\ .filter(or_(DefaultSkill.race == player.race, DefaultSkill.char_class == player.char_class))\ .all() skills = [] for default_skill in default_skills: skill = PlayerSkill() skill.skill_template = default_skill.skill_template skill.player = self.session.merge(player) skills.append(skill) self.session.add_all(skills) self.session.commit() except Exception as e: Logger.error('[SkillMgr]: {}'.format(e)) finally: return self
async def process(self): guid = int.from_bytes(self.packet[6:], 'little') Logger.error('[Active Mover]: guid = {}'.format(guid)) return None, None
QueuesRegistry.session_keys_queue = asyncio.Queue() QueuesRegistry.players_queue = asyncio.Queue() QueuesRegistry.remove_player_queue = asyncio.Queue() QueuesRegistry.connections_queue = asyncio.Queue() QueuesRegistry.disconnect_queue = asyncio.Queue() QueuesRegistry.packets_queue = asyncio.Queue() QueuesRegistry.broadcast_callback_queue = asyncio.Queue() try: loop.run_until_complete( asyncio.gather( login_server.get_instance(), world_server.get_instance(), # websocket_server.get_instance(), asyncio.ensure_future(world_manager.run()))) except Exception as e: Logger.error('[Run]: (run until complete) {}'.format(e)) raise e try: loop.run_forever() except KeyboardInterrupt: # TODO: add signal to stop all runned tasks pass except Exception as e: Logger.error('[Run]: (run forever) {}'.format(e)) raise e finally: loop.close()
executor = ProcessPoolExecutor(max_workers=cpu_count()) subprocess.run('redis-server --daemonize yes', shell=True) try: loop.run_until_complete( asyncio.gather( login_server.get_instance(), world_server.get_instance(), asyncio.ensure_future(world_server.refresh_connections()), asyncio.ensure_future( world_server.send_update_packet_to_player()), # FIXME: anti-pattern # https://stackoverflow.com/questions/49275895/asyncio-multiple-concurrent-servers/49280706#49280706 loop.run_in_executor(executor, WebServer.run), asyncio.ensure_future(WorldManager().run()))) except Exception as e: Logger.error('Exception on Run step: {}'.format(e)) traceback.print_exc() try: loop.run_forever() except KeyboardInterrupt: pass except Exception as e: Logger.error('Exception: {}'.format(e)) pass loop.close()