Quarry: a Minecraft protocol library¶
Quarry is a Python library that implements the Minecraft protocol. It allows you to write special purpose clients, servers and proxies.
Features¶
- Supports Minecraft versions 1.7 through 1.15.2
- Supports Python 2.7 and 3.5+
- Built upon
twisted
andcryptography
- Exposes base classes and hooks for implementing your own client, server or proxy
- Implements many Minecraft data types, such as NBT, Anvil, chunk sections, command graphs and entity metadata
- Implements the design of the protocol - packet headers, modes, compression, encryption, login/session, etc.
- Implements all packets in “init”, “status” and “login” modes
- Does not implement most packets in “play” mode - it is left up to you to hook and implement the packets you’re interested in
Documentation¶
Networking¶
Writing a Client¶
A client is generally made up of three parts:
- A
Profile
orOfflineProfile
object, representing the Minecraft account to use. - A subclass of
ClientFactory
. Client factories don’t do a lot; simply pass a profile to its initializer and then callconnect()
. You may also want to subclass from twisted’s ReconnectingClientFactory - A subclass of
ClientProtocol
. This represents your connection to the server.
See also
Skeleton Client¶
By default quarry proceeds through the authentication process and then switches into the “play” protocol mode. The skeleton client below will receive world data from the server, but as it does not send any position updates it will be disconnected by the server after a few seconds. Please see the Examples for less silly clients.
from twisted.internet import defer, reactor
from quarry.net.client import ClientFactory, ClientProtocol
from quarry.auth import Profile
class ExampleClientProtocol(ClientProtocol):
pass
class ExampleClientFactory(ClientFactory):
protocol = ExampleClientProtocol
@defer.inlineCallbacks
def main():
print("logging in...")
profile = yield Profile.from_credentials(
"someone@somewhere.com", "p4ssw0rd")
factory = ExampleClientFactory(profile)
print("connecting...")
factory = yield factory.connect("localhost", 25565)
print("connected!")
if __name__ == "__main__":
main()
reactor.run()
Offline Profiles¶
Use an OfflineProfile
if you only need to log into offline-mode
servers:
from quarry.net.auth import OfflineProfile
profile = OfflineProfile("Notch")
Online Profiles¶
Quarry also provides a number of methods for logging in to the Mojang session
servers. Each of these returns a Deferred that will fire with a
Profile
object when login succeeds. Defining a callback and then
calling Profile.from_credentials(...).addCallback(myfunc)
is one approach,
but it’s usually cleaner to use inlineCallbacks, as in the first example.
Writing a Server¶
A server is generally made up of two parts:
- A subclass of
ServerFactory
. Under normal circumstances only oneServerFactory
is instantiated. This object represents your game server as a whole. - A subclass of
ServerProtocol
. Each object represents a connection with a client.
See also
Skeleton Server¶
By default quarry takes clients through the authentication process and then
switches into the “play” protocol mode. Normally at this point you would
implement player_joined()
to either
disconnect the client or start the process of spawning the player. In the
skeleton server below we don’t do either, which leaves the client on the
“Logging in…” screen. Please see the Examples for less pointless
servers.
from twisted.internet import reactor
from quarry.net.server import ServerFactory, ServerProtocol
class ExampleServerProtocol(ServerProtocol):
pass
class ExampleServerFactory(ClientFactory):
protocol = ExampleServerProtocol
factory = ExampleServerFactory()
factory.listen('127.0.0.1', 25565)
reactor.run()
Writing a Proxy¶
A quarry proxy has five main parts:
Class | Superclass | Description |
DownstreamFactory |
ServerFactory |
Spawns Downstream objects for connecting clients |
Downstream |
ServerProtocol |
Connection with an external client |
Bridge |
PacketDispatcher |
Forwards packets between the up/downstream |
UpstreamFactory |
ClientFactory |
Spawns an Upstream |
Upstream |
ClientProtocol |
Connection with an external server |
In ASCII art:
+--------+ +--------------------------------+ +--------+
| mojang | ----> | QUARRY | ----> | mojang |
| client | <---- | downstream | bridge | upstream | <---- | server |
+--------+ +--------------------------------+ +--------+
Typically the Bridge
and DownstreamFactory
are
customized.
When the user connects, the DownstreamFactory
creates a
Downstream
object to communicate with the external client. If we’re running
in online-mode, we go through server-side auth with mojang.
Once the user is authed, we spawn a UpstreamFactory
, which makes a
connection to the external server and spawns an Upstream
to handle it.
If requested we go through client-side auth.
At this point both endpoints of the proxy are authenticated and switched to
“play” mode. The Bridge
assumes responsibility for passing packets
between the endpoints. Proxy business logic is typically implemented by
defining packet handlers in a Bridge
subclass, much like in client and
server protocols. Unlike clients and servers, the
method name must include the packet direction before its name, e.g.:
# Hook server-to-client keep alive
def packet_upstream_tab_complete(self, buff):
# Unpack the packet
p_text = buff.unpack_string()
# Do a custom thing
if p_text.startswith("/msg"):
return # Drop the packet
# Forward the packet
buff.restore()
self.upstream.send_packet("tab_complete", buff.read())
If a packet is hooked but not explicitly forwarded it is effectively dropped.
Unhooked packets are handled by Bridge.packet_unhandled()
, which
forwards packets by default.
Skeleton Proxy¶
The proxy below will do online-mode authentication with a client connecting on port 25565, then connect in offline mode to a server running on port 25566 and begin exchanging packets via the bridge.
from twisted.internet import reactor
from quarry.net.proxy import DownstreamFactory, Bridge
class ExampleBridge(Bridge):
pass
def main(argv):
factory = DownstreamFactory()
factory.bridge_class = ExampleBridge
factory.connect_host = "127.0.0.1"
factory.connect_port = 25566
factory.listen("127.0.0.1", 25565)
reactor.run()
if __name__ == "__main__":
import sys
main(sys.argv[1:])
Downstream Factories¶
Bridges¶
-
class
quarry.net.proxy.
Bridge
(downstream_factory, downstream)[source]¶ This class exchanges packets between the upstream and downstream.
-
upstream_factory_class
= <class 'quarry.net.proxy.UpstreamFactory'>¶
-
log_level
= 20¶
-
logger
= None¶
-
downstream_factory
= None¶
-
downstream
= None¶
-
upstream_profile
= None¶
-
upstream_factory
= None¶
-
upstream
= None¶
-
make_profile
()[source]¶ Returns the profile to use for the upstream connection. By default, use an offline profile with the same display name as the remote client.
-
downstream_ready
()[source]¶ Called when the downstream is waiting for forwarding to begin. By default, this method begins a connection to the remote server.
-
upstream_ready
()[source]¶ Called when the upstream is waiting for forwarding to begin. By default, enables forwarding.
-
enable_forwarding
()[source]¶ Enables forwarding. Packet handlers in the
Upstream
andDownstream
cease to be called, and all packets are routed via theBridge
. This method is called byupstream_ready()
by default.
-
enable_fast_forwarding
()[source]¶ Enables fast forwarding. Quarry passes network data between endpoints without decoding packets, and therefore all packet handlers cease to be called. Both parts of the proxy must be operating at the same compression threshold. This method is not called by default.
-
packet_received
(buff, direction, name)[source]¶ Called when a packet is received a remote. Usually this method dispatches the packet to a method named
packet_<direction>_<packet name>
, or callspacket_unhandled()
if no such methods exists. You might want to override this to implement your own dispatch logic or logging.
-
Factories and Protocols¶
Factories¶
Factories represent your minecraft server or client as a whole. Normally only one factory is created.
Client factories require a Profile
object to be
supplied to the initializer. Use the ClientFactory.connect()
method to
connect. If force_protocol_version
is not defined, this method will make
two connections to the server; the first is used to establish the server’s
protocol version.
-
class
quarry.net.client.
ClientFactory
(profile=None)[source]¶ -
protocol
¶ alias of
ClientProtocol
-
force_protocol_version
= None¶
-
get_buff_type
(protocol_version)¶ Gets a buffer type for the given protocol version.
-
Server factories are used to customize server-wide behaviour. Use
listen()
to listen for connections. A set of all
associated ServerProtocol
objects is available as
players()
.
-
class
quarry.net.server.
ServerFactory
[source]¶ -
protocol
¶ alias of
ServerProtocol
-
motd
= 'A Minecraft Server'¶
-
max_players
= 20¶
-
icon_path
= None¶
-
online_mode
= True¶
-
prevent_proxy_connections
= True¶
-
compression_threshold
= 256¶
-
auth_timeout
= 30¶
-
players
= None¶
-
force_protocol_version
= None¶
-
get_buff_type
(protocol_version)¶ Gets a buffer type for the given protocol version.
-
Protocols¶
Protocols represent a connection to a remote minecraft server or client. For most common usages, clients have only one protocol active at any given time. In protocols you can define packet handlers or override methods in order to respond to events.
-
class
quarry.net.protocol.
Protocol
[source]¶ Minecraft protocol implementation common to both clients and servers. You should not subclass from this class, but rather subclass from one of the three classes below.
The methods/attributes given below relate specifically to quarry; the rest are given in the Connection, Authentication and Packets sections further on.
-
factory
= None¶ A reference to the factory
-
logger
= None¶ The logger for this protocol.
-
-
class
quarry.net.server.
ServerProtocol
(factory, remote_addr)[source]¶ This class represents a connection with a client
-
class
quarry.net.client.
ClientProtocol
(factory, remote_addr)[source]¶ This class represents a connection to a server
Connection¶
Override the connection_made()
,
connection_lost()
and connection_timed_out()
methods to handle connection events. The remote’s IP address is available as
the remote_addr
attribute.
In servers, connect_host
stores the
hostname the client reported that it connected to; this can be used to
implement virtual hosting.
-
Protocol.
remote_addr
= None¶ The IP address of the remote.
-
ServerProtocol.
connect_host
= None¶
-
ServerProtocol.
connect_port
= None¶
-
Protocol.
closed
= False¶
Authentication¶
Override the auth_ok()
and auth_failed()
methods to handle an authentication outcome. In servers, the player’s display
name can be obtained as display_name
,
with display_name_confirmed
being set
to True
when authentication is successful. In clients, the in-use profile
is available as self.factory.profile
.
Override the player_joined()
and player_left()
methods to respond to a player entering “play” mode (via the authentication
process) or quitting the game from “play” mode. You can check the player’s
current status via in_game
-
ServerProtocol.
display_name
= None¶
-
ServerProtocol.
display_name_confirmed
= False¶
-
Protocol.
in_game
= False¶
Packets¶
Call send_packet()
to send a packet:
# Add a diamond sword to the first hotbar slot
window_id = 0
slot_id = 36
item_id = 276
self.send_packet("set_slot",
self.buff_type.pack('bh', window_id, slot_id) +
self.buff_type.pack_slot(item_id))
To construct the payload, call static methods on
Buffer
. A reference to this class is available as
self.buff_type
.
To receive a packet, implement a method in your subclass of
ClientProtocol
or
ServerProtocol
with a name like
packet_<packet name>
:
def packet_update_health(self, buff):
health = buff.unpack('f')
food = buff.unpack_varint()
saturation = buff.unpack('f')
See also
You are passed a Buffer
instance, which contains
the payload of the packet. If you hook a packet, you should ensure you read the
entire payload.
Packet dispatching can be customized. If you override
packet_unhandled()
you can handle any packets without a
matching packet_<packet name>
handler. If you override
packet_received()
, you can replace the entire
packet_<packet name>
dispatching.
-
Protocol.
buff_type
= None¶ Usually a reference to a
Buffer
class. This is useful when constructing a packet payload for use insend_packet()
-
Protocol.
packet_received
(buff, name)[source]¶ Called when a packet is received from the remote. Usually this method dispatches the packet to a method named
packet_<packet name>
, or callspacket_unhandled()
if no such methods exists. You might want to override this to implement your own dispatch logic or logging.
Ticking¶
To register delayed or repeating callbacks, call methods on the
Ticker
object available as self.ticker
.
-
class
quarry.net.ticker.
Ticker
(logger)[source]¶ -
tick
= 0¶ The current tick
-
interval
= 0.05¶ Interval between ticks, in seconds
-
max_lag
= 40¶ Maximum number of delayed ticks before they’re all skipped
-
add_loop
(interval, callback)[source]¶ Repeatedly run a callback.
Parameters: - interval – The interval in ticks
- callback – The callback to run
Returns: An instance providing a
stop()
method
-
add_delay
(delay, callback)[source]¶ Run a callback after a delay.
Parameters: - delay – The delay in ticks
- callback – The callback to run
Returns: An instance providing
stop()
andrestart()
methods
-
Packet Names¶
See the Minecraft Coalition Wiki for a details on every packet.
Minecraft 1.15.2¶
acknowledge_player_digging
(downstream)advancement_tab
(upstream)advancements
(downstream)animation
(downstream, upstream)attach_entity
(downstream)block_action
(downstream)block_break_animation
(downstream)block_change
(downstream)block_metadata_request
(upstream)block_metadata_response
(downstream)boss_bar
(downstream)camera
(downstream)change_game_state
(downstream)chat_message
(downstream, upstream)chunk_data
(downstream)click_window
(upstream)client_settings
(upstream)client_status
(upstream)close_window
(downstream, upstream)collect_item
(downstream)combat_event
(downstream)confirm_transaction
(downstream, upstream)craft_recipe_request
(upstream)craft_recipe_response
(downstream)crafting_book_data
(upstream)creative_inventory_action
(upstream)declare_commands
(downstream)declare_recipes
(downstream)destroy_entities
(downstream)disconnect
(downstream)display_scoreboard
(downstream)edit_book
(upstream)effect
(downstream)enchant_item
(upstream)entity
(downstream)entity_action
(upstream)entity_effect
(downstream)entity_equipment
(downstream)entity_head_look
(downstream)entity_look
(downstream)entity_look_and_relative_move
(downstream)entity_metadata
(downstream)entity_metadata_request
(upstream)entity_properties
(downstream)entity_relative_move
(downstream)entity_sound_effect
(downstream)entity_status
(downstream)entity_teleport
(downstream)entity_velocity
(downstream)explosion
(downstream)face_player
(downstream)handshake
(upstream)held_item_change
(downstream, upstream)join_game
(downstream)keep_alive
(downstream, upstream)lock_difficulty
(upstream)login_disconnect
(downstream)login_encryption_request
(downstream)login_encryption_response
(upstream)login_plugin_request
(downstream)login_plugin_response
(upstream)login_set_compression
(downstream)login_start
(upstream)login_success
(downstream)map
(downstream)multi_block_change
(downstream)name_item
(upstream)named_sound_effect
(downstream)open_book
(downstream)open_horse_window
(downstream)open_sign_editor
(downstream)open_window
(downstream)particle
(downstream)pick_item
(upstream)player
(upstream)player_abilities
(downstream, upstream)player_block_placement
(upstream)player_digging
(upstream)player_list_header_footer
(downstream)player_list_item
(downstream)player_look
(upstream)player_position
(upstream)player_position_and_look
(downstream, upstream)plugin_message
(downstream, upstream)remove_entity_effect
(downstream)resource_pack_send
(downstream)resource_pack_status
(upstream)respawn
(downstream)scoreboard_objective
(downstream)select_advancement_tab
(downstream)select_trade
(upstream)server_difficulty
(downstream)set_beacon_effect
(upstream)set_cooldown
(downstream)set_difficulty
(upstream)set_experience
(downstream)set_passengers
(downstream)set_slot
(downstream)sound_effect
(downstream)spawn_experience_orb
(downstream)spawn_global_entity
(downstream)spawn_mob
(downstream)spawn_object
(downstream)spawn_painting
(downstream)spawn_player
(downstream)spawn_position
(downstream)spectate
(upstream)statistics
(downstream)status_ping
(upstream)status_pong
(downstream)status_request
(upstream)status_response
(downstream)steer_boat
(upstream)steer_vehicle
(upstream)stop_sound
(downstream)tab_complete
(downstream, upstream)tags
(downstream)teams
(downstream)teleport_confirm
(upstream)time_update
(downstream)title
(downstream)trade_list
(downstream)unload_chunk
(downstream)unlock_recipes
(downstream)update_block_entity
(downstream)update_command_block
(upstream)update_command_block_minecart
(upstream)update_health
(downstream)update_jigsaw_block
(upstream)update_light
(downstream)update_score
(downstream)update_sign
(upstream)update_structure_block
(upstream)update_view_distance
(downstream)update_view_position
(downstream)use_entity
(upstream)use_item
(upstream)vehicle_move
(downstream, upstream)window_items
(downstream)window_property
(downstream)world_border
(downstream)
Data Types¶
Buffers¶
Quarry implements Minecraft’s data types by way of the Buffer
class.
When quarry reads a packet, it stores its payload in a buffer object
and passes the buffer to a packet handler. The packet handler then unpacks
the payload, which usually made up of multiple fields of differing types. You
can read from the front of the buffer via the Buffer.read()
method or any
of the unpack_*()
methods listed below
Buffers also provide a number of static methods that pack data into
a byte string. These are named like pack_*()
.
When unpacking data you work with a buffer object, whereas when packing
data you work with a buffer type. A reference to the buffer type is available
from Protocol
objects as self.buff_type
.
-
class
quarry.types.buffer.
Buffer
(data=None)[source]¶ -
registry
= <quarry.types.registry.OpaqueRegistry object>¶ An object that encodes/decodes IDs, such as blocks and items.
-
add
(data)¶ Add some bytes to the end of the buffer.
-
discard
()¶ Discards the entire buffer contents.
-
classmethod
pack
(fmt, *fields)¶ Pack fields into a struct. The format accepted is the same as for
struct.pack()
.
-
classmethod
pack_array
(fmt, array)¶ Packs array into a struct. The format accepted is the same as for
struct.pack()
.
-
classmethod
pack_block
(block, packer=None)¶ Packs a block.
-
classmethod
pack_chat
(message)¶ Pack a Minecraft chat message.
-
classmethod
pack_chunk_section
(blocks, block_lights=None, sky_lights=None)¶ Packs a chunk section. The supplied argument should be an instance of
quarry.types.chunk.BlockArray
.
-
classmethod
pack_command_node
(node, nodes)¶ Packs a command node.
-
classmethod
pack_command_node_properties
(parser, properties)¶ Packs the properties of an
argument
command node.
-
classmethod
pack_commands
(root_node)¶ Packs a command graph.
-
classmethod
pack_direction
(direction)¶ Packs a direction.
-
classmethod
pack_entity_metadata
(metadata)¶ Packs entity metadata.
-
classmethod
pack_ingredient
(ingredient)¶ Packs a crafting recipe ingredient alternation.
-
classmethod
pack_json
(obj)¶ Serialize an object to JSON and pack it to a Minecraft string.
-
classmethod
pack_nbt
(tag=None)¶ Packs an NBT tag
-
classmethod
pack_optional
(packer, val)¶ Packs a boolean indicating whether val is None. If not,
packer(val)
is appended to the returned string.
-
classmethod
pack_optional_varint
(number)¶ Packs an optional varint.
-
classmethod
pack_packet
(data, compression_threshold=-1)¶ Unpacks a packet frame. This method handles length-prefixing and compression.
-
classmethod
pack_particle
(kind, data=None)¶ Packs a particle.
-
classmethod
pack_pose
(pose)¶ Packs a pose.
-
classmethod
pack_position
(x, y, z)¶ Packs a Position.
-
classmethod
pack_recipe
(name, type, **recipe)¶ Packs a crafting recipe.
-
classmethod
pack_rotation
(x, y, z)¶ Packs a rotation.
-
classmethod
pack_slot
(item=None, count=1, tag=None)¶ Packs a slot.
-
classmethod
pack_string
(text)¶ Pack a varint-prefixed utf8 string.
-
classmethod
pack_uuid
(uuid)¶ Packs a UUID.
-
classmethod
pack_varint
(number, max_bits=32)¶ Packs a varint.
-
classmethod
pack_villager
(kind, profession, level)¶ Packs villager data.
-
read
(length=None)¶ Read length bytes from the beginning of the buffer, or all bytes if length is
None
-
save
()¶ Saves the buffer contents.
-
unpack
(fmt)¶ Unpack a struct. The format accepted is the same as for
struct.unpack()
.
-
unpack_array
(fmt, length)¶ Unpack an array struct. The format accepted is the same as for
struct.unpack()
.
-
unpack_block
(unpacker=None)¶ Unpacks a block.
-
unpack_chat
()¶ Unpack a Minecraft chat message.
-
unpack_chunk_section
(overworld=True)¶ Unpacks a chunk section. Returns a sequence of length 4096 (16x16x16).
-
unpack_command_node
()¶ Unpacks a command node.
-
unpack_command_node_properties
(parser)¶ Unpacks the properties of an
argument
command node.
-
unpack_commands
(resolve_redirects=True)¶ Unpacks a command graph.
If resolve_redirects is
True
(the default), the returned structure may contain contain circular references, and therefore cannot be serialized to JSON (or similar). If it isFalse
, all node redirect information is stripped, resulting in a directed acyclic graph.
-
unpack_direction
()¶ Unpacks a direction.
-
unpack_entity_metadata
()¶ Unpacks entity metadata.
-
unpack_ingredient
()¶ Unpacks a crafting recipe ingredient alternation.
-
unpack_json
()¶ Unpack a Minecraft string and interpret it as JSON.
-
unpack_nbt
()¶ Unpacks NBT tag(s).
-
unpack_optional
(unpacker)¶ Unpacks a boolean. If it’s True, return the value of
unpacker()
. Otherwise return None.
-
unpack_optional_varint
()¶ Unpacks an optional varint.
-
unpack_packet
(cls, compression_threshold=-1)¶ Unpacks a packet frame. This method handles length-prefixing and compression.
-
unpack_particle
()¶ Unpacks a particle. Returns an
(kind, data)
pair.
-
unpack_pose
()¶ Unpacks a pose.
-
unpack_position
()¶ Unpacks a position.
-
unpack_recipe
()¶ Unpacks a crafting recipe.
-
unpack_rotation
()¶ Unpacks a rotation
-
unpack_slot
()¶ Unpacks a slot.
-
unpack_string
()¶ Unpack a varint-prefixed utf8 string.
-
unpack_uuid
()¶ Unpacks a UUID.
-
unpack_varint
(max_bits=32)¶ Unpacks a varint.
-
unpack_villager
()¶ Unpacks villager data.
-
Protocol Versions¶
Some data types vary between Minecraft versions. Quarry automatically sets the
buff_type
attribute of Protocol
instance to an appropriate buffer
class when the protocol version becomes known.
Minecraft 1.7¶
Support for Minecraft 1.7+ is implemented in the Buffer1_7
class.
Minecraft 1.9¶
Support for Minecraft 1.9+ is implemented in the Buffer1_9
class.
Changes from 1.7:
pack_chunk_section()
andunpack_chunk_section()
added.pack_entity_metadata()
andunpack_entity_metadata()
modified.
Minecraft 1.13¶
Support for Minecraft 1.13+ is implemented in the Buffer1_13
class.
Changes from 1.9:
pack_commands()
andunpack_commands()
added.pack_particle()
andunpack_particle()
added.pack_recipes()
andunpack_recipes()
added.pack_chunk_section_palette()
andunpack_chunk_section_palette()
modified.pack_slot()
andunpack_slot()
modified.pack_entity_metadata()
andunpack_entity_metadata()
modified.
Minecraft 1.13.2¶
Support for Minecraft 1.13.2+ is implemented in the Buffer1_13_2
class.
Changes from 1.13:
pack_slot()
andunpack_slot()
modified.
Minecraft 1.14¶
Support for Minecraft 1.14+ is implemented in the Buffer1_14
class.
Changes from 1.13.2:
pack_villager()
andunpack_villager()
added.pack_optional_varint()
andunpack_optional_varint()
added.pack_pose()
andunpack_pose()
added.pack_chunk_section()
andunpack_chunk_section()
modified.pack_position()
andunpack_position()
modified.pack_entity_metadata()
andunpack_entity_metadata()
modified.pack_particle()
andunpack_particle()
modified.pack_recipes()
andunpack_recipes()
modified.
Registry¶
Quarry can be told to encode/decode block, item and other information by
setting the registry
attribute on the in-use buffer. This can be
set directly or by deriving a subclass and customizing
get_buff_type()
. The registry
affects the following methods:
unpack_slot()
andpack_slot()
unpack_block()
andpack_block()
unpack_entity_metadata()
andpack_entity_metadata()
unpack_chunk_section()
andpack_chunk_section()
unpack_villager()
andpack_villager()
unpack_particle()
andpack_particle()
All registry objects have the following methods:
-
Registry.
is_air_block
(obj)[source]¶ Returns true if the given object is considered air for lighting purposes.
Quarry supports the following registry types:
-
class
quarry.types.registry.
OpaqueRegistry
(max_bits)[source]¶ Registry that passes IDs through unchanged. This is the default.
-
class
quarry.types.registry.
BitShiftRegistry
(max_bits)[source]¶ Registry implementing the Minecraft 1.7 - 1.12 bit-shift format for blocks.
Blocks decode to a
(block_id, metadata)
pair. Items pass through unchanged.
-
class
quarry.types.registry.
LookupRegistry
(blocks, registries)[source]¶ Registry implementing a dictionary lookup, recommended for 1.13+.
Blocks decode to a
dict
where the only guaranteed key isu'name'
. Items decode to astr
name.Use the
from_jar()
orfrom_json()
class methods to load data from the official server.
Chat Messages¶
Minecraft chat is implemented in the Message
class.
-
class
quarry.types.chat.
Message
(value)[source]¶ Represents a Minecraft chat message.
Blocks and Chunks¶
Minecraft uses tightly-packed arrays to store data like light levels,
heightmaps and block data. Quarry can read and write these formats in both
Chunk Data packets and .mca
files. Two classes are available for
working with this data:
-
class
quarry.types.chunk.
PackedArray
(storage, sector_width, value_width, fresh)[source]¶ This class provides support for an array where values are tightly packed into a number of bits (such as 4 bits for light or 9 bits for height).
All operations associated with fixed-size mutable sequences are supported, such as slicing.
Internally data is stored as a bit array with contiguous values, starting at the leftmost bits. Serializing to/from bytes is achieved by performing bitwise reversals of values and sectors; these reversals are deferred until access to packed values is needed.
Several constructors are available for specific uses of packed arrays:
- Light data used 4-bit values and 8-bit sectors
- Height data uses 9-bit values and 64-bit sectors
- Block data uses 64-bit sectors
-
storage
= None¶ The
bitstring.BitArray
object used for storage.
-
sector_width
= None¶ The width in bits of sectors. Used in (de)serialization.
-
value_width
= None¶ The width in bits of values.
-
fresh
= None¶ Whether this array is new and empty
-
twiddled
= None¶ Whether this array is contiguous (assumes non-empty, non-aligned)
-
classmethod
from_bytes
(bytes, sector_width, value_width=None)[source]¶ Deserialize a packed array from the given bytes.
-
classmethod
from_light_bytes
(bytes)[source]¶ Deserialize a packed array from the given light data bytes.
-
classmethod
from_block_bytes
(bytes, value_width=None)[source]¶ Deserialize a packed array from the given block data bytes.
-
classmethod
from_height_bytes
(bytes)[source]¶ Deserialize a packed array from the given height data bytes.
-
init_storage
()[source]¶ Initializes the storage by performing bitwise reversals.
You should not need to call this method.
-
class
quarry.types.chunk.
BlockArray
(storage, palette, registry, non_air=-1)[source]¶ This class provides support for block arrays. It wraps a
PackedArray
object and implements block encoding/decoding, palettes, and counting of non-air blocks for lighting purposes. It stores precisely 4096 (16x16x16) values.All operations associated with fixed-size mutable sequences are supported, such as slicing.
A palette is used when there are fewer than 256 unique values; the value width varies from 4 to 8 bits depending on the size of the palette, and is automatically adjusted upwards as necessary. Use
repack()
to reclaim space by eliminating unused entries.When 256 or more unique values are present, the palette is unused and values are stored directly.
-
storage
= None¶ The
PackedArray
object used for storage.
-
palette
= None¶ List of encoded block values. Empty when palette is not used.
-
registry
= None¶ The Registry object used to encode/decode blocks
-
classmethod
from_bytes
(bytes, palette, registry, non_air=-1, value_width=None)[source]¶ Deserialize a block array from the given bytes.
-
classmethod
from_nbt
(section, registry, non_air=-1)[source]¶ Creates a block array that uses the given NBT section tag as storage for block data and the palette. Minecraft 1.13+ only.
-
non_air
¶ The number of non-air blocks
-
Packets¶
On the client side, you can unpack a Chunk Data packet as follows:
def packet_chunk_data(self, buff):
x, z, full = buff.unpack('ii?')
bitmask = buff.unpack_varint()
heightmap = buff.unpack_nbt() # added in 1.14
biomes = buff.unpack_array('I', 1024) if full else None # changed in 1.15
sections_length = buff.unpack_varint()
sections = buff.unpack_chunk(bitmask)
block_entities = [buff.unpack_nbt() for _ in range(buff.unpack_varint())]
On the server side:
def send_chunk(self, x, z, full, heightmap, sections, biomes, block_entities):
sections_data = self.bt.pack_chunk(sections)
self.send_packet(
'chunk_data',
self.bt.pack('ii?', x, z, full),
self.bt.pack_chunk_bitmask(sections),
self.bt.pack_nbt(heightmap), # added in 1.14
self.bt.pack_array('I', biomes) if full else b'', # changed in 1.15
self.bt.pack_varint(len(sections_data)),
sections_data,
self.bt.pack_varint(len(block_entities)),
b''.join(self.bt.pack_nbt(entity) for entity in block_entities))
The variables used in these examples are as follows:
Variable | Value type |
---|---|
x |
int |
z |
int |
full |
bool |
bitmask |
int |
heightmap |
TagRoot[TagCompound[TagLongArray[PackedArray]]] |
sections |
List[Optional[BlockArray]] |
biomes |
List[int] |
block_entities |
List[TagRoot] |
Regions¶
Quarry can load and save data from the .mca
format via the
RegionFile
class. NBT tags such as "BlockStates"
,
"BlockLight"
, "SkyLight"
and heightmaps such as "MOTION_BLOCKING"
make their values available as PackedArray
objects.
Use BlockArray.from_nbt()
with a
LookupRegistry
to create a block array backed
by NBT data. Modifications to the block array will automatically be reflected
in the NBT data, and vice versa.
Putting these pieces together, the following function could be used to set a block in an existing region file:
import os.path
from quarry.types.nbt import RegionFile
from quarry.types.registry import LookupRegistry
from quarry.types.chunk import BlockArray
def set_block(server_path, x, y, z, block):
rx, x = divmod(x, 512)
rz, z = divmod(z, 512)
cx, x = divmod(x, 16)
cy, y = divmod(y, 16)
cz, z = divmod(z, 16)
jar_path = os.path.join(server_path, "minecraft_server.jar")
region_path = os.path.join(server_path, "world", "region", "r.%d.%d.mca" % (rx, rz))
registry = LookupRegistry.from_jar(jar_path)
with RegionFile(region_path) as region:
chunk, section = region.load_chunk_section(cx, cy, cz)
blocks = BlockArray.from_nbt(section, registry)
blocks[256 * y + 16 * z + x] = block
region.save_chunk(chunk)
set_block("/path/to/server", 10, 80, 40, {'name': 'minecraft:bedrock'})
NBT¶
Quarry implements the Named Binary Tag (NBT) format. The following tag types
are available from the quarry.types.nbt
module:
Class | Value type |
---|---|
TagByte |
int |
TagShort |
int |
TagInt |
int |
TagLong |
int |
TagFloat |
float |
TagDouble |
float |
TagByteArray |
PackedArray with 8-bit sectors |
TagIntArray |
PackedArray with 32-bit sectors |
TagLongArray |
PackedArray with 64-bit sectors |
TagString |
str (py3) or unicode (py2) |
TagList |
list of tags. |
TagCompound |
dict of names and tags. |
TagRoot |
dict containing a single name and tag. |
Note
Unlike some other NBT libraries, a tag’s name is stored by its parent -
either a TagRoot
or a TagCompound
. A tag when considered alone is
always nameless.
Tags¶
All tag types have the following attributes and methods:
-
Tag.
__init__
(value)¶ Creates a tag object from the given value.
-
classmethod
Tag.
from_bytes
(bytes)¶ Creates a tag object from data at the beginning of the supplied byte string.
-
classmethod
Tag.
from_buff
(buff)¶ Creates a tag object from data at the beginning of the supplied
Buffer
object.
-
Tag.
to_obj
()¶ Returns a friendly representation of the tag using only basic Python datatypes. This is a lossy operation, as Python has fewer data types than NBT.
-
Tag.
to_bytes
()¶ Returns a packed version of the tag as a byte string.
-
Tag.
value
¶ The value of the tag.
When working with NBT in relation to a Protocol
,
the Buffer.unpack_nbt()
and Buffer.pack_nbt()
methods may be
helpful.
Files¶
You can open an NBT file using the NBTFile
class.
You can open Minecraft 1.13+ world files (.mca
) using the RegionFile
class, which can also function as a context manager. See Blocks and Chunks for
information on loading block and light data.
Examples¶
The quarry source tree includes a few example uses of the quarry
module:
# List examples
$ python -m examples
# Run an example
$ python -m examples.client_messenger
If you have quarry
in your python search path, you can run the example
files directly.
Clients¶
Pinger¶
"""
Pinger example client
This example client connects to a server in "status" mode to retrieve some
information about the server. The information returned is what you'd normally
see in the "Multiplayer" menu of the official client.
"""
from twisted.internet import reactor
from quarry.net.client import ClientFactory, ClientProtocol
class PingProtocol(ClientProtocol):
def status_response(self, data):
for k, v in sorted(data.items()):
if k != "favicon":
self.logger.info("%s --> %s" % (k, v))
reactor.stop()
class PingFactory(ClientFactory):
protocol = PingProtocol
protocol_mode_next = "status"
def main(argv):
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("host")
parser.add_argument("-p", "--port", default=25565, type=int)
args = parser.parse_args(argv)
factory = PingFactory()
factory.connect(args.host, args.port)
reactor.run()
if __name__ == "__main__":
import sys
main(sys.argv[1:])
Player Lister¶
"""
Player lister example client
This client requires a Mojang account for online-mode servers. It logs in to
the server and prints the players listed in the tab menu.
"""
from twisted.internet import reactor, defer
from quarry.net.client import ClientFactory, ClientProtocol
from quarry.net.auth import ProfileCLI
class PlayerListProtocol(ClientProtocol):
def setup(self):
self.players = {}
def packet_player_list_item(self, buff):
# 1.7.x
if self.protocol_version <= 5:
p_player_name = buff.unpack_string()
p_online = buff.unpack('?')
p_ping = buff.unpack('h')
if p_online:
self.players[p_player_name] = {
'name': p_player_name,
'ping': p_ping
}
elif p_player_name in self.players:
del self.players[p_player_name]
# 1.8.x
else:
p_action = buff.unpack_varint()
p_count = buff.unpack_varint()
for i in range(p_count):
p_uuid = buff.unpack_uuid()
if p_action == 0: # ADD_PLAYER
p_player_name = buff.unpack_string()
p_properties_count = buff.unpack_varint()
p_properties = {}
for j in range(p_properties_count):
p_property_name = buff.unpack_string()
p_property_value = buff.unpack_string()
p_property_is_signed = buff.unpack('?')
if p_property_is_signed:
p_property_signature = buff.unpack_string()
p_properties[p_property_name] = p_property_value
p_gamemode = buff.unpack_varint()
p_ping = buff.unpack_varint()
p_has_display_name = buff.unpack('?')
if p_has_display_name:
p_display_name = buff.unpack_chat()
else:
p_display_name = None
self.players[p_uuid] = {
'name': p_player_name,
'properties': p_properties,
'gamemode': p_gamemode,
'ping': p_ping,
'display_name': p_display_name
}
elif p_action == 1: # UPDATE_GAMEMODE
p_gamemode = buff.unpack_varint()
if p_uuid in self.players:
self.players[p_uuid]['gamemode'] = p_gamemode
elif p_action == 2: # UPDATE_LATENCY
p_ping = buff.unpack_varint()
if p_uuid in self.players:
self.players[p_uuid]['ping'] = p_ping
elif p_action == 3: # UPDATE_DISPLAY_NAME
p_has_display_name = buff.unpack('?')
if p_has_display_name:
p_display_name = buff.unpack_chat()
else:
p_display_name = None
if p_uuid in self.players:
self.players[p_uuid]['display_name'] = p_display_name
elif p_action == 4: # REMOVE_PLAYER
if p_uuid in self.players:
del self.players[p_uuid]
def packet_chunk_data(self, buff):
buff.discard()
# convert self.players into a more readable format
printable_players = []
for data in self.players.values():
printable_players.append((data['name'], data['ping']))
for display_name, ping in sorted(printable_players):
self.logger.info("%4sms %s" % (ping, display_name))
reactor.stop()
class PlayerListFactory(ClientFactory):
protocol = PlayerListProtocol
@defer.inlineCallbacks
def run(args):
# Log in
profile = yield ProfileCLI.make_profile(args)
# Create factory
factory = PlayerListFactory(profile)
# Connect!
factory.connect(args.host, args.port)
def main(argv):
parser = ProfileCLI.make_parser()
parser.add_argument("host")
parser.add_argument("-p", "--port", default=25565, type=int)
args = parser.parse_args(argv)
run(args)
reactor.run()
if __name__ == "__main__":
import sys
main(sys.argv[1:])
Chat Logger¶
"""
Chat logger example client
This client stays in-game after joining. It prints chat messages received from
the server and slowly rotates (thanks c45y for the idea).
"""
from twisted.internet import reactor, defer
from quarry.net.client import ClientFactory, SpawningClientProtocol
from quarry.net.auth import ProfileCLI
class ChatLoggerProtocol(SpawningClientProtocol):
def packet_chat_message(self, buff):
p_text = buff.unpack_chat()
# 1.7.x
if self.protocol_version <= 5:
pass
# 1.8.x
else:
p_position = buff.unpack('B')
self.logger.info(":: %s" % p_text)
class ChatLoggerFactory(ClientFactory):
protocol = ChatLoggerProtocol
@defer.inlineCallbacks
def run(args):
# Log in
profile = yield ProfileCLI.make_profile(args)
# Create factory
factory = ChatLoggerFactory(profile)
# Connect!
factory.connect(args.host, args.port)
def main(argv):
parser = ProfileCLI.make_parser()
parser.add_argument("host")
parser.add_argument("-p", "--port", default=25565, type=int)
args = parser.parse_args(argv)
run(args)
reactor.run()
if __name__ == "__main__":
import sys
main(sys.argv[1:])
Messenger¶
"""
Messenger example client
Bridges minecraft chat (in/out) with stdout and stdin.
"""
import os
import sys
from twisted.internet import defer, reactor, stdio
from twisted.protocols import basic
from quarry.net.auth import ProfileCLI
from quarry.net.client import ClientFactory, SpawningClientProtocol
class StdioProtocol(basic.LineReceiver):
delimiter = os.linesep.encode('ascii')
in_encoding = getattr(sys.stdin, "encoding", 'utf8')
out_encoding = getattr(sys.stdout, "encoding", 'utf8')
def lineReceived(self, line):
self.minecraft_protocol.send_chat(line.decode(self.in_encoding))
def send_line(self, text):
self.sendLine(text.encode(self.out_encoding))
class MinecraftProtocol(SpawningClientProtocol):
spawned = False
def packet_chat_message(self, buff):
p_text = buff.unpack_chat().to_string()
# 1.7.x
if self.protocol_version <= 5:
p_position = 0
# 1.8.x
else:
p_position = buff.unpack('B')
if p_position in (0, 1) and p_text.strip():
self.stdio_protocol.send_line(p_text)
def send_chat(self, text):
self.send_packet("chat_message", self.buff_type.pack_string(text))
class MinecraftFactory(ClientFactory):
protocol = MinecraftProtocol
log_level = "WARN"
def buildProtocol(self, addr):
minecraft_protocol = super(MinecraftFactory, self).buildProtocol(addr)
stdio_protocol = StdioProtocol()
minecraft_protocol.stdio_protocol = stdio_protocol
stdio_protocol.minecraft_protocol = minecraft_protocol
stdio.StandardIO(stdio_protocol)
return minecraft_protocol
@defer.inlineCallbacks
def run(args):
# Log in
profile = yield ProfileCLI.make_profile(args)
# Create factory
factory = MinecraftFactory(profile)
# Connect!
factory.connect(args.host, args.port)
def main(argv):
parser = ProfileCLI.make_parser()
parser.add_argument("host")
parser.add_argument("port", nargs='?', default=25565, type=int)
args = parser.parse_args(argv)
run(args)
reactor.run()
if __name__ == "__main__":
main(sys.argv[1:])
Servers¶
Downtime Server¶
"""
Example "downtime" server
This server kicks players with the MOTD when they try to connect. It can be
useful for when you want players to know that your usual server is down for
maintenance.
"""
from twisted.internet import reactor
from quarry.net.server import ServerFactory, ServerProtocol
class DowntimeProtocol(ServerProtocol):
def packet_login_start(self, buff):
buff.discard()
self.close(self.factory.motd)
class DowntimeFactory(ServerFactory):
protocol = DowntimeProtocol
def main(argv):
# Parse options
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--host", default="", help="address to listen on")
parser.add_argument("-p", "--port", default=25565, type=int, help="port to listen on")
parser.add_argument("-m", "--message", default="We're down for maintenance",
help="message to kick users with")
args = parser.parse_args(argv)
# Create factory
factory = DowntimeFactory()
factory.motd = args.message
# Listen
factory.listen(args.host, args.port)
reactor.run()
if __name__ == "__main__":
import sys
main(sys.argv[1:])
Auth Server¶
"""
Example "auth" server
This server authenticates players with the mojang session server, then kicks
them. Useful for server websites that ask users for a valid Minecraft account.
"""
from twisted.internet import reactor
from quarry.net.server import ServerFactory, ServerProtocol
class AuthProtocol(ServerProtocol):
def player_joined(self):
# This method gets called when a player successfully joins the server.
# If we're in online mode (the default), this means auth with the
# session server was successful and the user definitely owns the
# display name they claim to.
# Call super. This switches us to "play" mode, marks the player as
# in-game, and does some logging.
ServerProtocol.player_joined(self)
# Define your own logic here. It could be an HTTP request to an API,
# or perhaps an update to a database table.
display_name = self.display_name
ip_addr = self.remote_addr.host
self.logger.info("[%s authed with IP %s]" % (display_name, ip_addr))
# Kick the player.
self.close("Thanks, you are now registered!")
class AuthFactory(ServerFactory):
protocol = AuthProtocol
motd = "Auth Server"
def main(argv):
# Parse options
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--host", default="", help="address to listen on")
parser.add_argument("-p", "--port", default=25565, type=int, help="port to listen on")
args = parser.parse_args(argv)
# Create factory
factory = AuthFactory()
# Listen
factory.listen(args.host, args.port)
reactor.run()
if __name__ == "__main__":
import sys
main(sys.argv[1:])
Chat Room Server¶
"""
Example "chat room" server
This server authenticates players, then spawns them in an empty world and does
the bare minimum to keep them in-game. Players can speak to eachother using
chat.
Supports Minecraft 1.15. Earlier versions will not work as the packet formats
differ.
"""
from twisted.internet import reactor
from quarry.net.server import ServerFactory, ServerProtocol
class ChatRoomProtocol(ServerProtocol):
def player_joined(self):
# Call super. This switches us to "play" mode, marks the player as
# in-game, and does some logging.
ServerProtocol.player_joined(self)
# Send "Join Game" packet
self.send_packet("join_game",
self.buff_type.pack("iBqiB",
0, # entity id
3, # game mode
0, # dimension
0, # hashed seed
0), # max players
self.buff_type.pack_string("flat"), # level type
self.buff_type.pack_varint(1), # view distance
self.buff_type.pack("??",
False, # reduced debug info
True)) # show respawn screen
# Send "Player Position and Look" packet
self.send_packet("player_position_and_look",
self.buff_type.pack("dddff?",
0, # x
255, # y
0, # z
0, # yaw
0, # pitch
0b00000), # flags
self.buff_type.pack_varint(0)) # teleport id
# Start sending "Keep Alive" packets
self.ticker.add_loop(20, self.update_keep_alive)
# Announce player joined
self.factory.send_chat(u"\u00a7e%s has joined." % self.display_name)
def player_left(self):
ServerProtocol.player_left(self)
# Announce player left
self.factory.send_chat(u"\u00a7e%s has left." % self.display_name)
def update_keep_alive(self):
# Send a "Keep Alive" packet
# 1.7.x
if self.protocol_version <= 338:
payload = self.buff_type.pack_varint(0)
# 1.12.2
else:
payload = self.buff_type.pack('Q', 0)
self.send_packet("keep_alive", payload)
def packet_chat_message(self, buff):
# When we receive a chat message from the player, ask the factory
# to relay it to all connected players
p_text = buff.unpack_string()
self.factory.send_chat("<%s> %s" % (self.display_name, p_text))
class ChatRoomFactory(ServerFactory):
protocol = ChatRoomProtocol
motd = "Chat Room Server"
def send_chat(self, message):
for player in self.players:
player.send_packet("chat_message",player.buff_type.pack_chat(message) + player.buff_type.pack('B', 0) )
def main(argv):
# Parse options
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--host", default="", help="address to listen on")
parser.add_argument("-p", "--port", default=25565, type=int, help="port to listen on")
args = parser.parse_args(argv)
# Create factory
factory = ChatRoomFactory()
# Listen
factory.listen(args.host, args.port)
reactor.run()
if __name__ == "__main__":
import sys
main(sys.argv[1:])
Proxies¶
Chat Hider Proxy¶
"""
"Quiet mode" example proxy
Allows a client to turn on "quiet mode" which hides chat messages
"""
from twisted.internet import reactor
from quarry.net.proxy import DownstreamFactory, Bridge
class QuietBridge(Bridge):
quiet_mode = False
def packet_upstream_chat_message(self, buff):
buff.save()
chat_message = self.read_chat(buff, "upstream")
self.logger.info(" >> %s" % chat_message)
if chat_message.startswith("/quiet"):
# Switch mode
self.quiet_mode = not self.quiet_mode
action = self.quiet_mode and "enabled" or "disabled"
msg = "Quiet mode %s" % action
self.downstream.send_packet("chat_message",
self.write_chat(msg, "downstream"))
elif self.quiet_mode and not chat_message.startswith("/"):
# Don't let the player send chat messages in quiet mode
msg = "Can't send messages while in quiet mode"
self.downstream.send_packet("chat_message",
self.write_chat(msg, "downstream"))
else:
# Pass to upstream
buff.restore()
self.upstream.send_packet("chat_message", buff.read())
def packet_downstream_chat_message(self, buff):
chat_message = self.read_chat(buff, "downstream")
self.logger.info(" :: %s" % chat_message)
if self.quiet_mode and chat_message.startswith("<"):
# Ignore message we're in quiet mode and it looks like chat
pass
else:
# Pass to downstream
buff.restore()
self.downstream.send_packet("chat_message", buff.read())
def read_chat(self, buff, direction):
buff.save()
if direction == "upstream":
p_text = buff.unpack_string()
return p_text
elif direction == "downstream":
p_text = str(buff.unpack_chat())
# 1.7.x
if self.upstream.protocol_version <= 5:
p_position = 0
# 1.8.x
else:
p_position = buff.unpack('B')
if p_position in (0, 1):
return p_text
def write_chat(self, text, direction):
if direction == "upstream":
return self.buff_type.pack_string(text)
elif direction == "downstream":
data = self.buff_type.pack_chat(text)
# 1.7.x
if self.downstream.protocol_version <= 5:
pass
# 1.8.x
else:
data += self.buff_type.pack('B', 0)
return data
class QuietDownstreamFactory(DownstreamFactory):
bridge_class = QuietBridge
motd = "Proxy Server"
def main(argv):
# Parse options
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-a", "--listen-host", default="", help="address to listen on")
parser.add_argument("-p", "--listen-port", default=25565, type=int, help="port to listen on")
parser.add_argument("-b", "--connect-host", default="127.0.0.1", help="address to connect to")
parser.add_argument("-q", "--connect-port", default=25565, type=int, help="port to connect to")
args = parser.parse_args(argv)
# Create factory
factory = QuietDownstreamFactory()
factory.connect_host = args.connect_host
factory.connect_port = args.connect_port
# Listen
factory.listen(args.listen_host, args.listen_port)
reactor.run()
if __name__ == "__main__":
import sys
main(sys.argv[1:])
Changelog¶
master¶
Nothing yet.
v1.5.1¶
- Added support for Minecraft 1.15.2
v1.5¶
- Added support for Minecraft 1.15 and 1.15.1
- Dropped support for Python 3.4
- Added
TagRoot.from_body()
constructor. - Added
Message.__repr__()
method. - Revised implementation of chunk data
- Added dependency on
bitstring
- Added a new
PackedArray
class for working with tightly-packed arrays. This replaces theLightArray
class, and additionally supports heightmaps and raw block data. This particular implementation ensures values are contiguous in memory, which speeds up gets/sets at the expense of a de/serialization process that involves two passes of bitwise reversals. - Reworked
BlockArray
to usePackedArray
internally. - Changed the value type of NBT arrays from a
list
ofint
to aPackedArray
. A heuristic is used to determine the value width. - Revised
Buffer1_14.un/pack_chunk_section()
to include arguments for block/skylight data, for consistency with earlierBuffer
classes. - Added
Buffer1_9.un/pack_chunk()
methods. - Added
Buffer1_9.un/pack_chunk_section_array()
methods. - Added
Buffer1_9.pack_chunk_bitmask()
method.
- Added dependency on
v1.4¶
- Added support for Minecraft 1.14.3 and 1.14.4
- Fixed support for Minecraft 1.7
v1.3¶
- Added support for Minecraft 1.14 - 1.14.2
- BREAKING CHANGE!
BlockMap
objects are replaced byRegistry
objects with greater responsibilities, reflecting the increase in information generated by the official server when run with--reports
. Villager and particle data is now decoded when using aLookupRegistry
in a buffer. Other information (for example, mob names from IDs) can be decoded in packet handlers. BlockArray
objects now track the number of non-air blocks, which is conveyed inchunk_data
packets.- Added methods for packing/unpacking optional varints, rotation, direction, villager and pose data.
- BREAKING CHANGE!
v1.2¶
- Added support for Minecraft 1.13.2
- Fixed support for server icons (thanks @dries007) and added caching.
v1.1.1¶
- Various bugfixes.
v1.1¶
Added support for Minecraft 1.13.
- Added 1.13 packet enumeration.
- The wire format of chunk sections, entity metadata and slots has changed. Slots no longer contain a ‘damage’ field.
- Added methods for packing/unpacking particles and command graphs.
- Clients now respond to
login_plugin_request
messages with alogin_plugin_response
indicating that the client didn’t understand the request. Like other quarry packet handlers, this method can be overridden in a subclass to implement a custom authentication flow.
Slot/blocks/chunks/regions improvements:
Added
quarry.types.block
module, containing three classes for handling block and item IDs:OpaqueBlockMap
passes IDs through unchangedBitShiftBlockMap
decodes blocks by bit-shifting - this format is used in Minecraft 1.7 through 1.12. Item IDs pass through unchanged.LookupBlockMap
decodes by looking up in a dictionary. This class hasfrom_jar()
andfrom_json()
methods for loading this dictionary from the official server (1.13+).
Buffer
types gain ablock_map
attribute. By default this is anOpaqueBlockMap(13)
. The buffer’s block map is consulted by methods that deal with slots, entity metadata and chunk data.BlockArray
objects must now be given a block map on initialization, and will pass getitem/setitem values through the map.Added
quarry.types.nbt.RegionFile
class, which supports reading and writing NBT chunk data to.mca
files.BlockArray
andLightArray
now support afrom_nbt()
class method. This creates an array that is a view on to an NBT compound tag representing a section (as might be retrieved via aRegionFile
). Supports Minecraft 1.13+ only.BlockArray.palette
is now an empty list rather thanNone
when a palette is not in useAdded
Buffer.pack_block()
andBuffer.unpack_block()
methods.Slot dictionaries now use an
'item'
key to store the item identifier, rather than'id'
. An empty slot is now represented with an'item'
value ofNone
rather than-1
.
Added
quarry.types.nbt.TagLongArray
class.Added
quarry.types.nbt.TagRoot.body
property to retrieve the child tag.Added
quarry.types.nbt._Tag.from_bytes()
method.Added
quarry.types.uuid.UUID.random()
constructor.Added
Protocol.get_packet_name()
andProtocol.get_packet_ident()
methods. These can be overridden to support custom packet name lookup behaviour.Moved
PacketDispatcher.dump_packet()
toBuffer.hexdump()
.Fixed unpacking of byte entity metadata.
Fixed NBT handling of 1-length arrays.
Fixed
SpawningClientProtocol
not responding to keep-alives.Fixed unicode handling in chat unpacking.
v1.0¶
- Changes to
quarry.types.buffer
:- Split
Buffer
intoBuffer1_7
andBuffer_1_9
, and select an appropriate buffer type by protocol version. This is done in anticipation of revisions to the slot and entity metadata formats in 1.13. - Moved some packet framing logic from
Protocol
intoBuffer.pack_packet()
andBuffer.unpack_packet()
- Added
Buffer.pack_optional()
andBuffer.unpack_optional()
, which handle boolean-prefixed optional data. - Added
Buffer.pack_array()
andBuffer.unpack_array()
convenience methods. - Made
Buffer.pack_entity_metadata()
andBuffer.unpack_entity_metadata()
work with a dictionary rather than a list of tuples. Also corrected a couple of issues with re-packing data. - Removed the
signed
argument fromBuffer.pack_varint()
andBuffer.unpack_varint()
. All varints are now signed.
- Split
- Changes to
quarry.types.chunk
:- Made
BlockArray
setitem/getitem accept/return an opaque ID, rather than a 2-tuple of(block_id, metadata)
. In Minecraft 1.13 it’s no longer possible to convert between the two with bitshifting. - Added
BlockArray.empty()
andLightArray.empty()
methods to initialize empty (zero-filled) block/light arrays. - Added
BlockArray.is_empty()
method, which can be used by servers to check whether a chunk section should be sent.
- Made
- Changes to
quarry.types.nbt
:- Added
TagCompound.update()
method, which performs a “deep” update of an NBT tree.
- Added
- Changes to
quarry.net
:- Added
Proxy.disable_forwarding()
ClientFactory.connect()
no longer acceptsprotocol_mode_next
andprotocol_version
arguments.ServerFactory.force_protocol_version
has moved toFactory.force_protocol_version
, and is now observed by clients.ClientProtocol.protocol_mode_next
has moved toClientFactory.protocol_mode_next
, and now defaults to “login”.- Removed
Protocol.compression_enabled
. Uncompressed connections are now indicated byProtocol.compression_threshold == -1
. - Modified
Profile.validate()
to not automatically attempt to refresh invalid profiles. This should be an explicit user choice. - Added
Profile.to_file()
, which saves to a JSON file containing a subset of the information available in~/.minecraft/launcher_profiles.json
. - Fixed restarting a stopped
Ticker
.
- Added
- Fixed
client_messenger
chat unpacking. - Fixed the
entity_properties
andadvancements
packets being swapped.
v0.9.1¶
- Dropped support for Python 3.3.
- Fixed Python 3.4+ compatibility issues.
- Made
SpawningClientProtocol
sendplayer_position_and_look
rather thanplayer_position
. - Fixed ticker logger being
None
.
v0.9¶
- Added support for Minecraft 1.12.2.
- Added documentation for proxies
- Added a “fast forwarding” mode for proxies that skips packing/unpacking of packets.
- Re-arranged some proxy internals.
- Replaced
quarry.net.tasks
withquarry.net.ticker
. An instance of theTicker
class is available asself.ticker
from protocols. This object hasadd_delay()
andadd_loop()
methods for setting up delayed and repeating tasks respectively. The interface similar to the previousTasks
object, except that timings are now given in ticks rather than seconds. The new tick loop is closer to the vanilla minecraft tick loop: delayed ticks are run faster the usual, and when too many ticks are queued they are skipped altogether. - Added
quarry.types.chat
module for handling Minecraft chat. Chat packing/unpacking methods inBuffer
now accept/return an instance of theMessage
class. - Added
Buffer.pack_slot()
method. - Added
Buffer.pack_entity_metadata()
andBuffer.unpack_entity_metadata()
methods. - Added
ServerFactory.prevent_proxy_connections
attribute, defaulting toTrue
, that prevents clients from connecting via a proxy. Note that this specifically affects online mode, and works by comparing the IP of the connecting client with the IP recorded as making the authentication request with the Mojang session server.
v0.8¶
- Added support for Minecraft 1.12.1. Thanks to Christian Hogan for the patch.
v0.7¶
- Added support for Minecraft 1.12
- Several breaking changes! Read on for more.
- Removed the
quarry.utils
package. Its contents have been distributed as follows:- The
buffer
,chunk
,nbt
anduuid
(renamed fromtypes
) modules have moved into a newquarry.types
package. - The
auth
,crypto
,http
andtasks
modules have moved into thequarry.net
package. - The
error
module was removed.ProtocolError
is now part ofquarry.net.protocol
.
- The
- Revised the NBT implementation
TagByteArray
andTagIntArray
have more straightforward signatures for__init__
andfrom_buff
TagList
now stores its contents as a list of tags, rather than a list of tag values. It no longer accepts aninner_kind
initialiser parameter, as this is derived from the type of the first supplied tag.NamedTag
is removed.TagCompound
now stores its value as adict
of names and tags, rather than alist
ofNamedTag
objects.TagRoot
is introduced as the top-level tag. This is essentially aTagCompound
containing a single record.- Added a new
alt_repr
function that prints a tag using the same representation as in the NBT specification. - Improved performance.
- Added some tests.
- Substantially expanded documentation.
- Added a new
server_chat_room
example. This server spawns a player in an empty world and allows player to chat to eachother. - Made
Protocol.send_packet()
accept any number ofdata
arguments, which are concatenated together. - Made
Buffer.__init__()
accept a string argument, which is equivalent to creating an empty buffer and callingadd()
. - Added
Buffer.pack_chunk_section()
andBuffer.unpack_chunk_section()
. These methods work with newquarry.types.chunk
types:LightArray
(4 bits per block) andBlockArray
(4-8 or 13 bits per block, with an optional palette). - Added
Buffer.pack_position()
, which packs co-ordinates into along
and complementsBuffer.unpack_position()
. - Added a
Bridge.make_profile()
method, which is called to provide a profile to theUpstreamFactory
. The default implementation generates an offline profile with the same display name as theDownstream
.
v0.6.3¶
- Fix bundle
v0.6.2¶
- Added support for Minecraft 1.11.2
- Added a default implementation for the “disconnect” packet, which now does the same thing as “login_disconnect”, i.e. logs a warning and closes the connection.
v0.6.1¶
- Fix bundle
v0.6¶
- Added support for Minecraft 1.11
- BREAKING CHANGES!
- Throughout the codebase, references to
username
have changed todisplay_name
for consistency with Mojang’s terminology. Factory.run()
andFactory.stop()
have been removed for being misleading about the role of factories. Use twisted’sreactor.run()
instead.quarry.mojang
has been renamed toquarry.auth
and substantially rewritten.- Offline profiles are now represented by
OfflineProfile
objects. - Online profiles have a number of new static creator methods:
-
from_credentials()
accepts an email address and password -from_token()
accepts a client and access token, display name and UUID -from_file()
loads a profile from the Mojang launcher. - A new
ProfileCLI
class provides a couple of useful methods for creating profiles from command-line arguments. - Profiles must now be provided to the
ClientFactory
initializer, rather than set as a class variable. When a profile is not given, an offline profile is used. In proxies, the initialiser forUpstreamFactory
must be re-implemented if the proxy connects to the backing server in online mode. Factory.auth_timeout
has moved toServerFactory.auth_timeout
. Clients now useProfile.timeout
when calling/join
endpoint.
- Throughout the codebase, references to
ClientFactory.connect
returns a deferred that will fire after afterreactor.connectTCP
is called for the last time. Usually there is a small time delay before this happens while quarry queries the server’s version.- Clients will refresh a profile if
/join
indicates a token is invalid, then retry the/join
once. - Added a new
SpawningClientProtocol
class that implements enough packets to keep a player in-game - Added a new
client_messenger
example. This bridges minecraft chat (in/out) with stdout and stdin.
v0.5¶
- Added
Buffer.unpack_nbt()
andBuffer.pack_nbt()
methods for working with the NBT (Named Binary Tag) format. - Added
Buffer.unpack_position()
method. This unpacks a 26/12/26-packed position. - Added
strip_styles
parameter toBuffer.unpack_chat()
. If set to false, text is returned including old-style style escapes (U+00A7 plus a character) - A stopping client factory no longer invalidates its profile.
- Added Python 3 compatibility to
PacketDispatcher.dump_packet()
- Fix tests for
Buffer.unpack_chat()
v0.4¶
- Added support for Minecraft 1.10
- Added support for Minecraft 1.9.3 and 1.9.4
- Improved the varint implementation - it now supports signed and magnitude-limited numbers. Also added some sensible defaults to various bits of quarry that use varints.
- Made
Buffer.unpack_chat()
not add curly braces to “translate” objects without accompanying “with” objects. - Made
Buffer.unpack_chat()
strip old-style (u00A7) chat escapes.
v0.3.1¶
- Added support for Minecraft 1.9.1 and 1.9.2
- Fixed protocol error in example chat logger when connecting to 1.9 servers
v0.3¶
- Added support for Minecraft 1.9
- Compression is now supported in servers
- Servers will now reject new connections when full
- Servers will now report a forced protocol version in status responses, rather than repeating the client’s version.
- The point at which a proxy will connect to the upstream server is now customisable.
- Renamed “maps” packet to “map”
- Renamed “sign editor open” packet to “open sign editor”
- Renamed
ServerFactory.favicon_path
toServerFactory.favicon
- Renamed
quarry.util
toquarry.utils
- Removed
protocol_mode
parameter from some proxy callbacks - Added many new docstrings; made documentation use Sphinx’s
autodoc
- Fixed exception handling when looking up a packet name. Thanks to PangeaCake for the fix.
- Fixed issue where an exception was raised when generating an offline-mode UUID in Python 3. Thanks to PangeaCake for the report.
- Fixed issue with compression in proxies when the upstream server set the compression threshold after passthrough had been enabled. Thanks to PangeaCake for the report.
- (tests)
quarry.utils.buffer
andquarry.utils.types
are now covered.
v0.2.3¶
- (documentation) Fixed changelog for v0.2.2
v0.2.2¶
- Fixed proxies
- (documentation) Added changelog
v0.2.1¶
- (documentation) Fixed front page
v0.2¶
- Tentative Python 3 support
- Removed
@register
. Packet handlers are now looked up by method name - Packets are now addressed by name, rather than mode and ident
Protocol.recv_addr
renamed toProtocol.remote_addr
- Client profile is automatically invalidated when
ClientFactory
stops - (internals)
PacketDispatcher
moved fromquarry.util
toquarry.net
- (examples) Chat logger now closely emulates vanilla client behaviour when sending “player”
- (documentation) It now exists!
v0.1¶
- Initial release