async def read_message(data: bytes) -> Message: buffer = KurisoBuffer(None) await buffer.write_to_buffer(data) return Message(sender=await buffer.read_osu_string(), body=await buffer.read_osu_string(), to=await buffer.read_osu_string(), client_id=await buffer.read_int_32())
async def read_match(data: bytes) -> TypedReadMatch: buffer = KurisoBuffer(None) await buffer.write_to_buffer(data) await buffer.read_int_16( ) # skip 3 bytes for id and inProgress because default is False await buffer.read_byte() match_type = MatchTypes(await buffer.read_byte()) mods = Mods(await buffer.read_int_32()) name = await buffer.read_osu_string() password = await buffer.read_osu_string() beatmap_name = await buffer.read_osu_string() beatmap_id = await buffer.read_int_32() beatmap_md5 = await buffer.read_osu_string() slots = [Slot() for _ in range(0, 16)] # make slots for slot in slots: slot.status = SlotStatus(await buffer.read_byte()) for slot in slots: slot.team = SlotTeams(await buffer.read_byte()) for slot in slots: if slot.status.value & SlotStatus.HasPlayer: await buffer.read_int_32() host_id = await buffer.read_int_32() play_mode = GameModes(await buffer.read_byte()) scoring_type = MatchScoringTypes(await buffer.read_byte()) team_type = MatchTeamTypes(await buffer.read_byte()) is_freemod = await buffer.read_bool() match_freemod = MultiSpecialModes(int(is_freemod)) if is_freemod: for slot in slots: slot.mods = Mods(await buffer.read_int_32()) seed = await buffer.read_int_32() t_dict = { 'match_type': match_type, 'mods': mods, 'name': name, 'password': password, 'beatmap_name': beatmap_name, 'beatmap_id': beatmap_id, 'beatmap_md5': beatmap_md5, 'slots': slots, 'host_id': host_id, 'play_mode': play_mode, 'scoring_type': scoring_type, 'team_type': team_type, 'match_freemod': match_freemod, 'seed': seed } return t_dict
async def match_score_update(packet_data: bytes, token: 'Player'): if not token.match: return False match = token.match slotInd = -1 for (ind, slot) in enumerate(match.slots): if slot.token == token: slotInd = ind break slot = match.slots[slotInd] # We need extract score and hp buf = KurisoBuffer('') await buf.write_to_buffer(packet_data) await buf.slice_buffer(17) # = skip 17 bytes score = await buf.read_int_32() # = 4 bytes await buf.slice_buffer(5) # = skip 5 bytes hp_points = await buf.read_byte() slot.score = score slot.failed = hp_points == 254 packet_data = bytearray(packet_data) packet_data[4] = slotInd score_updated = await PacketBuilder.MultiScoreUpdate(packet_data) await match.enqueue_to_specific(score_updated, SlotStatus.Playing) return True
async def read_new_presence(data: bytes) -> TypedPresence: buffer = KurisoBuffer(None) await buffer.write_to_buffer(data) return { 'action': await buffer.read_byte(), 'action_text': await buffer.read_osu_string(), 'map_md5': await buffer.read_osu_string(), 'mods': await buffer.read_u_int_32(), 'mode': await buffer.read_byte(), 'map_id': await buffer.read_int_32() }
async def read_friend_id(data: bytes) -> int: buffer = KurisoBuffer(None) await buffer.write_to_buffer(data) return await buffer.read_int_32()
async def read_channel_name(data: bytes) -> str: buffer = KurisoBuffer(None) await buffer.write_to_buffer(data) return await buffer.read_osu_string()
async def read_request_users_stats(data: bytes) -> List[int]: buffer = KurisoBuffer(None) await buffer.write_to_buffer(data) return await buffer.read_i32_list()
async def read_mp_join_data(data: bytes) -> Tuple[int, str]: buffer = KurisoBuffer(None) await buffer.write_to_buffer(data) return await buffer.read_int_32(), await buffer.read_osu_string()
async def main_handler(request: Request): if request.headers.get("user-agent", "") != "osu!": return HTMLResponse(f"<html>{Context.motd_html}</html>") token = request.headers.get("osu-token", None) if token: if token == '': response = await PacketBuilder.UserID(-5) return BanchoResponse(response) # send to re-login token_object = Context.players.get_token(token=token) if not token_object: # send to re-login, because token doesn't exists in storage response = await PacketBuilder.UserID(-5) return BanchoResponse(response) token_object.last_packet_unix = int(time.time()) # packets recieve raw_bytes = KurisoBuffer(None) await raw_bytes.write_to_buffer(await request.body()) response = bytearray() while not raw_bytes.EOF(): packet_id = await raw_bytes.read_u_int_16() _ = await raw_bytes.read_int_8() # empty byte packet_length = await raw_bytes.read_int_32() if packet_id == OsuPacketID.Client_Pong.value: # client just spamming it and tries to say, that he is normal :sip: continue data = await raw_bytes.slice_buffer(packet_length) if token_object.is_restricted and packet_id not in ALLOWED_RESTRICT_PACKETS: logger.wlog( f"[{token_object.token}/{token_object.name}] Ignored packet {packet_id}(account restrict)" ) continue if packet_id in OsuEvent.handlers: # This packet can be handled by OsuEvent Class, call it now! # Oh wait let go this thing in async executor. await OsuEvent.handlers[packet_id](data, token_object) logger.klog( f"[{token_object.token}/{token_object.name}] Has triggered {OsuPacketID(packet_id)} with packet length: {packet_length}" ) else: logger.wlog( f"[Events] Packet ID: {packet_id} not found in events handlers" ) response += token_object.dequeue() response = BanchoResponse(bytes(response), token=token_object.token) return response else: # first login # Structure (new line = "|", already split) # [0] osu! version # [1] plain mac addressed, separated by "." # [2] mac addresses hash set # [3] unique ID # [4] disk ID start_time = time.time() # auth speed benchmark time loginData = (await request.body()).decode().split("\n") if len(loginData) < 3: return BanchoResponse(await PacketBuilder.UserID(-5)) if not await userHelper.check_login(loginData[0], loginData[1], request.client.host): logger.elog( f"[{loginData[0]}] tried to login but failed with password") return BanchoResponse(await PacketBuilder.UserID(-1)) user_data = await userHelper.get_start_user(loginData[0]) if not user_data: return BanchoResponse(await PacketBuilder.UserID(-1)) data = loginData[2].split("|") hashes = data[3].split(":")[:-1] time_offset = int(data[1]) pm_private = data[4] == '1' isTourney = "tourney" in data[0] # check if user already on kuriso if Context.players.get_token(uid=user_data['id']) and not isTourney: # wtf osu await Context.players.get_token(uid=user_data['id']).logout() if (user_data['privileges'] & Privileges.USER_PENDING_VERIFICATION) or \ not await userHelper.user_have_hardware(user_data['id']): # we need to verify our user is_success_verify = await userHelper.activate_user( user_data['id'], user_data['username'], hashes) if not is_success_verify: response = (await PacketBuilder.UserID( -1 ) + await PacketBuilder.Notification( 'Your HWID is not clear. Contact Staff to create account!') ) return BanchoResponse(bytes(response)) else: user_data['privileges'] = KurikkuPrivileges.Normal.value await Context.mysql.execute( "UPDATE hw_user SET activated = 1 WHERE userid = %s AND mac = %s AND unique_id = %s AND disk_id = %s", [user_data['id'], hashes[2], hashes[3], hashes[4]]) if (user_data["privileges"] & KurikkuPrivileges.Normal) != KurikkuPrivileges.Normal and \ (user_data["privileges"] & Privileges.USER_PENDING_VERIFICATION) == 0: logger.elog(f"[{loginData}] Banned chmo tried to login") response = (await PacketBuilder.UserID( -1 ) + await PacketBuilder.Notification( 'You are banned. Join our discord for additional information.') ) return BanchoResponse(bytes(response)) if ((user_data["privileges"] & Privileges.USER_PUBLIC > 0) and (user_data["privileges"] & Privileges.USER_NORMAL == 0)) \ and (user_data["privileges"] & Privileges.USER_PENDING_VERIFICATION) == 0: logger.elog(f"[{loginData}] Locked dude tried to login") response = ( await PacketBuilder.UserID(-1) + await PacketBuilder.Notification( 'You are locked by staff. Join discord and ask for unlock!' )) return BanchoResponse(bytes(response)) if bool(Context.bancho_settings['bancho_maintenance']): # send to user that maintenance if not (user_data['privileges'] & KurikkuPrivileges.Developer): response = (await PacketBuilder.UserID( -1 ) + await PacketBuilder.Notification( 'Kuriso! is in maintenance mode. Please try to login again later.' )) return BanchoResponse(bytes(response)) await Context.mysql.execute( ''' INSERT INTO hw_user (userid, mac, unique_id, disk_id, occurencies) VALUES (%s, %s, %s, %s, 1) ON DUPLICATE KEY UPDATE occurencies = occurencies + 1''', [user_data['id'], hashes[2], hashes[3], hashes[4]] ) # log hardware и не ебёт что osu_version = data[0] await userHelper.setUserLastOsuVer(user_data['id'], osu_version) osuVersionInt = osu_version[1:9] now = datetime.datetime.now() vernow = datetime.datetime(int(osuVersionInt[:4]), int(osuVersionInt[4:6]), int(osuVersionInt[6:8]), 00, 00) deltanow = now - vernow if not osuVersionInt[0].isdigit() or \ deltanow.days > 360 or int(osuVersionInt) < 20200811: response = (await PacketBuilder.UserID( -2 ) + await PacketBuilder.Notification( 'Sorry, you use outdated/bad osu!version. Please update your game to join server' )) return BanchoResponse(bytes(response)) if isTourney: if Context.players.get_token(uid=user_data['id']): # manager was logged before, we need just add additional token token, player = Context.players.get_token( uid=user_data['id']).add_additional_client() else: player = TourneyPlayer( int(user_data['id']), user_data['username'], user_data['privileges'], time_offset, pm_private, 0 if user_data['silence_end'] - int(time.time()) < 0 else user_data['silence_end'] - int(time.time()), is_tourneymode=True, ip=request.client.host) await asyncio.gather(*[ player.parse_friends(), player.update_stats(), player.parse_country(request.client.host) ]) else: # create Player instance finally!!!! player = Player( int(user_data['id']), user_data['username'], user_data['privileges'], time_offset, pm_private, 0 if user_data['silence_end'] - int(time.time()) < 0 else user_data['silence_end'] - int(time.time()), ip=request.client.host) await asyncio.gather(*[ player.parse_friends(), player.update_stats(), player.parse_country(request.client.host) ]) if "ppy.sh" in request.url.netloc and not ( player.is_admin or (player.privileges & KurikkuPrivileges.TournamentStaff == KurikkuPrivileges.TournamentStaff)): return BanchoResponse( bytes(await PacketBuilder.UserID( -5 ) + await PacketBuilder.Notification( 'Sorry, you use outdated connection to server. Please use devserver flag' ))) user_country = await userHelper.get_country(user_data['id']) if user_country == "XX": await userHelper.set_country(user_data['id'], player.country[1]) start_bytes_async = await asyncio.gather(*[ PacketBuilder.UserID(player.id), PacketBuilder.ProtocolVersion(19), PacketBuilder.BanchoPrivileges(player.bancho_privs), PacketBuilder.UserPresence(player), PacketBuilder.UserStats(player), PacketBuilder.FriendList(player.friends), PacketBuilder.SilenceEnd( player.silence_end if player.silence_end > 0 else 0), PacketBuilder.Notification( f'''Welcome to kuriso!\nBuild ver: v{Context.version}\nCommit: {Context.commit_id}''' ), PacketBuilder.Notification( f'Authorization took: {round((time.time() - start_time) * 1000, 4)}ms' ) ]) start_bytes = b''.join(start_bytes_async) if Context.bancho_settings.get('login_notification', None): start_bytes += await PacketBuilder.Notification( Context.bancho_settings.get('login_notification', None)) if Context.bancho_settings.get('bancho_maintenance', None): start_bytes += await PacketBuilder.Notification( 'Don\'t forget enable server after maintenance :sip:') if Context.bancho_settings['menu_icon']: start_bytes += await PacketBuilder.MainMenuIcon( Context.bancho_settings['menu_icon']) if isTourney and Context.players.get_token(uid=user_data['id']): logger.klog( f"[{player.token}/{player.name}] Joined kuriso as additional client for origin!" ) for p in Context.players.get_all_tokens(): if p.is_restricted: continue start_bytes += bytes(await PacketBuilder.UserPresence(p) + await PacketBuilder.UserStats(p)) else: for p in Context.players.get_all_tokens(): if p.is_restricted: continue start_bytes += bytes(await PacketBuilder.UserPresence(p) + await PacketBuilder.UserStats(p)) p.enqueue( bytes(await PacketBuilder.UserPresence(player) + await PacketBuilder.UserStats(player))) await userHelper.saveBanchoSession(player.id, request.client.host) Context.players.add_token(player) await Context.redis.set("ripple:online_users", len(Context.players.get_all_tokens(True))) logger.klog(f"[{player.token}/{player.name}] Joined kuriso!") # default channels to join is #osu, #announce and #english await asyncio.gather(*[ Context.channels['#osu'].join_channel( player), Context.channels['#announce'].join_channel(player), Context.channels['#english'].join_channel(player) ]) for (_, chan) in Context.channels.items(): if not chan.temp_channel and chan.can_read: start_bytes += await PacketBuilder.ChannelAvailable(chan) start_bytes += await PacketBuilder.ChannelListeningEnd() if player.is_restricted: start_bytes += await PacketBuilder.UserRestricted() await CrystalBot.ez_message( player.name, "Your account is currently in restricted mode. Please visit kurikku's website for more information." ) Context.stats['osu_versions'].labels(osu_version=osu_version).inc() Context.stats['devclient_usage'].labels(host=request.url.netloc).inc() return BanchoResponse(start_bytes, player.token)