1
+# Minecraft Remote Console module.
2
+#
3
+# Copyright (C) 2015 Barnaby Gale
4
+#
5
+# Permission is hereby granted, free of charge, to any person obtaining a copy
6
+# of this software and associated documentation files (the "Software"), to
7
+# deal in the Software without restriction, including without limitation the
8
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9
+# sell copies of the Software, and to permit persons to whom the Software is
10
+# furnished to do so, subject to the following conditions:
11
+#
12
+# The above copyright notice and this permission notice shall be included in
13
+# all copies of the Software and its documentation and acknowledgment shall be
14
+# given in the documentation and software packages that this Software was
15
+# used.
16
+#
17
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20
+# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+import socket
25
+import select
26
+import struct
27
+import time
28
+
29
+
30
+class MCRconException(Exception):
31
+ pass
32
+
33
+
34
+class MCRcon(object):
35
+ socket = None
36
+
37
+ def connect(self, host, port, password):
38
+ if self.socket is not None:
39
+ raise MCRconException("Already connected")
40
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
41
+ self.socket.connect((host, port))
42
+ self.send(3, password)
43
+
44
+ def disconnect(self):
45
+ if self.socket is None:
46
+ raise MCRconException("Already disconnected")
47
+ self.socket.close()
48
+ self.socket = None
49
+
50
+ def read(self, length):
51
+ data = b""
52
+ while len(data) < length:
53
+ data += self.socket.recv(length - len(data))
54
+ return data
55
+
56
+ def send(self, out_type, out_data):
57
+ if self.socket is None:
58
+ raise MCRconException("Must connect before sending data")
59
+
60
+ # Send a request packet
61
+ out_payload = struct.pack('<ii', 0, out_type) + out_data.encode('utf8') + b'\x00\x00'
62
+ out_length = struct.pack('<i', len(out_payload))
63
+ self.socket.send(out_length + out_payload)
64
+
65
+ # Read response packets
66
+ in_data = ""
67
+ while True:
68
+ # Read a packet
69
+ in_length, = struct.unpack('<i', self.read(4))
70
+ in_payload = self.read(in_length)
71
+ in_id, in_type = struct.unpack('<ii', in_payload[:8])
72
+ in_data_partial, in_padding = in_payload[8:-2], in_payload[-2:]
73
+
74
+ # Sanity checks
75
+ if in_padding != b'\x00\x00':
76
+ raise MCRconException("Incorrect padding")
77
+ if in_id == -1:
78
+ raise MCRconException("Login failed")
79
+
80
+ # Record the response
81
+ in_data += in_data_partial.decode('utf8')
82
+
83
+ # If there's nothing more to receive, return the response
84
+ if len(select.select([self.socket], [], [], 0)[0]) == 0:
85
+ return in_data
86
+
87
+ def command(self, command):
88
+ result = self.send(2, command)
89
+ time.sleep(0.003) # MC-72390 workaround
90
+ return result