Add Link Play

This commit is contained in:
Lost-MSth
2022-01-19 17:52:32 +08:00
parent add81ee639
commit 849f4f7260
14 changed files with 1058 additions and 79 deletions

View File

View File

View File

@@ -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/<room_code>'), 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__':

View File

View File

@@ -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

View File

@@ -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证书路径

View File

View File

@@ -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()

View File

@@ -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

View File

@@ -0,0 +1,8 @@
class Config:
TIME_LIMIT = 3600000
COMMAND_INTERVAL = 1000000
COUNTDOWM_TIME = 3999
PLAYER_TIMEOUT = 30000000

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File