diff --git a/latest version/api/__init__.py b/latest version/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/latest version/database/__init__.py b/latest version/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/latest version/main.py b/latest version/main.py index 4690751..d44f28b 100644 --- a/latest version/main.py +++ b/latest version/main.py @@ -16,8 +16,11 @@ import server.arcdownload import server.arcpurchase import server.init import server.character +import server.arclinkplay +from udpserver.udp_main import link_play import os import sys +from multiprocessing import Process, Pipe app = Flask(__name__) @@ -31,62 +34,7 @@ app.register_blueprint(web.login.bp) app.register_blueprint(web.index.bp) app.register_blueprint(api.api_main.bp) -log_dict = { - 'version': 1, - 'root': { - 'level': 'INFO', - 'handlers': ['wsgi', 'error_file'] - }, - 'handlers': { - 'wsgi': { - 'class': 'logging.StreamHandler', - 'stream': 'ext://flask.logging.wsgi_errors_stream', - 'formatter': 'default' - }, - "error_file": { - "class": "logging.handlers.RotatingFileHandler", - "maxBytes": 1024 * 1024, - "backupCount": 1, - "encoding": "utf-8", - "level": "ERROR", - "formatter": "default", - "filename": "./log/error.log" - } - }, - 'formatters': { - 'default': { - 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s' - } - } -} -if Config.ALLOW_LOG_INFO: - log_dict['root']['handlers'] = ['wsgi', 'info_file', 'error_file'] - log_dict['handlers']['info_file'] = { - "class": "logging.handlers.RotatingFileHandler", - "maxBytes": 1024 * 1024, - "backupCount": 1, - "encoding": "utf-8", - "level": "INFO", - "formatter": "default", - "filename": "./log/info.log" - } - -dictConfig(log_dict) - -if not server.init.check_before_run(app): - app.logger.error('Something wrong. The server will not run.') - input('Press ENTER key to exit.') - sys.exit() - -app.logger.info("Start to initialize data in 'songfile' table...") -try: - error = server.arcdownload.initialize_songfile() -except: - error = 'Something wrong.' -if error: - app.logger.warning(error) -else: - app.logger.info('Complete!') +conn1, conn2 = Pipe() def add_url_prefix(url, strange_flag=False): @@ -702,40 +650,70 @@ def download(file_path): @app.route(add_url_prefix('/multiplayer/me/room/create'), methods=['POST']) @server.auth.auth_required(request) def room_create(user_id): - return error_return(151) - # return jsonify({ - # "success": True, - # "value": { - # "roomCode": "Fuck616", - # "roomId": "16465282253677196096", - # "token": "16465282253677196096", - # "key": "czZNUmivWm6c3SpMaPIXcA==", - # "playerId": "12753", - # "userId": user_id, - # "endPoint": "192.168.1.200", - # "port": 10900, - # "orderedAllowedSongs": "9w93DwcH93AA8HcPAAAHAHcAAHBwAABwcAAAAHB3AAAAcAcAAHAAAHAAAAB3BwD3AAAABwAAAAAAAAAAAAAAAAAAAAAAAAAHAHAHBwcAAAAAcHd3cAAAAAAHBwcAAAAAAAAAAAAHdwAHAAAAcAdwBwAAAAAAdwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" - # } - # }) + if not Config.UDP_PORT or Config.UDP_PORT == '': + return error_return(151), 404 + + client_song_map = request.json['clientSongMap'] + error_code, value = server.arclinkplay.create_room( + conn1, user_id, client_song_map) + + if error_code == 0: + value['endPoint'] = request.host.split(':')[0] + value['port'] = int(Config.UDP_PORT) + return jsonify({ + "success": True, + "value": value + }) + else: + return error_return(error_code), 400 # 加入房间 @app.route(add_url_prefix('/multiplayer/me/room/join/'), methods=['POST']) @server.auth.auth_required(request) def room_join(user_id, room_code): - return error_return(151) + if not Config.UDP_PORT or Config.UDP_PORT == '': + return error_return(151), 404 + + client_song_map = request.json['clientSongMap'] + error_code, value = server.arclinkplay.join_room( + conn1, user_id, client_song_map, room_code) + + if error_code == 0: + value['endPoint'] = request.host.split(':')[0] + value['port'] = int(Config.UDP_PORT) + return jsonify({ + "success": True, + "value": value + }) + else: + return error_return(error_code), 400 -@app.route(add_url_prefix('/multiplayer/me/update'), methods=['POST']) # ? +@app.route(add_url_prefix('/multiplayer/me/update'), methods=['POST']) # 更新房间 @server.auth.auth_required(request) def multiplayer_update(user_id): - return error_return(151) + if not Config.UDP_PORT or Config.UDP_PORT == '': + return error_return(151), 404 + + token = request.json['token'] + error_code, value = server.arclinkplay.update_room(conn1, user_id, token) + + if error_code == 0: + value['endPoint'] = request.host.split(':')[0] + value['port'] = int(Config.UDP_PORT) + return jsonify({ + "success": True, + "value": value + }) + else: + return error_return(error_code), 400 @app.route(add_url_prefix('/user/me/request_delete'), methods=['POST']) # 删除账号 @server.auth.auth_required(request) def user_delete(user_id): - return error_return(151) + return error_return(151), 404 # 三个设置,写在最后降低优先级 @@ -751,11 +729,80 @@ def sys_set(user_id, path): def main(): - if Config.SSL_CERT and Config.SSL_KEY: - app.run(Config.HOST, Config.PORT, ssl_context=( - Config.SSL_CERT, Config.SSL_KEY)) + log_dict = { + 'version': 1, + 'root': { + 'level': 'INFO', + 'handlers': ['wsgi', 'error_file'] + }, + 'handlers': { + 'wsgi': { + 'class': 'logging.StreamHandler', + 'stream': 'ext://flask.logging.wsgi_errors_stream', + 'formatter': 'default' + }, + "error_file": { + "class": "logging.handlers.RotatingFileHandler", + "maxBytes": 1024 * 1024, + "backupCount": 1, + "encoding": "utf-8", + "level": "ERROR", + "formatter": "default", + "filename": "./log/error.log" + } + }, + 'formatters': { + 'default': { + 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s' + } + } + } + if Config.ALLOW_LOG_INFO: + log_dict['root']['handlers'] = ['wsgi', 'info_file', 'error_file'] + log_dict['handlers']['info_file'] = { + "class": "logging.handlers.RotatingFileHandler", + "maxBytes": 1024 * 1024, + "backupCount": 1, + "encoding": "utf-8", + "level": "INFO", + "formatter": "default", + "filename": "./log/info.log" + } + + dictConfig(log_dict) + + if not server.init.check_before_run(app): + app.logger.error('Something wrong. The server will not run.') + input('Press ENTER key to exit.') + sys.exit() + + app.logger.info("Start to initialize data in 'songfile' table...") + try: + error = server.arcdownload.initialize_songfile() + except: + error = 'Something wrong.' + if error: + app.logger.warning(error) else: - app.run(Config.HOST, Config.PORT) + app.logger.info('Complete!') + + if Config.UDP_PORT and Config.UDP_PORT != '': + process = [Process(target=link_play, args=( + conn2, Config.HOST, int(Config.UDP_PORT)))] + [p.start() for p in process] + app.logger.info("UDP server is running...") + if Config.SSL_CERT and Config.SSL_KEY: + app.run(Config.HOST, Config.PORT, ssl_context=( + Config.SSL_CERT, Config.SSL_KEY)) + else: + app.run(Config.HOST, Config.PORT) + [p.join() for p in process] + else: + if Config.SSL_CERT and Config.SSL_KEY: + app.run(Config.HOST, Config.PORT, ssl_context=( + Config.SSL_CERT, Config.SSL_KEY)) + else: + app.run(Config.HOST, Config.PORT) if __name__ == '__main__': diff --git a/latest version/server/__init__.py b/latest version/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/latest version/server/arclinkplay.py b/latest version/server/arclinkplay.py new file mode 100644 index 0000000..4fe5bf7 --- /dev/null +++ b/latest version/server/arclinkplay.py @@ -0,0 +1,113 @@ +from .sql import Connect +import base64 + + +def get_song_unlock(client_song_map): + # 处理可用歌曲bit,返回bytes + + user_song_unlock = [0] * 512 + for i in range(0, 1024, 2): + x = 0 + y = 0 + if str(i) in client_song_map: + if client_song_map[str(i)][0]: + x += 1 + if client_song_map[str(i)][1]: + x += 2 + if client_song_map[str(i)][2]: + x += 4 + if client_song_map[str(i)][3]: + x += 8 + if str(i+1) in client_song_map: + if client_song_map[str(i+1)][0]: + y += 1 + if client_song_map[str(i+1)][1]: + y += 2 + if client_song_map[str(i+1)][2]: + y += 4 + if client_song_map[str(i+1)][3]: + y += 8 + + user_song_unlock[i // 2] = y*16 + x + + return bytes(user_song_unlock) + + +def create_room(conn, user_id, client_song_map): + # 创建房间,返回错误码和房间与用户信息 + error_code = 108 + + with Connect() as c: + c.execute('''select name from user where user_id=?''', (user_id,)) + x = c.fetchone() + if x is not None: + name = x[0] + + song_unlock = get_song_unlock(client_song_map) + + conn.send((1, name, song_unlock)) + data = conn.recv() + if data[0] == 0: + error_code = 0 + return error_code, {'roomCode': data[1], + 'roomId': str(data[2]), + 'token': str(data[3]), + 'key': (base64.b64encode(data[4])).decode(), + 'playerId': str(data[5]), + 'userId': user_id, + 'orderedAllowedSongs': (base64.b64encode(song_unlock)).decode() + } + + return error_code, None + + +def join_room(conn, user_id, client_song_map, room_code): + # 加入房间,返回错误码和房间与用户信息 + error_code = 108 + + with Connect() as c: + c.execute('''select name from user where user_id=?''', (user_id,)) + x = c.fetchone() + if x is not None: + name = x[0] + + song_unlock = get_song_unlock(client_song_map) + + conn.send((2, name, song_unlock, room_code)) + data = conn.recv() + if data[0] == 0: + error_code = 0 + return error_code, {'roomCode': data[1], + 'roomId': str(data[2]), + 'token': str(data[3]), + 'key': (base64.b64encode(data[4])).decode(), + 'playerId': str(data[5]), + 'userId': user_id, + 'orderedAllowedSongs': (base64.b64encode(data[6])).decode() + } + else: + error_code = data[0] + + return error_code, None + + +def update_room(conn, user_id, token): + # 更新房间,返回错误码和房间与用户信息 + error_code = 108 + + conn.send((3, int(token))) + data = conn.recv() + if data[0] == 0: + error_code = 0 + return error_code, {'roomCode': data[1], + 'roomId': str(data[2]), + 'token': token, + 'key': (base64.b64encode(data[3])).decode(), + 'playerId': str(data[4]), + 'userId': user_id, + 'orderedAllowedSongs': (base64.b64encode(data[5])).decode() + } + else: + error_code = data[0] + + return error_code, None diff --git a/latest version/setting.py b/latest version/setting.py index 0184c7e..6ff0175 100644 --- a/latest version/setting.py +++ b/latest version/setting.py @@ -35,6 +35,17 @@ class Config(): -------------------- ''' + ''' + -------------------- + 联机功能的端口号,若为空,则默认不开启联机功能 + Port of your link play server + If it is blank, link play will be unavailable. + ''' + UDP_PORT = '10900' + ''' + -------------------- + ''' + ''' -------------------- SSL证书路径 diff --git a/latest version/udpserver/__init__.py b/latest version/udpserver/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/latest version/udpserver/aes.py b/latest version/udpserver/aes.py new file mode 100644 index 0000000..21de449 --- /dev/null +++ b/latest version/udpserver/aes.py @@ -0,0 +1,24 @@ +import os +from cryptography.hazmat.primitives.ciphers import ( + Cipher, algorithms, modes +) + + +def encrypt(key, plaintext, associated_data): + iv = os.urandom(12) + encryptor = Cipher( + algorithms.AES(key), + modes.GCM(iv, min_tag_length=12), + ).encryptor() + encryptor.authenticate_additional_data(associated_data) + ciphertext = encryptor.update(plaintext) + encryptor.finalize() + return (iv, ciphertext, encryptor.tag) + + +def decrypt(key, associated_data, iv, ciphertext, tag): + decryptor = Cipher( + algorithms.AES(key), + modes.GCM(iv, tag, min_tag_length=12), + ).decryptor() + decryptor.authenticate_additional_data(associated_data) + return decryptor.update(ciphertext) + decryptor.finalize() diff --git a/latest version/udpserver/udp_class.py b/latest version/udpserver/udp_class.py new file mode 100644 index 0000000..c79ea1d --- /dev/null +++ b/latest version/udpserver/udp_class.py @@ -0,0 +1,174 @@ +def b(value, length=1): + return value.to_bytes(length=length, byteorder='little') + + +def bi(value): + return int.from_bytes(value, byteorder='little') + + +class Player: + def __init__(self) -> None: + self.player_id = 0 + self.player_name = b'\x45\x6d\x70\x74\x79\x50\x6c\x61\x79\x65\x72\x00\x00\x00\x00\x00' + self.token = 0 + + self.character_id = 0xff + self.last_character_id = 0xff + self.is_uncapped = 0 + + self.difficulty = 0xff + self.last_difficulty = 0xff + self.score = 0 + self.last_score = 0 + self.timer = 0 + self.last_timer = 0 + self.cleartype = 0 + self.last_cleartype = 0 + self.best_score_flag = 0 + self.best_player_flag = 0 + self.finish_flag = 0 + + self.player_state = 1 + self.download_percent = 0 + self.online = 0 + + self.last_timestamp = 0 + self.extra_command_queue = [] + + self.song_unlock = b'\x00' * 512 + + self.start_command_num = 0 + + def set_player_name(self, player_name: str): + self.player_name = player_name.encode('ascii') + if len(self.player_name) > 16: + self.player_name = self.player_name[:16] + else: + self.player_name += b'\x00' * (16 - len(self.player_name)) + + +class Room: + def __init__(self) -> None: + self.room_id = 0 + self.room_code = 'AAAA00' + + self.countdown = 0xffffffff + self.timestamp = 0 + self.state = 0 + self.song_idx = 0xffff + self.last_song_idx = 0xffff + + self.song_unlock = b'\x00' * 512 + + self.host_id = 0 + self.players = [Player(), Player(), Player(), Player()] + self.player_num = 0 + + self.interval = 1000 + self.times = 100 + + self.round_switch = 0 + + self.command_queue = [] + self.command_queue_length = 0 + + def get_players_info(self): + # 获取所有玩家信息 + re = b'' + for i in self.players: + re += b(i.player_id, 8) + b(i.character_id) + b(i.is_uncapped) + b(i.difficulty) + b(i.score, 4) + \ + b(i.timer, 4) + b(i.cleartype) + b(i.player_state) + \ + b(i.download_percent) + b(i.online) + b'\x00' + i.player_name + return re + + def get_player_last_score(self): + # 获取上次曲目玩家分数,返回bytes + if self.last_song_idx == 0xffff: + return b'\xff\xff\x00\x00\x00\x00\x00\x00\x00' * 4 + re = b'' + + for i in range(4): + player = self.players[i] + + if player.player_id != 0: + re += b(player.last_character_id) + b(player.last_difficulty) + b(player.last_score, 4) + b( + player.last_cleartype) + b(player.best_score_flag) + b(player.best_player_flag) + else: + re += b'\xff\xff\x00\x00\x00\x00\x00\x00\x00' + + return re + + def make_round(self): + # 轮换房主 + for i in range(4): + if self.players[i].player_id == self.host_id: + for j in range(1, 4): + if self.players[(i + j) % 4].player_id != 0: + self.host_id = self.players[(i + j) % 4].player_id + break + break + + def delete_player(self, player_index: int): + # 删除某个玩家 + self.player_num -= 1 + if self.players[player_index].player_id == self.host_id: + self.make_round() + + self.players[player_index].online = 0 + self.players[player_index] = Player() + self.update_song_unlock() + + def update_song_unlock(self): + # 更新房间可用歌曲 + r = bi(b'\xff' * 512) + for i in self.players: + if i.player_id != 0: + r &= bi(i.song_unlock) + + self.song_unlock = b(r, 512) + + def is_ready(self, old_state: int, player_state: int): + # 是否全部准备就绪 + if self.state == old_state: + for i in self.players: + if i.player_id != 0 and (i.player_state != player_state or i.online == 0): + return False + + return True + else: + return False + + def is_finish(self): + # 是否全部进入结算 + for i in self.players: + if i.player_id != 0 and (i.finish_flag == 0 or i.online == 0): + return False + + return True + + def make_finish(self): + # 结算 + self.state = 8 + self.last_song_idx = self.song_idx + + max_score = 0 + max_score_i = [] + for i in range(4): + player = self.players[i] + if player.player_id != 0: + player.finish_flag = 0 + player.last_timer = player.timer + player.last_score = player.score + player.last_cleartype = player.cleartype + player.last_character_id = player.character_id + player.last_difficulty = player.difficulty + player.best_player_flag = 0 + + if player.last_score > max_score: + max_score = player.last_score + max_score_i = [i] + elif player.last_score == max_score: + max_score_i.append(i) + + for i in max_score_i: + self.players[i].best_player_flag = 1 diff --git a/latest version/udpserver/udp_config.py b/latest version/udpserver/udp_config.py new file mode 100644 index 0000000..0a0a9d6 --- /dev/null +++ b/latest version/udpserver/udp_config.py @@ -0,0 +1,8 @@ +class Config: + TIME_LIMIT = 3600000 + + COMMAND_INTERVAL = 1000000 + + COUNTDOWM_TIME = 3999 + + PLAYER_TIMEOUT = 30000000 diff --git a/latest version/udpserver/udp_main.py b/latest version/udpserver/udp_main.py new file mode 100644 index 0000000..cd0cc80 --- /dev/null +++ b/latest version/udpserver/udp_main.py @@ -0,0 +1,223 @@ +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 diff --git a/latest version/udpserver/udp_parser.py b/latest version/udpserver/udp_parser.py new file mode 100644 index 0000000..7c74afc --- /dev/null +++ b/latest version/udpserver/udp_parser.py @@ -0,0 +1,333 @@ +from operator import irshift +from .udp_sender import CommandSender +from .udp_class import bi, Room +from .udp_config import Config +import time + + +class CommandParser: + def __init__(self, room: Room, player_index: int = 0) -> None: + self.room = room + self.player_index = player_index + + def get_commands(self, command): + self.command = command + l = {b'\x06\x16\x01': self.command_01, + b'\x06\x16\x02': self.command_02, + b'\x06\x16\x03': self.command_03, + b'\x06\x16\x04': self.command_04, + b'\x06\x16\x05': self.command_05, + b'\x06\x16\x06': self.command_06, + b'\x06\x16\x07': self.command_07, + b'\x06\x16\x08': self.command_08, + b'\x06\x16\x09': self.command_09, + b'\x06\x16\x0a': self.command_0a, + b'\x06\x16\x0b': self.command_0b + } + r = l[command[:3]]() + + re = [] + + flag_13 = False + for i in range(max(bi(self.command[12:16]), self.room.players[self.player_index].start_command_num), self.room.command_queue_length): + if self.room.command_queue[i][:3] == b'\x06\x16\x13': + if flag_13: + break + flag_13 = True + re.append(self.room.command_queue[i]) + + if self.room.players[self.player_index].extra_command_queue: + re += self.room.players[self.player_index].extra_command_queue + self.room.players[self.player_index].extra_command_queue = [] + + if r: + re += r + + return re + + def command_01(self): + # 给房主 + player_id = bi(self.command[24:32]) + for i in self.room.players: + if i.player_id == player_id and i.online == 1: + self.room.host_id = player_id + + x = CommandSender(self.room) + x.random_code = self.command[16:24] + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_10()) + + return None + + def command_02(self): + x = CommandSender(self.room) + x.random_code = self.command[16:24] + song_idx = bi(self.command[24:26]) + + flag = 2 + if self.room.state == 2: + flag = 0 + self.room.state = 3 + self.room.song_idx = song_idx + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_11()) + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_13()) + + return [x.command_0d(flag)] + + def command_03(self): + # 尝试进入结算 + x = CommandSender(self.room) + x.random_code = self.command[16:24] + player = self.room.players[self.player_index] + player.score = bi(self.command[24:28]) + player.cleartype = self.command[28] + player.difficulty = self.command[29] + player.best_score_flag = self.command[30] + player.finish_flag = 1 + player.last_timestamp -= Config.COMMAND_INTERVAL + self.room.last_song_idx = self.room.song_idx + + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_12(self.player_index)) + + if self.room.is_finish(): + self.room.make_finish() + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_13()) + + return None + + def command_04(self): + # 踢人 + x = CommandSender(self.room) + x.random_code = self.command[16:24] + player_id = bi(self.command[24:32]) + flag = 2 + if self.room.players[self.player_index].player_id == self.room.host_id and player_id != self.room.host_id: + for i in range(4): + if self.room.players[i].player_id == player_id: + flag = 1 + self.room.delete_player(i) + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_12(i)) + self.room.update_song_unlock() + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_14()) + break + + return [x.command_0d(flag)] + + def command_05(self): + pass + + def command_06(self): + x = CommandSender(self.room) + x.random_code = self.command[16:24] + self.room.state = 1 + self.room.song_idx = 0xffff + + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_13()) + return None + + def command_07(self): + x = CommandSender(self.room) + x.random_code = self.command[16:24] + self.room.players[self.player_index].song_unlock = self.command[24:536] + self.room.update_song_unlock() + + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_14()) + return None + + def command_08(self): + self.room.round_switch = bi(self.command[24:25]) + x = CommandSender(self.room) + x.random_code = self.command[16:24] + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_13()) + + return None + + def command_09(self): + re = [] + x = CommandSender(self.room) + x.random_code = self.command[16:24] + player = self.room.players[self.player_index] + + if bi(self.command[12:16]) == 0: + player.online = 1 + self.room.state = 1 + self.room.update_song_unlock() + player.start_command_num = self.room.command_queue_length + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_15()) + else: + if x.timestamp - player.last_timestamp >= Config.COMMAND_INTERVAL: + re.append(x.command_0c()) + player.last_timestamp = x.timestamp + + # 离线判断 + for i in range(4): + if i != self.player_index: + t = self.room.players[i] + if t.player_id != 0: + if t.last_timestamp != 0: + if t.online == 1 and x.timestamp - t.last_timestamp >= 5000000: + t.online = 0 + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_12(i)) + elif t.online == 0 and x.timestamp - t.last_timestamp >= Config.PLAYER_TIMEOUT: + self.room.delete_player(i) + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_12(i)) + + flag_11 = False + flag_12 = False + flag_13 = False + + if player.online == 0: + flag_12 = True + player.online = 1 + + if self.room.is_ready(1, 1): + flag_13 = True + self.room.state = 2 + + if player.player_state != self.command[32]: + flag_12 = True + player.player_state = self.command[32] + + if player.difficulty != self.command[33] and player.player_state != 5 and player.player_state != 6 and player.player_state != 7 and player.player_state != 8: + flag_12 = True + player.difficulty = self.command[33] + + if player.cleartype != self.command[34] and player.player_state != 7 and player.player_state != 8: + flag_12 = True + player.cleartype = self.command[34] + + if player.download_percent != self.command[35]: + flag_12 = True + player.download_percent = self.command[35] + + if player.character_id != self.command[36]: + flag_12 = True + player.character_id = self.command[36] + + if player.is_uncapped != self.command[37]: + flag_12 = True + player.is_uncapped = self.command[37] + + if self.room.state == 3 and player.score != bi(self.command[24:28]): + flag_12 = True + player.score = bi(self.command[24:28]) + + if self.room.is_ready(3, 4): + flag_13 = True + self.room.countdown = Config.COUNTDOWM_TIME + self.room.timestamp = round(time.time() * 1000) + self.room.state = 4 + + if self.room.state == 4 or self.room.state == 5 or self.room.state == 6: + timestamp = round(time.time() * 1000) + self.room.countdown -= timestamp - self.room.timestamp + self.room.timestamp = timestamp + if self.room.state == 4 and self.room.countdown <= 0: + # 此处不清楚 + self.room.state = 5 + self.room.countdown = 5999 + flag_11 = True + flag_13 = True + + if self.room.state == 5 and self.room.is_ready(5, 6): + self.room.state = 6 + flag_13 = True + + if self.room.state == 5 and self.room.is_ready(5, 7): + self.room.state = 7 + self.room.countdown = 0xffffffff + flag_13 = True + + if self.room.state == 5 and self.room.countdown <= 0: + print('我怎么知道这是啥') + + if self.room.state == 6 and self.room.countdown <= 0: + # 此处不清楚 + self.room.state = 7 + self.room.countdown = 0xffffffff + flag_13 = True + + if self.room.countdown <= 0: + self.room.countdown = 0 + + if self.room.state == 7 or self.room.state == 8: + if player.timer < bi(self.command[28:32]) or bi(self.command[28:32]) == 0 and player.timer != 0: + player.last_timer = player.timer + player.last_score = player.score + player.timer = bi(self.command[28:32]) + player.score = bi(self.command[24:28]) + + if player.timer != 0 or self.room.state != 8: + for i in self.room.players: + i.extra_command_queue.append( + x.command_0e(self.player_index)) + + if self.room.is_ready(8, 1): + flag_13 = True + self.room.state = 1 + self.room.song_idx = 0xffff + if self.room.round_switch == 1: + self.room.make_round() + + for i in self.room.players: + i.timer = 0 + i.score = 0 + + if self.room.is_finish(): + # 有人退房导致的结算 + self.room.make_finish() + flag_13 = True + + if flag_11: + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_11()) + if flag_12: + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_12(self.player_index)) + if flag_13: + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_13()) + + return re + + def command_0a(self): + # 退出房间 + self.room.delete_player(self.player_index) + + x = CommandSender(self.room) + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_12(self.player_index)) + + if self.room.state == 3: + self.room.state = 1 + self.room.song_idx = 0xffff + # self.room.command_queue_length += 1 + # self.room.command_queue.append(x.command_11()) + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_13()) + self.room.command_queue_length += 1 + self.room.command_queue.append(x.command_14()) + return None + + def command_0b(self): + # 推荐歌曲 + song_idx = bi(self.command[16:18]) + x = CommandSender(self.room) + for i in range(4): + if self.player_index != i and self.room.players[i].online == 1: + self.room.players[i].extra_command_queue.append( + x.command_0f(self.player_index, song_idx)) + + return None diff --git a/latest version/udpserver/udp_sender.py b/latest version/udpserver/udp_sender.py new file mode 100644 index 0000000..3d2d9f8 --- /dev/null +++ b/latest version/udpserver/udp_sender.py @@ -0,0 +1,46 @@ +import time +from .udp_class import Room, b + + +class CommandSender: + def __init__(self, room: Room = Room()) -> None: + self.room = room + self.timestamp = round(time.time() * 1000000) + + self.random_code = b'\x11\x11\x11\x11\x00\x00\x00\x00' + + def command_0c(self): + return b'\x06\x16\x0c\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(self.room.state) + b(self.room.countdown, 4) + b(self.timestamp, 8) + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' + + def command_0d(self, code: int): + return b'\x06\x16\x0d\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(code) + b'\x07\x07\x07\x07\x07\x07\x07' + + def command_0e(self, player_index: int): + # 分数广播 + player = self.room.players[player_index] + return b'\x06\x16\x0e\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + b(player.player_id, 8) + b(player.character_id) + b(player.is_uncapped) + b(player.difficulty) + b(player.score, 4) + b(player.timer, 4) + b(player.cleartype) + b(player.player_state) + b(player.download_percent) + b'\x01' + b(player.last_score, 4) + b(player.last_timer, 4) + b(player.online) + + def command_0f(self, player_index: int, song_idx: int): + # 歌曲推荐 + player = self.room.players[player_index] + return b'\x06\x16\x0f\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + b(player.player_id, 8) + b(song_idx, 2) + b'\x06\x06\x06\x06\x06\x06' + + def command_10(self): + # 房主宣告 + return b'\x06\x16\x10\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(self.room.host_id, 8) + + def command_11(self): + return b'\x06\x16\x11\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + self.room.get_players_info() + b'\x08\x08\x08\x08\x08\x08\x08\x08' + + def command_12(self, player_index: int): + player = self.room.players[player_index] + return b'\x06\x16\x12\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(player_index) + b(player.player_id, 8) + b(player.character_id) + b(player.is_uncapped) + b(player.difficulty) + b(player.score, 4) + b(player.timer, 4) + b(player.cleartype) + b(player.player_state) + b(player.download_percent) + b(player.online) + + def command_13(self): + return b'\x06\x16\x13\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(self.room.host_id, 8) + b(self.room.state) + b(self.room.countdown, 4) + b(self.timestamp, 8) + b(self.room.song_idx, 2) + b(self.room.interval, 2) + b(self.room.times, 7) + self.room.get_player_last_score() + b(self.room.last_song_idx, 2) + b(self.room.round_switch, 1) + b'\x01' + + def command_14(self): + return b'\x06\x16\x14\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + self.room.song_unlock + b'\x08\x08\x08\x08\x08\x08\x08\x08' + + def command_15(self): + return b'\x06\x16\x15\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.room.get_players_info() + self.room.song_unlock + b(self.room.host_id, 8) + b(self.room.state) + b(self.room.countdown, 4) + b(self.timestamp, 8) + b(self.room.song_idx, 2) + b(self.room.interval, 2) + b(self.room.times, 7) + self.room.get_player_last_score() + b(self.room.last_song_idx, 2) + b(self.room.round_switch, 1) + b'\x09\x09\x09\x09\x09\x09\x09\x09\x09' diff --git a/latest version/web/__init__.py b/latest version/web/__init__.py new file mode 100644 index 0000000..e69de29