-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.py
289 lines (289 loc) · 15.3 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/usr/bin/python
# -*- coding: utf-8 -*-
from twisted.internet import protocol, reactor
from twisted.internet.task import LoopingCall
from os.path import abspath
from plugin_core import PluginSystem
import struct, json, zlib, sys, packets, configparser, datetime
class BufferUnderrun(Exception): pass
class Tasks(object):
def __init__(self):
self._tasks = []
def add_loop(self, time, callback, *args):
task = LoopingCall(callback, *args)
task.start(time, now=False)
self._tasks.append(task)
return task
def add_delay(self, time, callback, *args):
task = reactor.callLater(time, callback, *args)
def stop():
if task.active(): task.cancel()
def restart():
if task.active(): task.reset(time)
task.restart = restart
task.stop = stop
self._tasks.append(task)
return task
def stop_all(self):
while len(self._tasks) > 0:
task = self._tasks.pop(0)
task.stop()
class ProtocolError(Exception):
@classmethod
def mode_mismatch(cls, ident, mode): return cls('Unexpected packet; ID: {0}; Mode: {1}'.format(ident, mode))
@classmethod
def step_mismatch(cls, ident, step): return cls('Unexpected packet; ID: {0}; Step: {1}'.format(ident, step))
class Buffer(object):
def __init__(self):
self.buff1 = b''
self.buff2 = b''
def length(self): return len(self.buff1)
def add(self, data): self.buff1 += data
def save(self): self.buff2 = self.buff1
def restore(self): self.buff1 = self.buff2
def unpack_raw(self, l):
if len(self.buff1) < l: raise BufferUnderrun()
d, self.buff1 = self.buff1[:l], self.buff1[l:]
return d
def unpack(self, ty):
s = struct.unpack('>'+ty, self.unpack_raw(struct.calcsize(ty)))
return s[0] if len(ty) == 1 else s
def unpack_string(self): return self.unpack_raw(self.unpack_varint()).decode('utf-8')
def unpack_array(self): return self.unpack_raw(self.unpack('h'))
def unpack_varint(self):
d = 0
for i in range(5):
b = self.unpack('B')
d |= (b & 0x7F) << 7*i
if not b & 0x80: break
return d
def unpack_json(self):
obj = json.loads(self.unpack_string())
return obj
def unpack_chat(self): return self.unpack_json()
@classmethod
def pack_uuid(cls, uuid): return uuid.to_bytes()
@classmethod
def pack_json(cls, obj): return cls.pack_string(json.dumps(obj))
@classmethod
def pack_chat(cls, text): return cls.pack_json({'text': text})
@classmethod
def pack(cls, ty, *data): return struct.pack('>'+ty, *data)
@classmethod
def pack_slot(cls, id=-1, count=1, damage=0, tag=None): return cls.pack('hbh', id, count, damage) + cls.pack_nbt(tag)
@classmethod
def pack_nbt(cls, tag=None):
if tag is None: return b'\x00'
return tag.to_bytes()
@classmethod
def pack_string(cls, data):
data = data.encode('utf-8')
return cls.pack_varint(len(data)) + data
@classmethod
def pack_array(cls, data): return cls.pack('h', len(data)) + data
@classmethod
def pack_varint(cls, 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
class AuthProtocol(protocol.Protocol):
protocol_mode = 0
protocol_version = 0
login_step = 0
def __init__(self, factory, addr):
self.x, self.y, self.z, self.on_ground, self.slot = 1, 400, 0, True, 0
self.username = 'NONE'
self.joined = False
self.factory = factory
self.client_addr = addr.host
self.buff = Buffer()
self.tasks = Tasks()
self.cipher = lambda d: d
def dataReceived(self, data):
self.buff.add(data)
while True:
try:
packet_length = self.buff.unpack_varint()
packet_body = self.buff.unpack_raw(packet_length)
try: self.packet_received(packet_body)
except ProtocolError as e:
self.factory.logging('Protocol Error: ', e)
self.kick('Protocol Error!\n\n%s' % (e))
break
self.buff.save()
except BufferUnderrun: break
def packet_received(self, data):
buff = Buffer()
buff.add(data)
try:
ident = buff.unpack_varint()
if self.factory.debug: print(str(ident))
if self.protocol_mode == 3:
key = (self.protocol_version, self.get_mode(), 'upstream', ident)
try: name = packets.packet_names[key]
except KeyError: raise ProtocolError('No name known for packet: %s' % (key,))
self.plugin_event('packet_recived', ident, name)
if self.factory.debug: print(str(name))
if name == 'player_position':
self.x, self.y, self.z, self.o = buff.unpack('ddd?')
self.plugin_event('player_move', self.x, self.y, self.z, self.on_ground)
if name == 'held_item_change': self.s = buff.unpack('h')
if name == 'chat_message':
self.chat_message = buff.unpack_string()
self.plugin_event('chat_message', self.chat_message)
if self.chat_message[0] == '/': self.handle_command(self.chat_message[1:])
else: self.send_chat_all('<%s> %s' % (self.username, self.chat_message))
if self.protocol_mode == 0:
if ident == 0:
self.protocol_version = buff.unpack_varint()
self.server_addr = buff.unpack_string()
self.server_port = buff.unpack('H')
self.protocol_mode = buff.unpack_varint()
else: raise ProtocolError.mode_mismatch(ident, self.protocol_mode)
elif self.protocol_mode == 1:
if ident == 0: self.send_packet('status_response', self.buff.pack_string(json.dumps(self.factory.get_status(self.protocol_version))))
elif ident == 1:
time = buff.unpack('Q')
self.send_packet('status_pong', self.buff.pack('Q', time))
if self.factory.print_ping:
self.factory.logging(self.client_addr + ' pinged')
self.close()
else: raise ProtocolError.mode_mismatch(ident, self.protocol_mode)
elif self.protocol_mode == 2:
self.username = buff.unpack_string()
if not self.joined:
self.joined = True
self.send_packet('login_success', buff.pack_string('19e34a23-53d5-4bc2-a649-c9575ef08bb6') + buff.pack_string(self.username))
self.protocol_mode = 3
self.factory.players.add(self)
self.send_chat_all('§e%s joined on server!' % (self.username))
self.factory.logging('%s joined on server with parms: %s|[%s]%s' % (self.username, self.protocol_version, self.client_addr, self.get_mode()))
if self.protocol_version == 47:
self.send_packet('join_game', buff.pack('iBbBB', 0, 0, 0, 0, 0) + buff.pack_string('flat') + buff.pack('?', False))
self.send_packet('player_position_and_look', buff.pack('dddffb', float(0), float(400), float(0), float(-90), float(0), 0b00000))
elif self.protocol_version == 107:
self.send_packet('join_game', buff.pack('iBbBB', 0, 0, 0, 0, 0) + buff.pack_string('flat') + buff.pack('?', False))
self.send_packet('player_position_and_look', buff.pack('dddffb', float(0), float(400), float(0), float(-90), float(0), True) + buff.pack_varint(0))
else:
self.send_packet('join_game', buff.pack('iBiBB', 0, 0, 0, 0, 0) + buff.pack_string('flat') + buff.pack('?', False))
self.send_packet('player_position_and_look', buff.pack('dddff?', float(0), float(400), float(0), float(-90), float(0), True) + buff.pack_varint(0))
self.send_chunk()
self.plugin_event('player_join')
self.tasks.add_loop(5, self.send_keep_alive)
else: raise ProtocolError.mode_mismatch(ident, self.protocol_mode)
except: pass
def send_packet(self, name, data):
key = (self.protocol_version, self.get_mode(), 'downstream', name)
try: ident = packets.packet_idents[key]
except KeyError: raise ProtocolError('No ID known for packet: %s' % (key,))
data = Buffer.pack_varint(ident) + data
data = Buffer.pack_varint(len(data)) + data
if len(data) >= 256: data = Buffer.pack_varint(len(data)) + zlib.compress(data)
else: data = Buffer.pack_varint(0) + data
data = self.cipher(data)
self.transport.write(data)
def close(self):
self.transport.loseConnection()
def connectionLost(self, reason=None):
self.tasks.stop_all()
if self.get_mode() in ('login', 'play'):
self.factory.players.discard(self)
self.plugin_event('player_leave')
self.send_chat_all('§e%s leaved from server!' % (self.username))
self.factory.logging('%s leaved from server with parms: %s|[%s]%s' % (self.username, self.protocol_version, self.client_addr, self.get_mode()))
def kick(self, message):
if self.get_mode() == 'login': self.send_packet('login_disconnect', self.buff.pack_chat(message.replace('&', u'\u00A7')))
else: self.send_packet('disconnect', self.buff.pack_chat(message.replace('&', u'\u00A7')))
self.close()
def send_title(self, message, sub, fadein, stay, fadeout):
self.send_packet('title', self.buff.pack_varint(0) + self.buff.pack_chat(message))
self.send_packet('title', self.buff.pack_varint(1) + self.buff.pack_chat(sub))
if self.protocol_version <= 210: self.send_packet('title', self.buff.pack_varint(2) + self.buff.pack('iii', fadein, stay, fadeout))
else: self.send_packet('title', self.buff.pack_varint(3) + self.buff.pack('iii', fadein, stay, fadeout))
def send_chunk(self):
if self.protocol_version == 47: self.send_packet('chunk_data', self.buff.pack('ii?H', 0, 0, True, 0) + self.buff.pack_varint(0))
elif self.protocol_version == 109 or self.protocol_version == 108 or self.protocol_version == 107: self.send_packet('chunk_data', self.buff.pack('ii?', 0, 0, True) + self.buff.pack_varint(0) + self.buff.pack_varint(0))
else: self.send_packet('chunk_data', self.buff.pack('ii?H', 0, 0, True, 0) + self.buff.pack_varint(0))
def send_spawn_player(self, entity_id, player_uuid, x, y, z, yaw, pitch):
self.send_packet("spawn_player", self.buff.pack_varint(entity_id) + self.buff.pack_uuid(player_uuid) + self.buff_type.pack('dddbbBdb', x, y, z, yaw, pitch, 0, 7, 20))
def send_held_item_change(self, slot):
self.send_packet('held_item_change', self.buff.pack('b', slot))
def send_update_health(self, heal, food):
self.send_packet('update_health', self.buff.pack('f', heal) + self.buff.pack_varint(food) + self.buff.pack('f', 0.0))
def send_set_experience(self, exp, lvl):
self.send_packet('set_experience', self.buff.pack('f', exp) + self.buff.pack_varint(lvl) + self.buff.pack_varint(0))
def send_chat(self, msg):
self.send_packet('chat_message', self.buff.pack_chat(msg) + self.buff.pack('b', 0))
def send_chat_all(self, msg):
for player in self.factory.players:
player.send_packet('chat_message', self.buff.pack_chat(msg) + self.buff.pack('b', 0))
def send_player_list_header_footer(self, up, down):
self.send_packet('player_list_header_footer', self.buff.pack_chat(up) + self.buff.pack_chat(down))
def send_set_slot(self, id, count, slot, window=0):
self.send_packet('set_slot', self.buff.pack('bh', window, slot) + self.buff.pack_slot(id, count, 0, None))
def send_keep_alive(self):
if self.protocol_version <= 338: payload = self.buff.pack_varint(0)
else: payload = self.buff.pack('Q', 0)
self.send_packet('keep_alive', payload)
def set_position(self, x, y, z):
self.send_packet('player_position_and_look', self.buff.pack('dddff?', float(x), float(y), float(z), float(-90), float(0), True))
def kick_all(self, msg):
for player in self.factory.players:
player.kick(msg)
def handle_command(self, command_string):
self.factory.logging('Player ' + self.username + ' issued server command: /' + command_string + '')
command_list = command_string.split(' ')
command, arguments = command_list[0], command_string.split(' ')[1:]
self.plugin_event('player_command', command, arguments)
def get_mode(self):
if self.protocol_mode == 0: mm = 'init'
elif self.protocol_mode == 1: mm = 'status'
elif self.protocol_mode == 2: mm = 'login'
elif self.protocol_mode == 3: mm = 'play'
else: mm = 'unknown'
return mm
def plugin_event(self, event_name, *args, **kwargs):
self.factory.plugin_system.call_event(event_name, self, *args, **kwargs)
def stop(self):
for player in self.factory.players:
player.kick('Server stopped')
reactor.removeAll()
reactor.iterate()
reactor.stop()
class AuthServer(protocol.Factory):
def __init__(self):
self.config = configparser.RawConfigParser()
self.config.read('server.properties')
self.plugin_system = PluginSystem(folder=abspath('plugins'))
self.plugin_system.register_events()
self.players = set()
self.s_port = int(self.config.get('server', 'server-port'))
self.s_host = self.config.get('server', 'server-ip')
self.print_ping = self.str2bool(self.config.get('server', 'print-ping'))
self.max_players = int(self.config.get('server', 'max-players'))
self.debug = self.str2bool(self.config.get('server', 'debug'))
self.motd = self.config.get('server', 'motd')
self.status = {'description': self.motd.replace('&', u'\u00A7'),'players': {'max': self.max_players, 'online': len(self.players)},'version': {'name': '', 'protocol': 0}}
def run(self):
reactor.listenTCP(self.s_port, self, interface=self.s_host)
self.logging('Server started on %s:%s' % (self.s_host, str(self.s_port)))
reactor.run()
self.logging('Done!')
def buildProtocol(self, addr): return AuthProtocol(self, addr)
def logging(self, message):
message = '%s | %s\n' % (datetime.datetime.now().strftime('[%H:%M:%S]'), message)
print(message)
with open('logger.log', 'a') as the_file: the_file.write(message)
def get_status(self, protocol_version):
d = dict(self.status)
d['version']['protocol'] = protocol_version
return d
def str2bool(self, bool):
if bool[0].lower() == 't': return True
return False
if __name__ == '__main__':
AuthServer().run()