Beispiel #1
0
 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())
Beispiel #2
0
    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
Beispiel #3
0
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
Beispiel #4
0
 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()
     }
Beispiel #5
0
 async def read_friend_id(data: bytes) -> int:
     buffer = KurisoBuffer(None)
     await buffer.write_to_buffer(data)
     return await buffer.read_int_32()
Beispiel #6
0
 async def read_channel_name(data: bytes) -> str:
     buffer = KurisoBuffer(None)
     await buffer.write_to_buffer(data)
     return await buffer.read_osu_string()
Beispiel #7
0
 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()
Beispiel #8
0
 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()
Beispiel #9
0
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)