def firstPacketLoginAccount(self, packet): username = packet.string() password = packet.string() if not username and not config.anyAccountWillDo: self.exitWithError("You must enter your account number") return # Initialize the packet to send pkg = TibiaPacket() if username: # Our funny way of doing async SQL account = yield sql.runQuery("SELECT `id`, `premdays` FROM `accounts` WHERE `name` = %s AND `password` = SHA1(CONCAT(`salt`, %s))", username, password) if account: characters = yield sql.runQuery("SELECT `name`,`world_id` FROM `players` WHERE account_id = %s", account[0]['id']) if not username or not account: if config.anyAccountWillDo: account = ((0,0),) characters = config.anyAccountPlayerMap else: self.exitWithError("Invalid username or password") return if config.letGameServerRunTheLoginServer: import game.scriptsystem game.scriptsystem.get("preSendLogin").run(client=self, characters=characters, account=account, username=username, password=password) # Send motd pkg.uint8(0x14) pkg.string(config.motd) # Add character list pkg.uint8(0x64) pkg.uint8(len(characters)) for character in characters: ip = config.servers[character['world_id']][0] port = config.gamePort if ':' in ip: ip, port = ip.split(':') port = int(port) if self.address == '127.0.0.1': ip = '127.0.0.1' elif ip in IPS: ip = IPS[ip] elif ip != 'auto': _ip = ip ip = socket.gethostbyname(ip) IPS[_ip] = ip else: import urllib.request, urllib.error, urllib.parse try: ip = urllib.request.urlopen("http://vapus.net/ip.php").read() except: ip = "" if not ip: raise Exception("[ERROR] Automatic IP service is down!") IPS['auto'] = ip # Save IPS here. # pickle.dump(IPS, open('IP_CACHE', 'wb'), 2) pkg.string(character['name']) pkg.string(config.servers[character['world_id']][1]) pkg.raw(socket.inet_aton(ip if type(ip) == str else ip.decode('utf-8'))) pkg.length += 4 pkg.uint16(port) # Add premium days pkg.uint16(account[0]['premdays']) pkg.send(self) # Send self.username = username self.password = password self.gotFirst = True
def onFirstPacket(self, packet): packetType = packet.uint8() IN_TEST = False if packetType == 0xFF: # Special case for tests. IN_TEST = True if packetType == 0x0A and not self.ready: packet.pos += 2 # OS 0x00 and 0x01 #packet.uint16() version = packet.uint16() # Version int if version >= 972: version = packet.uint32() packet.uint8() # Client type. self.protocol = game.protocol.getProtocol(version) self.version = version print "Client protocol version %d" % version if not self.protocol: log.msg("Trying to load a invalid protocol") self.transport.loseConnection() return if not IN_TEST: if (len(packet.data) - packet.pos) == 128: # RSA 1024 is always 128 packet.data = otcrypto.decryptRSA(packet.getData()) # NOTICE: Should we do it in a seperate thread? packet.pos = 0 # Reset position else: log.msg("RSA, length != 128 (it's %d)" % (packet.length - packet.pos)) self.transport.loseConnection() return if not packet.data or packet.uint8(): # RSA needs to decrypt just fine, so we get the data, and the first byte should be 0 log.msg("RSA, first char != 0") self.transport.loseConnection() return # Set the XTEA key k = (packet.uint32(), packet.uint32(), packet.uint32(), packet.uint32()) sum = 0 self.xtea = {} for x in xrange(32): self.xtea[x] = sum + k[sum & 3] & 0xffffffff sum = (sum + 0x9E3779B9) & 0xffffffff self.xtea[32 + x] = sum + k[sum>>11 & 3] & 0xffffffff ip = self.transport.getPeer().host # Ban check. if game.ban.ipIsBanned(ip): self.exitWithError("Your ip is banned.\n %s" % game.ban.banIps[ip].message()) return if config.gameMaxConnections <= (self.connections + len(waitingListIps)): if ip in waitingListIps: i = waitingListIps.index(ip) + 1 lastChecks[ip] = time.time() # Note: Everyone below this threshhold might connect. So even if your #1 on the list and there is two free slots, you can be unlucky and don't get them. if i + self.connections > config.gameMaxConnections: self.exitWaitingList("Too many players online. You are at place %d on the waiting list." % i, i) return else: waitingListIps.append(ip) lastChecks[ip] = time.time() self.exitWaitingList("Too many players online. You are at place %d on the waiting list." % len(waitingListIps), len(waitingListIps)) return self.connections += 1 try: waitingListIps.remove(ip) del lastChecks[ip] except: pass # "Gamemaster" mode? gamemaster = packet.uint8() # Check if version is correct if version > config.versionMax or version < config.versionMin: self.exitWithError(config.versionError) return # Some weird thing with 9.7. try: # Check if there is a username (and a password) username = packet.string() characterName = packet.string() password = packet.string() except: self.exitWithError("Try again.") return if (not username and not config.anyAccountWillDo) or not characterName: self.exitWithError("Could not get your account name, or character name") return packet.pos += 6 # I don't know what this is # Our funny way of doing async SQL account = yield sql.conn.runQuery("SELECT `id`,`language` FROM `accounts` WHERE `name` = %s AND `password` = SHA1(CONCAT(`salt`, %s))", (username, password)) if not account: account = game.scriptsystem.get("loginAccountFailed").runSync(None, client=self, username=username, password=password) if not account or account == True: self.exitWithError("Invalid username or password") return account = account[0] # Ban check. if game.ban.accountIsBanned(account['id']): self.exitWithError("Your account is banned.\n %s" % game.ban.banAccounts[account['id']].message()) return if not len(account) >= 2 or not account['language']: language = config.defaultLanguage else: language = account['language'] character = yield sql.conn.runQuery("SELECT p.`id`,p.`name`,p.`world_id`,p.`group_id`,p.`account_id`,p.`vocation`,p.`health`,p.`mana`,p.`soul`,p.`manaspent`,p.`experience`,p.`posx`,p.`posy`,p.`posz`,p.`instanceId`,p.`sex`,p.`looktype`,p.`lookhead`,p.`lookbody`,p.`looklegs`,p.`lookfeet`,p.`lookaddons`,p.`lookmount`,p.`town_id`,p.`skull`,p.`stamina`, p.`storage`, p.`inventory`, p.`depot`, p.`conditions`, s.`fist`,s.`fist_tries`,s.`sword`,s.`sword_tries`,s.`club`,s.`club_tries`,s.`axe`,s.`axe_tries`,s.`distance`,s.`distance_tries`,s.`shield`,s.`shield_tries`,s.`fishing`, s.`fishing_tries`, g.`guild_id`, g.`guild_rank`, p.`balance` FROM `players` AS `p` LEFT JOIN player_skills AS `s` ON p.`id` = s.`player_id` LEFT JOIN player_guild AS `g` ON p.`id` = g.`player_id` WHERE p.account_id = %s AND p.`name` = %s AND p.`world_id` = %s", (account['id'], characterName, config.worldId)) if not character: character = game.scriptsystem.get("loginCharacterFailed").runSync(None, client=self, account=account, name=characterName) if not character or character == True: self.exitWithError("Character can't be loaded") return character = character[0] if gamemaster and character['group_id'] < 3: self.exitWithError("You are not gamemaster! Turn off gamemaster mode in your IP changer.") return # Ban check. if isinstance(character, game.player.Player): if game.ban.playerIsBanned(character): self.exitWithError("Your player is banned.\n %s" % game.ban.banAccounts[character.data["id"]].message()) return elif game.ban.playerIsBanned(character['id']): self.exitWithError("Your player is banned.\n %s" % game.ban.banAccounts[character['id']].message()) return # If we "made" a new character in a script, character = the player. player = None if isinstance(character, game.player.Player): player = character game.player.allPlayers[player.name()] = player elif character['name'] in game.player.allPlayers: player = game.player.allPlayers[character['name']] if player.client: self.exitWithError("This character is already logged in!") return sql.runOperation("UPDATE `players` SET `lastlogin` = %s, `online` = 1 WHERE `id` = %s", (int(time.time()), character['id'])) if player: self.player = player if self.player.data["health"] <= 0: self.player.onSpawn() self.player.client = self tile = getTile(self.player.position) tile.placeCreature(self.player) # Send update tile to refresh all players. We use refresh because it fixes the order of things as well. updateTile(self.player.position, tile) else: # Bulld the dict since we disabled automaticly doing this. Here we cast Decimal objects to int aswell (no longer automaticly either) yield deathlist.loadDeathList(character['id']) character["language"] = language game.player.allPlayers[character['name']] = game.player.Player(self, character) self.player = game.player.allPlayers[character['name']] if self.player.data["health"] <= 0: self.player.onSpawn() try: tile = getTile(self.player.position) tile.placeCreature(self.player) # Send update tile to refresh all players. We use refresh because it fixes the order of things as well. updateTile(self.player.position, tile) except AttributeError: self.player.position = Position(*game.map.mapInfo.towns[1][1]) tile = getTile(self.player.position) tile.placeCreature(self.player) # Send update tile to refresh all players. We use refresh because it fixes the order of things as well. updateTile(self.player.position, tile) # Update last login sql.runOperation("UPDATE `players` SET `lastlogin` = %s WHERE `id` = %s", (int(time.time()), character['id'])) self.packet = self.player.packet self.player.sendFirstPacket() self.ready = True # We can now accept other packages # Call the login script game.scriptsystem.get("login").runSync(self.player) # If we got a waiting list, now is a good time to verify the list if lastChecks: checkTime = time.time() for entry in lastChecks: if checkTime - lastChecks[entry] > 3600: waitingListIps.remove(entry) del lastChecks[entry] elif packetType == 0x00 and self.transport.getPeer().host in config.executeProtocolIps: self.gotFirst = False t = TibiaPacket() if not config.executeProtocolAuthKeys: self.ready = 2 try: while True: op = packet.string() print op if op == "CALL" and self.ready == 2: print "do this" result = yield game.functions.executeCode(packet.string()) t.string(result) elif op == "AUTH": print "auth" result = packet.string() in config.executeProtocolAuthKeys if result: t.string("True") self.ready = 2 else: t.string("False") except struct.error: pass # End of the line t.send(self)
def exitWithError(self, message, error = 0x14): packet = TibiaPacket() packet.uint8(error) # Error code packet.string(message) # Error message packet.send(self) self.close()
def exitWithError(self, message): packet = TibiaPacket(0x14) packet.string(message) # Error message packet.send(self) self.loseConnection()
def exitWaitingList(self, message, slot): packet = TibiaPacket(0x16) packet.string(message) # Error message packet.uint8(15 + (2 * slot)) packet.send(self) self.loseConnection()
def exitWaitingList(self, message, slot): packet = TibiaPacket(0x16) packet.string(message) # Error message packet.uint8(15 + (2 * slot)) packet.send(self) self.loseConnection()
def onFirstPacket(self, packet): packetType = packet.uint8() IN_TEST = False if packetType == 0xFF: # Special case for tests. IN_TEST = True if packetType == 0x0A and not self.ready: packet.pos += 2 # OS 0x00 and 0x01 #packet.uint16() version = packet.uint16() # Version int if version >= 972: version = packet.uint32() packet.uint8() # Client type. self.protocol = game.protocol.getProtocol(version) self.version = version print "Client protocol version %d" % version if not self.protocol: log.msg("Trying to load a invalid protocol") self.transport.loseConnection() return if not IN_TEST: if (len(packet.data) - packet.pos) == 128: # RSA 1024 is always 128 packet.data = otcrypto.decryptRSA(packet.getData( )) # NOTICE: Should we do it in a seperate thread? packet.pos = 0 # Reset position else: log.msg("RSA, length != 128 (it's %d)" % (packet.length - packet.pos)) self.transport.loseConnection() return if not packet.data or packet.uint8( ): # RSA needs to decrypt just fine, so we get the data, and the first byte should be 0 log.msg("RSA, first char != 0") self.transport.loseConnection() return # Set the XTEA key k = (packet.uint32(), packet.uint32(), packet.uint32(), packet.uint32()) sum = 0 self.xtea = {} for x in xrange(32): self.xtea[x] = sum + k[sum & 3] & 0xffffffff sum = (sum + 0x9E3779B9) & 0xffffffff self.xtea[32 + x] = sum + k[sum >> 11 & 3] & 0xffffffff ip = self.transport.getPeer().host # Ban check. if game.ban.ipIsBanned(ip): self.exitWithError("Your ip is banned.\n %s" % game.ban.banIps[ip].message()) return if config.gameMaxConnections <= (self.connections + len(waitingListIps)): if ip in waitingListIps: i = waitingListIps.index(ip) + 1 lastChecks[ip] = time.time() # Note: Everyone below this threshhold might connect. So even if your #1 on the list and there is two free slots, you can be unlucky and don't get them. if i + self.connections > config.gameMaxConnections: self.exitWaitingList( "Too many players online. You are at place %d on the waiting list." % i, i) return else: waitingListIps.append(ip) lastChecks[ip] = time.time() self.exitWaitingList( "Too many players online. You are at place %d on the waiting list." % len(waitingListIps), len(waitingListIps)) return self.connections += 1 try: waitingListIps.remove(ip) del lastChecks[ip] except: pass # "Gamemaster" mode? gamemaster = packet.uint8() # Check if version is correct if version > config.versionMax or version < config.versionMin: self.exitWithError(config.versionError) return # Some weird thing with 9.7. try: # Check if there is a username (and a password) username = packet.string() characterName = packet.string() password = packet.string() except: self.exitWithError("Try again.") return if (not username and not config.anyAccountWillDo) or not characterName: self.exitWithError( "Could not get your account name, or character name") return packet.pos += 6 # I don't know what this is # Our funny way of doing async SQL account = yield sql.conn.runQuery( "SELECT `id`,`language` FROM `accounts` WHERE `name` = %s AND `password` = SHA1(CONCAT(`salt`, %s))", (username, password)) if not account: account = game.scriptsystem.get("loginAccountFailed").runSync( None, client=self, username=username, password=password) if not account or account == True: self.exitWithError("Invalid username or password") return account = account[0] # Ban check. if game.ban.accountIsBanned(account['id']): self.exitWithError( "Your account is banned.\n %s" % game.ban.banAccounts[account['id']].message()) return if not len(account) >= 2 or not account['language']: language = config.defaultLanguage else: language = account['language'] character = yield sql.conn.runQuery( "SELECT p.`id`,p.`name`,p.`world_id`,p.`group_id`,p.`account_id`,p.`vocation`,p.`health`,p.`mana`,p.`soul`,p.`manaspent`,p.`experience`,p.`posx`,p.`posy`,p.`posz`,p.`instanceId`,p.`sex`,p.`looktype`,p.`lookhead`,p.`lookbody`,p.`looklegs`,p.`lookfeet`,p.`lookaddons`,p.`lookmount`,p.`town_id`,p.`skull`,p.`stamina`, p.`storage`, p.`inventory`, p.`depot`, p.`conditions`, s.`fist`,s.`fist_tries`,s.`sword`,s.`sword_tries`,s.`club`,s.`club_tries`,s.`axe`,s.`axe_tries`,s.`distance`,s.`distance_tries`,s.`shield`,s.`shield_tries`,s.`fishing`, s.`fishing_tries`, g.`guild_id`, g.`guild_rank`, p.`balance` FROM `players` AS `p` LEFT JOIN player_skills AS `s` ON p.`id` = s.`player_id` LEFT JOIN player_guild AS `g` ON p.`id` = g.`player_id` WHERE p.account_id = %s AND p.`name` = %s AND p.`world_id` = %s", (account['id'], characterName, config.worldId)) if not character: character = game.scriptsystem.get( "loginCharacterFailed").runSync(None, client=self, account=account, name=characterName) if not character or character == True: self.exitWithError("Character can't be loaded") return character = character[0] if gamemaster and character['group_id'] < 3: self.exitWithError( "You are not gamemaster! Turn off gamemaster mode in your IP changer." ) return # Ban check. if isinstance(character, game.player.Player): if game.ban.playerIsBanned(character): self.exitWithError( "Your player is banned.\n %s" % game.ban.banAccounts[character.data["id"]].message()) return elif game.ban.playerIsBanned(character['id']): self.exitWithError( "Your player is banned.\n %s" % game.ban.banAccounts[character['id']].message()) return # If we "made" a new character in a script, character = the player. player = None if isinstance(character, game.player.Player): player = character game.player.allPlayers[player.name()] = player elif character['name'] in game.player.allPlayers: player = game.player.allPlayers[character['name']] if player.client: self.exitWithError("This character is already logged in!") return sql.runOperation( "UPDATE `players` SET `lastlogin` = %s, `online` = 1 WHERE `id` = %s", (int(time.time()), character['id'])) if player: self.player = player if self.player.data["health"] <= 0: self.player.onSpawn() self.player.client = self tile = getTile(self.player.position) tile.placeCreature(self.player) # Send update tile to refresh all players. We use refresh because it fixes the order of things as well. updateTile(self.player.position, tile) else: # Bulld the dict since we disabled automaticly doing this. Here we cast Decimal objects to int aswell (no longer automaticly either) yield deathlist.loadDeathList(character['id']) character["language"] = language game.player.allPlayers[character['name']] = game.player.Player( self, character) self.player = game.player.allPlayers[character['name']] if self.player.data["health"] <= 0: self.player.onSpawn() try: tile = getTile(self.player.position) tile.placeCreature(self.player) # Send update tile to refresh all players. We use refresh because it fixes the order of things as well. updateTile(self.player.position, tile) except AttributeError: self.player.position = Position( *game.map.mapInfo.towns[1][1]) tile = getTile(self.player.position) tile.placeCreature(self.player) # Send update tile to refresh all players. We use refresh because it fixes the order of things as well. updateTile(self.player.position, tile) # Update last login sql.runOperation( "UPDATE `players` SET `lastlogin` = %s WHERE `id` = %s", (int(time.time()), character['id'])) self.packet = self.player.packet self.player.sendFirstPacket() self.ready = True # We can now accept other packages # Call the login script game.scriptsystem.get("login").runSync(self.player) # If we got a waiting list, now is a good time to verify the list if lastChecks: checkTime = time.time() for entry in lastChecks: if checkTime - lastChecks[entry] > 3600: waitingListIps.remove(entry) del lastChecks[entry] elif packetType == 0x00 and self.transport.getPeer( ).host in config.executeProtocolIps: self.gotFirst = False t = TibiaPacket() if not config.executeProtocolAuthKeys: self.ready = 2 try: while True: op = packet.string() print op if op == "CALL" and self.ready == 2: print "do this" result = yield game.functions.executeCode( packet.string()) t.string(result) elif op == "AUTH": print "auth" result = packet.string( ) in config.executeProtocolAuthKeys if result: t.string("True") self.ready = 2 else: t.string("False") except struct.error: pass # End of the line t.send(self)
def exitWithError(self, message): packet = TibiaPacket(0x14) packet.string(message) # Error message packet.send(self) self.loseConnection()
def exitWithError(self, message, error = 0x0A): packet = TibiaPacket() packet.uint8(error) # Error code packet.string(message) # Error message packet.send(self) self.loseConnection()
def onFirstPacket(self, packet): global IPS try: packet.uint8() except: return if config.letGameServerRunTheLoginServer: pos = packet.pos packet.pos = 0 packetType = packet.uint8() if packetType == 0xFF: if packetType == 0xFF: if packet.getX(4) == "info": try: sendPlayers == packet.uint8() except: sendPlayers = False data = "" # TODO: Send XML format. elif packetType == 0x01: # Silly status protocol. No multi world support... reqInfo = packet.uint16() pkg = TibiaPacket() if reqInfo & 0x01: # REQUEST_BASIC_SERVER_INFO pkg.uint8(0x10) pkg.string(config.server[0][0]) pkg.string(config.server[0][1]) pkg.uint16(4) pkg.uint32(config.loginPort) if reqInfo & 0x02: # REQUEST_SERVER_OWNER_INFO pkg.uint8(0x11) pkg.string(config.ownerName) pkg.string(config.ownerEmail) if reqInfo & 0x04: # REQUEST_MISC_SERVER_INFO pkg.uint8(0x12) pkg.string(config.motd) pkg.string(config.location) pkg.string(config.url) pkg.uint64(time.time() - SERVER_START + config.tibiaTimeOffset) if reqInfo & 0x08: # REQUEST_PLAYERS_INFO pkg.uint8(0x20) pkg.uint32(len(core.game.allPlayers)) pkg.uint32(config.gameMaxConnections) pkg.uint32(len(core.game.allPlayers)) # TODO: Track record. if reqInfo & 0x10: # REQUEST_SERVER_MAP_INFO pkg.uint8(0x30) pkg.string(core.game.map.mapInfo.description) pkg.string(core.game.map.mapInfo.author) pkg.uint16(core.game.map.mapInfo.width) pkg.uint16(core.game.map.mapInfo.height) # 0x20 and 0x40 is unimplanted. if reqInfo & 0x80: pkg.uint8(0x23) pkg.string("PyOT") pkg.string(SERVER_VERSION) pkg.string("%s-%s" % (config.versionMin, config.versionMax)) pkg.send(self) return packet.pos = pos packet.pos += 2 #packet.uint16() # OS 0x00 and 0x01 version = packet.uint16() # Version int if version >= 971: version = packet.uint32() packet.uint8() # Client type. packet.pos += 12 # Checksum for files if (len(packet.data) - packet.pos) == 128: # RSA 1024 is always 128 packet.data = otcrypto.decryptRSA(packet.getData()) packet.pos = 0 # Reset position else: print("RSA, length != 128 (it's %d)" % (len(packet.data) - packet.pos)) self.transport.loseConnection() return v = packet.uint8() if not len(packet.data) or v != 0: # RSA needs to decrypt just fine, so we get the data, and the first byte should be 0 print("RSA, first char != 0 ") self.transport.loseConnection() return # Set the XTEA key k = (packet.uint32(), packet.uint32(), packet.uint32(), packet.uint32()) sum = 0 self.xtea = [0] * 64 for x in range(32): self.xtea[x] = sum + k[sum & 3] & 0xffffffff sum = (sum + 0x9E3779B9) & 0xffffffff self.xtea[32 + x] = sum + k[sum>>11 & 3] & 0xffffffff # If cffi, cast this. if otcrypto.ffi: self.xtea = otcrypto.ffi.new("const uint32_t[]", self.xtea) # Check if version is correct if version > config.versionMax or version < config.versionMin: self.exitWithError(config.versionError) print("Version incorrect") return # Check if there is a username (and a password) username = packet.string() password = packet.string() if not username and not config.anyAccountWillDo: self.exitWithError("You must enter your account number") return # Initialize the packet to send pkg = TibiaPacket() if username: # Our funny way of doing async SQL account = yield sql.runQuery("SELECT `id`, `premdays` FROM `accounts` WHERE `name` = %s AND `password` = SHA1(CONCAT(`salt`, %s))", username, password) if account: characters = yield sql.runQuery("SELECT `name`,`world_id` FROM `players` WHERE account_id = %s", account[0]['id']) if not username or not account: if config.anyAccountWillDo: account = ((0,0),) characters = config.anyAccountPlayerMap else: self.exitWithError("Invalid username or password") return if config.letGameServerRunTheLoginServer: import game.scriptsystem game.scriptsystem.get("preSendLogin").run(client=self, characters=characters, account=account, username=username, password=password) # Send motd pkg.uint8(0x14) pkg.string(config.motd) # Add character list pkg.uint8(0x64) pkg.uint8(len(characters)) for character in characters: ip = config.servers[character['world_id']][0] port = config.gamePort if ':' in ip: ip, port = ip.split(':') port = int(port) if self.address == '127.0.0.1': ip = '127.0.0.1' elif ip in IPS: ip = IPS[ip] elif ip != 'auto': _ip = ip ip = socket.gethostbyname(ip) IPS[_ip] = ip else: import urllib.request, urllib.error, urllib.parse try: ip = urllib.request.urlopen("http://vapus.net/ip.php").read() except: ip = "" if not ip: raise Exception("[ERROR] Automatic IP service is down!") IPS['auto'] = ip # Save IPS here. pickle.dump(IPS, open('IP_CACHE', 'wb'), 2) pkg.string(character['name']) pkg.string(config.servers[character['world_id']][1]) pkg.raw(socket.inet_aton(ip if type(ip) == str else ip.decode('utf-8'))) pkg.length += 4 pkg.uint16(port) if version >= 980: pkg.uint8(0) # Add premium days pkg.uint16(account[0]['premdays']) pkg.send(self) # Send
def exitWithError(self, message, error = 0x0A): packet = TibiaPacket() packet.uint8(error) # Error code packet.string(message) # Error message packet.send(self) self.loseConnection()
def onFirstPacket(self, packet): global IPS try: packet.uint8() except: return if config.letGameServerRunTheLoginServer: pos = packet.pos packet.pos = 0 packetType = packet.uint8() if packetType == 0xFF: if packetType == 0xFF: if packet.getX(4) == "info": try: sendPlayers == packet.uint8() except: sendPlayers = False data = "" # TODO: Send XML format. elif packetType == 0x01: # Silly status protocol. No multi world support... reqInfo = packet.uint16() pkg = TibiaPacket() if reqInfo & 0x01: # REQUEST_BASIC_SERVER_INFO pkg.uint8(0x10) pkg.string(config.server[0][0]) pkg.string(config.server[0][1]) pkg.uint16(4) pkg.uint32(config.loginPort) if reqInfo & 0x02: # REQUEST_SERVER_OWNER_INFO pkg.uint8(0x11) pkg.string(config.ownerName) pkg.string(config.ownerEmail) if reqInfo & 0x04: # REQUEST_MISC_SERVER_INFO pkg.uint8(0x12) pkg.string(config.motd) pkg.string(config.location) pkg.string(config.url) pkg.uint64(time.time() - SERVER_START + config.tibiaTimeOffset) if reqInfo & 0x08: # REQUEST_PLAYERS_INFO pkg.uint8(0x20) pkg.uint32(len(core.game.allPlayers)) pkg.uint32(config.gameMaxConnections) pkg.uint32(len(core.game.allPlayers)) # TODO: Track record. if reqInfo & 0x10: # REQUEST_SERVER_MAP_INFO pkg.uint8(0x30) pkg.string(core.game.map.mapInfo.description) pkg.string(core.game.map.mapInfo.author) pkg.uint16(core.game.map.mapInfo.width) pkg.uint16(core.game.map.mapInfo.height) # 0x20 and 0x40 is unimplanted. if reqInfo & 0x80: pkg.uint8(0x23) pkg.string("PyOT") pkg.string(SERVER_VERSION) pkg.string("%s-%s" % (config.versionMin, config.versionMax)) pkg.send(self) return packet.pos = pos packet.pos += 2 #packet.uint16() # OS 0x00 and 0x01 version = packet.uint16() # Version int if version >= 971: version = packet.uint32() packet.uint8() # Client type. packet.pos += 12 # Checksum for files if (len(packet.data) - packet.pos) == 128: # RSA 1024 is always 128 packet.data = otcrypto.decryptRSA(packet.getData()) # NOTICE: We don't have to yield this since we are already in a seperate thread? packet.pos = 0 # Reset position else: log.msg("RSA, length != 128 (it's %d)" % (len(packet.data) - packet.pos)) self.transport.loseConnection() return if not packet.data or packet.uint8(): # RSA needs to decrypt just fine, so we get the data, and the first byte should be 0 log.msg("RSA, first char != 0") self.transport.loseConnection() return # Set the XTEA key k = (packet.uint32(), packet.uint32(), packet.uint32(), packet.uint32()) sum = 0 self.xtea = {} for x in xrange(32): self.xtea[x] = sum + k[sum & 3] & 0xffffffff sum = (sum + 0x9E3779B9) & 0xffffffff self.xtea[32 + x] = sum + k[sum>>11 & 3] & 0xffffffff # Check if version is correct if version > config.versionMax or version < config.versionMin: self.exitWithError(config.versionError) return # Check if there is a username (and a password) username = packet.string() password = packet.string() if not username and not config.anyAccountWillDo: self.exitWithError("You must enter your account number") return # Initialize the packet to send pkg = TibiaPacket() if username: # Our funny way of doing async SQL account = yield sql.conn.runQuery("SELECT `id`, `premdays` FROM `accounts` WHERE `name` = %s AND `password` = SHA1(CONCAT(`salt`, %s))", (username, password)) if account: characters = yield sql.conn.runQuery("SELECT `name`,`world_id` FROM `players` WHERE account_id = %s", (account[0]['id'])) if not username or not account: if config.anyAccountWillDo: account = ((0,0),) characters = config.anyAccountPlayerMap else: self.exitWithError("Invalid username or password") return if config.letGameServerRunTheLoginServer: import game.scriptsystem game.scriptsystem.get("preSendLogin").runSync(None, client=self, characters=characters, account=account, username=username, password=password) # Send motd pkg.uint8(0x14) pkg.string(config.motd) # Add character list pkg.uint8(0x64) pkg.uint8(len(characters)) for character in characters: ip = config.servers[character['world_id']][0] port = config.gamePort if ':' in ip: ip, port = ip.split(':') port = int(port) if self.transport.getPeer().host == '127.0.0.1': ip = '127.0.0.1' elif ip in IPS: ip = IPS[ip] elif ip != 'auto': _ip = ip ip = socket.gethostbyname(ip) IPS[_ip] = ip else: import urllib2 try: ip = urllib2.urlopen("http://vapus.net/ip.php").read() except: ip = "" if not ip: raise Exception("[ERROR] Automatic IP service is down!") IPS['auto'] = ip # Save IPS here. cPickle.dump(IPS, open('IP_CACHE', 'wb'), 2) pkg.string(character['name']) pkg.string(config.servers[character['world_id']][1]) pkg.raw(socket.inet_aton(ip)) pkg.uint16(port) if version >= 980: pkg.uint8(0) # Add premium days pkg.uint16(account[0]['premdays']) pkg.send(self) # Send