Source code for proxy

# I'll probably split this file into more parts later on, like such: 
# proxy folder: __init__.py (Proxy), client.py (Client), server.py (Server), network.py (Packet), bot.py (will contain Bot, for bot code)
# this could definitely use some code-cleaning.  
import socket, threading, struct, StringIO, time, traceback, json, random, hashlib, os, zlib, binascii, uuid, md5, storage, world
from config import Config
try: # Weird system for handling non-standard modules
	import encryption, requests
	IMPORT_SUCCESS = True
except:
	IMPORT_SUCCESS = False
[docs]class Proxy: def __init__(self, wrapper): self.wrapper = wrapper self.server = wrapper.server self.socket = False self.isServer = False self.clients = [] self.skins = {} self.uuidTranslate = {} self.storage = storage.Storage("proxy-data") self.privateKey = encryption.generate_key_pair() self.publicKey = encryption.encode_public_key(self.privateKey)
[docs] def host(self): # get the protocol version from the server while not self.wrapper.server.status == 2: time.sleep(.2) self.pollServer() while not self.socket: time.sleep(1) try: self.socket = socket.socket() self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((self.wrapper.config["Proxy"]["proxy-bind"], self.wrapper.config["Proxy"]["proxy-port"])) self.socket.listen(5) except: print "Proxy mode could not bind - retrying" self.socket = False while not self.wrapper.halt: try: sock, addr = self.socket.accept() client = Client(sock, addr, self.wrapper, self.publicKey, self.privateKey, self) t = threading.Thread(target=client.handle, args=()) t.daemon = True t.start() self.clients.append(client) # remove stale clients for i, client in enumerate(self.wrapper.proxy.clients): if client.abort: del self.wrapper.proxy.clients[i] except: print traceback.print_exc() try: client.disconnect("Some error") except: pass
[docs] def pollServer(self): sock = socket.socket() sock.connect(("localhost", self.wrapper.config["Proxy"]["server-port"])) packet = Packet(sock, self) packet.send(0x00, "varint|string|ushort|varint", (5, "localhost", self.wrapper.config["Proxy"]["server-port"], 1)) packet.send(0x00, "", ()) packet.flush() while True: id, original = packet.grabPacket() if id == 0x00: data = json.loads(packet.read("string:response")["response"]) self.wrapper.server.protocolVersion = data["version"]["protocol"] self.wrapper.server.maxPlayers = self.wrapper.config["Proxy"]["max-players"] break sock.close()
[docs] def getClientByServerUUID(self, id): for client in self.clients: if str(client.serverUUID) == str(id): self.uuidTranslate[str(id)] = str(client.uuid) return client if str(id) in self.uuidTranslate: return uuid.UUID(hex=self.uuidTranslate[str(id)])
[docs] def lookupUUID(self, uuid): #if not self.storage.key("uuid-cache"): # self.storage.key("uuid-cache", {}) if "uuid-cache" not in self.storage: self.storage["uuid-cache"] = {} if uuid in self.storage["uuid-cache"]: return self.storage["uuid-cache"][uuid] #with open("usercache.json", "r") as f: # usercache = json.loads(f.read()) #for i in usercache: # if i["uuid"] == str(uuid): # return i return None
[docs] def lookupUsername(self, username): if "uuid-cache" not in self.storage: self.storage["uuid-cache"] = {} for uuid in self.storage["uuid-cache"]: if self.storage["uuid-cache"][uuid]["name"] == username: return uuid r = requests.get("https://api.mojang.com/users/profiles/minecraft/%s" % username) uuid = self.formatUUID(r.json()["id"]) self.setUUID(uuid, username) return uuid
[docs] def formatUUID(self, name): return uuid.UUID(bytes=name.decode("hex")).hex
[docs] def setUUID(self, uuid, name): if not self.storage.key("uuid-cache"): self.storage.key("uuid-cache", {}) self.storage.key("uuid-cache")[str(uuid)] = {"uuid": str(uuid), "name": name, "expiresOn": time.strftime("%Y-%m-%d %H:%M:%S %z")}
[docs] def banUUID(self, uuid, reason="Banned by an operator", source="Server"): if not self.storage.key("banned-uuid"): self.storage.key("banned-uuid", {}) self.storage.key("banned-uuid")[str(uuid)] = {"reason": reason, "source": source, "created": time.time(), "name": self.lookupUUID(uuid)["name"]}
[docs] def isUUIDBanned(self, uuid): # Check if the UUID of the user is banned if not self.storage.key("banned-uuid"): self.storage.key("banned-uuid", {}) if uuid in self.storage.key("banned-uuid"): return True else: return False
[docs] def isAddressBanned(self, address): # Check if the IP address is banned if not self.storage.key("banned-address"): self.storage.key("banned-address", {}) if address in self.storage.key("banned-address"): return True else: return False
[docs]class Client: # handle client/game connection def __init__(self, socket, addr, wrapper, publicKey, privateKey, proxy): self.socket = socket self.wrapper = wrapper self.config = wrapper.config self.socket = socket self.publicKey = publicKey self.privateKey = privateKey self.proxy = proxy self.addr = addr self.abort = False self.log = wrapper.log self.tPing = time.time() self.server = None self.isServer = False self.isLocal = True self.uuid = None self.serverUUID = None self.server = None self.address = None self.handshake = False self.state = 0 # 0 = init, 1 = motd, 2 = login, 3 = active, 4 = authorizing self.packet = Packet(self.socket, self) self.send = self.packet.send self.read = self.packet.read self.sendRaw = self.packet.sendRaw self.username = None self.gamemode = 0 self.dimension = 0 self.position = (0, 0, 0) self.inventory = {} self.slot = 0 self.windowCounter = 2 for i in range(45): self.inventory[i] = None
[docs] def connect(self, ip=None, port=None): if not self.server == None: self.address = (ip, port) if not ip == None: self.server_temp = Server(self, self.wrapper, ip, port) try: self.server_temp.connect() self.server.close(kill_client=False) self.server = self.server_temp except: self.server_temp.close(kill_client=False) self.server_temp = None self.send(0x02, "string|byte", ("{text:'Could not connect to that server!', color:red, bold:true}", 0)) self.address = None return else: self.server = Server(self, self.wrapper, ip, port) self.server.connect() t = threading.Thread(target=self.server.handle, args=()) t.daemon = True t.start() self.server.send(0x00, "varint|string|ushort|varint", (self.version, "localhost", self.config["Proxy"]["server-port"], 2)) self.server.send(0x00, "string", (self.username,)) # self.server.send(0x46, "varint", (-1,)) # self.server.packet.compression = True # self.packet.compression = True self.server.state = 2
[docs] def close(self): self.abort = True try: self.socket.close() except: pass if self.server: self.server.abort = True self.server.close() for i, client in enumerate(self.wrapper.proxy.clients): if client.username == self.username: del self.wrapper.proxy.clients[i]
[docs] def disconnect(self, message): print "Client disconnecting: %s" % message if self.state == 3: self.send(0x40, "json", ({"text": message, "color": "red"},)) else: self.send(0x00, "json", ({"text": message, "color": "red"},)) time.sleep(1) self.close()
[docs] def flush(self): while not self.abort: self.packet.flush() time.sleep(0.05) # UUID operations
[docs] def UUIDIntToHex(self, uuid): uuid = uuid.encode("hex") uuid = "%s-%s-%s-%s-%s" % (uuid[:8], uuid[8:12], uuid[12:16], uuid[16:20], uuid[20:]) return uuid
[docs] def UUIDHexToInt(self, uuid): uuid = uuid.replace("-", "").decode("hex") return uuid
[docs] def UUIDFromName(self, name): m = md5.new() m.update(name) d = bytearray(m.digest()) d[6] &= 0x0f d[6] |= 0x30 d[8] &= 0x3f d[8] |= 0x80 return uuid.UUID(bytes=str(d))
[docs] def getPlayerObject(self): if self.username in self.wrapper.server.players: return self.wrapper.server.players[self.username] return False
[docs] def parse(self, id): if id == 0x00: if self.state == 0: data = self.read("varint:version|string:address|ushort:port|varint:state") self.version = data["version"] self.wrapper.server.protocolVersion = self.version self.packet.version = self.version if not self.wrapper.server.status == 2: self.disconnect("Server has not finished booting. Please try connecting again in a few seconds") return if data["state"] in (1, 2): self.state = data["state"] else: self.disconnect("Invalid state '%d'" % data["state"]) return False elif self.state == 1: sample = [] for i in self.wrapper.server.players: player = self.wrapper.server.players[i] sample.append({"name": player.username, "id": str(player.uuid)}) MOTD = {"description": self.config["Proxy"]["motd"], "players": {"max": self.wrapper.server.maxPlayers, "online": len(self.wrapper.server.players), "sample": sample}, "version": {"name": self.wrapper.server.version, "protocol": self.wrapper.server.protocolVersion} } if os.path.exists("server-icon.png"): f = open("server-icon.png", "r") serverIcon = "data:image/png;base64," + f.read().encode("base64") f.close() MOTD["favicon"] = serverIcon self.send(0x00, "string", (json.dumps(MOTD),)) self.state = 5 return False elif self.state == 2: data = self.read("string:username") self.username = data["username"] if self.config["Proxy"]["online-mode"]: self.state = 4 self.verifyToken = encryption.generate_challenge_token() self.serverID = encryption.generate_server_id() self.send(0x01, "string|bytearray|bytearray", (self.serverID, self.publicKey, self.verifyToken)) else: self.connect() self.uuid = uuid.uuid3(uuid.NAMESPACE_OID, "OfflinePlayer: %s" % self.username) self.serverUUID = self.UUIDFromName("OfflinePlayer:" + self.username) self.send(0x02, "string|string", (str(self.uuid), self.username)) self.state = 3 self.log.info("%s logged in (IP: %s)" % (self.username, self.addr[0])) return False elif self.state == 3: return False if id == 0x01: if self.state == 3: # chat packet if not self.isLocal == True: return True data = self.read("string:message") if data is None: return False try: if not self.wrapper.callEvent("player.rawMessage", {"player": self.getPlayerObject(), "message": data["message"]}): return False if data["message"][0] == "/": def args(i): try: return data["message"].split(" ")[i] except: return "" def argsAfter(i): try: return data["message"].split(" ")[i:] except: return "" return self.wrapper.callEvent("player.runCommand", {"player": self.getPlayerObject(), "command": args(0)[1:], "args": argsAfter(1)}) except: print traceback.format_exc() elif self.state == 4: # encryption response packet data = self.read("bytearray:shared_secret|bytearray:verify_token") sharedSecret = encryption.decrypt_shared_secret(data["shared_secret"], self.privateKey) verifyToken = encryption.decrypt_shared_secret(data["verify_token"], self.privateKey) h = hashlib.sha1() h.update(self.serverID) h.update(sharedSecret) h.update(self.publicKey) serverId = self.packet.hexdigest(h) r = requests.get("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s" % (self.username, serverId)) # print "SessionServer response: %s" % r.text self.packet.sendCipher = encryption.AES128CFB8(sharedSecret) self.packet.recvCipher = encryption.AES128CFB8(sharedSecret) if not verifyToken == self.verifyToken: self.disconnect("Verify tokens are not the same") return False try: data = r.json() self.uuid = data["id"] self.uuid = "%s-%s-%s-%s-%s" % (self.uuid[:8], self.uuid[8:12], self.uuid[12:16], self.uuid[16:20], self.uuid[20:]) self.uuid = uuid.UUID(self.uuid) if not data["name"] == self.username: self.disconnect("Client's username did not match Mojang's record") return False for property in data["properties"]: if property["name"] == "textures": self.skinBlob = property["value"] self.wrapper.proxy.skins[self.uuid] = self.skinBlob self.properties = data["properties"] except: self.disconnect("Session Server Error") return False if self.proxy.lookupUUID(self.uuid): self.username = self.proxy.lookupUUID(self.uuid)["name"] self.serverUUID = self.UUIDFromName("OfflinePlayer:" + self.username) if self.version > 26: self.packet.setCompression(256) # Ban code should go here self.send(0x02, "string|string", (str(self.uuid), self.username)) self.state = 3 self.connect() self.log.info("%s logged in (UUID: %s | IP: %s)" % (self.username, self.uuid, self.addr[0])) self.proxy.setUUID(self.uuid, self.username) return False elif self.state == 5: # ping packet during status request keepAlive = self.read("long:keepAlive")["keepAlive"] self.send(0x01, "long", (keepAlive,)) if id == 0x04: data = self.read("double:x|double:y|double:z|bool:on_ground") #objection = self.wrapper.callEvent("player.move", {"player": self.username, "xyz": (data["x"], data["y"], data["z"]), "on_ground": data["on_ground"]}) self.position = (data["x"], data["y"], data["z"]) #else: #self.wrapper.server.run("tp %s %d %d %d" % (self.username, data["x"], data["y"], data["z"])) if id == 0x06: data = self.read("double:x|double:y|double:z|float:yaw|float:pitch|bool:on_ground") #objection = self.wrapper.callEvent("player.move", {"player": self.username, "xyz": (data["x"], data["y"], data["z"]), "on_ground": data["on_ground"]}) self.position = (data["x"], data["y"], data["z"]) if self.server.state is not 3: return False if id == 0x07: # Player Block Dig if not self.isLocal == True: return True if self.version < 6: data = self.read("byte:status|int:x|ubyte:y|int:z|byte:face") position = (data["x"], data["y"], data["z"]) else: data = self.read("byte:status|position:position|byte:face") position = data["position"] if data is None: return False if data["status"] == 2: if not self.wrapper.callEvent("player.dig", {"player": self.getPlayerObject(), "position": position, "action": "end_break", "face": data["face"]}): return False if data["status"] == 0: if not self.gamemode == 1: if not self.wrapper.callEvent("player.dig", {"player": self.getPlayerObject(), "position": position, "action": "begin_break", "face": data["face"]}): return False else: if not self.wrapper.callEvent("player.dig", {"player": self.getPlayerObject(), "position": position, "action": "end_break", "face": data["face"]}): return False if self.server.state is not 3: return False if id == 0x08: # Player Block Placement if not self.isLocal == True: return True if self.version < 6: data = self.read("int:x|ubyte:y|int:z|byte:face|slot:item") position = (data["x"], data["y"], data["z"]) else: data = self.read("position:position|byte:face|slot:item") position = data["position"] position = data["position"] if position == None: if not self.wrapper.callEvent("player.action", {"player": self.getPlayerObject()}): return False else: face = data["face"] if face == 0: # Compensate position = (position[0], position[1] - 1, position[2]) elif face == 1: position = (position[0], position[1] + 1, position[2]) elif face == 2: position = (position[0], position[1], position[2] - 1) elif face == 3: position = (position[0], position[1], position[2] + 1) elif face == 4: position = (position[0] - 1, position[1], position[2]) elif face == 5: position = (position[0] + 1, position[1], position[2]) if not self.wrapper.callEvent("player.place", {"player": self.getPlayerObject(), "position": position, "item": data["item"]}): return False if self.server.state is not 3: return False if id == 0x09: # Held Item Change slot = self.read("short:short")["short"] if self.slot > -1 and self.slot < 9: self.slot = slot else: return False return True
[docs] def handle(self): t = threading.Thread(target=self.flush, args=()) t.daemon = True t.start() try: while not self.abort: try: id, original = self.packet.grabPacket() self.original = original except EOFError: self.close() break except: if Config.debug: print "Failed to grab packet (CLIENT):" print traceback.format_exc() self.close() break if time.time() - self.tPing > 1 and self.state == 3: if self.version > 32: self.send(0x00, "varint", (random.randrange(0, 99999),)) else: self.send(0x00, "int", (random.randrange(0, 99999),)) self.tPing = time.time() if self.parse(id) and self.server: if self.server.state == 3: self.server.sendRaw(original) except: print "Error in the Client->Server method:" print traceback.format_exc()
[docs]class Server: # Handle Server Connection def __init__(self, client, wrapper, ip=None, port=None): self.client = client self.wrapper = wrapper self.ip = ip self.port = port self.abort = False self.isServer = True self.proxy = wrapper.proxy self.lastPacketIDs = [] self.state = 0 # 0 = init, 1 = motd, 2 = login, 3 = active, 4 = authorizing self.packet = None self.version = self.wrapper.server.protocolVersion self.log = wrapper.log self.safe = False
[docs] def connect(self): self.socket = socket.socket() if self.ip == None: self.socket.connect(("localhost", self.wrapper.config["Proxy"]["server-port"])) else: self.socket.connect((self.ip, self.port)) self.client.isLocal = False self.packet = Packet(self.socket, self) self.packet.version = self.client.version self.username = self.client.username self.send = self.packet.send self.read = self.packet.read self.sendRaw = self.packet.sendRaw t = threading.Thread(target=self.flush, args=()) t.daemon = True t.start()
[docs] def close(self, reason="Disconnected", kill_client=True): if Config.debug: print "Last packet IDs (Server->Client) before disconnection:" print self.lastPacketIDs self.abort = True self.packet = None try: self.socket.close() except: pass if self.client.isLocal == False and kill_client: self.client.isLocal = True self.client.send(0x02, "string|byte", ("{text:'Disconnected from server: %s', color:red}" % reason.replace("'", "\\'"), 0)) self.client.connect() return # I may remove this later so the client can remain connected upon server disconnection # self.client.send(0x02, "string|byte", (json.dumps({"text": "Disconnected from server. Reason: %s" % reason, "color": "red"}),0)) # self.abort = True # self.client.connect() if kill_client: self.client.abort = True self.client.server = None self.client.close()
[docs] def flush(self): while not self.abort: self.packet.flush() # try: # self.packet.flush() # except: # print "Error while flushing, stopping" # print traceback.format_exc() # self.close() # break time.sleep(0.05)
[docs] def parse(self, id, original): if id == 0x00: if self.state < 3: message = self.read("string:string") self.log.info("Disconnected from Server: %s" % message["string"]) elif self.state == 3: if self.client.version > 7: id = self.read("int:i")["i"] if not id == None: self.send(0x00, "varint", (id,)) return False if id == 0x01: if self.state == 3: data = self.read("int:eid|ubyte:gamemode|byte:dimension|ubyte:difficulty|ubyte:max_players|string:level_type") self.client.gamemode = data["gamemode"] self.client.dimension = data["dimension"] if self.client.handshake: self.client.send(0x07, "int|ubyte|ubyte|string", (self.client.dimension, data["difficulty"], data["gamemode"], data["level_type"])) self.safe = True return False else: self.safe = True self.client.handshake = True elif self.state == 2: self.client.disconnect("Server is online mode. Please turn it off in server.properties.\n\nWrapper.py will handle authentication on its own, so do not worry about hackers.") return False if id == 0x02: if self.state == 2: # Login Success - UUID & Username are sent in this packet self.state = 3 return False elif self.state == 3: try: data = json.loads(self.read("string:json")["json"]) except: pass if not self.wrapper.callEvent("player.chatbox", {"player": self.client.getPlayerObject(), "json": data}): return False try: if data["translate"] == "chat.type.admin": return False except: pass if id == 0x03: if self.state == 2: data = self.read("varint:threshold") if not data["threshold"] == -1: self.packet.compression = True self.packet.compressThreshold = data["threshold"] else: self.packet.compression = False self.packet.compressThreshold = -1 return False if id == 0x05: data = self.read("int:x|int:y|int:z") self.wrapper.server.spawnPoint = (data["x"], data["y"], data["z"]) if id == 0x0c: # Spawn Player data = self.read("varint:eid|uuid:uuid|int:x|int:y|int:z|byte:yaw|byte:pitch|short:item|rest:metadata") if self.proxy.getClientByServerUUID(data["uuid"]): self.client.send(0x0c, "varint|uuid|int|int|int|byte|byte|short|raw", ( data["eid"], self.proxy.getClientByServerUUID(data["uuid"]).uuid, data["x"], data["y"], data["z"], data["yaw"], data["pitch"], data["item"], data["metadata"])) return False # if id == 0x21: # Chunk Data # if self.client.packet.compressThreshold == -1: # print "CLIENT COMPRESSION ENABLED" # self.client.packet.setCompression(256) if id == 0x26: # Map Chunk Bulk data = self.read("bool:skylight|varint:chunks") chunks = [] for i in range(data["chunks"]): meta = self.read("int:x|int:z|ushort:primary") chunks.append(meta) for i in range(data["chunks"]): meta = chunks[i] bitmask = bin(meta["primary"])[2:].zfill(16) primary = [] for i in bitmask: if i == "0": primary.append(False) if i == "1": primary.append(True) chunkColumn = bytearray() for i in primary: if i == True: chunkColumn += bytearray(self.packet.read_data(16*16*16 * 2)) # packetanisc metalight = bytearray(self.packet.read_data(16*16*16)) if data["skylight"]: skylight = bytearray(self.packet.read_data(16*16*16)) else: chunkColumn += bytearray(16*16*16 * 2) # Null Chunk #self.wrapper.server.world.setChunk(meta["x"], meta["z"], world.Chunk(chunkColumn, meta["x"], meta["z"])) #print "Reading chunk %d,%d" % (meta["x"], meta["z"]) if id == 0x2b: # Change Game State data = self.read("ubyte:reason|float:value") if data["reason"] == 3: self.client.gamemode = data["value"] if id == 0x2f: data = self.read("byte:wid|short:slot|slot:data") if data["wid"] == 0: self.client.inventory[data["slot"]] = data["data"] if id == 0x40: message = self.read("json:json")["json"] self.log.info("Disconnected from server: %s" % message) if self.client.isLocal == False: self.server.close(message) else: self.client.disconnect(message) return False if id == 0x38: head = self.read("varint:action|varint:length") z = 0 while z < head["length"]: serverUUID = self.read("uuid:uuid")["uuid"] client = self.client.proxy.getClientByServerUUID(serverUUID) if not client: z += 1 continue z += 1 if head["action"] == 0: properties = client.properties raw = "" for i in properties: raw += self.client.packet.send_string(i["name"]) # name raw += self.client.packet.send_string(i["value"]) # value if "signature" in i: raw += self.client.packet.send_bool(True) raw += self.client.packet.send_string(i["signature"]) # signature else: raw += self.client.packet.send_bool(False) raw += self.client.packet.send_varInt(0) raw += self.client.packet.send_varInt(0) raw += self.client.packet.send_bool(False) self.client.send(0x38, "varint|varint|uuid|string|varint|raw", (0, 1, client.uuid, client.username, len(properties), raw)) elif head["action"] == 1: data = self.read("varint:gamemode") self.client.send(0x38, "varint|varint|uuid|varint", (1, 1, client.uuid, data["gamemode"])) elif head["action"] == 2: data = self.read("varint:ping") self.client.send(0x38, "varint|varint|uuid|varint", (2, 1, client.uuid, data["ping"])) elif head["action"] == 3: data = self.read("bool:has_display") if data["has_display"]: data = self.read("string:displayname") self.client.send(0x38, "varint|varint|uuid|bool|string", (3, 1, client.uuid, True, data["displayname"])) else: self.client.send(0x38, "varint|varint|uuid|varint", (3, 1, client.uuid, False)) elif head["action"] == 4: self.client.send(0x38, "varint|varint|uuid", (4, 1, client)) return False return True
[docs] def handle(self): try: while not self.abort: try: id, original = self.packet.grabPacket() self.lastPacketIDs.append((hex(id), len(original))) if len(self.lastPacketIDs) > 10: for i,v in enumerate(self.lastPacketIDs): del self.lastPacketIDs[i] break except EOFError: print traceback.format_exc() self.close() break except: if Config.debug: print "Failed to grab packet (SERVER)" print traceback.format_exc() #self.disconnect("Internal Wrapper.py Error") # break if self.client.abort: self.close() break try: if self.parse(id, original) and self.safe: self.client.sendRaw(original) except: self.log.debug("Could not parse packet, connection may crumble:") self.log.debug(traceback.format_exc()) except: if Config.debug: print "Error in the Server->Client method:" print traceback.format_exc()
[docs]class Packet: # PACKET PARSING CODE def __init__(self, socket, obj): self.socket = socket self.obj = obj self.recvCipher = None self.sendCipher = None self.compressThreshold = -1 self.version = 5 self.bonk = False self.buffer = StringIO.StringIO() self.queue = []
[docs] def hexdigest(self, sh): d = long(sh.hexdigest(), 16) if d >> 39 * 4 & 0x8: return "-%x" % ((-d) & (2 ** (40 * 4) - 1)) return "%x" % d
[docs] def grabPacket(self): length = self.unpack_varInt() # if length == 0: return None # if length > 256: # print "Length: %d" % length dataLength = 0 if not self.compressThreshold == -1: dataLength = self.unpack_varInt() length = length - len(self.pack_varInt(dataLength)) payload = self.recv(length) if dataLength > 0: payload = zlib.decompress(payload) self.buffer = StringIO.StringIO(payload) id = self.read_varInt() return (id, payload)
[docs] def pack_varInt(self, val): total = b'' if val < 0: val = (1<<32)+val while val>=0x80: bits = val&0x7F val >>= 7 total += struct.pack('B', (0x80|bits)) bits = val&0x7F total += struct.pack('B', bits) return total
[docs] def unpack_varInt(self): total = 0 shift = 0 val = 0x80 while val&0x80: val = struct.unpack('B', self.recv(1))[0] total |= ((val&0x7F)<<shift) shift += 7 if total&(1<<31): total = total - (1<<32) return total
[docs] def setCompression(self, threshold): # self.sendRaw("\x03\x80\x02") self.send(0x03, "varint", (threshold,)) self.compressThreshold = threshold #time.sleep(1.5)
[docs] def flush(self): for p in self.queue: packet = p[1] id = struct.unpack("B", packet[0])[0] if p[0] > -1: # p[0] > -1: if len(packet) > self.compressThreshold: packetCompressed = self.pack_varInt(len(packet)) + zlib.compress(packet) packet = self.pack_varInt(len(packetCompressed)) + packetCompressed else: packet = self.pack_varInt(0) + packet packet = self.pack_varInt(len(packet)) + packet else: packet = self.pack_varInt(len(packet)) + packet # if not self.obj.isServer: # print packet.encode("hex") if self.sendCipher is None: self.socket.send(packet) else: self.socket.send(self.sendCipher.encrypt(packet)) self.queue = []
[docs] def sendRaw(self, payload): self.queue.append((self.compressThreshold, payload)) # -- SENDING AND PARSING PACKETS -- #
[docs] def read(self, expression): result = {} for exp in expression.split("|"): type = exp.split(":")[0] name = exp.split(":")[1] try: if type == "string": result[name] = self.read_string() if type == "json": result[name] = self.read_json() if type == "ubyte": result[name] = self.read_ubyte() if type == "byte": result[name] = self.read_byte() if type == "int": result[name] = self.read_int() if type == "short": result[name] = self.read_short() if type == "ushort": result[name] = self.read_ushort() if type == "long": result[name] = self.read_long() if type == "double": result[name] = self.read_double() if type == "float": result[name] = self.read_float() if type == "bool": result[name] = self.read_bool() if type == "varint": result[name] = self.read_varInt() if type == "bytearray": result[name] = self.read_bytearray() if type == "position": result[name] = self.read_position() if type == "slot": result[name] = self.read_slot() if type == "uuid": result[name] = self.read_uuid() if type == "metadata": result[name] = self.read_metadata() if type == "rest": result[name] = self.read_rest() except: # print traceback.format_exc() result[name] = None return result
[docs] def send(self, id, expression, payload): result = "" result += self.send_varInt(id) if len(expression) > 0: for i,type in enumerate(expression.split("|")): try: pay = payload[i] if type == "string": result += self.send_string(pay) if type == "json": result += self.send_json(pay) if type == "ubyte": result += self.send_ubyte(pay) if type == "byte": result += self.send_byte(pay) if type == "int": result += self.send_int(pay) if type == "short": result += self.send_short(pay) if type == "ushort": result += self.send_ushort(pay) if type == "varint": result += self.send_varInt(pay) if type == "float": result += self.send_float(pay) if type == "double": result += self.send_double(pay) if type == "long": result += self.send_long(pay) if type == "json": result += self.send_json(pay) if type == "bytearray": result += self.send_bytearray(pay) if type == "uuid": result += self.send_uuid(pay) if type == "metadata": result += self.send_metadata(pay) if type == "bool": result += self.send_bool(pay) if type == "position": result += self.send_position(pay) if type == "raw": result += pay except: pass #print traceback.format_exc() self.sendRaw(result) return result # -- SENDING DATA TYPES -- #
[docs] def send_byte(self, payload): return struct.pack("b", payload)
[docs] def send_ubyte(self, payload): return struct.pack("B", payload)
[docs] def send_string(self, payload): return self.send_varInt(len(payload)) + payload.encode("utf8")
def send_json(self, payload): return self.send_string(json.dumps(payload))
[docs] def send_int(self, payload): return struct.pack(">i", payload)
[docs] def send_long(self, payload): return struct.pack(">q", payload)
[docs] def send_short(self, payload): return struct.pack(">h", payload)
[docs] def send_ushort(self, payload): return struct.pack(">H", payload)
[docs] def send_float(self, payload): return struct.pack(">f", payload)
[docs] def send_double(self, payload): return struct.pack(">d", payload)
[docs] def send_varInt(self, payload): return self.pack_varInt(payload)
[docs] def send_bytearray(self, payload): return self.send_varInt(len(payload)) + payload
[docs] def send_json(self, payload): return self.send_string(json.dumps(payload))
[docs] def send_uuid(self, payload): return payload.bytes
[docs] def send_position(self, payload): x, y, z = position return self.send_long(((x & 0x3FFFFFF) << 38) | ((y & 0xFFF) << 26) | (z & 0x3FFFFFF))
[docs] def send_metadata(self, payload): b = "" for i in payload: item = payload[i] b+= chr(int("3", 2)) continue b += self.send_ubyte(0x7f) return b
[docs] def send_bool(self, payload): if payload == False: return self.send_byte(0) if payload == True: return self.send_byte(1) # -- READING DATA TYPES -- #
[docs] def recv(self, length): if length > 5000: d = "" while len(d) < length: m = length - len(d) if m > 5000: m = 5000 d += self.socket.recv(m) else: d = self.socket.recv(length) if len(d) == 0: raise EOFError("Packet was zero length, disconnecting") # while length > len(d): # print "Need %d more" % length - len(d) # d += self.socket.recv(length - len(d)) # if not length == len(d): # print "ACTUAL PACKET NOT LONG %d %d" % (length, len(d)) # print "Read more: %d" % len(self.socket.recv(1024)) #raise EOFError("Actual length of packet was not as long as expected!") if self.recvCipher is None: return d return self.recvCipher.decrypt(d)
[docs] def read_data(self, length): d = self.buffer.read(length) # if len(d) == 0 and length is not 0: # self.disconnect("Received no data - connection closed") # return "" return d
[docs] def read_byte(self): return struct.unpack("b", self.read_data(1))[0]
[docs] def read_ubyte(self): return struct.unpack("B", self.read_data(1))[0]
[docs] def read_long(self): return struct.unpack(">q", self.read_data(8))[0]
[docs] def read_ulong(self): return struct.unpack(">Q", self.read_data(8))[0]
[docs] def read_float(self): return struct.unpack(">f", self.read_data(4))[0]
[docs] def read_int(self): return struct.unpack(">i", self.read_data(4))[0]
[docs] def read_double(self): return struct.unpack(">d", self.read_data(8))[0]
[docs] def read_bool(self): if self.read_data(1) == 0x01: return True else: return False
[docs] def read_short(self): return struct.unpack(">h", self.read_data(2))[0]
[docs] def read_ushort(self): return struct.unpack(">H", self.read_data(2))[0]
[docs] def read_bytearray(self): return self.read_data(self.read_varInt())
[docs] def read_short_bytearray(self): return self.read_data(self.read_short())
[docs] def read_position(self): position = self.read_long() if position == -1: return None if position & 0x3FFFFFF > 67100000: z = -(-position & 0x3FFFFFF) else: z = position & 0x3FFFFFF position = (position >> 38, (position >> 26) & 0xFFF, z) return position
[docs] def read_slot(self): id = self.read_short() if not id == -1: count = self.read_ubyte() damage = self.read_short() return {"id": id, "count": count, "damage": damage} # nbtLength = self.read_short() # print count ,damage, nbtLength # if nbtLength > 0: nbt = self.read_data(nbtLength) # else: nbt = ""
[docs] def read_varInt(self): total = 0 shift = 0 val = 0x80 while val&0x80: val = struct.unpack('B', self.read_data(1))[0] total |= ((val&0x7F)<<shift) shift += 7 if total&(1<<31): total = total - (1<<32) return total
[docs] def read_uuid(self): i = self.read_data(16) i = uuid.UUID(bytes=i) return i
[docs] def read_string(self): return self.read_data(self.read_varInt())
[docs] def read_json(self): return json.loads(self.read_string())
[docs] def read_rest(self): return self.read_data(1024 * 1024)
[docs] def read_metadata(self): # This function is completely broken and needs fixing! data = {} while True: a = self.read_ubyte() if a == 0x7f: return data index = a & 0x1f type = a >> 5 if type == 0: data[index] = ("byte", self.read_byte()) if type == 1: data[index] = ("short", self.read_short()) if type == 2: data[index] = ("int", self.read_int()) if type == 3: data[index] = ("float", self.read_float()) if type == 4: data[index] = ("string", self.read_string()) if type == 5: data[index] = ("slot", self.read_slot()) if type == 6: data[index] = ("position", (self.read_int(), self.read_int(), self.read_int())) return data