Beispiel #1
0
    def connectionMade(self):
        """
        I am called when a connection has been established to a Minecraft server
        """
        self.writer = MinecraftWriter(self.transport)
        self.reader = MinecraftReader()
        self.dataReceived = self.reader.dataReceived

        self.read_loop()

        log.msg("ATTEMPTING TO HANDSHAKE")
        self.send_handshake(self.username)
 def setUp(self):
     self.transport = MockTransport()
     self.w = MinecraftWriter(self.transport)
Beispiel #3
0
class BaseMinecraftClientProtocol(Protocol):

    """
    I am a basic implementation of the client protocol

    I only ensure successful connection to the server, and do some of the heavy
    lifting processing the protocol. My subclasses are responsible for actually 
    implementing useful clients
    """

    server_password = "******"

    def __init__(self, username, password, session_id):
        self.username = username
        self.password = password
        self.session_id = session_id
        self.state = "not-ready"

    def connectionMade(self):
        """
        I am called when a connection has been established to a Minecraft server
        """
        self.writer = MinecraftWriter(self.transport)
        self.reader = MinecraftReader()
        self.dataReceived = self.reader.dataReceived

        self.read_loop()

        log.msg("ATTEMPTING TO HANDSHAKE")
        self.send_handshake(self.username)

    def connectionLost(self, reason):
        log.msg("connectionLost", reason)

    @defer.inlineCallbacks
    def read_loop(self):
        """
        I constantly read incoming packets off the wire. I magically suspend when there is no more data
        and resume when there is (see MinecraftReader for how that is implemented)
        """
        log.msg("Entering read loop")
        previous_packet_id = -1

        while True:
            packet_id = yield self.reader.read_packet_id()
            #log.msg("Got packet: 0x%02x" % packet_id)

            if packet_id == 0x00:
                self.on_keep_alive()

            elif packet_id == 0x01:
                unknown1 = yield self.reader.read_int()
                unknown2 = yield self.reader.read_string()
                unknown3 = yield self.reader.read_string()
                unknown4 = yield self.reader.read_long()
                unknown5 = yield self.reader.read_byte()
                self.on_login_response(unknown1, unknown2, unknown3)

            elif packet_id == 0x02:
                connection_hash = yield self.reader.read_string()
                self.on_handshake(connection_hash)

            elif packet_id == 0x03:
                message = yield self.reader.read_string()
                self.on_chat_message(message)

            elif packet_id == 0x04:
                time = yield self.reader.read_long()
                self.on_time_update(time)

            elif packet_id == 0x05:
                #type = yield self.reader.read_int()
                #count = yield self.reader.read_short()

                #payload = {}
                #for i in range(count):
                #    item_id = yield self.reader.read_short()
                #    if item_id != -1:
                #        count = yield self.reader.read_byte()

                #        health = yield self.reader.read_short()
                #        payload[i] = (item_id, count, health)

                #self.on_player_inventory(type, count, payload)

                entity_id = yield self.reader.read_int()
                slot = yield self.reader.read_short()
                item_id = yield self.reader.read_short()
                unknown = yield self.reader.read_short()
                #self.on_entity_equipment(entity_id, slot, item_id, unknown)

            elif packet_id == 0x06:
                x = yield self.reader.read_int()
                y = yield self.reader.read_int()
                z = yield self.reader.read_int()
                self.on_spawn_position(x, y, z)

            elif packet_id == 0x07:
                user = yield self.reader.read_int()
                target = yield self.reader.read_int()
                left_click = yield self.reader.read_bool()
                #self.on_use(user, target, left_click)

            elif packet_id == 0x08:
                half_hearts = yield self.reader.read_byte()
                self.on_player_health(half_hearts)

            elif packet_id == 0x09:
                #self.on_player_respawn()
                pass

            elif packet_id == 0x10:
                slot_id = yield self.reader.read_short()
                #self.on_holding_change(slot_id)

            elif packet_id == 0x0D:
                x = yield self.reader.read_double()
                y = yield self.reader.read_double()
                stance = yield self.reader.read_double()
                #y = yield self.reader.read_double()
                z = yield self.reader.read_double()
                yaw = yield self.reader.read_float()
                pitch = yield self.reader.read_float()
                on_ground = yield self.reader.read_bool()
                self.on_player_position_and_look(x, stance, y, z, yaw, pitch, on_ground)

            elif packet_id == 0x11:
                #item_type = yield self.reader.read_short()
                #count = yield self.reader.read_byte()
                #life = yield self.reader.read_short()
                #self.on_add_to_inventory(item_type, count, life)
                entity_id = yield self.reader.read_int()
                in_bed = yield self.reader.read_byte()
                x = yield self.reader.read_int()
                y = yield self.reader.read_byte()
                z = yield self.reader.read_int()
                # self.on_use_bed()

            elif packet_id == 0x12:
                eid = yield self.reader.read_int()
                animate = yield self.reader.read_byte()
                self.on_arm_animation(eid, animate)

            elif packet_id == 0x13:
                eid = yield self.reader.read_int()
                action = yield self.reader.read_byte()
                # self.on_action()

            elif packet_id == 0x14:
                eid = yield self.reader.read_int()
                player_name = yield self.reader.read_string()
                x = yield self.reader.read_int()
                y = yield self.reader.read_int()
                z = yield self.reader.read_int()
                rotation = yield self.reader.read_byte()
                pitch = yield self.reader.read_byte()
                current_item = yield self.reader.read_short()
                self.on_named_entity_spawn(eid, player_name, x, y, z, rotation, pitch, current_item)

            elif packet_id == 0x15:
                eid = yield self.reader.read_int()
                item = yield self.reader.read_short()
                count = yield self.reader.read_byte()
                x = yield self.reader.read_int()
                y = yield self.reader.read_int()
                z = yield self.reader.read_int()
                rotation = yield self.reader.read_byte()
                pitch = yield self.reader.read_byte()
                roll = yield self.reader.read_byte()
                self.on_pickup_spawn(eid, item, count, x, y, z, rotation, pitch, roll)

            elif packet_id == 0x16:
                collected_eid = yield self.reader.read_int()
                collector_eid = yield self.reader.read_int()
                self.on_collect_item(collected_eid, collector_eid)

            elif packet_id == 0x17:
                eid = yield self.reader.read_int()
                type = yield self.reader.read_byte()
                x = yield self.reader.read_int()
                y = yield self.reader.read_int()
                z = yield self.reader.read_int()
                self.on_add_object_vehicle(eid, type, x, y, z)

            elif packet_id == 0x18:
                eid = yield self.reader.read_int()
                type = yield self.reader.read_byte()
                x = yield self.reader.read_int()
                y = yield self.reader.read_int()
                z = yield self.reader.read_int()
                yaw = yield self.reader.read_byte()
                pitch = yield self.reader.read_byte()

                # FIXME:Indexed etadata....
                yield self.reader.read_metadata()

                self.on_mob_spawn(eid, type, x, y, z, yaw, pitch)

            elif packet_id == 0x19:
                # painting
                eid = yield self.reader.read_int()
                title = yield self.reader.read_string()
                x = yield self.reader.read_int()
                y = yield self.reader.read_int()
                z = yield self.reader.read_int()
                direction = yield self.reader.read_int()

            elif packet_id == 0x1B:
                yield self.reader.read_float()
                yield self.reader.read_float()
                yield self.reader.read_float()
                yield self.reader.read_float()
                yield self.reader.read_bool()
                yield self.reader.read_bool()

            elif packet_id == 0x1C:
                eid = yield self.reader.read_int()
                x = yield self.reader.read_short()
                y = yield self.reader.read_short()
                z = yield self.reader.read_short()

            elif packet_id == 0x1D:
                eid = yield self.reader.read_int()
                self.on_destroy_entity(eid)

            elif packet_id == 0x1E:
                eid = yield self.reader.read_int()
                self.on_entity(eid)

            elif packet_id == 0x1F:
                eid = yield self.reader.read_int()
                x = yield self.reader.read_byte()
                y = yield self.reader.read_byte()
                z = yield self.reader.read_byte()
                self.on_entity_relative_move(eid, x, y, z)

            elif packet_id == 0x20:
                eid = yield self.reader.read_int()
                yaw = yield self.reader.read_byte()
                pitch = yield self.reader.read_byte()
                self.on_entity_look(eid, yaw, pitch)

            elif packet_id == 0x21:
                eid = yield self.reader.read_int()
                x = yield self.reader.read_byte()
                y = yield self.reader.read_byte()
                z = yield self.reader.read_byte()
                yaw = yield self.reader.read_byte()
                pitch = yield self.reader.read_byte()
                self.on_entity_look_and_relative_move(eid, x, y, z, yaw, pitch)

            elif packet_id == 0x22:
                eid = yield self.reader.read_int()
                x = yield self.reader.read_int()
                y = yield self.reader.read_int()
                z = yield self.reader.read_int()
                yaw = yield self.reader.read_byte()
                pitch = yield self.reader.read_byte()
                self.on_entity_teleport(eid, x, y, z, yaw, pitch)

            elif packet_id == 0x26:
                eid = yield self.reader.read_int()
                damage = yield self.reader.read_byte()
                #self.on_entity_damage(eid, damage)

            elif packet_id == 0x27:
                eid = yield self.reader.read_int()
                ignore2 = yield self.reader.read_int()

            elif packet_id == 0x28:
                eid = yield self.reader.read_int()
                metadata = yield self.reader.read_metadata()

            elif packet_id == 0x32:
                x = yield self.reader.read_int()
                z = yield self.reader.read_int()
                mode = yield self.reader.read_bool()
                self.on_pre_chunk(x, z, mode)

            elif packet_id == 0x33:
                x = yield self.reader.read_int()
                y = yield self.reader.read_short()
                z = yield self.reader.read_int()
                size_x = yield self.reader.read_byte()
                size_y = yield self.reader.read_byte()
                size_z = yield self.reader.read_byte()
                compressed_chunk_size = yield self.reader.read_int()
                compressed_chunk = yield self.reader.read_raw(compressed_chunk_size)
                self.on_map_chunk(x, y, z, size_x, size_y, size_z, compressed_chunk_size, compressed_chunk)

            elif packet_id == 0x34:
                chunk_x = yield self.reader.read_int()
                chunk_z = yield self.reader.read_int()
                array_size = yield self.reader.read_short()
                coords = yield self.reader.read_array(self.reader.read_short, array_size)
                kinds = yield self.reader.read_array(self.reader.read_byte, array_size)
                metadatas = yield self.reader.read_array(self.reader.read_byte, array_size)
                self.on_multi_block_change(chunk_x, chunk_z, array_size, coords, kinds, metadatas)

            elif packet_id == 0x35:
                x = yield self.reader.read_int()
                y = yield self.reader.read_byte()
                z = yield self.reader.read_int()
                type = yield self.reader.read_byte()
                metadata = yield self.reader.read_byte()
                self.on_block_change(x, y, z, type, metadata)

            elif packet_id == 0x36:
                x = yield self.reader.read_int()
                y = yield self.reader.read_short()
                z = yield self.reader.read_int()
                instrument_type = yield self.reader.read_byte()
                pitch = yield self.reader.read_byte()

            elif packet_id == 0x3B:
                x = yield self.reader.read_int()
                y = yield self.reader.read_short()
                z = yield self.reader.read_int()
                payload_size = yield self.reader.read_short()
                payload_raw = yield self.reader.read_raw(payload_size)

                #payload = yield NBTReader(StringIO(payload_raw)).read_nbt()
                #print payload
                payload = {}

                self.on_complex_entity(x, y, z, payload)

            elif packet_id == 0x3C:
                x = yield self.reader.read_double()
                y = yield self.reader.read_double()
                z = yield self.reader.read_double()
                unknown = yield self.reader.read_float()
                record_count = yield self.reader.read_int()
                for i in range(record_count):
                    yield self.reader.read_byte()
                    yield self.reader.read_byte()
                    yield self.reader.read_byte()

            elif packet_id == 0x46:
                unknown1 = yield self.reader.read_byte()
                self.on_invalid_bed(unknown1)

            elif packet_id == 0x64:
                yield self.reader.read_byte()
                yield self.reader.read_byte()
                yield self.reader.read_string()
                yield self.reader.read_byte()

            elif packet_id == 0x65:
                yield self.reader.read_byte()

            elif packet_id == 0x67:
                window = yield self.reader.read_byte()
                slot = yield self.reader.read_short()
                item_id = yield self.reader.read_short()
                if item_id != -1:
                    item_count = yield self.reader.read_byte()
                    item_uses = yield self.reader.read_short()

            elif packet_id == 0x68:
                window = yield self.reader.read_byte()
                count = yield self.reader.read_short()

                payload = {}
                for i in range(count):
                    item_id = yield self.reader.read_short()
                    if item_id != -1:
                        count = yield self.reader.read_byte()
                        health = yield self.reader.read_short()
                        payload[i] = (item_id, count, health)

            elif packet_id == 0x69:
                window = yield self.reader.read_byte()
                progress = yield self.reader.read_short()
                value = yield self.reader.read_short()

            elif packet_id == 0x6A:
                window = yield self.read_byte()
                action = yield self.read_short()
                accepted = yield self.read_bool()

            elif packet_id == 0x82:
                x = yield self.reader.read_int()
                y = yield self.reader.read_short()
                z = yield self.reader.read_int()
                text1 = yield self.reader.read_string()
                text2 = yield self.reader.read_string()
                text3 = yield self.reader.read_string()
                text4 = yield self.reader.read_string()

            elif packet_id == 0xFF:
                reason = yield self.reader.read_string()
                self.on_kick(reason)
                defer.returnValue(None)

            else:
                log.msg("Got unknown packet_id: %x, Previous was: %x" % (packet_id, previous_packet_id))
                self.send_disconnect("I'm sorry Dave, didn't understand that")
                defer.returnValue(None)

            previous_packet_id = packet_id
            #log.msg("Packet processed")

    def on_keep_alive(self):
        self.send_keep_alive()

    def on_login_response(self, some_int, some_string_1, some_string_2):
        """
        I am sent by the server if it accepts the login request
        """
        pass

    @defer.inlineCallbacks
    def on_handshake(self, connection_hash):
        """
        I am the first packet sent when the client connects and am used for user authentication
        """
        log.msg("on_handshake('%s')" % connection_hash)

        if connection_hash == "-":
            pass
        elif connection_hash == "+":
            pass
        else:
            # Name verification call...
            confirmation = yield getPage("http://www.minecraft.net/game/joinserver.jsp?user=%s&sessionId=%s&serverId=%s" % (self.username, self.session_id, connection_hash.encode("UTF-8")), timeout=60)
            if confirmation != "OK":
                raise ValueError("Minecraft.net says no")

        self.send_login_request(10, self.username, self.server_password, 0, 0)

    def on_chat_message(self, message):
        """
        I am called when the client is sent a chat message
        """
        log.msg("Got message: %s" % message)

    def on_time_update(self, time):
        """
        I am called with the world or region time in minutes
        """
        pass

    def on_player_inventory(self, inv_type, num_items, payload):
        pass

    def on_spawn_position(self, X, Y, Z):
        pass

    def on_player_position_and_look(self, x, stance, y, z, yaw, pitch, on_ground):
        self.send_player_position_and_look(x, y, stance, z, yaw, pitch, on_ground)

        if self.state != "ready":
            self.keepalive = task.LoopingCall(self.send_keep_alive)
            self.keepalive.start(40)

            self.state = "ready"

    def on_player_health(self, half_hearts):
        pass

    def on_add_to_inventory(self, item_type, count, life):
        pass

    def on_arm_animation(self, eid, animate):
        pass

    def on_named_entity_spawn(self, eid, player_name, x, y, z, rotation, pitch, current_item):
        pass

    def on_pickup_spawn(self, eid, item, count, x, y, z, rotation, pitch, roll):
        pass

    def on_collect_item(self, collected_eid, collector_eid):
        pass

    def on_add_object_vehicle(self, eid, type, x, y, z):
        pass

    def on_mob_spawn(self, eid, type, x, y, z, yaw, pitch):
        pass

    def on_destroy_entity(self, eid):
        pass

    def on_entity(self, eid):
        pass

    def on_entity_relative_move(self, eid, x, y, z):
        pass

    def on_entity_look(self, eid, yaw, pitch):
        pass

    def on_entity_look_and_relative_move(self, eid, x, y, z, yaw, pitch):
        pass

    def on_entity_teleport(self, eid, x, y, z, yaw, pitch):
        pass

    def on_pre_chunk(self, x, z, mode):
        pass

    def on_map_chunk(self, x, y, z, size_x, size_y, size_z, compressed_chunk_size, compressed_chunk):
        pass

    def on_multi_block_change(self, chunk_x, chunk_z, array_size, coords, kinds, metadatas):
        pass

    def on_block_change(self, x, y, z, type, metadata):
        pass

    def on_complex_entity(self, x, y, z, payload):
        pass

    def on_kick(self, reason):
        """
        I am called when the server disconnects a client
        """
        log.msg("Got kicked: %s" % reason)
        self.transport.loseConnection()

    def on_invalid_bed(self, unknown1):
        pass

    def send_keep_alive(self):
        self.writer.write_packet_id(0x00)

    def send_login_request(self, protocol_version, username, password, unknown1, unknown2):
        self.writer.write_packet_id(0x01)
        self.writer.write_int(protocol_version)
        self.writer.write_string(username)
        self.writer.write_string(password)
        self.writer.write_long(unknown1)
        self.writer.write_byte(unknown2)

    def send_handshake(self, username):
        self.writer.write_packet_id(0x02)
        self.writer.write_string(username)

    def send_chat_message(self, message):
        self.writer.write_packet_id(0x03)
        self.writer.write_string(message.encode("UTF-8"))

    def send_player_inventory(self, type, count, payload):
        assert False, "Not figured out size/structure of payload yet"
        self.writer.write_packet_id(0x05)
        self.writer.write_int(type)
        self.writer.write_short(count)
        self.writer.write_xxxx(payload)

    def send_use_entity(self, pid, eid):
        self.writer.write_packet_id(0x07)
        self.writer.write_int(pid)
        self.writer.write_int(eid)
        self.writer.write_bool(attacking)

    def send_respawn(self):
        self.writer.write_packet_id(0x09)

    def send_player(self, on_ground):
        self.writer.write_packet_id(0x0A)
        self.writer.write_bool(on_ground)

    def send_player_position(self, x, y, stance, z, on_ground):
        self.writer.write_packet_id(0x0B)
        self.writer.write_double(x)
        self.writer.write_double(y)
        self.writer.write_double(stance)
        self.writer.write_double(z)
        self.writer.write_bool(on_ground)

    def send_player_look(self, yaw, pitch, on_ground):
        self.writer.write_packet_id(0x0C)
        self.writer.write_float(yaw)
        self.writer.write_float(pitch)
        self.writer.write_bool(on_ground)

    def send_player_position_and_look(self, x, y, stance, z, yaw, pitch, on_ground):
        self.writer.write_packet_id(0x0D)
        self.writer.write_double(x)
        self.writer.write_double(y)
        self.writer.write_double(stance)
        self.writer.write_double(z)
        self.writer.write_float(yaw)
        self.writer.write_float(pitch)
        self.writer.write_bool(on_ground)

    def send_player_digging(self, status, x, y, z, face):
        log.msg("dig", status, x, y, z, face)
        assert status >= 0 and status <= 3
        assert face >= 0 and face <= 5
        self.writer.write_packet_id(0x0E)
        self.writer.write_byte(status)
        self.writer.write_int(int(floor(x)))
        self.writer.write_byte(int(floor(y)))
        self.writer.write_int(int(floor(z)))
        self.writer.write_byte(face)

    def send_player_block_placement(self, block_id, x, y, z, direction):
        assert direction >= 0 and direction <= 5
        self.writer.write_packet_id(0x0F)
        self.writer.write_short(block_id)
        self.writer.write_int(int(floor(x)))
        self.writer.write_byte(int(floor(y)))
        self.writer.write_int(int(floor(z)))
        self.writer.write_byte(direction)

    def send_holding_change(self, unused, block_id):
        self.writer.write_packet_id(0x10)
        self.writer.write_int(unused)
        self.writer.write_short(block_id)

    def send_arm_animation(self, unused, animation):
        self.writer.write_packet_id(0x12)
        self.writer.write_int(unused)
        self.writer.write_byte(animation)

    def send_disconnect(self, reason):
        self.writer.write_packet_id(0xFF)
        self.writer.write_string("I'm outta here")

        self.transport.loseConnection()
class TestMinecraftWriter(TestCase):

    def setUp(self):
        self.transport = MockTransport()
        self.w = MinecraftWriter(self.transport)

    def test_write_byte(self):
        self.w.write_byte(0)
        self.failUnlessEqual(self.transport.data, "\x00")

    def test_write_short(self):
        self.w.write_short(0)
        self.failUnlessEqual(self.transport.data, "\x00\x00")

    def test_write_int(self):
        self.w.write_int(0)
        self.failUnlessEqual(self.transport.data, "\x00" * 4)

    def test_write_long(self):
        self.w.write_long(0)
        self.failUnlessEqual(self.transport.data, "\x00" * 8)

    def test_write_float(self):
        self.w.write_float(0.0)
        self.failUnlessEqual(self.transport.data, "\x00" * 4)

    def test_write_double(self):
        self.w.write_double(0.0)
        self.failUnlessEqual(self.transport.data, "\x00" * 8)

    def test_write_string(self):
        self.w.write_string("minecraft")
        self.failUnlessEqual(self.transport.data, "\x00\x09minecraft")

    def test_write_bool_false(self):
        self.w.write_bool(False)
        self.failUnlessEqual(self.transport.data, "\x00")

    def test_write_bool_true(self):
        self.w.write_bool(True)
        self.failUnlessEqual(self.transport.data, "\x01")