Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rubberduckycooly
GitHub Repository: rubberduckycooly/Sonic-1-2-2013-Decompilation
Path: blob/main/Server/Server.py
817 views
1
import builtins
2
import socket
3
import socketserver
4
import struct
5
import random
6
import sys
7
from typing import NamedTuple, Set, Tuple
8
from enum import IntEnum
9
from datetime import datetime
10
11
DATASIZE = 0x1000
12
CODES: Set[int] = set()
13
printmode = 1
14
15
SPSTRUCT = struct.Struct(f"B7sII{DATASIZE - 16}s")
16
EMPTY = b"\0\0\0\0\0\0\0\0"
17
18
CONNECT_MAGIC = 0x1F2F3F4F
19
20
21
class ClientHeaders(IntEnum):
22
REQUEST_CODE = 0x00
23
JOIN = 0x01
24
25
DATA = 0x10
26
DATA_VERIFIED = 0x11
27
28
QUERY_VERIFICATION = 0x20
29
30
LEAVE = 0xFF
31
32
33
class ServerHeaders(IntEnum):
34
CODES = 0x00
35
NEW_PLAYER = 0x01
36
37
DATA = 0x10
38
DATA_VERIFIED = 0x11
39
40
RECEIVED = 0x20
41
VERIFY_CLEAR = 0x21
42
43
INVALID_HEADER = 0x80
44
NO_ROOM = 0x81
45
UNKNOWN_PLAYER = 0x82
46
47
LEAVE = 0xFF
48
49
50
class ServerPacket(NamedTuple):
51
header: int
52
game: bytes
53
player: int
54
room: int
55
data: bytes
56
57
@classmethod
58
def frombytes(cls, data):
59
return cls._make(SPSTRUCT.unpack(data))
60
61
def tobytes(self) -> bytes:
62
s = ServerPacket(self.header, self.game,
63
self.player, self.room, self.data)
64
return SPSTRUCT.pack(*tuple(s))
65
66
def getint(self, index) -> int:
67
return int.from_bytes(self.data[index * 4:(index + 1) * 4], 'little')
68
69
70
def hex(i: int) -> str:
71
return builtins.hex(i).upper()[2:].zfill(8)
72
73
74
def randint() -> int:
75
return random.randint(1, 0xFFFFFFFF)
76
77
def print(*args):
78
builtins.print(datetime.now().strftime("[%m-%d-%y %H:%M:%S]"), *args)
79
80
81
class Player:
82
def __init__(self, client, code, game: "Game") -> None:
83
self.client = client
84
self.code: int = code
85
self.room: Room = game.get_room(0)
86
self.game = game
87
game.join(self)
88
self.room.players.add(self)
89
90
def move(self, room: "Room"):
91
self.room.leave(self)
92
self.room = room
93
self.room.join(self)
94
if self.room not in self.game.rooms:
95
self.game.rooms.add(self.room)
96
97
def __hash__(self) -> int:
98
return self.code
99
100
def deliver(self, packet: ServerPacket):
101
if printmode == 2:
102
print(f"[{self.game.name}/{hex(self.room.code)}] {ServerHeaders(packet.header).name} from {hex(packet.player)} to {hex(self.code)}")
103
server.socket.sendto(packet.tobytes(), self.client)
104
105
106
class Room:
107
def __init__(self, game: "Game", code=0) -> None:
108
self.code = code
109
self.players: Set[Player] = set()
110
self.game: Game = game
111
112
self.verifiying = False
113
self.verified: Set[Player] = set()
114
self.vcopy: Set[Player] = set()
115
116
def __hash__(self) -> int:
117
return self.code
118
119
def join(self, player: Player):
120
if player not in self.players:
121
self.players.add(player)
122
if printmode:
123
print(
124
f"[{self.game.name}/{hex(self.code)}] Player {hex(player.code)} joined")
125
126
def leave(self, player: Player):
127
if player in self.players:
128
self.players.remove(player)
129
if printmode:
130
print(
131
f"[{self.game.name}/{hex(self.code)}] Player {hex(player.code)} left")
132
133
def deliver(self, packet: ServerPacket, sender: Player) -> int:
134
sent = 0
135
queueSends = False
136
if packet.header in (ClientHeaders.DATA_VERIFIED, ClientHeaders.QUERY_VERIFICATION):
137
if not self.vcopy:
138
self.vcopy = self.players.copy()
139
if sender not in self.vcopy:
140
# impostor (we let them in bc we are Stupid Crewmates
141
self.vcopy.add(sender)
142
if self.players - self.vcopy:
143
# some people left. L
144
self.vcopy &= self.players
145
146
if packet.header == ClientHeaders.DATA_VERIFIED:
147
self.verifiying = True
148
self.verified.add(sender)
149
if printmode:
150
print(
151
f"[{self.game.name}/{hex(self.code)}] Verifying {len(self.verified)}/{len(self.vcopy)}")
152
153
elif not self.verifiying:
154
# hack. if querying and not verifying, use this to force "yes"
155
self.verified = self.players
156
157
if self.vcopy == self.verified:
158
pl = b"".join([x.code.to_bytes(8, "little")
159
for x in self.verified])
160
queueSends = True
161
self.verified.clear()
162
self.vcopy.clear()
163
self.verifiying = False
164
else:
165
sender.deliver(ServerPacket(ServerHeaders.RECEIVED,
166
self.game.bytename, sender.code, self.code, bytes()))
167
168
if packet.header != ClientHeaders.QUERY_VERIFICATION:
169
for player in self.players:
170
if player.code != sender.code:
171
player.deliver(packet)
172
sent += 1
173
174
if queueSends:
175
for player in self.players:
176
player.deliver(ServerPacket(ServerHeaders.VERIFY_CLEAR,
177
self.game.bytename, player.code, self.code, pl))
178
return sent
179
180
181
class EmptyRoom(Room):
182
def __init__(self, game: "Game") -> None:
183
super().__init__(game, code=0)
184
185
def deliver(self):
186
return
187
188
def join(self, player: Player):
189
self.players.add(player)
190
191
def leave(self, player: Player):
192
try:
193
self.players.remove(player)
194
except:
195
pass
196
197
198
class Game:
199
def __init__(self, name: bytes) -> None:
200
self.name: str = name.decode().rstrip("\x00")
201
self.bytename: bytes = name
202
self.rooms: Set[Room] = {EmptyRoom(self)}
203
204
def __hash__(self) -> int:
205
return hash(self.name)
206
207
def join(self, player: Player) -> None:
208
self.rooms.add(player.room)
209
if printmode:
210
print(
211
f"[{self.name}] New player {hex(player.code)}{f' in {hex(player.room.code)}' if player.room.code else ''}")
212
213
def leave(self, player: Player) -> bool:
214
for r in self.rooms.copy():
215
if player in r.players:
216
r.players.remove(player)
217
self.rooms.remove(r)
218
if printmode:
219
print(
220
f"[{self.name}] Player {hex(player.code)} in room {hex(player.room.code)} has left")
221
return True
222
return False
223
224
def get_room(self, room: int) -> Room:
225
for x in self.rooms:
226
if x.code == room:
227
return x
228
return self.get_room(0)
229
230
def deliver(self, packet: ServerPacket, sender: Player) -> int:
231
return sender.room.deliver(packet, sender)
232
233
234
class Handler(socketserver.BaseRequestHandler):
235
def handle(self):
236
self.server: Server
237
self.request: Tuple[bytes, socket.socket]
238
239
data = ServerPacket.frombytes(self.request[0])
240
try:
241
ClientHeaders(data.header)
242
except:
243
return # not a valid header
244
if printmode == 2:
245
print(
246
f"(SERVER) {ClientHeaders(data.header).name} from {hex(data.player)} to server")
247
if not data.player:
248
if data.header != ClientHeaders.REQUEST_CODE and data.room != CONNECT_MAGIC:
249
return # shhhhhh
250
player = Player(self.client_address, randint(),
251
self.server.get_game(data.game))
252
253
return self.send(ServerHeaders.CODES, player)
254
255
# it begins
256
257
if data.header == ClientHeaders.REQUEST_CODE:
258
# let's give a room code back and reassign
259
260
p = self.server.resolve_player(data.player, data.game)
261
if not p:
262
return self.send_raw(ServerPacket(ServerHeaders.UNKNOWN_PLAYER, data.game, data.player, data.room, bytes()))
263
if p.room.code:
264
return self.send(ServerHeaders.CODES, p, len(p.room.players).to_bytes(4, 'little') + b''.join([x.code.to_bytes(8, "little")
265
for x in p.room.players - {p}]))
266
roomid = 0
267
while (roomid in [x.code for x in p.game.rooms]):
268
roomid = ((randint() & 0xFFFFFFFF) & ~
269
data.getint(0)) | data.getint(1)
270
r = Room(p.game, roomid)
271
p.move(r)
272
return self.send(ServerHeaders.CODES, p, len(r.players).to_bytes(4, 'little') + b''.join([x.code.to_bytes(8, "little")
273
for x in r.players - {p}]))
274
if data.header == ClientHeaders.JOIN:
275
# data will be how many players there are so the client can decide "this is too many players"
276
p = self.server.resolve_player(data.player, data.game)
277
r = p.game.get_room(data.room)
278
if not r.code:
279
return self.send(ServerHeaders.NO_ROOM, p)
280
p.move(r)
281
self.send(ServerHeaders.CODES, p, len(r.players).to_bytes(4, 'little') + b''.join([x.code.to_bytes(8, "little")
282
for x in r.players - {p}]))
283
r.deliver(ServerPacket(ServerHeaders.NEW_PLAYER,
284
data.game, p.code, r.code, bytes()), p)
285
if data.header in (ClientHeaders.DATA, ClientHeaders.DATA_VERIFIED, ClientHeaders.QUERY_VERIFICATION):
286
if not data.room:
287
return self.send_raw(ServerPacket(ServerHeaders.INVALID_HEADER, data.game, data.player, data.room, bytes()))
288
p = self.server.resolve_player(data.player, data.game, data.room)
289
if not p:
290
return self.send_raw(ServerPacket(ServerHeaders.UNKNOWN_PLAYER, data.game, data.player, data.room, bytes()))
291
p.room.deliver(data, p)
292
293
if data.header == ClientHeaders.LEAVE:
294
p = self.server.resolve_player(data.player, data.game, data.room)
295
if not p or not p.room:
296
return
297
p.room.leave(p)
298
if not p.room.code:
299
return
300
if p.room.players:
301
p.room.deliver(data, p)
302
else:
303
p.game.rooms.remove(p.room)
304
if printmode:
305
print(f"[{p.game.name}] Room {hex(p.room.code)} disbanded")
306
307
def send(self, header, player: Player, data=()):
308
self.request[1].sendto(ServerPacket(header, player.game.bytename,
309
player.code, player.room.code, bytes(data)).tobytes(), player.client)
310
311
def send_raw(self, packet: ServerPacket):
312
self.request[1].sendto(packet.tobytes(), self.client_address)
313
314
315
class Server(socketserver.ThreadingUDPServer):
316
def __init__(self, server_address) -> None:
317
super().__init__(server_address, Handler)
318
self.games: Set[Game] = set()
319
320
def get_game(self, name: bytes, create=True) -> Game:
321
for game in self.games:
322
if game.bytename == name:
323
return game
324
if not create:
325
return False
326
# game doesn't exist, let's make it
327
g = Game(name)
328
self.games.add(g)
329
if printmode:
330
print(f"Game {g.name} created")
331
return g
332
333
def resolve_player(self, player: int, game: bytes = None, room: int = -1):
334
g = None
335
r = None
336
if game:
337
g = self.get_game(game, False)
338
if g:
339
if room != -1:
340
for x in g.rooms:
341
if x.code == room:
342
r = x
343
break
344
345
def searchR(player, r=r):
346
for x in r.players:
347
if x.code == player:
348
return x
349
return None
350
351
def searchG(player, g=g):
352
for r in g.rooms:
353
if ret := searchR(player, r):
354
return ret
355
return None
356
357
if not (g or r):
358
359
def search(player):
360
for g in self.games:
361
if ret := searchG(player, g):
362
return ret
363
return None
364
365
elif g and not r:
366
search = searchG
367
else:
368
search = searchR
369
return search(player)
370
371
372
if len(sys.argv) < 2:
373
print("must have IP and port")
374
exit()
375
376
ip = sys.argv[1]
377
p = int(sys.argv[2])
378
379
server: Server = Server((ip, p))
380
381
if len(sys.argv) > 3:
382
for x in sys.argv[2:]:
383
if x == "silent":
384
printmode = 0
385
if x == "debug":
386
printmode = 1
387
if x == "verbose":
388
printmode = 2
389
390
print("starting..")
391
server.serve_forever()
392
393