diff --git a/latest version/core/constant.py b/latest version/core/constant.py index f08a7de..8a36fee 100644 --- a/latest version/core/constant.py +++ b/latest version/core/constant.py @@ -38,7 +38,12 @@ class Constant: DOWNLOAD_LINK_PREFIX = Config.DOWNLOAD_LINK_PREFIX LINK_PLAY_UNLOCK_LENGTH = 512 # Units: bytes - LINK_PLAY_TIMEOUT = 10 # Units: seconds + LINK_PLAY_TIMEOUT = 5 # Units: seconds + + LINK_PLAY_HOST = '127.0.0.1' if Config.SET_LINK_PLAY_SERVER_AS_SUB_PROCESS else Config.LINK_PLAY_HOST + LINK_PLAY_TCP_PORT = Config.LINK_PLAY_TCP_PORT + LINK_PLAY_UDP_PORT = Config.LINK_PLAY_UDP_PORT + LINK_PLAY_AUTHENTICATION = Config.LINK_PLAY_AUTHENTICATION COURSE_STAMINA_COST = 4 diff --git a/latest version/core/linkplay.py b/latest version/core/linkplay.py index 459a226..31065d5 100644 --- a/latest version/core/linkplay.py +++ b/latest version/core/linkplay.py @@ -1,10 +1,13 @@ -from base64 import b64encode +import socket +from base64 import b64decode, b64encode from core.error import ArcError, Timeout from .constant import Constant from .user import UserInfo +socket.setdefaulttimeout(Constant.LINK_PLAY_TIMEOUT) + def get_song_unlock(client_song_map: dict) -> bytes: '''处理可用歌曲bit,返回bytes''' @@ -82,9 +85,8 @@ class Room: } -class LocalMultiPlayer: - def __init__(self, conn=None) -> None: - self.conn = conn +class RemoteMultiPlayer: + def __init__(self) -> None: self.user: 'Player' = None self.room: 'Room' = None @@ -93,30 +95,44 @@ class LocalMultiPlayer: def to_dict(self) -> dict: return dict(self.room.to_dict(), **self.user.to_dict()) + @staticmethod + def tcp(data: str) -> str: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect((Constant.LINK_PLAY_HOST, + Constant.LINK_PLAY_TCP_PORT)) + sock.sendall(bytes(data + "\n", "utf-8")) + try: + received = str(sock.recv(1024), "utf-8").strip() + except socket.timeout: + raise Timeout( + 'Timeout when waiting for data from link play server.', status=400) + # print(received) + return received + def data_swap(self, data: tuple) -> tuple: - self.conn.send(data) - if self.conn.poll(Constant.LINK_PLAY_TIMEOUT): - self.data_recv = self.conn.recv() - if self.data_recv[0] != 0: - raise ArcError('Link Play error.', - self.data_recv[0], status=400) - else: - raise Timeout( - 'Timeout when waiting for data from local udp server.', status=400) + + received = self.tcp(Constant.LINK_PLAY_AUTHENTICATION + + '|' + '|'.join([str(x) for x in data])) + + self.data_recv = received.split('|') + if self.data_recv[0] != '0': + raise ArcError('Link Play error.', + int(self.data_recv[0]), status=400) def create_room(self, user: 'Player' = None) -> None: '''创建房间''' if user is not None: self.user = user user.select_user_one_column('name') - self.data_swap((1, self.user.name, self.user.song_unlock)) + self.data_swap((1, self.user.name, b64encode( + self.user.song_unlock).decode('utf-8'))) self.room = Room() self.room.room_code = self.data_recv[1] - self.room.room_id = self.data_recv[2] + self.room.room_id = int(self.data_recv[2]) self.room.song_unlock = self.user.song_unlock - self.user.token = self.data_recv[3] - self.user.key = self.data_recv[4] - self.user.player_id = self.data_recv[5] + self.user.token = int(self.data_recv[3]) + self.user.key = b64decode(self.data_recv[4]) + self.user.player_id = int(self.data_recv[5]) def join_room(self, room: 'Room' = None, user: 'Player' = None) -> None: '''加入房间''' @@ -127,13 +143,13 @@ class LocalMultiPlayer: self.user.select_user_one_column('name') self.data_swap( - (2, self.user.name, self.user.song_unlock, room.room_code)) + (2, self.user.name, b64encode(self.user.song_unlock).decode('utf-8'), room.room_code)) self.room.room_code = self.data_recv[1] - self.room.room_id = self.data_recv[2] - self.room.song_unlock = self.data_recv[6] - self.user.token = self.data_recv[3] - self.user.key = self.data_recv[4] - self.user.player_id = self.data_recv[5] + self.room.room_id = int(self.data_recv[2]) + self.room.song_unlock = b64decode(self.data_recv[6]) + self.user.token = int(self.data_recv[3]) + self.user.key = b64decode(self.data_recv[4]) + self.user.player_id = int(self.data_recv[5]) def update_room(self, user: 'Player' = None) -> None: '''更新房间''' @@ -142,7 +158,7 @@ class LocalMultiPlayer: self.data_swap((3, self.user.token)) self.room = Room() self.room.room_code = self.data_recv[1] - self.room.room_id = self.data_recv[2] - self.room.song_unlock = self.data_recv[5] - self.user.key = self.data_recv[3] - self.user.player_id = self.data_recv[4] + self.room.room_id = int(self.data_recv[2]) + self.room.song_unlock = b64decode(self.data_recv[5]) + self.user.key = b64decode(self.data_recv[3]) + self.user.player_id = int(self.data_recv[4]) diff --git a/latest version/linkplay_server/__init__.py b/latest version/linkplay_server/__init__.py new file mode 100644 index 0000000..30eb288 --- /dev/null +++ b/latest version/linkplay_server/__init__.py @@ -0,0 +1 @@ +from .main import link_play diff --git a/latest version/udpserver/aes.py b/latest version/linkplay_server/aes.py similarity index 94% rename from latest version/udpserver/aes.py rename to latest version/linkplay_server/aes.py index 21de449..7248382 100644 --- a/latest version/udpserver/aes.py +++ b/latest version/linkplay_server/aes.py @@ -1,11 +1,11 @@ -import os +from os import urandom from cryptography.hazmat.primitives.ciphers import ( Cipher, algorithms, modes ) def encrypt(key, plaintext, associated_data): - iv = os.urandom(12) + iv = urandom(12) encryptor = Cipher( algorithms.AES(key), modes.GCM(iv, min_tag_length=12), diff --git a/latest version/linkplay_server/config.py b/latest version/linkplay_server/config.py new file mode 100644 index 0000000..2744d88 --- /dev/null +++ b/latest version/linkplay_server/config.py @@ -0,0 +1,30 @@ +from telnetlib import AUTHENTICATION + + +class Config: + ''' + Link Play server configuration + ''' + + ''' + 服务器地址、端口号、校验码 + Server address, port and verification code + ''' + HOST = '0.0.0.0' + UDP_PORT = 10900 + TCP_PORT = 10901 + AUTHENTICATION = 'my_link_play_server' + ''' + -------------------------------------------------- + ''' + + TIME_LIMIT = 3600000 + + COMMAND_INTERVAL = 1000000 + + COUNTDOWM_TIME = 3999 + + PLAYER_PRE_TIMEOUT = 3000000 + PLAYER_TIMEOUT = 20000000 + + LINK_PLAY_UNLOCK_LENGTH = 512 diff --git a/latest version/linkplay_server/main.py b/latest version/linkplay_server/main.py new file mode 100644 index 0000000..73e62fa --- /dev/null +++ b/latest version/linkplay_server/main.py @@ -0,0 +1,253 @@ +import base64 +import random +import socketserver +import threading +import time +from os import urandom + +# import binascii +from .aes import decrypt, encrypt +from .config import Config +from .udp_class import Player, Room, bi +from .udp_parser import CommandParser + +# token: {'key': key, 'room': Room, 'player_index': player_index, 'player_id': player_id} +link_play_data = {} +room_id_dict = {} # 'room_id': Room +room_code_dict = {} # 'room_code': Room +player_dict = {} # 'player_id' : Player +clean_timer = 0 +lock = threading.RLock() + + +def random_room_code(): + # 随机生成房间号 + re = '' + for _ in range(4): + re += chr(random.randint(65, 90)) + for _ in range(2): + re += str(random.randint(0, 9)) + + return re + + +def unique_random(dataset, length=8, random_func=None): + '''无重复随机,且默认非0''' + if random_func is None: + x = bi(urandom(length)) + while x in dataset or x == 0: + x = bi(urandom(length)) + else: + x = random_func() + while x in dataset: + x = random_func() + return x + + +def clear_player(token): + # 清除玩家信息和token + del player_dict[link_play_data[token]['player_id']] + del link_play_data[token] + + +def clear_room(room): + # 清除房间信息 + room_id = room.room_id + room_code = room.room_code + del room_id_dict[room_id] + del room_code_dict[room_code] + del room + + +def memory_clean(now): + # 内存清理 + lock.acquire() + + clean_room_list = [] + clean_player_list = [] + for token in link_play_data: + room = link_play_data[token]['room'] + if now - room.timestamp >= Config.TIME_LIMIT: + clean_room_list.append(room.room_id) + + if now - room.players[link_play_data[token]['player_index']].last_timestamp // 1000 >= Config.TIME_LIMIT: + clean_player_list.append(token) + + for room_id in room_id_dict: + if now - room_id_dict[room_id].timestamp >= Config.TIME_LIMIT: + clean_room_list.append(room_id) + + for room_id in clean_room_list: + if room_id in room_id_dict: + clear_room(room_id_dict[room_id]) + + for token in clean_player_list: + clear_player(token) + + lock.release() + + +class UDP_handler(socketserver.BaseRequestHandler): + def handle(self): + client_msg, server = self.request + try: + token = client_msg[:8] + iv = client_msg[8:20] + tag = client_msg[20:32] + ciphertext = client_msg[32:] + if int.from_bytes(token, byteorder='little') in link_play_data: + user = link_play_data[bi(token)] + else: + return None + + plaintext = decrypt(user['key'], b'', iv, ciphertext, tag) + except Exception as e: + print(e) + return None + # print(binascii.b2a_hex(plaintext)) + + commands = CommandParser( + user['room'], user['player_index']).get_commands(plaintext) + + if user['room'].players[user['player_index']].player_id == 0: + clear_player(bi(token)) + temp = [] + for i in commands: + if i[:3] == b'\x06\x16\x12': + temp.append(i) + commands = temp + # 处理不能正确被踢的问题 + + for i in commands: + iv, ciphertext, tag = encrypt(user['key'], i, b'') + # print(binascii.b2a_hex(i)) + + server.sendto(token + iv + tag[:12] + + ciphertext, self.client_address) + + +class TCP_handler(socketserver.StreamRequestHandler): + def handle(self): + self.data = self.rfile.readline().strip() + + message = self.data.decode('utf-8') + # print(message) + data = message.split('|') + if data[0] != Config.AUTHENTICATION: + self.wfile.write(b'No authentication') + return None + + global clean_timer + now = round(time.time() * 1000) + if now - clean_timer >= Config.TIME_LIMIT: + clean_timer = now + memory_clean(now) + + self.wfile.write(data_swap(data[1:]).encode('utf-8')) + + +def data_swap(data: list) -> str: + # data: list[str] = [command, ...] + if data[0] == '1': + # 开房 + # data = ['1', name, song_unlock, ] + # song_unlock: base64 str + name = data[1] + song_unlock = base64.b64decode(data[2]) + + key = urandom(16) + room_id = unique_random(room_id_dict) + + room = Room() + room.room_id = room_id + room_id_dict[room_id] = room + + player_id = unique_random(player_dict, 3) + player = Player() + player.player_id = player_id + player.set_player_name(name) + player_dict[player_id] = player + + player.song_unlock = song_unlock + room.song_unlock = song_unlock + room.host_id = player_id + room.players[0] = player + room.player_num = 1 + + room_code = unique_random(room_code_dict, random_func=random_room_code) + room.room_code = room_code + room_code_dict[room_code] = room + + token = room_id + player.token = token + + link_play_data[token] = {'key': key, + 'room': room, + 'player_index': 0, + 'player_id': player_id} + + return '|'.join([str(x) for x in (0, room_code, room_id, token, base64.b64encode(key).decode('utf-8'), player_id)]) + + elif data[0] == '2': + # 入房 + # data = ['2', name, song_unlock, room_code] + # song_unlock: base64 str + room_code = data[3].upper() + + if room_code not in room_code_dict: + # 房间号错误 + return '1202' + + room = room_code_dict[room_code] + if room.player_num == 4: + # 满人 + return '1201' + elif room.state != 2: + # 无法加入 + return '1205' + + name = data[1] + song_unlock = base64.b64decode(data[2]) + + key = urandom(16) + token = unique_random(link_play_data) + player_id = unique_random(player_dict, 3) + + player = Player() + player.player_id = player_id + player.set_player_name(name) + player.token = token + player_dict[player_id] = player + player.song_unlock = song_unlock + room.update_song_unlock() + for i in range(4): + if room.players[i].player_id == 0: + room.players[i] = player + player_index = i + break + link_play_data[token] = {'key': key, + 'room': room, + 'player_index': player_index, + 'player_id': player_id} + + return '|'.join([str(x) for x in (0, room_code, room.room_id, token, base64.b64encode(key).decode('utf-8'), player_id, base64.b64encode(room.song_unlock).decode('utf-8'))]) + + elif data[0] == '3': + # 房间信息更新 + # data = ['3', token] + token = int(data[1]) + if token in link_play_data: + r = link_play_data[token] + return '|'.join([str(x) for x in (0, r['room'].room_code, r['room'].room_id, base64.b64encode(r['key']).decode('utf-8'), r['room'].players[r['player_index']].player_id, base64.b64encode(r['room'].song_unlock).decode('utf-8'))]) + else: + return '108' + + +def link_play(ip: str = Config.HOST, udp_port: int = Config.UDP_PORT, tcp_port: int = Config.TCP_PORT): + udp_server = socketserver.ThreadingUDPServer((ip, udp_port), UDP_handler) + tcp_server = socketserver.ThreadingTCPServer((ip, tcp_port), TCP_handler) + + threads = [threading.Thread(target=udp_server.serve_forever), threading.Thread( + target=tcp_server.serve_forever)] + [t.start() for t in threads] + [t.join() for t in threads] diff --git a/latest version/udpserver/udp_class.py b/latest version/linkplay_server/udp_class.py similarity index 98% rename from latest version/udpserver/udp_class.py rename to latest version/linkplay_server/udp_class.py index 7d229fb..309a091 100644 --- a/latest version/udpserver/udp_class.py +++ b/latest version/linkplay_server/udp_class.py @@ -1,4 +1,4 @@ -from .udp_config import Config +from .config import Config def b(value, length=1): @@ -38,7 +38,7 @@ class Player: self.last_timestamp = 0 self.extra_command_queue = [] - self.song_unlock = b'\x00' * Config.LINK_PLAY_UNLOCK_LENGTH + self.song_unlock: bytes = b'\x00' * Config.LINK_PLAY_UNLOCK_LENGTH self.start_command_num = 0 diff --git a/latest version/udpserver/udp_parser.py b/latest version/linkplay_server/udp_parser.py similarity index 99% rename from latest version/udpserver/udp_parser.py rename to latest version/linkplay_server/udp_parser.py index 12d4947..f498fcb 100644 --- a/latest version/udpserver/udp_parser.py +++ b/latest version/linkplay_server/udp_parser.py @@ -1,7 +1,7 @@ import time from .udp_class import Room, bi -from .udp_config import Config +from .config import Config from .udp_sender import CommandSender @@ -311,7 +311,7 @@ class CommandParser: self.room.command_queue_length += 1 self.room.command_queue.append(x.command_12(self.player_index)) - if self.room.state == 3: + if self.room.state == 3 or self.room.state == 2: self.room.state = 1 self.room.song_idx = 0xffff # self.room.command_queue_length += 1 diff --git a/latest version/udpserver/udp_sender.py b/latest version/linkplay_server/udp_sender.py similarity index 100% rename from latest version/udpserver/udp_sender.py rename to latest version/linkplay_server/udp_sender.py diff --git a/latest version/main.py b/latest version/main.py index cdce326..90d2163 100644 --- a/latest version/main.py +++ b/latest version/main.py @@ -142,13 +142,15 @@ def main(): except: app.logger.warning('Initialization error!') - if Config.UDP_PORT and Config.UDP_PORT != '': - from server.multiplayer import conn2 - from udpserver.udp_main import link_play + if Config.LINK_PLAY_HOST and Config.SET_LINK_PLAY_SERVER_AS_SUB_PROCESS: + from linkplay_server import link_play process = [Process(target=link_play, args=( - conn2, Config.HOST, int(Config.UDP_PORT)))] + Config.LINK_PLAY_HOST, int(Config.LINK_PLAY_UDP_PORT), int(Config.LINK_PLAY_TCP_PORT)))] [p.start() for p in process] - app.logger.info("UDP server is running...") + app.logger.info("Link Play UDP server is running on " + + Config.LINK_PLAY_HOST + ':' + str(Config.LINK_PLAY_UDP_PORT) + " ...") + app.logger.info("Link Play TCP server is running on " + + Config.LINK_PLAY_HOST + ':' + str(Config.LINK_PLAY_TCP_PORT) + " ...") tcp_server_run() [p.join() for p in process] else: diff --git a/latest version/run_linkplay_server.py b/latest version/run_linkplay_server.py new file mode 100644 index 0000000..d95942b --- /dev/null +++ b/latest version/run_linkplay_server.py @@ -0,0 +1,9 @@ +import os +import sys + +import linkplay_server + +os.chdir(sys.path[0]) + +if __name__ == '__main__': + linkplay_server.link_play() diff --git a/latest version/server/multiplayer.py b/latest version/server/multiplayer.py index f531e27..723f0df 100644 --- a/latest version/server/multiplayer.py +++ b/latest version/server/multiplayer.py @@ -1,7 +1,5 @@ -from multiprocessing import Pipe - from core.error import ArcError -from core.linkplay import LocalMultiPlayer, Player, Room +from core.linkplay import Player, RemoteMultiPlayer, Room from core.sql import Connect from flask import Blueprint, request from setting import Config @@ -11,24 +9,23 @@ from .func import arc_try, success_return bp = Blueprint('multiplayer', __name__, url_prefix='/multiplayer') -conn1, conn2 = Pipe() - @bp.route('/me/room/create', methods=['POST']) # 创建房间 @auth_required(request) @arc_try def room_create(user_id): - if not Config.UDP_PORT or Config.UDP_PORT == '': - raise ArcError('The local udp server is down.', 151, status=404) + if not Config.LINK_PLAY_HOST: + raise ArcError('The link play server is unavailable.', 151, status=404) + with Connect() as c: - x = LocalMultiPlayer(conn1) + x = RemoteMultiPlayer() user = Player(c, user_id) user.get_song_unlock(request.json['clientSongMap']) x.create_room(user) r = x.to_dict() r['endPoint'] = request.host.split( - ':')[0] if Config.LINK_PLAY_HOST == '' else Config.LINK_PLAY_HOST - r['port'] = int(Config.UDP_PORT) + ':')[0] if Config.LINK_PLAY_DISPLAY_HOST == '' else Config.LINK_PLAY_DISPLAY_HOST + r['port'] = int(Config.LINK_PLAY_UDP_PORT) return success_return(r) @@ -36,11 +33,11 @@ def room_create(user_id): @auth_required(request) @arc_try def room_join(user_id, room_code): - if not Config.UDP_PORT or Config.UDP_PORT == '': - raise ArcError('The local udp server is down.', 151, status=404) + if not Config.LINK_PLAY_HOST: + raise ArcError('The link play server is unavailable.', 151, status=404) with Connect() as c: - x = LocalMultiPlayer(conn1) + x = RemoteMultiPlayer() user = Player(c, user_id) user.get_song_unlock(request.json['clientSongMap']) room = Room() @@ -48,8 +45,8 @@ def room_join(user_id, room_code): x.join_room(room, user) r = x.to_dict() r['endPoint'] = request.host.split( - ':')[0] if Config.LINK_PLAY_HOST == '' else Config.LINK_PLAY_HOST - r['port'] = int(Config.UDP_PORT) + ':')[0] if Config.LINK_PLAY_DISPLAY_HOST == '' else Config.LINK_PLAY_DISPLAY_HOST + r['port'] = int(Config.LINK_PLAY_UDP_PORT) return success_return(r) @@ -57,16 +54,16 @@ def room_join(user_id, room_code): @auth_required(request) @arc_try def multiplayer_update(user_id): - if not Config.UDP_PORT or Config.UDP_PORT == '': - raise ArcError('The local udp server is down.', 151, status=404) + if not Config.LINK_PLAY_HOST: + raise ArcError('The link play server is unavailable.', 151, status=404) with Connect() as c: - x = LocalMultiPlayer(conn1) + x = RemoteMultiPlayer() user = Player(c, user_id) user.token = int(request.json['token']) x.update_room(user) r = x.to_dict() r['endPoint'] = request.host.split( - ':')[0] if Config.LINK_PLAY_HOST == '' else Config.LINK_PLAY_HOST - r['port'] = int(Config.UDP_PORT) + ':')[0] if Config.LINK_PLAY_DISPLAY_HOST == '' else Config.LINK_PLAY_DISPLAY_HOST + r['port'] = int(Config.LINK_PLAY_UDP_PORT) return success_return(r) diff --git a/latest version/setting.py b/latest version/setting.py index cf0f443..4db57b6 100644 --- a/latest version/setting.py +++ b/latest version/setting.py @@ -30,29 +30,27 @@ class Config(): Allowed game versions If it is blank, all are allowed. ''' - ALLOW_APPVERSION = ['3.12.6', '3.12.6c', '4.0.256', '4.0.256c', '4.0.255', '4.0.255c'] + ALLOW_APPVERSION = ['3.12.6', '3.12.6c', + '4.0.256', '4.0.256c', '4.0.255', '4.0.255c'] ''' -------------------- ''' ''' -------------------- - 联机功能的端口号,若为空,则默认不开启联机功能 - Port of your link play server - If it is blank, link play will be unavailable. + 联机功能相关设置,请确保与Link Play服务器端的设置一致 + Setting of your link play server + Please ensure that the settings on the side of Link Play server are consistent. ''' - UDP_PORT = '10900' - ''' - -------------------- - ''' - - ''' - -------------------- - 联机功能地址,留空则自动获取 - Link Play address - If left blank, it will be obtained automatically. - ''' - LINK_PLAY_HOST = '' # ***.com + # SET_LINK_PLAY_SERVER_AS_SUB_PROCESS: If it is `True`, the link play server will run with the main server locally at the same time. + SET_LINK_PLAY_SERVER_AS_SUB_PROCESS = True + # LINK_PLAY_HOST: If it is blank, the link play feature will be disabled. + LINK_PLAY_HOST = '0.0.0.0' + LINK_PLAY_UDP_PORT = 10900 + LINK_PLAY_TCP_PORT = 10901 + LINK_PLAY_AUTHENTICATION = 'my_link_play_server' + # LINK_PLAY_DISPLAY_HOST: If it is blank, the host of link play server for the client will be obtained automatically. + LINK_PLAY_DISPLAY_HOST = '' ''' -------------------- ''' diff --git a/latest version/udpserver/__init__.py b/latest version/udpserver/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/latest version/udpserver/udp_config.py b/latest version/udpserver/udp_config.py deleted file mode 100644 index d5c88d5..0000000 --- a/latest version/udpserver/udp_config.py +++ /dev/null @@ -1,11 +0,0 @@ -class Config: - TIME_LIMIT = 3600000 - - COMMAND_INTERVAL = 1000000 - - COUNTDOWM_TIME = 3999 - - PLAYER_PRE_TIMEOUT = 3000000 - PLAYER_TIMEOUT = 20000000 - - LINK_PLAY_UNLOCK_LENGTH = 512 \ No newline at end of file diff --git a/latest version/udpserver/udp_main.py b/latest version/udpserver/udp_main.py deleted file mode 100644 index cd0cc80..0000000 --- a/latest version/udpserver/udp_main.py +++ /dev/null @@ -1,223 +0,0 @@ -import socketserver -import threading -import os -import random -import time -# import binascii -from . import aes -from .udp_parser import CommandParser -from .udp_class import Room, Player, bi -from .udp_config import Config - - -# token: {'key': key, 'room': Room, 'player_index': player_index, 'player_id': player_id} -link_play_data = {} -room_id_dict = {} # 'room_id': Room -room_code_dict = {} # 'room_code': Room -player_dict = {} # 'player_id' : Player -clean_timer = 0 - - -def random_room_code(): - # 随机生成房间号 - re = '' - for _ in range(4): - re += chr(random.randint(65, 90)) - for _ in range(2): - re += str(random.randint(0, 9)) - - return re - - -def clear_player(token): - # 清除玩家信息和token - del player_dict[link_play_data[token]['player_id']] - del link_play_data[token] - - -def clear_room(room): - # 清除房间信息 - room_id = room.room_id - room_code = room.room_code - del room_id_dict[room_id] - del room_code_dict[room_code] - del room - - -def memory_clean(now): - # 内存清理 - clean_room_list = [] - clean_player_list = [] - for token in link_play_data: - room = link_play_data[token]['room'] - if now - room.timestamp >= Config.TIME_LIMIT: - clean_room_list.append(room.room_id) - - if now - room.players[link_play_data[token]['player_index']].last_timestamp // 1000 >= Config.TIME_LIMIT: - clean_player_list.append(token) - - for room_id in room_id_dict: - if now - room_id_dict[room_id].timestamp >= Config.TIME_LIMIT: - clean_room_list.append(room_id) - - for room_id in clean_room_list: - if room_id in room_id_dict: - clear_room(room_id_dict[room_id]) - - for token in clean_player_list: - clear_player(token) - - -class UDPhandler(socketserver.BaseRequestHandler): - def handle(self): - client_msg, server = self.request - token = client_msg[:8] - iv = client_msg[8:20] - tag = client_msg[20:32] - ciphertext = client_msg[32:] - if int.from_bytes(token, byteorder='little') in link_play_data: - user = link_play_data[bi(token)] - else: - return None - - plaintext = aes.decrypt(user['key'], b'', iv, ciphertext, tag) - # print(binascii.b2a_hex(plaintext)) - - commands = CommandParser( - user['room'], user['player_index']).get_commands(plaintext) - - if user['room'].players[user['player_index']].player_id == 0: - clear_player(bi(token)) - temp = [] - for i in commands: - if i[:3] == b'\x06\x16\x12': - temp.append(i) - commands = temp - # 处理不能正确被踢的问题 - - for i in commands: - iv, ciphertext, tag = aes.encrypt(user['key'], i, b'') - # print(binascii.b2a_hex(i)) - - server.sendto(token + iv + tag[:12] + - ciphertext, self.client_address) - - -def server_run(ip, port): - server = socketserver.ThreadingUDPServer((ip, port), UDPhandler) - server.serve_forever() - - -def data_swap(conn): - clean_timer = 0 - while True: - try: - data = conn.recv() - except EOFError: - break - - now = round(time.time() * 1000) - if now - clean_timer >= Config.TIME_LIMIT: - clean_timer = now - memory_clean(now) - - if data[0] == 1: - # 开房 - key = os.urandom(16) - room_id = bi(os.urandom(8)) - while room_id in room_id_dict and room_id == 0: - room_id = bi(os.urandom(8)) - room = Room() - room.room_id = room_id - room_id_dict[room_id] = room - - player_id = bi(os.urandom(3)) - while player_id in player_dict and player_id == 0: - player_id = bi(os.urandom(3)) - player = Player() - player.player_id = player_id - player.set_player_name(data[1]) - player_dict[player_id] = player - - player.song_unlock = data[2] - room.song_unlock = data[2] - room.host_id = player_id - room.players[0] = player - room.player_num = 1 - - room_code = random_room_code() - while room_code in room_code_dict: - room_code = random_room_code() - room.room_code = room_code - room_code_dict[room_code] = room - - token = room_id - player.token = token - - link_play_data[token] = {'key': key, - 'room': room, - 'player_index': 0, - 'player_id': player_id} - - conn.send((0, room_code, room_id, token, key, player_id)) - - elif data[0] == 2: - room_code = data[3].upper() - if room_code not in room_code_dict: - # 房间号错误 - conn.send((1202, )) - else: - room = room_code_dict[room_code] - if room.player_num == 4: - # 满人 - conn.send((1201, )) - elif room.state != 2: - # 无法加入 - conn.send((1205, )) - else: - key = os.urandom(16) - token = bi(os.urandom(8)) - - player_id = bi(os.urandom(3)) - while player_id in player_dict and player_id == 0: - player_id = bi(os.urandom(3)) - player = Player() - player.player_id = player_id - player.set_player_name(data[1]) - player.token = token - player_dict[player_id] = player - - player.song_unlock = data[2] - - room.update_song_unlock() - for i in range(4): - if room.players[i].player_id == 0: - room.players[i] = player - player_index = i - break - - link_play_data[token] = {'key': key, - 'room': room, - 'player_index': player_index, - 'player_id': player_id} - - conn.send((0, room_code, room.room_id, - token, key, player_id, room.song_unlock)) - elif data[0] == 3: - token = data[1] - if token in link_play_data: - r = link_play_data[token] - conn.send((0, r['room'].room_code, r['room'].room_id, r['key'], - r['room'].players[r['player_index']].player_id, r['room'].song_unlock)) - else: - conn.send((108, )) - - -def link_play(conn, ip: str, port: int): - try: - server = threading.Thread(target=server_run, args=(ip, port)) - data_exchange = threading.Thread(target=data_swap, args=(conn,)) - server.start() - data_exchange.start() - except: - pass