Beispiel #1
0
def tibiaproxy_main(config):
    """tibiaproxy's entry point."""

    if config['debug']:
        sys.excepthook = run_pdb_hook

    plugins = []
    for filename in os.listdir('plugins'):
        if filename == "__init__.py" or filename[-3:] != ".py":
            continue
        plugin_name = filename[:-3]
        plugins += [importlib.import_module('plugins.' + plugin_name)]
        log("Loaded plugin %s." % plugin_name)

    server = Server(destination_login_host=config['destination_login_host'],
                    destination_login_port=config['destination_login_port'],
                    listen_login_host=config['listen_login_host'],
                    listen_login_port=config['listen_login_port'],
                    listen_game_host=config['listen_game_host'],
                    listen_game_port=config['listen_game_port'],
                    announce_host=config['announce_host'],
                    announce_port=config['announce_port'],
                    real_tibia=config['real_tibia'],
                    debug=config['debug'],
                    plugins=plugins)
    server.run()
Beispiel #2
0
def tibiaproxy_main(config):
    """tibiaproxy's entry point."""

    if config['debug']:
        sys.excepthook = run_pdb_hook

    plugins = []
    for filename in os.listdir('plugins'):
        if filename == "__init__.py" or filename[-3:] != ".py":
            continue
        plugin_name = filename[:-3]
        plugins += [importlib.import_module('plugins.' + plugin_name)]
        log("Loaded plugin %s." % plugin_name)

    server = Server(destination_login_host=config['destination_login_host'],
                    destination_login_port=config['destination_login_port'],
                    listen_login_host=config['listen_login_host'],
                    listen_login_port=config['listen_login_port'],
                    listen_game_host=config['listen_game_host'],
                    listen_game_port=config['listen_game_port'],
                    announce_host=config['announce_host'],
                    announce_port=config['announce_port'],
                    real_tibia=config['real_tibia'],
                    debug=config['debug'],
                    plugins=plugins)
    server.run()
Beispiel #3
0
 def accept_game_conn():
     conn, addr = self.g_s.accept()
     log("Received a game server connection from %s:%s" % addr)
     if not self.debug:
         t = threading.Thread(target=self.handleGame, args=[conn])
         t.start()
     else:
         self.handleGame(conn)
Beispiel #4
0
 def accept_login_conn():
     conn, addr = self.l_s.accept()
     log("Received a login connection from %s:%s" % addr)
     data = conn.recv(1024)
     msg = NetworkMessage(data)
     if not self.debug:
         t = threading.Thread(target=self.handleLogin, args=[conn, msg])
         t.start()
     else:
         self.handleLogin(conn, msg)
Beispiel #5
0
def parseReply(msg, xtea_key):
    """Parse the reply from the login server.

    Args:
        msg (NetworkMessage): the network message to be parsed.

    Returns dict or None
    """
    size = msg.getU16()

    # TODO: someday perhaps I'll have enough time to even check the checksums!
    msg.skipBytes(4)

    msg_buf = XTEA.XTEA_decrypt(msg.getRest(), xtea_key)
    msg = NetworkMessage(msg_buf)
    #assert(len(msg.getWithHeader()) == size)
    decrypted_size = msg.getU16()
    #assert(decrypted_size == size - 5)

    packet_type = msg.getByte()
    if packet_type != 0x14:
        # The reply doesn't seem to contain character list.
        return None
    motd = msg.getString()

    assert (msg.getByte() == 0x64)

    num_worlds = msg.getByte()
    worlds = []
    for _ in range(num_worlds):
        world_id = msg.getByte()
        world_name = msg.getString()
        world_hostname = msg.getString()
        world_port = msg.getU16()
        log("Received server address %s:%s" % (world_hostname, world_port))
        msg.skipBytes(1)  # no idea what's that.
        worlds += [
            create_login_world_entry(name=world_name,
                                     hostname=world_hostname,
                                     port=world_port)
        ]

    num_chars = msg.getByte()
    characters = []
    for _ in range(num_chars):
        world_num = msg.getByte()
        char_world = worlds[world_num]
        char_name = msg.getString()
        characters += [
            create_login_character_entry(name=char_name, world=char_world)
        ]

    return create_login_reply_info(characters, motd, worlds)
