├── MainServer ├── appsettings.json ├── Configurations │ ├── rank.json │ ├── database.json │ ├── matching.json │ ├── auth.json │ ├── server.json │ ├── logging.json │ └── events.json ├── Database │ ├── music.db3 │ ├── music471.db3 │ ├── music4MAX.db3 │ ├── music471omni.db3 │ ├── music474omni.db3 │ └── music4MAX465.db3 ├── BundledCertificates │ ├── cert.pfx │ ├── root.pfx │ └── Import.ps1 ├── .config │ └── dotnet-tools.json ├── Controllers │ ├── Game │ │ ├── ResponeController.cs │ │ ├── UpdateController.cs │ │ ├── UploadController.cs │ │ ├── IncomController.cs │ │ ├── ServiceOptionController.cs │ │ ├── AliveController.cs │ │ ├── ServerController.cs │ │ └── RankingController.cs │ ├── BaseController.cs │ └── API │ │ ├── PlayOptionController.cs │ │ ├── TestController.cs │ │ └── ProfilesController.cs ├── Properties │ └── launchSettings.json ├── Filters │ └── ApiExceptionFilterAttributes.cs └── app.manifest ├── .deepsource.toml ├── WebUI ├── wwwroot │ ├── favicon.ico │ ├── css │ │ └── open-iconic │ │ │ ├── font │ │ │ └── fonts │ │ │ │ ├── open-iconic.eot │ │ │ │ ├── open-iconic.otf │ │ │ │ ├── open-iconic.ttf │ │ │ │ └── open-iconic.woff │ │ │ └── ICON-LICENSE │ └── index.html ├── Pages │ ├── Index.razor │ ├── TotalResult.razor.cs │ ├── Cards.razor.cs │ ├── Dialogs │ │ ├── FavoriteDialog.razor │ │ └── ChangePlayerNameDialog.razor │ ├── TotalResult.razor │ ├── PlayRecords.razor.cs │ └── Option.razor ├── GlobalUsings.cs ├── Common │ ├── Models │ │ ├── Avatar.cs │ │ ├── Navigator.cs │ │ └── Title.cs │ ├── NavMenu.razor │ ├── MainLayout.razor.cs │ └── MainLayout.razor ├── App.razor ├── _Imports.razor ├── Services │ ├── IDataService.cs │ └── DataService.cs ├── Program.cs ├── WebUI.csproj ├── Properties │ └── launchSettings.json └── WebUI.sln ├── Domain ├── Entities │ ├── ShopScoreRank.cs │ ├── GlobalScoreRank.cs │ ├── MonthlyScoreRank.cs │ ├── MusicAou.cs │ ├── MusicExtra.cs │ ├── CardBdatum.cs │ ├── CardPlayCount.cs │ ├── OnlineMatch.cs │ ├── PlayNumRank.cs │ ├── MusicUnlock.cs │ ├── CardMain.cs │ ├── ScoreRank.cs │ ├── CardDetail.cs │ └── OnlineMatchEntry.cs ├── Enums │ ├── RewardType.cs │ ├── CardCommandType.cs │ ├── Difficulty.cs │ ├── ClearState.cs │ ├── RankingCommandType.cs │ ├── ShowFeverTranceOption.cs │ ├── ShowFastSlowOption.cs │ ├── CardReturnCode.cs │ └── CardRequestType.cs ├── Config │ ├── RelayConfig.cs │ ├── RankConfig.cs │ ├── EventConfig.cs │ ├── UnlockRewardConfig.cs │ ├── GameConfig.cs │ └── AuthConfig.cs ├── Models │ └── Event.cs └── Domain.csproj ├── Shared ├── Dto │ └── Api │ │ ├── SecondPlayOptionDto.cs │ │ ├── ClientCardDto.cs │ │ ├── MusicFavoriteDto.cs │ │ └── FirstPlayOptionDto.cs ├── Models │ ├── PlayOptionData.cs │ ├── StagePlayRecord.cs │ ├── SongPlayRecord.cs │ ├── TotalResultData.cs │ └── ServiceResult.cs └── Shared.csproj ├── doc ├── unlock_reward.md ├── genunlock.py ├── card_bdata.md ├── card_detail.md └── unlockables.csv ├── Infrastructure ├── Exceptions │ └── EventExceptions.cs ├── Common │ └── PathHelper.cs ├── Migrations │ ├── MigrationHelper.cs │ └── 20230214162154_AddPlayNumRank.cs ├── Infrastructure.csproj ├── DependencyInjection.cs └── Persistence │ └── MusicDbContext.cs ├── Application ├── Common │ ├── Exceptions │ │ └── CardExistsException.cs │ ├── Helpers │ │ └── TimeHelper.cs │ ├── Base │ │ └── RequestHandlerBase.cs │ ├── Behaviours │ │ ├── LoggingBehaviour.cs │ │ └── UnhandledExceptionBehaviour.cs │ └── Extensions │ │ └── XmlSerializationExtensions.cs ├── Mappers │ ├── ScoreRankMapper.cs │ ├── MusicMapper.cs │ ├── MusicAouMapper.cs │ ├── MusicExtraMapper.cs │ ├── CardMapper.cs │ ├── PlayNumRankMapper.cs │ ├── OnlineMatchMapper.cs │ ├── CardBDatumMapper.cs │ └── CardDetailMapper.cs ├── Interfaces │ ├── IEventManagerService.cs │ ├── IRequestWrapper.cs │ ├── IMusicDbContext.cs │ ├── ICardDependencyAggregate.cs │ └── ICardDbContext.cs ├── Dto │ └── Game │ │ ├── TotalTrophyDto.cs │ │ ├── MusicAouDto.cs │ │ ├── MusicExtraDto.cs │ │ ├── CardBDatumDto.cs │ │ ├── SessionDto.cs │ │ ├── CoinDto.cs │ │ ├── SkinDto.cs │ │ ├── TitleDto.cs │ │ ├── NavigatorDto.cs │ │ ├── SoundEffectDto.cs │ │ ├── ItemDto.cs │ │ ├── AvatarDto.cs │ │ ├── PlayNumRankDto.cs │ │ ├── MusicDto.cs │ │ ├── UnlockKeyNumDto.cs │ │ ├── CardDto.cs │ │ ├── ScoreRankDto.cs │ │ ├── CardDetailDto.cs │ │ └── OnlineMatchEntryDto.cs ├── Game │ ├── Rank │ │ ├── RankParam.cs │ │ ├── TenpoScoreRankContainer.cs │ │ ├── RankStatus.cs │ │ ├── GetEventRankQuery.cs │ │ ├── GetMonthlyScoreRankQuery.cs │ │ ├── GetPlayNumRankQuery.cs │ │ └── GetWPlayNumRankQuery.cs │ ├── Card │ │ ├── Write │ │ │ ├── WriteCoinCommand.cs │ │ │ ├── WriteCondCommand.cs │ │ │ ├── WriteItemCommand.cs │ │ │ ├── WriteSkinCommand.cs │ │ │ ├── WriteTitleCommand.cs │ │ │ ├── WriteAvatarCommand.cs │ │ │ ├── WriteNavigatorCommand.cs │ │ │ ├── WriteMusicDetailCommand.cs │ │ │ ├── WriteSoundEffectCommand.cs │ │ │ ├── WriteUnlockKeynumCommand.cs │ │ │ ├── WriteCardDetailCommand.cs │ │ │ ├── WriteCardCommand.cs │ │ │ └── WriteCardBDataCommand.cs │ │ ├── Management │ │ │ ├── CardReissueCommand.cs │ │ │ └── CardRegisterCommand.cs │ │ ├── CardDependencyAggregate.cs │ │ ├── CardRequest.cs │ │ ├── Read │ │ │ ├── ReadCondQuery.cs │ │ │ ├── ReadGetMessageQuery.cs │ │ │ ├── ReadEventRewardQuery.cs │ │ │ ├── ReadTotalTrophyQuery.cs │ │ │ ├── ReadCoinQuery.cs │ │ │ ├── ReadMusicExtraQuery.cs │ │ │ ├── ReadMusicQuery.cs │ │ │ ├── ReadItemQuery.cs │ │ │ ├── ReadSkinQuery.cs │ │ │ ├── ReadAvatarQuery.cs │ │ │ ├── ReadCardQuery.cs │ │ │ ├── ReadTitleQuery.cs │ │ │ ├── ReadNavigatorQuery.cs │ │ │ ├── ReadSoundEffectQuery.cs │ │ │ ├── ReadUnlockKeynumQuery.cs │ │ │ ├── ReadMusicAouQuery.cs │ │ │ ├── ReadCardBDataQuery.cs │ │ │ ├── ReadCardDetailQuery.cs │ │ │ └── ReadAllCardDetailsQuery.cs │ │ ├── Session │ │ │ └── GetSessionCommand.cs │ │ └── OnlineMatching │ │ │ └── UploadOnlineMatchingResultCommand.cs │ ├── Server │ │ └── GetDataQuery.cs │ └── Option │ │ └── PlayCountQuery.cs ├── GlobalUsings.cs ├── Api │ ├── GetCardsQuery.cs │ ├── SetPlayerNameCommand.cs │ ├── SetFavoriteMusicCommand.cs │ ├── GetPlayOptionQuery.cs │ ├── UnlockAllMusicCommand.cs │ └── SetPlayOptionCommand.cs ├── Application.csproj └── Jobs │ ├── UpdatePlayNumRankJob.cs │ └── MaintainNullValuesJob.cs ├── GCRelayServer ├── RelayPacketTypes.cs ├── DictEntry.cs ├── GCRelayServer.csproj ├── RelayPacket.cs └── Program.cs ├── GC-local-server-rewrite.sln.DotSettings └── GC-local-server-rewrite.sln.DotSettings.user /MainServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedHosts": "*" 3 | } 4 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "csharp" 5 | enabled = true -------------------------------------------------------------------------------- /MainServer/Configurations/rank.json: -------------------------------------------------------------------------------- 1 | { 2 | "Rank": { 3 | "RefreshIntervalHours" : 12 4 | } 5 | } -------------------------------------------------------------------------------- /MainServer/Configurations/database.json: -------------------------------------------------------------------------------- 1 | { 2 | "CardDbName": "card.db3", 3 | "MusicDbName": "music474omni.db3" 4 | } -------------------------------------------------------------------------------- /WebUI/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/WebUI/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Domain/Entities/ShopScoreRank.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public class ShopScoreRank : ScoreRank 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /MainServer/Database/music.db3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/MainServer/Database/music.db3 -------------------------------------------------------------------------------- /Domain/Entities/GlobalScoreRank.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public class GlobalScoreRank : ScoreRank 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /Domain/Entities/MonthlyScoreRank.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public class MonthlyScoreRank : ScoreRank 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /MainServer/Configurations/matching.json: -------------------------------------------------------------------------------- 1 | { 2 | "Relay": { 3 | "RelayServer": "127.0.0.1", 4 | "RelayPort": 3333 5 | } 6 | } -------------------------------------------------------------------------------- /MainServer/Database/music471.db3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/MainServer/Database/music471.db3 -------------------------------------------------------------------------------- /MainServer/Database/music4MAX.db3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/MainServer/Database/music4MAX.db3 -------------------------------------------------------------------------------- /MainServer/Database/music471omni.db3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/MainServer/Database/music471omni.db3 -------------------------------------------------------------------------------- /MainServer/Database/music474omni.db3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/MainServer/Database/music474omni.db3 -------------------------------------------------------------------------------- /MainServer/Database/music4MAX465.db3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/MainServer/Database/music4MAX465.db3 -------------------------------------------------------------------------------- /MainServer/BundledCertificates/cert.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/MainServer/BundledCertificates/cert.pfx -------------------------------------------------------------------------------- /MainServer/BundledCertificates/root.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/MainServer/BundledCertificates/root.pfx -------------------------------------------------------------------------------- /WebUI/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | Index 4 | 5 | Welcome to GC Local Server WebUI -------------------------------------------------------------------------------- /WebUI/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/WebUI/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /WebUI/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/WebUI/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /WebUI/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/WebUI/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /WebUI/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asesidaa/GC-local-server-rewrite/HEAD/WebUI/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /Domain/Entities/MusicAou.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public class MusicAou 4 | { 5 | public long MusicId { get; set; } 6 | 7 | public bool UseFlag { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /Domain/Entities/MusicExtra.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public class MusicExtra 4 | { 5 | public long MusicId { get; set; } 6 | 7 | public bool UseFlag { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /WebUI/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using System.Net.Http.Json; 4 | global using Microsoft.AspNetCore.Components; 5 | global using MudBlazor; 6 | global using Shared.Models; -------------------------------------------------------------------------------- /Shared/Dto/Api/SecondPlayOptionDto.cs: -------------------------------------------------------------------------------- 1 | namespace Shared.Dto.Api; 2 | 3 | public class SecondPlayOptionDto 4 | { 5 | public long CardId { get; set; } 6 | 7 | public long NavigatorId { get; set; } 8 | } -------------------------------------------------------------------------------- /Shared/Dto/Api/ClientCardDto.cs: -------------------------------------------------------------------------------- 1 | namespace Shared.Dto.Api; 2 | 3 | public class ClientCardDto 4 | { 5 | public long CardId { get; set; } 6 | 7 | public string PlayerName { get; set; } = string.Empty; 8 | } -------------------------------------------------------------------------------- /WebUI/Common/Models/Avatar.cs: -------------------------------------------------------------------------------- 1 | namespace WebUI.Common.Models; 2 | 3 | public class Avatar 4 | { 5 | public uint AvatarId { get; set; } 6 | 7 | public string AvatarName { get; set; } = string.Empty; 8 | } -------------------------------------------------------------------------------- /Domain/Enums/RewardType.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Enums; 2 | 3 | public enum RewardType 4 | { 5 | Music = 0, 6 | Item = 1, 7 | Avatar = 2, 8 | Navigator = 7, 9 | Coin = 8 10 | } -------------------------------------------------------------------------------- /doc/unlock_reward.md: -------------------------------------------------------------------------------- 1 | | Reward type | case | 2 | | ----------- | ---- | 3 | | Music | 0 | 4 | | Item | 1 | 5 | | Avatar | 2 | 6 | | Navigator | 7 | 7 | | GC | 8 | 8 | 9 | -------------------------------------------------------------------------------- /Domain/Enums/CardCommandType.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Enums; 2 | 3 | public enum CardCommandType 4 | { 5 | CardReadRequest = 256, 6 | CardWriteRequest = 768, 7 | RegisterRequest = 512, 8 | ReissueRequest = 1536 9 | } -------------------------------------------------------------------------------- /Infrastructure/Exceptions/EventExceptions.cs: -------------------------------------------------------------------------------- 1 | namespace Infrastructure.Exceptions; 2 | 3 | public class EventFileNotFoundException : Exception 4 | { 5 | } 6 | 7 | public class EventFileTypeUnknownException : Exception 8 | { 9 | } -------------------------------------------------------------------------------- /Application/Common/Exceptions/CardExistsException.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Common.Exceptions; 2 | 3 | public class CardExistsException : Exception 4 | { 5 | public CardExistsException(string? message) : base(message) 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /MainServer/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "7.0.3", 7 | "commands": [ 8 | "dotnet-ef" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Domain/Enums/Difficulty.cs: -------------------------------------------------------------------------------- 1 | using NetEscapades.EnumGenerators; 2 | 3 | namespace Domain.Enums; 4 | 5 | [EnumExtensions] 6 | public enum Difficulty 7 | { 8 | Simple = 0, 9 | Normal = 1, 10 | Hard = 2, 11 | Extra = 3 12 | } -------------------------------------------------------------------------------- /Shared/Dto/Api/MusicFavoriteDto.cs: -------------------------------------------------------------------------------- 1 | namespace Shared.Dto.Api; 2 | 3 | public class MusicFavoriteDto 4 | { 5 | public long CardId { get; set; } 6 | 7 | public int MusicId { get; set; } 8 | 9 | public bool IsFavorite { get; set; } 10 | } -------------------------------------------------------------------------------- /Domain/Entities/CardBdatum.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public partial class CardBdatum 4 | { 5 | public long CardId { get; set; } 6 | 7 | public string? Bdata { get; set; } 8 | 9 | public long BdataSize { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /Domain/Entities/CardPlayCount.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public partial class CardPlayCount 4 | { 5 | public long CardId { get; set; } 6 | 7 | public long PlayCount { get; set; } 8 | 9 | public DateTime LastPlayedTime { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /Domain/Enums/ClearState.cs: -------------------------------------------------------------------------------- 1 | using NetEscapades.EnumGenerators; 2 | 3 | namespace Domain.Enums; 4 | 5 | [EnumExtensions] 6 | public enum ClearState 7 | { 8 | NotPlayed = 0, 9 | Failed, 10 | Clear, 11 | NoMiss, 12 | FullChain, 13 | Perfect 14 | } -------------------------------------------------------------------------------- /WebUI/Common/NavMenu.razor: -------------------------------------------------------------------------------- 1 | 2 | Home 3 | Cards 4 | -------------------------------------------------------------------------------- /Domain/Config/RelayConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Config; 2 | 3 | public class RelayConfig 4 | { 5 | public const string RELAY_SECTION = "Relay"; 6 | 7 | public string RelayServer { get; set; } = "127.0.0.1"; 8 | 9 | public int RelayPort { get; set; } = 3333; 10 | } -------------------------------------------------------------------------------- /Application/Mappers/ScoreRankMapper.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Riok.Mapperly.Abstractions; 3 | 4 | namespace Application.Mappers; 5 | 6 | [Mapper] 7 | public static partial class ScoreRankMapper 8 | { 9 | public static partial ScoreRankDto ScoreRankToDto(this ScoreRank rank); 10 | } -------------------------------------------------------------------------------- /Application/Interfaces/IEventManagerService.cs: -------------------------------------------------------------------------------- 1 | using Domain.Models; 2 | 3 | namespace Application.Interfaces; 4 | 5 | public interface IEventManagerService 6 | { 7 | public void InitializeEvents(); 8 | 9 | public bool UseEvents(); 10 | 11 | public IEnumerable GetEvents(); 12 | } -------------------------------------------------------------------------------- /Application/Dto/Game/TotalTrophyDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class TotalTrophyDto 4 | { 5 | [XmlElement(ElementName = "card_id")] 6 | public long CardId { get; set; } 7 | 8 | [XmlElement(ElementName = "total_trophy_num")] 9 | public int TrophyNum { get; set; } 10 | } -------------------------------------------------------------------------------- /MainServer/Configurations/auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "Auth": { 3 | "Enabled": false, 4 | "Machines": [ 5 | { 6 | "TenpoId": "1", 7 | "TenpoName": "店舗1", 8 | "Pref": "東京都", 9 | "Location": "渋谷区", 10 | "Mac": "000000000000" 11 | } 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /Domain/Entities/OnlineMatch.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public class OnlineMatch 4 | { 5 | public long MatchId { get; set; } 6 | 7 | public List Entries { get; set; } = new(); 8 | 9 | public bool IsOpen { get; set; } 10 | 11 | public Guid Guid { get; set; } 12 | } -------------------------------------------------------------------------------- /Shared/Models/PlayOptionData.cs: -------------------------------------------------------------------------------- 1 | using Shared.Dto.Api; 2 | 3 | namespace Shared.Models; 4 | 5 | public class PlayOptionData 6 | { 7 | public long CardId { get; set; } 8 | 9 | public FirstPlayOptionDto OptionPart1 { get; set; } = new(); 10 | 11 | public SecondPlayOptionDto OptionPart2 { get; set; } = new(); 12 | } -------------------------------------------------------------------------------- /Domain/Enums/RankingCommandType.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Enums; 2 | 3 | public enum RankingCommandType 4 | { 5 | ShopRank = 4098, 6 | GlobalRank = 4119, 7 | PlayNumRank = 6657, 8 | EventRank = 6661, 9 | WScoreRank = 6663, 10 | WPlayNumRank = 6664, 11 | WShopRank = 6665, 12 | MonthlyRank = 6666 13 | } -------------------------------------------------------------------------------- /MainServer/Configurations/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "Kestrel": { 3 | "Endpoints": { 4 | "Http": { 5 | "Url": "http://*:80" 6 | }, 7 | "Https": { 8 | "Url": "https://*:443" 9 | }, 10 | "Test": { 11 | "Url": "http://*:5107" 12 | } 13 | } 14 | }, 15 | "ServerIp": "127.0.0.1" 16 | } -------------------------------------------------------------------------------- /Application/Interfaces/IRequestWrapper.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Application.Interfaces; 4 | 5 | public interface IRequestWrapper : IRequest> 6 | { 7 | 8 | } 9 | 10 | public interface IRequestHandlerWrapper : IRequestHandler> where TIn : IRequestWrapper 11 | { 12 | 13 | } -------------------------------------------------------------------------------- /Application/Interfaces/IMusicDbContext.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | 3 | namespace Application.Interfaces; 4 | 5 | public interface IMusicDbContext 6 | { 7 | public DbSet MusicAous { get; set; } 8 | 9 | public DbSet MusicExtras { get; set; } 10 | 11 | public DbSet MusicUnlocks { get; set; } 12 | } -------------------------------------------------------------------------------- /Application/Mappers/MusicMapper.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Riok.Mapperly.Abstractions; 3 | 4 | namespace Application.Mappers; 5 | 6 | [Mapper] 7 | public static partial class MusicMapper 8 | { 9 | public static partial MusicDto MusicToDto(this MusicUnlock music); 10 | 11 | private static int BoolToInt(bool value) => value ? 1 : 0; 12 | } -------------------------------------------------------------------------------- /Application/Interfaces/ICardDependencyAggregate.cs: -------------------------------------------------------------------------------- 1 | using Domain.Config; 2 | using Microsoft.Extensions.Options; 3 | 4 | namespace Application.Interfaces; 5 | 6 | public interface ICardDependencyAggregate 7 | { 8 | ICardDbContext CardDbContext { get; } 9 | IMusicDbContext MusicDbContext { get; } 10 | 11 | IOptions Options { get; } 12 | } -------------------------------------------------------------------------------- /Application/Mappers/MusicAouMapper.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Riok.Mapperly.Abstractions; 3 | 4 | namespace Application.Mappers; 5 | 6 | [Mapper] 7 | public static partial class MusicAouMapper 8 | { 9 | public static partial MusicAouDto MusicAouToDto(this MusicAou musicAou); 10 | 11 | private static int BoolToInt(bool value) => value ? 1 : 0; 12 | } -------------------------------------------------------------------------------- /Domain/Config/RankConfig.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Domain.Config; 4 | 5 | public class RankConfig 6 | { 7 | public const string RANK_SECTION = "Rank"; 8 | 9 | [Required, Range(1, double.MaxValue, ErrorMessage = "Value for {0} must be greater than 0!")] 10 | public int RefreshIntervalHours { get; set; } = 24; 11 | } -------------------------------------------------------------------------------- /MainServer/Controllers/Game/ResponeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace MainServer.Controllers.Game; 4 | 5 | [ApiController] 6 | [Route("service/respone")] 7 | public class ResponeController : ControllerBase 8 | { 9 | [HttpPost("respone.php")] 10 | public IActionResult Respone() 11 | { 12 | return Ok("1"); 13 | } 14 | } -------------------------------------------------------------------------------- /Application/Mappers/MusicExtraMapper.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Riok.Mapperly.Abstractions; 3 | 4 | namespace Application.Mappers; 5 | 6 | [Mapper] 7 | public static partial class MusicExtraMapper 8 | { 9 | public static partial MusicExtraDto MusicExtraToDto(this MusicExtra musicExtra); 10 | 11 | private static int BoolToInt(bool value) => value ? 1 : 0; 12 | } -------------------------------------------------------------------------------- /Domain/Models/Event.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Models; 2 | 3 | public class Event 4 | { 5 | public string Name { get; set; } = string.Empty; 6 | 7 | public string Md5 { get; set; } = string.Empty; 8 | 9 | public int Index { get; set; } 10 | 11 | public string NotBefore { get; set; } = string.Empty; 12 | 13 | public string NotAfter { get; set; } = string.Empty; 14 | } -------------------------------------------------------------------------------- /Application/Dto/Game/MusicAouDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class MusicAouDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement(ElementName = "music_id")] 9 | public int MusicId { get; set; } 10 | 11 | [XmlElement(ElementName = "use_flag")] 12 | public int UseFlag { get; set; } 13 | } -------------------------------------------------------------------------------- /Application/Game/Rank/RankParam.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Application.Game.Rank; 4 | 5 | public class RankParam 6 | { 7 | [XmlElement(ElementName = "card_id")] 8 | [DefaultValue("0")] 9 | public long CardId { get; set; } 10 | 11 | [XmlElement(ElementName = "tenpo_id")] 12 | [DefaultValue("0")] 13 | public int TenpoId { get; set; } 14 | } -------------------------------------------------------------------------------- /Application/Dto/Game/MusicExtraDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class MusicExtraDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement(ElementName = "music_id")] 9 | public int MusicId { get; set; } 10 | 11 | [XmlElement(ElementName = "use_flag")] 12 | public int UseFlag { get; set; } 13 | } -------------------------------------------------------------------------------- /Application/Common/Helpers/TimeHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Common.Helpers; 2 | 3 | public static class TimeHelper 4 | { 5 | public static string CurrentTimeToString() 6 | { 7 | return DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss"); 8 | } 9 | 10 | public static string DateToString(DateTime time) 11 | { 12 | return time.ToString("yyyy/MM/dd hh:mm:ss"); 13 | } 14 | } -------------------------------------------------------------------------------- /MainServer/Controllers/Game/UpdateController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace MainServer.Controllers.Game; 4 | 5 | [ApiController] 6 | [Route("update/cgi")] 7 | public class UpdateController : ControllerBase 8 | { 9 | // TODO: Check update properly 10 | [HttpGet("check.php")] 11 | public IActionResult UpdateCheck() 12 | { 13 | return NotFound(); 14 | } 15 | } -------------------------------------------------------------------------------- /Application/Dto/Game/CardBDatumDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class CardBDatumDto 4 | { 5 | [XmlElement(ElementName = "card_id")] 6 | public long CardId { get; set; } 7 | 8 | [XmlElement(ElementName = "bdata")] 9 | public string CardBdata { get; set; } = string.Empty; 10 | 11 | [XmlElement(ElementName = "bdata_size")] 12 | public int BDataSize { get; set; } 13 | } -------------------------------------------------------------------------------- /Application/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using Microsoft.EntityFrameworkCore; 4 | global using System.Xml.Serialization; 5 | global using Application.Common.Base; 6 | global using Application.Common.Extensions; 7 | global using Shared.Dto.Api; 8 | global using Shared.Models; 9 | global using Application.Dto.Game; 10 | global using Application.Interfaces; 11 | global using Application.Mappers; -------------------------------------------------------------------------------- /GCRelayServer/RelayPacketTypes.cs: -------------------------------------------------------------------------------- 1 | namespace GCRelayServer; 2 | 3 | public static class RelayPacketTypes 4 | { 5 | public const ushort HEART_BEAT = 0xB0; 6 | 7 | public const ushort HEART_BEAT_RESPONSE = 0xB1; 8 | 9 | public const ushort START_MATCHING = 0xA0; 10 | 11 | public const ushort START_MATCHING_RESPONSE = 0xA1; 12 | 13 | public const ushort REGISTER_MATCHING = 0xA6; 14 | } -------------------------------------------------------------------------------- /Domain/Config/EventConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Config; 2 | 3 | public class EventConfig 4 | { 5 | public const string EVENT_SECTION = "Events"; 6 | 7 | public bool UseEvents { get; set; } 8 | 9 | public List EventFiles { get; set; } = new(); 10 | } 11 | 12 | public class EventFile 13 | { 14 | public string FileName { get; set; } = string.Empty; 15 | 16 | public int Index { get; set; } 17 | } -------------------------------------------------------------------------------- /Shared/Dto/Api/FirstPlayOptionDto.cs: -------------------------------------------------------------------------------- 1 | using Domain.Enums; 2 | 3 | namespace Shared.Dto.Api; 4 | 5 | public class FirstPlayOptionDto 6 | { 7 | public long CardId { get; set; } 8 | 9 | public long AvatarId { get; set; } 10 | 11 | public int TitleId { get; set; } 12 | 13 | public ShowFastSlowOption ShowFastSlowOption { get; set; } 14 | 15 | public ShowFeverTranceOption ShowFeverTranceOption { get; set; } 16 | } -------------------------------------------------------------------------------- /WebUI/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Not found 7 | 8 |

