Skip to content

Instantly share code, notes, and snippets.

@yonderbread
Created February 25, 2021 20:15
Show Gist options
  • Save yonderbread/2eb2917e5f40a29ff3eb291128d958d7 to your computer and use it in GitHub Desktop.
Save yonderbread/2eb2917e5f40a29ff3eb291128d958d7 to your computer and use it in GitHub Desktop.
Minecraft chat client class
import socket
import sys
import threading
import struct
PACKETS = {
"send" :{
"handshake": {},
"login": {},
"play": {}
},
"recieve":{
"login": {},
"play": {}
}
}
def varint_pack(d):
o = b''
while True:
b = d & 0x7F
d >>= 7
o += struct.pack("B", b | (0x80 if d > 0 else 0))
if d == 0:
break
return o
def varint_unpack(s):
d, l = 0, 0
length = len(s)
if length > 5:
length = 5
for i in range(length):
l += 1
b = s[i]
d |= (b & 0x7F) << 7 * i
if not b & 0x80:
break
return (d, s[l:])
def pack_data(data):
return varint_pack(len(data)) + data
def unpack_data(data):
l, data = varint_unpack(data)
return data[:l], data[l:]
def pack_string(data):
return pack_data(data.encode())
def unpack_string(data):
s, r = unpack_data(data)
return s.decode(), r
def unpack_struct(f, s):
data = struct.unpack_from(f, s)
r = s[struct.calcsize(f):]
return data, r
def _packet(direc, state, name, id):
def decor(func):
if direc == "send":
func.packid = id
PACKETS[direc][state][name] = func
elif direc == "recieve":
func.packname = name
PACKETS[direc][state][id] = func
return func
return decor
class Client:
def __init__(self,
username: str,
version: int = 754,
debug: bool = False):
self.username = username
self.connected = False
self.running = False
self.state = "handshake"
self._thread = threading.Thread(self._listen)
self._version = version
self._debug = debug
self._host = None
self._port = None
self._sock = socket.socket(
socket.AF_INET,
socket.SOCK_STREAM
)
def connect(self, host: str, port: int = 25565):
self._host = host
self._port = port
try:
self._sock.connect((self._host, self._port))
except ConnectionRefusedError:
self._send_debug_message("Connection refused.")
sys.exit(1)
self.running = True
self._send_debug_message("Handshaking with server...")
self._send("handshake", self._version,
self._host, self._port, 2)
self._send_debug_message("Logging in...")
self._send("start", self.username)
self._send_debug_message("Ready")
self._thread.start()
def _send_debug_message(self, message: str):
if self._debug:
print(message)
def send_message(self, message: str):
self._send("chat", message)
def _listen(self):
while self.running:
data = self._sock.recv(1024)
if not data:
self._send_debug_message("Connection aborted by server.")
self.running = False
msg = self._recv(data)
if self.debug and msg:
print(msg)
def _send(self, packet, *args, **kwargs):
func = PACKETS["send"][self.state][packet]
packid = varint_pack(int(func.packid, 16))
data = func(*args, **kwargs)
self._sock.sendall(pack_data(packid + data))
def _recv(self, data=None):
if isinstance(data, type(None)):
data = self._sock.recv(1024)
if not data:
return
data = unpack_data(data)[0]
packid, data = varint_unpack(data)
packid = str(hex(packid))
packs = PACKETS["recieve"][self.state]
if packid not in packs:
return
packet = packs[packid]
return packet.packname, packet(data)
@_packet("recieve", "play", "disconnect", "0x40")
@_packet("recieve", "login", "disconnect", "0x0")
def _p(self, data):
message = unpack_string(data)[0]
self._send_debug_message('Disconnected from server.')
return message
@_packet("recieve", "login", "success", "0x02")
def _p(self, data):
self.state = "play"
uuid, data = unpack_string(data)
name = unpack_string(data)[0]
return uuid, name
@_packet("recieve", "play", "keep-alive", "0x0")
def _p(self, data):
_id = struct.unpack('!i', data)[0]
self._send("keep-alive", _id)
return _id
@_packet("recieve", "play", "joined", "0x1")
def _p(self, data):
self.connected = True
s, data = unpack_struct('!iBbBB', data)
level = unpack_string(data)[0]
return s + (level,)
@_packet("recieve", "play", "chat", "0x2")
def _p(self, data):
message = unpack_string(data)[0]
self._send_debug_message(message)
return message
@_packet("send", "handshake", "handshake", "0x0")
def _p(self, version, host, port, next):
if next == 2:
self.state = "login"
version = varint_pack(version)
host = pack_string(host)
port = struct.pack('!H', port)
next = varint_pack(next)
return version + host + port + next
@staticmethod
@_packet("send", "login", "start", "0x0")
def _p(name):
name = pack_string(name)
return name
@_packet("send", "play", "keep-alive", "0x0")
def _p(id):
_id = struct.pack('!i', id)
return _id
@_packet("send", "play", "chat", "0x1")
def _p(message):
message = pack_string(message)
return message
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment