├── .editorconfig ├── .gitignore ├── .gitmodules ├── README.md ├── RippleDatabaseMerger ├── Base.cs ├── Database │ ├── Models │ │ ├── RippleBeatmap.cs │ │ ├── RippleScore.cs │ │ ├── RippleStats.cs │ │ └── RippleUser.cs │ └── RippleDbContext.cs ├── Enums │ └── RipplePrivileges.cs └── RippleDatabaseMerger.csproj ├── gulagDatabaseMerger ├── Base.cs ├── Database │ ├── GulagDbContext.cs │ └── Models │ │ ├── GulagBeatmap.cs │ │ ├── GulagScore.cs │ │ ├── GulagStats.cs │ │ └── GulagUser.cs ├── Enums │ └── GulagPrivileges.cs └── gulagDatabaseMerger.csproj ├── oyasumi.sln ├── oyasumi.sln.DotSettings ├── oyasumi.sln.DotSettings.user └── oyasumi ├── API ├── Controllers │ ├── GeneralController.cs │ ├── LeaderboardController.cs │ └── UsersController.cs ├── Request │ ├── LoginRequest.cs │ ├── PasswordMergeRequest.cs │ ├── RegistrationRequest.cs │ ├── UserUpdatePasswordRequest.cs │ ├── UserUpdateRequest.cs │ └── UserpageUpdateRequest.cs ├── Response │ ├── LeaderboardResponse.cs │ ├── MeResponse.cs │ ├── ProfileResponse.cs │ ├── ProfileScoreResponse.cs │ └── ServerStatsResponse.cs └── Utilities │ └── Misc.cs ├── AssemblyInfo.cs ├── Attributes ├── CommandAttribute.cs └── PacketAttribute.cs ├── Base.cs ├── Chat ├── CommandEvents.cs ├── Commands.cs └── Objects │ └── ScheduledCommand.cs ├── Controllers ├── AvatarController.cs ├── BanchoController.cs ├── DirectController.cs ├── OyasumiController.cs ├── RegistrationController.cs └── WebController.cs ├── Database ├── Attributes │ └── Table.cs ├── DbContext.cs ├── DbReader.cs ├── DbWriter.cs └── Tables │ ├── DbBeatmap.cs │ ├── DbChannel.cs │ ├── Friend.cs │ ├── Score.cs │ ├── Token.cs │ ├── User.cs │ └── UserStats.cs ├── Enums ├── ActionStatuses.cs ├── BadFlags.cs ├── BanchoPermissions.cs ├── CompletedStatus.cs ├── LeaderboardMode.cs ├── LoginReplies.cs ├── MatchScoringTypes.cs ├── MatchSpecialModes.cs ├── MatchTeamTypes.cs ├── MatchTypes.cs ├── Mods.cs ├── PacketType.cs ├── PlayMode.cs ├── Privileges.cs ├── RankedStatus.cs ├── RankingType.cs ├── SlotStatus.cs └── SlotTeams.cs ├── Events ├── ChatChannelJoin.cs ├── ChatChannelLeave.cs ├── ChatMessagePrivate.cs ├── ChatMessagePublic.cs ├── Disconnect.cs ├── FriendAdd.cs ├── FriendRemove.cs ├── LobbyJoin.cs ├── MatchChangeOptions.cs ├── MatchChangePassword.cs ├── MatchChangeTeam.cs ├── MatchComplete.cs ├── MatchCreate.cs ├── MatchFailed.cs ├── MatchHasBeatmap.cs ├── MatchInvite.cs ├── MatchJoin.cs ├── MatchLeave.cs ├── MatchLoadComplete.cs ├── MatchNoBeatmap.cs ├── MatchReady.cs ├── MatchScoreUpdate.cs ├── MatchSkip.cs ├── MatchSlotChange.cs ├── MatchSlotLock.cs ├── MatchStart.cs ├── MatchTransferHost.cs ├── MatchUnready.cs ├── MatchUpdateMods.cs ├── Pong.cs ├── RequestPlayerList.cs ├── SpectatorCantSpectate.cs ├── SpectatorFrames.cs ├── SpectatorJoin.cs ├── SpectatorLeft.cs ├── TournamentJoinMatch.cs ├── TournamentLeaveMatch.cs ├── TournamentMatchInfo.cs ├── UserPresenceRequest.cs ├── UserStatsRequest.cs ├── UserStatusRequestOwn.cs └── UserStatusUpdate.cs ├── Extensions ├── ArrayExtensions.cs ├── ScoreExtensions.cs ├── SerializationExtensions.cs └── UserExtensions.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── GlobalSuppressions.cs ├── IO ├── PacketReader.cs └── PacketWriter.cs ├── Interfaces └── IStats.cs ├── Layouts └── BanchoPackets.cs ├── Managers ├── BeatmapManager.cs ├── ChannelManager.cs ├── MatchManager.cs └── PresenceManager.cs ├── Objects ├── Beatmap.cs ├── Channel.cs ├── Chart.cs ├── Config.cs ├── Filter.cs ├── Match.cs ├── Packet.cs ├── Presence.cs ├── Score.cs └── VisualSettings.cs ├── Properties └── launchSettings.json ├── Startup.cs ├── ThirdParty ├── AwaitableInfo.cs ├── CoercedAwaitableInfo.cs ├── ObjectMethodExecutor.cs ├── ObjectMethodExecutorAwaitable.cs ├── ObjectMethodExecutorCompiledFast.cs ├── ObjectMethodExecutorCompiledFastClosure.cs └── ObjectMethodExecutorFSharpSupport.cs ├── Utilities ├── Calculator.cs ├── Crypto.cs ├── IMultiKeyDictionary.cs ├── MultiKeyDictionary.cs ├── NetUtils.cs ├── ReflectionUtils.cs ├── Replay.cs ├── ScoreUtils.cs └── Time.cs ├── lib ├── BCrypt.dll └── oppai.dll ├── oyasumi.csproj └── oyasumi.csproj.user /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CS0626: Метод, оператор или метод доступа помечен как внешний и не имеет атрибутов 4 | dotnet_diagnostic.CS0626.severity = none 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vs/ 4 | config.json 5 | oyasumi/data/osr 6 | oyasumi/data/ 7 | /.idea/.idea.oyasumi 8 | .idea/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "osu"] 2 | path = osu 3 | url = https://github.com/ppy/osu 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oyasumi - the osu! server implementation 2 | 3 | ##### Features 4 | * Score sumbission 5 | * Beatmap Leaderboard 6 | * Login 7 | * Chat 8 | * Spectators 9 | * Multiplayer 10 | * Data Caching 11 | * Commands 12 | 13 | ##### How can I contribute in this project? 14 | - Create issues with bug reports 15 | 16 | #### Goals 17 | - Just for fun 18 | 19 | # Setup 20 | Before setup you need **dotnet** > 5.0, on lower versions you won't be able to compile oyasumi 21 | 22 | ### Clone repository 23 | ```sh 24 | $ git clone https://github.com/xxCherry/oyasumi --recurse-submodules 25 | ``` 26 | 27 | ### Restore & Build all projects 28 | ```sh 29 | $ dotnet restore . && dotnet build . -c Release 30 | ``` 31 | 32 | ### Copy compiled oyasumi to your folder 33 | ```sh 34 | $ cp -R oyasumi/bin/Release/net5.0 /any/path 35 | ``` 36 | 37 | ### Start oyasumi and edit configuration file! 38 | ```sh 39 | $ ./oyasumi 40 | $ nano config.json 41 | ``` 42 | 43 | # Unique features 44 | 45 | * Scheduled commands (multi-line commands): allows you to write argument in the next messages. (Idea by cmyui) 46 | * Command presence filter: allows you to check if ***Presence*** matches the command conditions. 47 | 48 | # FAQ 49 | 50 | * Q: How to start oyasumi on custom port? 51 | * A: `./oyasumi --urls=http://localhost:port` 52 | 53 | * Q: How to enable relax pp? 54 | * A: You need to edit osu!'s repository. Add `public bool Ranked = true` in `OsuModRelax.cs` [Planned to use my own fork of osu! repository] 55 | -------------------------------------------------------------------------------- /RippleDatabaseMerger/Database/Models/RippleBeatmap.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using oyasumi.Enums; 4 | 5 | namespace RippleDatabaseMerger.Database.Models 6 | { 7 | 8 | [Table("beatmaps")] 9 | public class RippleBeatmap 10 | { 11 | [Required] 12 | [Column("id")] 13 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 14 | public int Id { get; set; } 15 | 16 | [Required] [Column("beatmap_md5")] public string Checksum { get; set; } 17 | 18 | [Required] [Column("ranked")] public RankedStatus Status { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /RippleDatabaseMerger/Database/Models/RippleScore.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace RippleDatabaseMerger.Database.Models 5 | { 6 | [Table("scores")] 7 | public class RippleScore 8 | { 9 | [Required] 10 | [Column("id")] 11 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 12 | public int Id { get; set; } 13 | [Required] [Column("beatmap_md5")] public string BeatmapChecksum { get; set; } 14 | [Required] [Column("userid")] public int UserId { get; set; } 15 | [Required] [Column("score")] public int Score { get; set; } 16 | [Required] [Column("max_combo")] public int MaxCombo { get; set; } 17 | [Required] [Column("mods")] public int Mods { get; set; } 18 | [Required] [Column("300_count")] public int Count300 { get; set; } 19 | [Required] [Column("100_count")] public int Count100 { get; set; } 20 | [Required] [Column("50_count")] public int Count50 { get; set; } 21 | [Required] [Column("gekis_count")] public int CountGeki { get; set; } 22 | [Required] [Column("katus_count")] public int CountKatu { get; set; } 23 | [Required] [Column("misses_count")] public int CountMiss { get; set; } 24 | [Required] [Column("time")] public string Time { get; set; } 25 | [Required] [Column("play_mode")] public sbyte PlayMode { get; set; } 26 | [Required] [Column("accuracy")] public double Accuracy { get; set; } 27 | [Required] [Column("pp")] public float Performance { get; set; } 28 | [Required] [Column("completed")] public byte Completed { get; set; } 29 | } 30 | 31 | [Table("scores_relax")] 32 | public class RippleRelaxScore 33 | { 34 | [Required] 35 | [Column("id")] 36 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 37 | public int Id { get; set; } 38 | [Required] [Column("beatmap_md5")] public string BeatmapChecksum { get; set; } 39 | [Required] [Column("userid")] public int UserId { get; set; } 40 | [Required] [Column("score")] public int Score { get; set; } 41 | [Required] [Column("max_combo")] public int MaxCombo { get; set; } 42 | [Required] [Column("mods")] public int Mods { get; set; } 43 | [Required] [Column("300_count")] public int Count300 { get; set; } 44 | [Required] [Column("100_count")] public int Count100 { get; set; } 45 | [Required] [Column("50_count")] public int Count50 { get; set; } 46 | [Required] [Column("gekis_count")] public int CountGeki { get; set; } 47 | [Required] [Column("katus_count")] public int CountKatu { get; set; } 48 | [Required] [Column("misses_count")] public int CountMiss { get; set; } 49 | [Required] [Column("time")] public string Time { get; set; } 50 | [Required] [Column("play_mode")] public sbyte PlayMode { get; set; } 51 | [Required] [Column("accuracy")] public double Accuracy { get; set; } 52 | [Required] [Column("pp")] public float Performance { get; set; } 53 | [Required] [Column("completed")] public byte Completed { get; set; } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /RippleDatabaseMerger/Database/Models/RippleStats.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations.Schema; 2 | using Microsoft.Build.Framework; 3 | 4 | namespace RippleDatabaseMerger.Database.Models 5 | { 6 | // lazy to make it look cool 7 | [Table("users_stats")] 8 | public class RippleStats 9 | { 10 | [Required] 11 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 12 | public int id { get; set; } 13 | 14 | [Required] public string username { get; set; } 15 | [Required] public string? userpage_content { get; set; } 16 | [Required] public string? country { get; set; } 17 | [Required] public int play_style { get; set; } 18 | [Required] public int favourite_mode { get; set; } 19 | [Required] public string? custom_badge_icon { get; set; } 20 | [Required] public string? custom_badge_name { get; set; } 21 | [Required] public bool show_custom_badge { get; set; } 22 | [Required] public int level_std { get; set; } 23 | [Required] public int level_taiko { get; set; } 24 | [Required] public int level_ctb { get; set; } 25 | [Required] public int level_mania { get; set; } 26 | 27 | [Required] public long ranked_score_std { get; set; } 28 | [Required] public long ranked_score_taiko { get; set; } 29 | [Required] public long ranked_score_ctb { get; set; } 30 | [Required] public long ranked_score_mania { get; set; } 31 | 32 | [Required] public long total_score_std { get; set; } 33 | [Required] public long total_score_taiko { get; set; } 34 | [Required] public long total_score_ctb { get; set; } 35 | [Required] public long total_score_mania { get; set; } 36 | 37 | [Required] public int total_hits_std { get; set; } 38 | [Required] public int total_hits_taiko { get; set; } 39 | [Required] public int total_hits_ctb { get; set; } 40 | [Required] public int total_hits_mania { get; set; } 41 | 42 | [Required] public int replays_watched_std { get; set; } 43 | [Required] public int replays_watched_taiko { get; set; } 44 | [Required] public int replays_watched_ctb { get; set; } 45 | [Required] public int replays_watched_mania { get; set; } 46 | 47 | [Required] public int playcount_std { get; set; } 48 | [Required] public int playcount_taiko { get; set; } 49 | [Required] public int playcount_ctb { get; set; } 50 | [Required] public int playcount_mania { get; set; } 51 | 52 | [Required] public float avg_accuracy_std { get; set; } 53 | [Required] public float avg_accuracy_taiko { get; set; } 54 | [Required] public float avg_accuracy_ctb { get; set; } 55 | [Required] public float avg_accuracy_mania { get; set; } 56 | 57 | [Required] public int pp_std { get; set; } 58 | [Required] public int pp_taiko { get; set; } 59 | [Required] public int pp_ctb { get; set; } 60 | [Required] public int pp_mania { get; set; } 61 | } 62 | 63 | [Table("rx_stats")] 64 | public class RippleRelaxStats 65 | { 66 | [Required] 67 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 68 | public int id { get; set; } 69 | [Required] public string? username { get; set; } 70 | [Required] public string? country { get; set; } 71 | 72 | [Required] public int favourite_mode { get; set; } 73 | 74 | [Required] public int level_std { get; set; } 75 | [Required] public int level_taiko { get; set; } 76 | [Required] public int level_ctb { get; set; } 77 | [Required] public int level_mania { get; set; } 78 | 79 | [Required] public long ranked_score_std { get; set; } 80 | [Required] public long ranked_score_taiko { get; set; } 81 | [Required] public long ranked_score_ctb { get; set; } 82 | [Required] public long ranked_score_mania { get; set; } 83 | 84 | [Required] public long total_score_std { get; set; } 85 | [Required] public long total_score_taiko { get; set; } 86 | [Required] public long total_score_ctb { get; set; } 87 | [Required] public long total_score_mania { get; set; } 88 | 89 | [Required] public int total_hits_std { get; set; } 90 | [Required] public int total_hits_taiko { get; set; } 91 | [Required] public int total_hits_ctb { get; set; } 92 | [Required] public int total_hits_mania { get; set; } 93 | 94 | [Required] public int replays_watched_std { get; set; } 95 | [Required] public int replays_watched_taiko { get; set; } 96 | [Required] public int replays_watched_ctb { get; set; } 97 | [Required] public int replays_watched_mania { get; set; } 98 | 99 | [Required] public int playcount_std { get; set; } 100 | [Required] public int playcount_taiko { get; set; } 101 | [Required] public int playcount_ctb { get; set; } 102 | [Required] public int playcount_mania { get; set; } 103 | 104 | [Required] public float avg_accuracy_std { get; set; } 105 | [Required] public float avg_accuracy_taiko { get; set; } 106 | [Required] public float avg_accuracy_ctb { get; set; } 107 | [Required] public float avg_accuracy_mania { get; set; } 108 | 109 | [Required] public int pp_std { get; set; } 110 | [Required] public int pp_taiko { get; set; } 111 | [Required] public int pp_ctb { get; set; } 112 | [Required] public int pp_mania { get; set; } 113 | } 114 | } -------------------------------------------------------------------------------- /RippleDatabaseMerger/Database/Models/RippleUser.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using RippleDatabaseMerger.Enums; 4 | 5 | namespace RippleDatabaseMerger.Database.Models 6 | { 7 | [Table("users")] 8 | public class RippleUser 9 | { 10 | [Required] 11 | [Column("id")] 12 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 13 | public int Id { get; set; } 14 | [Column("username")] [Required] public string Name { get; set; } 15 | [Column("password_md5")] [Required] public string Password { get; set; } 16 | [Column("register_datetime")] [Required] public int JoinTimestamp { get; set; } 17 | [Column("email")] [Required] public string Email { get; set; } 18 | [Column("privileges")] [Required] public RipplePrivileges Privileges { get; set; } 19 | [Column("ssalt")] [Required] public byte[] Salt { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /RippleDatabaseMerger/Database/RippleDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using RippleDatabaseMerger.Database.Models; 3 | 4 | namespace RippleDatabaseMerger.Database 5 | { 6 | public class RippleDbContext : DbContext 7 | { 8 | public RippleDbContext(DbContextOptions options) : base(options) 9 | { 10 | } 11 | 12 | public DbSet Users { get; set; } 13 | public DbSet Stats { get; set; } 14 | public DbSet RelaxStats { get; set; } 15 | public DbSet Scores { get; set; } 16 | public DbSet RelaxScores { get; set; } 17 | public DbSet Beatmaps { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RippleDatabaseMerger/Enums/RipplePrivileges.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RippleDatabaseMerger.Enums 4 | { 5 | [Flags] 6 | public enum RipplePrivileges 7 | { 8 | UserBanned = 0, 9 | UserPublic = 1, 10 | UserNormal = 2 << 0, 11 | UserDonor = 2 << 1, 12 | AdminAccessRAP = 2 << 2, 13 | AdminManageUsers = 2 << 3, 14 | AdminBanUsers = 2 << 4, 15 | AdminSilenceUsers = 2 << 5, 16 | AdminWipeUsers = 2 << 6, 17 | AdminManageBeatmaps = 2 << 7, 18 | AdminManageServers = 2 << 8, 19 | AdminManageSettings = 2 << 9, 20 | AdminManageBetaKeys = 2 << 10, 21 | AdminManageReports = 2 << 11, 22 | AdminManageDocs = 2 << 12, 23 | AdminManageBadges = 2 << 13, 24 | AdminViewRAPLogs = 2 << 14, 25 | AdminManagePrivileges = 2 << 15, 26 | AdminSendAlerts = 2 << 16, 27 | AdminChatMod = 2 << 17, 28 | AdminKickUsers = 2 << 18, 29 | UserPendingVerification = 2 << 19, 30 | UserTournamentStaff = 2 << 20, 31 | AdminCaker = 2 << 21, 32 | AdminViewTopScores = 2 << 22 33 | } 34 | } -------------------------------------------------------------------------------- /RippleDatabaseMerger/RippleDatabaseMerger.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /gulagDatabaseMerger/Database/GulagDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using gulagDatabaseMerger.Database.Models; 3 | 4 | namespace gulagDatabaseMerger.Database 5 | { 6 | public class GulagDbContext : DbContext 7 | { 8 | public GulagDbContext(DbContextOptions options) : base(options) 9 | { 10 | } 11 | 12 | public DbSet Users { get; set; } 13 | public DbSet Stats { get; set; } 14 | public DbSet Scores { get; set; } 15 | public DbSet RelaxScores { get; set; } 16 | public DbSet Beatmaps { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gulagDatabaseMerger/Database/Models/GulagBeatmap.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using oyasumi.Enums; 4 | 5 | namespace gulagDatabaseMerger.Database.Models 6 | { 7 | 8 | [Table("maps")] 9 | public class GulagBeatmap 10 | { 11 | [Required] 12 | [Column("id")] 13 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 14 | public int Id { get; set; } 15 | 16 | [Required] [Column("md5")] public string Checksum { get; set; } 17 | [Required] [Column("status")] public RankedStatus Status { get; set; } 18 | [Required] [Column("set_id")] public int SetId { get; set; } 19 | [Required] [Column("artist")] public string Artist { get; set; } 20 | [Required] [Column("title")] public string Title { get; set; } 21 | [Required] [Column("version")] public string Version { get; set; } 22 | [Required] [Column("creator")] public string Creator { get; set; } 23 | [Required] [Column("plays")] public int PlayCount { get; set; } 24 | [Required] [Column("passes")] public int PassCount { get; set; } 25 | [Required] [Column("frozen")] public bool Frozen { get; set; } 26 | [Required] [Column("bpm")] public float BPM { get; set; } 27 | [Required] [Column("cs")] public float CS { get; set; } 28 | [Required] [Column("ar")] public float AR { get; set; } 29 | [Required] [Column("od")] public float OD { get; set; } 30 | [Required] [Column("hp")] public float HP { get; set; } 31 | [Required] [Column("diff")] public float SR { get; set; } 32 | } 33 | } -------------------------------------------------------------------------------- /gulagDatabaseMerger/Database/Models/GulagScore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace gulagDatabaseMerger.Database.Models 6 | { 7 | [Table("scores_vn")] 8 | public class GulagScore 9 | { 10 | [Required] 11 | [Column("id")] 12 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 13 | public int Id { get; set; } 14 | [Required] [Column("map_md5")] public string BeatmapChecksum { get; set; } 15 | [Required] [Column("userid")] public int UserId { get; set; } 16 | [Required] [Column("score")] public int Score { get; set; } 17 | [Required] [Column("status")] public int Completed { get; set; } 18 | [Required] [Column("max_combo")] public int MaxCombo { get; set; } 19 | [Required] [Column("mods")] public int Mods { get; set; } 20 | [Required] [Column("n300")] public int Count300 { get; set; } 21 | [Required] [Column("n100")] public int Count100 { get; set; } 22 | [Required] [Column("n50")] public int Count50 { get; set; } 23 | [Required] [Column("ngeki")] public int CountGeki { get; set; } 24 | [Required] [Column("nkatu")] public int CountKatu { get; set; } 25 | [Required] [Column("nmiss")] public int CountMiss { get; set; } 26 | [Required] [Column("play_time")] public DateTime Time { get; set; } 27 | [Required] [Column("mode")] public int PlayMode { get; set; } 28 | [Required] [Column("acc")] public double Accuracy { get; set; } 29 | [Required] [Column("pp")] public float Performance { get; set; } 30 | } 31 | 32 | [Table("scores_rx")] 33 | public class GulagRelaxScore 34 | { 35 | [Required] 36 | [Column("id")] 37 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 38 | public int Id { get; set; } 39 | [Required] [Column("map_md5")] public string BeatmapChecksum { get; set; } 40 | [Required] [Column("userid")] public int UserId { get; set; } 41 | [Required] [Column("score")] public int Score { get; set; } 42 | [Required] [Column("status")] public int Completed { get; set; } 43 | [Required] [Column("max_combo")] public int MaxCombo { get; set; } 44 | [Required] [Column("mods")] public int Mods { get; set; } 45 | [Required] [Column("n300")] public int Count300 { get; set; } 46 | [Required] [Column("n100")] public int Count100 { get; set; } 47 | [Required] [Column("n50")] public int Count50 { get; set; } 48 | [Required] [Column("ngeki")] public int CountGeki { get; set; } 49 | [Required] [Column("nkatu")] public int CountKatu { get; set; } 50 | [Required] [Column("nmiss")] public int CountMiss { get; set; } 51 | [Required] [Column("play_type")] public DateTime Time { get; set; } 52 | [Required] [Column("mode")] public int PlayMode { get; set; } 53 | [Required] [Column("acc")] public double Accuracy { get; set; } 54 | [Required] [Column("pp")] public float Performance { get; set; } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /gulagDatabaseMerger/Database/Models/GulagStats.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations.Schema; 2 | using Microsoft.Build.Framework; 3 | 4 | namespace gulagDatabaseMerger.Database.Models 5 | { 6 | // lazy to make it look cool 7 | [Table("stats")] 8 | public class GulagStats 9 | { 10 | [Required] 11 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 12 | public int id { get; set; } 13 | 14 | [Required] public int tscore_vn_std { get; set; } 15 | [Required] public int tscore_vn_taiko { get; set; } 16 | [Required] public int tscore_vn_catch { get; set; } 17 | [Required] public int tscore_vn_mania { get; set; } 18 | [Required] public int tscore_rx_std { get; set; } 19 | [Required] public int tscore_rx_taiko { get; set; } 20 | [Required] public int tscore_rx_catch { get; set; } 21 | [Required] public int tscore_ap_std { get; set; } 22 | [Required] public int rscore_vn_std { get; set; } 23 | [Required] public int rscore_vn_taiko { get; set; } 24 | [Required] public int rscore_vn_catch { get; set; } 25 | [Required] public int rscore_vn_mania { get; set; } 26 | [Required] public int rscore_rx_std { get; set; } 27 | [Required] public int rscore_rx_taiko { get; set; } 28 | [Required] public int rscore_rx_catch { get; set; } 29 | [Required] public int rscore_ap_std { get; set; } 30 | [Required] public int pp_vn_taiko { get; set; } 31 | [Required] public int pp_vn_catch { get; set; } 32 | [Required] public int pp_vn_mania { get; set; } 33 | [Required] public int pp_rx_std { get; set; } 34 | [Required] public int pp_rx_taiko { get; set; } 35 | [Required] public int pp_rx_catch { get; set; } 36 | [Required] public int pp_ap_std { get; set; } 37 | [Required] public int plays_vn_std { get; set; } 38 | [Required] public int plays_vn_taiko { get; set; } 39 | [Required] public int plays_vn_catch { get; set; } 40 | [Required] public int plays_vn_mania { get; set; } 41 | [Required] public int plays_rx_std { get; set; } 42 | [Required] public int plays_rx_taiko { get; set; } 43 | [Required] public int plays_rx_catch { get; set; } 44 | [Required] public int plays_ap_std { get; set; } 45 | [Required] public int playtime_vn_std { get; set; } 46 | [Required] public int playtime_vn_taiko { get; set; } 47 | [Required] public int playtime_vn_catch { get; set; } 48 | [Required] public int playtime_vn_mania { get; set; } 49 | [Required] public int playtime_rx_std { get; set; } 50 | [Required] public int playtime_rx_taiko { get; set; } 51 | [Required] public int playtime_rx_catch { get; set; } 52 | [Required] public int playtime_ap_std { get; set; } 53 | [Required] public float acc_vn_std { get; set; } 54 | [Required] public float acc_vn_taiko { get; set; } 55 | [Required] public float acc_vn_catch { get; set; } 56 | [Required] public float acc_vn_mania { get; set; } 57 | [Required] public float acc_rx_std { get; set; } 58 | [Required] public float acc_rx_taiko { get; set; } 59 | [Required] public float acc_rx_catch { get; set; } 60 | [Required] public float acc_ap_std { get; set; } 61 | [Required] public int maxcombo_vn_std { get; set; } 62 | [Required] public int maxcombo_vn_taiko { get; set; } 63 | [Required] public int maxcombo_vn_catch { get; set; } 64 | [Required] public int maxcombo_vn_mania { get; set; } 65 | [Required] public int maxcombo_rx_std { get; set; } 66 | [Required] public int maxcombo_rx_taiko { get; set; } 67 | [Required] public int maxcombo_rx_catch { get; set; } 68 | [Required] public int maxcombo_ap_std { get; set; } 69 | } 70 | } -------------------------------------------------------------------------------- /gulagDatabaseMerger/Database/Models/GulagUser.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using gulagDatabaseMerger.Enums; 4 | 5 | namespace gulagDatabaseMerger.Database.Models 6 | { 7 | [Table("users")] 8 | public class GulagUser 9 | { 10 | [Required] 11 | [Column("id")] 12 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 13 | public int Id { get; set; } 14 | [Column("name")] [Required] public string Name { get; set; } 15 | [Column("safe_name")] [Required] public string SafeName { get; set; } 16 | [Column("pw_bcrypt")] [Required] public string Password { get; set; } 17 | [Column("creation_time")] [Required] public int JoinTimestamp { get; set; } 18 | [Column("email")] [Required] public string Email { get; set; } 19 | [Column("priv")] [Required] public GulagPrivileges Privileges { get; set; } 20 | [Column("country")] [Required] public string Country { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /gulagDatabaseMerger/Enums/GulagPrivileges.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace gulagDatabaseMerger.Enums 4 | { 5 | [Flags] 6 | public enum GulagPrivileges 7 | { 8 | Normal = 1 << 0, // is an unbanned player. 9 | Verified = 1 << 1, // has logged in to the server in-game. 10 | 11 | // has bypass to low-ceiling anticheat measures (trusted). 12 | Whitelisted = 1 << 2, 13 | 14 | // donation tiers, receives some extra benefits. 15 | Supporter = 1 << 4, 16 | Premium = 1 << 5, 17 | 18 | // notable users, receives some extra benefits. 19 | Alumni = 1 << 7, 20 | 21 | // staff permissions, able to manage server state. 22 | Tournament = 1 << 10, //able to manage match state without host. 23 | Nominator = 1 << 11, // able to manage maps ranked status. 24 | Mod = 1 << 12, // able to manage users (level 1). 25 | Admin = 1 << 13, // able to manage users (level 2). 26 | Dangerous = 1 << 14, // able to manage full server state. 27 | 28 | Donator = Supporter | Premium, 29 | Staff = Mod | Admin | Dangerous 30 | } 31 | } -------------------------------------------------------------------------------- /gulagDatabaseMerger/gulagDatabaseMerger.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /oyasumi.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30608.117 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "oyasumi", "oyasumi\oyasumi.csproj", "{F55A8069-D540-46D7-A833-A042EFDCDB6D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Osu", "osu\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{20BC41A4-6E76-4BA1-8C53-BB853CF10713}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game", "osu\osu.Game\osu.Game.csproj", "{B2E66911-B875-475B-92CA-7ABE6C9F9BB5}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Catch", "osu\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{098903ED-DDB9-4FB1-83C1-BA9CE94FD251}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Taiko", "osu\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj", "{A2F3B53D-5C21-42E9-B47E-7E6825389121}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Mania", "osu\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{72188D0C-4AD9-49AE-8FD6-57971CAA5D6F}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RippleDatabaseMerger", "RippleDatabaseMerger\RippleDatabaseMerger.csproj", "{A5B0DAE2-376A-40DE-96C7-0DE9510B2C5D}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GulagDatabaseMerger", "GulagDatabaseMerger\GulagDatabaseMerger.csproj", "{DC445D11-F9FE-46C1-8F25-3661E286D40F}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Debug|x86 = Debug|x86 26 | Release|Any CPU = Release|Any CPU 27 | Release|x86 = Release|x86 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {F55A8069-D540-46D7-A833-A042EFDCDB6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {F55A8069-D540-46D7-A833-A042EFDCDB6D}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {F55A8069-D540-46D7-A833-A042EFDCDB6D}.Debug|x86.ActiveCfg = Debug|x86 33 | {F55A8069-D540-46D7-A833-A042EFDCDB6D}.Debug|x86.Build.0 = Debug|x86 34 | {F55A8069-D540-46D7-A833-A042EFDCDB6D}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {F55A8069-D540-46D7-A833-A042EFDCDB6D}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {F55A8069-D540-46D7-A833-A042EFDCDB6D}.Release|x86.ActiveCfg = Release|x86 37 | {F55A8069-D540-46D7-A833-A042EFDCDB6D}.Release|x86.Build.0 = Release|x86 38 | {20BC41A4-6E76-4BA1-8C53-BB853CF10713}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {20BC41A4-6E76-4BA1-8C53-BB853CF10713}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {20BC41A4-6E76-4BA1-8C53-BB853CF10713}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {20BC41A4-6E76-4BA1-8C53-BB853CF10713}.Debug|x86.Build.0 = Debug|Any CPU 42 | {20BC41A4-6E76-4BA1-8C53-BB853CF10713}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {20BC41A4-6E76-4BA1-8C53-BB853CF10713}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {20BC41A4-6E76-4BA1-8C53-BB853CF10713}.Release|x86.ActiveCfg = Release|Any CPU 45 | {20BC41A4-6E76-4BA1-8C53-BB853CF10713}.Release|x86.Build.0 = Release|Any CPU 46 | {B2E66911-B875-475B-92CA-7ABE6C9F9BB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {B2E66911-B875-475B-92CA-7ABE6C9F9BB5}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {B2E66911-B875-475B-92CA-7ABE6C9F9BB5}.Debug|x86.ActiveCfg = Debug|Any CPU 49 | {B2E66911-B875-475B-92CA-7ABE6C9F9BB5}.Debug|x86.Build.0 = Debug|Any CPU 50 | {B2E66911-B875-475B-92CA-7ABE6C9F9BB5}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {B2E66911-B875-475B-92CA-7ABE6C9F9BB5}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {B2E66911-B875-475B-92CA-7ABE6C9F9BB5}.Release|x86.ActiveCfg = Release|Any CPU 53 | {B2E66911-B875-475B-92CA-7ABE6C9F9BB5}.Release|x86.Build.0 = Release|Any CPU 54 | {098903ED-DDB9-4FB1-83C1-BA9CE94FD251}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {098903ED-DDB9-4FB1-83C1-BA9CE94FD251}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {098903ED-DDB9-4FB1-83C1-BA9CE94FD251}.Debug|x86.ActiveCfg = Debug|Any CPU 57 | {098903ED-DDB9-4FB1-83C1-BA9CE94FD251}.Debug|x86.Build.0 = Debug|Any CPU 58 | {098903ED-DDB9-4FB1-83C1-BA9CE94FD251}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {098903ED-DDB9-4FB1-83C1-BA9CE94FD251}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {098903ED-DDB9-4FB1-83C1-BA9CE94FD251}.Release|x86.ActiveCfg = Release|Any CPU 61 | {098903ED-DDB9-4FB1-83C1-BA9CE94FD251}.Release|x86.Build.0 = Release|Any CPU 62 | {A2F3B53D-5C21-42E9-B47E-7E6825389121}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {A2F3B53D-5C21-42E9-B47E-7E6825389121}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {A2F3B53D-5C21-42E9-B47E-7E6825389121}.Debug|x86.ActiveCfg = Debug|Any CPU 65 | {A2F3B53D-5C21-42E9-B47E-7E6825389121}.Debug|x86.Build.0 = Debug|Any CPU 66 | {A2F3B53D-5C21-42E9-B47E-7E6825389121}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {A2F3B53D-5C21-42E9-B47E-7E6825389121}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {A2F3B53D-5C21-42E9-B47E-7E6825389121}.Release|x86.ActiveCfg = Release|Any CPU 69 | {A2F3B53D-5C21-42E9-B47E-7E6825389121}.Release|x86.Build.0 = Release|Any CPU 70 | {72188D0C-4AD9-49AE-8FD6-57971CAA5D6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {72188D0C-4AD9-49AE-8FD6-57971CAA5D6F}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {72188D0C-4AD9-49AE-8FD6-57971CAA5D6F}.Debug|x86.ActiveCfg = Debug|Any CPU 73 | {72188D0C-4AD9-49AE-8FD6-57971CAA5D6F}.Debug|x86.Build.0 = Debug|Any CPU 74 | {72188D0C-4AD9-49AE-8FD6-57971CAA5D6F}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {72188D0C-4AD9-49AE-8FD6-57971CAA5D6F}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {72188D0C-4AD9-49AE-8FD6-57971CAA5D6F}.Release|x86.ActiveCfg = Release|Any CPU 77 | {72188D0C-4AD9-49AE-8FD6-57971CAA5D6F}.Release|x86.Build.0 = Release|Any CPU 78 | {A5B0DAE2-376A-40DE-96C7-0DE9510B2C5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {A5B0DAE2-376A-40DE-96C7-0DE9510B2C5D}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {A5B0DAE2-376A-40DE-96C7-0DE9510B2C5D}.Debug|x86.ActiveCfg = Debug|Any CPU 81 | {A5B0DAE2-376A-40DE-96C7-0DE9510B2C5D}.Debug|x86.Build.0 = Debug|Any CPU 82 | {A5B0DAE2-376A-40DE-96C7-0DE9510B2C5D}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {A5B0DAE2-376A-40DE-96C7-0DE9510B2C5D}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {A5B0DAE2-376A-40DE-96C7-0DE9510B2C5D}.Release|x86.ActiveCfg = Release|Any CPU 85 | {A5B0DAE2-376A-40DE-96C7-0DE9510B2C5D}.Release|x86.Build.0 = Release|Any CPU 86 | {DC445D11-F9FE-46C1-8F25-3661E286D40F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 87 | {DC445D11-F9FE-46C1-8F25-3661E286D40F}.Debug|Any CPU.Build.0 = Debug|Any CPU 88 | {DC445D11-F9FE-46C1-8F25-3661E286D40F}.Debug|x86.ActiveCfg = Debug|Any CPU 89 | {DC445D11-F9FE-46C1-8F25-3661E286D40F}.Debug|x86.Build.0 = Debug|Any CPU 90 | {DC445D11-F9FE-46C1-8F25-3661E286D40F}.Release|Any CPU.ActiveCfg = Release|Any CPU 91 | {DC445D11-F9FE-46C1-8F25-3661E286D40F}.Release|Any CPU.Build.0 = Release|Any CPU 92 | {DC445D11-F9FE-46C1-8F25-3661E286D40F}.Release|x86.ActiveCfg = Release|Any CPU 93 | {DC445D11-F9FE-46C1-8F25-3661E286D40F}.Release|x86.Build.0 = Release|Any CPU 94 | EndGlobalSection 95 | GlobalSection(SolutionProperties) = preSolution 96 | HideSolutionNode = FALSE 97 | EndGlobalSection 98 | GlobalSection(ExtensibilityGlobals) = postSolution 99 | SolutionGuid = {AECAD6C1-4942-4087-B176-10D751BA112D} 100 | EndGlobalSection 101 | EndGlobal 102 | -------------------------------------------------------------------------------- /oyasumi.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True -------------------------------------------------------------------------------- /oyasumi.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> -------------------------------------------------------------------------------- /oyasumi/API/Controllers/GeneralController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Dapper; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Newtonsoft.Json; 6 | using oyasumi.API.Response; 7 | using oyasumi.Database; 8 | using oyasumi.Managers; 9 | using oyasumi.Objects; 10 | 11 | namespace oyasumi.API.Controllers 12 | { 13 | // Uncatigorized stuff 14 | [ApiController] 15 | [Route("/api/")] 16 | public class GeneralController : Controller 17 | { 18 | [HttpGet("/stats/users")] 19 | public IActionResult Stats() 20 | { 21 | var registered = DbContext.Users.Values.Length; 22 | var online = PresenceManager.Presences.Values.ToList().Count; 23 | 24 | return Content(JsonConvert.SerializeObject(new ServerStatsResponse 25 | { 26 | RegisteredUsers = registered, 27 | CurrentlyPlaying = online - 1 28 | })); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /oyasumi/API/Controllers/LeaderboardController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Dapper; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Newtonsoft.Json; 7 | using oyasumi.API.Response; 8 | using oyasumi.Database; 9 | using oyasumi.Database.Models; 10 | using oyasumi.Enums; 11 | using oyasumi.Extensions; 12 | using oyasumi.Interfaces; 13 | using oyasumi.Utilities; 14 | 15 | namespace oyasumi.API.Controllers 16 | { 17 | [ApiController] 18 | [Route("/api/")] 19 | public class LeaderboardController : Controller 20 | { 21 | [HttpGet("leaderboard")] 22 | public IActionResult Leaderboard( 23 | [FromQuery(Name = "mode")] PlayMode mode, 24 | [FromQuery(Name = "l")] int limit, 25 | [FromQuery(Name = "p")] int page, 26 | [FromQuery(Name = "relax")] bool isRelax, 27 | [FromQuery(Name = "country")] string country = "") 28 | { 29 | var stats = isRelax switch 30 | { 31 | false => Base.UserStatsCache[LeaderboardMode.Vanilla].Values, 32 | true => Base.UserStatsCache[LeaderboardMode.Relax].Values 33 | }; 34 | 35 | Response.ContentType = "application/json"; 36 | return Content(JsonConvert.SerializeObject(stats 37 | .Where(x => x.Performance(mode) > 0 && !DbContext.Users[x.Id].Banned()) 38 | .OrderByDescending(x => x.Performance(mode)) 39 | .Skip(page <= 1 ? 0 : (page - 1) * limit) 40 | .Take(limit) 41 | .Select(x => new LeaderboardResponse 42 | { 43 | Id = x.Id, 44 | Username = DbContext.Users[x.Id].Username, 45 | Country = DbContext.Users[x.Id].Country, 46 | Accuracy = x.Accuracy(mode), 47 | Performance = x.Performance(mode), 48 | Playcount = x.Playcount(mode) 49 | }))); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /oyasumi/API/Request/LoginRequest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace oyasumi.API.Request 4 | { 5 | public class LoginRequest 6 | { 7 | [JsonProperty("login")] public string Login { get; set; } 8 | [JsonProperty("password")] public string Password { get; set; } 9 | [JsonProperty("captcha_key")] public string CaptchaKey { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /oyasumi/API/Request/PasswordMergeRequest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace oyasumi.API.Request 4 | { 5 | public class PasswordMergeRequest 6 | { 7 | [JsonProperty("username")] public string Username { get; set; } 8 | [JsonProperty("password")] public string Password { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /oyasumi/API/Request/RegistrationRequest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace oyasumi.API.Request 4 | { 5 | public class RegistrationRequest 6 | { 7 | [JsonProperty("login")] public string Login { get; set; } 8 | [JsonProperty("password")] public string Password { get; set; } 9 | [JsonProperty("email")] public string Email { get; set; } 10 | [JsonProperty("captcha_key")] public string CaptchaKey { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /oyasumi/API/Request/UserUpdatePasswordRequest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace oyasumi.API.Request 4 | { 5 | public class UserUpdatePasswordRequest 6 | { 7 | [JsonProperty("email")] public string Email { get; set; } 8 | [JsonProperty("new_password")] public string NewPassword { get; set; } 9 | [JsonProperty("current_password")] public string CurrentPassword { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /oyasumi/API/Request/UserUpdateRequest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace oyasumi.API.Request 4 | { 5 | public class UserUpdateRequest 6 | { 7 | [JsonProperty("nc_instead_dt")] public bool PreferNightcore { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /oyasumi/API/Request/UserpageUpdateRequest.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace oyasumi.API.Request 4 | { 5 | public class UserpageUpdateRequest 6 | { 7 | [JsonProperty("content")] public string Content { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /oyasumi/API/Response/LeaderboardResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace oyasumi.API.Response 4 | { 5 | public class LeaderboardResponse 6 | { 7 | [JsonProperty("id")] public int Id { get; set; } 8 | [JsonProperty("username")] public string Username { get; set; } 9 | [JsonProperty("country")] public string Country { get; set; } 10 | [JsonProperty("level")] public int Level { get; set; } 11 | [JsonProperty("playcount")] public int Playcount { get; set; } 12 | [JsonProperty("performance")] public int Performance { get; set; } 13 | [JsonProperty("accuracy")] public float Accuracy { get; set; } 14 | [JsonProperty("ss_ranks")] public int SSRanks { get; set; } 15 | [JsonProperty("s_ranks")] public int SRanks { get; set; } 16 | [JsonProperty("a_ranks")] public int ARanks { get; set; } 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /oyasumi/API/Response/MeResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using oyasumi.Enums; 3 | 4 | namespace oyasumi.API.Response 5 | { 6 | public class MeResponse 7 | { 8 | [JsonProperty("id")] public int Id { get; set; } 9 | [JsonProperty("username")] public string Username { get; set; } 10 | [JsonProperty("privileges")] public Privileges Privileges { get; set; } 11 | [JsonProperty("banned")] public bool Banned { get; set; } 12 | [JsonProperty("email")] public string Email { get; set; } 13 | [JsonProperty("nc_instead_dt")] public bool PreferNightcore { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /oyasumi/API/Response/ProfileResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace oyasumi.API.Response 4 | { 5 | public class ProfileResponse 6 | { 7 | [JsonProperty("id")] public int Id { get; set; } 8 | [JsonProperty("username")] public string Username { get; set; } 9 | [JsonProperty("place")] public int Place { get; set; } 10 | [JsonProperty("level")] public int Level { get; set; } 11 | [JsonProperty("playcount")] public int Playcount { get; set; } 12 | [JsonProperty("performance")] public int Performance { get; set; } 13 | [JsonProperty("country")] public string Country { get; set; } 14 | [JsonProperty("ss_ranks")] public int SSRanks { get; set; } 15 | [JsonProperty("s_ranks")] public int SRanks { get; set; } 16 | [JsonProperty("a_ranks")] public int ARanks { get; set; } 17 | [JsonProperty("userpage_context")] public string UserpageContent { get; set; } 18 | [JsonProperty("account_created_at")] public int AccountCreatedAt { get; set; } 19 | [JsonProperty("replays_watched")] public int ReplaysWatched { get; set; } 20 | [JsonProperty("ranked_score")] public long RankedScore { get; set; } 21 | [JsonProperty("total_score")] public long TotalScore { get; set; } 22 | [JsonProperty("total_hits")] public int TotalHits { get; set; } 23 | [JsonProperty("accuracy")] public float Accuracy { get; set; } 24 | 25 | //public int verification_type { get; set; } 26 | 27 | //public int is_supporter { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /oyasumi/API/Response/ProfileScoreResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using oyasumi.Enums; 3 | using oyasumi.Objects; 4 | 5 | namespace oyasumi.API.Response 6 | { 7 | public class ProfileScoreResponse 8 | { 9 | [JsonProperty("id")] public int Id { get; set; } 10 | [JsonProperty("beatmap")] public Beatmap Beatmap { get; set; } 11 | [JsonProperty("mods")] public Mods Mods { get; set; } 12 | [JsonProperty("50_count")] public int Count50 { get; set; } 13 | [JsonProperty("100_count")] public int Count100 { get; set; } 14 | [JsonProperty("300_count")] public int Count300 { get; set; } 15 | [JsonProperty("gekis_count")] public int CountGeki { get; set; } 16 | [JsonProperty("katus_count")] public int CountKatu { get; set; } 17 | [JsonProperty("miss_count")] public int CountMiss { get; set; } 18 | [JsonProperty("max_combo")] public int Combo { get; set; } 19 | [JsonProperty("accuracy")] public double Accuracy { get; set; } 20 | [JsonProperty("timestamp")] public int Timestamp { get; set; } 21 | [JsonProperty("pp")] public double Performance { get; set; } 22 | [JsonProperty("rank")] public string Rank { get; set; } 23 | } 24 | } -------------------------------------------------------------------------------- /oyasumi/API/Response/ServerStatsResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace oyasumi.API.Response 4 | { 5 | public class ServerStatsResponse 6 | { 7 | [JsonProperty("registered")] public int RegisteredUsers { get; set; } 8 | [JsonProperty("playing")] public int CurrentlyPlaying { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /oyasumi/API/Utilities/Misc.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Newtonsoft.Json; 5 | using oyasumi.Objects; 6 | 7 | namespace oyasumi.API.Utilities 8 | { 9 | public class CaptchaResponse 10 | { 11 | [JsonProperty("success")] public bool Success { get; set; } 12 | [JsonProperty("timestamp")] public string Timestamp { get; set; } 13 | [JsonProperty("host")] public string Host { get; set; } 14 | } 15 | 16 | public class Misc 17 | { 18 | public static bool VerifyToken(string token) 19 | { 20 | return true; 21 | return token is not null && Base.TokenCache[token] is not null; 22 | } 23 | 24 | public static async Task VerifyCaptcha(string key, string userIp) 25 | { 26 | var content = new FormUrlEncodedContent(new[] 27 | { 28 | new KeyValuePair("secret", Config.Properties.RecaptchaPrivate), 29 | new KeyValuePair("response", key), 30 | new KeyValuePair("remoteip", userIp) 31 | }); 32 | var httpClient = new HttpClient(); 33 | var response = await httpClient.PostAsync("https://www.google.com/recaptcha/api/siteverify", content); 34 | 35 | var responseObject = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); 36 | return responseObject.Success; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /oyasumi/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | // In SDK-style projects such as this one, several assembly attributes that were historically 4 | // defined in this file are now automatically added during build and populated with 5 | // values defined in project properties. For details of which attributes are included 6 | // and how to customise this process see: https://aka.ms/assembly-info-properties 7 | 8 | 9 | // Setting ComVisible to false makes the types in this assembly not visible to COM 10 | // components. If you need to access a type in this assembly from COM, set the ComVisible 11 | // attribute to true on that type. 12 | 13 | [assembly: ComVisible(false)] 14 | 15 | // The following GUID is for the ID of the typelib if this project is exposed to COM. 16 | 17 | [assembly: Guid("fab00113-7797-4906-9163-1fd8fd351168")] 18 | 19 | [assembly: Fody.ConfigureAwait(false)] 20 | -------------------------------------------------------------------------------- /oyasumi/Attributes/CommandAttribute.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Enums; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Objects; 8 | using System.Reflection; 9 | 10 | namespace oyasumi.Attributes 11 | { 12 | [AttributeUsage(AttributeTargets.Method)] 13 | public class CommandAttribute : Attribute 14 | { 15 | public string Command; 16 | public string Description; 17 | public int RequiredArgs; 18 | public bool IsPublic; 19 | public Privileges PrivilegesRequired; 20 | public string Filter; 21 | public bool Scheduled; 22 | public string OnArgsPushed; 23 | 24 | public CommandAttribute(string command, string description, bool isPublic, Privileges privileges, int requiredArgs = -1, bool scheduled = false, string filter = null, string onArgsPushed = null) 25 | { 26 | Command = command; 27 | Description = description; 28 | RequiredArgs = requiredArgs; 29 | IsPublic = isPublic; 30 | PrivilegesRequired = privileges; 31 | Filter = filter; 32 | Scheduled = scheduled; 33 | 34 | // Available only on scheduled commands where you can pass multi-line args, so you will able to check argument 35 | // (Really hate that i can't pass Delegate or atleast MethodInfo here) 36 | OnArgsPushed = onArgsPushed; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /oyasumi/Attributes/PacketAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using oyasumi.Enums; 3 | 4 | namespace oyasumi 5 | { 6 | public class PacketAttribute : Attribute 7 | { 8 | public PacketType PacketType; 9 | public PacketAttribute(PacketType t) => 10 | PacketType = t; 11 | }} -------------------------------------------------------------------------------- /oyasumi/Base.cs: -------------------------------------------------------------------------------- 1 | #define NO_LOGGING 2 | 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Reflection; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Internal; 10 | using Microsoft.Extensions.Logging; 11 | using oyasumi.Database.Models; 12 | using oyasumi.Enums; 13 | using oyasumi.Interfaces; 14 | using oyasumi.Objects; 15 | using oyasumi.Utilities; 16 | 17 | namespace oyasumi 18 | { 19 | public class PacketItem 20 | { 21 | public ObjectMethodExecutorCompiledFast Executor { get; set; } 22 | public bool IsDbContextRequired { get; set; } 23 | } 24 | 25 | public class BeatmapItem 26 | { 27 | public Beatmap Beatmap { get; set; } 28 | public bool IsSet { get; set; } 29 | } 30 | 31 | public class CommandItem 32 | { 33 | public string Name { get; set; } 34 | public ObjectMethodExecutorCompiledFast Executor { get; set; } 35 | public bool IsPublic { get; set; } 36 | public int RequiredArgs { get; set; } 37 | public Privileges Privileges { get; set; } 38 | public string Filter { get; set; } 39 | public bool Scheduled { get; set; } 40 | public string OnArgsPushed { get; set; } 41 | } 42 | 43 | public class Base 44 | { 45 | public static Type[] Types; 46 | 47 | public static readonly ConcurrentDictionary MethodCache = new(); 48 | public static readonly ConcurrentDictionary> FriendCache = new(); 49 | public static readonly ConcurrentDictionary PacketImplCache = new(); 50 | public static readonly ConcurrentDictionary> UserStatsCache = new() 51 | { 52 | [LeaderboardMode.Vanilla] = new(), 53 | [LeaderboardMode.Relax] = new() 54 | }; 55 | public static readonly ConcurrentDictionary PasswordCache = new(); 56 | public static readonly MultiKeyDictionary UserCache = new(); 57 | public static readonly ConcurrentDictionary CommandCache = new(); 58 | public static readonly MultiKeyDictionary TokenCache = new(); 59 | 60 | public static readonly ConcurrentQueue BeatmapDbStatusUpdate = new(); 61 | public static readonly ConcurrentQueue UserDbUpdate = new(); 62 | 63 | public static readonly string ConnectionString = $"server=localhost;database={Config.Properties.Database};" + 64 | $"user={Config.Properties.Username};password={Config.Properties.Password};Allow User Variables=true"; 65 | public static void Main(string[] args) 66 | { 67 | Types = Assembly.GetEntryAssembly().GetTypes(); 68 | CreateHostBuilder(args).Build().Run(); 69 | } 70 | 71 | public static IHostBuilder CreateHostBuilder(string[] args) => 72 | Host.CreateDefaultBuilder(args) 73 | #if NO_LOGGING 74 | .ConfigureLogging(logging => 75 | { 76 | logging.ClearProviders(); 77 | }) 78 | #endif 79 | .ConfigureWebHostDefaults(webBuilder => 80 | { 81 | webBuilder.UseStartup(); 82 | }); 83 | } 84 | } -------------------------------------------------------------------------------- /oyasumi/Chat/CommandEvents.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Managers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Objects; 8 | 9 | namespace oyasumi.Chat 10 | { 11 | public class CommandEvents 12 | { 13 | // If you want to break the execution of command 14 | // Just return false in the event method 15 | public delegate Task OnArgsPushed(Presence sender, string channel, int index, string arg); 16 | 17 | public static async Task UBan_OnArgsPushed(Presence sender, string channel, int index, string arg) 18 | { 19 | if (index == 0) 20 | { 21 | var user = Base.UserCache[arg]; 22 | if (user is null) 23 | { 24 | await ChannelManager.BotMessage(sender, channel, "User not found."); 25 | return false; 26 | } 27 | } 28 | return true; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /oyasumi/Chat/Objects/ScheduledCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static oyasumi.Chat.CommandEvents; 7 | 8 | namespace oyasumi.Chat.Objects 9 | { 10 | public class ScheduledCommand 11 | { 12 | public string Name { get; set; } 13 | public string[] Args { get; set; } 14 | public int ArgsRequired { get; set; } 15 | public OnArgsPushed OnArgsPushed { get; set; } 16 | public bool NoErrors { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /oyasumi/Controllers/AvatarController.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Devcorner.NIdenticon; 7 | 8 | namespace oyasumi.Controllers 9 | { 10 | [Route("/{id:int}")] 11 | public class AvatarController : OyasumiController 12 | { 13 | public async Task Index(int id) 14 | { 15 | if (System.IO.File.Exists($"./data/avatars/{id}.png")) 16 | { 17 | var file = await System.IO.File.ReadAllBytesAsync($"./data/avatars/{id}.png"); 18 | return Bytes(file); 19 | } 20 | 21 | var user = Base.UserCache[id]; 22 | 23 | if (user is null) 24 | return StatusCode(404); 25 | 26 | var generator = new IdenticonGenerator(); 27 | await using var ms = new MemoryStream(); 28 | 29 | generator.Create(user.Username, new Size(64, 64)).Save(ms, ImageFormat.Png); 30 | 31 | await System.IO.File.WriteAllBytesAsync($"./data/avatars/{id}.png", ms.ToArray()); 32 | 33 | return Bytes(ms.ToArray()); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /oyasumi/Controllers/DirectController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using oyasumi.Objects; 3 | 4 | namespace oyasumi.Controllers 5 | { 6 | public class DirectController : OyasumiController 7 | { 8 | [HttpGet] 9 | [Route("/d/{id}")] 10 | public IActionResult Index(string id) => 11 | Redirect($"{Config.Properties.BeatmapMirror}/d/{id}"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /oyasumi/Controllers/OyasumiController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace oyasumi.Controllers 4 | { 5 | public class OyasumiController : Controller 6 | { 7 | [NonAction] 8 | public FileContentResult Bytes(byte[] data) 9 | { 10 | return new FileContentResult(data, "application/octet-stream"); 11 | } 12 | 13 | [NonAction] 14 | public FileContentResult NoTokenBytes(byte[] data) 15 | { 16 | Response.Headers["cho-token"] = "no-token"; 17 | 18 | return new FileContentResult(data, "application/octet-stream"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /oyasumi/Controllers/RegistrationController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Newtonsoft.Json; 3 | using oyasumi.Database; 4 | using oyasumi.Database.Models; 5 | using oyasumi.Enums; 6 | using oyasumi.Extensions; 7 | using oyasumi.Interfaces; 8 | using oyasumi.Utilities; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Text; 13 | using System.Text.RegularExpressions; 14 | using System.Threading.Tasks; 15 | 16 | namespace oyasumi.Controllers 17 | { 18 | [Route("/users")] 19 | public class RegistrationController : OyasumiController 20 | { 21 | [HttpPost] 22 | public IActionResult Index() 23 | { 24 | var username = (string)Request.Form["user[username]"]; 25 | var email = (string)Request.Form["user[user_email]"]; 26 | var plainPassword = (string)Request.Form["user[password]"]; // plain text 27 | 28 | var errors = new Dictionary>() 29 | { 30 | ["username"] = new (), 31 | ["user_email"] = new(), 32 | ["password"] = new() 33 | }; 34 | 35 | var users = DbContext.Users.Values; 36 | 37 | if (!Regex.IsMatch(username, @"^[\w \[\]-]{2,15}$")) 38 | errors["username"].Add("Must be 2 - 15 characters in length."); 39 | 40 | if (username.Contains(" ") && username.Contains("_")) 41 | errors["username"].Add("May contain '_' and ' ', but not both."); 42 | 43 | if (users.Any(x => x.Username == username)) 44 | errors["username"].Add("Username already taken by another player.."); 45 | 46 | if (!Regex.IsMatch(email, 47 | @"^[^@\s]+@[^@\s]+\.[^@\s]+$", 48 | RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250))) 49 | errors["email"].Add("Invalid email syntax."); 50 | 51 | if (users.Any(x => x.Email == email)) 52 | errors["email"].Add("Email already taken by another player."); 53 | 54 | var passwordLength = plainPassword.Length; 55 | if (!(passwordLength >= 8 && passwordLength <= 32)) 56 | errors["password"].Add("Must be 8-32 characters in length."); 57 | 58 | var uniqueCharsCount = plainPassword.Distinct().Count(); 59 | if (uniqueCharsCount <= 3) 60 | errors["password"].Add("Must have more than 3 unique characters."); 61 | 62 | 63 | if (errors["username"].Count > 0 || errors["user_email"].Count > 0 || errors["password"].Count > 0) 64 | { 65 | var serializedErrors = JsonConvert.SerializeObject(errors); 66 | var compiledJson = "{'form_error': {'user':" + serializedErrors + "}}"; 67 | 68 | return NetUtils.Content(compiledJson, 422); 69 | } 70 | 71 | if (Request.Form["check"] != "0") 72 | return Ok("<>"); 73 | 74 | var passwordMd5 = Crypto.ComputeHash(plainPassword); 75 | var passwordBcrypt = Crypto.GenerateHash(passwordMd5); 76 | 77 | if (!Base.PasswordCache.TryGetValue(passwordMd5, out var _)) 78 | Base.PasswordCache.TryAdd(passwordMd5, passwordBcrypt); 79 | 80 | var user = new User 81 | { 82 | Id = DbContext.Users.Count + 1, 83 | Username = username, 84 | UsernameSafe = username.ToSafe(), 85 | Password = passwordBcrypt, 86 | Country = "XX", 87 | Privileges = Privileges.Normal 88 | }; 89 | 90 | var vanillaStats = new VanillaStats() 91 | { 92 | Id = user.Id 93 | }; 94 | 95 | var relaxStats = new RelaxStats() 96 | { 97 | Id = user.Id 98 | }; 99 | 100 | DbContext.Users.Add(user.Id, user.Username, user); 101 | 102 | DbContext.VanillaStats.TryAdd(user.Id, vanillaStats); 103 | DbContext.RelaxStats.TryAdd(user.Id, relaxStats); 104 | 105 | var token = new Token 106 | { 107 | UserId = user.Id, 108 | UserToken = Guid.NewGuid().ToString() 109 | }; 110 | 111 | return Ok("<>"); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /oyasumi/Database/Attributes/Table.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Database.Attributes 8 | { 9 | public class Table : Attribute 10 | { 11 | public string Name; 12 | public Table(string name) 13 | { 14 | Name = name; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /oyasumi/Database/DbContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using oyasumi.Database.Models; 4 | using oyasumi.Utilities; 5 | 6 | namespace oyasumi.Database 7 | { 8 | public class DbContext 9 | { 10 | public static ConcurrentDictionary VanillaStats = new(); 11 | public static ConcurrentDictionary RelaxStats = new(); 12 | public static MultiKeyDictionary Users = new(); 13 | public static MultiKeyDictionary Beatmaps = new(); 14 | public static List Channels = new(); 15 | public static List Scores = new(); 16 | public static List Friends = new(); 17 | 18 | public static void Load() 19 | { 20 | DbReader.Load(@"./oyasumi_database.ch"); 21 | } 22 | public static void Save() 23 | { 24 | DbWriter.Save(@"./oyasumi_database.ch"); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /oyasumi/Database/DbWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using oyasumi.Utilities; 9 | using oyasumi.Database.Attributes; 10 | 11 | namespace oyasumi.Database 12 | { 13 | public class DbWriter 14 | { 15 | private static StringBuilder _builder = new StringBuilder(); 16 | 17 | private static Dictionary> Columns = new(); 18 | private static Dictionary Tables = new(); 19 | 20 | public static void Save(string path) 21 | { 22 | _builder.Append("t_oyasumi:"); 23 | 24 | foreach (var type in Assembly.GetEntryAssembly().GetTypes()) 25 | { 26 | var attr = type.GetCustomAttribute(); 27 | if (attr is not null) 28 | { 29 | var propList = new List(); 30 | 31 | foreach (var property in type.GetProperties()) 32 | propList.Add(property); 33 | 34 | Columns.Add(attr.Name, propList); 35 | Tables.Add(attr.Name, type); 36 | } 37 | } 38 | 39 | for (var i = 0; i < Tables.Count; i++) 40 | { 41 | var table = Tables.ElementAt(i); 42 | 43 | _builder.Append($"{table.Key} "); 44 | _builder.Append('['); 45 | 46 | var props = Columns[table.Key]; 47 | 48 | var tableObject = typeof(DbContext).GetField(table.Key)?.GetValue(null); // Get the instance of table's object 49 | 50 | IMultiKeyDictionary tableMultiDict = null; 51 | IDictionary tableDict = null; 52 | IList tableList = null; 53 | 54 | if (tableObject is not null) 55 | { 56 | var interfaceTypes = tableObject.GetType().GetInterfaces(); 57 | if (interfaceTypes.FirstOrDefault() == typeof(IMultiKeyDictionary)) 58 | { 59 | tableMultiDict = (IMultiKeyDictionary)tableObject; 60 | } 61 | else if (interfaceTypes.Contains(typeof(IDictionary))) 62 | { 63 | tableDict = (IDictionary)tableObject; 64 | } 65 | else 66 | { 67 | tableList = (IList)tableObject; 68 | } 69 | } 70 | 71 | if (tableObject is null) 72 | { 73 | Console.WriteLine($"Can't find database list for the {table.Key}."); 74 | _builder.Append(']'); 75 | continue; 76 | } 77 | 78 | var instanceCount = tableDict is not null ? 79 | tableDict.Count : tableMultiDict is not null ? 80 | tableMultiDict.Count : tableList.Count; 81 | 82 | IEnumerable tableDictValues = null; 83 | 84 | if (tableDict is not null) 85 | tableDictValues = tableDict.Values.Cast(); 86 | 87 | for (var j = 0; j < instanceCount; j++) 88 | { 89 | object instance = null; 90 | 91 | instance = tableDict is not null ? tableDictValues.ElementAt(j) : tableMultiDict is not null ? tableMultiDict.ValueAt(j) : tableList[j]; 92 | 93 | for (var k = 0; k < props.Count; k++) 94 | { 95 | var prop = props[k]; 96 | 97 | _builder.Append($"{prop.PropertyType.FullName}\a {prop.GetValue(instance)}"); 98 | 99 | if (k != props.Count - 1) 100 | _builder.Append("\x01"); 101 | } 102 | 103 | if (j != instanceCount - 1) 104 | _builder.Append(" \x02 "); 105 | } 106 | 107 | _builder.Append(']'); 108 | if (i != Tables.Count - 1) 109 | _builder.Append('\x03'); 110 | } 111 | 112 | File.WriteAllText(path, _builder.ToString()); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /oyasumi/Database/Tables/DbBeatmap.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Database.Attributes; 2 | using oyasumi.Enums; 3 | 4 | namespace oyasumi.Database.Models 5 | { 6 | [Table("Beatmaps")] 7 | public class DbBeatmap 8 | { 9 | public int BeatmapId { get; set; } 10 | public string BeatmapMd5 { get; set; } 11 | public int Id { get; set; } 12 | public string FileName { get; set; } 13 | public int BeatmapSetId { get; set; } 14 | public RankedStatus Status { get; set; } 15 | public bool Frozen { get; set; } 16 | public int PlayCount { get; set; } 17 | public int PassCount { get; set; } 18 | public string Artist { get; set; } 19 | public string Title { get; set; } 20 | public string DifficultyName { get; set; } 21 | public string Creator { get; set; } 22 | public float BPM { get; set; } 23 | public float CircleSize { get; set; } 24 | public float OverallDifficulty { get; set; } 25 | public float ApproachRate { get; set; } 26 | public float HPDrainRate { get; set; } 27 | public float Stars { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /oyasumi/Database/Tables/DbChannel.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Database.Attributes; 2 | 3 | namespace oyasumi.Database.Models 4 | { 5 | [Table("Channels")] 6 | public class DbChannel 7 | { 8 | public int Id { get; set; } 9 | public string Name { get; set; } 10 | public string Topic { get; set; } 11 | public bool Public { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /oyasumi/Database/Tables/Friend.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Database.Attributes; 2 | 3 | namespace oyasumi.Database.Models 4 | { 5 | [Table("Friends")] 6 | public class Friend 7 | { 8 | public int Id { get; set; } 9 | public int Friend1 { get; set; } 10 | public int Friend2 { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /oyasumi/Database/Tables/Score.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Enums; 2 | using oyasumi.Database.Attributes; 3 | using System; 4 | 5 | namespace oyasumi.Database.Models 6 | { 7 | [Table("Scores")] 8 | public class DbScore 9 | { 10 | public int Id { get; set; } 11 | public string ReplayChecksum { get; set; } 12 | public string FileChecksum { get; set; } 13 | public int Count100 { get; set; } 14 | public int Count300 { get; set; } 15 | public int Count50 { get; set; } 16 | public int CountGeki { get; set; } 17 | public int CountKatu { get; set; } 18 | public int CountMiss { get; set; } 19 | public int TotalScore { get; set; } 20 | public double Accuracy { get; set; } 21 | public int MaxCombo { get; set; } 22 | public bool Passed { get; set; } 23 | public Mods Mods { get; set; } 24 | public PlayMode PlayMode { get; set; } 25 | public BadFlags Flags { get; set; } 26 | public int OsuVersion { get; set; } 27 | public bool Perfect { get; set; } 28 | public int UserId { get; set; } 29 | public DateTime Date { get; set; } 30 | public bool Relaxing { get; set; } 31 | public bool AutoPiloting { get; set; } 32 | public double PerformancePoints { get; set; } 33 | public CompletedStatus Completed { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /oyasumi/Database/Tables/Token.cs: -------------------------------------------------------------------------------- 1 | namespace oyasumi.Database.Models 2 | { 3 | public class Token 4 | { 5 | public int Id { get; set; } 6 | public string UserToken { get; set; } 7 | public int UserId { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /oyasumi/Database/Tables/User.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Enums; 2 | using oyasumi.Database.Attributes; 3 | using System; 4 | 5 | namespace oyasumi.Database.Models 6 | { 7 | [Table("Users")] 8 | public class User 9 | { 10 | public int Id { get; set; } 11 | public string Username { get; set; } 12 | public Privileges Privileges { get; set; } 13 | public DateTimeOffset JoinDate { get; set; } = DateTime.UtcNow; 14 | public string UsernameAka { get; set; } 15 | public string Password { get; set; } 16 | public string Email { get; set; } 17 | public string UsernameSafe { get; set; } 18 | public string Country { get; set; } 19 | public string UserpageContent { get; set; } 20 | public bool PreferNightcore { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /oyasumi/Database/Tables/UserStats.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Database.Attributes; 2 | using oyasumi.Interfaces; 3 | 4 | namespace oyasumi.Database.Models 5 | { 6 | [Table("VanillaStats")] 7 | public class VanillaStats : IStats 8 | { 9 | public int Id { get; set; } 10 | public bool IsPublic { get; set; } 11 | public long TotalScoreOsu { get; set; } = 0; 12 | public long TotalScoreTaiko { get; set; } = 0; 13 | public long TotalScoreCtb { get; set; } = 0; 14 | public long TotalScoreMania { get; set; } = 0; 15 | 16 | public long RankedScoreOsu { get; set; } = 0; 17 | public long RankedScoreTaiko { get; set; } = 0; 18 | public long RankedScoreCtb { get; set; } = 0; 19 | public long RankedScoreMania { get; set; } = 0; 20 | 21 | public int PerformanceOsu { get; set; } = 0; 22 | public int PerformanceTaiko { get; set; } = 0; 23 | public int PerformanceCtb { get; set; } = 0; 24 | public int PerformanceMania { get; set; } = 0; 25 | 26 | public float AccuracyOsu { get; set; } = 0; 27 | public float AccuracyTaiko { get; set; } = 0; 28 | public float AccuracyCtb { get; set; } = 0; 29 | public float AccuracyMania { get; set; } = 0; 30 | 31 | public int PlaycountOsu { get; set; } = 0; 32 | public int PlaycountTaiko { get; set; } = 0; 33 | public int PlaycountCtb { get; set; } = 0; 34 | public int PlaycountMania { get; set; } = 0; 35 | 36 | public int RankOsu { get; set; } = 0; 37 | public int RankTaiko { get; set; } = 0; 38 | public int RankCtb { get; set; } = 0; 39 | public int RankMania { get; set; } = 0; 40 | } 41 | 42 | [Table("RelaxStats")] 43 | public class RelaxStats : IStats 44 | { 45 | public int Id { get; set; } 46 | 47 | public bool IsPublic { get; set; } 48 | public long TotalScoreOsu { get; set; } = 0; 49 | public long TotalScoreTaiko { get; set; } = 0; 50 | public long TotalScoreCtb { get; set; } = 0; 51 | public long TotalScoreMania { get; set; } = 0; 52 | 53 | public long RankedScoreOsu { get; set; } = 0; 54 | public long RankedScoreTaiko { get; set; } = 0; 55 | public long RankedScoreCtb { get; set; } = 0; 56 | public long RankedScoreMania { get; set; } = 0; 57 | 58 | public int PerformanceOsu { get; set; } = 0; 59 | public int PerformanceTaiko { get; set; } = 0; 60 | public int PerformanceCtb { get; set; } = 0; 61 | public int PerformanceMania { get; set; } = 0; 62 | 63 | public float AccuracyOsu { get; set; } = 0; 64 | public float AccuracyTaiko { get; set; } = 0; 65 | public float AccuracyCtb { get; set; } = 0; 66 | public float AccuracyMania { get; set; } = 0; 67 | 68 | public int PlaycountOsu { get; set; } = 0; 69 | public int PlaycountTaiko { get; set; } = 0; 70 | public int PlaycountCtb { get; set; } = 0; 71 | public int PlaycountMania { get; set; } = 0; 72 | 73 | public int RankOsu { get; set; } = 0; 74 | public int RankTaiko { get; set; } = 0; 75 | public int RankCtb { get; set; } = 0; 76 | public int RankMania { get; set; } = 0; 77 | } 78 | } -------------------------------------------------------------------------------- /oyasumi/Enums/ActionStatuses.cs: -------------------------------------------------------------------------------- 1 | namespace oyasumi.Enums 2 | { 3 | public enum ActionStatuses 4 | { 5 | Idle, 6 | Afk, 7 | Playing, 8 | Editing, 9 | Modding, 10 | Multiplayer, 11 | Watching, 12 | Unknown, 13 | Testing, 14 | Submitting, 15 | Paused, 16 | Lobby, 17 | Multiplaying, 18 | OsuDirect 19 | }; 20 | } -------------------------------------------------------------------------------- /oyasumi/Enums/BadFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Enums 8 | { 9 | [Flags] 10 | public enum BadFlags 11 | { 12 | Clean = 0, 13 | SpeedHackDetected = 1 << 1, 14 | IncorrectModValue = 1 << 2, 15 | MultipleOsuClients = 1 << 3, 16 | ChecksumFailure = 1 << 4, 17 | FlashlightChecksumIncorrect = 1 << 5, 18 | OsuExecutableChecksum = 1 << 6, //server-side 19 | MissingProcessesInList = 1 << 7, //server-side 20 | FlashLightImageHack = 1 << 8, 21 | SpinnerHack = 1 << 9, 22 | TransparentWindow = 1 << 10, 23 | FastPress = 1 << 11, 24 | RawMouseDiscrepancy = 1 << 12, 25 | RawKeyboardDiscrepancy = 1 << 13, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /oyasumi/Enums/BanchoPermissions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace oyasumi.Enums 4 | { 5 | [Flags] 6 | public enum BanchoPermissions 7 | { 8 | None = 0, 9 | Normal = 1, 10 | BAT = 2, 11 | Supporter = 4, 12 | Moderator = 8, 13 | Peppy = 16, 14 | Tournament = 32 15 | } 16 | } -------------------------------------------------------------------------------- /oyasumi/Enums/CompletedStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Enums 8 | { 9 | public enum CompletedStatus 10 | { 11 | Failed, 12 | Submitted, 13 | Best 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /oyasumi/Enums/LeaderboardMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Enums 8 | { 9 | public enum LeaderboardMode 10 | { 11 | Vanilla, 12 | Relax, 13 | Autopilot 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /oyasumi/Enums/LoginReplies.cs: -------------------------------------------------------------------------------- 1 | namespace oyasumi.Enums 2 | { 3 | public enum LoginReplies 4 | { 5 | RequireVerification = -8, 6 | PasswordReset = -7, 7 | TestBuildNoSupporter = -6, 8 | ServerSideError = -5, 9 | BannedError = -4, 10 | BannedError2 = -3, // same as -4 11 | OldVersion = -2, 12 | WrongCredentials = -1 13 | } 14 | } -------------------------------------------------------------------------------- /oyasumi/Enums/MatchScoringTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Enums 8 | { 9 | public enum MatchScoringTypes 10 | { 11 | Score = 0, 12 | Accuracy = 1, 13 | Combo = 2, 14 | ScoreV2 = 3, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /oyasumi/Enums/MatchSpecialModes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Enums 8 | { 9 | [Flags] 10 | public enum MultiSpecialModes 11 | { 12 | None = 0, 13 | FreeMod = 1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /oyasumi/Enums/MatchTeamTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Enums 8 | { 9 | public enum MatchTeamTypes 10 | { 11 | HeadToHead = 0, 12 | TagCoop = 1, 13 | TeamVs = 2, 14 | TagTeamVs = 3 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /oyasumi/Enums/MatchTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Enums 8 | { 9 | public enum MatchTypes 10 | { 11 | Standard = 0, 12 | Powerplay = 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /oyasumi/Enums/Mods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace oyasumi.Enums 4 | { 5 | [Flags] 6 | public enum Mods 7 | { 8 | None = 0, 9 | NoFail = 1 << 0, 10 | Easy = 1 << 1, 11 | TouchDevice = 1 << 2, 12 | Hidden = 1 << 3, 13 | HardRock = 1 << 4, 14 | SuddenDeath = 1 << 5, 15 | DoubleTime = 1 << 6, 16 | Relax = 1 << 7, 17 | HalfTime = 1 << 8, 18 | Nightcore = 1 << 9, 19 | Flashlight = 1 << 10, 20 | Autoplay = 1 << 11, 21 | SpunOut = 1 << 12, 22 | Relax2 = 1 << 13, 23 | Perfect = 1 << 14, 24 | Key4 = 1 << 15, 25 | Key5 = 1 << 16, 26 | Key6 = 1 << 17, 27 | Key7 = 1 << 18, 28 | Key8 = 1 << 19, 29 | FadeIn = 1 << 20, 30 | Random = 1 << 21, 31 | Cinema = 1 << 22, 32 | Target = 1 << 23, 33 | Key9 = 1 << 24, 34 | KeyCoop = 1 << 25, 35 | Key1 = 1 << 26, 36 | Key3 = 1 << 27, 37 | Key2 = 1 << 28, 38 | LastMod = 1 << 29, 39 | KeyMod = Key1 | Key2 | Key3 | Key4 | Key5 | Key6 | Key7 | Key8 | Key9 | KeyCoop, 40 | 41 | FreeModAllowed = Hidden | HardRock | DoubleTime | Flashlight | FadeIn | Easy | Relax | Relax2 | SpunOut | 42 | NoFail | Easy | HalfTime | Autoplay | KeyMod, 43 | 44 | ScoreIncreaseMods = Hidden | HardRock | DoubleTime | Flashlight | FadeIn | Easy | Relax | Relax2 | SpunOut | 45 | NoFail | Easy | HalfTime | Autoplay | SuddenDeath | Perfect | KeyMod | Target | Random | 46 | Nightcore | LastMod, 47 | 48 | SpeedAltering = DoubleTime | Nightcore | HalfTime 49 | } 50 | } -------------------------------------------------------------------------------- /oyasumi/Enums/PlayMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace oyasumi.Enums 4 | { 5 | public enum PlayMode 6 | { 7 | Osu = 0, 8 | Taiko = 1, 9 | CatchTheBeat = 2, 10 | OsuMania = 3 11 | } 12 | } -------------------------------------------------------------------------------- /oyasumi/Enums/Privileges.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Enums 8 | { 9 | [Flags] 10 | public enum Privileges 11 | { 12 | Banned = 0, 13 | Restricted = 1 << 1, 14 | Normal = 1 << 2, 15 | Verified = 1 << 3, 16 | ManageBeatmaps = 1 << 4, 17 | ManageUsers = 1 << 5 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /oyasumi/Enums/RankedStatus.cs: -------------------------------------------------------------------------------- 1 | namespace oyasumi.Enums 2 | { 3 | public enum RankedStatus : sbyte 4 | { 5 | Unknown = -2, 6 | NotSubmitted = -1, 7 | LatestPending = 0, 8 | NeedUpdate = 1, 9 | Ranked = 2, 10 | Approved = 3, 11 | Qualified = 4, 12 | Loved = 5 13 | } 14 | 15 | public enum APIRankedStatus : sbyte 16 | { 17 | Graveyard = -2, 18 | WorkInProgress = -1, 19 | LatestPending = 0, 20 | Ranked = 1, 21 | Approved = 2, 22 | Qualified = 3, 23 | Loved = 4 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /oyasumi/Enums/RankingType.cs: -------------------------------------------------------------------------------- 1 | namespace oyasumi.Enums 2 | { 3 | public enum RankingType 4 | { 5 | Local, 6 | Top, 7 | SelectedMod, 8 | Friends, 9 | Country 10 | } 11 | } -------------------------------------------------------------------------------- /oyasumi/Enums/SlotStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Enums 8 | { 9 | [Flags] 10 | public enum SlotStatus 11 | { 12 | Open = 1, 13 | Locked = 2, 14 | NotReady = 4, 15 | Ready = 8, 16 | NoMap = 16, 17 | Playing = 32, 18 | Complete = 64, 19 | HasPlayer = NotReady | Ready | NoMap | Playing | Complete, 20 | Quit = 128 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /oyasumi/Enums/SlotTeams.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Enums 8 | { 9 | public enum SlotTeams 10 | { 11 | Neutral, 12 | Blue, 13 | Red 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /oyasumi/Events/ChatChannelJoin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class ChatChannelJoin 16 | { 17 | [Packet(PacketType.ClientChatChannelJoin)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var ms = new MemoryStream(p.Data); 21 | using var reader = new SerializationReader(ms); 22 | 23 | var channel = reader.ReadString(); 24 | 25 | await pr.JoinChannel(channel); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /oyasumi/Events/ChatChannelLeave.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class ChatChannelLeave 16 | { 17 | [Packet(PacketType.ClientChatChannelLeave)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var ms = new MemoryStream(p.Data); 21 | using var reader = new SerializationReader(ms); 22 | 23 | var channel = reader.ReadString(); 24 | 25 | await pr.LeaveChannel(channel); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /oyasumi/Events/ChatMessagePrivate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.IO; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Layouts; 11 | using oyasumi.Managers; 12 | using oyasumi.Objects; 13 | 14 | 15 | namespace oyasumi.Events 16 | { 17 | public class ChatMessagePrivate 18 | { 19 | [Packet(PacketType.ClientChatMessagePrivate)] 20 | public static async Task Handle(Packet p, Presence pr) 21 | { 22 | var ms = new MemoryStream(p.Data); 23 | using var reader = new SerializationReader(ms); 24 | 25 | var client = reader.ReadString(); 26 | var message = reader.ReadString(); 27 | var target = reader.ReadString(); 28 | 29 | await ChannelManager.SendMessage(pr, message, target, false) 30 | ; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /oyasumi/Events/ChatMessagePublic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Layouts; 11 | using oyasumi.Managers; 12 | using oyasumi.Objects; 13 | 14 | namespace oyasumi.Events 15 | { 16 | public class ChatMessagePublic 17 | { 18 | [Packet(PacketType.ClientChatMessagePublic)] 19 | public static async Task Handle(Packet p, Presence pr) 20 | { 21 | var ms = new MemoryStream(p.Data); 22 | using var reader = new SerializationReader(ms); 23 | 24 | var client = reader.ReadString(); 25 | var message = reader.ReadString(); 26 | var target = reader.ReadString(); 27 | 28 | if (target == "#spectator" && pr.Spectating is not null) 29 | target = $"spect_{pr.Spectating.Id}"; 30 | else if (target == "#spectator" && pr.Spectators is not null) 31 | target = $"spect_{pr.Id}"; 32 | else if (target == "#multiplayer" && pr.CurrentMatch is not null) 33 | target = $"multi_{pr.CurrentMatch.Id}"; 34 | 35 | await ChannelManager.SendMessage(pr, message, target, true) 36 | ; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /oyasumi/Events/Disconnect.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Enums; 2 | using oyasumi.Managers; 3 | using oyasumi.Objects; 4 | using oyasumi.Utilities; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Events 8 | { 9 | public class Disconnect 10 | { 11 | [Packet(PacketType.ClientDisconnect)] 12 | public static async Task Handle(Packet p, Presence pr) 13 | { 14 | if (Time.CurrentUnixTimestamp - pr.LoginTime < 2) 15 | return; 16 | 17 | await PresenceManager.Remove(pr); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /oyasumi/Events/FriendAdd.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Database; 9 | using oyasumi.Database.Models; 10 | using oyasumi.Enums; 11 | using oyasumi.IO; 12 | using oyasumi.Layouts; 13 | using oyasumi.Managers; 14 | using oyasumi.Objects; 15 | 16 | namespace oyasumi.Events 17 | { 18 | public class FriendAdd 19 | { 20 | [Packet(PacketType.ClientFriendsAdd)] 21 | public static void Handle(Packet p, Presence pr) 22 | { 23 | var ms = new MemoryStream(p.Data); 24 | using var reader = new SerializationReader(ms); 25 | 26 | var id = reader.ReadInt32(); 27 | 28 | var friend = PresenceManager.GetPresenceById(id); 29 | 30 | if (friend is null) 31 | return; 32 | 33 | var exists = DbContext.Friends.FirstOrDefault(x => x.Friend2 == id); 34 | 35 | if (exists is not null) 36 | return; 37 | 38 | DbContext.Friends.Add(new () 39 | { 40 | Friend1 = pr.Id, 41 | Friend2 = id 42 | }); 43 | 44 | Base.FriendCache[pr.Id].Add(friend.Id); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /oyasumi/Events/FriendRemove.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Database; 9 | using oyasumi.Database.Models; 10 | using oyasumi.Enums; 11 | using oyasumi.IO; 12 | using oyasumi.Layouts; 13 | using oyasumi.Managers; 14 | using oyasumi.Objects; 15 | 16 | namespace oyasumi.Events 17 | { 18 | public class FriendRemove 19 | { 20 | [Packet(PacketType.ClientFriendsRemove)] 21 | public static void Handle(Packet p, Presence pr) 22 | { 23 | var ms = new MemoryStream(p.Data); 24 | using var reader = new SerializationReader(ms); 25 | 26 | var id = reader.ReadInt32(); 27 | 28 | var friend = DbContext.Friends.FirstOrDefault(x => x.Friend2 == id); 29 | 30 | if (friend is null) 31 | return; 32 | 33 | DbContext.Friends.Remove(friend); 34 | 35 | Base.FriendCache.FirstOrDefault(x => x.Key == friend.Friend1).Value.Remove(friend.Friend2); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /oyasumi/Events/LobbyJoin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class LobbyJoin 16 | { 17 | [Packet(PacketType.ClientLobbyJoin)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | foreach (var (_, match) in MatchManager.Matches) 21 | { 22 | if (match.PasswordRequired) 23 | match.GamePassword = " "; 24 | await pr.NewMatch(match); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchChangeOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Org.BouncyCastle.Math.EC; 8 | using osu.Game.IO.Legacy; 9 | using oyasumi.Database; 10 | using oyasumi.Enums; 11 | using oyasumi.Extensions; 12 | using oyasumi.IO; 13 | using oyasumi.Layouts; 14 | using oyasumi.Managers; 15 | using oyasumi.Objects; 16 | 17 | namespace oyasumi.Events 18 | { 19 | public class MatchChangeOption 20 | { 21 | [Packet(PacketType.ClientMultiSettingsChange)] 22 | public static async Task Handle(Packet p, Presence pr) 23 | { 24 | var reader = new SerializationReader(new MemoryStream(p.Data)); 25 | var newMatch = await reader.ReadMatch(); 26 | 27 | var match = pr.CurrentMatch; 28 | 29 | if (match is null) 30 | return; 31 | 32 | 33 | if (match.BeatmapChecksum != newMatch.BeatmapChecksum || 34 | match.PlayMode != newMatch.PlayMode || 35 | match.Type != newMatch.Type || 36 | match.ScoringType != newMatch.ScoringType || 37 | match.TeamType != newMatch.TeamType) 38 | { 39 | match.Unready(SlotStatus.Ready); 40 | } 41 | 42 | match.Beatmap = newMatch.Beatmap; 43 | match.BeatmapChecksum = newMatch.BeatmapChecksum; 44 | match.BeatmapId = newMatch.BeatmapId; 45 | match.SpecialModes = newMatch.SpecialModes; 46 | match.GameName = newMatch.GameName.Length > 0 ? newMatch.GameName : $"{match.Host.Username}'s game"; 47 | 48 | if (match.TeamType != newMatch.TeamType) 49 | { 50 | if (match.TeamType == MatchTeamTypes.TagTeamVs || match.TeamType == MatchTeamTypes.TeamVs) 51 | { 52 | var i = 0; 53 | foreach (var slot in match.Slots) 54 | { 55 | if (slot.Team == SlotTeams.Neutral) 56 | slot.Team = i % 2 == 1 ? SlotTeams.Red : SlotTeams.Blue; 57 | i++; 58 | } 59 | } 60 | else 61 | { 62 | foreach (var slot in match.Slots) 63 | slot.Team = SlotTeams.Neutral; 64 | } 65 | } 66 | 67 | match.Type = newMatch.Type; 68 | match.ScoringType = newMatch.ScoringType; 69 | match.TeamType = newMatch.TeamType; 70 | match.PlayMode = newMatch.PlayMode; 71 | match.Seed = newMatch.Seed; 72 | 73 | if (match.TeamType == MatchTeamTypes.TagCoop) 74 | match.SpecialModes &= ~MultiSpecialModes.FreeMod; 75 | 76 | if (newMatch.FreeMods != match.FreeMods) 77 | { 78 | if (newMatch.FreeMods) 79 | { 80 | foreach (var slot in match.Slots) 81 | { 82 | if ((slot.Status & SlotStatus.HasPlayer) > 0) 83 | slot.Mods = match.ActiveMods & ~Mods.SpeedAltering; 84 | } 85 | match.ActiveMods &= Mods.SpeedAltering; 86 | } 87 | else 88 | { 89 | foreach (var slot in match.Slots) 90 | { 91 | if (slot.Presence is not null && slot.Presence.Id == match.Host.Id) 92 | { 93 | match.ActiveMods = slot.Mods | (match.ActiveMods & Mods.SpeedAltering); 94 | break; 95 | } 96 | } 97 | } 98 | } 99 | 100 | match.FreeMods = newMatch.FreeMods; 101 | 102 | foreach (var presence in match.Presences) 103 | await presence.MatchUpdate(match); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchChangePassword.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Database; 9 | using oyasumi.Enums; 10 | using oyasumi.Extensions; 11 | using oyasumi.IO; 12 | using oyasumi.Layouts; 13 | using oyasumi.Managers; 14 | using oyasumi.Objects; 15 | 16 | namespace oyasumi.Events 17 | { 18 | public class MatchChangePassword 19 | { 20 | [Packet(PacketType.ClientMultiChangePassword)] 21 | public static async Task Handle(Packet p, Presence pr) 22 | { 23 | var match = pr.CurrentMatch; 24 | 25 | if (match is null) 26 | return; 27 | 28 | var reader = new SerializationReader(new MemoryStream(p.Data)); 29 | 30 | var newMatch = await reader.ReadMatch(); 31 | 32 | if (string.IsNullOrEmpty(newMatch.GamePassword)) 33 | match.GamePassword = null; 34 | 35 | match.GamePassword = newMatch.GamePassword; 36 | 37 | foreach (var presence in match.Presences) 38 | await presence.MatchUpdate(match); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchChangeTeam.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class MatchChangeTeam 16 | { 17 | [Packet(PacketType.ClientMultiChangeTeam)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var match = pr.CurrentMatch; 21 | 22 | if (match is null) 23 | return; 24 | 25 | var slot = match.Slots.FirstOrDefault(x => x.Presence == pr); 26 | slot.Team = slot.Team == SlotTeams.Red ? SlotTeams.Blue : SlotTeams.Red; 27 | 28 | foreach (var presence in match.Presences) 29 | await presence.MatchUpdate(match); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchComplete.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Org.BouncyCastle.Math.EC.Rfc7748; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Layouts; 11 | using oyasumi.Managers; 12 | using oyasumi.Objects; 13 | 14 | namespace oyasumi.Events 15 | { 16 | public class MatchComplete 17 | { 18 | [Packet(PacketType.ClientMultiMatchCompleted)] 19 | public static async Task Handle(Packet p, Presence pr) 20 | { 21 | var match = pr.CurrentMatch; 22 | 23 | if (match is null) 24 | return; 25 | 26 | match.Slots.FirstOrDefault(x => x.Presence == pr).Status = SlotStatus.Complete; 27 | 28 | if (match.Slots.Any(x => x.Status == SlotStatus.Playing)) 29 | return; 30 | 31 | match.Unready(SlotStatus.Complete); 32 | 33 | match.InProgress = false; 34 | 35 | foreach (var presence in match.Presences) 36 | { 37 | presence.PacketEnqueue(new() 38 | { 39 | Type = PacketType.ServerMultiMatchFinished 40 | }); 41 | await presence.MatchUpdate(match); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchCreate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Layouts; 11 | using oyasumi.Managers; 12 | using oyasumi.Objects; 13 | using oyasumi.Database; 14 | using oyasumi.Extensions; 15 | 16 | namespace oyasumi.Events 17 | { 18 | public class MatchCreate 19 | { 20 | [Packet(PacketType.ClientMultiMatchCreate)] 21 | public static async Task Handle(Packet p, Presence pr) 22 | { 23 | var reader = new SerializationReader(new MemoryStream(p.Data)); 24 | var match = await reader.ReadMatch(); 25 | 26 | if (string.IsNullOrEmpty(match.GamePassword)) 27 | match.GamePassword = null; 28 | 29 | match.Host = pr; 30 | 31 | await pr.JoinMatch(match, match.GamePassword); 32 | 33 | var channel = new Channel($"multi_{match.Id}", "", 1, true); 34 | match.Channel = channel; 35 | ChannelManager.Channels.TryAdd(channel.RawName, channel); 36 | 37 | await pr.JoinChannel($"multi_{match.Id}"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchFailed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class MatchFailed 16 | { 17 | [Packet(PacketType.ClientMultiFailed)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var match = pr.CurrentMatch; 21 | 22 | if (match is null) 23 | return; 24 | 25 | var slotId = -1; 26 | for (var i = 0; i < Match.MAX_PLAYERS; i++) 27 | { 28 | if (match.Slots[i].Presence == pr) 29 | { 30 | slotId = i; 31 | break; 32 | } 33 | } 34 | 35 | foreach (var presence in match.Presences) 36 | await presence.MatchPlayerFailed(slotId); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchHasBeatmap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class MatchHasBeatmap 16 | { 17 | [Packet(PacketType.ClientMultiBeatmapAvailable)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var match = pr.CurrentMatch; 21 | 22 | if (match is null) 23 | return; 24 | 25 | var slot = match.Slots.FirstOrDefault(x => x.Presence == pr); 26 | 27 | slot.Status = SlotStatus.NotReady; 28 | 29 | foreach (var presence in match.Presences) 30 | await presence.MatchUpdate(match); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchInvite.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Layouts; 11 | using oyasumi.Managers; 12 | using oyasumi.Objects; 13 | 14 | namespace oyasumi.Events 15 | { 16 | public class MatchInvite 17 | { 18 | [Packet(PacketType.ClientMultiInvite)] 19 | public static async Task Handle(Packet p, Presence pr) 20 | { 21 | var match = pr.CurrentMatch; 22 | 23 | if (match is null) 24 | return; 25 | 26 | var reader = new SerializationReader(new MemoryStream(p.Data)); 27 | 28 | var target = PresenceManager.GetPresenceById(reader.ReadInt32()); 29 | 30 | if (target is null) 31 | return; 32 | 33 | await target.MatchInvite(pr, $"Come join to my game: [osump://{match.Id}/{match.GamePassword} {match.GameName}]") 34 | ; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchJoin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Layouts; 11 | using oyasumi.Managers; 12 | using oyasumi.Objects; 13 | 14 | namespace oyasumi.Events 15 | { 16 | public class MatchJoin 17 | { 18 | [Packet(PacketType.ClientMultiMatchJoin)] 19 | public static async Task Handle(Packet p, Presence pr) 20 | { 21 | var reader = new SerializationReader(new MemoryStream(p.Data)); 22 | var matchId = reader.ReadInt32(); 23 | var password = reader.ReadString(); 24 | 25 | if (MatchManager.Matches.TryGetValue(matchId, out var match)) 26 | { 27 | await pr.JoinMatch(match, password); 28 | 29 | if (pr.CurrentMatch is not null) 30 | await pr.JoinChannel($"multi_{match.Id}"); 31 | } 32 | else 33 | pr.MatchJoinFail(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchLeave.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class MatchLeave 16 | { 17 | [Packet(PacketType.ClientMultiMatchLeave)] 18 | public static async Task Handle(Packet p, Presence pr) => 19 | await pr.LeaveMatch(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchLoadComplete.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class MatchLoadComplete 16 | { 17 | [Packet(PacketType.ClientMultiMatchLoadComplete)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var match = pr.CurrentMatch; 21 | 22 | if (match is null) 23 | return; 24 | 25 | if (--match.NeedLoad == 0) 26 | { 27 | foreach (var presence in match.Presences) 28 | presence.AllPlayersLoaded(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchNoBeatmap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class MatchNoBeatmap 16 | { 17 | [Packet(PacketType.ClientMultiBeatmapMissing)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var match = pr.CurrentMatch; 21 | 22 | if (match is null) 23 | return; 24 | 25 | var slot = match.Slots.FirstOrDefault(x => x.Presence == pr); 26 | 27 | slot.Status = SlotStatus.NoMap; 28 | 29 | foreach (var presence in match.Presences) 30 | await presence.MatchUpdate(match); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchReady.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class MatchReady 16 | { 17 | [Packet(PacketType.ClientMultiReady)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var match = pr.CurrentMatch; 21 | 22 | if (match is null) 23 | return; 24 | 25 | var slot = match.Slots.FirstOrDefault(x => x.Presence == pr); 26 | 27 | slot.Status = SlotStatus.Ready; 28 | 29 | foreach (var presence in match.Presences) 30 | await presence.MatchUpdate(match); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchScoreUpdate.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Enums; 2 | using oyasumi.IO; 3 | using oyasumi.Managers; 4 | using oyasumi.Objects; 5 | using oyasumi.Utilities; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | 9 | namespace oyasumi.Events 10 | { 11 | public class MatchScoreUpdate 12 | { 13 | [Packet(PacketType.ClientMultiScoreUpdate)] 14 | public static void Handle(Packet p, Presence pr) 15 | { 16 | var match = pr.CurrentMatch; 17 | 18 | if (match is null) 19 | return; 20 | 21 | var slotIndex = -1; 22 | 23 | for (var i = 0; i < Match.MAX_PLAYERS; i++) 24 | { 25 | if (match.Slots[i].Presence == pr) 26 | { 27 | slotIndex = i; 28 | break; 29 | } 30 | } 31 | 32 | p.Data[4] = (byte)slotIndex; 33 | 34 | foreach (var presence in pr.CurrentMatch.Presences) 35 | { 36 | presence.PacketEnqueue(new () 37 | { 38 | Type = PacketType.ServerMultiScoreUpdate, 39 | Data = p.Data 40 | }); 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /oyasumi/Events/MatchSkip.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class MatchSkip 16 | { 17 | [Packet(PacketType.ClientMultiSkipRequest)] 18 | public static void Handle(Packet p, Presence pr) 19 | { 20 | var match = pr.CurrentMatch; 21 | 22 | if (match is null) 23 | return; 24 | 25 | var prSlot = match.Slots.FirstOrDefault(x => x.Presence == pr); 26 | prSlot.Skipped = true; 27 | 28 | if (match.Slots.Any(slot => slot.Status == SlotStatus.Playing && !slot.Skipped)) 29 | return; 30 | 31 | foreach (var presence in match.Presences) 32 | { 33 | presence.PacketEnqueue(new() 34 | { 35 | Type = PacketType.ServerMultiSkip 36 | }); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchSlotChange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Layouts; 11 | using oyasumi.Managers; 12 | using oyasumi.Objects; 13 | 14 | namespace oyasumi.Events 15 | { 16 | public class MatchSlotChange 17 | { 18 | [Packet(PacketType.ClientMultiSlotChange)] 19 | public static async Task Handle(Packet p, Presence pr) 20 | { 21 | var match = pr.CurrentMatch; 22 | 23 | if (match is null) 24 | return; 25 | 26 | var reader = new SerializationReader(new MemoryStream(p.Data)); 27 | var slotIndex = reader.ReadInt32(); 28 | 29 | if (match.InProgress || slotIndex > Match.MAX_PLAYERS - 1 || slotIndex < 0) 30 | return; 31 | 32 | var slot = match.Slots[slotIndex]; 33 | 34 | if ((slot.Status & SlotStatus.HasPlayer) > 0 || slot.Status == SlotStatus.Locked) 35 | return; 36 | 37 | var currentSlot = match.Slots.FirstOrDefault(x => x.Presence == pr); 38 | 39 | slot.CopyFrom(currentSlot); 40 | currentSlot.Clear(); 41 | 42 | foreach (var presence in match.Presences) 43 | await presence.MatchUpdate(match); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchSlotLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Layouts; 11 | using oyasumi.Managers; 12 | using oyasumi.Objects; 13 | 14 | namespace oyasumi.Events 15 | { 16 | public class MatchSlotLock 17 | { 18 | [Packet(PacketType.ClientMultiSlotLock)] 19 | public static async Task Handle(Packet p, Presence pr) 20 | { 21 | var match = pr.CurrentMatch; 22 | 23 | if (match is null) 24 | return; 25 | 26 | var reader = new SerializationReader(new MemoryStream(p.Data)); 27 | var slotIndex = reader.ReadInt32(); 28 | 29 | if (match.InProgress || slotIndex > Match.MAX_PLAYERS - 1 || slotIndex < 0) 30 | return; 31 | 32 | var slot = match.Slots[slotIndex]; 33 | 34 | if (slot.Presence == match.Host) 35 | return; 36 | 37 | if (slot.Status == SlotStatus.Locked) 38 | slot.Status = SlotStatus.Open; 39 | else if ((slot.Status & SlotStatus.HasPlayer) > 0) 40 | { 41 | slot.Mods = Mods.None; 42 | slot.Presence = null; 43 | slot.Status = SlotStatus.Locked; 44 | slot.Team = SlotTeams.Neutral; 45 | } 46 | else 47 | slot.Status = SlotStatus.Locked; 48 | 49 | foreach (var presence in match.Presences) 50 | await presence.MatchUpdate(match); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchStart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class MatchStart 16 | { 17 | [Packet(PacketType.ClientMultiMatchStart)] 18 | public static void Handle(Packet p, Presence pr) => 19 | pr.CurrentMatch?.Start(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchTransferHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Layouts; 11 | using oyasumi.Managers; 12 | using oyasumi.Objects; 13 | 14 | namespace oyasumi.Events 15 | { 16 | public class MatchTransferHost 17 | { 18 | [Packet(PacketType.ClientMultiTransferHost)] 19 | public static async Task Handle(Packet p, Presence pr) 20 | { 21 | var match = pr.CurrentMatch; 22 | 23 | if (match is null) 24 | return; 25 | 26 | var reader = new SerializationReader(new MemoryStream(p.Data)); 27 | 28 | var slotIndex = reader.ReadInt32(); 29 | 30 | if (match.InProgress || slotIndex > Match.MAX_PLAYERS - 1 || slotIndex < 0) 31 | return; 32 | 33 | var newHost = match.Slots[slotIndex]; 34 | 35 | if (newHost.Presence is null) 36 | return; 37 | 38 | match.Host = newHost.Presence; 39 | match.Host.MatchTransferHost(); 40 | 41 | foreach (var presence in match.Presences) 42 | await presence.MatchUpdate(match); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchUnready.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Managers; 11 | using oyasumi.Objects; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class MatchUnready 16 | { 17 | [Packet(PacketType.ClientMultiNotReady)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var match = pr.CurrentMatch; 21 | 22 | if (match is null) 23 | return; 24 | 25 | var slot = match.Slots.FirstOrDefault(x => x.Presence == pr); 26 | 27 | slot.Status = SlotStatus.NotReady; 28 | 29 | foreach (var presence in match.Presences) 30 | await presence.MatchUpdate(match); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /oyasumi/Events/MatchUpdateMods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using osu.Game.IO.Legacy; 8 | using oyasumi.Enums; 9 | using oyasumi.IO; 10 | using oyasumi.Layouts; 11 | using oyasumi.Managers; 12 | using oyasumi.Objects; 13 | 14 | namespace oyasumi.Events 15 | { 16 | public class MatchUpdateMods 17 | { 18 | [Packet(PacketType.ClientMultiChangeMods)] 19 | public static async Task Handle(Packet p, Presence pr) 20 | { 21 | var match = pr.CurrentMatch; 22 | 23 | if (match is null) 24 | return; 25 | 26 | var reader = new SerializationReader(new MemoryStream(p.Data)); 27 | 28 | var mods = (Mods)reader.ReadInt32(); 29 | 30 | if (match.FreeMods) 31 | { 32 | if (match.Host == pr) 33 | match.ActiveMods = mods & Mods.SpeedAltering; 34 | 35 | match.Slots.FirstOrDefault(x => x.Presence == pr).Mods = mods & ~Mods.SpeedAltering; 36 | } 37 | else 38 | match.ActiveMods = mods; 39 | 40 | foreach (var presence in match.Presences) 41 | await presence.MatchUpdate(match); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /oyasumi/Events/Pong.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using oyasumi.Enums; 4 | using oyasumi.Layouts; 5 | using oyasumi.Objects; 6 | using oyasumi.Utilities; 7 | 8 | namespace oyasumi.Events 9 | { 10 | public class Pong 11 | { 12 | [Packet(PacketType.ClientPong)] 13 | public static async Task Handle(Packet p, Presence pr) 14 | { 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /oyasumi/Events/RequestPlayerList.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Enums; 2 | using oyasumi.Layouts; 3 | using oyasumi.Managers; 4 | using oyasumi.Objects; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Events 8 | { 9 | public class RequestPlayerList 10 | { 11 | [Packet(PacketType.ClientRequestPlayerList)] 12 | public static async Task Handle(Packet p, Presence pr) 13 | { 14 | foreach (var presence in PresenceManager.Presences.Values) 15 | await pr.UserPresence(presence); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /oyasumi/Events/SpectatorCantSpectate.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Enums; 2 | using oyasumi.IO; 3 | using oyasumi.Managers; 4 | using oyasumi.Objects; 5 | using oyasumi.Utilities; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | 9 | namespace oyasumi.Events 10 | { 11 | public class SpectatorCantSpectate 12 | { 13 | [Packet(PacketType.ServerSpectateNoBeatmap)] 14 | public static void Handle(Packet p, Presence pr) 15 | { 16 | var cantSpectatePacket = new Packet() 17 | { 18 | Type = PacketType.ServerSpectateNoBeatmap, 19 | Data = p.Data 20 | }; 21 | 22 | pr.Spectating.PacketEnqueue(cantSpectatePacket); 23 | 24 | foreach (var presence in pr.Spectators) 25 | { 26 | presence.PacketEnqueue(cantSpectatePacket); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /oyasumi/Events/SpectatorFrames.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Enums; 2 | using oyasumi.IO; 3 | using oyasumi.Managers; 4 | using oyasumi.Objects; 5 | using oyasumi.Utilities; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | 9 | namespace oyasumi.Events 10 | { 11 | public class SpectatorFrames 12 | { 13 | [Packet(PacketType.ClientSpectateData)] 14 | public static void Handle(Packet p, Presence pr) 15 | { 16 | foreach (var presence in pr.Spectators) 17 | { 18 | presence.PacketEnqueue(new () 19 | { 20 | Type = PacketType.ServerSpectateData, 21 | Data = p.Data 22 | }); 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /oyasumi/Events/SpectatorJoin.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using osu.Game.IO.Legacy; 4 | using oyasumi.Enums; 5 | using oyasumi.IO; 6 | using oyasumi.Layouts; 7 | using oyasumi.Managers; 8 | using oyasumi.Objects; 9 | 10 | namespace oyasumi.Events 11 | { 12 | public class SpectatorJoin 13 | { 14 | [Packet(PacketType.ClientSpectateStart)] 15 | public static async Task Handle(Packet p, Presence pr) 16 | { 17 | var ms = new MemoryStream(p.Data); 18 | using var reader = new SerializationReader(ms); 19 | 20 | var userId = reader.ReadInt32(); 21 | 22 | var spectatingPresence = PresenceManager.GetPresenceById(userId); 23 | 24 | pr.Spectating = spectatingPresence; 25 | await pr.Spectating.SpectatorJoined(pr.Id); 26 | 27 | if (pr.Spectating.SpectatorChannel is null) 28 | { 29 | var channel = new Channel($"spect_{pr.Spectating.Id}", "", 1, true); 30 | pr.Spectating.SpectatorChannel = channel; 31 | 32 | ChannelManager.Channels.TryAdd(channel.RawName, channel); 33 | 34 | await pr.Spectating.JoinChannel($"spect_{pr.Spectating.Id}") 35 | ; 36 | } 37 | 38 | await pr.JoinChannel($"spect_{pr.Spectating.Id}") 39 | ; 40 | 41 | spectatingPresence.Spectators.Add(pr); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /oyasumi/Events/SpectatorLeft.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Enums; 2 | using oyasumi.IO; 3 | using oyasumi.Layouts; 4 | using oyasumi.Managers; 5 | using oyasumi.Objects; 6 | using oyasumi.Utilities; 7 | using System.IO; 8 | using System.Threading.Tasks; 9 | 10 | namespace oyasumi.Events 11 | { 12 | public class SpectatorLeft 13 | { 14 | [Packet(PacketType.ClientSpectateStop)] 15 | public static async Task Handle(Packet p, Presence pr) 16 | { 17 | if (pr.Spectating is not null) 18 | { 19 | pr.Spectating.Spectators.Remove(pr); 20 | await pr.Spectating.SpectatorLeft(pr.Id); 21 | await pr.LeaveChannel($"spect_{pr.Spectating.Id}", true); 22 | 23 | if (pr.Spectators.Count == 0) 24 | await pr.Spectating.LeaveChannel($"spect_{pr.Spectating.Id}", true) 25 | ; 26 | 27 | pr.Spectating = null; 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /oyasumi/Events/TournamentJoinMatch.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using osu.Game.IO.Legacy; 4 | using oyasumi.Enums; 5 | using oyasumi.Managers; 6 | using oyasumi.Objects; 7 | 8 | namespace oyasumi.Events 9 | { 10 | public class TournamentJoinMatch 11 | { 12 | [Packet(PacketType.ClientMultiJoinChannel)] 13 | public static async Task Handle(Packet p, Presence pr) 14 | { 15 | if (!pr.Tourney && (pr.BanchoPermissions & BanchoPermissions.Tournament) == 0) 16 | return; 17 | 18 | var ms = new MemoryStream(p.Data); 19 | using var reader = new SerializationReader(ms); 20 | 21 | var matchId = reader.ReadInt32(); 22 | 23 | MatchManager.Matches.TryGetValue(matchId, out var match); 24 | 25 | if (match is not null) 26 | await pr.JoinChannel("multi_" + match.Id); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /oyasumi/Events/TournamentLeaveMatch.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using osu.Game.IO.Legacy; 4 | using oyasumi.Enums; 5 | using oyasumi.Managers; 6 | using oyasumi.Objects; 7 | 8 | namespace oyasumi.Events 9 | { 10 | public class TournamentLeaveMatch 11 | { 12 | [Packet(PacketType.ClientMultiLeaveChannel)] 13 | public static async Task Handle(Packet p, Presence pr) 14 | { 15 | if (!pr.Tourney && (pr.BanchoPermissions & BanchoPermissions.Tournament) == 0) 16 | return; 17 | 18 | var ms = new MemoryStream(p.Data); 19 | using var reader = new SerializationReader(ms); 20 | 21 | var matchId = reader.ReadInt32(); 22 | 23 | MatchManager.Matches.TryGetValue(matchId, out var match); 24 | 25 | if (match is not null) 26 | await pr.LeaveChannel("multi_" + match.Id); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /oyasumi/Events/TournamentMatchInfo.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using osu.Game.IO.Legacy; 4 | using oyasumi.Enums; 5 | using oyasumi.Layouts; 6 | using oyasumi.Managers; 7 | using oyasumi.Objects; 8 | 9 | namespace oyasumi.Events 10 | { 11 | public class TournamentMatchInfo 12 | { 13 | [Packet(PacketType.ClientMultiMatchInfoRequest)] 14 | public static async Task Handle(Packet p, Presence pr) 15 | { 16 | if (!pr.Tourney && (pr.BanchoPermissions & BanchoPermissions.Tournament) == 0) 17 | return; 18 | 19 | var ms = new MemoryStream(p.Data); 20 | using var reader = new SerializationReader(ms); 21 | 22 | var matchId = reader.ReadInt32(); 23 | 24 | MatchManager.Matches.TryGetValue(matchId, out var match); 25 | 26 | if (match is not null) 27 | await pr.MatchUpdate(match); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /oyasumi/Events/UserPresenceRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using osu.Game.IO.Legacy; 6 | using oyasumi.Enums; 7 | using oyasumi.IO; 8 | using oyasumi.Layouts; 9 | using oyasumi.Managers; 10 | using oyasumi.Objects; 11 | 12 | namespace oyasumi.Events 13 | { 14 | public class UserPresenceRequest 15 | { 16 | [Packet(PacketType.ClientUserPresenceRequest)] 17 | public static async Task Handle(Packet p, Presence pr) 18 | { 19 | var ms = new MemoryStream(p.Data); 20 | using var reader = new SerializationReader(ms); 21 | 22 | var presenceIds = new List(); 23 | int length = reader.ReadInt16(); 24 | for (var i = 0; i < length; i++) 25 | presenceIds.Add(reader.ReadInt32()); 26 | 27 | foreach (var prId in presenceIds) 28 | { 29 | var otherPresence = PresenceManager.GetPresenceById(prId); 30 | if (otherPresence is not null) 31 | await pr.UserPresence(otherPresence); 32 | else 33 | await pr.UserLogout(prId); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /oyasumi/Events/UserStatsRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using osu.Game.IO.Legacy; 6 | using oyasumi.Enums; 7 | using oyasumi.IO; 8 | using oyasumi.Layouts; 9 | using oyasumi.Managers; 10 | using oyasumi.Objects; 11 | using oyasumi.Utilities; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class UserStatsRequest 16 | { 17 | [Packet(PacketType.ClientUserStatsRequest)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var ms = new MemoryStream(p.Data); 21 | using var reader = new SerializationReader(ms); 22 | 23 | var presenceIds = new List(); 24 | 25 | int length = reader.ReadInt16(); 26 | for (var i = 0; i < length; i++) 27 | presenceIds.Add(reader.ReadInt32()); 28 | 29 | foreach (var prId in presenceIds) 30 | { 31 | if (prId == pr.Id) 32 | continue; 33 | 34 | var otherPresence = PresenceManager.GetPresenceById(prId); 35 | if (otherPresence is not null) 36 | await pr.UserStats(otherPresence); 37 | else 38 | await pr.UserLogout(prId); 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /oyasumi/Events/UserStatusRequestOwn.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using oyasumi.Enums; 4 | using oyasumi.IO; 5 | using oyasumi.Layouts; 6 | using oyasumi.Objects; 7 | 8 | namespace oyasumi.Events 9 | { 10 | public class UserStatusRequestOwn 11 | { 12 | [Packet(PacketType.ClientStatusRequestOwn)] 13 | public static async Task Handle(Packet p, Presence pr) => 14 | await pr.UserStats(); 15 | } 16 | } -------------------------------------------------------------------------------- /oyasumi/Events/UserStatusUpdate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Org.BouncyCastle.Asn1.Cms; 5 | using osu.Game.IO.Legacy; 6 | using oyasumi.Database; 7 | using oyasumi.Enums; 8 | using oyasumi.IO; 9 | using oyasumi.Layouts; 10 | using oyasumi.Objects; 11 | using static oyasumi.Objects.Presence; 12 | 13 | namespace oyasumi.Events 14 | { 15 | public class UserStatusUpdate 16 | { 17 | [Packet(PacketType.ClientUserStatus)] 18 | public static async Task Handle(Packet p, Presence pr) 19 | { 20 | var ms = new MemoryStream(p.Data); 21 | using var reader = new SerializationReader(ms); 22 | 23 | pr.Status = new () 24 | { 25 | Status = (ActionStatuses) reader.ReadByte(), 26 | StatusText = reader.ReadString(), 27 | BeatmapChecksum = reader.ReadString(), 28 | CurrentMods = (Mods) reader.ReadUInt32(), 29 | CurrentPlayMode = (PlayMode) reader.ReadByte(), 30 | BeatmapId = reader.ReadInt32() 31 | }; 32 | 33 | var lbMode = pr.Status.CurrentMods switch 34 | { 35 | var mod when (mod & Mods.Relax) > 0 => LeaderboardMode.Relax, 36 | _ => LeaderboardMode.Vanilla, 37 | }; 38 | 39 | pr.GetOrUpdateUserStats(lbMode, false); 40 | 41 | await pr.UserStats(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /oyasumi/Extensions/ArrayExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace oyasumi.Extensions 5 | { 6 | public static class ArrayExtensions 7 | { 8 | public static void RemoveAll(ref T[] array, Predicate filter) where T : IComparable 9 | { 10 | var newArray = new T[array.Length]; 11 | var index = 0; 12 | 13 | foreach (var item in array) 14 | { 15 | if (!filter(item)) 16 | { 17 | newArray[index] = item; 18 | index++; 19 | } 20 | } 21 | 22 | Array.Resize(ref newArray, index); 23 | array = newArray; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /oyasumi/Extensions/ScoreExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using oyasumi.Database; 3 | using oyasumi.Database.Models; 4 | using oyasumi.Enums; 5 | using oyasumi.Managers; 6 | using oyasumi.Objects; 7 | using oyasumi.Utilities; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.IO; 11 | using System.Linq; 12 | using System.Security.Cryptography; 13 | using System.Text; 14 | using System.Threading.Tasks; 15 | 16 | namespace oyasumi.Extensions 17 | { 18 | public static class ScoreExtensions 19 | { 20 | private const string SUBMISSION_KEY = "osu!-scoreburgr---------{0}"; 21 | 22 | public static async Task ToScore(this (string encScore, string iv, string osuVersion) self) 23 | { 24 | var scoreDecrypted = Crypto.DecryptString( 25 | self.encScore, 26 | Encoding.ASCII.GetBytes(string.Format(SUBMISSION_KEY, self.osuVersion)), 27 | self.iv 28 | ); 29 | 30 | var split = scoreDecrypted.Split(':'); 31 | 32 | var mods = (Mods)uint.Parse(split[13]); 33 | var isRelax = (mods & Mods.Relax) > 0; 34 | var isAutopilot = (mods & Mods.Relax2) > 0; 35 | 36 | var beatmap = await BeatmapManager.Get(split[0], setId: -1); 37 | 38 | var osuVersionUnformatted = split[17]; 39 | var osuVersion = osuVersionUnformatted.Trim(); 40 | 41 | var flags = (osuVersionUnformatted.Length - osuVersion.Length) & ~4; 42 | 43 | var presence = PresenceManager.GetPresenceByName(split[1].TrimEnd()); // TrimEnd() because osu! adds extra space if user is supporter 44 | 45 | if (presence is null) 46 | return new (); 47 | 48 | return new() 49 | { 50 | FileChecksum = split[0], 51 | Presence = presence, 52 | User = presence.User, 53 | Count300 = int.Parse(split[3]), 54 | Count100 = int.Parse(split[4]), 55 | Count50 = int.Parse(split[5]), 56 | CountGeki = int.Parse(split[6]), 57 | CountKatu = int.Parse(split[7]), 58 | CountMiss = int.Parse(split[8]), 59 | TotalScore = int.Parse(split[9]), 60 | MaxCombo = int.Parse(split[10]), 61 | Perfect = bool.Parse(split[11]), 62 | Mods = mods, 63 | Relaxing = isRelax, 64 | Autopiloting = isAutopilot, 65 | Passed = bool.Parse(split[14]), 66 | PlayMode = (PlayMode)uint.Parse(split[15]), 67 | Date = DateTime.UtcNow, 68 | Beatmap = beatmap, 69 | OsuVersion = int.Parse(osuVersion), 70 | Flags = (BadFlags)flags 71 | }; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /oyasumi/Extensions/SerializationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using osu.Game.IO.Legacy; 3 | using oyasumi.Enums; 4 | using oyasumi.Managers; 5 | using oyasumi.Objects; 6 | 7 | namespace oyasumi.Extensions 8 | { 9 | public static class SerializationExtensions 10 | { 11 | public static async Task ReadMatch(this SerializationReader reader) 12 | { 13 | var match = new Match(); 14 | 15 | reader.ReadInt16(); // match id 16 | reader.ReadByte(); // in progress 17 | 18 | match.Type = (MatchTypes)reader.ReadByte(); 19 | match.ActiveMods = (Mods)reader.ReadInt32(); 20 | 21 | match.GameName = reader.ReadString(); 22 | match.GamePassword = reader.ReadString(); 23 | 24 | reader.ReadString(); // beatmap name 25 | 26 | match.BeatmapId = reader.ReadInt32(); 27 | match.BeatmapChecksum = reader.ReadString(); 28 | 29 | match.Beatmap = await BeatmapManager.Get(match.BeatmapChecksum); 30 | 31 | foreach (var slot in match.Slots) 32 | slot.Status = (SlotStatus)reader.ReadByte(); 33 | 34 | foreach (var slot in match.Slots) 35 | slot.Team = (SlotTeams)reader.ReadByte(); 36 | 37 | foreach (var slot in match.Slots) 38 | if ((slot.Status & SlotStatus.HasPlayer) > 0) 39 | reader.ReadInt32(); 40 | 41 | match.Host = PresenceManager.GetPresenceById(reader.ReadInt32()); 42 | 43 | match.PlayMode = (PlayMode)reader.ReadByte(); 44 | match.ScoringType = (MatchScoringTypes)reader.ReadByte(); 45 | match.TeamType = (MatchTeamTypes)reader.ReadByte(); 46 | match.FreeMods = reader.ReadBoolean(); 47 | 48 | if (match.FreeMods) 49 | foreach (var slot in match.Slots) 50 | slot.Mods = (Mods)reader.ReadInt32(); 51 | 52 | match.Seed = reader.ReadInt32(); 53 | 54 | return match; 55 | } 56 | 57 | public static void WriteMatch(this SerializationWriter writer, Match match) 58 | { 59 | writer.Write((short)match.Id); 60 | writer.Write(match.InProgress); 61 | writer.Write((byte)match.Type); 62 | writer.Write((uint)match.ActiveMods); 63 | writer.Write(match.GameName); 64 | writer.Write(match.GamePassword); 65 | writer.Write(match.Beatmap.Name); 66 | writer.Write(match.BeatmapId); 67 | writer.Write(match.BeatmapChecksum); 68 | 69 | for (var i = 0; i < Match.MAX_PLAYERS; i++) 70 | writer.Write((byte)match.Slots[i].Status); 71 | 72 | for (var i = 0; i < Match.MAX_PLAYERS; i++) 73 | writer.Write((byte)match.Slots[i].Team); 74 | 75 | for (var i = 0; i < Match.MAX_PLAYERS; i++) 76 | if ((match.Slots[i].Status & SlotStatus.HasPlayer) > 0) 77 | { 78 | writer.Write(match.Slots[i].Presence.Id); 79 | } 80 | 81 | writer.Write(match.Host.Id); 82 | 83 | writer.Write((byte)match.PlayMode); 84 | writer.Write((byte)match.ScoringType); 85 | writer.Write((byte)match.TeamType); 86 | writer.Write(match.FreeMods); 87 | 88 | //Write((byte)match.SpecialModes); 89 | 90 | if (match.FreeMods) 91 | for (var i = 0; i < Match.MAX_PLAYERS; i++) 92 | writer.Write((int)match.Slots[i].Mods); 93 | 94 | writer.Write(match.Seed); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /oyasumi/Extensions/UserExtensions.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Managers; 2 | using System; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using oyasumi.Database.Models; 7 | using oyasumi.Enums; 8 | using oyasumi.Interfaces; 9 | 10 | namespace oyasumi.Extensions 11 | { 12 | public static class UserExtensions 13 | { 14 | public static async Task<(string username, string password, string osuVersion, int timezone)> ParseLoginDataAsync(this Stream self) 15 | { 16 | using var reader = new StreamReader(self, leaveOpen: true); 17 | 18 | var username = await reader.ReadLineAsync(); 19 | var password = await reader.ReadLineAsync(); 20 | var data = (await reader.ReadLineAsync())?.Split("|"); 21 | 22 | 23 | return (username, password, data[0], int.Parse(data[1])); 24 | } 25 | 26 | public static bool CheckLogin(this (string username, string password) self) 27 | { 28 | var pr = PresenceManager.GetPresenceByName(self.username); 29 | return pr is not null && Base.PasswordCache.TryGetValue(self.password, out _); 30 | } 31 | 32 | public static string ToSafe(this string self) => 33 | self.Replace(" ", "_").ToLower(); 34 | 35 | public static bool Banned(this User self) => 36 | (self.Privileges & Privileges.Normal) == 0; 37 | 38 | public static long TotalScore(this IStats stats, PlayMode mode) 39 | { 40 | return mode switch 41 | { 42 | PlayMode.Osu => stats.TotalScoreOsu, 43 | PlayMode.Taiko => stats.TotalScoreTaiko, 44 | PlayMode.CatchTheBeat => stats.TotalScoreCtb, 45 | PlayMode.OsuMania => stats.TotalScoreMania 46 | }; 47 | } 48 | 49 | public static long RankedScore(this IStats stats, PlayMode mode) 50 | { 51 | return mode switch 52 | { 53 | PlayMode.Osu => stats.RankedScoreOsu, 54 | PlayMode.Taiko => stats.RankedScoreTaiko, 55 | PlayMode.CatchTheBeat => stats.RankedScoreCtb, 56 | PlayMode.OsuMania => stats.RankedScoreMania 57 | }; 58 | } 59 | 60 | public static int Performance(this IStats stats, PlayMode mode) 61 | { 62 | return mode switch 63 | { 64 | PlayMode.Osu => stats.PerformanceOsu, 65 | PlayMode.Taiko => stats.PerformanceTaiko, 66 | PlayMode.CatchTheBeat => stats.PerformanceCtb, 67 | PlayMode.OsuMania => stats.PerformanceMania 68 | }; 69 | } 70 | 71 | public static float Accuracy(this IStats stats, PlayMode mode) 72 | { 73 | return mode switch 74 | { 75 | PlayMode.Osu => stats.AccuracyOsu, 76 | PlayMode.Taiko => stats.AccuracyTaiko, 77 | PlayMode.CatchTheBeat => stats.AccuracyCtb, 78 | PlayMode.OsuMania => stats.AccuracyMania 79 | }; 80 | } 81 | 82 | public static int Playcount(this IStats stats, PlayMode mode) 83 | { 84 | return mode switch 85 | { 86 | PlayMode.Osu => stats.PlaycountOsu, 87 | PlayMode.Taiko => stats.PlaycountTaiko, 88 | PlayMode.CatchTheBeat => stats.PlaycountCtb, 89 | PlayMode.OsuMania => stats.PlaycountMania 90 | }; 91 | } 92 | 93 | public static int Rank(this IStats stats, PlayMode mode) 94 | { 95 | return mode switch 96 | { 97 | PlayMode.Osu => stats.RankOsu, 98 | PlayMode.Taiko => stats.RankTaiko, 99 | PlayMode.CatchTheBeat => stats.RankCtb, 100 | PlayMode.OsuMania => stats.RankMania 101 | }; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /oyasumi/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /oyasumi/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Usage", "MA0006:Use String.Equals instead of equality operator", Justification = "", Scope = "member", Target = "~M:oyasumi.Controllers.WebController.SubmitModular~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")] 9 | [assembly: SuppressMessage("Usage", "MA0011:IFormatProvider is missing", Justification = "", Scope = "member", Target = "~M:oyasumi.Controllers.WebController.SubmitModular~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")] -------------------------------------------------------------------------------- /oyasumi/IO/PacketReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using osu.Game.IO.Legacy; 9 | using oyasumi.Enums; 10 | using oyasumi.Objects; 11 | 12 | namespace oyasumi.IO 13 | { 14 | public static class PacketReader 15 | { 16 | private static readonly ConcurrentDictionary _packetCache = new (); 17 | public static IEnumerable Parse(MemoryStream data) 18 | { 19 | using var reader = new SerializationReader(data); 20 | 21 | while (data.Position != data.Length) 22 | yield return Read(reader); 23 | } 24 | 25 | private static Packet Read(BinaryReader reader) 26 | { 27 | var type = (PacketType)reader.ReadInt16(); 28 | 29 | reader.ReadByte(); 30 | 31 | var length = reader.ReadInt32(); 32 | var packetData = reader.ReadBytes(length); 33 | 34 | var packet = new Packet 35 | { 36 | Type = type, 37 | Data = packetData 38 | }; 39 | 40 | return packet; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /oyasumi/IO/PacketWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using osu.Game.IO.Legacy; 7 | using oyasumi.Objects; 8 | 9 | namespace oyasumi.IO 10 | { 11 | public class PacketWriter 12 | { 13 | private readonly MemoryStream _data; 14 | private bool _clean; 15 | 16 | public PacketWriter() => 17 | _data = new (); 18 | 19 | public byte[] ToBytes() 20 | { 21 | if (_clean) 22 | throw new ("Unable to access because ToBytes() method can be called only once"); 23 | 24 | var array = _data.ToArray(); 25 | 26 | _clean = true; 27 | 28 | return array; 29 | } 30 | 31 | public async Task Write(IAsyncEnumerable packetList) 32 | { 33 | await foreach (var packet in packetList) 34 | await Write(packet); 35 | } 36 | 37 | public async Task Write(Packet packet) 38 | { 39 | await using var writer = new SerializationWriter(new MemoryStream()); 40 | 41 | writer.Write((short) packet.Type); 42 | writer.Write((byte) 0); 43 | writer.Write(packet.Data); 44 | 45 | ((MemoryStream)writer.BaseStream).WriteTo(_data); 46 | } 47 | 48 | public async Task Write(byte[] packet) 49 | { 50 | await using var ms = new MemoryStream(packet); 51 | 52 | ms.WriteTo(_data); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /oyasumi/Interfaces/IStats.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace oyasumi.Interfaces 9 | { 10 | public interface IStats 11 | { 12 | public int Id { get; set; } 13 | public bool IsPublic { get; set; } 14 | public long TotalScoreOsu { get; set; } 15 | public long TotalScoreTaiko { get; set; } 16 | public long TotalScoreCtb { get; set; } 17 | public long TotalScoreMania { get; set; } 18 | 19 | public long RankedScoreOsu { get; set; } 20 | public long RankedScoreTaiko { get; set; } 21 | public long RankedScoreCtb { get; set; } 22 | public long RankedScoreMania { get; set; } 23 | 24 | public int PerformanceOsu { get; set; } 25 | public int PerformanceTaiko { get; set; } 26 | public int PerformanceCtb { get; set; } 27 | public int PerformanceMania { get; set; } 28 | 29 | public float AccuracyOsu { get; set; } 30 | public float AccuracyTaiko { get; set; } 31 | public float AccuracyCtb { get; set; } 32 | public float AccuracyMania { get; set; } 33 | 34 | public int PlaycountOsu { get; set; } 35 | public int PlaycountTaiko { get; set; } 36 | public int PlaycountCtb { get; set; } 37 | public int PlaycountMania { get; set; } 38 | 39 | public int RankOsu { get; set; } 40 | public int RankTaiko { get; set; } 41 | public int RankCtb { get; set; } 42 | public int RankMania { get; set; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /oyasumi/Managers/BeatmapManager.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using oyasumi.Database; 4 | using oyasumi.Database.Models; 5 | using oyasumi.Enums; 6 | using oyasumi.Extensions; 7 | using oyasumi.Objects; 8 | using oyasumi.Utilities; 9 | 10 | namespace oyasumi.Managers 11 | { 12 | public struct BeatmapTitle 13 | { 14 | public string Title; 15 | public string Artist; 16 | public string Creator; 17 | public string Difficulty; 18 | } 19 | 20 | public static class BeatmapManager 21 | { 22 | public static MultiKeyDictionary Beatmaps = new(); 23 | 24 | /// 25 | /// Getting beatmap by fastest method available 26 | /// 27 | /// MD5 checksum of beatmap 28 | /// Name of osu beatmap file 29 | /// Beatmap set id 30 | /// Leaderboard 31 | /// GameMode 32 | public static async Task Get 33 | ( 34 | string checksum = "", string fileName = "", int setId = 0, 35 | bool leaderboard = true, PlayMode mode = PlayMode.Osu 36 | ) 37 | { 38 | if (string.IsNullOrEmpty(checksum) && string.IsNullOrEmpty(fileName) && setId == 0) 39 | return null; 40 | 41 | var beatmap = DbContext.Beatmaps[checksum]; 42 | var cachedBeatmap = Beatmaps[checksum]; 43 | 44 | if (cachedBeatmap is null && beatmap is not null) 45 | Beatmaps.Add(beatmap.BeatmapId, beatmap.BeatmapMd5, beatmap.FromDb(leaderboard, mode)); 46 | 47 | if (beatmap is null) 48 | { 49 | if (setId == -1) 50 | { 51 | var apiBeatmap = await Beatmap.Get(checksum, fileName, leaderboard, mode); 52 | 53 | if (apiBeatmap is null) 54 | return null; 55 | 56 | setId = apiBeatmap.SetId; 57 | } 58 | 59 | // Let's try to find beatmap by set id 60 | beatmap = DbContext.Beatmaps.Values.Where(x => x.BeatmapSetId == setId).FirstOrDefault(); 61 | 62 | var beatmapSet = Beatmap.GetBeatmapSet(setId, fileName, leaderboard, mode); 63 | 64 | if (beatmap is not null) // If beatmap is already exist then we need to set status to NeedUpdate 65 | { 66 | DbContext.Beatmaps.ExecuteWhere(x => x.BeatmapSetId == setId, (beatmap) => 67 | { 68 | beatmap.Status = RankedStatus.NeedUpdate; 69 | return beatmap; 70 | }); 71 | 72 | Beatmaps.ExecuteWhere(x => x.SetId == setId, (beatmap) => 73 | { 74 | beatmap.Status = RankedStatus.NeedUpdate; 75 | return beatmap; 76 | }); 77 | } 78 | 79 | await foreach (var b in beatmapSet) 80 | { 81 | if (b is null) 82 | return null; 83 | 84 | DbContext.Beatmaps.Add(b.Id, b.FileChecksum, b.ToDb()); 85 | Beatmaps.Add(b.Id, b.FileChecksum, b); 86 | } 87 | } 88 | 89 | return Beatmaps[checksum]; 90 | } 91 | 92 | public static Beatmap FromDb(this DbBeatmap b, bool leaderboard, PlayMode mode) 93 | { 94 | var metadata = new BeatmapMetadata 95 | { 96 | Artist = b.Artist, 97 | Title = b.Title, 98 | DifficultyName = b.DifficultyName, 99 | Creator = b.Creator, 100 | ApproachRate = b.ApproachRate, 101 | CircleSize = b.CircleSize, 102 | OverallDifficulty = b.OverallDifficulty, 103 | HPDrainRate = b.HPDrainRate, 104 | BPM = b.BPM, 105 | Stars = b.Stars 106 | }; 107 | return new (b.BeatmapMd5, b.FileName, b.BeatmapId, b.BeatmapSetId, metadata, 108 | b.Status, false, 0, 0, 0, 0, leaderboard, mode); 109 | } 110 | 111 | public static DbBeatmap ToDb(this Beatmap b) 112 | { 113 | return new() 114 | { 115 | Artist = b.Metadata.Artist, 116 | Title = b.Metadata.Title, 117 | DifficultyName = b.Metadata.DifficultyName, 118 | Creator = b.Metadata.Creator, 119 | ApproachRate = b.Metadata.ApproachRate, 120 | CircleSize = b.Metadata.CircleSize, 121 | OverallDifficulty = b.Metadata.OverallDifficulty, 122 | HPDrainRate = b.Metadata.HPDrainRate, 123 | BPM = b.Metadata.BPM, 124 | Stars = b.Metadata.Stars, 125 | BeatmapMd5 = b.FileChecksum, 126 | BeatmapId = b.Id, 127 | BeatmapSetId = b.SetId, 128 | Status = b.Status, 129 | Frozen = b.Frozen, 130 | PlayCount = b.PlayCount, 131 | PassCount = b.PassCount, 132 | FileName = b.FileName 133 | }; 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /oyasumi/Managers/MatchManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using oyasumi.Objects; 7 | using oyasumi.Layouts; 8 | using oyasumi.Enums; 9 | 10 | namespace oyasumi.Managers 11 | { 12 | public static class MatchManager 13 | { 14 | public static Dictionary Matches = new (); 15 | private static int _idCounter; 16 | 17 | public static async Task JoinMatch(this Presence pr, Match match, string password) 18 | { 19 | if (pr.CurrentMatch is not null || (match.PasswordRequired && match.GamePassword != password)) 20 | { 21 | pr.MatchJoinFail(); 22 | return; 23 | } 24 | 25 | if (!Matches.TryGetValue(match.Id, out _)) 26 | { 27 | match.Id = ++_idCounter; 28 | Matches.Add(match.Id, match); 29 | } 30 | 31 | var slot = match.FreeSlot; 32 | 33 | if (slot is null) 34 | { 35 | pr.MatchJoinFail(); 36 | return; 37 | } 38 | 39 | slot.Status = SlotStatus.NotReady; 40 | slot.Presence = pr; 41 | 42 | match.Presences.Add(pr); 43 | 44 | await pr.MatchJoinSuccess(match); 45 | 46 | pr.CurrentMatch = match; 47 | 48 | foreach (var presence in match.Presences) 49 | await presence.MatchUpdate(match); 50 | } 51 | 52 | public static async Task LeaveMatch(this Presence pr) 53 | { 54 | var match = pr.CurrentMatch; 55 | 56 | if (match is null) 57 | return; 58 | 59 | var slot = match.Slots.FirstOrDefault(x => x.Presence == pr); 60 | 61 | slot?.Clear(); 62 | 63 | match.Presences.Remove(pr); 64 | await pr.LeaveChannel($"multi_{pr.CurrentMatch.Id}", true) 65 | ; 66 | 67 | if (match.Presences.Count == 0) 68 | { 69 | Matches.Remove(match.Id); 70 | ChannelManager.Channels.TryRemove($"multi_{pr.CurrentMatch.Id}", out _); 71 | } 72 | else 73 | foreach (var presence in match.Presences) 74 | await presence.MatchUpdate(match); 75 | 76 | pr.CurrentMatch = null; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /oyasumi/Managers/PresenceManager.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using oyasumi.Enums; 4 | using oyasumi.Layouts; 5 | using oyasumi.Objects; 6 | using oyasumi.Utilities; 7 | 8 | namespace oyasumi.Managers 9 | { 10 | public static class PresenceManager 11 | { 12 | public static readonly MultiKeyDictionary Presences = new (); 13 | 14 | public static void Add(Presence p) => 15 | Presences.Add(p.Id, p.Token, p); 16 | 17 | public static async Task Remove(Presence p) 18 | { 19 | p.Spectating?.SpectatorLeft(p.Id); 20 | 21 | Presences.Remove(p.Id); 22 | 23 | foreach (var pr in Presences.Values) 24 | await pr.UserLogout(p.Id); 25 | 26 | foreach (var channel in p.Channels.Values) 27 | await p.LeaveChannel(channel.Name); 28 | } 29 | 30 | public static Presence GetPresenceByToken(string token) => Presences[token]; 31 | public static Presence GetPresenceById(int id) => Presences[id]; 32 | public static Presence GetPresenceByName(string name) => Presences.Values.FirstOrDefault(x => x.Username == name); 33 | public static void PacketEnqueue(Packet p) 34 | { 35 | foreach (var pr in Presences.Values) 36 | pr.PacketEnqueue(p); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /oyasumi/Objects/Channel.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Managers; 2 | using oyasumi.Utilities; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace oyasumi.Objects 11 | { 12 | public class Channel 13 | { 14 | public ConcurrentDictionary Presences = new (); 15 | 16 | public string Name => RawName.StartsWith("multi_") ? "#multiplayer" : RawName.StartsWith("spect_") ? "#spectator" : RawName; 17 | public string RawName; 18 | public string Description; 19 | public int UserCount; 20 | public bool PublicWrite; 21 | 22 | public Channel(string name, string description, int userCount, bool publicWrite) 23 | { 24 | RawName = name; 25 | Description = description; 26 | UserCount = userCount; 27 | PublicWrite = publicWrite; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /oyasumi/Objects/Chart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace oyasumi.Objects 9 | { 10 | public struct Chart 11 | { 12 | private static NumberFormatInfo _nfi = new CultureInfo("en-US", false).NumberFormat; 13 | 14 | private readonly string _chartId; 15 | private readonly string _chartUrl; 16 | private readonly string _chartName; 17 | 18 | private readonly Presence _pBefore; 19 | private readonly Presence _pAfter; 20 | 21 | private readonly Score _sBefore; 22 | private readonly Score _sAfter; 23 | 24 | private readonly string _achievements; 25 | 26 | private readonly int _scoreId; 27 | 28 | public Chart(string chartId, string chartUrl, string chartName, int scoreId, string achievements, Score sBefore, Score sAfter, Presence pBefore, Presence pAfter) 29 | { 30 | _chartId = chartId; 31 | _chartUrl = chartUrl; 32 | _chartName = chartName; 33 | _sBefore = sBefore; 34 | _sAfter = sAfter; 35 | _pBefore = pBefore; 36 | _pAfter = pAfter; 37 | _achievements = achievements; 38 | _scoreId = scoreId; 39 | } 40 | 41 | // TODO: max combo 42 | public override string ToString() => 43 | $"chartId:{_chartId}" + 44 | $"|chartUrl:{_chartUrl}" + 45 | $"|chartName:{_chartName}" + 46 | $"|rankBefore:{_sBefore?.Rank ?? _pBefore.Rank}" + 47 | $"|rankAfter:{(_sBefore == null ? _pAfter.Rank : _sAfter.Rank)}" + 48 | $"|maxComboBefore:0" + 49 | $"|maxComboAfter:0" + 50 | $"|accuracyBefore:{_sBefore?.Accuracy * 100 ?? _pBefore.Accuracy * 100}" + 51 | $"|accuracyAfter:{(_sBefore == null ? _pAfter.Accuracy * 100 : _sAfter.Accuracy * 100)}" + 52 | $"|rankedScoreBefore:{_sBefore?.TotalScore ?? _pBefore.RankedScore}" + 53 | $"|rankedScoreAfter:{(_sBefore == null ? _pAfter.RankedScore : _sAfter.TotalScore)}" + 54 | $"|totalScoreBefore:{_sBefore?.TotalScore ?? _pBefore.TotalScore}" + 55 | $"|totalScoreAfter:{(_sBefore == null ? _pAfter.TotalScore : _sAfter.TotalScore)}" + 56 | $"|ppBefore:{_sBefore?.PerformancePoints ?? _pBefore.Performance}" + 57 | $"|ppAfter:{(_sBefore == null ? _pAfter.Performance : _sAfter.PerformancePoints)}" + 58 | (_achievements == null ? "" : "|achievements-new:" + _achievements) + 59 | $"|onlineScoreId:{_scoreId}"; 60 | 61 | public static string Build(Beatmap beatmap, Chart beatmapChart, Chart overallChart) 62 | => $"beatmapId:{beatmap.Id}|beatmapSetId:{beatmap.SetId}|beatmapPlaycount:0|beatmapPasscount:0|approvedDate:\n\n{beatmapChart}\n{overallChart}"; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /oyasumi/Objects/Config.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Newtonsoft.Json; 3 | 4 | namespace oyasumi.Objects 5 | { 6 | public class ConfigScheme 7 | { 8 | public string Database { get; set; } 9 | public string Username { get; set; } 10 | public string Password { get; set; } 11 | public string BeatmapMirror { get; set; } 12 | public string OsuApiUrl { get; set; } 13 | public string OsuApiKey { get; set; } 14 | public string RecaptchaPrivate { get; set; } 15 | } 16 | 17 | public class Config 18 | { 19 | public static ConfigScheme Properties 20 | { 21 | get 22 | { 23 | if (!File.Exists("config.json")) 24 | { 25 | File.WriteAllText("config.json", JsonConvert.SerializeObject(new ConfigScheme 26 | { 27 | Database = "oyasumi", 28 | Username = "root", 29 | Password = "", 30 | OsuApiUrl = "https://old.ppy.sh" 31 | })); 32 | } 33 | return JsonConvert.DeserializeObject(File.ReadAllText(@"config.json")); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /oyasumi/Objects/Filter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace oyasumi.Objects 4 | { 5 | // Don't recommend to look thru this 6 | public class Filter 7 | { 8 | private readonly string _filter; 9 | private readonly object _presence; 10 | 11 | public Filter(string filter, object pr) 12 | { 13 | _filter = filter; 14 | _presence = pr; 15 | } 16 | 17 | /// Presence filter format 18 | /// 19 | /// It's simple, for example: 20 | /// CurrentMatch is not null 21 | /// 22 | /// They're can be splitted by | 23 | /// CurrentMatch is not null | Spectators is not null | Accuracy greater 0.0 24 | /// It's equivalent to CurrentMatch is not null or Spectators is not null or Accuracy greater 0.0 25 | /// 26 | /// First string is always field of Presence 27 | /// 28 | /// 29 | /// Number types: 30 | /// Integer: 1, 2, 3 etc. 31 | /// Double: 1.5, 1.6, 1.9 etc. 32 | /// Float: 1.6f, 1.4f, 1.8f etc. 33 | /// 34 | /// 35 | public bool IsMatch() 36 | { 37 | var noOther = !_filter.Contains('|'); 38 | 39 | var expressions = noOther ? new [] { _filter } : _filter.Split("|"); 40 | 41 | foreach (var expr in expressions) 42 | { 43 | var tokens = expr.Split(' '); 44 | var field = tokens[0]; 45 | for (var i = 0; i < tokens.Length; i++) 46 | { 47 | var token = tokens[i]; 48 | if (token == "is") 49 | { 50 | if (i + 1 > tokens.Length) 51 | throw new("No value to compare."); 52 | var next = tokens[i + 1]; 53 | if (next == "not") 54 | { 55 | if (i + 2 > tokens.Length) 56 | throw new("No value to compare."); 57 | next = tokens[i + 2]; 58 | 59 | if (next == "null") 60 | return typeof(Presence).GetField(field).GetValue(_presence) is not null; 61 | } 62 | if (next == "null") 63 | return typeof(Presence).GetField(field).GetValue(_presence) is null; 64 | 65 | if (next == "greater") 66 | { 67 | if (i + 2 > tokens.Length) 68 | throw new("No value to compare."); 69 | next = tokens[i + 2]; 70 | if (next.Contains(".")) 71 | { 72 | if (next.LastOrDefault() == 'f') 73 | { 74 | if (float.TryParse(next, out var f)) 75 | { 76 | return f > (float)typeof(Presence).GetField(field).GetValue(_presence); 77 | } 78 | } 79 | 80 | if (double.TryParse(next, out var d)) 81 | return d > (double)typeof(Presence).GetField(field).GetValue(_presence); 82 | } 83 | 84 | if (int.TryParse(next, out var num)) 85 | return num > (int)typeof(Presence).GetField(field).GetValue(_presence); 86 | } 87 | 88 | if (next == "less") 89 | { 90 | if (i + 2 > tokens.Length) 91 | throw new("No value to compare."); 92 | next = tokens[i + 2]; 93 | if (next.Contains(".")) 94 | { 95 | if (next.LastOrDefault() == 'f') 96 | { 97 | if (float.TryParse(next, out var f)) 98 | return f < (float)typeof(Presence).GetField(field).GetValue(_presence); 99 | } 100 | 101 | if (double.TryParse(next, out var d)) 102 | return d < (double)typeof(Presence).GetField(field).GetValue(_presence); 103 | } 104 | 105 | if (int.TryParse(next, out var num)) 106 | return num < (int)typeof(Presence).GetField(field).GetValue(_presence); 107 | } 108 | } 109 | if (token == string.Empty) 110 | continue; 111 | } 112 | } 113 | return false; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /oyasumi/Objects/Match.cs: -------------------------------------------------------------------------------- 1 | using Org.BouncyCastle.Asn1.Esf; 2 | using oyasumi.Enums; 3 | using oyasumi.Layouts; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace oyasumi.Objects 11 | { 12 | public class Slot 13 | { 14 | public SlotStatus Status = SlotStatus.Open; 15 | public SlotTeams Team = SlotTeams.Neutral; 16 | public Mods Mods = Mods.None; 17 | public Presence Presence; 18 | public bool Skipped; 19 | 20 | public void CopyFrom(Slot source) 21 | { 22 | Mods = source.Mods; 23 | Presence = source.Presence; 24 | Status = source.Status; 25 | Team = source.Team; 26 | } 27 | 28 | public void Clear() 29 | { 30 | Mods = Mods.None; 31 | Presence = null; 32 | Status = SlotStatus.Open; 33 | Team = SlotTeams.Neutral; 34 | } 35 | } 36 | 37 | public class Match 38 | { 39 | public const int MAX_PLAYERS = 16; 40 | public bool PasswordRequired => GamePassword is not null; 41 | 42 | public string GameName; 43 | public int Id { get; set; } 44 | public readonly Slot[] Slots = new Slot[MAX_PLAYERS]; 45 | public Beatmap Beatmap; 46 | public string BeatmapChecksum; 47 | public int BeatmapId = -1; 48 | public bool InProgress; 49 | public Mods ActiveMods; 50 | public Presence Host; 51 | public int Seed; 52 | 53 | public int NeedLoad; 54 | 55 | public Channel Channel; 56 | public readonly List Presences = new (); 57 | 58 | public MatchTypes Type; 59 | public PlayMode PlayMode; 60 | public MatchScoringTypes ScoringType; 61 | public MatchTeamTypes TeamType; 62 | public MultiSpecialModes SpecialModes; 63 | 64 | public string GamePassword; 65 | 66 | public bool FreeMods; 67 | 68 | public Match() 69 | { 70 | for (var i = 0; i < MAX_PLAYERS; i++) 71 | Slots[i] = new (); 72 | } 73 | 74 | public Slot FreeSlot => Slots.FirstOrDefault(slot => slot.Status == SlotStatus.Open); 75 | 76 | public void Unready(SlotStatus status) 77 | { 78 | foreach (var slot in Slots) 79 | if (slot.Status == status) 80 | slot.Status = SlotStatus.NotReady; 81 | } 82 | 83 | public async Task Start() 84 | { 85 | var hasBeatmapPrs = new List(); 86 | 87 | foreach (var slot in Slots) 88 | { 89 | if ((slot.Status & SlotStatus.HasPlayer) != 0 && slot.Status != SlotStatus.NoMap) 90 | { 91 | slot.Status = SlotStatus.Playing; 92 | 93 | ++NeedLoad; 94 | hasBeatmapPrs.Add(slot.Presence); 95 | } 96 | } 97 | 98 | InProgress = true; 99 | 100 | foreach (var presence in hasBeatmapPrs) 101 | await presence.MatchStart(this); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /oyasumi/Objects/Packet.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Enums; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace oyasumi.Objects 5 | { 6 | public struct Packet 7 | { 8 | public PacketType Type { get; set; } 9 | public byte[] Data { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /oyasumi/Objects/VisualSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace oyasumi.Objects 8 | { 9 | public class VisualSettings 10 | { 11 | public int DimLevel { get; set; } 12 | public bool DisableSamples { get; set; } 13 | public bool DisableSkins { get; set; } 14 | public bool DisableStoryboard { get; set; } 15 | public bool BackgroundVisible { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /oyasumi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "oyasumi": { 4 | "commandName": "Project", 5 | "commandLineArgs": "--urls=http://localhost:5000" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /oyasumi/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Builder; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.AspNetCore.Http.Features; 10 | using Microsoft.AspNetCore.HttpsPolicy; 11 | using Microsoft.Extensions.Configuration; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using Microsoft.Extensions.Hosting; 14 | using Microsoft.Extensions.Logging; 15 | using oyasumi.Database; 16 | using oyasumi.Enums; 17 | using oyasumi.Extensions; 18 | using oyasumi.Managers; 19 | using oyasumi.Objects; 20 | using oyasumi.Utilities; 21 | using Serilog; 22 | using Serilog.Events; 23 | using Serilog.Sinks.Discord; 24 | 25 | namespace oyasumi 26 | { 27 | public class Startup 28 | { 29 | public Startup(IConfiguration configuration) => 30 | Configuration = configuration; 31 | 32 | public IConfiguration Configuration { get; } 33 | 34 | // This method gets called by the runtime. Use this method to add services to the container. 35 | public void ConfigureServices(IServiceCollection services) 36 | { 37 | services.AddControllersWithViews(); 38 | 39 | services.Configure(x => 40 | { 41 | x.ValueLengthLimit = int.MaxValue; 42 | x.MultipartBodyLengthLimit = int.MaxValue; 43 | x.MemoryBufferThreshold = int.MaxValue; 44 | x.BufferBodyLengthLimit = int.MaxValue; 45 | x.MultipartBoundaryLengthLimit = int.MaxValue; 46 | x.MultipartHeadersLengthLimit = int.MaxValue; 47 | } 48 | ); 49 | } 50 | 51 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 52 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) 53 | { 54 | DbContext.Load(); 55 | AppDomain.CurrentDomain.ProcessExit += (sender, e) => 56 | { 57 | DbContext.Save(); 58 | }; 59 | 60 | if (env.IsDevelopment()) 61 | { 62 | app.UseDeveloperExceptionPage(); 63 | } 64 | else 65 | { 66 | app.UseExceptionHandler("/Home/Error"); 67 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 68 | app.UseHsts(); 69 | } 70 | 71 | if (!Directory.Exists("./data/")) 72 | Directory.CreateDirectory("./data/"); 73 | if (!Directory.Exists("./data/beatmaps/")) 74 | Directory.CreateDirectory("./data/beatmaps/"); 75 | if (!Directory.Exists("./data/avatars/")) 76 | Directory.CreateDirectory("./data/avatars/"); 77 | if (!Directory.Exists("./data/osr/")) 78 | Directory.CreateDirectory("./data/osr/"); 79 | 80 | var vanillaStats = DbContext.VanillaStats; 81 | var relaxStats = DbContext.RelaxStats; 82 | 83 | var friends = DbContext.Friends; 84 | //var tokens = DbContext.Tokens; 85 | 86 | foreach (var v in vanillaStats) 87 | { 88 | Base.UserStatsCache[LeaderboardMode.Vanilla].TryAdd(v.Key, v.Value); 89 | } 90 | 91 | foreach (var r in relaxStats) 92 | { 93 | Base.UserStatsCache[LeaderboardMode.Relax].TryAdd(r.Key, r.Value); 94 | } 95 | 96 | foreach (var f in friends) 97 | Base.FriendCache.TryAdd(f.Friend1, new()); 98 | 99 | foreach (var f in friends) 100 | Base.FriendCache[f.Friend1].Add(f.Friend2); 101 | 102 | /*foreach (var t in tokens) 103 | Base.TokenCache.Add(t.UserToken, t.UserId, t);*/ 104 | 105 | ChannelManager.Channels.TryAdd("#osu", new ("#osu", "Default osu! channel", 1, true)); 106 | 107 | foreach (var chan in DbContext.Channels) 108 | ChannelManager.Channels.TryAdd(chan.Name, new (chan.Name, chan.Topic, 1, chan.Public)); 109 | 110 | var bot = new Presence(int.MaxValue, "oyasumi", 0, 0f, 0, 0, 0, 0) 111 | { 112 | Status = 113 | { 114 | Status = ActionStatuses.Watching, 115 | StatusText = "for sneaky gamers" 116 | } 117 | }; 118 | 119 | 120 | PresenceManager.Add(bot); 121 | // TODO: remove this lol 122 | // TODO: Replace by something better than Thread() 123 | // Disconnect inactive users and other stuff 124 | /*new Thread(() => 125 | { 126 | while (true) 127 | { 128 | if (Base.BeatmapDbStatusUpdate.Any()) 129 | { 130 | while (Base.BeatmapDbStatusUpdate.TryDequeue(out var item)) 131 | { 132 | if (item.IsSet) 133 | { 134 | var result = item; 135 | var beatmaps = _context.Beatmaps 136 | .AsEnumerable() 137 | .Where(x => result.Beatmap.SetId == x.BeatmapSetId); 138 | 139 | foreach (var b in beatmaps) 140 | b.Status = item.Beatmap.Status; 141 | } 142 | else 143 | { 144 | var result = item; 145 | _context.Beatmaps 146 | .FirstOrDefault(x => x.BeatmapId == result.Beatmap.Id) 147 | .Status = item.Beatmap.Status; 148 | } 149 | } 150 | 151 | _context.SaveChanges(); 152 | } 153 | 154 | if (Base.UserDbUpdate.Count > 0) 155 | { 156 | while (Base.UserDbUpdate.TryDequeue(out var u)) 157 | { 158 | var user = _context.Users.FirstOrDefault(x => x.Id == u.Id); 159 | user.Password = u.Password; 160 | } 161 | 162 | _context.SaveChanges(); 163 | } 164 | 165 | var currentTime = Time.CurrentUnixTimestamp; 166 | var presences = PresenceManager.Presences.Values; 167 | 168 | if (!presences.Any()) continue; 169 | 170 | foreach (var pr in presences) 171 | { 172 | if (pr.Username == "oyasumi") continue; 173 | if (currentTime - pr.LastPing < 60 || pr.LastPing == 0) 174 | continue; 175 | 176 | PresenceManager.Remove(pr); 177 | 178 | Console.WriteLine($"Remove {pr.Username}"); 179 | } 180 | Thread.Sleep(30); 181 | } 182 | }).Start(); 183 | */ 184 | //app.UseHttpsRedirection(); 185 | 186 | app.UseRouting(); 187 | 188 | app.UseAuthorization(); 189 | 190 | app.UseEndpoints(endpoints => 191 | { 192 | endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); 193 | }); 194 | } 195 | } 196 | } -------------------------------------------------------------------------------- /oyasumi/ThirdParty/AwaitableInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace Microsoft.Extensions.Internal 10 | { 11 | internal struct AwaitableInfo 12 | { 13 | public Type AwaiterType { get; } 14 | public PropertyInfo AwaiterIsCompletedProperty { get; } 15 | public MethodInfo AwaiterGetResultMethod { get; } 16 | public MethodInfo AwaiterOnCompletedMethod { get; } 17 | public MethodInfo AwaiterUnsafeOnCompletedMethod { get; } 18 | public Type ResultType { get; } 19 | public MethodInfo GetAwaiterMethod { get; } 20 | 21 | public AwaitableInfo( 22 | Type awaiterType, 23 | PropertyInfo awaiterIsCompletedProperty, 24 | MethodInfo awaiterGetResultMethod, 25 | MethodInfo awaiterOnCompletedMethod, 26 | MethodInfo awaiterUnsafeOnCompletedMethod, 27 | Type resultType, 28 | MethodInfo getAwaiterMethod) 29 | { 30 | AwaiterType = awaiterType; 31 | AwaiterIsCompletedProperty = awaiterIsCompletedProperty; 32 | AwaiterGetResultMethod = awaiterGetResultMethod; 33 | AwaiterOnCompletedMethod = awaiterOnCompletedMethod; 34 | AwaiterUnsafeOnCompletedMethod = awaiterUnsafeOnCompletedMethod; 35 | ResultType = resultType; 36 | GetAwaiterMethod = getAwaiterMethod; 37 | } 38 | 39 | public static bool IsTypeAwaitable(Type type, out AwaitableInfo awaitableInfo) 40 | { 41 | // Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347 42 | 43 | // Awaitable must have method matching "object GetAwaiter()" 44 | var getAwaiterMethod = type.GetRuntimeMethods().FirstOrDefault(m => 45 | m.Name.Equals("GetAwaiter", StringComparison.OrdinalIgnoreCase) 46 | && m.GetParameters().Length == 0 47 | && m.ReturnType != null); 48 | if (getAwaiterMethod == null) 49 | { 50 | awaitableInfo = default(AwaitableInfo); 51 | return false; 52 | } 53 | 54 | var awaiterType = getAwaiterMethod.ReturnType; 55 | 56 | // Awaiter must have property matching "bool IsCompleted { get; }" 57 | var isCompletedProperty = awaiterType.GetRuntimeProperties().FirstOrDefault(p => 58 | p.Name.Equals("IsCompleted", StringComparison.OrdinalIgnoreCase) 59 | && p.PropertyType == typeof(bool) 60 | && p.GetMethod != null); 61 | if (isCompletedProperty == null) 62 | { 63 | awaitableInfo = default(AwaitableInfo); 64 | return false; 65 | } 66 | 67 | // Awaiter must implement INotifyCompletion 68 | var awaiterInterfaces = awaiterType.GetInterfaces(); 69 | var implementsINotifyCompletion = awaiterInterfaces.Any(t => t == typeof(INotifyCompletion)); 70 | if (!implementsINotifyCompletion) 71 | { 72 | awaitableInfo = default(AwaitableInfo); 73 | return false; 74 | } 75 | 76 | // INotifyCompletion supplies a method matching "void OnCompleted(Action action)" 77 | var iNotifyCompletionMap = awaiterType 78 | .GetTypeInfo() 79 | .GetRuntimeInterfaceMap(typeof(INotifyCompletion)); 80 | var onCompletedMethod = iNotifyCompletionMap.InterfaceMethods.Single(m => 81 | m.Name.Equals("OnCompleted", StringComparison.OrdinalIgnoreCase) 82 | && m.ReturnType == typeof(void) 83 | && m.GetParameters().Length == 1 84 | && m.GetParameters()[0].ParameterType == typeof(Action)); 85 | 86 | // Awaiter optionally implements ICriticalNotifyCompletion 87 | var implementsICriticalNotifyCompletion = awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion)); 88 | MethodInfo unsafeOnCompletedMethod; 89 | if (implementsICriticalNotifyCompletion) 90 | { 91 | // ICriticalNotifyCompletion supplies a method matching "void UnsafeOnCompleted(Action action)" 92 | var iCriticalNotifyCompletionMap = awaiterType 93 | .GetTypeInfo() 94 | .GetRuntimeInterfaceMap(typeof(ICriticalNotifyCompletion)); 95 | unsafeOnCompletedMethod = iCriticalNotifyCompletionMap.InterfaceMethods.Single(m => 96 | m.Name.Equals("UnsafeOnCompleted", StringComparison.OrdinalIgnoreCase) 97 | && m.ReturnType == typeof(void) 98 | && m.GetParameters().Length == 1 99 | && m.GetParameters()[0].ParameterType == typeof(Action)); 100 | } 101 | else 102 | { 103 | unsafeOnCompletedMethod = null; 104 | } 105 | 106 | // Awaiter must have method matching "void GetResult" or "T GetResult()" 107 | var getResultMethod = awaiterType.GetRuntimeMethods().FirstOrDefault(m => 108 | m.Name.Equals("GetResult") 109 | && m.GetParameters().Length == 0); 110 | if (getResultMethod == null) 111 | { 112 | awaitableInfo = default(AwaitableInfo); 113 | return false; 114 | } 115 | 116 | awaitableInfo = new AwaitableInfo( 117 | awaiterType, 118 | isCompletedProperty, 119 | getResultMethod, 120 | onCompletedMethod, 121 | unsafeOnCompletedMethod, 122 | getResultMethod.ReturnType, 123 | getAwaiterMethod); 124 | return true; 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /oyasumi/ThirdParty/CoercedAwaitableInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq.Expressions; 6 | 7 | namespace Microsoft.Extensions.Internal 8 | { 9 | internal struct CoercedAwaitableInfo 10 | { 11 | public AwaitableInfo AwaitableInfo { get; } 12 | public Expression CoercerExpression { get; } 13 | public Type CoercerResultType { get; } 14 | public bool RequiresCoercion => CoercerExpression != null; 15 | 16 | public CoercedAwaitableInfo(AwaitableInfo awaitableInfo) 17 | { 18 | AwaitableInfo = awaitableInfo; 19 | CoercerExpression = null; 20 | CoercerResultType = null; 21 | } 22 | 23 | public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType, AwaitableInfo coercedAwaitableInfo) 24 | { 25 | CoercerExpression = coercerExpression; 26 | CoercerResultType = coercerResultType; 27 | AwaitableInfo = coercedAwaitableInfo; 28 | } 29 | 30 | public static bool IsTypeAwaitable(Type type, out CoercedAwaitableInfo info) 31 | { 32 | if (AwaitableInfo.IsTypeAwaitable(type, out var directlyAwaitableInfo)) 33 | { 34 | info = new CoercedAwaitableInfo(directlyAwaitableInfo); 35 | return true; 36 | } 37 | 38 | // It's not directly awaitable, but maybe we can coerce it. 39 | // Currently we support coercing FSharpAsync. 40 | if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type, 41 | out var coercerExpression, 42 | out var coercerResultType)) 43 | { 44 | if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo)) 45 | { 46 | info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo); 47 | return true; 48 | } 49 | } 50 | 51 | info = default(CoercedAwaitableInfo); 52 | return false; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /oyasumi/ThirdParty/ObjectMethodExecutorAwaitable.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace Microsoft.Extensions.Internal 8 | { 9 | /// 10 | /// Provides a common awaitable structure that can 11 | /// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an 12 | /// application-defined custom awaitable. 13 | /// 14 | public struct ObjectMethodExecutorAwaitable 15 | { 16 | private readonly object _customAwaitable; 17 | private readonly Func _getAwaiterMethod; 18 | private readonly Func _isCompletedMethod; 19 | private readonly Func _getResultMethod; 20 | private readonly Action _onCompletedMethod; 21 | private readonly Action _unsafeOnCompletedMethod; 22 | 23 | // Perf note: since we're requiring the customAwaitable to be supplied here as an object, 24 | // this will trigger a further allocation if it was a value type (i.e., to box it). We can't 25 | // fix this by making the customAwaitable type generic, because the calling code typically 26 | // does not know the type of the awaitable/awaiter at compile-time anyway. 27 | // 28 | // However, we could fix it by not passing the customAwaitable here at all, and instead 29 | // passing a func that maps directly from the target object (e.g., controller instance), 30 | // target method (e.g., action method info), and params array to the custom awaiter in the 31 | // GetAwaiter() method below. In effect, by delaying the actual method call until the 32 | // upstream code calls GetAwaiter on this ObjectMethodExecutorAwaitable instance. 33 | // This optimization is not currently implemented because: 34 | // [1] It would make no difference when the awaitable was an object type, which is 35 | // by far the most common scenario (e.g., System.Task). 36 | // [2] It would be complex - we'd need some kind of object pool to track all the parameter 37 | // arrays until we needed to use them in GetAwaiter(). 38 | // We can reconsider this in the future if there's a need to optimize for ValueTask 39 | // or other value-typed awaitables. 40 | 41 | public ObjectMethodExecutorAwaitable( 42 | object customAwaitable, 43 | Func getAwaiterMethod, 44 | Func isCompletedMethod, 45 | Func getResultMethod, 46 | Action onCompletedMethod, 47 | Action unsafeOnCompletedMethod) 48 | { 49 | _customAwaitable = customAwaitable; 50 | _getAwaiterMethod = getAwaiterMethod; 51 | _isCompletedMethod = isCompletedMethod; 52 | _getResultMethod = getResultMethod; 53 | _onCompletedMethod = onCompletedMethod; 54 | _unsafeOnCompletedMethod = unsafeOnCompletedMethod; 55 | } 56 | 57 | public Awaiter GetAwaiter() 58 | { 59 | var customAwaiter = _getAwaiterMethod(_customAwaitable); 60 | return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod, _unsafeOnCompletedMethod); 61 | } 62 | 63 | public struct Awaiter : ICriticalNotifyCompletion 64 | { 65 | private readonly object _customAwaiter; 66 | private readonly Func _isCompletedMethod; 67 | private readonly Func _getResultMethod; 68 | private readonly Action _onCompletedMethod; 69 | private readonly Action _unsafeOnCompletedMethod; 70 | 71 | public Awaiter( 72 | object customAwaiter, 73 | Func isCompletedMethod, 74 | Func getResultMethod, 75 | Action onCompletedMethod, 76 | Action unsafeOnCompletedMethod) 77 | { 78 | _customAwaiter = customAwaiter; 79 | _isCompletedMethod = isCompletedMethod; 80 | _getResultMethod = getResultMethod; 81 | _onCompletedMethod = onCompletedMethod; 82 | _unsafeOnCompletedMethod = unsafeOnCompletedMethod; 83 | } 84 | 85 | public bool IsCompleted => _isCompletedMethod(_customAwaiter); 86 | 87 | public object GetResult() => _getResultMethod(_customAwaiter); 88 | 89 | public void OnCompleted(Action continuation) 90 | { 91 | _onCompletedMethod(_customAwaiter, continuation); 92 | } 93 | 94 | public void UnsafeOnCompleted(Action continuation) 95 | { 96 | // If the underlying awaitable implements ICriticalNotifyCompletion, use its UnsafeOnCompleted. 97 | // If not, fall back on using its OnCompleted. 98 | // 99 | // Why this is safe: 100 | // - Implementing ICriticalNotifyCompletion is a way of saying the caller can choose whether it 101 | // needs the execution context to be preserved (which it signals by calling OnCompleted), or 102 | // that it doesn't (which it signals by calling UnsafeOnCompleted). Obviously it's faster *not* 103 | // to preserve and restore the context, so we prefer that where possible. 104 | // - If a caller doesn't need the execution context to be preserved and hence calls UnsafeOnCompleted, 105 | // there's no harm in preserving it anyway - it's just a bit of wasted cost. That's what will happen 106 | // if a caller sees that the proxy implements ICriticalNotifyCompletion but the proxy chooses to 107 | // pass the call on to the underlying awaitable's OnCompleted method. 108 | 109 | var underlyingMethodToUse = _unsafeOnCompletedMethod ?? _onCompletedMethod; 110 | underlyingMethodToUse(_customAwaiter, continuation); 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /oyasumi/Utilities/Calculator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | using oyasumi.Database.Models; 9 | using oyasumi.Enums; 10 | using oyasumi.Objects; 11 | 12 | namespace oyasumi.Utilities 13 | { 14 | public static class Calculator 15 | { 16 | public static async Task GetBeatmap(string md5, int id = 0) 17 | { 18 | var file = $"./data/beatmaps/{md5}.osu"; 19 | 20 | if (File.Exists(file)) 21 | return md5; 22 | 23 | using var httpClient = new HttpClient(); 24 | var data = await httpClient.GetByteArrayAsync($"https://osu.ppy.sh/osu/{id}"); 25 | 26 | md5 = Crypto.ComputeHash(data); // probably md5 got updated, so re-compute it. 27 | 28 | await File.WriteAllBytesAsync($"./data/beatmaps/{md5}.osu", data); 29 | return md5; 30 | } 31 | 32 | public static async Task GetBeatmapByMd5(string md5) 33 | { 34 | var file = $"./data/beatmaps/{md5}.osu"; 35 | 36 | if (File.Exists(file)) 37 | return md5; 38 | 39 | using var client = new HttpClient(); 40 | 41 | var resp = await client.GetAsync($"{Config.Properties.BeatmapMirror}/api/md5/{md5}"); 42 | 43 | if (!resp.IsSuccessStatusCode) // if map not found or mirror is down then set status to NotSubmitted 44 | return string.Empty; 45 | 46 | var beatmap = JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync()); 47 | 48 | var requestedDifficulty = beatmap.ChildrenBeatmaps.FirstOrDefault(x => x.FileMD5 == md5); 49 | var data = await client.GetByteArrayAsync($"https://osu.ppy.sh/osu/{requestedDifficulty.BeatmapID}"); 50 | 51 | md5 = Crypto.ComputeHash(data); // probably md5 got updated, so re-compute it. 52 | 53 | await File.WriteAllBytesAsync($"./data/beatmaps/{md5}.osu", data); 54 | 55 | return md5; 56 | } 57 | 58 | public static async Task CalculatePerformancePoints(DbScore score) 59 | { 60 | var beatmapMd5 = await GetBeatmapByMd5(score.FileChecksum); 61 | 62 | if (beatmapMd5 == string.Empty) 63 | return 0.0; 64 | 65 | var workingBeatmap = new ProcessorWorkingBeatmap($"./data/beatmaps/{beatmapMd5}.osu"); 66 | 67 | var psp = new ProcessorScoreDecoder(workingBeatmap); 68 | var parsedScore = psp.Parse(score); 69 | 70 | var categoryAttribs = new Dictionary(System.StringComparer.Ordinal); 71 | return (double)parsedScore.ScoreInfo.Ruleset 72 | .CreateInstance() 73 | .CreatePerformanceCalculator(workingBeatmap, parsedScore.ScoreInfo) 74 | .Calculate(categoryAttribs); 75 | } 76 | 77 | public static async Task CalculatePerformancePoints(Score score) 78 | { 79 | var beatmapMd5 = await GetBeatmap(score.FileChecksum, score.Beatmap.Id); 80 | 81 | var beatmap = new ProcessorWorkingBeatmap($"./data/beatmaps/{beatmapMd5}.osu"); 82 | 83 | var psp = new ProcessorScoreDecoder(beatmap); 84 | var parsedScore = psp.Parse(score, $"./data/osr/{score.ReplayChecksum}.osr"); 85 | 86 | var categoryAttribs = new Dictionary(StringComparer.Ordinal); 87 | return (double)parsedScore.ScoreInfo.Ruleset 88 | .CreateInstance() 89 | .CreatePerformanceCalculator(beatmap, parsedScore.ScoreInfo) 90 | .Calculate(categoryAttribs); 91 | } 92 | 93 | public static string CalculateRank(DbScore score) 94 | { 95 | var tHits = score.Count50 + score.Count100 + score.Count300 + score.CountMiss; 96 | var ratio300 = (float)score.Count300 / tHits; 97 | var ratio50 = (float)score.Count50 / tHits; 98 | if (ratio300 == 1) 99 | { 100 | return (score.Mods & Mods.Hidden) > 0 || 101 | (score.Mods & Mods.Flashlight) > 0 102 | ? "SSHD" 103 | : "SS"; 104 | } 105 | 106 | if (ratio300 > 0.9 && ratio50 <= 0.01 && score.CountMiss == 0) 107 | { 108 | return (score.Mods & Mods.Hidden) > 0 || 109 | (score.Mods & Mods.Flashlight) > 0 110 | ? "SHD" 111 | : "S"; 112 | } 113 | 114 | if ((ratio300 > 0.8 && score.CountMiss == 0) || ratio300 > 0.9) 115 | return "A"; 116 | if ((ratio300 > 0.7 && score.CountMiss == 0) || ratio300 > 0.8) 117 | return "B"; 118 | return ratio300 > 0.6 ? "C" : "D"; 119 | } 120 | 121 | public static double CalculateAccuracy(Score score) 122 | { 123 | int totalHits; 124 | float accuracy = 0; 125 | 126 | switch (score.PlayMode) 127 | { 128 | case PlayMode.Osu: 129 | totalHits = score.Count300 + score.Count100 + score.Count50 + score.CountMiss; 130 | 131 | if (totalHits > 0) 132 | accuracy = (float)(((score.Count50 * 50.0) + (score.Count100 * 100.0) + (score.Count300 * 300.0)) / (totalHits * 300.0)); 133 | 134 | return accuracy; 135 | 136 | case PlayMode.Taiko: 137 | totalHits = score.Count50 + score.Count100 + score.Count300 + score.CountMiss; 138 | return totalHits > 0 139 | ? (double)(score.Count100 * 150 + score.Count300 * 300) / (totalHits * 300) 140 | : 1; 141 | 142 | case PlayMode.CatchTheBeat: 143 | totalHits = score.Count50 + score.Count100 + score.Count300 + score.CountMiss + score.CountKatu; 144 | return totalHits > 0 ? (double)(score.Count50 + score.Count100 + score.Count300) / totalHits : 1; 145 | 146 | case PlayMode.OsuMania: 147 | totalHits = score.Count50 + score.Count100 + score.Count300 + score.CountMiss + score.CountGeki + 148 | score.CountKatu; 149 | return totalHits > 0 150 | ? (double)(score.Count50 * 50 + score.Count100 * 100 + score.CountKatu * 200 + 151 | (score.Count300 + score.CountGeki) * 300) / (totalHits * 300) 152 | : 1; 153 | default: 154 | return 0; 155 | } 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /oyasumi/Utilities/Crypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | using Org.BouncyCastle.Crypto.Engines; 6 | using Org.BouncyCastle.Crypto.Modes; 7 | using Org.BouncyCastle.Crypto.Paddings; 8 | using Org.BouncyCastle.Crypto.Parameters; 9 | using scr = Org.BouncyCastle.Crypto.Generators.SCrypt; 10 | 11 | namespace oyasumi.Utilities 12 | { 13 | public static class Crypto 14 | { 15 | public static string GenerateHash(string password) => 16 | BCrypt.Net.BCrypt.HashPassword(password, 10); // BCrypt.generate_hash(password, 10); 17 | 18 | public static bool VerifyPassword(string plaintext, string hash) => 19 | BCrypt.Net.BCrypt.Verify(plaintext, hash); // BCrypt.validate_password(plaintext, hash); 20 | 21 | public static string ComputeHash(string str) 22 | => ComputeHash(Encoding.UTF8.GetBytes(str)); 23 | 24 | public static string ComputeHash(byte[] buffer) 25 | { 26 | var md5 = MD5.Create(); 27 | var data = md5.ComputeHash(buffer); 28 | var sb = new StringBuilder(); 29 | 30 | foreach (var b in data) 31 | sb.Append(b.ToString("x2")); 32 | 33 | return sb.ToString(); 34 | } 35 | 36 | public static string DecryptString(string message, byte[] key, string iv) 37 | { 38 | var msgBytes = Convert.FromBase64String(message); 39 | var engine = new RijndaelEngine(256); 40 | 41 | var blockCipher = new CbcBlockCipher(engine); 42 | var cipher = new PaddedBufferedBlockCipher(blockCipher, new Pkcs7Padding()); 43 | 44 | var keyParam = new KeyParameter(key); 45 | var keyParamWithIv = new ParametersWithIV(keyParam, Convert.FromBase64String(iv), 0, 32); 46 | 47 | cipher.Init(false, keyParamWithIv); 48 | 49 | var comparisonBytes = new byte[cipher.GetOutputSize(msgBytes.Length)]; 50 | var length = cipher.ProcessBytes(msgBytes, comparisonBytes, 0); 51 | 52 | cipher.DoFinal(comparisonBytes, length); 53 | 54 | return Encoding.UTF8.GetString(comparisonBytes); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /oyasumi/Utilities/IMultiKeyDictionary.cs: -------------------------------------------------------------------------------- 1 | namespace oyasumi.Utilities 2 | { 3 | public interface IMultiKeyDictionary 4 | { 5 | void Add(object primaryRaw, object secondaryRaw, object valueRaw); 6 | object ValueAt(int index); 7 | int Count { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /oyasumi/Utilities/MultiKeyDictionary.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Collections.Concurrent; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace oyasumi.Utilities 7 | { 8 | public class MultiKeyDictionary : IMultiKeyDictionary 9 | { 10 | private readonly ConcurrentDictionary _firstDictionary = new(); 11 | private readonly ConcurrentDictionary _secondDictionary = new(); 12 | private readonly ConcurrentDictionary _gateDictionary = new(); 13 | private readonly ConcurrentDictionary _reverseGateDictionary = new(); 14 | 15 | public V this[T1 primary] 16 | { 17 | get => _firstDictionary.TryGetValue(primary, out var item) ? item : default; 18 | set 19 | { 20 | _gateDictionary.TryGetValue(primary, out var secondary); 21 | _firstDictionary.TryUpdate(primary, value, 22 | _firstDictionary.TryGetValue(primary, out var item1) ? item1 : default); 23 | _secondDictionary.TryUpdate(secondary, value, 24 | _secondDictionary.TryGetValue(secondary, out var item2) ? item2 : default); 25 | } 26 | } 27 | 28 | public V this[T2 secondary] 29 | { 30 | get => _secondDictionary.TryGetValue(secondary, out var item) ? item : default; 31 | set 32 | { 33 | _reverseGateDictionary.TryGetValue(secondary, out var primary); 34 | _firstDictionary.TryUpdate(primary, value, 35 | _firstDictionary.TryGetValue(primary, out var item1) ? item1 : default); 36 | _secondDictionary.TryUpdate(secondary, value, 37 | _secondDictionary.TryGetValue(secondary, out var item2) ? item2 : default); 38 | } 39 | } 40 | 41 | public V[] Values => _firstDictionary.Values.ToArray(); 42 | public void RemoveAll(Predicate match) 43 | { 44 | var dictCopy = _firstDictionary; 45 | foreach (var kvp in dictCopy) 46 | { 47 | if (match(kvp.Value)) 48 | Remove(kvp.Key); 49 | } 50 | } 51 | 52 | public void ExecuteWhere(Predicate filter, Func action) 53 | { 54 | foreach (var kvp in _firstDictionary) 55 | { 56 | if (filter(kvp.Value)) 57 | Modify(kvp.Key, action(kvp.Value)); 58 | } 59 | } 60 | 61 | public int Count => _firstDictionary.Count; 62 | 63 | public void Add(T1 primary, T2 secondary, V value) 64 | { 65 | _firstDictionary.TryAdd(primary, value); 66 | _secondDictionary.TryAdd(secondary, value); 67 | 68 | _gateDictionary.TryAdd(primary, secondary); 69 | _reverseGateDictionary.TryAdd(secondary, primary); 70 | } 71 | 72 | public void Modify(T1 primary, V value) 73 | { 74 | _gateDictionary.TryGetValue(primary, out var secondary); 75 | 76 | this[primary] = value; 77 | this[secondary] = value; 78 | } 79 | 80 | public void Add(object primaryRaw, object secondaryRaw, object valueRaw) 81 | { 82 | var primary = (T1)primaryRaw; 83 | var secondary = (T2)secondaryRaw; 84 | var value = (V)valueRaw; 85 | 86 | _firstDictionary.TryAdd(primary, value); 87 | _secondDictionary.TryAdd(secondary, value); 88 | 89 | _gateDictionary.TryAdd(primary, secondary); 90 | _reverseGateDictionary.TryAdd(secondary, primary); 91 | } 92 | 93 | public object ValueAt(int index) 94 | => _firstDictionary.ElementAt(index).Value; 95 | 96 | public void Remove(T1 primary) 97 | { 98 | _gateDictionary.TryGetValue(primary, out var secondary); 99 | 100 | _firstDictionary.TryRemove(primary, out _); 101 | _secondDictionary.TryRemove(secondary, out _); 102 | 103 | _gateDictionary.TryRemove(primary, out _); 104 | _reverseGateDictionary.TryRemove(secondary, out _); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /oyasumi/Utilities/NetUtils.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace oyasumi.Utilities 12 | { 13 | public static class NetUtils 14 | { 15 | public static ContentResult Content(string message, int code = 200) 16 | { 17 | var result = new ContentResult 18 | { 19 | StatusCode = code, 20 | Content = message 21 | }; 22 | 23 | return result; 24 | } 25 | 26 | public static ContentResult StatusCode(object message, int code) 27 | { 28 | var dict = new Dictionary 29 | { 30 | ["result"] = message 31 | }; 32 | 33 | var result = new ContentResult 34 | { 35 | ContentType = "application/json", 36 | StatusCode = code, 37 | Content = JsonConvert.SerializeObject(dict) 38 | }; 39 | 40 | return result; 41 | } 42 | 43 | public static ContentResult Error(object message) 44 | { 45 | var dict = new Dictionary 46 | { 47 | ["result"] = message 48 | }; 49 | 50 | var result = new ContentResult 51 | { 52 | ContentType = "application/json", 53 | StatusCode = 401, 54 | Content = JsonConvert.SerializeObject(dict) 55 | }; 56 | 57 | return result; 58 | } 59 | 60 | public static async Task<(string CountryCode, float Latitude, float Longitude)> FetchGeoLocation(string ip) 61 | { 62 | using var httpClient = new HttpClient(); 63 | var data = (dynamic)JsonConvert.DeserializeObject(await httpClient.GetStringAsync("http://ip-api.com/json/" + ip)); 64 | 65 | return (data.countryCode, data.lat, data.lon); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /oyasumi/Utilities/ReflectionUtils.cs: -------------------------------------------------------------------------------- 1 | using oyasumi.Database; 2 | using oyasumi.Objects; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Reflection.Emit; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using FastExpressionCompiler; 11 | using Microsoft.Extensions.Internal; 12 | using System.Linq.Expressions; 13 | using System.Runtime.CompilerServices; 14 | 15 | namespace oyasumi.Utilities 16 | { 17 | public class ReflectionUtils 18 | { 19 | public delegate void HandleDelegate(Packet p, Presence pr); 20 | public static Action CompilePacketHandler(MethodInfo meth) 21 | { 22 | var dynMethod = new DynamicMethod("Handle", 23 | typeof(void), 24 | new Type[] { 25 | typeof(Packet), 26 | typeof(Presence) 27 | }, 28 | typeof(string).Module); 29 | 30 | var il = dynMethod.GetILGenerator(); 31 | 32 | il.Emit(OpCodes.Ldarg_0); 33 | il.Emit(OpCodes.Ldarg_1); 34 | il.Emit(OpCodes.Call, meth); 35 | il.Emit(OpCodes.Ret); 36 | 37 | return (Action)dynMethod.CreateDelegate(typeof(Action)); 38 | } 39 | 40 | public static ObjectMethodExecutorCompiledFast GetExecutor(MethodInfo meth) 41 | { 42 | return ObjectMethodExecutorCompiledFast.Create(meth, meth.DeclaringType.GetTypeInfo()); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /oyasumi/Utilities/Replay.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using osu.Game.IO.Legacy; 3 | using oyasumi.Enums; 4 | 5 | namespace oyasumi.Utilities 6 | { 7 | public class Replay 8 | { 9 | public string BeatmapChecksum { get; set; } 10 | public PlayMode PlayMode { get; set;} 11 | 12 | public string Username { get; set;} 13 | 14 | public ushort Count300 { get; set; } 15 | public ushort Count100 { get; set; } 16 | public ushort Count50 { get; set; } 17 | public ushort CountGeki { get; set; } 18 | public ushort CountKatu { get; set; } 19 | public ushort CountMiss { get; set; } 20 | public int TotalScore { get; set; } 21 | public int MaxCombo { get; set; } 22 | public Mods Mods { get; set; } 23 | 24 | public static Replay Parse(Stream stream) 25 | { 26 | using var sr = new SerializationReader(stream); 27 | var replay = new Replay(); 28 | 29 | replay.PlayMode = (PlayMode) sr.ReadByte(); 30 | sr.ReadInt32(); // Version 31 | 32 | replay.BeatmapChecksum = sr.ReadString(); 33 | replay.Username = sr.ReadString(); 34 | 35 | sr.ReadString(); // Replay Checksum 36 | 37 | replay.Count300 = sr.ReadUInt16(); 38 | replay.Count100 = sr.ReadUInt16(); 39 | replay.Count50 = sr.ReadUInt16(); 40 | replay.CountGeki = sr.ReadUInt16(); 41 | replay.CountKatu = sr.ReadUInt16(); 42 | replay.CountMiss = sr.ReadUInt16(); 43 | 44 | replay.TotalScore = sr.ReadInt32(); 45 | replay.MaxCombo = sr.ReadUInt16(); 46 | 47 | sr.ReadBoolean(); // Perfect 48 | 49 | replay.Mods = (Mods)sr.ReadInt32(); 50 | 51 | sr.ReadString(); // HpGraph 52 | sr.ReadInt64(); // Date 53 | sr.ReadByteArray(); // Replay Data 54 | 55 | sr.ReadInt64(); // OnlineID, i guess we don't need check 2012-2014 clients 56 | 57 | // TODO: replay data parsing (i do it when will make anticheat) 58 | 59 | return replay; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /oyasumi/Utilities/Time.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace oyasumi.Utilities 4 | { 5 | public static class Time 6 | { 7 | public static int ToUnixTimestamp(this DateTime self) => 8 | (int) self.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; 9 | 10 | public static int CurrentUnixTimestamp 11 | => (int)DateTimeOffset.Now.ToUnixTimeSeconds(); 12 | 13 | public static DateTime UnixTimestampFromDateTime(int timestamp) => 14 | new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) 15 | .AddSeconds(timestamp) 16 | .ToLocalTime(); 17 | } 18 | } -------------------------------------------------------------------------------- /oyasumi/lib/BCrypt.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxCherry/oyasumi/cf815ef35dc959947aaae552f6aff1cc85701727/oyasumi/lib/BCrypt.dll -------------------------------------------------------------------------------- /oyasumi/lib/oppai.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxCherry/oyasumi/cf815ef35dc959947aaae552f6aff1cc85701727/oyasumi/lib/oppai.dll -------------------------------------------------------------------------------- /oyasumi/oyasumi.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | preview 6 | net5.0 7 | AnyCPU;x86 8 | true 9 | 10 | 11 | 12 | x64 13 | 14 | 15 | 16 | x86 17 | 18 | 19 | 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | all 31 | runtime; build; native; contentfiles; analyzers; buildtransitive 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /oyasumi/oyasumi.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | oyasumi 5 | MvcControllerEmptyScaffolder 6 | root/Controller 7 | 600 8 | True 9 | False 10 | True 11 | 12 | False 13 | 14 | 15 | ProjectDebugger 16 | 17 | 18 | ProjectDebugger 19 | 20 | --------------------------------------------------------------------------------