1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-07-01 00:45:26 +02:00
Files
vcmi/launcher/proxyServer.py
2022-10-25 05:27:53 +04:00

407 lines
13 KiB
Python
Executable File

from calendar import c
from distutils.command.clean import clean
from pickletools import bytes8
import socket
import re
import uuid
from threading import Thread
# server's IP address
SERVER_HOST = "0.0.0.0"
SERVER_PORT = 5002 # port we want to use
# initialize list/set of all connected client's sockets
client_sockets = dict()
class GameConnection:
server: socket
client: socket
serverInit = False
clientInit = False
messageQueueIn = []
messageQueueOut = []
def __init__(self) -> None:
pass
class Session:
total = 1
joined = 0
password = ""
protected = False
name: str
host: socket
host_uuid: str
players = []
connections = []
started = False
def __init__(self, host: socket, name: str) -> None:
self.name = name
self.host = host
self.players = [host]
self.joined += 1
def isJoined(self, player: socket) -> bool:
return player in self.players
def join(self, player: socket):
if not self.isJoined(player) and self.joined < self.total:
self.players.append(player)
self.joined += 1
def leave(self, player: socket):
if not self.isJoined(player) or player == self.host:
return
self.players.remove(player)
self.joined -= 1
def addConnection(self, conn: socket, isServer: bool) -> GameConnection:
#find uninitialized server connection
for gc in self.connections:
if isServer and not gc.serverInit:
gc.server = conn
gc.serverInit = True
return gc
if not isServer and not gc.clientInit:
gc.client = conn
gc.clientInit = True
return gc
#no existing connection - create the new one
gc = GameConnection()
if isServer:
gc.server = conn
gc.serverInit = True
else:
gc.client = conn
gc.clientInit = True
self.connections.append(gc)
return gc
def validPipe(self, conn) -> bool:
for gc in self.connections:
if gc.server == conn or gc.client == conn:
return gc.serverInit and gc.clientInit
return False
def getPipe(self, conn) -> socket:
for gc in self.connections:
if gc.server == conn:
return gc.client
if gc.client == conn:
return gc.server
# create a TCP socket
s = socket.socket()
# make the port as reusable port
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# bind the socket to the address we specified
s.bind((SERVER_HOST, SERVER_PORT))
# listen for upcoming connections
s.listen(10)
print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")
# list of active sessions
sessions = dict()
def handleDisconnection(client: socket):
sender = client_sockets[client]
if sender["joined"]:
if not sender["session"].started:
if sender["session"].host == client:
#destroy the session, sending messages inside the function
deleteSession(sender["session"])
else:
sender["session"].leave(client)
sender["joined"] = False
message = f":>>KICK:{sender['session'].name}:{sender['username']}"
for client_socket in sender["session"].players:
client_socket.send(message.encode())
updateStatus(sender["session"])
updateSessions()
client.close()
sender["valid"] = False
def send(client: socket, message: str):
sender = client_sockets[client]
if "valid" not in sender or sender["valid"]:
client.send(message.encode())
def broadcast(clients: list, message: str):
for c in clients:
send(c, message)
def sendSessions(client: socket):
msg2 = ""
counter = 0
for s in sessions.values():
if not s.started:
msg2 += f":{s.name}:{s.joined}:{s.total}:{s.protected}"
counter += 1
msg = f":>>SESSIONS:{counter}{msg2}"
send(client, msg)
def updateSessions():
for s in client_sockets.keys():
sendSessions(s)
def deleteSession(session: Session):
msg = f":>>KICK:{session.name}"
for player in session.players:
client_sockets[player]["joined"] = False
msg2 = msg + f":{client_sockets[player]['username']}"
send(player, msg2)
sessions.pop(session.name)
def updateStatus(session: Session):
msg = f":>>STATUS:{session.joined}"
for player in session.players:
msg += f":{client_sockets[player]['username']}:{client_sockets[player]['ready']}"
broadcast(session.players, msg)
def startSession(session: Session):
session.started = True
session.host_uuid = str(uuid.uuid4())
hostMessage = f":>>HOST:{session.host_uuid}:{session.joined - 1}" #one client will be connected locally
for player in session.players:
client_sockets[player]['uuid'] = str(uuid.uuid4())
msg = f":>>START:{client_sockets[player]['uuid']}"
send(player, msg)
#host message must be after start message
send(session.host, hostMessage)
def dispatch(client: socket, sender: dict, arr: bytes):
if len(arr) == 0:
return
msg = str(arr)
print(msg)
if msg[-1] == '\n' or (msg[-1] == '\'' and msg[-2] == '\n'):
sender["prevmessage"] += msg
return
else:
msg = f"{sender['prevmessage']}{msg}"
sender["prevmessage"] = ""
#check for game mode connection
_gameModeIdentifier = msg.partition('Aiya!')
if _gameModeIdentifier[0] != '' and _gameModeIdentifier[1] == 'Aiya!':
sender["aiya"] = True
if sender["aiya"]:
_uuid = msg.partition('$')[2]
match = re.search(r"\((\w+)\)", msg)
_appType = ''
if match != None:
_appType = match.group(1)
if not _uuid == '' and not _appType == '':
#search for uuid
for session in sessions.values():
if session.started:
if _uuid.find(session.host_uuid) != -1 and _appType == "server":
gc = session.addConnection(client, True)
for qmsg in gc.messageQueueIn:
send(gc.server, qmsg)
sender["session"] = session
sender["game"] = True
if not gc.clientInit:
gc.messageQueueOut.append(arr)
return
if _appType == "client":
for p in session.players:
if _uuid.find(client_sockets[p]["uuid"]) != -1:
#client connection
gc = session.addConnection(client, False)
for qmsg in gc.messageQueueOut:
send(gc.client, qmsg)
sender["session"] = session
sender["game"] = True
if not gc.serverInit:
gc.messageQueueIn.append(arr)
return
break
#game mode
if sender["game"] and sender["session"].validPipe(client):
sender["session"].getPipe(client).send(arr)
return
#lobby mode
msg = arr.decode()
_open = msg.partition('<')
_close = _open[2].partition('>')
if _open[0] != '' or _open[1] == '' or _open[2] == '' or _close[0] == '' or _close[1] == '':
print(f"[!] Incorrect message from {sender['address']}: {msg}")
return
_nextTag = _close[2].partition('<')
tag = _close[0]
tag_value = _nextTag[0]
#greetings to the server
if tag == "GREETINGS":
if sender["auth"]:
print(f"[!] Greetings from authorized user {sender['username']} {sender['address']}")
return
print(f"[*] User {sender['address']} autorized as {tag_value}")
sender["username"] = tag_value
sender["auth"] = True
sender["joined"] = False
sendSessions(client)
#VCMI version received
if tag == "VER" and sender["auth"]:
print(f"[*] User {sender['username']} has version {tag_value}")
#message received
if tag == "MSG" and sender["auth"]:
message = f":>>MSG:{sender['username']}:{tag_value}"
if sender["joined"]:
broadcast(sender["session"].players, message)
else:
broadcast(client_sockets.keys(), message)
#new session
if tag == "NEW" and sender["auth"] and not sender["joined"]:
if tag_value in sessions:
#refuse creating game
message = f":>>ERROR:Cannot create session with name {tag_value}, session with this name already exists"
send(client, message)
return
sessions[tag_value] = Session(client, tag_value)
sender["joined"] = True
sender["ready"] = False
sender["session"] = sessions[tag_value]
#set password for the session
if tag == "PSWD" and sender["auth"] and sender["joined"] and sender["session"].host == client:
sender["session"].password = tag_value
sender["session"].protected = tag_value != ""
#set amount of players to the new session
if tag == "COUNT" and sender["auth"] and sender["joined"] and sender["session"].host == client:
if sender["session"].total != 1:
#refuse changing amount of players
message = f":>>ERROR:Changing amount of players is not possible for existing session"
send(client, message)
return
sender["session"].total = int(tag_value)
#now session is ready to be broadcasted
message = f":>>JOIN:{sender['session'].name}:{sender['username']}"
send(client, message)
updateStatus(sender["session"])
updateSessions()
#join session
if tag == "JOIN" and sender["auth"] and not sender["joined"]:
if tag_value not in sessions:
message = f":>>ERROR:Session with name {tag_value} doesn't exist"
send(client, message)
return
if sessions[tag_value].joined >= sessions[tag_value].total:
message = f":>>ERROR:Session {tag_value} is full"
send(client, message)
return
if sessions[tag_value].started:
message = f":>>ERROR:Session {tag_value} is started"
send(client, message)
return
sender["joined"] = True
sender["ready"] = False
sender["session"] = sessions[tag_value]
if tag == "PSWD" and sender["auth"] and sender["joined"] and sender["session"].host != client:
if not sender["session"].protected or sender["session"].password == tag_value:
sender["session"].join(client)
message = f":>>JOIN:{sender['session'].name}:{sender['username']}"
broadcast(sender["session"].players, message)
updateStatus(sender["session"])
updateSessions()
else:
sender["joined"] = False
message = f":>>ERROR:Incorrect password"
send(client, message)
return
#leaving session
if tag == "LEAVE" and sender["auth"] and sender["joined"] and sender["session"].name == tag_value:
if sender["session"].host == client:
#destroy the session, sending messages inside the function
deleteSession(sender["session"])
else:
message = f":>>KICK:{sender['session'].name}:{sender['username']}"
broadcast(sender["session"].players, message)
sender["session"].leave(client)
sender["joined"] = False
updateStatus(sender["session"])
updateSessions()
if tag == "READY" and sender["auth"] and sender["joined"] and sender["session"].name == tag_value:
if sender["session"].joined > 0 and sender["session"].host == client:
startSession(sender["session"])
updateSessions()
dispatch(client, sender, (_nextTag[1] + _nextTag[2]).encode())
def listen_for_client(cs):
"""
This function keep listening for a message from `cs` socket
Whenever a message is received, broadcast it to all other connected clients
"""
while True:
try:
# keep listening for a message from `cs` socket
msg = cs.recv(2048)
except Exception as e:
# client no longer connected
print(f"[!] Error: {e}")
handleDisconnection(cs)
return
dispatch(cs, client_sockets[cs], msg)
while True:
# we keep listening for new connections all the time
client_socket, client_address = s.accept()
print(f"[+] {client_address} connected.")
# add the new connected client to connected sockets
client_sockets[client_socket] = {"address": client_address, "auth": False, "username": "", "joined": False, "aiya": False, "prevmessage": "", "game": False}
# start a new thread that listens for each client's messages
t = Thread(target=listen_for_client, args=(client_socket,))
# make the thread daemon so it ends whenever the main thread ends
t.daemon = True
# start the thread
t.start()
# close client sockets
for cs in client_sockets:
cs.close()
# close server socket
s.close()