Beispiel #6
0
def parseReply(msg, xtea_key):
    """Parse the reply from the login server.

    Args:
        msg (NetworkMessage): the network message to be parsed.

    Returns dict or None
    """
    size = msg.getU16()

    # TODO: someday perhaps I'll have enough time to even check the checksums!
    msg.skipBytes(4)

    msg_buf = XTEA.XTEA_decrypt(msg.getRest(), xtea_key)
    msg = NetworkMessage(msg_buf)
    #assert(len(msg.getWithHeader()) == size)
    decrypted_size = msg.getU16()
    #assert(decrypted_size == size - 5)

    packet_type = msg.getByte()
    if packet_type != 0x14:
        # The reply doesn't seem to contain character list.
        return None
    motd = msg.getString()

    assert(msg.getByte() == 0x64)

    num_worlds = msg.getByte()
    worlds = []
    for _ in range(num_worlds):
        world_id = msg.getByte()
        world_name = msg.getString()
        world_hostname = msg.getString()
        world_port = msg.getU16()
        log("Received server address %s:%s" % (world_hostname, world_port))
        msg.skipBytes(1)  # no idea what's that.
        worlds += [create_login_world_entry(name=world_name,
                                            hostname=world_hostname,
                                            port=world_port)]

    num_chars = msg.getByte()
    characters = []
    for _ in range(num_chars):
        world_num = msg.getByte()
        char_world = worlds[world_num]
        char_name = msg.getString()
        characters += [create_login_character_entry(name=char_name,
                                                    world=char_world)]

    return create_login_reply_info(characters, motd, worlds)
Beispiel #7
0
 def getTile():
     got_effect = False
     for stackPos in range(256):
         if msg.peekU16() >= 0xff00:
             return msg.getU16() & 0xff
         if not got_effect:
             msg.getU16()
             got_effect = True
             continue
         assert(stackPos <= 10)
         thingId = msg.getU16()
         log(thingId)
         assert(thingId != 0)
         if thingId == 96:
             log("strange... got a staticText")
         elif thingId in [97, 98, 99]:
             # it's a creature
             pass
         else:
             # it's a thing - might require reading 3
             # extra bytes
             pass
Beispiel #8
0
    def handleLogin(self, conn, msg):
        """Handles the login communication, passing it to the destination host,
        modifying the server IPs and returning the modified character list to
        the user.

        Args:
            conn (socket): the already established connection.
            msg (NetworkMessage): the first message received.

        Returns None
        """
        xtea_key = LoginProtocol.parseFirstMessage(msg)

        # Connect to the destination host, send the request and read the reply.
        dest_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        log("Connecting to the destination host...")
        dest_s.connect((self.destination_login_host,
                        self.destination_login_port))
        if not self.real_tibia:
            dest_s.send(msg.getRaw())
        else:
            reencrypted = RSA.RSA_encrypt(RSA.RSA_decrypt(msg.getRaw()[28:]))
            unencrypted = msg.getRaw()[6:28]
            new_buf = ""
            new_buf += msg.getRaw()[:2]
            new_buf += struct.pack("<I",
                                   adlerChecksum(unencrypted+reencrypted))
            new_buf += unencrypted
            new_buf += reencrypted
            dest_s.send(new_buf)
        data = dest_s.recv(1024)
        if data == '':
            log("Server disconnected.")
            conn.close()
            return
        msg = NetworkMessage(data)
        reply = LoginProtocol.parseReply(msg, xtea_key)
        if reply is None:
            # The reply doesn't seem to contain character list - just forward
            # it.
            log("WARNING: Passing through the login request.")
            conn.send(data)
            conn.close()
            return

        for character in reply['characters']:
            self.characters[character['name']] = character

        # Replace the IP and port with the address to the proxy.
        client_reply = copy.deepcopy(reply)
        for world in client_reply['worlds']:
            world['hostname'] = self.announce_host
            world['port'] = self.announce_port
        client_reply_msg = LoginProtocol.prepareReply(client_reply)
        # Send the message and close the connection.
        conn.send(client_reply_msg.getEncrypted(xtea_key))
        conn.close()
