Path: blob/main/Server/Server.py
817 views
import builtins1import socket2import socketserver3import struct4import random5import sys6from typing import NamedTuple, Set, Tuple7from enum import IntEnum8from datetime import datetime910DATASIZE = 0x100011CODES: Set[int] = set()12printmode = 11314SPSTRUCT = struct.Struct(f"B7sII{DATASIZE - 16}s")15EMPTY = b"\0\0\0\0\0\0\0\0"1617CONNECT_MAGIC = 0x1F2F3F4F181920class ClientHeaders(IntEnum):21REQUEST_CODE = 0x0022JOIN = 0x012324DATA = 0x1025DATA_VERIFIED = 0x112627QUERY_VERIFICATION = 0x202829LEAVE = 0xFF303132class ServerHeaders(IntEnum):33CODES = 0x0034NEW_PLAYER = 0x013536DATA = 0x1037DATA_VERIFIED = 0x113839RECEIVED = 0x2040VERIFY_CLEAR = 0x214142INVALID_HEADER = 0x8043NO_ROOM = 0x8144UNKNOWN_PLAYER = 0x824546LEAVE = 0xFF474849class ServerPacket(NamedTuple):50header: int51game: bytes52player: int53room: int54data: bytes5556@classmethod57def frombytes(cls, data):58return cls._make(SPSTRUCT.unpack(data))5960def tobytes(self) -> bytes:61s = ServerPacket(self.header, self.game,62self.player, self.room, self.data)63return SPSTRUCT.pack(*tuple(s))6465def getint(self, index) -> int:66return int.from_bytes(self.data[index * 4:(index + 1) * 4], 'little')676869def hex(i: int) -> str:70return builtins.hex(i).upper()[2:].zfill(8)717273def randint() -> int:74return random.randint(1, 0xFFFFFFFF)7576def print(*args):77builtins.print(datetime.now().strftime("[%m-%d-%y %H:%M:%S]"), *args)787980class Player:81def __init__(self, client, code, game: "Game") -> None:82self.client = client83self.code: int = code84self.room: Room = game.get_room(0)85self.game = game86game.join(self)87self.room.players.add(self)8889def move(self, room: "Room"):90self.room.leave(self)91self.room = room92self.room.join(self)93if self.room not in self.game.rooms:94self.game.rooms.add(self.room)9596def __hash__(self) -> int:97return self.code9899def deliver(self, packet: ServerPacket):100if printmode == 2:101print(f"[{self.game.name}/{hex(self.room.code)}] {ServerHeaders(packet.header).name} from {hex(packet.player)} to {hex(self.code)}")102server.socket.sendto(packet.tobytes(), self.client)103104105class Room:106def __init__(self, game: "Game", code=0) -> None:107self.code = code108self.players: Set[Player] = set()109self.game: Game = game110111self.verifiying = False112self.verified: Set[Player] = set()113self.vcopy: Set[Player] = set()114115def __hash__(self) -> int:116return self.code117118def join(self, player: Player):119if player not in self.players:120self.players.add(player)121if printmode:122print(123f"[{self.game.name}/{hex(self.code)}] Player {hex(player.code)} joined")124125def leave(self, player: Player):126if player in self.players:127self.players.remove(player)128if printmode:129print(130f"[{self.game.name}/{hex(self.code)}] Player {hex(player.code)} left")131132def deliver(self, packet: ServerPacket, sender: Player) -> int:133sent = 0134queueSends = False135if packet.header in (ClientHeaders.DATA_VERIFIED, ClientHeaders.QUERY_VERIFICATION):136if not self.vcopy:137self.vcopy = self.players.copy()138if sender not in self.vcopy:139# impostor (we let them in bc we are Stupid Crewmates140self.vcopy.add(sender)141if self.players - self.vcopy:142# some people left. L143self.vcopy &= self.players144145if packet.header == ClientHeaders.DATA_VERIFIED:146self.verifiying = True147self.verified.add(sender)148if printmode:149print(150f"[{self.game.name}/{hex(self.code)}] Verifying {len(self.verified)}/{len(self.vcopy)}")151152elif not self.verifiying:153# hack. if querying and not verifying, use this to force "yes"154self.verified = self.players155156if self.vcopy == self.verified:157pl = b"".join([x.code.to_bytes(8, "little")158for x in self.verified])159queueSends = True160self.verified.clear()161self.vcopy.clear()162self.verifiying = False163else:164sender.deliver(ServerPacket(ServerHeaders.RECEIVED,165self.game.bytename, sender.code, self.code, bytes()))166167if packet.header != ClientHeaders.QUERY_VERIFICATION:168for player in self.players:169if player.code != sender.code:170player.deliver(packet)171sent += 1172173if queueSends:174for player in self.players:175player.deliver(ServerPacket(ServerHeaders.VERIFY_CLEAR,176self.game.bytename, player.code, self.code, pl))177return sent178179180class EmptyRoom(Room):181def __init__(self, game: "Game") -> None:182super().__init__(game, code=0)183184def deliver(self):185return186187def join(self, player: Player):188self.players.add(player)189190def leave(self, player: Player):191try:192self.players.remove(player)193except:194pass195196197class Game:198def __init__(self, name: bytes) -> None:199self.name: str = name.decode().rstrip("\x00")200self.bytename: bytes = name201self.rooms: Set[Room] = {EmptyRoom(self)}202203def __hash__(self) -> int:204return hash(self.name)205206def join(self, player: Player) -> None:207self.rooms.add(player.room)208if printmode:209print(210f"[{self.name}] New player {hex(player.code)}{f' in {hex(player.room.code)}' if player.room.code else ''}")211212def leave(self, player: Player) -> bool:213for r in self.rooms.copy():214if player in r.players:215r.players.remove(player)216self.rooms.remove(r)217if printmode:218print(219f"[{self.name}] Player {hex(player.code)} in room {hex(player.room.code)} has left")220return True221return False222223def get_room(self, room: int) -> Room:224for x in self.rooms:225if x.code == room:226return x227return self.get_room(0)228229def deliver(self, packet: ServerPacket, sender: Player) -> int:230return sender.room.deliver(packet, sender)231232233class Handler(socketserver.BaseRequestHandler):234def handle(self):235self.server: Server236self.request: Tuple[bytes, socket.socket]237238data = ServerPacket.frombytes(self.request[0])239try:240ClientHeaders(data.header)241except:242return # not a valid header243if printmode == 2:244print(245f"(SERVER) {ClientHeaders(data.header).name} from {hex(data.player)} to server")246if not data.player:247if data.header != ClientHeaders.REQUEST_CODE and data.room != CONNECT_MAGIC:248return # shhhhhh249player = Player(self.client_address, randint(),250self.server.get_game(data.game))251252return self.send(ServerHeaders.CODES, player)253254# it begins255256if data.header == ClientHeaders.REQUEST_CODE:257# let's give a room code back and reassign258259p = self.server.resolve_player(data.player, data.game)260if not p:261return self.send_raw(ServerPacket(ServerHeaders.UNKNOWN_PLAYER, data.game, data.player, data.room, bytes()))262if p.room.code:263return self.send(ServerHeaders.CODES, p, len(p.room.players).to_bytes(4, 'little') + b''.join([x.code.to_bytes(8, "little")264for x in p.room.players - {p}]))265roomid = 0266while (roomid in [x.code for x in p.game.rooms]):267roomid = ((randint() & 0xFFFFFFFF) & ~268data.getint(0)) | data.getint(1)269r = Room(p.game, roomid)270p.move(r)271return self.send(ServerHeaders.CODES, p, len(r.players).to_bytes(4, 'little') + b''.join([x.code.to_bytes(8, "little")272for x in r.players - {p}]))273if data.header == ClientHeaders.JOIN:274# data will be how many players there are so the client can decide "this is too many players"275p = self.server.resolve_player(data.player, data.game)276r = p.game.get_room(data.room)277if not r.code:278return self.send(ServerHeaders.NO_ROOM, p)279p.move(r)280self.send(ServerHeaders.CODES, p, len(r.players).to_bytes(4, 'little') + b''.join([x.code.to_bytes(8, "little")281for x in r.players - {p}]))282r.deliver(ServerPacket(ServerHeaders.NEW_PLAYER,283data.game, p.code, r.code, bytes()), p)284if data.header in (ClientHeaders.DATA, ClientHeaders.DATA_VERIFIED, ClientHeaders.QUERY_VERIFICATION):285if not data.room:286return self.send_raw(ServerPacket(ServerHeaders.INVALID_HEADER, data.game, data.player, data.room, bytes()))287p = self.server.resolve_player(data.player, data.game, data.room)288if not p:289return self.send_raw(ServerPacket(ServerHeaders.UNKNOWN_PLAYER, data.game, data.player, data.room, bytes()))290p.room.deliver(data, p)291292if data.header == ClientHeaders.LEAVE:293p = self.server.resolve_player(data.player, data.game, data.room)294if not p or not p.room:295return296p.room.leave(p)297if not p.room.code:298return299if p.room.players:300p.room.deliver(data, p)301else:302p.game.rooms.remove(p.room)303if printmode:304print(f"[{p.game.name}] Room {hex(p.room.code)} disbanded")305306def send(self, header, player: Player, data=()):307self.request[1].sendto(ServerPacket(header, player.game.bytename,308player.code, player.room.code, bytes(data)).tobytes(), player.client)309310def send_raw(self, packet: ServerPacket):311self.request[1].sendto(packet.tobytes(), self.client_address)312313314class Server(socketserver.ThreadingUDPServer):315def __init__(self, server_address) -> None:316super().__init__(server_address, Handler)317self.games: Set[Game] = set()318319def get_game(self, name: bytes, create=True) -> Game:320for game in self.games:321if game.bytename == name:322return game323if not create:324return False325# game doesn't exist, let's make it326g = Game(name)327self.games.add(g)328if printmode:329print(f"Game {g.name} created")330return g331332def resolve_player(self, player: int, game: bytes = None, room: int = -1):333g = None334r = None335if game:336g = self.get_game(game, False)337if g:338if room != -1:339for x in g.rooms:340if x.code == room:341r = x342break343344def searchR(player, r=r):345for x in r.players:346if x.code == player:347return x348return None349350def searchG(player, g=g):351for r in g.rooms:352if ret := searchR(player, r):353return ret354return None355356if not (g or r):357358def search(player):359for g in self.games:360if ret := searchG(player, g):361return ret362return None363364elif g and not r:365search = searchG366else:367search = searchR368return search(player)369370371if len(sys.argv) < 2:372print("must have IP and port")373exit()374375ip = sys.argv[1]376p = int(sys.argv[2])377378server: Server = Server((ip, p))379380if len(sys.argv) > 3:381for x in sys.argv[2:]:382if x == "silent":383printmode = 0384if x == "debug":385printmode = 1386if x == "verbose":387printmode = 2388389print("starting..")390server.serve_forever()391392393