Sorry, there's nothing at this address.

9 |
10 |
11 |
-------------------------------------------------------------------------------- /WebUI/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Components.Forms 3 | @using Microsoft.AspNetCore.Components.Routing 4 | @using Microsoft.AspNetCore.Components.Web 5 | @using Microsoft.AspNetCore.Components.Web.Virtualization 6 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 7 | @using Microsoft.JSInterop 8 | @using MudBlazor 9 | @using WebUI 10 | @using WebUI.Common 11 | @using WebUI.Services 12 | @using WebUI.Common.Models -------------------------------------------------------------------------------- /Application/Mappers/CardMapper.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Riok.Mapperly.Abstractions; 3 | 4 | namespace Application.Mappers; 5 | 6 | [Mapper] 7 | public static partial class CardMapper 8 | { 9 | public static partial CardDto CardMainToCardDto(this CardMain cardMain); 10 | public static partial CardMain CardDtoToCardMain(this CardDto cardDto); 11 | 12 | public static partial ClientCardDto CardMainToClientDto(this CardMain cardMain); 13 | } -------------------------------------------------------------------------------- /Domain/Enums/ShowFeverTranceOption.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using NetEscapades.EnumGenerators; 3 | 4 | namespace Domain.Enums; 5 | 6 | [EnumExtensions] 7 | public enum ShowFeverTranceOption : long 8 | { 9 | [Display(Name = "Default option")] 10 | Default = 0, 11 | 12 | [Display(Name = "Show fever/trance")] 13 | Show = 1, 14 | 15 | [Display(Name = "Do not show fever/trance")] 16 | NotShow = 2 17 | } -------------------------------------------------------------------------------- /Shared/Models/StagePlayRecord.cs: -------------------------------------------------------------------------------- 1 | using Domain.Enums; 2 | 3 | namespace Shared.Models; 4 | 5 | public class StagePlayRecord 6 | { 7 | public int Score { get; set; } 8 | 9 | public int PlayCount { get; set; } 10 | 11 | public int MaxChain { get; set; } 12 | 13 | public Difficulty Difficulty { get; set; } 14 | 15 | public ClearState ClearState { get; set; } 16 | 17 | public DateTime LastPlayTime { get; set; } 18 | } -------------------------------------------------------------------------------- /Application/Mappers/PlayNumRankMapper.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Riok.Mapperly.Abstractions; 3 | 4 | namespace Application.Mappers; 5 | 6 | [Mapper] 7 | public static partial class PlayNumRankMapper 8 | { 9 | [MapProperty(nameof(PlayNumRank.MusicId), nameof(PlayNumRankDto.Pcol1))] 10 | [MapProperty(nameof(PlayNumRank.PlayCount), nameof(PlayNumRankDto.ScoreBi1))] 11 | public static partial PlayNumRankDto PlayNumRankToDto(this PlayNumRank rank); 12 | } -------------------------------------------------------------------------------- /Shared/Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Domain/Config/UnlockRewardConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Domain.Enums; 3 | 4 | namespace Domain.Config; 5 | 6 | public class UnlockRewardConfig 7 | { 8 | public int RewardId { get; set; } 9 | 10 | [JsonConverter(typeof(JsonStringEnumConverter))] 11 | public RewardType RewardType { get; set; } 12 | 13 | public int TargetId { get; set; } 14 | 15 | public int TargetNum { get; set; } 16 | 17 | public int KeyNum { get; set; } 18 | } -------------------------------------------------------------------------------- /Application/Game/Rank/TenpoScoreRankContainer.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Rank; 2 | 3 | [XmlRoot("root")] 4 | public class TenpoScoreRankContainer 5 | { 6 | [XmlArray(ElementName = "t_score_rank")] 7 | [XmlArrayItem(ElementName = "record")] 8 | // ReSharper disable once UnusedAutoPropertyAccessor.Global 9 | public List Ranks { get; init; } = new(); 10 | 11 | [XmlElement("ranking_status")] 12 | public RankStatus Status { get; set; } = new(); 13 | } -------------------------------------------------------------------------------- /MainServer/Controllers/Game/UploadController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace MainServer.Controllers.Game; 4 | [ApiController] 5 | [Route("service/upload")] 6 | public class UploadController : ControllerBase 7 | { 8 | private const string UPLOAD_RESPONSE = "1\n" + 9 | "OK"; 10 | 11 | [HttpPost("upload.php")] 12 | public IActionResult Upload() 13 | { 14 | return Ok(UPLOAD_RESPONSE); 15 | } 16 | } -------------------------------------------------------------------------------- /Shared/Models/SongPlayRecord.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Shared.Models; 4 | 5 | public class SongPlayRecord 6 | { 7 | public string Title { get; set; } = string.Empty; 8 | 9 | public string Artist { get; set; } = string.Empty; 10 | 11 | public int MusicId { get; set; } 12 | 13 | public bool IsFavorite { get; set; } 14 | 15 | public int TotalPlayCount { get; set; } 16 | 17 | public List StagePlayRecords { get; set; } = new(); 18 | } -------------------------------------------------------------------------------- /Domain/Entities/PlayNumRank.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public class PlayNumRank 4 | { 5 | public int MusicId { get; set; } 6 | 7 | public int PlayCount { get; set; } 8 | 9 | public int Rank { get; set; } 10 | 11 | public int Rank2 { get; set; } 12 | 13 | public int PrevRank { get; set; } 14 | public int PrevRank2 { get; set; } 15 | 16 | public string Title { get; set; } = string.Empty; 17 | 18 | public string Artist { get; set; } = string.Empty; 19 | } -------------------------------------------------------------------------------- /Domain/Entities/MusicUnlock.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public partial class MusicUnlock 4 | { 5 | public long MusicId { get; set; } 6 | 7 | public string Title { get; set; } = string.Empty; 8 | 9 | public string? Artist { get; set; } = string.Empty; 10 | 11 | public DateTime ReleaseDate { get; set; } 12 | 13 | public DateTime EndDate { get; set; } 14 | 15 | public bool NewFlag { get; set; } 16 | 17 | public bool UseFlag { get; set; } 18 | 19 | public bool CalcFlag { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /Domain/Config/GameConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Config; 2 | 3 | public class GameConfig 4 | { 5 | public const string GAME_SECTION = "Game"; 6 | 7 | public int AvatarCount { get; set; } 8 | 9 | public int NavigatorCount { get; set; } 10 | 11 | public int ItemCount { get; set; } 12 | 13 | public int SkinCount { get; set; } 14 | 15 | public int SeCount { get; set; } 16 | 17 | public int TitleCount { get; set; } 18 | 19 | public List UnlockRewards { get; set; } = new(); 20 | } -------------------------------------------------------------------------------- /Application/Game/Rank/RankStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Rank; 2 | 3 | public class RankStatus 4 | { 5 | [XmlElement("table_name")] 6 | public string TableName { get; set; } = string.Empty; 7 | 8 | [XmlElement("start_date")] 9 | public string StartDate { get; set; } = string.Empty; 10 | 11 | [XmlElement("end_date")] 12 | public string EndDate { get; set; } = string.Empty; 13 | 14 | [XmlElement("status")] 15 | public int Status { get; set; } 16 | 17 | [XmlElement("rows")] 18 | public int Rows { get; set; } 19 | } -------------------------------------------------------------------------------- /Domain/Enums/ShowFastSlowOption.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using NetEscapades.EnumGenerators; 3 | 4 | namespace Domain.Enums; 5 | 6 | [EnumExtensions] 7 | public enum ShowFastSlowOption : long 8 | { 9 | [Display(Name = "Default option")] 10 | Default = 0, 11 | 12 | [Display(Name = "Show fast/slow near avatars")] 13 | NearAvatar = 1, 14 | 15 | [Display(Name = "Show fast/show near judgement text")] 16 | NearJudgement = 2, 17 | 18 | [Display(Name = "Do not show fast/slow")] 19 | NotShow = 3 20 | } -------------------------------------------------------------------------------- /MainServer/Controllers/Game/IncomController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace MainServer.Controllers.Game; 4 | 5 | [ApiController] 6 | [Route("service/incom")] 7 | public class IncomController : ControllerBase 8 | { 9 | private const string INCOM_RESPONSE = "1+1"; 10 | 11 | [HttpPost("incom.php")] 12 | public IActionResult Incom() 13 | { 14 | return Ok(INCOM_RESPONSE); 15 | } 16 | 17 | [HttpPost("incomALL.php")] 18 | public IActionResult IncomAll() 19 | { 20 | return Ok(INCOM_RESPONSE); 21 | } 22 | } -------------------------------------------------------------------------------- /Application/Mappers/OnlineMatchMapper.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Riok.Mapperly.Abstractions; 3 | 4 | namespace Application.Mappers; 5 | 6 | [Mapper] 7 | public static partial class OnlineMatchMapper 8 | { 9 | public static partial OnlineMatchEntryDto OnlineMatchEntryToDto(this OnlineMatchEntry entry); 10 | 11 | public static partial OnlineMatchEntry DtoToOnlineMatchEntry(this OnlineMatchEntryDto entryDto); 12 | 13 | private static string DateTimeToString(DateTime dateTime) 14 | { 15 | return dateTime.ToString("yyyy/MM/dd hh:mm:ss"); 16 | } 17 | } -------------------------------------------------------------------------------- /Domain/Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | compile; build; native; contentfiles; analyzers; buildtransitive 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /WebUI/Services/IDataService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using WebUI.Common.Models; 3 | 4 | namespace WebUI.Services; 5 | 6 | public interface IDataService 7 | { 8 | public Task InitializeAsync(); 9 | 10 | public IReadOnlyList GetAvatarsSortedById(); 11 | 12 | public IReadOnlyList GetNavigatorsSortedById(); 13 | 14 | public IReadOnlyList GetTitlesSortedById(); 15 | 16 | public Avatar? GetAvatarById(uint id); 17 | 18 | public Title? GetTitleById(uint id); 19 | 20 | public Navigator? GetNavigatorById(uint id); 21 | } -------------------------------------------------------------------------------- /GCRelayServer/DictEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace GCRelayServer; 4 | 5 | public class DictEntry 6 | { 7 | public List<EndPoint> EndPoints { get; set; } = new(); 8 | 9 | public DateTime LastAccessTime { get; set; } = DateTime.Now; 10 | 11 | public void AddEndpoint(EndPoint endPoint, bool shouldClear = false) 12 | { 13 | if (shouldClear) 14 | { 15 | EndPoints.Clear(); 16 | } 17 | if (EndPoints.Contains(endPoint)) 18 | { 19 | return; 20 | } 21 | EndPoints.Add(endPoint); 22 | } 23 | } -------------------------------------------------------------------------------- /Domain/Config/AuthConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Config; 2 | 3 | public class AuthConfig 4 | { 5 | public const string AUTH_SECTION = "Auth"; 6 | 7 | public bool Enabled { get; set; } 8 | 9 | public List<Machine> Machines { get; set; } = new(); 10 | } 11 | 12 | public class Machine 13 | { 14 | public string TenpoId { get; set; } = string.Empty; 15 | public string TenpoName { get; set; } = string.Empty; 16 | public string Pref { get; set; } = string.Empty; 17 | public string Location { get; set; } = string.Empty; 18 | public string Mac { get; set; } = string.Empty; 19 | } -------------------------------------------------------------------------------- /Domain/Entities/CardMain.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public partial class CardMain 4 | { 5 | public long CardId { get; set; } 6 | 7 | public string PlayerName { get; set; } = string.Empty; 8 | 9 | public long ScoreI1 { get; set; } 10 | 11 | public long Fcol1 { get; set; } 12 | 13 | public long Fcol2 { get; set; } 14 | 15 | public long Fcol3 { get; set; } 16 | 17 | public string AchieveStatus { get; set; } = string.Empty; 18 | 19 | public string? Created { get; set; } = string.Empty; 20 | 21 | public string? Modified { get; set; } = string.Empty; 22 | } 23 | -------------------------------------------------------------------------------- /MainServer/Controllers/BaseController.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace MainServer.Controllers; 5 | 6 | public abstract class BaseController<T> : ControllerBase where T : BaseController<T> 7 | { 8 | private ILogger<T>? logger; 9 | 10 | private ISender? mediator; 11 | 12 | protected ISender Mediator => (mediator ??= HttpContext.RequestServices.GetService<ISender>()) ?? throw new InvalidOperationException(); 13 | 14 | protected ILogger<T> Logger => (logger ??= HttpContext.RequestServices.GetService<ILogger<T>>()) ?? throw new InvalidOperationException(); 15 | } -------------------------------------------------------------------------------- /Application/Dto/Game/SessionDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class SessionDto 4 | { 5 | [XmlElement(ElementName = "card_id")] 6 | public long CardId { get; set; } 7 | 8 | [XmlElement(ElementName = "mac_addr")] 9 | public string Mac { get; set; } = "000000000000"; 10 | 11 | [XmlElement(ElementName = "session_id")] 12 | public string SessionId { get; set; } = "12345678901234567890123456789012"; 13 | 14 | [XmlElement(ElementName = "expires")] 15 | public int Expires { get; set; } 16 | 17 | [XmlElement(ElementName = "player_id")] 18 | public int PlayerId { get; set; } 19 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteCoinCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Write; 2 | 3 | public record WriteCoinCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class WriteCoinCommandHandler : RequestHandlerBase<WriteCoinCommand, string> 6 | { 7 | public WriteCoinCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) {} 8 | 9 | public override Task<ServiceResult<string>> Handle(WriteCoinCommand request, CancellationToken cancellationToken) 10 | { 11 | // TODO: Add proper implementation 12 | return Task.FromResult(new ServiceResult<string>(request.Data)); 13 | } 14 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteCondCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Write; 2 | 3 | public record WriteCondCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class WriteCondCommandHandler : RequestHandlerBase<WriteCondCommand, string> 6 | { 7 | public WriteCondCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) {} 8 | 9 | public override Task<ServiceResult<string>> Handle(WriteCondCommand request, CancellationToken cancellationToken) 10 | { 11 | // TODO: Add proper implementation 12 | return Task.FromResult(new ServiceResult<string>(request.Data)); 13 | } 14 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteItemCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Write; 2 | 3 | public record WriteItemCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class WriteItemCommandHandler : RequestHandlerBase<WriteItemCommand, string> 6 | { 7 | public WriteItemCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) {} 8 | 9 | public override Task<ServiceResult<string>> Handle(WriteItemCommand request, CancellationToken cancellationToken) 10 | { 11 | // TODO: Add proper implementation 12 | return Task.FromResult(new ServiceResult<string>(request.Data)); 13 | } 14 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteSkinCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Write; 2 | 3 | public record WriteSkinCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class WriteSkinCommandHandler : RequestHandlerBase<WriteSkinCommand, string> 6 | { 7 | public WriteSkinCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) {} 8 | 9 | public override Task<ServiceResult<string>> Handle(WriteSkinCommand request, CancellationToken cancellationToken) 10 | { 11 | // TODO: Add proper implementation 12 | return Task.FromResult(new ServiceResult<string>(request.Data)); 13 | } 14 | } -------------------------------------------------------------------------------- /GCRelayServer/GCRelayServer.csproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Microsoft.NET.Sdk"> 2 | 3 | <PropertyGroup> 4 | <OutputType>Exe</OutputType> 5 | <TargetFramework>net7.0</TargetFramework> 6 | <ImplicitUsings>enable</ImplicitUsings> 7 | <Nullable>enable</Nullable> 8 | <LangVersion>11</LangVersion> 9 | </PropertyGroup> 10 | 11 | <ItemGroup> 12 | <PackageReference Include="BinarySerializer" Version="8.6.3.2" /> 13 | <PackageReference Include="NetCoreServer" Version="7.0.0" /> 14 | <PackageReference Include="Swan.Logging" Version="6.0.2-beta.96" /> 15 | </ItemGroup> 16 | 17 | </Project> 18 | -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteTitleCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Write; 2 | 3 | public record WriteTitleCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class WriteTitleCommandHandler : RequestHandlerBase<WriteTitleCommand, string> 6 | { 7 | public WriteTitleCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) {} 8 | 9 | public override Task<ServiceResult<string>> Handle(WriteTitleCommand request, CancellationToken cancellationToken) 10 | { 11 | // TODO: Add proper implementation 12 | return Task.FromResult(new ServiceResult<string>(request.Data)); 13 | } 14 | } -------------------------------------------------------------------------------- /MainServer/Controllers/Game/ServiceOptionController.cs: -------------------------------------------------------------------------------- 1 | using Application.Game.Option; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace MainServer.Controllers.Game; 5 | 6 | [ApiController] 7 | [Route("service/option")] 8 | public class ServiceOptionController : BaseController<ServiceOptionController> 9 | { 10 | [HttpGet("PlayInfo.php")] 11 | public async Task<ActionResult<string>> GetPlayCount([FromQuery(Name = "card_id")] long cardId) 12 | { 13 | var query = new PlayCountQuery(cardId); 14 | var count = await Mediator.Send(query); 15 | 16 | return Ok("1\n" + 17 | $"{count}"); 18 | } 19 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteAvatarCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Write; 2 | 3 | public record WriteAvatarCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class WriteAvatarCommandHandler : RequestHandlerBase<WriteAvatarCommand, string> 6 | { 7 | public WriteAvatarCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) {} 8 | 9 | public override Task<ServiceResult<string>> Handle(WriteAvatarCommand request, CancellationToken cancellationToken) 10 | { 11 | // TODO: Add proper implementation 12 | return Task.FromResult(new ServiceResult<string>(request.Data)); 13 | } 14 | } -------------------------------------------------------------------------------- /Application/Dto/Game/CoinDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class CoinDto 4 | { 5 | [XmlElement(ElementName = "card_id")] 6 | public long CardId { get; set; } 7 | 8 | [XmlElement(ElementName = "current")] 9 | public int CurrentCoins { get; set; } 10 | 11 | [XmlElement(ElementName = "total")] 12 | public int TotalCoins { get; set; } 13 | 14 | [XmlElement("monthly")] 15 | public int MonthlyCoins { get; set; } 16 | 17 | [XmlElement("created")] 18 | public string Created { get; set; } = string.Empty; 19 | 20 | [XmlElement("modified")] 21 | public string Modified { get; set; } = string.Empty; 22 | } -------------------------------------------------------------------------------- /WebUI/Common/MainLayout.razor.cs: -------------------------------------------------------------------------------- 1 | using MudBlazor; 2 | 3 | namespace WebUI.Common; 4 | 5 | public partial class MainLayout 6 | { 7 | bool drawerOpen = true; 8 | 9 | public bool IsDarkMode { get; set; } 10 | 11 | public MudThemeProvider MudThemeProvider { get; set; } = null!; 12 | 13 | protected override async Task OnAfterRenderAsync(bool firstRender) 14 | { 15 | if (firstRender) 16 | { 17 | IsDarkMode = await MudThemeProvider.GetSystemPreference(); 18 | StateHasChanged(); 19 | } 20 | } 21 | 22 | void DrawerToggle() 23 | { 24 | drawerOpen = !drawerOpen; 25 | } 26 | } -------------------------------------------------------------------------------- /WebUI/Common/Models/Navigator.cs: -------------------------------------------------------------------------------- 1 | namespace WebUI.Common.Models; 2 | 3 | public class Navigator 4 | { 5 | public uint Id { get; set; } 6 | 7 | public string NavigatorName { get; set; } = string.Empty; 8 | 9 | public NavigatorGenre Genre { get; set; } 10 | 11 | public string IllustrationCredit { get; set; } = string.Empty; 12 | 13 | public string ToolTipJp { get; set; } = string.Empty; 14 | 15 | public string ToolTipEn { get; set; } = string.Empty; 16 | } 17 | 18 | public enum NavigatorGenre 19 | { 20 | Default = 1, 21 | Original = 2, 22 | Game = 3, 23 | Touhou = 4, 24 | Vocaloid = 5, 25 | Collab = 6, 26 | } -------------------------------------------------------------------------------- /Application/Game/Card/Management/CardReissueCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Management; 2 | 3 | public record CardReissueCommand(long CardId) : IRequestWrapper<string>; 4 | 5 | public class ReissueCommandHandler : RequestHandlerBase<CardReissueCommand, string> 6 | { 7 | public ReissueCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) 8 | { 9 | } 10 | 11 | public override Task<ServiceResult<string>> Handle(CardReissueCommand request, CancellationToken cancellationToken) 12 | { 13 | // TODO: Support actual reissue 14 | return Task.FromResult(ServiceResult.Failed<string>(ServiceError.NotReissue)); 15 | } 16 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteNavigatorCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Write; 2 | 3 | public record WriteNavigatorCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class WriteNavigatorCommandHandler : RequestHandlerBase<WriteNavigatorCommand, string> 6 | { 7 | public WriteNavigatorCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) {} 8 | 9 | public override Task<ServiceResult<string>> Handle(WriteNavigatorCommand request, CancellationToken cancellationToken) 10 | { 11 | // TODO: Add proper implementation 12 | return Task.FromResult(new ServiceResult<string>(request.Data)); 13 | } 14 | } -------------------------------------------------------------------------------- /Application/Game/Card/CardDependencyAggregate.cs: -------------------------------------------------------------------------------- 1 | using Domain.Config; 2 | using Microsoft.Extensions.Options; 3 | 4 | namespace Application.Game.Card; 5 | 6 | public class CardDependencyAggregate : ICardDependencyAggregate 7 | { 8 | public CardDependencyAggregate(ICardDbContext cardDbContext, IMusicDbContext musicDbContext, IOptions<GameConfig> options) 9 | { 10 | CardDbContext = cardDbContext; 11 | MusicDbContext = musicDbContext; 12 | Options = options; 13 | } 14 | 15 | public ICardDbContext CardDbContext { get; } 16 | public IMusicDbContext MusicDbContext { get; } 17 | 18 | public IOptions<GameConfig> Options { get; } 19 | } -------------------------------------------------------------------------------- /Domain/Enums/CardReturnCode.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Enums; 2 | 3 | public enum CardReturnCode 4 | { 5 | /// <summary> 6 | /// Normal 7 | /// 処理は正常に完了しました in debug string 8 | /// </summary> 9 | Ok = 1, 10 | 11 | /// <summary> 12 | /// New card 13 | /// 未登録のカードです in debug string 14 | /// </summary> 15 | CardNotRegistered = 23, 16 | 17 | /// <summary> 18 | /// Not reissue, to determine whether it is a new card or reissued card 19 | /// 再発行予約がありません in debug string 20 | /// </summary> 21 | NotReissue = 27, 22 | 23 | /// <summary> 24 | /// Server side validation error 25 | /// </summary> 26 | Unknown = 9999 27 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteMusicDetailCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Write; 2 | 3 | public record WriteMusicDetailCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class WriteMusicDetailCommandHandler : RequestHandlerBase<WriteMusicDetailCommand, string> 6 | { 7 | public WriteMusicDetailCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) {} 8 | 9 | public override Task<ServiceResult<string>> Handle(WriteMusicDetailCommand request, CancellationToken cancellationToken) 10 | { 11 | // TODO: Add proper implementation 12 | return Task.FromResult(new ServiceResult<string>(request.Data)); 13 | } 14 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteSoundEffectCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Write; 2 | 3 | public record WriteSoundEffectCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class WriteSoundEffectCommandHandler : RequestHandlerBase<WriteSoundEffectCommand, string> 6 | { 7 | public WriteSoundEffectCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) {} 8 | 9 | public override Task<ServiceResult<string>> Handle(WriteSoundEffectCommand request, CancellationToken cancellationToken) 10 | { 11 | // TODO: Add proper implementation 12 | return Task.FromResult(new ServiceResult<string>(request.Data)); 13 | } 14 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteUnlockKeynumCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Write; 2 | 3 | public record WriteUnlockKeynumCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class WriteUnlockKeynumCommandHandler : RequestHandlerBase<WriteUnlockKeynumCommand, string> 6 | { 7 | public WriteUnlockKeynumCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) {} 8 | 9 | public override Task<ServiceResult<string>> Handle(WriteUnlockKeynumCommand request, CancellationToken cancellationToken) 10 | { 11 | // TODO: Add proper implementation 12 | return Task.FromResult(new ServiceResult<string>(request.Data)); 13 | } 14 | } -------------------------------------------------------------------------------- /doc/genunlock.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import sqlite3 3 | import json 4 | 5 | 6 | con = sqlite3.connect('music471omni.db3') 7 | cur = con.cursor() 8 | id = 6 9 | with open('unlockables.csv', encoding='utf8') as csvfile: 10 | reader = csv.reader(csvfile, delimiter=',') 11 | for row in reader: 12 | name = row[0] 13 | res = cur.execute("SELECT music_id FROM music_unlock where title=:t", {"t":name}) 14 | for music_id in res: 15 | config = { 16 | "RewardId": id, 17 | "RewardType": "Music", 18 | "TargetId": music_id[0], 19 | "TargetNum": 1, 20 | "KeyNum": 1 21 | } 22 | id += 1 23 | print(json.dumps(config, indent=4) + ",") 24 | 25 | -------------------------------------------------------------------------------- /Application/Mappers/CardBDatumMapper.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Riok.Mapperly.Abstractions; 3 | 4 | namespace Application.Mappers; 5 | 6 | [Mapper] 7 | public static partial class CardBDatumMapper 8 | { 9 | [MapProperty(nameof(CardBdatum.Bdata), nameof(CardBDatumDto.CardBdata))] 10 | [MapProperty(nameof(CardBdatum.BdataSize), nameof(CardBDatumDto.BDataSize))] 11 | public static partial CardBDatumDto CardBDatumToDto(this CardBdatum cardBdatum); 12 | 13 | [MapProperty(nameof(CardBDatumDto.CardBdata), nameof(CardBdatum.Bdata))] 14 | [MapProperty(nameof(CardBDatumDto.BDataSize), nameof(CardBdatum.BdataSize))] 15 | public static partial CardBdatum DtoToCardBDatum(this CardBDatumDto dto); 16 | } -------------------------------------------------------------------------------- /Application/Dto/Game/SkinDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class SkinDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement(ElementName = "card_id")] 9 | public long CardId { get; set; } 10 | 11 | [XmlElement(ElementName = "skin_id")] 12 | public int SkinId { get; set; } 13 | 14 | [XmlElement("created")] 15 | public string Created { get; set; } = string.Empty; 16 | 17 | [XmlElement("modified")] 18 | public string Modified { get; set; } = string.Empty; 19 | 20 | [XmlElement("new_flag")] 21 | public int NewFlag { get; set; } 22 | 23 | [XmlElement("use_flag")] 24 | public int UseFlag { get; set; } 25 | } -------------------------------------------------------------------------------- /Application/Game/Card/CardRequest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace Application.Game.Card; 4 | 5 | public class CardRequest 6 | { 7 | [ModelBinder(Name = "mac_addr")] 8 | public string Mac { get; set; } = string.Empty; 9 | 10 | [ModelBinder(Name = "cmd_str")] 11 | public int CardCommandType { get; set; } 12 | 13 | [ModelBinder(Name = "type")] 14 | public int CardRequestType { get; set; } 15 | 16 | [ModelBinder(Name = "card_no")] 17 | public long CardId { get; set; } 18 | 19 | [ModelBinder(Name = "tenpo_id")] 20 | public string TenpoId { get; set; } = "1337"; 21 | 22 | [ModelBinder(Name = "data")] 23 | public string Data { get; set; } = string.Empty; 24 | } -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadCondQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadCondQuery(long CardId) : IRequestWrapper<string>; 5 | 6 | public class ReadCondQueryHandler : RequestHandlerBase<ReadCondQuery, string> 7 | { 8 | private const string COND_XPATH = "/root/cond"; 9 | 10 | public ReadCondQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 11 | { 12 | } 13 | 14 | public override Task<ServiceResult<string>> Handle(ReadCondQuery request, CancellationToken cancellationToken) 15 | { 16 | var result = new object().SerializeCardData(COND_XPATH); 17 | 18 | return Task.FromResult(new ServiceResult<string>(result)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Application/Dto/Game/TitleDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class TitleDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement(ElementName = "card_id")] 9 | public long CardId { get; set; } 10 | 11 | [XmlElement(ElementName = "title_id")] 12 | public int TitleId { get; set; } 13 | 14 | [XmlElement("created")] 15 | public string Created { get; set; } = string.Empty; 16 | 17 | [XmlElement("modified")] 18 | public string Modified { get; set; } = string.Empty; 19 | 20 | [XmlElement("new_flag")] 21 | public int NewFlag { get; set; } 22 | 23 | [XmlElement("use_flag")] 24 | public int UseFlag { get; set; } 25 | } -------------------------------------------------------------------------------- /Application/Dto/Game/NavigatorDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class NavigatorDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement(ElementName = "card_id")] 9 | public long CardId { get; set; } 10 | 11 | [XmlElement(ElementName = "navigator_id")] 12 | public int NavigatorId { get; set; } 13 | 14 | [XmlElement("created")] 15 | public string Created { get; set; } = string.Empty; 16 | 17 | [XmlElement("modified")] 18 | public string Modified { get; set; } = string.Empty; 19 | 20 | [XmlElement("new_flag")] 21 | public int NewFlag { get; set; } 22 | 23 | [XmlElement("use_flag")] 24 | public int UseFlag { get; set; } 25 | } -------------------------------------------------------------------------------- /Application/Dto/Game/SoundEffectDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class SoundEffectDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement(ElementName = "card_id")] 9 | public long CardId { get; set; } 10 | 11 | [XmlElement(ElementName = "sound_effect_id")] 12 | public int SoundEffectId { get; set; } 13 | 14 | [XmlElement("created")] 15 | public string Created { get; set; } = string.Empty; 16 | 17 | [XmlElement("modified")] 18 | public string Modified { get; set; } = string.Empty; 19 | 20 | [XmlElement("new_flag")] 21 | public int NewFlag { get; set; } 22 | 23 | [XmlElement("use_flag")] 24 | public int UseFlag { get; set; } 25 | } -------------------------------------------------------------------------------- /MainServer/BundledCertificates/Import.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [switch]$WhatIf = $false 3 | ) 4 | $certsInMy = Get-ChildItem Cert:\LocalMachine\My 5 | $certsInMy | Where-Object {$_.Issuer -match "Taito Arcade Machine CA"} | Remove-Item -WhatIf:$WhatIf 6 | 7 | $certsInRoot = Get-ChildItem Cert:\LocalMachine\Root 8 | $certsInRoot | Where-Object {$_.Issuer -match "Taito Arcade Machine CA"} | Remove-Item -WhatIf:$WhatIf 9 | 10 | Get-ChildItem -Path root.pfx | Import-PfxCertificate -CertStoreLocation Cert:\LocalMachine\My -WhatIf:$WhatIf 11 | Get-ChildItem -Path cert.pfx | Import-PfxCertificate -CertStoreLocation Cert:\LocalMachine\My -WhatIf:$WhatIf 12 | 13 | Get-ChildItem -Path root.pfx | Import-PfxCertificate -CertStoreLocation Cert:\LocalMachine\Root -WhatIf:$WhatIf -------------------------------------------------------------------------------- /WebUI/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 3 | using WebUI; 4 | using MudBlazor.Services; 5 | using WebUI.Services; 6 | 7 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 8 | builder.RootComponents.Add<App>("#app"); 9 | builder.RootComponents.Add<HeadOutlet>("head::after"); 10 | 11 | builder.Services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 12 | builder.Services.AddMudServices(); 13 | builder.Services.AddSingleton<IDataService, DataService>(); 14 | 15 | var host = builder.Build(); 16 | 17 | var service = host.Services.GetRequiredService<IDataService>(); 18 | await service.InitializeAsync(); 19 | 20 | await host.RunAsync(); -------------------------------------------------------------------------------- /Application/Api/GetCardsQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Api; 2 | 3 | public record GetCardsQuery() : IRequestWrapper<List<ClientCardDto>>; 4 | 5 | public class GetCardsQueryHandler : RequestHandlerBase<GetCardsQuery, List<ClientCardDto>> 6 | { 7 | public GetCardsQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 8 | { 9 | } 10 | 11 | public override async Task<ServiceResult<List<ClientCardDto>>> Handle(GetCardsQuery request, CancellationToken cancellationToken) 12 | { 13 | var cards = await CardDbContext.CardMains.ToListAsync(cancellationToken: cancellationToken); 14 | var dtoList = cards.Select(card => card.CardMainToClientDto()).ToList(); 15 | 16 | return new ServiceResult<List<ClientCardDto>>(dtoList); 17 | } 18 | } -------------------------------------------------------------------------------- /MainServer/Configurations/logging.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], 4 | "MinimumLevel": { 5 | "Default": "Information", 6 | "Override": { 7 | "Microsoft": "Warning", 8 | "Microsoft.AspNetCore": "Warning", 9 | "Microsoft.Hosting.Lifetime": "Information" 10 | } 11 | }, 12 | "Filter": [ 13 | { 14 | "Name": "ByExcluding", 15 | "Args": { 16 | "expression": "@mt = 'An unhandled exception has occurred while executing the request.'" 17 | } 18 | } 19 | ], 20 | "WriteTo": [ 21 | { 22 | "Name": "File", 23 | "Args": { "path": "./Logs/log-.txt", "rollingInterval": "Day" } 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadGetMessageQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadGetMessageQuery(long CardId) : IRequestWrapper<string>; 5 | 6 | public class ReadGetMessageQueryHandler : RequestHandlerBase<ReadGetMessageQuery, string> 7 | { 8 | private const string GET_MESSAGE_XPATH = "/root/get_message"; 9 | 10 | public ReadGetMessageQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 11 | { 12 | } 13 | 14 | public override Task<ServiceResult<string>> Handle(ReadGetMessageQuery request, CancellationToken cancellationToken) 15 | { 16 | var result = new object().SerializeCardData(GET_MESSAGE_XPATH); 17 | 18 | return Task.FromResult(new ServiceResult<string>(result)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Application/Common/Base/RequestHandlerBase.cs: -------------------------------------------------------------------------------- 1 | using Domain.Config; 2 | 3 | namespace Application.Common.Base; 4 | 5 | public abstract class RequestHandlerBase<TIn, TOut>: IRequestHandlerWrapper<TIn, TOut> 6 | where TIn : IRequestWrapper<TOut> 7 | { 8 | protected ICardDbContext CardDbContext { get; } 9 | protected IMusicDbContext MusicDbContext { get; } 10 | 11 | protected GameConfig Config { get; } 12 | 13 | public RequestHandlerBase(ICardDependencyAggregate aggregate) 14 | { 15 | CardDbContext = aggregate.CardDbContext; 16 | MusicDbContext = aggregate.MusicDbContext; 17 | Config = aggregate.Options.Value; 18 | } 19 | 20 | public abstract Task<ServiceResult<TOut>> Handle(TIn request, CancellationToken cancellationToken); 21 | } -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadEventRewardQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadEventRewardQuery(long CardId) : IRequestWrapper<string>; 5 | 6 | public class ReadEventRewardQueryHandler : RequestHandlerBase<ReadEventRewardQuery, string> 7 | { 8 | private const string EVENT_REWARD_XPATH = "/root/event_reward"; 9 | 10 | public ReadEventRewardQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 11 | { 12 | } 13 | 14 | public override Task<ServiceResult<string>> Handle(ReadEventRewardQuery request, CancellationToken cancellationToken) 15 | { 16 | var result = new object().SerializeCardData(EVENT_REWARD_XPATH); 17 | 18 | return Task.FromResult(new ServiceResult<string>(result)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MainServer/Controllers/API/PlayOptionController.cs: -------------------------------------------------------------------------------- 1 | using Application.Api; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Shared.Models; 4 | 5 | namespace MainServer.Controllers.API; 6 | 7 | [ApiController] 8 | [Route("api/[controller]")] 9 | public class PlayOptionController : BaseController<PlayOptionController> 10 | { 11 | [HttpGet("{cardId:long}")] 12 | public async Task<ServiceResult<PlayOptionData>> GetPlayOptionById(long cardId) 13 | { 14 | var result = await Mediator.Send(new GetPlayOptionQuery(cardId)); 15 | return result; 16 | } 17 | 18 | [HttpPost] 19 | public async Task<ServiceResult<bool>> SetPlayOption(PlayOptionData data) 20 | { 21 | var result = await Mediator.Send(new SetPlayOptionCommand(data)); 22 | return result; 23 | } 24 | } -------------------------------------------------------------------------------- /GC-local-server-rewrite.sln.DotSettings: -------------------------------------------------------------------------------- 1 | <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> 2 | <s:Boolean x:Key="/Default/UserDictionary/Words/=Adlibs/@EntryIndexedValue">True</s:Boolean> 3 | <s:Boolean x:Key="/Default/UserDictionary/Words/=Keynum/@EntryIndexedValue">True</s:Boolean> 4 | <s:Boolean x:Key="/Default/UserDictionary/Words/=Touhou/@EntryIndexedValue">True</s:Boolean> 5 | <s:Boolean x:Key="/Default/UserDictionary/Words/=unlockable/@EntryIndexedValue">True</s:Boolean> 6 | <s:Boolean x:Key="/Default/UserDictionary/Words/=Vocaloid/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> -------------------------------------------------------------------------------- /Application/Dto/Game/ItemDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class ItemDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement(ElementName = "card_id")] 9 | public long CardId { get; set; } 10 | 11 | [XmlElement(ElementName = "item_id")] 12 | public int ItemId { get; set; } 13 | 14 | [XmlElement(ElementName = "item_num")] 15 | public int ItemNum { get; set; } = 90; 16 | 17 | [XmlElement("created")] 18 | public string Created { get; set; } = "1"; 19 | 20 | [XmlElement("modified")] 21 | public string Modified { get; set; } = "1"; 22 | 23 | [XmlElement("new_flag")] 24 | public int NewFlag { get; set; } 25 | 26 | [XmlElement("use_flag")] 27 | public int UseFlag { get; set; } = 1; 28 | } -------------------------------------------------------------------------------- /Application/Common/Behaviours/LoggingBehaviour.cs: -------------------------------------------------------------------------------- 1 | using MediatR.Pipeline; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Application.Common.Behaviours; 5 | 6 | public class LoggingBehaviour<TRequest> : IRequestPreProcessor<TRequest> where TRequest : notnull 7 | { 8 | private readonly ILogger<TRequest> logger; 9 | 10 | // ReSharper disable once ContextualLoggerProblem 11 | public LoggingBehaviour(ILogger<TRequest> logger) 12 | { 13 | this.logger = logger; 14 | } 15 | 16 | public Task Process(TRequest request, CancellationToken cancellationToken) 17 | { 18 | var requestName = typeof(TRequest).Name; 19 | 20 | logger.LogInformation("Received request: {RequestName}, content: {Request}", requestName, request); 21 | 22 | return Task.CompletedTask; 23 | } 24 | } -------------------------------------------------------------------------------- /Application/Dto/Game/AvatarDto.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Application.Dto.Game; 4 | 5 | public class AvatarDto 6 | { 7 | [XmlAttribute(AttributeName = "id")] 8 | public int Id { get; set; } 9 | 10 | [XmlElement(ElementName = "card_id")] 11 | public long CardId { get; set; } 12 | 13 | [XmlElement(ElementName = "avatar_id")] 14 | public int AvatarId { get; set; } 15 | 16 | [XmlElement("created")] 17 | [DefaultValue("")] 18 | public string Created { get; set; } = string.Empty; 19 | 20 | [XmlElement("modified")] 21 | [DefaultValue("")] 22 | public string Modified { get; set; } = string.Empty; 23 | 24 | [XmlElement("new_flag")] 25 | public int NewFlag { get; set; } 26 | 27 | [XmlElement("use_flag")] 28 | public int UseFlag { get; set; } 29 | } -------------------------------------------------------------------------------- /MainServer/Controllers/Game/AliveController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace MainServer.Controllers.Game; 4 | 5 | [ApiController] 6 | [Route("alive")] 7 | public class AliveController : ControllerBase 8 | { 9 | [HttpGet("i.php")] 10 | public IActionResult AliveCheck() 11 | { 12 | var remoteIpAddress = Request.HttpContext.Connection.RemoteIpAddress; 13 | var serverIpAddress = Request.HttpContext.Connection.LocalIpAddress; 14 | var response = $"REMOTE ADDRESS:{remoteIpAddress}\n" + 15 | "SERVER NAME:GCLocalServer\n" + 16 | $"SERVER ADDR:{serverIpAddress}"; 17 | return Ok(response); 18 | } 19 | 20 | [HttpGet("{id}/Alive.txt")] 21 | public IActionResult GetAliveFile() 22 | { 23 | return Ok(""); 24 | } 25 | } -------------------------------------------------------------------------------- /Application/Dto/Game/PlayNumRankDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class PlayNumRankDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement("rank")] 9 | public int Rank { get; set; } 10 | 11 | [XmlElement("rank2")] 12 | public int Rank2 { get; set; } 13 | 14 | [XmlElement("prev_rank")] 15 | public int PrevRank { get; set; } 16 | 17 | [XmlElement("prev_rank2")] 18 | public int PrevRank2 { get; set; } 19 | 20 | [XmlElement("pcol1")] 21 | public int Pcol1 { get; set; } 22 | 23 | [XmlElement("score_bi1")] 24 | public int ScoreBi1 { get; set; } 25 | 26 | [XmlElement("title")] 27 | public string Title { get; set; } = string.Empty; 28 | 29 | [XmlElement("artist")] 30 | public string Artist { get; set; } = string.Empty; 31 | } -------------------------------------------------------------------------------- /Domain/Entities/ScoreRank.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public class ScoreRank 4 | { 5 | public long CardId { get; set; } 6 | 7 | public string PlayerName { get; set; } = string.Empty; 8 | 9 | public long Rank { get; set; } 10 | 11 | public long TotalScore { get; set; } 12 | 13 | public int AvatarId { get; set; } 14 | 15 | public long TitleId { get; set; } 16 | 17 | public long Fcol1 { get; set; } 18 | 19 | public int PrefId { get; set; } 20 | 21 | public string Pref { get; set; } = string.Empty; 22 | 23 | public int AreaId { get; set; } 24 | 25 | public string Area { get; set; } = string.Empty; 26 | 27 | public int LastPlayTenpoId { get; set; } 28 | 29 | public string TenpoName { get; set; } = string.Empty; 30 | 31 | public string Title { get; set; } = string.Empty; 32 | 33 | } -------------------------------------------------------------------------------- /Infrastructure/Common/PathHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Validation; 3 | 4 | namespace Infrastructure.Common; 5 | 6 | public static class PathHelper 7 | { 8 | public static string DatabasePath = Path.Combine(BasePath, "Database"); 9 | 10 | public static string ConfigurationPath = Path.Combine(BasePath, "Configurations"); 11 | 12 | public static string BasePath 13 | { 14 | get 15 | { 16 | var assemblyPath = Environment.ProcessPath; 17 | Assumes.NotNull(assemblyPath); 18 | 19 | #if DEBUG 20 | var parentFullName = Directory.GetParent(assemblyPath)?.Parent?.Parent?.Parent?.FullName; 21 | 22 | return parentFullName ?? ""; 23 | 24 | #else 25 | var parent = Directory.GetParent(assemblyPath); 26 | Assumes.NotNull(parent); 27 | return parent.ToString(); 28 | #endif 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /MainServer/Configurations/events.json: -------------------------------------------------------------------------------- 1 | { 2 | "Events": { 3 | "UseEvents": false, 4 | "EventFiles": [ 5 | { 6 | "FileName": "event_103_20201125.evt", 7 | "Index": 0 8 | }, 9 | { 10 | "FileName": "event_20201125_reg.jpg", 11 | "Index": 1 12 | }, 13 | { 14 | "FileName": "event_20201125_sgreg.png", 15 | "Index": 2 16 | }, 17 | { 18 | "FileName": "news_big_20201125_0.jpg", 19 | "Index": 0 20 | }, 21 | { 22 | "FileName": "news_big_20201125_2.jpg", 23 | "Index": 2 24 | }, 25 | { 26 | "FileName": "news_small_20201125_1.jpg", 27 | "Index": 1 28 | }, 29 | { 30 | "FileName": "telop_20201125.txt", 31 | "Index": 0 32 | }, 33 | { 34 | "FileName": "event_unlock_20201125.cmp", 35 | "Index": 8 36 | } 37 | ] 38 | } 39 | } -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadTotalTrophyQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadTotalTrophyQuery(long CardId) : IRequestWrapper<string>; 5 | 6 | public class ReadTotalTrophyQueryHandler : RequestHandlerBase<ReadTotalTrophyQuery, string> 7 | { 8 | private const string TOTAL_TROPHY_XPATH = "/root/total_trophy"; 9 | 10 | public ReadTotalTrophyQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 11 | { 12 | } 13 | 14 | public override Task<ServiceResult<string>> Handle(ReadTotalTrophyQuery request, CancellationToken cancellationToken) 15 | { 16 | var trophy = new TotalTrophyDto 17 | { 18 | CardId = request.CardId, 19 | TrophyNum = 8 20 | }; 21 | 22 | var result = trophy.SerializeCardData(TOTAL_TROPHY_XPATH); 23 | 24 | return Task.FromResult(new ServiceResult<string>(result)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /doc/card_bdata.md: -------------------------------------------------------------------------------- 1 | # Card Bdata table 2 | 3 | Stores some binary card data, encoded in base64 4 | 5 | | index | size | data | 6 | | ----- | ---- | ----------------------------------------- | 7 | | 0 | 6 | fixed value "1.51" | 8 | | 1 | 6 | Last played song and difficulty? | 9 | | 2 | 68 | Something consist of 17 ints | 10 | | 3 | 4 | Start related, unknown | 11 | | 4 | 4 | Something whose max value is 100, unknown | 12 | | 5 | 4 | Unknown | 13 | | 6 | 4 | Unknown | 14 | | 7 | 4 | Unknown | 15 | | 8 | 4 | Always 0 | 16 | | 9 | 160 | 40 previously newly played song id | 17 | | 10 | 16 | Filetime + stamp count | 18 | 19 | -------------------------------------------------------------------------------- /Domain/Entities/CardDetail.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public partial class CardDetail 4 | { 5 | public long CardId { get; set; } 6 | 7 | public long Pcol1 { get; set; } 8 | 9 | public long Pcol2 { get; set; } 10 | 11 | public long Pcol3 { get; set; } 12 | 13 | public long ScoreI1 { get; set; } 14 | 15 | public long ScoreUi1 { get; set; } 16 | 17 | public long ScoreUi2 { get; set; } 18 | 19 | public long ScoreUi3 { get; set; } 20 | 21 | public long ScoreUi4 { get; set; } 22 | 23 | public long ScoreUi5 { get; set; } 24 | 25 | public long ScoreUi6 { get; set; } 26 | 27 | public long ScoreBi1 { get; set; } 28 | 29 | public string? LastPlayTenpoId { get; set; } = string.Empty; 30 | 31 | public long Fcol1 { get; set; } 32 | 33 | public long Fcol2 { get; set; } 34 | 35 | public long Fcol3 { get; set; } 36 | 37 | public DateTime? LastPlayTime { get; set; } 38 | } 39 | -------------------------------------------------------------------------------- /WebUI/WebUI.csproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> 2 | 3 | <PropertyGroup> 4 | <TargetFramework>net7.0</TargetFramework> 5 | <Nullable>enable</Nullable> 6 | <ImplicitUsings>enable</ImplicitUsings> 7 | </PropertyGroup> 8 | 9 | <ItemGroup> 10 | <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.3" /> 11 | <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.3" PrivateAssets="all" /> 12 | <PackageReference Include="MudBlazor" Version="6.1.9" /> 13 | <PackageReference Include="Throw" Version="1.3.1" /> 14 | </ItemGroup> 15 | 16 | <ItemGroup> 17 | <ProjectReference Include="..\Shared\Shared.csproj" /> 18 | </ItemGroup> 19 | 20 | <ItemGroup> 21 | <_ContentIncludedByDefault Remove="wwwroot\sample-data\weather.json" /> 22 | </ItemGroup> 23 | 24 | 25 | </Project> -------------------------------------------------------------------------------- /Application/Interfaces/ICardDbContext.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | 3 | namespace Application.Interfaces; 4 | 5 | public interface ICardDbContext 6 | { 7 | public DbSet<CardBdatum> CardBdata { get; set; } 8 | 9 | public DbSet<CardDetail> CardDetails { get; set; } 10 | 11 | public DbSet<CardMain> CardMains { get; set; } 12 | 13 | public DbSet<CardPlayCount> CardPlayCounts { get; set; } 14 | 15 | public DbSet<PlayNumRank> PlayNumRanks { get; set; } 16 | 17 | public DbSet<GlobalScoreRank> GlobalScoreRanks { get; set; } 18 | 19 | public DbSet<MonthlyScoreRank> MonthlyScoreRanks { get; set; } 20 | 21 | public DbSet<ShopScoreRank> ShopScoreRanks { get; set; } 22 | 23 | public DbSet<OnlineMatch> OnlineMatches { get; set; } 24 | 25 | public DbSet<OnlineMatchEntry> OnlineMatchEntries { get; set; } 26 | 27 | public Task<int> SaveChangesAsync(CancellationToken cancellationToken); 28 | 29 | 30 | } -------------------------------------------------------------------------------- /Application/Game/Card/Session/GetSessionCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Session; 2 | 3 | public record GetSessionCommand(long CardId, string Mac) : IRequestWrapper<string>; 4 | 5 | public class GetSessionCommandHandler : RequestHandlerBase<GetSessionCommand, string> 6 | { 7 | private const string SESSION_XPATH = "/root/session"; 8 | 9 | public GetSessionCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) 10 | { 11 | } 12 | 13 | public override Task<ServiceResult<string>> Handle(GetSessionCommand request, CancellationToken cancellationToken) 14 | { 15 | var session = new SessionDto 16 | { 17 | CardId = request.CardId, 18 | Mac = request.Mac, 19 | PlayerId = 1, 20 | Expires = 9999, 21 | SessionId = "12345678901234567890123456789012" 22 | }; 23 | return Task.FromResult(new ServiceResult<string>(session.SerializeCardData(SESSION_XPATH))); 24 | } 25 | } -------------------------------------------------------------------------------- /Application/Dto/Game/MusicDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class MusicDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement("music_id")] 9 | public int MusicId { get; set; } 10 | 11 | [XmlElement(ElementName = "title")] 12 | public string Title { get; set; } = string.Empty; 13 | 14 | [XmlElement(ElementName = "artist")] 15 | public string Artist { get; set; } = string.Empty; 16 | 17 | [XmlElement(ElementName = "release_date")] 18 | public string ReleaseDate { get; set; } = "2013-01-01 08:00:00"; 19 | 20 | [XmlElement(ElementName = "end_date")] 21 | public string EndDate { get; set; } = "2030-01-01 08:00:00"; 22 | 23 | [XmlElement("new_flag")] 24 | public int NewFlag { get; set; } 25 | 26 | [XmlElement("use_flag")] 27 | public int UseFlag { get; set; } 28 | 29 | [XmlElement("calc_flag")] 30 | public int CalcFlag { get; set; } 31 | } -------------------------------------------------------------------------------- /GC-local-server-rewrite.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 | <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> 2 | 3 | 4 | <s:Boolean x:Key="/Default/UserDictionary/Words/=ADDR/@EntryIndexedValue">True</s:Boolean> 5 | <s:Boolean x:Key="/Default/UserDictionary/Words/=BDATA/@EntryIndexedValue">True</s:Boolean> 6 | <s:Boolean x:Key="/Default/UserDictionary/Words/=Fcol/@EntryIndexedValue">True</s:Boolean> 7 | <s:Boolean x:Key="/Default/UserDictionary/Words/=Incom/@EntryIndexedValue">True</s:Boolean> 8 | <s:Boolean x:Key="/Default/UserDictionary/Words/=Pcol/@EntryIndexedValue">True</s:Boolean> 9 | <s:Boolean x:Key="/Default/UserDictionary/Words/=Respone/@EntryIndexedValue">True</s:Boolean> 10 | <s:Boolean x:Key="/Default/UserDictionary/Words/=Tenpo/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> -------------------------------------------------------------------------------- /Application/Dto/Game/UnlockKeyNumDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class UnlockKeyNumDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement(ElementName = "card_id")] 9 | public long CardId { get; set; } 10 | 11 | [XmlElement(ElementName = "reward_id")] 12 | public int RewardId { get; set; } 13 | 14 | [XmlElement(ElementName = "key_num")] 15 | public int KeyNum { get; set; } 16 | 17 | [XmlElement(ElementName = "reward_count")] 18 | public int RewardCount { get; set; } 19 | 20 | [XmlElement("expired_flag")] 21 | public int ExpiredFlag { get; set; } 22 | 23 | [XmlElement("use_flag")] 24 | public int UseFlag { get; set; } 25 | 26 | [XmlElement("cash_flag")] 27 | public int CashFlag { get; set; } 28 | 29 | [XmlElement("created")] 30 | public string Created { get; set; } = string.Empty; 31 | 32 | [XmlElement("modified")] 33 | public string Modified { get; set; } = string.Empty; 34 | } -------------------------------------------------------------------------------- /Application/Game/Card/OnlineMatching/UploadOnlineMatchingResultCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.OnlineMatching; 2 | 3 | public record UploadOnlineMatchingResultCommand(long CardId, string Data) : IRequestWrapper<string>; 4 | 5 | public class UploadOnlineMatchingResultCommandHandler : RequestHandlerBase<UploadOnlineMatchingResultCommand, string> 6 | { 7 | private const string XPATH = "/root/online_battle_result"; 8 | 9 | public UploadOnlineMatchingResultCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate) 10 | { 11 | } 12 | 13 | public override Task<ServiceResult<string>> Handle(UploadOnlineMatchingResultCommand request, CancellationToken cancellationToken) 14 | { 15 | var result = new OnlineMatchingResult { Status = 1 }.SerializeCardData(XPATH); 16 | return Task.FromResult(new ServiceResult<string>(result)); 17 | } 18 | } 19 | 20 | public class OnlineMatchingResult 21 | { 22 | [XmlElement(ElementName = "status")] 23 | public int Status { get; set; } 24 | } -------------------------------------------------------------------------------- /MainServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:55391", 8 | "sslPort": 44317 9 | } 10 | }, 11 | "profiles": { 12 | "MainServer": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": false, 16 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 17 | "applicationUrl": "http://localhost:5107", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadCoinQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadCoinQuery(long CardId) : IRequestWrapper<string>; 5 | 6 | public class ReadCoinQueryHandler : RequestHandlerBase<ReadCoinQuery, string> 7 | { 8 | private const string COIN_XPATH = "/root/coin"; 9 | 10 | public ReadCoinQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 11 | { 12 | } 13 | 14 | public override Task<ServiceResult<string>> Handle(ReadCoinQuery request, CancellationToken cancellationToken) 15 | { 16 | var dto = new CoinDto 17 | { 18 | CardId = request.CardId, 19 | CurrentCoins = 900000, 20 | MonthlyCoins = 900000, 21 | TotalCoins = 900000, 22 | Created = "2013-01-01 08:00:00", 23 | Modified = "2013-01-01 08:00:00" 24 | }; 25 | 26 | var result = dto.SerializeCardData(COIN_XPATH); 27 | 28 | return Task.FromResult(new ServiceResult<string>(result)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /WebUI/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:65283", 7 | "sslPort": 44398 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "WebUI": { 20 | "commandName": "Project", 21 | "dotnetRunMessages": true, 22 | "launchBrowser": true, 23 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 24 | "applicationUrl": "http://localhost:5107", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Application/Dto/Game/CardDto.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Application.Dto.Game; 4 | 5 | public class CardDto 6 | { 7 | [XmlElement(ElementName = "card_id")] 8 | public long CardId { get; set; } 9 | 10 | [XmlElement(ElementName = "player_name")] 11 | [DefaultValue("")] 12 | public string PlayerName { get; set; } = string.Empty; 13 | 14 | [XmlElement("score_i1")] 15 | public long ScoreI1 { get; set; } 16 | 17 | [XmlElement("fcol1")] 18 | public long Fcol1 { get; set; } 19 | 20 | [XmlElement("fcol2")] 21 | public long Fcol2 { get; set; } 22 | 23 | [XmlElement("fcol3")] 24 | public long Fcol3 { get; set; } 25 | 26 | [XmlElement("achieve_status")] 27 | [DefaultValue("")] 28 | public string AchieveStatus { get; set; } = string.Empty; 29 | 30 | [XmlElement("created")] 31 | [DefaultValue("")] 32 | public string Created { get; set; } = string.Empty; 33 | 34 | [XmlElement("modified")] 35 | [DefaultValue("")] 36 | public string Modified { get; set; } = string.Empty; 37 | } -------------------------------------------------------------------------------- /Application/Common/Behaviours/UnhandledExceptionBehaviour.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Application.Common.Behaviours; 5 | 6 | public class UnhandledExceptionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> 7 | where TRequest : IRequest<TResponse> 8 | { 9 | private readonly ILogger<TRequest> logger; 10 | 11 | // ReSharper disable once ContextualLoggerProblem 12 | public UnhandledExceptionBehaviour(ILogger<TRequest> logger) 13 | { 14 | this.logger = logger; 15 | } 16 | 17 | public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) 18 | { 19 | try 20 | { 21 | return await next(); 22 | } 23 | catch (Exception ex) 24 | { 25 | var requestName = typeof(TRequest).Name; 26 | 27 | logger.LogError(ex, "Unhandled Exception for Request {Name} {@Request}", requestName, request); 28 | 29 | throw; 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /Shared/Models/TotalResultData.cs: -------------------------------------------------------------------------------- 1 | namespace Shared.Models; 2 | 3 | public class TotalResultData 4 | { 5 | public long CardId { get; set; } 6 | 7 | public string PlayerName { get; set; } = string.Empty; 8 | 9 | public PlayerData PlayerData { get; set; } = new(); 10 | 11 | public StageCountData StageCountData { get; set; } = new(); 12 | } 13 | 14 | public class PlayerData 15 | { 16 | public long TotalScore { get; set; } 17 | 18 | public int AverageScore { get; set; } 19 | 20 | public int TotalSongCount { get; set; } 21 | 22 | public int PlayedSongCount { get; set; } 23 | 24 | public int Rank { get; set; } 25 | } 26 | 27 | public class StageCountData 28 | { 29 | public int Total { get; set; } 30 | 31 | public int Cleared { get; set; } 32 | 33 | public int NoMiss { get; set; } 34 | 35 | public int FullChain { get; set; } 36 | 37 | public int S { get; set; } 38 | 39 | public int Ss { get; set; } 40 | 41 | public int Sss { get; set; } 42 | 43 | public int Perfect { get; set; } 44 | } -------------------------------------------------------------------------------- /MainServer/Controllers/API/TestController.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Infrastructure.Persistence; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace MainServer.Controllers.API 6 | { 7 | [Route("api/[controller]")] 8 | [ApiController] 9 | public class TestController : ControllerBase 10 | { 11 | private readonly MusicDbContext context; 12 | 13 | public TestController(MusicDbContext context) 14 | { 15 | this.context = context; 16 | } 17 | 18 | [HttpGet] 19 | public MusicUnlock GetOne() 20 | { 21 | return context.MusicUnlocks.First(); 22 | } 23 | 24 | [HttpPost] 25 | public ActionResult<string> TestXmlInputOutput([FromForm(Name = "my_model")]TestModel model, 26 | [FromForm(Name = "my_type")]int type) 27 | { 28 | return Ok($"{model.Name}\n{model.Age}\n{type}"); 29 | } 30 | } 31 | 32 | public class TestModel 33 | { 34 | public string Name { get; set; } = string.Empty; 35 | 36 | public int Age { get; set; } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /WebUI/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /WebUI/WebUI.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31717.71 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebUI", "WebUI.csproj", "{14A8F0A8-0A25-44AD-83B4-8BFE328E3148}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {14A8F0A8-0A25-44AD-83B4-8BFE328E3148}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {14A8F0A8-0A25-44AD-83B4-8BFE328E3148}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {14A8F0A8-0A25-44AD-83B4-8BFE328E3148}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {14A8F0A8-0A25-44AD-83B4-8BFE328E3148}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {5A60C47D-9115-4F94-A725-C8F37B455972} 24 | EndGlobalSection 25 | EndGlobal -------------------------------------------------------------------------------- /Domain/Entities/OnlineMatchEntry.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Entities; 2 | 3 | public partial class OnlineMatchEntry 4 | { 5 | public long MatchId { get; set; } 6 | 7 | public long EntryId { get; set; } 8 | 9 | public long MachineId { get; set; } 10 | 11 | public long EventId { get; set; } 12 | 13 | public DateTime StartTime { get; set; } 14 | 15 | public long Status { get; set; } 16 | 17 | public long CardId { get; set; } 18 | 19 | public string PlayerName { get; set; } = string.Empty; 20 | 21 | public long AvatarId { get; set; } 22 | 23 | public long TitleId { get; set; } 24 | 25 | public long ClassId { get; set; } 26 | 27 | public long GroupId { get; set; } 28 | 29 | public long TenpoId { get; set; } = 1337; 30 | 31 | public string TenpoName { get; set; } = "GCLocalServer"; 32 | 33 | public long PrefId { get; set; } 34 | 35 | public string Pref { get; set; } = "nesys"; 36 | 37 | public long MessageId { get; set; } 38 | 39 | public long MatchTimeout { get; set; } = 99; 40 | 41 | public long MatchWaitTime { get; set; } = 10; 42 | 43 | public long MatchRemainingTime { get; set; } = 89; 44 | 45 | } -------------------------------------------------------------------------------- /WebUI/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | 4 | <head> 5 | <meta charset="utf-8"/> 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/> 7 | <title>WebUI 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 |
26 | An unhandled error has occurred. 27 | Reload 28 | 🗙 29 |
30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadMusicExtraQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadMusicExtraQuery(long CardId) : IRequestWrapper; 5 | 6 | public class ReadMusicExtraQueryHandler : RequestHandlerBase 7 | { 8 | private const string MUSIC_EXTRA_XPATH = "/root/music_extra"; 9 | 10 | private const string RECORD_XPATH = $"{MUSIC_EXTRA_XPATH}/record"; 11 | 12 | public ReadMusicExtraQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 13 | { 14 | } 15 | 16 | public override async Task> Handle(ReadMusicExtraQuery request, CancellationToken cancellationToken) 17 | { 18 | var musics = await MusicDbContext.MusicExtras.ToListAsync(cancellationToken: cancellationToken); 19 | var dtoList = musics.Select((aou, i) => 20 | { 21 | var dto = aou.MusicExtraToDto(); 22 | dto.Id = i; 23 | return dto; 24 | }).ToList(); 25 | 26 | var result = dtoList.Count == 0 ? new object().SerializeCardData(MUSIC_EXTRA_XPATH) 27 | : dtoList.SerializeCardDataList(RECORD_XPATH); 28 | 29 | return new ServiceResult(result); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadMusicQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace Application.Game.Card.Read; 4 | 5 | 6 | public record ReadMusicQuery(long CardId) : IRequestWrapper; 7 | 8 | public class ReadMusicQueryHandler : RequestHandlerBase 9 | { 10 | private const string MUSIC_XPATH = "/root/music/record"; 11 | public ReadMusicQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 12 | { 13 | } 14 | 15 | [SuppressMessage("ReSharper.DPA", "DPA0007: Large number of DB records", 16 | Justification = "To return all musics, the whole table need to be returned")] 17 | public override async Task> Handle(ReadMusicQuery request, CancellationToken cancellationToken) 18 | { 19 | var musics = await MusicDbContext.MusicUnlocks.ToListAsync(cancellationToken: cancellationToken); 20 | var dtoList = musics.Select((unlock, i) => 21 | { 22 | var dto = unlock.MusicToDto(); 23 | dto.Id = i; 24 | return dto; 25 | }); 26 | 27 | var result = dtoList.SerializeCardDataList(MUSIC_XPATH); 28 | 29 | return new ServiceResult(result); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /WebUI/Pages/TotalResult.razor.cs: -------------------------------------------------------------------------------- 1 | using Throw; 2 | 3 | namespace WebUI.Pages; 4 | 5 | public partial class TotalResult 6 | { 7 | private readonly List breadcrumbs = new() 8 | { 9 | new BreadcrumbItem("Cards", href: "/Cards") 10 | }; 11 | 12 | [Parameter] 13 | public long CardId { get; set; } 14 | 15 | [Inject] 16 | public required HttpClient Client { get; set; } 17 | 18 | private string? errorMessage; 19 | 20 | private TotalResultData? totalResultData; 21 | 22 | protected async override Task OnInitializedAsync() 23 | { 24 | await base.OnInitializedAsync(); 25 | 26 | breadcrumbs.Add(new BreadcrumbItem($"Card: {CardId}", href:null, disabled:true)); 27 | breadcrumbs.Add(new BreadcrumbItem("TotalResult", href: $"/Cards/TotalResult/{CardId}", disabled: false)); 28 | 29 | var result = await Client.GetFromJsonAsync>($"api/Profiles/TotalResult/{CardId}"); 30 | result.ThrowIfNull(); 31 | 32 | if (!result.Succeeded) 33 | { 34 | errorMessage = result.Error!.Message; 35 | return; 36 | } 37 | 38 | totalResultData = result.Data; 39 | } 40 | } -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadItemQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | public record ReadItemQuery(long CardId) : IRequestWrapper; 4 | 5 | public class ReadItemQueryHandler : RequestHandlerBase 6 | { 7 | private const string ITEM_XPATH = "/root/item/record"; 8 | 9 | public ReadItemQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 10 | { 11 | } 12 | 13 | public override Task> Handle(ReadItemQuery request, CancellationToken cancellationToken) 14 | { 15 | var count = Config.ItemCount; 16 | var list = new List(); 17 | for (int i = 0; i < count; i++) 18 | { 19 | var item = new ItemDto 20 | { 21 | Id = i, 22 | CardId = request.CardId, 23 | ItemId = i + 1, 24 | ItemNum = 90, 25 | Created = "2013-01-01", 26 | Modified = "2013-01-01", 27 | NewFlag = 0, 28 | UseFlag = 1 29 | }; 30 | list.Add(item); 31 | } 32 | 33 | var result = list.SerializeCardDataList(ITEM_XPATH); 34 | 35 | return Task.FromResult(new ServiceResult(result)); 36 | } 37 | } -------------------------------------------------------------------------------- /Application/Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadSkinQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadSkinQuery(long CardId) : IRequestWrapper; 5 | 6 | public class ReadSkinQueryHandler : RequestHandlerBase 7 | { 8 | private const string SKIN_XPATH = "/root/skin/record"; 9 | 10 | public ReadSkinQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 11 | { 12 | } 13 | 14 | public override Task> Handle(ReadSkinQuery request, CancellationToken cancellationToken) 15 | { 16 | var count = Config.SkinCount; 17 | 18 | var list = new List(); 19 | for (int i = 0; i < count; i++) 20 | { 21 | var skin = new SkinDto 22 | { 23 | Id = i, 24 | CardId = request.CardId, 25 | SkinId = i + 1, 26 | Created = "2013-01-01 08:00:00", 27 | Modified = "2013-01-01 08:00:00", 28 | NewFlag = 0, 29 | UseFlag = 1 30 | }; 31 | list.Add(skin); 32 | } 33 | 34 | var result = list.SerializeCardDataList(SKIN_XPATH); 35 | 36 | return Task.FromResult(new ServiceResult(result)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadAvatarQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadAvatarQuery(long CardId) : IRequestWrapper; 5 | 6 | public class ReadAvatarQueryHandler : RequestHandlerBase 7 | { 8 | private const string AVATAR_XPATH = "/root/avatar/record"; 9 | 10 | public ReadAvatarQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 11 | { 12 | } 13 | 14 | public override Task> Handle(ReadAvatarQuery request, CancellationToken cancellationToken) 15 | { 16 | var count = Config.AvatarCount; 17 | var list = new List(); 18 | for (int i = 0; i < count; i++) 19 | { 20 | var avatar = new AvatarDto 21 | { 22 | Id = i, 23 | CardId = request.CardId, 24 | AvatarId = i + 1, 25 | Created = "2013-01-01 08:00:00", 26 | Modified = "2013-01-01 08:00:00", 27 | NewFlag = 0, 28 | UseFlag = 1 29 | }; 30 | list.Add(avatar); 31 | } 32 | 33 | var result = list.SerializeCardDataList(AVATAR_XPATH); 34 | 35 | return Task.FromResult(new ServiceResult(result)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadCardQuery.cs: -------------------------------------------------------------------------------- 1 | using Domain.Enums; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Application.Game.Card.Read; 5 | 6 | public record ReadCardQuery(long CardId) : IRequestWrapper; 7 | 8 | public class ReadQueryHandler : RequestHandlerBase 9 | { 10 | private readonly ILogger logger; 11 | 12 | public ReadQueryHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) { 13 | this.logger = logger; 14 | } 15 | 16 | public override async Task> Handle(ReadCardQuery request, CancellationToken cancellationToken) 17 | { 18 | var card = await CardDbContext.CardMains.FirstOrDefaultAsync(card => card.CardId == request.CardId, cancellationToken: cancellationToken); 19 | if (card is null) 20 | { 21 | logger.LogInformation("Card of id: {CardId} does not exist! Registering a new one...", request.CardId); 22 | return ServiceResult.Failed(new ServiceError($"Card id: {request.CardId} does not exist!", (int)CardReturnCode.CardNotRegistered)); 23 | } 24 | 25 | var result = card.CardMainToCardDto().SerializeCardData("/root/card"); 26 | 27 | return new ServiceResult(result); 28 | } 29 | } -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadTitleQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadTitleQuery(long CardId) : IRequestWrapper; 5 | 6 | public class ReadTitleQueryHandler : RequestHandlerBase 7 | { 8 | private const string TITLE_XPATH = "/root/title/record"; 9 | 10 | public ReadTitleQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 11 | { 12 | } 13 | 14 | public override Task> Handle(ReadTitleQuery request, CancellationToken cancellationToken) 15 | { 16 | var count = Config.TitleCount; 17 | 18 | var list = new List(); 19 | for (int i = 0; i < count; i++) 20 | { 21 | var soundEffect = new TitleDto 22 | { 23 | Id = i, 24 | CardId = request.CardId, 25 | TitleId = i + 1, 26 | Created = "2013-01-01 08:00:00", 27 | Modified = "2013-01-01 08:00:00", 28 | NewFlag = 0, 29 | UseFlag = 1 30 | }; 31 | list.Add(soundEffect); 32 | } 33 | 34 | var result = list.SerializeCardDataList(TITLE_XPATH); 35 | 36 | return Task.FromResult(new ServiceResult(result)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadNavigatorQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadNavigatorQuery(long CardId) : IRequestWrapper; 5 | 6 | public class ReadNavigatorQueryHandler : RequestHandlerBase 7 | { 8 | private const string NAVIGATOR_XPATH = "/root/navigator/record"; 9 | 10 | public ReadNavigatorQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 11 | { 12 | } 13 | 14 | public override Task> Handle(ReadNavigatorQuery request, CancellationToken cancellationToken) 15 | { 16 | var count = Config.NavigatorCount; 17 | 18 | var list = new List(); 19 | for (int i = 0; i < count; i++) 20 | { 21 | var navigator = new NavigatorDto 22 | { 23 | Id = i, 24 | CardId = request.CardId, 25 | NavigatorId = i + 1, 26 | Created = "2013-01-01 08:00:00", 27 | Modified = "2013-01-01 08:00:00", 28 | NewFlag = 0, 29 | UseFlag = 1 30 | }; 31 | list.Add(navigator); 32 | } 33 | 34 | var result = list.SerializeCardDataList(NAVIGATOR_XPATH); 35 | 36 | return Task.FromResult(new ServiceResult(result)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadSoundEffectQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadSoundEffectQuery(long CardId) : IRequestWrapper; 5 | 6 | public class ReadSoundEffectQueryHandler : RequestHandlerBase 7 | { 8 | private const string SOUND_EFFECT_XPATH = "/root/sound_effect/record"; 9 | public ReadSoundEffectQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 10 | { 11 | } 12 | 13 | public override Task> Handle(ReadSoundEffectQuery request, CancellationToken cancellationToken) 14 | { 15 | var count = Config.SeCount; 16 | 17 | var list = new List(); 18 | for (int i = 0; i < count; i++) 19 | { 20 | var soundEffect = new SoundEffectDto 21 | { 22 | Id = i, 23 | CardId = request.CardId, 24 | SoundEffectId = i + 1, 25 | Created = "2013-01-01 08:00:00", 26 | Modified = "2013-01-01 08:00:00", 27 | NewFlag = 0, 28 | UseFlag = 1 29 | }; 30 | list.Add(soundEffect); 31 | } 32 | 33 | var result = list.SerializeCardDataList(SOUND_EFFECT_XPATH); 34 | 35 | return Task.FromResult(new ServiceResult(result)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Application/Game/Rank/GetEventRankQuery.cs: -------------------------------------------------------------------------------- 1 | using Application.Common.Helpers; 2 | 3 | namespace Application.Game.Rank; 4 | 5 | public record GetEventRankQuery() : IRequestWrapper; 6 | 7 | public class GetEventRankQueryHandler : IRequestHandlerWrapper 8 | { 9 | public Task> Handle(GetEventRankQuery request, CancellationToken cancellationToken) 10 | { 11 | var container = new EventRankContainer 12 | { 13 | Ranks = new List(), 14 | Status = new RankStatus 15 | { 16 | TableName = "EventRank", 17 | StartDate = TimeHelper.DateToString(DateTime.Today), 18 | EndDate = TimeHelper.DateToString(DateTime.Today), 19 | Rows = 0, 20 | Status = 0 21 | } 22 | }; 23 | 24 | return Task.FromResult(new ServiceResult(container.SerializeCardData())); 25 | } 26 | } 27 | 28 | [XmlRoot("root")] 29 | public class EventRankContainer 30 | { 31 | [XmlArray(ElementName = "event_rank")] 32 | [XmlArrayItem(ElementName = "record")] 33 | // ReSharper disable once UnusedAutoPropertyAccessor.Global 34 | public List Ranks { get; init; } = new(); 35 | 36 | [XmlElement("ranking_status")] 37 | public RankStatus Status { get; set; } = new(); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadUnlockKeynumQuery.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Game.Card.Read; 2 | 3 | 4 | public record ReadUnlockKeynumQuery(long CardId) : IRequestWrapper; 5 | 6 | public class ReadUnlockKeynumQueryHandler : RequestHandlerBase 7 | { 8 | private const string UNLOCK_KEYNUM_XPATH = "/root/unlock_keynum/record"; 9 | public ReadUnlockKeynumQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 10 | { 11 | } 12 | 13 | public override Task> Handle(ReadUnlockKeynumQuery request, CancellationToken cancellationToken) 14 | { 15 | var unlockables = Config.UnlockRewards; 16 | var list = unlockables.Select((unlockable, index) => new UnlockKeyNumDto 17 | { 18 | Id = index, 19 | CardId = request.CardId, 20 | RewardId = unlockable.RewardId, 21 | KeyNum = 0, 22 | RewardCount = 0, 23 | CashFlag = 0, 24 | ExpiredFlag = 0, 25 | UseFlag = 1, 26 | Created = "2013-01-01 08:00:00", 27 | Modified = "2020-01-01 08:00:00" 28 | }) 29 | .ToList(); 30 | var result = list.SerializeCardDataList(UNLOCK_KEYNUM_XPATH); 31 | 32 | return Task.FromResult(new ServiceResult(result)); 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /doc/card_detail.md: -------------------------------------------------------------------------------- 1 | # Card detail table 2 | 3 | card_id, pcol1, pcol2, pcol3 are primary keys 4 | 5 | | pcol1 | pco2 | pcol3 | Function | 6 | | :---: | :-----: | :--------: | :----------------------------------------------------------: | 7 | | 0 | 0 | 0 | score_i1:**avatar** score_ui1:**fast/slow** score_ui2:**fever/trance** fcol1: fcol2:**title** fcol3:**sound** | 8 | | 1 | 0 | 0 | score_i1:**navigator** fcol3:**unknown** | 9 | | 10 | song id | 0 | score_i1:**skin**, score_ui2:**Whether the song is unlocked**, score_ui6: **The song need to be unlocked**, fcol1:**Favorite song** | 10 | | 20 | song id | difficulty | score_i1: **rank(S and above=0,A=1,etc)**,**score_ui1:**play count** score_ui2:**clear count** score_ui3:**no miss or higher count** score_ui4: **full_chain or higher count** score_ui5: **1 for S+,2 for S++** score_ui6: **perfect count** | 11 | | 21 | song id | difficulty | score_ui1/score_ui5: **highest score**, score_ui2/score_ui6:**highest_score time** score_ui3:**max_chain** score_ui4: **max chain time** fcol1:**max hit adlib count** fcol2:**time for fcol1** | 12 | | 30 | 0 | 0 | score_ui2:**Unknown, looks like some sort of count** | 13 | | 31 | 0 | 0 | score_bi1: **Unknown, looks like some sort of count** | 14 | 15 | -------------------------------------------------------------------------------- /Infrastructure/Migrations/MigrationHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using Infrastructure.Common; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace Infrastructure.Migrations; 7 | 8 | public static class MigrationHelper 9 | { 10 | public static bool Exists(string tableName) 11 | { 12 | var options = new DbContextOptionsBuilder().UseSqlite($"Data Source={GetConnectionString()}"); 13 | using var context = new DbContext(options.Options); 14 | using var command = context.Database.GetDbConnection().CreateCommand(); 15 | command.CommandText = $"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='{tableName}';"; 16 | command.CommandType = CommandType.Text; 17 | context.Database.OpenConnection(); 18 | 19 | using var reader = command.ExecuteReader(); 20 | 21 | return reader.Read()? (long)reader[0] == 1 : false; 22 | } 23 | 24 | private static string GetConnectionString() 25 | { 26 | var builder = new ConfigurationBuilder() 27 | .SetBasePath(PathHelper.ConfigurationPath) 28 | .AddJsonFile("database.json", optional: false, reloadOnChange: false); 29 | 30 | var cardDbName = builder.Build()["CardDbName"] ?? "card.db3"; 31 | var cardDbPath = Path.Combine(PathHelper.DatabasePath, cardDbName); 32 | return cardDbPath; 33 | } 34 | } -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadMusicAouQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace Application.Game.Card.Read; 4 | 5 | 6 | public record ReadMusicAouQuery(long CardId) : IRequestWrapper; 7 | 8 | public class ReadMusicAouQueryHandler : RequestHandlerBase 9 | { 10 | private const string MUSIC_AOU_XPATH = "/root/music_aou"; 11 | 12 | private const string RECORD_XPATH = $"{MUSIC_AOU_XPATH}/record"; 13 | 14 | public ReadMusicAouQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 15 | { 16 | } 17 | 18 | [SuppressMessage("ReSharper.DPA", "DPA0007: Large number of DB records", 19 | Justification = "To return all musics, the whole table need to be returned")] 20 | public override async Task> Handle(ReadMusicAouQuery request, CancellationToken cancellationToken) 21 | { 22 | var musics = await MusicDbContext.MusicAous.ToListAsync(cancellationToken: cancellationToken); 23 | var dtoList = musics.Select((aou, i) => 24 | { 25 | var dto = aou.MusicAouToDto(); 26 | dto.Id = i; 27 | return dto; 28 | }).ToList(); 29 | 30 | var result = dtoList.Count == 0 ? new object().SerializeCardData(MUSIC_AOU_XPATH) 31 | : dtoList.SerializeCardDataList(RECORD_XPATH); 32 | 33 | return new ServiceResult(result); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /GCRelayServer/RelayPacket.cs: -------------------------------------------------------------------------------- 1 | using BinarySerialization; 2 | 3 | namespace GCRelayServer; 4 | 5 | public class RelayPacket 6 | { 7 | [FieldOrder(0)] 8 | [FieldEndianness(Endianness.Big)] 9 | public ushort Magic; 10 | 11 | [FieldOrder(1)] 12 | [FieldEndianness(Endianness.Big)] 13 | public ushort RemainingSize; 14 | 15 | [FieldOrder(2)] 16 | [FieldEndianness(Endianness.Big)] 17 | public ushort RequestMainType; 18 | 19 | [FieldOrder(3)] 20 | [FieldCount(6)] 21 | public byte[] Unknown0 = Array.Empty(); 22 | 23 | [FieldOrder(4)] 24 | [FieldEndianness(Endianness.Big)] 25 | public ushort RequestSubType; 26 | 27 | [FieldOrder(5)] 28 | [FieldEndianness(Endianness.Big)] 29 | public ushort Unknown1; 30 | 31 | [FieldOrder(6)] 32 | [FieldEndianness(Endianness.Big)] 33 | public ushort DataSize; 34 | 35 | [FieldOrder(7)] 36 | [FieldEndianness(Endianness.Big)] 37 | public ushort Unknown2; 38 | 39 | [FieldOrder(8)] 40 | [FieldEndianness(Endianness.Big)] 41 | public uint MatchingId; 42 | 43 | [FieldOrder(9)] 44 | [FieldEndianness(Endianness.Big)] 45 | public uint EntryNo; 46 | 47 | [FieldOrder(10)] 48 | [FieldEndianness(Endianness.Big)] 49 | public uint MachineId; 50 | 51 | [FieldOrder(11)] 52 | [FieldEndianness(Endianness.Big)] 53 | public uint Unknown3; 54 | 55 | [FieldOrder(12)] 56 | public byte[] Data = Array.Empty(); 57 | } -------------------------------------------------------------------------------- /Infrastructure/Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Application/Api/SetPlayerNameCommand.cs: -------------------------------------------------------------------------------- 1 | using Application.Common.Helpers; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Application.Api; 5 | 6 | public record SetPlayerNameCommand(ClientCardDto Card) : IRequestWrapper; 7 | 8 | public class SetPlayerNameCommandHandler : RequestHandlerBase 9 | { 10 | private readonly ILogger logger; 11 | public SetPlayerNameCommandHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) 12 | { 13 | this.logger = logger; 14 | } 15 | 16 | public override async Task> Handle(SetPlayerNameCommand request, CancellationToken cancellationToken) 17 | { 18 | var card = await CardDbContext.CardMains.FirstOrDefaultAsync(card => card.CardId == request.Card.CardId, cancellationToken: cancellationToken); 19 | 20 | if (card is null) 21 | { 22 | logger.LogWarning("Attempt to set name for a non existing card {CardId}", request.Card.CardId); 23 | return ServiceResult.Failed(ServiceError.UserNotFound); 24 | } 25 | 26 | card.PlayerName = request.Card.PlayerName; 27 | card.Modified = TimeHelper.CurrentTimeToString(); 28 | 29 | CardDbContext.CardMains.Update(card); 30 | var count = await CardDbContext.SaveChangesAsync(cancellationToken); 31 | return count == 1 ? new ServiceResult(true) : ServiceResult.Failed(ServiceError.DatabaseSaveFailed); 32 | } 33 | } -------------------------------------------------------------------------------- /WebUI/Common/Models/Title.cs: -------------------------------------------------------------------------------- 1 | namespace WebUI.Common.Models; 2 | 3 | public class Title 4 | { 5 | public uint Id { get; set; } 6 | 7 | public string TitleName { get; set; } = string.Empty; 8 | 9 | public string UnlockRequirementJp { get; set; } = string.Empty; 10 | 11 | public string UnlockRequirementEn { get; set; } = string.Empty; 12 | 13 | public UnlockType UnlockType { get; set; } 14 | } 15 | 16 | public enum UnlockType 17 | { 18 | Invalid = 0, 19 | Default = 1, 20 | Clear = 2, 21 | NoMiss = 3, 22 | FullChain = 4, 23 | SRankSimpleStages = 5, 24 | SRankNormalStages = 6, 25 | SRankHardStages = 7, 26 | SRankExtraStages = 8, 27 | SRankSNH = 9, 28 | SPlusRankSNH = 10, 29 | SPlusPlusRankSNH = 11, 30 | Event = 12, 31 | Prefecture = 13, 32 | ChainMilestone = 14, 33 | Adlibs = 15, 34 | ConsecutiveNoMiss = 16, 35 | ClearsUsingItems = 17, 36 | Avatars = 18, 37 | MultiplayerStarsTotal = 19, 38 | SongSet20 = 20, 39 | SongSet21 = 21, 40 | SongSet22 = 22, 41 | SongSet23 = 23, 42 | SongSet24 = 24, 43 | SongSet25 = 25, 44 | SongSet26 = 26, 45 | ProfileLevel = 27, 46 | Perfect = 28, 47 | OnlineMatching = 29, 48 | Trophies = 30, 49 | } -------------------------------------------------------------------------------- /Application/Game/Card/Management/CardRegisterCommand.cs: -------------------------------------------------------------------------------- 1 | using Application.Common.Helpers; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Application.Game.Card.Management; 5 | 6 | public record CardRegisterCommand(long CardId, string Data) : IRequestWrapper; 7 | 8 | public class RegisterCommandHandler : RequestHandlerBase 9 | { 10 | private readonly ILogger logger; 11 | public RegisterCommandHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) 12 | { 13 | this.logger = logger; 14 | } 15 | 16 | public override async Task> Handle(CardRegisterCommand request, CancellationToken cancellationToken) 17 | { 18 | var exists = CardDbContext.CardMains.Any(card => card.CardId == request.CardId); 19 | if (exists) 20 | { 21 | return ServiceResult.Failed(ServiceError.CustomMessage($"Card {request.CardId} already exists!")); 22 | } 23 | 24 | var card = request.Data.DeserializeCardData().CardDtoToCardMain(); 25 | card.CardId = request.CardId; 26 | card.Created = TimeHelper.CurrentTimeToString(); 27 | card.Modified = card.Created; 28 | logger.LogInformation("New card {{Id: {Id}, Player Name: {Name}}} registered", card.CardId, card.PlayerName); 29 | CardDbContext.CardMains.Add(card); 30 | await CardDbContext.SaveChangesAsync(cancellationToken); 31 | 32 | return new ServiceResult(request.Data); 33 | } 34 | } -------------------------------------------------------------------------------- /Infrastructure/Migrations/20230214162154_AddPlayNumRank.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Infrastructure.Migrations 6 | { 7 | /// 8 | public partial class AddPlayNumRank : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.CreateTable( 14 | name: "PlayNumRank", 15 | columns: table => new 16 | { 17 | MusicId = table.Column(type: "INTEGER", nullable: false), 18 | PlayCount = table.Column(type: "INTEGER", nullable: false), 19 | Rank = table.Column(type: "INTEGER", nullable: false), 20 | Rank2 = table.Column(type: "INTEGER", nullable: false), 21 | PrevRank = table.Column(type: "INTEGER", nullable: false), 22 | PrevRank2 = table.Column(type: "INTEGER", nullable: false), 23 | Title = table.Column(type: "TEXT", nullable: false), 24 | Artist = table.Column(type: "TEXT", nullable: false) 25 | }, 26 | constraints: table => 27 | { 28 | table.PrimaryKey("PK_PlayNumRank", x => x.MusicId); 29 | }); 30 | } 31 | 32 | /// 33 | protected override void Down(MigrationBuilder migrationBuilder) 34 | { 35 | migrationBuilder.DropTable( 36 | name: "PlayNumRank"); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadCardBDataQuery.cs: -------------------------------------------------------------------------------- 1 | using Domain.Enums; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Application.Game.Card.Read; 5 | 6 | 7 | public record ReadCardBDataQuery(long CardId) : IRequestWrapper; 8 | 9 | public class ReadBDataQueryHandler : RequestHandlerBase 10 | { 11 | private const string CARD_BDATA_XPATH = "/root/card_bdata"; 12 | 13 | private readonly ILogger logger; 14 | 15 | public ReadBDataQueryHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) 16 | { 17 | this.logger = logger; 18 | } 19 | 20 | public override async Task> Handle(ReadCardBDataQuery request, CancellationToken cancellationToken) 21 | { 22 | var exists = await CardDbContext.CardMains.AnyAsync(card => card.CardId == request.CardId, cancellationToken: cancellationToken); 23 | if (!exists) 24 | { 25 | logger.LogWarning("Card id: {CardId} does not exist!", request.CardId); 26 | return ServiceResult.Failed( 27 | new ServiceError($"Card id: {request.CardId} does not exist!", (int)CardReturnCode.CardNotRegistered)); 28 | } 29 | 30 | var bdata = await CardDbContext.CardBdata.FirstOrDefaultAsync( 31 | card => card.CardId == request.CardId, cancellationToken: cancellationToken); 32 | 33 | var result = bdata?.CardBDatumToDto().SerializeCardData(CARD_BDATA_XPATH) 34 | ?? new object().SerializeCardData(CARD_BDATA_XPATH); 35 | 36 | return new ServiceResult(result); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Shared/Models/ServiceResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Shared.Models; 4 | 5 | /// 6 | /// A standard response for service calls. 7 | /// 8 | /// Return data type 9 | public class ServiceResult : ServiceResult 10 | { 11 | public T? Data { get; set; } 12 | 13 | public ServiceResult() { } 14 | 15 | public ServiceResult(T? data) 16 | { 17 | Data = data; 18 | } 19 | 20 | public ServiceResult(T? data, ServiceError error) : base(error) 21 | { 22 | Data = data; 23 | } 24 | 25 | public ServiceResult(ServiceError error) : base(error) 26 | { 27 | 28 | } 29 | } 30 | 31 | public class ServiceResult 32 | { 33 | public bool Succeeded => Error is null; 34 | 35 | public ServiceError? Error { get; set; } 36 | 37 | public ServiceResult(ServiceError? error) 38 | { 39 | error ??= ServiceError.DefaultError; 40 | 41 | Error = error; 42 | } 43 | 44 | public ServiceResult() { } 45 | 46 | #region Helper Methods 47 | 48 | public static ServiceResult Failed(ServiceError error) 49 | { 50 | return new ServiceResult(error); 51 | } 52 | 53 | public static ServiceResult Failed(ServiceError error) 54 | { 55 | return new ServiceResult(error); 56 | } 57 | 58 | public static ServiceResult Failed(T data, ServiceError error) 59 | { 60 | return new ServiceResult(data, error); 61 | } 62 | 63 | public static ServiceResult Success(T data) 64 | { 65 | return new ServiceResult(data); 66 | } 67 | 68 | #endregion 69 | } -------------------------------------------------------------------------------- /Application/Dto/Game/ScoreRankDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class ScoreRankDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement(ElementName = "card_id")] 9 | public long CardId { get; set; } 10 | 11 | [XmlElement(ElementName = "player_name")] 12 | public string PlayerName { get; set; } = string.Empty; 13 | 14 | [XmlElement(ElementName = "rank")] 15 | public long Rank { get; set; } 16 | 17 | [XmlElement(ElementName = "rank2")] 18 | public long Rank2 { get; set; } 19 | 20 | [XmlElement(ElementName = "score_bi1")] 21 | public long TotalScore { get; set; } 22 | 23 | [XmlElement(ElementName = "score_i1")] 24 | public int AvatarId { get; set; } 25 | 26 | [XmlElement(ElementName = "fcol2")] 27 | public long TitleId { get; set; } 28 | 29 | [XmlElement(ElementName = "fcol1")] 30 | public long Fcol1 { get; set; } 31 | 32 | [XmlElement(ElementName = "pref_id")] 33 | public int PrefId { get; set; } 34 | 35 | [XmlElement(ElementName = "pref")] 36 | public string Pref { get; set; } = string.Empty; 37 | 38 | [XmlElement(ElementName = "area_id")] 39 | public int AreaId { get; set; } 40 | 41 | [XmlElement(ElementName = "area")] 42 | public string Area { get; set; } = string.Empty; 43 | 44 | [XmlElement(ElementName = "last_play_tenpo_id")] 45 | public int LastPlayTenpoId { get; set; } 46 | 47 | [XmlElement(ElementName = "tenpo_name")] 48 | public string TenpoName { get; set; } = string.Empty; 49 | 50 | [XmlElement(ElementName = "title")] 51 | public string Title { get; set; } = string.Empty; 52 | } -------------------------------------------------------------------------------- /Application/Game/Server/GetDataQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using MediatR; 3 | 4 | namespace Application.Game.Server; 5 | 6 | public record GetDataQuery(string Host, string Scheme) : IRequest; 7 | 8 | public class GetDataQueryHandler : IRequestHandler 9 | { 10 | private readonly IEventManagerService eventManagerService; 11 | 12 | public GetDataQueryHandler(IEventManagerService eventManagerService) 13 | { 14 | this.eventManagerService = eventManagerService; 15 | } 16 | 17 | public Task Handle(GetDataQuery request, CancellationToken cancellationToken) 18 | { 19 | var response = "count=0\n" + 20 | "nexttime=180"; 21 | if (!eventManagerService.UseEvents()) 22 | { 23 | return Task.FromResult(response); 24 | } 25 | 26 | var urlBase = $"{request.Scheme}://{request.Host}/events/"; 27 | var dataString = new StringBuilder(); 28 | var events = eventManagerService.GetEvents(); 29 | var count = 0; 30 | foreach (var pair in events.Select((@event, i) => new {Value = @event, Index = i})) 31 | { 32 | var value = pair.Value; 33 | var index = pair.Index; 34 | var fileUrl = $"{urlBase}{value.Name}"; 35 | var eventString = $"{index},{fileUrl},{value.NotBefore},{value.NotAfter},{value.Md5},{value.Index}"; 36 | dataString.Append(eventString).Append('\n'); 37 | count++; 38 | } 39 | 40 | response = $"count={count}\n" + 41 | "nexttime=1\n" + 42 | $"{dataString}"; 43 | 44 | return Task.FromResult(response); 45 | } 46 | } -------------------------------------------------------------------------------- /Domain/Enums/CardRequestType.cs: -------------------------------------------------------------------------------- 1 | namespace Domain.Enums; 2 | 3 | public enum CardRequestType 4 | { 5 | #region Read 6 | 7 | ReadCard = 259, 8 | ReadCardDetail = 260, 9 | ReadCardDetails = 261, 10 | ReadCardBData = 264, 11 | ReadAvatar = 418, 12 | ReadItem = 420, 13 | ReadSkin = 422, 14 | ReadTitle = 424, 15 | ReadMusic = 428, 16 | ReadMusicDetail = 428, 17 | ReadEventReward = 441, 18 | ReadNavigator = 443, 19 | ReadWMusicDetail = 428, 20 | ReadMusicExtra = 465, 21 | ReadMusicAou = 467, 22 | ReadCoin = 468, 23 | ReadUnlockReward = 507, 24 | ReadUnlockKeynum = 509, 25 | ReadSoundEffect = 8458, 26 | ReadGetMessage = 8461, 27 | ReadCond = 8465, 28 | ReadTotalTrophy = 8468, 29 | 30 | #endregion 31 | 32 | #region Session 33 | 34 | GetSession = 401, 35 | StartSession = 402, 36 | 37 | #endregion 38 | 39 | 40 | #region Write 41 | 42 | WriteCard = 771, 43 | WriteCardDetail = 772, 44 | WriteCardBData = 776, 45 | WriteAvatar = 929, 46 | WriteItem = 931, 47 | WriteSkin = 933, 48 | WriteTitle = 935, 49 | WriteMusic = 939, 50 | WriteMusicDetail = 941, 51 | WriteWMusicDetail = 961, 52 | WriteNavigator = 954, 53 | WriteCoin = 980, 54 | WriteUnlockKeynum = 1020, 55 | WriteSoundEffect = 8969, 56 | WriteCond = 8976, 57 | 58 | #endregion 59 | 60 | 61 | #region Online Matching 62 | 63 | StartOnlineMatching = 8705, 64 | UpdateOnlineMatching = 8961, 65 | UploadOnlineMatchingResult = 8709, 66 | 67 | #endregion 68 | } -------------------------------------------------------------------------------- /MainServer/Controllers/Game/ServerController.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Application.Game.Server; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace MainServer.Controllers.Game; 6 | 7 | [ApiController] 8 | [Route("server")] 9 | public class ServerController : BaseController 10 | { 11 | [HttpGet("cursel.php")] 12 | public ActionResult GetCursel() 13 | { 14 | return Ok("1\n"); 15 | } 16 | 17 | [HttpGet("gameinfo.php")] 18 | public ActionResult GetGameInfo() 19 | { 20 | return Ok("0\n" + 21 | "3\n" + 22 | "301000,test1\n" + 23 | "302000,test2\n" + 24 | "303000,test3"); 25 | } 26 | 27 | [HttpGet("certify.php")] 28 | public async Task Certify(string? gid, string? mac, 29 | [FromQuery(Name = "r")]string? random, [FromQuery(Name = "md")]string? md5) 30 | { 31 | var host = Request.Host.Value; 32 | var command = new CertifyCommand(gid, mac, random, md5, host); 33 | var result = await Mediator.Send(command); 34 | var shiftJis = Encoding.GetEncoding(932); 35 | var originalBytes = Encoding.UTF8.GetBytes(result); 36 | var converted = Encoding.Convert(Encoding.Default, shiftJis, originalBytes); 37 | result = shiftJis.GetString(converted); 38 | //Response.ContentType = "text/plain; charset=shift_jis"; 39 | 40 | return Content(result, "text/plain", shiftJis); 41 | } 42 | 43 | [HttpGet("data.php")] 44 | public async Task> GetData() 45 | { 46 | var query = new GetDataQuery(Request.Host.Value, Request.Scheme); 47 | return Ok(await Mediator.Send(query)); 48 | } 49 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteCardDetailCommand.cs: -------------------------------------------------------------------------------- 1 | using Domain.Enums; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Application.Game.Card.Write; 5 | 6 | public record WriteCardDetailCommand(long CardId, string TenpoId, string Data) : IRequestWrapper; 7 | 8 | public class WriteDetailCommandHandler : RequestHandlerBase 9 | { 10 | private readonly ILogger logger; 11 | 12 | public WriteDetailCommandHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) 13 | { 14 | this.logger = logger; 15 | } 16 | 17 | public override async Task> Handle(WriteCardDetailCommand request, CancellationToken cancellationToken) 18 | { 19 | var exists = await CardDbContext.CardMains.AnyAsync(card => card.CardId == request.CardId, cancellationToken: cancellationToken); 20 | if (!exists) 21 | { 22 | logger.LogWarning("Card id: {CardId} does not exist!", request.CardId); 23 | return ServiceResult.Failed( 24 | new ServiceError($"Card id: {request.CardId} does not exist!", (int)CardReturnCode.CardNotRegistered)); 25 | } 26 | 27 | var dto = request.Data.DeserializeCardData(); 28 | var detail = dto.DtoToCardDetail(); 29 | detail.CardId = request.CardId; 30 | detail.LastPlayTime = DateTime.Now; 31 | detail.LastPlayTenpoId = request.TenpoId; 32 | await CardDbContext.CardDetails.Upsert(detail).RunAsync(cancellationToken); 33 | 34 | await CardDbContext.SaveChangesAsync(cancellationToken); 35 | 36 | return new ServiceResult(request.Data); 37 | } 38 | } -------------------------------------------------------------------------------- /GCRelayServer/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Swan.Logging; 3 | 4 | namespace GCRelayServer 5 | { 6 | 7 | public static class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | #if DEBUG 12 | ConsoleLogger.Instance.LogLevel = LogLevel.Debug; 13 | #endif 14 | // UDP server port 15 | var port = 3333; 16 | if (args.Length > 0) 17 | { 18 | port = int.Parse(args[0]); 19 | } 20 | 21 | $"UDP server port: {port}".Info(); 22 | 23 | // Create a new UDP echo server 24 | var server = new RelayServer(IPAddress.Any, port); 25 | 26 | // Start the server 27 | "Server starting...".Info(); 28 | server.Start(); 29 | "Server started".Info(); 30 | 31 | "Press Enter to stop the server or '!' to restart the server...".Info(); 32 | 33 | // Perform text input 34 | for (;;) 35 | { 36 | var line = Console.ReadLine(); 37 | if (string.IsNullOrEmpty(line)) 38 | { 39 | break; 40 | } 41 | 42 | // Restart the server 43 | if (line != "!") 44 | { 45 | continue; 46 | } 47 | "Server restarting...".Info(); 48 | server.Restart(); 49 | "Server restarted".Info(); 50 | } 51 | 52 | // Stop the server 53 | "Server stopping...".Info(); 54 | server.Stop(); 55 | "Server stopped, press any key to close".Info(); 56 | Console.ReadKey(true); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /WebUI/Pages/Cards.razor.cs: -------------------------------------------------------------------------------- 1 | using Shared.Dto.Api; 2 | using Throw; 3 | using WebUI.Pages.Dialogs; 4 | 5 | namespace WebUI.Pages; 6 | 7 | public partial class Cards 8 | { 9 | [Inject] 10 | public required HttpClient Client { get; set; } 11 | 12 | [Inject] 13 | public required IDialogService DialogService { get; set; } 14 | 15 | [Inject] 16 | public required ILogger Logger { get; set; } 17 | 18 | private List? CardDtos { get; set; } 19 | 20 | private string ErrorMessage { get; set; } = string.Empty; 21 | 22 | protected override async Task OnInitializedAsync() 23 | { 24 | await base.OnInitializedAsync(); 25 | 26 | var result = await Client.GetFromJsonAsync>>("api/Profiles"); 27 | result.ThrowIfNull(); 28 | 29 | Logger.LogInformation("Result: {Result}", result.Succeeded); 30 | 31 | if (!result.Succeeded) 32 | { 33 | ErrorMessage = result.Error!.Message; 34 | return; 35 | } 36 | CardDtos = result.Data; 37 | } 38 | 39 | private async Task OnEditPlayerNameClicked(ClientCardDto card) 40 | { 41 | var options = new DialogOptions 42 | { 43 | CloseOnEscapeKey = false, 44 | DisableBackdropClick = true, 45 | FullWidth = true 46 | }; 47 | var parameters = new DialogParameters { { "Data", card } }; 48 | var dialog = await DialogService.ShowAsync("Favorite", parameters, options); 49 | // ReSharper disable once UnusedVariable 50 | var result = await dialog.Result; 51 | if (!result.Canceled) 52 | { 53 | StateHasChanged(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Infrastructure/DependencyInjection.cs: -------------------------------------------------------------------------------- 1 | using Application.Interfaces; 2 | using Infrastructure.Common; 3 | using Infrastructure.Persistence; 4 | using Infrastructure.Services; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace Infrastructure; 10 | 11 | public static class DependencyInjection 12 | { 13 | public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) 14 | { 15 | services.AddSingleton(); 16 | 17 | services.AddDbContext(option => 18 | { 19 | var dbName = configuration["MusicDbName"]; 20 | if (string.IsNullOrEmpty(dbName)) 21 | { 22 | dbName = "music471omni.db3"; 23 | } 24 | 25 | var path = Path.Combine(PathHelper.DatabasePath, dbName); 26 | option.UseSqlite($"Data Source={path}"); 27 | }); 28 | 29 | services.AddDbContext(option => 30 | { 31 | var dbName = configuration["CardDbName"]; 32 | if (string.IsNullOrEmpty(dbName)) 33 | { 34 | dbName = "card.db3"; 35 | } 36 | 37 | var path = Path.Combine(PathHelper.DatabasePath, dbName); 38 | option.UseSqlite($"Data Source={path}"); 39 | }); 40 | 41 | services.AddScoped(provider => provider.GetService() ?? throw new InvalidOperationException()); 42 | services.AddScoped(provider => provider.GetService() ?? throw new InvalidOperationException()); 43 | 44 | return services; 45 | } 46 | } -------------------------------------------------------------------------------- /WebUI/Common/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 15 | 16 | 17 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | GC Local Server WebUI 29 | 30 | 31 | 32 | 33 | 34 | @Body 35 | 36 | 37 | -------------------------------------------------------------------------------- /Application/Mappers/CardDetailMapper.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Riok.Mapperly.Abstractions; 3 | 4 | namespace Application.Mappers; 5 | 6 | [Mapper] 7 | public static partial class CardDetailMapper 8 | { 9 | public static partial CardDetailDto CardDetailToDto(this CardDetail cardDetail); 10 | 11 | public static partial CardDetail DtoToCardDetail(this CardDetailDto dto); 12 | 13 | [MapperIgnoreSource(nameof(FirstPlayOptionDto.CardId))] 14 | [MapProperty(nameof(FirstPlayOptionDto.AvatarId), nameof(CardDetail.ScoreI1))] 15 | [MapProperty(nameof(FirstPlayOptionDto.TitleId), nameof(CardDetail.Fcol2))] 16 | [MapProperty(nameof(FirstPlayOptionDto.ShowFastSlowOption), nameof(CardDetail.ScoreUi1))] 17 | [MapProperty(nameof(FirstPlayOptionDto.ShowFeverTranceOption), nameof(CardDetail.ScoreUi2))] 18 | public static partial void MapFirstOptionDetail(this FirstPlayOptionDto dto, CardDetail detail); 19 | 20 | [MapperIgnoreSource(nameof(SecondPlayOptionDto.CardId))] 21 | [MapProperty(nameof(SecondPlayOptionDto.NavigatorId), nameof(CardDetail.ScoreI1))] 22 | public static partial void MapSecondOptionDetail(this SecondPlayOptionDto dto, CardDetail detail); 23 | 24 | [MapProperty(nameof(CardDetail.ScoreI1), nameof(FirstPlayOptionDto.AvatarId))] 25 | [MapProperty(nameof(CardDetail.Fcol2), nameof(FirstPlayOptionDto.TitleId))] 26 | [MapProperty(nameof(CardDetail.ScoreUi1), nameof(FirstPlayOptionDto.ShowFastSlowOption))] 27 | [MapProperty(nameof(CardDetail.ScoreUi2), nameof(FirstPlayOptionDto.ShowFeverTranceOption))] 28 | public static partial FirstPlayOptionDto CardDetailToFirstOption(this CardDetail detail); 29 | 30 | [MapProperty(nameof(CardDetail.ScoreI1), nameof(SecondPlayOptionDto.NavigatorId))] 31 | public static partial SecondPlayOptionDto CardDetailToSecondOption(this CardDetail detail); 32 | 33 | } -------------------------------------------------------------------------------- /Application/Api/SetFavoriteMusicCommand.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Application.Api; 4 | 5 | public record SetFavoriteMusicCommand(MusicFavoriteDto Data) : IRequestWrapper; 6 | 7 | public class SetFavoriteMusicCommandHandler : RequestHandlerBase 8 | { 9 | private readonly ILogger logger; 10 | 11 | public SetFavoriteMusicCommandHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) 12 | { 13 | this.logger = logger; 14 | } 15 | 16 | public override async Task> Handle(SetFavoriteMusicCommand request, 17 | CancellationToken cancellationToken) 18 | { 19 | var musicDetail = await CardDbContext.CardDetails.FirstOrDefaultAsync(detail => 20 | detail.CardId == request.Data.CardId && 21 | detail.Pcol1 == 10 && 22 | detail.Pcol2 == request.Data.MusicId && 23 | detail.Pcol3 == 0, 24 | cancellationToken); 25 | 26 | if (musicDetail is null) 27 | { 28 | logger.LogWarning("Attempt to set favorite for non existing music, card id: {CardId}, music id: {MusicId}", 29 | request.Data.CardId, request.Data.MusicId); 30 | return ServiceResult.Failed(ServiceError.CustomMessage("Music record not found")); 31 | } 32 | 33 | musicDetail.Fcol1 = request.Data.IsFavorite ? 1 : 0; 34 | CardDbContext.CardDetails.Update(musicDetail); 35 | var count = await CardDbContext.SaveChangesAsync(cancellationToken); 36 | 37 | return count == 1 ? new ServiceResult(true) : ServiceResult.Failed(ServiceError.DatabaseSaveFailed); 38 | } 39 | } -------------------------------------------------------------------------------- /MainServer/Controllers/API/ProfilesController.cs: -------------------------------------------------------------------------------- 1 | using Application.Api; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Shared.Dto.Api; 4 | using Shared.Models; 5 | 6 | namespace MainServer.Controllers.API; 7 | 8 | [ApiController] 9 | [Route("api/[controller]")] 10 | public class ProfilesController : BaseController 11 | { 12 | [HttpGet] 13 | public async Task>> GetAllCards() 14 | { 15 | var result = await Mediator.Send(new GetCardsQuery()); 16 | return result; 17 | } 18 | 19 | [HttpGet("TotalResult/{cardId:long}")] 20 | public async Task> GetCardTotalResultById(long cardId) 21 | { 22 | var result = await Mediator.Send(new GetTotalResultQuery(cardId)); 23 | return result; 24 | } 25 | 26 | [HttpGet("SongPlayRecords/{cardId:long}")] 27 | public async Task>> GetSongPlayRecords(long cardId) 28 | { 29 | var result = await Mediator.Send(new GetSongPlayRecordsQuery(cardId)); 30 | 31 | return result; 32 | } 33 | 34 | [HttpPost("SetFavorite")] 35 | public async Task> SetFavoriteMusic(MusicFavoriteDto favorite) 36 | { 37 | var result = await Mediator.Send(new SetFavoriteMusicCommand(favorite)); 38 | return result; 39 | } 40 | 41 | [HttpPost("PlayerName")] 42 | public async Task> SetPlayerName(ClientCardDto card) 43 | { 44 | var result = await Mediator.Send(new SetPlayerNameCommand(card)); 45 | return result; 46 | } 47 | 48 | [HttpPost("UnlockAllMusic/{cardId:long}")] 49 | public async Task> UnlockAllMusic(long cardId) 50 | { 51 | var result = await Mediator.Send(new UnlockAllMusicCommand(cardId)); 52 | 53 | return result; 54 | } 55 | } -------------------------------------------------------------------------------- /Application/Api/GetPlayOptionQuery.cs: -------------------------------------------------------------------------------- 1 | using Shared.Models; 2 | 3 | namespace Application.Api; 4 | 5 | public record GetPlayOptionQuery(long CardId) : IRequestWrapper; 6 | 7 | public class GetPlayOptionQueryHandler : RequestHandlerBase 8 | { 9 | public GetPlayOptionQueryHandler(ICardDependencyAggregate aggregate) : base(aggregate) 10 | { 11 | } 12 | 13 | public override async Task> Handle(GetPlayOptionQuery request, 14 | CancellationToken cancellationToken) 15 | { 16 | var optionDetail1 = await CardDbContext.CardDetails.FirstOrDefaultAsync(detail => 17 | detail.CardId == request.CardId && 18 | detail.Pcol1 == 0 && 19 | detail.Pcol2 == 0 && 20 | detail.Pcol3 == 0, 21 | cancellationToken: cancellationToken); 22 | var optionDetail2 = await CardDbContext.CardDetails.FirstOrDefaultAsync(detail => 23 | detail.CardId == request.CardId && 24 | detail.Pcol1 == 1 && 25 | detail.Pcol2 == 0 && 26 | detail.Pcol3 == 0, 27 | cancellationToken: cancellationToken); 28 | if (optionDetail1 is null || 29 | optionDetail2 is null) 30 | { 31 | return ServiceResult.Failed(ServiceError.CustomMessage("At least one of the play option records not found")); 32 | } 33 | 34 | var result = new PlayOptionData 35 | { 36 | CardId = request.CardId, 37 | OptionPart1 = optionDetail1.CardDetailToFirstOption(), 38 | OptionPart2 = optionDetail2.CardDetailToSecondOption() 39 | }; 40 | 41 | return new ServiceResult(result); 42 | } 43 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteCardCommand.cs: -------------------------------------------------------------------------------- 1 | using Application.Common.Helpers; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Application.Game.Card.Write; 5 | 6 | public record WriteCardCommand(long CardId, string Data) : IRequestWrapper; 7 | 8 | public class WriteCommandHandler : RequestHandlerBase 9 | { 10 | private readonly ILogger logger; 11 | 12 | public WriteCommandHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) 13 | { 14 | this.logger = logger; 15 | } 16 | 17 | public override async Task> Handle(WriteCardCommand request, CancellationToken cancellationToken) 18 | { 19 | var dto = request.Data.DeserializeCardData(); 20 | dto.CardId = request.CardId; 21 | 22 | var card = await CardDbContext.CardMains.FirstOrDefaultAsync(card => card.CardId == request.CardId, cancellationToken: cancellationToken); 23 | 24 | if (card is null) 25 | { 26 | logger.LogInformation("Creating new card {CardId}", request.CardId); 27 | card = dto.CardDtoToCardMain(); 28 | card.Created = TimeHelper.CurrentTimeToString(); 29 | card.Modified = TimeHelper.CurrentTimeToString(); 30 | CardDbContext.CardMains.Add(card); 31 | } 32 | else 33 | { 34 | logger.LogInformation("Updating {CardId}", request.CardId); 35 | card.Fcol1 = dto.Fcol1; 36 | card.Fcol2 = dto.Fcol2; 37 | card.Fcol3 = dto.Fcol3; 38 | card.ScoreI1 = dto.ScoreI1; 39 | card.Modified = TimeHelper.CurrentTimeToString(); 40 | CardDbContext.CardMains.Update(card); 41 | } 42 | 43 | await CardDbContext.SaveChangesAsync(cancellationToken); 44 | 45 | return new ServiceResult(request.Data); 46 | } 47 | } -------------------------------------------------------------------------------- /Application/Dto/Game/CardDetailDto.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Application.Dto.Game; 4 | 5 | public class CardDetailDto 6 | { 7 | [XmlAttribute(AttributeName = "id")] 8 | public int Id { get; set; } = -1; 9 | 10 | public bool ShouldSerializeId() 11 | { 12 | return Id != -1; 13 | } 14 | 15 | [XmlElement(ElementName = "card_id")] 16 | public long CardId { get; set; } 17 | 18 | [XmlElement(ElementName = "pcol1")] 19 | public int Pcol1 { get; set; } 20 | 21 | [XmlElement(ElementName = "pcol2")] 22 | public int Pcol2 { get; set; } 23 | 24 | [XmlElement(ElementName = "pcol3")] 25 | public int Pcol3 { get; set; } 26 | 27 | [XmlElement(ElementName = "score_i1")] 28 | public long ScoreI1 { get; set; } 29 | 30 | [XmlElement(ElementName = "score_ui1")] 31 | public long ScoreUi1 { get; set; } 32 | 33 | [XmlElement(ElementName = "score_ui2")] 34 | public long ScoreUi2 { get; set; } 35 | 36 | [XmlElement(ElementName = "score_ui3")] 37 | public long ScoreUi3 { get; set; } 38 | 39 | [XmlElement(ElementName = "score_ui4")] 40 | public long ScoreUi4 { get; set; } 41 | 42 | [XmlElement(ElementName = "score_ui5")] 43 | public long ScoreUi5 { get; set; } 44 | 45 | [XmlElement(ElementName = "score_ui6")] 46 | public long ScoreUi6 { get; set; } 47 | 48 | [XmlElement(ElementName = "score_bi1")] 49 | public long ScoreBi1 { get; set; } 50 | 51 | [XmlElement(ElementName = "last_play_tenpo_id")] 52 | [DefaultValue("1337")] 53 | public string LastPlayTenpoId { get; set; } = "1337"; 54 | 55 | [XmlElement("fcol1")] 56 | public int Fcol1 { get; set; } 57 | 58 | [XmlElement("fcol2")] 59 | public int Fcol2 { get; set; } 60 | 61 | [XmlElement("fcol3")] 62 | public int Fcol3 { get; set; } 63 | 64 | [XmlIgnore] 65 | public DateTime LastPlayTime { get; set; } = DateTime.MinValue; 66 | } -------------------------------------------------------------------------------- /Application/Game/Option/PlayCountQuery.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Application.Game.Option; 5 | 6 | public record PlayCountQuery(long CardId) : IRequest; 7 | 8 | public class PlayCountQueryHandler : IRequestHandler 9 | { 10 | private readonly ICardDbContext context; 11 | 12 | private readonly ILogger logger; 13 | 14 | public PlayCountQueryHandler(ICardDbContext context, ILogger logger) 15 | { 16 | this.context = context; 17 | this.logger = logger; 18 | } 19 | 20 | public async Task Handle(PlayCountQuery request, CancellationToken cancellationToken) 21 | { 22 | return await GetPlayCount(request.CardId); 23 | } 24 | 25 | private async Task GetPlayCount(long cardId) 26 | { 27 | var record = await context.CardPlayCounts.FirstOrDefaultAsync(count => count.CardId == cardId); 28 | if (record is null) 29 | { 30 | return 0; 31 | } 32 | 33 | var now = DateTime.Now; 34 | var lastPlayedTime = record.LastPlayedTime; 35 | 36 | if (now <= lastPlayedTime) 37 | { 38 | logger.LogWarning("Clock skew detected! " + 39 | "Current time: {Now}," + 40 | "Last Play Time: {Last}", now, lastPlayedTime); 41 | return 0; 42 | } 43 | 44 | DateTime start; 45 | DateTime end; 46 | if (now.Hour >= 8) 47 | { 48 | start = DateTime.Today.AddHours(8); 49 | end = start.AddHours(24); 50 | } 51 | else 52 | { 53 | end = DateTime.Today.AddHours(8); 54 | start = end.AddHours(-24); 55 | } 56 | 57 | var inBetween = lastPlayedTime >= start && lastPlayedTime <= end; 58 | return inBetween ? record.PlayCount : 0; 59 | } 60 | } -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadCardDetailQuery.cs: -------------------------------------------------------------------------------- 1 | using Domain.Enums; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Application.Game.Card.Read; 5 | 6 | 7 | public record ReadCardDetailQuery(long CardId, string Data) : IRequestWrapper; 8 | 9 | public class ReadDetailQueryHandler : RequestHandlerBase 10 | { 11 | private const string CARD_DETAILS_XPATH = "/root/card_detail"; 12 | 13 | private readonly ILogger logger; 14 | 15 | public ReadDetailQueryHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) 16 | { 17 | this.logger = logger; 18 | } 19 | 20 | public override async Task> Handle(ReadCardDetailQuery request, CancellationToken cancellationToken) 21 | { 22 | var exists = await CardDbContext.CardMains.AnyAsync(card => card.CardId == request.CardId, cancellationToken: cancellationToken); 23 | if (!exists) 24 | { 25 | logger.LogWarning("Card id: {CardId} does not exist!", request.CardId); 26 | return ServiceResult.Failed( 27 | new ServiceError($"Card id: {request.CardId} does not exist!", (int)CardReturnCode.CardNotRegistered)); 28 | } 29 | 30 | var queryCondition = request.Data.DeserializeCardData(); 31 | var detail = await CardDbContext.CardDetails.FirstOrDefaultAsync(cardDetail => 32 | cardDetail.CardId == request.CardId && 33 | cardDetail.Pcol1 == queryCondition.Pcol1 && 34 | cardDetail.Pcol2 == queryCondition.Pcol2 && 35 | cardDetail.Pcol3 == queryCondition.Pcol3, cancellationToken: cancellationToken); 36 | 37 | var dto = detail?.CardDetailToDto(); 38 | 39 | var result = dto?.SerializeCardData(CARD_DETAILS_XPATH) ?? 40 | new object().SerializeCardData(CARD_DETAILS_XPATH); 41 | 42 | return new ServiceResult(result); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /WebUI/Pages/Dialogs/FavoriteDialog.razor: -------------------------------------------------------------------------------- 1 | @using Shared.Dto.Api 2 | @using Throw 3 | @inject HttpClient Client 4 | 5 | 6 | 7 | @if (!Data.IsFavorite) 8 | { 9 | 10 | 11 | Add to favorite? 12 | 13 | } 14 | else 15 | { 16 | 17 | 18 | Remove from favorite? 19 | 20 | } 21 | 22 | 23 | 24 | 25 | 26 | 27 | Cancel 28 | Confirm 29 | 30 | 31 | 32 | @code{ 33 | 34 | [CascadingParameter] 35 | public required MudDialogInstance MudDialog { get; set; } 36 | 37 | [Parameter] 38 | public required SongPlayRecord Data { get; set; } 39 | 40 | [Parameter] 41 | public long CardId { get; set; } 42 | 43 | private async Task Submit() 44 | { 45 | var favoriteData = new MusicFavoriteDto 46 | { 47 | CardId = CardId, 48 | IsFavorite = !Data.IsFavorite, 49 | MusicId = Data.MusicId 50 | }; 51 | 52 | var response = await Client.PostAsJsonAsync("api/Profiles/SetFavorite", favoriteData); 53 | var result = await response.Content.ReadFromJsonAsync>(); 54 | result.ThrowIfNull(); 55 | 56 | MudDialog.Close(DialogResult.Ok(result)); 57 | } 58 | 59 | private void Cancel() => MudDialog.Cancel(); 60 | 61 | } -------------------------------------------------------------------------------- /Application/Game/Rank/GetMonthlyScoreRankQuery.cs: -------------------------------------------------------------------------------- 1 | using Application.Common.Helpers; 2 | 3 | namespace Application.Game.Rank; 4 | 5 | public record GetMonthlyScoreRankQuery() : IRequestWrapper; 6 | 7 | public class GetMonthlyScoreRankQueryHandler : IRequestHandlerWrapper 8 | { 9 | private readonly ICardDbContext cardDbContext; 10 | 11 | public GetMonthlyScoreRankQueryHandler(ICardDbContext cardDbContext) 12 | { 13 | this.cardDbContext = cardDbContext; 14 | } 15 | 16 | public async Task> Handle(GetMonthlyScoreRankQuery request, CancellationToken cancellationToken) 17 | { 18 | var ranks = await cardDbContext.MonthlyScoreRanks.OrderBy(rank => rank.Rank) 19 | .Take(30).ToListAsync(cancellationToken: cancellationToken); 20 | 21 | var dtoList = ranks.Select((rank, i) => 22 | { 23 | var dto = rank.ScoreRankToDto(); 24 | dto.Id = i; 25 | dto.Rank2 = dto.Rank; 26 | return dto; 27 | }).ToList(); 28 | 29 | var container = new MonthlyScoreRankContainer 30 | { 31 | Ranks = dtoList, 32 | Status = new RankStatus 33 | { 34 | TableName = "MonthlyScoreRank", 35 | StartDate = TimeHelper.DateToString(DateTime.Today), 36 | EndDate = TimeHelper.DateToString(DateTime.Today), 37 | Rows = dtoList.Count, 38 | Status = 1 39 | } 40 | }; 41 | 42 | return new ServiceResult(container.SerializeCardData()); 43 | } 44 | } 45 | 46 | [XmlRoot("root")] 47 | public class MonthlyScoreRankContainer 48 | { 49 | [XmlArray(ElementName = "m_score_rank")] 50 | [XmlArrayItem(ElementName = "record")] 51 | // ReSharper disable once UnusedAutoPropertyAccessor.Global 52 | public List Ranks { get; init; } = new(); 53 | 54 | [XmlElement("ranking_status")] 55 | public RankStatus Status { get; set; } = new(); 56 | } 57 | 58 | -------------------------------------------------------------------------------- /Application/Game/Rank/GetPlayNumRankQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Application.Common.Helpers; 3 | 4 | namespace Application.Game.Rank; 5 | 6 | public record GetPlayNumRankQuery(): IRequestWrapper; 7 | 8 | public class GetPlayNumRankQueryHandler : IRequestHandlerWrapper 9 | { 10 | private readonly ICardDbContext cardDbContext; 11 | 12 | public GetPlayNumRankQueryHandler(ICardDbContext cardDbContext) 13 | { 14 | this.cardDbContext = cardDbContext; 15 | } 16 | 17 | public async Task> Handle(GetPlayNumRankQuery request, CancellationToken cancellationToken) 18 | { 19 | var ranks = await cardDbContext.PlayNumRanks.OrderBy(rank => rank.Rank) 20 | .Take(30).ToListAsync(cancellationToken: cancellationToken); 21 | 22 | var status = new RankStatus 23 | { 24 | TableName = "PlayNumRank", 25 | StartDate = TimeHelper.DateToString(Process.GetCurrentProcess().StartTime.Date), 26 | EndDate = TimeHelper.DateToString(DateTime.Today), 27 | Rows = ranks.Count, 28 | Status = 1 29 | }; 30 | 31 | var dtoList = ranks.Select((rank, i) => 32 | { 33 | var dto = rank.PlayNumRankToDto(); 34 | dto.Id = i; 35 | return dto; 36 | }).ToList(); 37 | 38 | var container = new PlayNumRankContainer 39 | { 40 | Ranks = dtoList, 41 | Status = status 42 | }; 43 | 44 | var result = container.SerializeCardData(); 45 | 46 | return new ServiceResult(result); 47 | } 48 | } 49 | 50 | [XmlRoot("root")] 51 | public class PlayNumRankContainer 52 | { 53 | [XmlArray(ElementName = "play_num_rank")] 54 | [XmlArrayItem(ElementName = "record")] 55 | // ReSharper disable once UnusedAutoPropertyAccessor.Global 56 | public List Ranks { get; init; } = new(); 57 | 58 | [XmlElement("ranking_status")] 59 | public RankStatus Status { get; set; } = new(); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /Application/Game/Rank/GetWPlayNumRankQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Application.Common.Helpers; 3 | 4 | namespace Application.Game.Rank; 5 | 6 | public record GetWPlayNumRankQuery(): IRequestWrapper; 7 | 8 | public class GetWPlayNumRankQueryHandler : IRequestHandlerWrapper 9 | { 10 | private readonly ICardDbContext cardDbContext; 11 | 12 | public GetWPlayNumRankQueryHandler(ICardDbContext cardDbContext) 13 | { 14 | this.cardDbContext = cardDbContext; 15 | } 16 | 17 | public async Task> Handle(GetWPlayNumRankQuery request, CancellationToken cancellationToken) 18 | { 19 | var ranks = await cardDbContext.PlayNumRanks.OrderBy(rank => rank.Rank) 20 | .Take(30).ToListAsync(cancellationToken: cancellationToken); 21 | 22 | var status = new RankStatus 23 | { 24 | TableName = "PlayNumRank", 25 | StartDate = TimeHelper.DateToString(Process.GetCurrentProcess().StartTime.Date), 26 | EndDate = TimeHelper.DateToString(DateTime.Today), 27 | Rows = ranks.Count, 28 | Status = 1 29 | }; 30 | 31 | var dtoList = ranks.Select((rank, i) => 32 | { 33 | var dto = rank.PlayNumRankToDto(); 34 | dto.Id = i; 35 | return dto; 36 | }).ToList(); 37 | 38 | var container = new WPlayNumRankContainer 39 | { 40 | Ranks = dtoList, 41 | Status = status 42 | }; 43 | 44 | var result = container.SerializeCardData(); 45 | 46 | return new ServiceResult(result); 47 | } 48 | } 49 | 50 | [XmlRoot("root")] 51 | public class WPlayNumRankContainer 52 | { 53 | [XmlArray(ElementName = "w_play_num_rank")] 54 | [XmlArrayItem(ElementName = "record")] 55 | // ReSharper disable once UnusedAutoPropertyAccessor.Global 56 | public List Ranks { get; init; } = new(); 57 | 58 | [XmlElement("ranking_status")] 59 | public RankStatus Status { get; set; } = new(); 60 | } 61 | 62 | -------------------------------------------------------------------------------- /MainServer/Controllers/Game/RankingController.cs: -------------------------------------------------------------------------------- 1 | using Application.Game.Rank; 2 | using Domain.Enums; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Throw; 5 | 6 | namespace MainServer.Controllers.Game; 7 | 8 | [ApiController] 9 | [Route("ranking")] 10 | public class RankingController : BaseController 11 | { 12 | [HttpGet("ranking.php")] 13 | public async Task> Ranking([FromQuery(Name = "cmd_type")] int rankType, 14 | [FromQuery(Name = "tenpo_id")] int tenpoId, 15 | [FromQuery(Name = "param")] string param) 16 | { 17 | var type = (RankingCommandType)rankType; 18 | type.Throw().IfOutOfRange(); 19 | 20 | var result = type switch 21 | { 22 | RankingCommandType.GlobalRank => await Mediator.Send(new GetGlobalScoreRankQuery(param)), 23 | RankingCommandType.PlayNumRank => await Mediator.Send(new GetPlayNumRankQuery()), 24 | RankingCommandType.EventRank => await Mediator.Send(new GetEventRankQuery()), 25 | RankingCommandType.MonthlyRank => await Mediator.Send(new GetMonthlyScoreRankQuery()), 26 | RankingCommandType.ShopRank => await Mediator.Send(new GetTenpoScoreRankQuery(tenpoId, param)), 27 | RankingCommandType.WScoreRank => await Mediator.Send(new GetWScoreRankQuery(param)), 28 | RankingCommandType.WPlayNumRank => await Mediator.Send(new GetWPlayNumRankQuery()), 29 | RankingCommandType.WShopRank => await Mediator.Send(new GetWTenpoScoreRankQuery(tenpoId, param)), 30 | _ => throw new ArgumentOutOfRangeException(nameof(type), type, "Should not happen!") 31 | }; 32 | 33 | if (result.Succeeded) 34 | { 35 | var normalResult = "1\n" + 36 | $"{result.Data}"; 37 | return Ok(normalResult); 38 | } 39 | 40 | // Here error is not null since Succeeded => Error is null; 41 | var errorMessage = $"{result.Error!.Code}\n" + 42 | $"{result.Error!.Message}"; 43 | return Ok(errorMessage); 44 | } 45 | } -------------------------------------------------------------------------------- /Application/Common/Extensions/XmlSerializationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using ChoETL; 3 | using Throw; 4 | 5 | namespace Application.Common.Extensions; 6 | 7 | public static class XmlSerializationExtensions 8 | { 9 | public static T DeserializeCardData(this string source) where T : class 10 | { 11 | using var reader = new ChoXmlReader(new StringReader(source)).WithXPath("/root/data"); 12 | reader.Configuration.IgnoreFieldValueMode = ChoIgnoreFieldValueMode.Any; 13 | 14 | var result = reader.Read(); 15 | result.ThrowIfNull(); 16 | 17 | return result; 18 | } 19 | 20 | public static string SerializeCardData(this T source, string xpath) where T : class 21 | { 22 | var buffer = new StringBuilder(); 23 | using (var writer = new ChoXmlWriter(buffer).WithXPath(xpath).UseXmlSerialization()) 24 | { 25 | writer.Configuration.OmitXmlDeclaration = false; 26 | writer.Configuration.DoNotEmitXmlNamespace = true; 27 | writer.Write(source); 28 | } 29 | return buffer.ToString(); 30 | } 31 | 32 | public static string SerializeCardData(this T source) where T : class 33 | { 34 | var buffer = new StringBuilder(); 35 | using (var writer = new ChoXmlWriter(buffer).UseXmlSerialization()) 36 | { 37 | writer.Configuration.OmitXmlDeclaration = false; 38 | writer.Configuration.DoNotEmitXmlNamespace = true; 39 | writer.Configuration.IgnoreRootName = true; 40 | writer.Write(source); 41 | } 42 | return buffer.ToString(); 43 | } 44 | 45 | public static string SerializeCardDataList(this IEnumerable source, string xpath) where T : class 46 | { 47 | var buffer = new StringBuilder(); 48 | using (var writer = new ChoXmlWriter(buffer).WithXPath(xpath).UseXmlSerialization()) 49 | { 50 | writer.Configuration.OmitXmlDeclaration = false; 51 | writer.Configuration.DoNotEmitXmlNamespace = true; 52 | writer.Write(source); 53 | } 54 | 55 | return buffer.ToString(); 56 | } 57 | } -------------------------------------------------------------------------------- /Application/Api/UnlockAllMusicCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using ChoETL; 3 | using Domain.Enums; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Application.Api; 7 | 8 | public record UnlockAllMusicCommand(long CardId) : IRequestWrapper; 9 | 10 | public class UnlockAllMusicCommandHandler : RequestHandlerBase 11 | { 12 | private readonly ILogger logger; 13 | 14 | public UnlockAllMusicCommandHandler(ICardDependencyAggregate aggregate, 15 | ILogger logger) : base(aggregate) 16 | { 17 | this.logger = logger; 18 | } 19 | 20 | [SuppressMessage("ReSharper.DPA", "DPA0007: Large number of DB records")] 21 | [SuppressMessage("ReSharper.DPA", "DPA0006: Large number of DB commands")] 22 | public override async Task> Handle(UnlockAllMusicCommand request, CancellationToken cancellationToken) 23 | { 24 | var exists = await CardDbContext.CardDetails.AnyAsync( 25 | detail => detail.CardId == request.CardId, cancellationToken); 26 | 27 | if (!exists) 28 | { 29 | logger.LogWarning("Attempt to unlock for card {Card} that does not exist or is empty!", request.CardId); 30 | return ServiceResult.Failed(ServiceError.CustomMessage("Unlock failed")); 31 | } 32 | 33 | var unlockables = Config.UnlockRewards 34 | .Where(config => config.RewardType == RewardType.Music) 35 | .Select(config => new CardDetailDto 36 | { 37 | CardId = request.CardId, 38 | Pcol1 = 10, 39 | Pcol2 = config.TargetId, 40 | Pcol3 = 0, 41 | LastPlayTenpoId = "1337", 42 | LastPlayTime = DateTime.Now, 43 | ScoreUi2 = 1, 44 | ScoreUi6 = 1 45 | }.DtoToCardDetail()); 46 | 47 | await CardDbContext.CardDetails.UpsertRange(unlockables).RunAsync(cancellationToken); 48 | await CardDbContext.SaveChangesAsync(cancellationToken); 49 | 50 | return new ServiceResult(true); 51 | } 52 | } -------------------------------------------------------------------------------- /Application/Game/Card/Write/WriteCardBDataCommand.cs: -------------------------------------------------------------------------------- 1 | using Domain.Entities; 2 | using Domain.Enums; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Application.Game.Card.Write; 6 | 7 | public record WriteCardBDataCommand(long CardId, string Data) : IRequestWrapper; 8 | 9 | public class WriteBDataCommandHandler : RequestHandlerBase 10 | { 11 | private readonly ILogger logger; 12 | 13 | public WriteBDataCommandHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) 14 | { 15 | this.logger = logger; 16 | } 17 | 18 | public override async Task> Handle(WriteCardBDataCommand request, CancellationToken cancellationToken) 19 | { 20 | var exists = await CardDbContext.CardMains.AnyAsync(card => card.CardId == request.CardId, cancellationToken: cancellationToken); 21 | if (!exists) 22 | { 23 | logger.LogWarning("Card id: {CardId} does not exist!", request.CardId); 24 | return ServiceResult.Failed( 25 | new ServiceError($"Card id: {request.CardId} does not exist!", (int)CardReturnCode.CardNotRegistered)); 26 | } 27 | 28 | var dto = request.Data.DeserializeCardData(); 29 | var data = dto.DtoToCardBDatum(); 30 | data.CardId = request.CardId; 31 | await CardDbContext.CardBdata.Upsert(data).RunAsync(cancellationToken); 32 | 33 | var cardPlayCount = await CardDbContext.CardPlayCounts 34 | .FirstOrDefaultAsync(count => count.CardId == request.CardId, cancellationToken); 35 | if (cardPlayCount is null) 36 | { 37 | cardPlayCount = new CardPlayCount 38 | { 39 | CardId = request.CardId, 40 | PlayCount = 0, 41 | LastPlayedTime = DateTime.Now 42 | }; 43 | } 44 | cardPlayCount.PlayCount++; 45 | cardPlayCount.LastPlayedTime = DateTime.Now; 46 | await CardDbContext.CardPlayCounts.Upsert(cardPlayCount).RunAsync(cancellationToken); 47 | 48 | await CardDbContext.SaveChangesAsync(cancellationToken); 49 | 50 | return new ServiceResult(request.Data); 51 | } 52 | } -------------------------------------------------------------------------------- /WebUI/Pages/TotalResult.razor: -------------------------------------------------------------------------------- 1 | @page "/Cards/TotalResult/{cardId:long}" 2 | 3 | 4 | 5 | Total Result 6 |

Total Result

7 | 8 | @if (errorMessage is not null) 9 | { 10 | @errorMessage 11 | return; 12 | } 13 | 14 | @if (totalResultData is null) 15 | { 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | return; 25 | } 26 | 27 | @if (totalResultData.PlayerData.PlayedSongCount == 0) 28 | { 29 | 30 | No Play Record 31 | 32 | return; 33 | } 34 | 35 | 36 | Player Name: @totalResultData.PlayerName 37 | Total Score: @totalResultData.PlayerData.TotalScore 38 | Rank: @totalResultData.PlayerData.Rank 39 | Average Score: @totalResultData.PlayerData.AverageScore 40 | Played Song Count: @totalResultData.PlayerData.PlayedSongCount / @totalResultData.PlayerData.TotalSongCount 41 | Cleared Stage Count: @totalResultData.StageCountData.Cleared / @totalResultData.StageCountData.Total 42 | No Miss Stage Count: @totalResultData.StageCountData.NoMiss / @totalResultData.StageCountData.Total 43 | Full Chain Stage Count: @totalResultData.StageCountData.FullChain / @totalResultData.StageCountData.Total 44 | Perfect Stage Count: @totalResultData.StageCountData.Perfect / @totalResultData.StageCountData.Total 45 | S and Above Stage Count: @totalResultData.StageCountData.S / @totalResultData.StageCountData.Total 46 | S+ and Above Stage Count: @totalResultData.StageCountData.Ss / @totalResultData.StageCountData.Total 47 | S++ and Above Stage Count: @totalResultData.StageCountData.Sss / @totalResultData.StageCountData.Total 48 | -------------------------------------------------------------------------------- /Application/Dto/Game/OnlineMatchEntryDto.cs: -------------------------------------------------------------------------------- 1 | namespace Application.Dto.Game; 2 | 3 | public class OnlineMatchEntryDto 4 | { 5 | [XmlAttribute(AttributeName = "id")] 6 | public int Id { get; set; } 7 | 8 | [XmlElement(ElementName = "machine_id")] 9 | public long MachineId { get; set; } 10 | 11 | [XmlElement(ElementName = "event_id")] 12 | public long EventId { get; set; } 13 | 14 | [XmlElement(ElementName = "matching_id")] 15 | public long MatchId { get; set; } 16 | 17 | [XmlElement(ElementName = "entry_no")] 18 | public long EntryId { get; set; } 19 | 20 | [XmlElement(ElementName = "entry_start")] 21 | public string StartTime { get; set; } = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); 22 | 23 | [XmlElement(ElementName = "status")] 24 | public long Status { get; set; } = 1; 25 | 26 | [XmlElement(ElementName = "card_id")] 27 | public long CardId { get; set; } 28 | 29 | [XmlElement(ElementName = "player_name")] 30 | public string PlayerName { get; set; } = string.Empty; 31 | 32 | [XmlElement(ElementName = "avatar_id")] 33 | public long AvatarId { get; set; } 34 | 35 | [XmlElement(ElementName = "title_id")] 36 | public long TitleId { get; set; } 37 | 38 | [XmlElement(ElementName = "class_id")] 39 | public long ClassId { get; set; } 40 | 41 | [XmlElement(ElementName = "group_id")] 42 | public long GroupId { get; set; } 43 | 44 | [XmlElement(ElementName = "tenpo_id")] 45 | public long TenpoId { get; set; } = 1337; 46 | 47 | [XmlElement(ElementName = "tenpo_name")] 48 | public string TenpoName { get; set; } = "GCLocalServer"; 49 | 50 | [XmlElement(ElementName = "pref_id")] 51 | public long PrefId { get; set; } 52 | 53 | [XmlElement(ElementName = "pref")] 54 | public string Pref { get; set; } = "nesys"; 55 | 56 | [XmlElement(ElementName = "message_id")] 57 | public long MessageId { get; set; } 58 | 59 | [XmlElement(ElementName = "matching_timeout")] 60 | public long MatchTimeout { get; set; } = 99; 61 | 62 | [XmlElement(ElementName = "matching_wait_time")] 63 | public long MatchWaitTime { get; set; } = 10; 64 | 65 | [XmlElement(ElementName = "matching_remaining_time")] 66 | public long MatchRemainingTime { get; set; } = 89; 67 | } -------------------------------------------------------------------------------- /Application/Api/SetPlayOptionCommand.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Shared.Models; 3 | 4 | namespace Application.Api; 5 | 6 | public record SetPlayOptionCommand(PlayOptionData Data) : IRequestWrapper; 7 | 8 | public class SetPlayOptionCommandHandler : RequestHandlerBase 9 | { 10 | private readonly ILogger logger; 11 | 12 | public SetPlayOptionCommandHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) 13 | { 14 | this.logger = logger; 15 | } 16 | 17 | public override async Task> Handle(SetPlayOptionCommand request, CancellationToken cancellationToken) 18 | { 19 | var optionDetail1 = await CardDbContext.CardDetails.FirstOrDefaultAsync(detail => 20 | detail.CardId == request.Data.CardId && 21 | detail.Pcol1 == 0 && 22 | detail.Pcol2 == 0 && 23 | detail.Pcol3 == 0, 24 | cancellationToken: cancellationToken); 25 | var optionDetail2 = await CardDbContext.CardDetails.FirstOrDefaultAsync(detail => 26 | detail.CardId == request.Data.CardId && 27 | detail.Pcol1 == 1 && 28 | detail.Pcol2 == 0 && 29 | detail.Pcol3 == 0, 30 | cancellationToken: cancellationToken); 31 | 32 | if (optionDetail1 is null || 33 | optionDetail2 is null) 34 | { 35 | logger.LogWarning("Attempt to set play options for card id {CardId} failed due to missing data", 36 | request.Data.CardId); 37 | return ServiceResult.Failed(ServiceError.CustomMessage("At least one of the play option records not found")); 38 | } 39 | 40 | request.Data.OptionPart1.MapFirstOptionDetail(optionDetail1); 41 | request.Data.OptionPart2.MapSecondOptionDetail(optionDetail2); 42 | 43 | CardDbContext.CardDetails.Update(optionDetail1); 44 | CardDbContext.CardDetails.Update(optionDetail2); 45 | 46 | var count = await CardDbContext.SaveChangesAsync(cancellationToken); 47 | return count == 1 ? new ServiceResult(true) : ServiceResult.Failed(ServiceError.DatabaseSaveFailed); 48 | } 49 | } -------------------------------------------------------------------------------- /Application/Game/Card/Read/ReadAllCardDetailsQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Domain.Enums; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Application.Game.Card.Read; 6 | 7 | 8 | public record ReadAllCardDetailsQuery(long CardId) : IRequestWrapper; 9 | 10 | public class ReadAllDetailsQueryHandler : RequestHandlerBase 11 | { 12 | private const string CARD_DETAILS_XPATH = "/root/card_detail"; 13 | private const string RECORD_XPATH = $"{CARD_DETAILS_XPATH}/record"; 14 | 15 | private readonly ILogger logger; 16 | 17 | public ReadAllDetailsQueryHandler(ICardDependencyAggregate aggregate, ILogger logger) : base(aggregate) 18 | { 19 | this.logger = logger; 20 | } 21 | 22 | [SuppressMessage("ReSharper.DPA", "DPA0007: Large number of DB records", 23 | Justification = "Card details will return all records by design, which results in a large number of DB records")] 24 | public override async Task> Handle(ReadAllCardDetailsQuery request, CancellationToken cancellationToken) 25 | { 26 | var exists = await CardDbContext.CardMains.AnyAsync(card => card.CardId == request.CardId, cancellationToken: cancellationToken); 27 | if (!exists) 28 | { 29 | logger.LogWarning("Card id: {CardId} does not exist!", request.CardId); 30 | return ServiceResult.Failed( 31 | new ServiceError($"Card id: {request.CardId} does not exist!", (int)CardReturnCode.CardNotRegistered)); 32 | } 33 | 34 | var cardDetails = await CardDbContext.CardDetails 35 | .Where(detail => detail.CardId == request.CardId) 36 | .ToListAsync(cancellationToken: cancellationToken); 37 | 38 | string result; 39 | if (cardDetails.Count == 0) 40 | { 41 | result = new object().SerializeCardData(CARD_DETAILS_XPATH); 42 | } 43 | else 44 | { 45 | var dtoList = cardDetails.Select((detail, i) => 46 | { 47 | var dto = detail.CardDetailToDto(); 48 | dto.Id = i; 49 | return dto; 50 | }); 51 | result = dtoList.SerializeCardDataList(RECORD_XPATH); 52 | } 53 | 54 | return new ServiceResult(result); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /WebUI/Services/DataService.cs: -------------------------------------------------------------------------------- 1 | using Throw; 2 | using WebUI.Common.Models; 3 | 4 | namespace WebUI.Services; 5 | 6 | public class DataService : IDataService 7 | { 8 | private Dictionary avatars = new(); 9 | 10 | private Dictionary navigators = new(); 11 | 12 | private Dictionary titles = new(); 13 | 14 | private List sortedAvatarList = new(); 15 | 16 | private List sortedNavigatorList = new(); 17 | 18 | private List sortedTitleList = new(); 19 | 20 | private readonly HttpClient client; 21 | 22 | public DataService(HttpClient client) 23 | { 24 | this.client = client; 25 | } 26 | 27 | public async Task InitializeAsync() 28 | { 29 | var avatarList = await client.GetFromJsonAsync<List<Avatar>>("data/Avatars.json"); 30 | avatarList.ThrowIfNull(); 31 | avatars = avatarList.ToDictionary(avatar => avatar.AvatarId); 32 | sortedAvatarList = avatarList.OrderBy(avatar => avatar.AvatarId).ToList(); 33 | 34 | var navigatorList = await client.GetFromJsonAsync<List<Navigator>>("data/Navigators.json"); 35 | navigatorList.ThrowIfNull(); 36 | navigators = navigatorList.ToDictionary(navigator => navigator.Id); 37 | sortedNavigatorList = navigatorList.OrderBy(navigator => navigator.Id).ToList(); 38 | 39 | var titleList = await client.GetFromJsonAsync<List<Title>>("data/Titles.json"); 40 | titleList.ThrowIfNull(); 41 | titles = titleList.ToDictionary(title => title.Id); 42 | sortedTitleList = titleList.OrderBy(title => title.Id).ToList(); 43 | } 44 | 45 | public IReadOnlyList<Avatar> GetAvatarsSortedById() 46 | { 47 | return sortedAvatarList; 48 | } 49 | 50 | public IReadOnlyList<Navigator> GetNavigatorsSortedById() 51 | { 52 | return sortedNavigatorList; 53 | } 54 | 55 | public IReadOnlyList<Title> GetTitlesSortedById() 56 | { 57 | return sortedTitleList; 58 | } 59 | 60 | public Avatar? GetAvatarById(uint id) 61 | { 62 | return avatars.GetValueOrDefault(id); 63 | } 64 | 65 | public Title? GetTitleById(uint id) 66 | { 67 | return titles.GetValueOrDefault(id); 68 | } 69 | 70 | public Navigator? GetNavigatorById(uint id) 71 | { 72 | return navigators.GetValueOrDefault(id); 73 | } 74 | } -------------------------------------------------------------------------------- /doc/unlockables.csv: -------------------------------------------------------------------------------- 1 | 曲名,アーティスト,収録日,必要キー数 2 | Perverse Love Rock!,中沢伴行 (I've) feat. IA,先行配信2016/09/01通常配信2016/10/05,2 3 | ネトゲ廃人シュプレヒコール,さつき が てんこもり feat. 初音ミク,先行配信2016/10/20通常配信2017/01/12,5 4 | 深海シティアンダーグラウンド,田中B feat. 鏡音リン,先行配信2017/04/13通常配信2017/07/21,2 5 | あのね…実はわたし、夢眠ねむなんだ…,夢眠ネム,先行配信2017/06/12通常配信2017/09/07,5 6 | 月夜ノ森,ぼのりす feat. 初音ミク,先行配信2017/07/12通常配信2017/10/27,3 7 | ビットチューン,out of service feat. IA,先行配信2018/01/18通常配信2018/07/02,2 8 | Miraculous Encounter,インターネット社 feat. GUMI,先行配信2018/02/21通常配信2018/07/02,2 9 | 人生リセットボタン,kemu feat. GUMI,2018/07/02,8 10 | ボロボロだ,n-buna feat. 初音ミク,先行配信2018/06/06通常配信2018/08/23,10 11 | D+D→T+p,Minstrel feat. LIQU@。,2015/09/07,5 12 | カミノヒ,COSIO(ZUNTATA) × ランコ(豚乙女),先行配信2016/05/30通常配信2016/08/12,5 13 | "Help me, ERINNNNNN!! -Cranky remix-",ビートまりお × Cranky,先行配信2016/07/29通常配信2016/09/28,5 14 | Dream Coaster,A-One,先行配信2016/12/01通常配信2017/02/27,3 15 | コノハズク,豚乙女,先行配信2017/05/01通常配信2017/08/09,10 16 | DIVINE PLACE,TatshMusicCircle,先行配信2017/08/09通常配信2017/11/08,10 17 | 東方輝仁裂,MASAKI,先行配信2017/11/08通常配信2018/03/20,30 18 | 秘神マターラ-HYPER TECHNO MIX-,Yu Shimoda(ZUNTATA),2018/07/25,15 19 | 揺蕩うLove (feat. らっぷびと),魂音泉,先行配信2018/05/01通常配信2018/08/02,10 20 | Say Around,A-One,2018/10/10,10 21 | 2112410403927243233368,253215,先行配信2015/04/01通常配信2015/06/02,25 22 | ピアノ・ソナタ「月光」第三楽章,ICON,2018/07/18,9 23 | EZ Mode,Akira Complex,2018/10/17,15 24 | UP & DOWN,KO3,2018/10/24,15 25 | Never Stop (GC Mix),Relect,2018/11/21,15 26 | Fun-House,COSIO (ZUNTATA),2016/03/10,5 27 | Cardiac Rhythm,COSIO,2015/03/25,5 28 | Flyaway,櫻井浩司,2016/03/10,5 29 | メシアの邂逅,Tatsh,2015/04/22,15 30 | Got noir forever.,E.G.G.,2015/02/25,25 31 | orbital,namae.,2016/03/10,5 32 | Got hive of Ra,E.G.G.,先行配信2016/09/28通常配信2016/12/14,25 33 | Got recover run,E.G.G.,先行配信2017/12/14通常配信2018/04/16,25 34 | Warrior,Cranky,先行配信2017/02/20通常配信2017/05/22,15 35 | TECHNO COMPLEX,DJ MURASAME a.k.a. Tatsh,先行配信2017/03/16通常配信2017/06/19,10 36 | "Good Night, Bad Luck.",t+pazolite,先行配信2017/06/26通常配信2017/09/22,30 37 | Got more raves? -xiRemix-,xi,先行配信2018/03/29通常配信2018/07/02,25 38 | MOVING FASTER FT. LINDSEY MARIE,Daniel Seven,2018/07/11,15 39 | Crimson Phoenix,xi vs MASAKI,先行配信2018/03/29通常配信2018/08/02,15 40 | HAPPY! LUCKY! FUTURE WORLD!,EmoCo.,2018/08/16,15 41 | Cosmic Express,Snail's House,2018/08/30,15 42 | Xand-Roid,Tatsh,2018/09/14,20 43 | Pegasus,NaMiRa,2018/11/21,15 44 | Little Higher,nora2r,2018/12/05,15 45 | Got sorted view.,E.G.G.,2019/03/13,25 46 | Lemegeton -little key of solomon-,Team Grimoire vs MASAKI,2019/03/28,30 47 | 希,technoplanet feat. Shun [RIGEL],2021/12/01,15 48 | Valkyrja Requiem,iTIC,2021/12/08,15 49 | コスモスタート,打打だいず,2021/12/15,15 -------------------------------------------------------------------------------- /Application/Jobs/UpdatePlayNumRankJob.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Domain.Entities; 3 | using Microsoft.Extensions.Logging; 4 | using Quartz; 5 | 6 | namespace Application.Jobs; 7 | 8 | public class UpdatePlayNumRankJob : IJob 9 | { 10 | private readonly ILogger<UpdatePlayNumRankJob> logger; 11 | 12 | private readonly ICardDbContext cardDbContext; 13 | 14 | private readonly IMusicDbContext musicDbContext; 15 | 16 | public static readonly JobKey KEY = new JobKey("UpdatePlayNumRankJob"); 17 | 18 | public UpdatePlayNumRankJob(ILogger<UpdatePlayNumRankJob> logger, ICardDbContext cardDbContext, 19 | IMusicDbContext musicDbContext) 20 | { 21 | this.logger = logger; 22 | this.cardDbContext = cardDbContext; 23 | this.musicDbContext = musicDbContext; 24 | } 25 | 26 | public async Task Execute(IJobExecutionContext context) 27 | { 28 | logger.LogInformation("Start maintaining play num rank"); 29 | await UpdatePlayNumRank(); 30 | } 31 | 32 | 33 | [SuppressMessage("ReSharper.DPA", "DPA0007: Large number of DB records", 34 | Justification = "All music will be read")] 35 | private async Task UpdatePlayNumRank() 36 | { 37 | var playRecords = await cardDbContext.CardDetails 38 | .Where(detail => detail.Pcol1 == 20).ToListAsync(); 39 | 40 | var playNumRanks = new List<PlayNumRank>(); 41 | var musics = await musicDbContext.MusicUnlocks.ToListAsync(); 42 | foreach (var music in musics) 43 | { 44 | var playCount = playRecords 45 | .Where(detail => detail.Pcol2 == music.MusicId) 46 | .Sum(detail => detail.ScoreUi1); 47 | var playNumRank = new PlayNumRank 48 | { 49 | MusicId = (int)music.MusicId, 50 | Artist = music.Artist ?? string.Empty, 51 | Title = music.Title, 52 | PlayCount = (int)playCount 53 | }; 54 | playNumRanks.Add(playNumRank); 55 | } 56 | playNumRanks = playNumRanks.OrderByDescending(rank => rank.PlayCount).ToList(); 57 | var result = playNumRanks.Select((rank, i) => 58 | { 59 | rank.Rank = i+1; 60 | return rank; 61 | }).ToList(); 62 | await cardDbContext.PlayNumRanks.UpsertRange(result).RunAsync(); 63 | await cardDbContext.SaveChangesAsync(new CancellationToken()); 64 | 65 | logger.LogInformation("Updating play num rank done"); 66 | } 67 | } -------------------------------------------------------------------------------- /WebUI/Pages/PlayRecords.razor.cs: -------------------------------------------------------------------------------- 1 | using Throw; 2 | using WebUI.Pages.Dialogs; 3 | 4 | namespace WebUI.Pages; 5 | 6 | public partial class PlayRecords 7 | { 8 | private readonly List<BreadcrumbItem> breadcrumbs = new() 9 | { 10 | new BreadcrumbItem("Cards", href: "/Cards") 11 | }; 12 | 13 | [Parameter] 14 | public long CardId { get; set; } 15 | 16 | [Inject] 17 | public required HttpClient Client { get; set; } 18 | 19 | [Inject] 20 | public required IDialogService DialogService { get; set; } 21 | 22 | private string? errorMessage; 23 | 24 | private List<SongPlayRecord>? songPlayRecords; 25 | 26 | protected async override Task OnInitializedAsync() 27 | { 28 | await base.OnInitializedAsync(); 29 | 30 | breadcrumbs.Add(new BreadcrumbItem($"Card: {CardId}", href:null, disabled:true)); 31 | breadcrumbs.Add(new BreadcrumbItem("TotalResult", href: $"/Cards/PlayRecords/{CardId}", disabled: false)); 32 | 33 | var result = await Client.GetFromJsonAsync<ServiceResult<List<SongPlayRecord>>>($"api/Profiles/SongPlayRecords/{CardId}"); 34 | result.ThrowIfNull(); 35 | 36 | if (!result.Succeeded) 37 | { 38 | errorMessage = result.Error!.Message; 39 | return; 40 | } 41 | 42 | songPlayRecords = result.Data; 43 | } 44 | 45 | private static string GetRating(int score) => score switch 46 | { 47 | > 990000 => "S++", 48 | > 950000 => "S+", 49 | > 900000 => "S", 50 | > 800000 => "A", 51 | > 700000 => "B", 52 | > 500000 => "C", 53 | > 300000 => "D", 54 | _ => "E" 55 | }; 56 | 57 | private async Task OnFavoriteToggled(SongPlayRecord data) 58 | { 59 | var options = new DialogOptions 60 | { 61 | CloseOnEscapeKey = false, 62 | DisableBackdropClick = true, 63 | FullWidth = true 64 | }; 65 | 66 | var parameters = new DialogParameters 67 | { 68 | { "Data", data }, 69 | {"CardId", CardId} 70 | }; 71 | var dialog = await DialogService.ShowAsync<FavoriteDialog>("Favorite", parameters, options); 72 | var result = await dialog.Result; 73 | 74 | if (result.Canceled) 75 | { 76 | return; 77 | } 78 | 79 | if (result.Data is ServiceResult<bool> serviceResult && serviceResult.Data) 80 | { 81 | data.IsFavorite = !data.IsFavorite; 82 | } 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /MainServer/Filters/ApiExceptionFilterAttributes.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Filters; 4 | using Shared.Models; 5 | 6 | namespace MainServer.Filters; 7 | 8 | /// <summary> 9 | /// Exception filters 10 | /// </summary> 11 | public class ApiExceptionFilterService : ExceptionFilterAttribute 12 | { 13 | private readonly IDictionary<Type, Action<ExceptionContext>> exceptionHandlers; 14 | 15 | private readonly ILogger<ApiExceptionFilterService> logger; 16 | 17 | /// <summary> 18 | /// Constructor 19 | /// </summary> 20 | public ApiExceptionFilterService(ILogger<ApiExceptionFilterService> logger) 21 | { 22 | this.logger = logger; 23 | // Register known exception types and handlers. 24 | exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>> 25 | { 26 | { typeof(ArgumentOutOfRangeException), HandleArgumentOutOfRangeException } 27 | }; 28 | } 29 | 30 | /// <summary> 31 | /// On exception event 32 | /// </summary> 33 | /// <param name="context"></param> 34 | public override void OnException(ExceptionContext context) 35 | { 36 | HandleException(context); 37 | 38 | base.OnException(context); 39 | } 40 | 41 | private void HandleException(ExceptionContext context) 42 | { 43 | var type = context.Exception.GetType(); 44 | if (exceptionHandlers.ContainsKey(type)) 45 | { 46 | exceptionHandlers[type].Invoke(context); 47 | return; 48 | } 49 | 50 | logger.LogError(context.Exception, "An unknown exception happens"); 51 | HandleUnknownException(context); 52 | } 53 | 54 | private static void HandleUnknownException(ExceptionContext context) 55 | { 56 | var details = ServiceResult.Failed(ServiceError.DefaultError); 57 | 58 | context.Result = new ObjectResult(details) 59 | { 60 | StatusCode = StatusCodes.Status500InternalServerError 61 | }; 62 | 63 | context.ExceptionHandled = true; 64 | } 65 | 66 | private void HandleArgumentOutOfRangeException(ExceptionContext context) 67 | { 68 | logger.LogError(context.Exception, "Get an argument out of bound exception"); 69 | var exception = context.Exception as ArgumentOutOfRangeException; 70 | Debug.Assert(exception != null, nameof(exception) + " != null"); 71 | 72 | var variable = exception.ParamName ?? "Unknown"; 73 | var details = ServiceResult.Failed(ServiceError.CustomMessage($"Argument {variable} out of bounds!")); 74 | 75 | context.Result = new ObjectResult(details) 76 | { 77 | StatusCode = StatusCodes.Status400BadRequest 78 | }; 79 | 80 | context.ExceptionHandled = true; 81 | } 82 | } -------------------------------------------------------------------------------- /WebUI/Pages/Dialogs/ChangePlayerNameDialog.razor: -------------------------------------------------------------------------------- 1 | @using System.Text.RegularExpressions 2 | @using Shared.Dto.Api 3 | @using Throw 4 | @inject HttpClient Client 5 | @inject ILogger<ChangePlayerNameDialog> Logger 6 | @{ 7 | #pragma warning disable CS8974 8 | } 9 | <MudDialog> 10 | <TitleContent> 11 | <MudText Typo="Typo.h6"> 12 | Change Player Name 13 | </MudText> 14 | </TitleContent> 15 | <DialogContent> 16 | <MudForm @bind-IsValid="IsValid"> 17 | <MudTextField Value="@Data.CardId" Label="CardId" ReadOnly="true"/> 18 | <MudTextField @bind-Value="Data.PlayerName" 19 | Immediate="true" 20 | Counter="PLAYER_NAME_MAX_LENGTH" 21 | MaxLength="PLAYER_NAME_MAX_LENGTH" 22 | Validation="ValidatePlayerName"/> 23 | </MudForm> 24 | </DialogContent> 25 | <DialogActions> 26 | <MudButton OnClick="Cancel">Cancel</MudButton> 27 | <MudButton Color="Color.Primary" OnClick="Submit" Disabled="@(!IsValid)">Confirm</MudButton> 28 | </DialogActions> 29 | </MudDialog> 30 | 31 | @code { 32 | [CascadingParameter] 33 | MudDialogInstance MudDialog { get; set; } = null!; 34 | 35 | [Parameter] 36 | public ClientCardDto Data { get; set; } = null!; 37 | 38 | private string originalName = string.Empty; 39 | 40 | private bool IsValid { get; set; } 41 | 42 | private const int PLAYER_NAME_MAX_LENGTH = 8; 43 | 44 | protected override void OnInitialized() 45 | { 46 | base.OnInitialized(); 47 | originalName = new string(Data.PlayerName); 48 | } 49 | 50 | async Task Submit() 51 | { 52 | if (originalName.Equals(Data.PlayerName)) 53 | { 54 | MudDialog.Close(DialogResult.Ok(true)); 55 | return; 56 | } 57 | 58 | Logger.LogInformation("Data is {CardId}, {Name}", Data.CardId, Data.PlayerName); 59 | var response = await Client.PostAsJsonAsync("api/Profiles/PlayerName", Data); 60 | var result = await response.Content.ReadFromJsonAsync<ServiceResult<bool>>(); 61 | result.ThrowIfNull(); 62 | Logger.LogInformation("SetPlayerName result is {Result}", result.Succeeded); 63 | MudDialog.Close(DialogResult.Ok(result)); 64 | } 65 | 66 | void Cancel() 67 | { 68 | Data.PlayerName = originalName; 69 | MudDialog.Cancel(); 70 | } 71 | 72 | private static string? ValidatePlayerName(string playerName) 73 | { 74 | const string pattern = @"^[a-zA-Z0-9!?,./\-+:<>_\\@*#&=() ]{1,8}$"; 75 | 76 | return playerName.Length switch 77 | { 78 | 0 => "Player name cannot be empty!", 79 | > PLAYER_NAME_MAX_LENGTH => "Player name cannot be longer than 8 characters!", 80 | _ => !Regex.IsMatch(playerName, pattern) ? "Player name contains invalid character!" : null 81 | }; 82 | } 83 | } -------------------------------------------------------------------------------- /Infrastructure/Persistence/MusicDbContext.cs: -------------------------------------------------------------------------------- 1 | using Application.Interfaces; 2 | using Domain.Entities; 3 | using Infrastructure.Common; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Infrastructure.Persistence; 7 | 8 | public partial class MusicDbContext : DbContext, IMusicDbContext 9 | { 10 | public MusicDbContext() 11 | { 12 | } 13 | 14 | public MusicDbContext(DbContextOptions<MusicDbContext> options) 15 | : base(options) 16 | { 17 | } 18 | 19 | public virtual DbSet<MusicAou> MusicAous { get; set; } = null!; 20 | 21 | public virtual DbSet<MusicExtra> MusicExtras { get; set; } = null!; 22 | 23 | public virtual DbSet<MusicUnlock> MusicUnlocks { get; set; } = null!; 24 | 25 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 26 | { 27 | if (optionsBuilder.IsConfigured) 28 | { 29 | return; 30 | } 31 | 32 | var defaultDb = Path.Combine(PathHelper.DatabasePath, "music471omni.db3"); 33 | optionsBuilder.UseSqlite($"Data Source={defaultDb}"); 34 | } 35 | protected override void OnModelCreating(ModelBuilder modelBuilder) 36 | { 37 | modelBuilder.Entity<MusicAou>(entity => 38 | { 39 | entity.HasKey(e => e.MusicId); 40 | 41 | entity.ToTable("music_aou"); 42 | 43 | entity.Property(e => e.MusicId) 44 | .ValueGeneratedNever() 45 | .HasColumnName("music_id"); 46 | entity.Property(e => e.UseFlag).HasColumnName("use_flag"); 47 | }); 48 | 49 | modelBuilder.Entity<MusicExtra>(entity => 50 | { 51 | entity.HasKey(e => e.MusicId); 52 | 53 | entity.ToTable("music_extra"); 54 | 55 | entity.Property(e => e.MusicId) 56 | .ValueGeneratedNever() 57 | .HasColumnName("music_id"); 58 | entity.Property(e => e.UseFlag).HasColumnName("use_flag"); 59 | }); 60 | 61 | modelBuilder.Entity<MusicUnlock>(entity => 62 | { 63 | entity.HasKey(e => e.MusicId); 64 | 65 | entity.ToTable("music_unlock"); 66 | 67 | entity.Property(e => e.MusicId) 68 | .ValueGeneratedNever() 69 | .HasColumnName("music_id"); 70 | entity.Property(e => e.Artist).HasColumnName("artist").IsRequired(false); 71 | entity.Property(e => e.Title).HasColumnName("title"); 72 | entity.Property(e => e.ReleaseDate).HasColumnName("release_date"); 73 | entity.Property(e => e.EndDate).HasColumnName("end_date"); 74 | entity.Property(e => e.NewFlag).HasColumnName("new_flag"); 75 | entity.Property(e => e.UseFlag).HasColumnName("use_flag"); 76 | entity.Property(e => e.CalcFlag).HasColumnName("calc_flag"); 77 | }); 78 | 79 | OnModelCreatingPartial(modelBuilder); 80 | } 81 | 82 | partial void OnModelCreatingPartial(ModelBuilder modelBuilder); 83 | } 84 | -------------------------------------------------------------------------------- /WebUI/Pages/Option.razor: -------------------------------------------------------------------------------- 1 | @page "/Cards/Option/{cardId:long}" 2 | @using Domain.Enums 3 | 4 | 5 | <MudBreadcrumbs Items="breadcrumbs" Class="px-0"></MudBreadcrumbs> 6 | 7 | <PageTitle>Option</PageTitle> 8 | <h1>Play Options</h1> 9 | 10 | @if (errorMessage is not null) 11 | { 12 | <MudText Color="Color.Error" Typo="Typo.h3">@errorMessage</MudText> 13 | return; 14 | } 15 | 16 | @if (playOptionData is null) 17 | { 18 | <MudStack> 19 | <MudSkeleton Width="100%"/> 20 | <MudSkeleton Width="100%"/> 21 | <MudSkeleton Width="100%"/> 22 | <MudSkeleton Width="100%"/> 23 | <MudSkeleton Width="100%"/> 24 | <MudSkeleton Width="100%"/> 25 | </MudStack> 26 | return; 27 | } 28 | 29 | <MudStack> 30 | <MudStack Row="true"> 31 | <MudField Label="Avatar">@GetAvatarName((uint)playOptionData.OptionPart1.AvatarId)</MudField> 32 | <MudButton Variant="Variant.Text" OnClick="OpenChangeAvatarDialog">Change Avatar</MudButton> 33 | </MudStack> 34 | 35 | <MudStack Row="true"> 36 | <MudField Label="Title">@GetTitleName((uint)playOptionData.OptionPart1.TitleId)</MudField> 37 | <MudButton Variant="Variant.Text" OnClick="OpenChangeTitleDialog">Change Title</MudButton> 38 | </MudStack> 39 | 40 | <MudStack Row="true"> 41 | <MudField Label="Navigator">@GetNavigatorName((uint)playOptionData.OptionPart2.NavigatorId)</MudField> 42 | <MudButton Variant="Variant.Text" OnClick="OpenChangeNavigatorDialog">Change Navigator</MudButton> 43 | </MudStack> 44 | 45 | 46 | <MudSelect T="ShowFastSlowOption" Label="Fast/Slow option" Variant="Variant.Outlined" 47 | @bind-Value="@playOptionData.OptionPart1.ShowFastSlowOption"> 48 | @foreach (var item in ShowFastSlowOptionExtensions.GetValues()) 49 | { 50 | <MudSelectItem Value="item">@item.ToStringFast()</MudSelectItem> 51 | } 52 | </MudSelect> 53 | 54 | <MudSelect T="ShowFeverTranceOption" Label="Fever/Trance option" Variant="Variant.Outlined" 55 | @bind-Value="@playOptionData.OptionPart1.ShowFeverTranceOption"> 56 | @foreach (var item in ShowFeverTranceOptionExtensions.GetValues()) 57 | { 58 | <MudSelectItem Value="item">@item.ToStringFast()</MudSelectItem> 59 | } 60 | </MudSelect> 61 | 62 | <MudButton Color="Color.Info" Variant="Variant.Filled" OnClick="SaveOptions"> 63 | @if (isSaving) 64 | { 65 | <MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true"/> 66 | <MudText Class="ms-2">Saving...</MudText> 67 | } 68 | else 69 | { 70 | <MudIcon Icon="@Icons.Material.Filled.Save"></MudIcon> 71 | <MudText>Save</MudText> 72 | } 73 | </MudButton> 74 | <MudButton Color="Color.Default" Variant="Variant.Filled" OnClick="UnlockMusics"> 75 | <MudIcon Icon="@Icons.Material.Filled.LockOpen"></MudIcon> 76 | <MudText>Unlock All Musics</MudText> 77 | </MudButton> 78 | </MudStack> -------------------------------------------------------------------------------- /Application/Jobs/MaintainNullValuesJob.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Domain.Config; 3 | using Domain.Enums; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Options; 6 | using Quartz; 7 | 8 | namespace Application.Jobs; 9 | 10 | public class MaintainNullValuesJob : IJob 11 | { 12 | private readonly ILogger<MaintainNullValuesJob> logger; 13 | 14 | private readonly ICardDbContext cardDbContext; 15 | 16 | private readonly GameConfig config; 17 | 18 | public static readonly JobKey KEY = new("MaintainNullValuesJob"); 19 | 20 | public MaintainNullValuesJob(ILogger<MaintainNullValuesJob> logger, ICardDbContext cardDbContext, IOptions<GameConfig> options) 21 | { 22 | this.logger = logger; 23 | this.cardDbContext = cardDbContext; 24 | config = options.Value; 25 | } 26 | 27 | [SuppressMessage("ReSharper.DPA", "DPA0007: Large number of DB records", 28 | Justification = "All details might be read")] 29 | [SuppressMessage("ReSharper.DPA", "DPA0006: Large number of DB commands")] 30 | public async Task Execute(IJobExecutionContext context) 31 | { 32 | logger.LogInformation("Starting changing null values in card detail table"); 33 | 34 | var details = await cardDbContext.CardDetails.Where(detail => detail.LastPlayTenpoId == null || 35 | detail.LastPlayTenpoId == "GC local server" 36 | || detail.LastPlayTime == null).ToListAsync(); 37 | details.ForEach(detail => 38 | { 39 | detail.LastPlayTenpoId = "1337"; 40 | detail.LastPlayTime = DateTime.MinValue; 41 | }); 42 | 43 | cardDbContext.CardDetails.UpdateRange(details); 44 | var count = await cardDbContext.SaveChangesAsync(new CancellationToken()); 45 | 46 | logger.LogInformation("Updated {Count} entries in card detail table", count); 47 | 48 | logger.LogInformation("Starting closing unfinished matches"); 49 | var matches = await cardDbContext.OnlineMatches.Where(match => match.IsOpen == true).ToListAsync(); 50 | matches.ForEach(match => match.IsOpen = false); 51 | cardDbContext.OnlineMatches.UpdateRange(matches); 52 | count = await cardDbContext.SaveChangesAsync(new CancellationToken()); 53 | 54 | logger.LogInformation("Closed {Count} matches", count); 55 | 56 | logger.LogInformation("Starting to remove previously new songs"); 57 | var unlockables = config.UnlockRewards 58 | .Where(c => c.RewardType == RewardType.Music).ToDictionary(rewardConfig => rewardConfig.TargetId); 59 | var targets = await cardDbContext.CardDetails.Where(detail => detail.Pcol1 == 10).ToListAsync(); 60 | foreach (var target in targets) 61 | { 62 | if (unlockables.ContainsKey((int)target.Pcol2)) 63 | { 64 | continue; 65 | } 66 | target.ScoreUi2 = 0; 67 | target.ScoreUi6 = 0; 68 | } 69 | cardDbContext.CardDetails.UpdateRange(targets); 70 | count = await cardDbContext.SaveChangesAsync(new CancellationToken()); 71 | 72 | logger.LogInformation("Fixed {Count} records", count); 73 | } 74 | } -------------------------------------------------------------------------------- /MainServer/app.manifest: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> 3 | <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/> 4 | <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"> 5 | <security> 6 | <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"> 7 | <!-- UAC 清单选项 8 | 如果想要更改 Windows 用户帐户控制级别,请使用 9 | 以下节点之一替换 requestedExecutionLevel 节点。 10 | 11 | <requestedExecutionLevel level="asInvoker" uiAccess="false" /> 12 | <requestedExecutionLevel level="requireAdministrator" uiAccess="false" /> 13 | <requestedExecutionLevel level="highestAvailable" uiAccess="false" /> 14 | 15 | 指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。 16 | 如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此 17 | 元素。 18 | --> 19 | <requestedExecutionLevel level="requireAdministrator" uiAccess="false" /> 20 | </requestedPrivileges> 21 | </security> 22 | </trustInfo> 23 | 24 | <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> 25 | <application> 26 | <!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的 27 | Windows 版本的列表。取消评论适当的元素, 28 | Windows 将自动选择最兼容的环境。 --> 29 | 30 | <!-- Windows Vista --> 31 | <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />--> 32 | 33 | <!-- Windows 7 --> 34 | <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />--> 35 | 36 | <!-- Windows 8 --> 37 | <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />--> 38 | 39 | <!-- Windows 8.1 --> 40 | <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />--> 41 | 42 | <!-- Windows 10 --> 43 | <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> 44 | 45 | </application> 46 | </compatibility> 47 | 48 | <!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行 49 | 自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需 50 | 选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应 51 | 在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。 52 | 53 | 将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation --> 54 | <!-- 55 | <application xmlns="urn:schemas-microsoft-com:asm.v3"> 56 | <windowsSettings> 57 | <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> 58 | <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware> 59 | </windowsSettings> 60 | </application> 61 | --> 62 | 63 | <!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) --> 64 | <!-- 65 | <dependency> 66 | <dependentAssembly> 67 | <assemblyIdentity 68 | type="win32" 69 | name="Microsoft.Windows.Common-Controls" 70 | version="6.0.0.0" 71 | processorArchitecture="*" 72 | publicKeyToken="6595b64144ccf1df" 73 | language="*" 74 | /> 75 | </dependentAssembly> 76 | </dependency> 77 | --> 78 | 79 | </assembly> 80 | --------------------------------------------------------------------------------