Beispiel #9
0
    def run(self):
        """Run serveLogin and serveGame threads and sleep forever.

        Returns None
        """
        log(("Listening on address %s:%s (login), %s:%s (game), connections " +
             "will be forwarded to %s:%s") % (self.listen_login_host,
                                              self.listen_login_port,
                                              self.listen_game_host,
                                              self.listen_game_port,
                                              self.destination_login_host,
                                              self.destination_login_port))

        self.l_s.listen(1)
        self.g_s.listen(1)

        if not self.debug:
            t_l = threading.Thread(target=self.serveLogin)
            g_l = threading.Thread(target=self.serveGame)

            t_l.daemon = True
            g_l.daemon = True

            t_l.start()
            g_l.start()
        else:
            self.serveLogin(True)
            self.serveGame(True)

        if not self.debug:
            # http://stackoverflow.com/q/3788208/1091116
            try:
                while True:
                    time.sleep(100)
            except (KeyboardInterrupt, SystemExit):
                sys.exit("Received keyboard interrupt, quitting")
Beispiel #10
0
    def handleGame(self, conn):
        """Connect to the game server, relay the packets between the the
        player, the proxy and the game server, reading them and running
        callbacks based on incoming/outgoing data.

        TODO: add anything beyond the eval() proof of concept. This function
        is one giant kludge at the moment.

        Args:
            conn (socket): the already established connection.
            data (str): the raw data sent by the player.

        Returns None
        """

        # send a bogus challenge = 109, timestamp = 1385139009
        conn.send(b'\x0c\x00@\x02!\x07\x06\x00\x1fA\x8b\x8fRm')

        data = conn.recv(2)
        size = struct.unpack("<H", data)[0]
        data += conn.recv(size)
        # Read the XTEA key from the player, pass on the original packet.
        msg = NetworkMessage(data)
        firstmsg_contents = GameProtocol.parseFirstMessage(msg)

        # Connect to the game server.
        dest_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        character = self.characters[firstmsg_contents['character_name']]
        game_host = character['world']['hostname']
        game_port = character['world']['port']
        log("Connecting to the game server (%s:%s)." % (game_host, game_port))
        dest_s.connect((game_host, game_port))
        size_raw = dest_s.recv(2)
        size = struct.unpack("<H", size_raw)[0]
        checksum = dest_s.recv(4)
        data = dest_s.recv(size - 4)
        msg = NetworkMessage(data)

        challenge_data = GameProtocol.parseChallengeMessage(msg)

        xtea_key = firstmsg_contents['xtea_key']
        firstmsg_contents['timestamp'] = challenge_data['timestamp']
        firstmsg_contents['random_number'] = challenge_data['random_number']
        dest_s.send(GameProtocol.prepareReply(firstmsg_contents,
                                              self.real_tibia))

        conn_obj = Connection(conn, xtea_key)
        received_player = False
        while True:
            # Wait until either the player or the server sent some data.
            has_data, _, _ = select.select([conn, dest_s], [], [])
            if conn in has_data:
                data = bytearray()
                size_raw = conn.recv(2)
                data += size_raw
                if size_raw == bytearray():
                    log("The client disconnected")
                    break
                size = struct.unpack("<H", size_raw)[0]
                data += conn.recv(size+4)
                msg = NetworkMessage(data)
                msg_size = msg.getU16()
                msg.getU32()  # skip the checksum validation
                if msg_size != len(data) - 2:
                    log("Strange packet from client: %s" % repr(data))
                    log("len(data)=%s, msg_size=%s" % (len(data), msg_size))
                    dest_s.send(data)
                    continue
                msg_buf = XTEA.XTEA_decrypt(msg.getRest(), xtea_key)
                msg = NetworkMessage(msg_buf)
                msg.getU16()
                packet_type = msg.getByte()
                if packet_type in GameProtocol.client_packet_types:
                    if self.debug:
                        log("C [%s] %s" % (hex(packet_type),
                            GameProtocol.client_packet_types[packet_type]))
                else:
                    log("Got a packet of type %s from client" % packet_type)
                should_forward = True
                if packet_type == 150:
                    # We got a player "say" request. Read what the player
                    # wanted to say, treat it like a Python expression and send
                    # the result back to the user.
                    msg.skipBytes(1)
                    player_said = msg.getString()
                    for plugin in self.plugins:
                        if 'on_client_say' in dir(plugin):
                            plugin_returned = plugin.on_client_say(conn_obj,
                                                                   player_said)

                            if plugin_returned:
                                should_forward = False

                if should_forward:
                    # Otherwise, just pass the packet to the server.
                    dest_s.send(data)
            if dest_s in has_data:
                # Server sent us some data.
                data = bytearray()
                size_raw = dest_s.recv(2)
                data += size_raw
                if data == bytearray():
                    conn.close()
                    log("The server disconnected")
                    break
                size = struct.unpack("<H", size_raw)[0]
                data += dest_s.recv(size)
                msg = NetworkMessage(data)
                msg_size = msg.getU16()
                msg.getU32()  # skip the checksum validation
                if msg_size != len(data) - 2:
                    log("Strange packet from server: %s" % repr(data))
                    log("len(data)=%s, msg_size=%s" % (len(data), msg_size))
                    conn.send(data)
                    continue
                msg_buf = XTEA.XTEA_decrypt(msg.getRest(), xtea_key)
                msg = NetworkMessage(msg_buf)
                msg.getU16()
                while msg.finished():
                    def getMapDescription():
                        def getTile():
                            got_effect = False
                            for stackPos in range(256):
                                if msg.peekU16() >= 0xff00:
                                    return msg.getU16() & 0xff
                                if not got_effect:
                                    msg.getU16()
                                    got_effect = True
                                    continue
                                assert(stackPos <= 10)
                                thingId = msg.getU16()
                                log(thingId)
                                assert(thingId != 0)
                                if thingId == 96:
                                    log("strange... got a staticText")
                                elif thingId in [97, 98, 99]:
                                    # it's a creature
                                    pass
                                else:
                                    # it's a thing - might require reading 3
                                    # extra bytes
                                    pass

                        def getFloorDescription(x, y, z, w, h, offset, skip):
                            for nx in range(w):
                                for ny in range(h):
                                    getTile()

                        x, y, z = msg.getCoordinates()
                        w = 18
                        h = 14
                        skip = [-1]
                        if z > 7:
                            startz = z - 2
                            endz = min(16 - 1, z + 2)
                            zstep = 1
                        else:
                            startz = 7
                            endz = 0
                            zstep = -1
                        nz = startz
                        while nz != endz + zstep:
                            getFloorDescription(x, y, nz, w, h, z - nz, skip)
                            nz += zstep
                    packet_type = msg.getByte()
                    if packet_type in GameProtocol.server_packet_types:
                        if self.debug:
                            log("S [%s] %s" % (hex(packet_type),
                                GameProtocol.server_packet_types[packet_type])
                                )
                    else:
                        log("Got a packet of type %s from server" %
                            packet_type)
                    # login successful
                    if packet_type == 0x17:
                        msg.getU32()   # player ID
                        msg.getU16()   # beat duration
                        msg.getU32()   # speed A
                        msg.getU32()   # speed B
                        msg.getU32()   # speed C
                        msg.getByte()  # 1 if can report bugs, 0 otherwise

                        # no idea what's this:
                        assert_equal(msg.getByte(), 0xB6)
                        assert_equal(msg.getByte(), 0x7F)
                        assert_equal(msg.getByte(), 0x00)
                        assert_equal(msg.getByte(), 0x0A)
                        assert_equal(msg.getByte(), 0x0F)
                        assert_equal(msg.getByte(), 0x64)
                        getMapDescription()
                        log("TODO")
                        received_player = True
                    # FYI box
                    if packet_type == 0x15:
                        fyi = msg.getString()
                        log("Got a FYI: %s" % fyi)

                log("Sending!")

                conn.send(data)