diff --git a/Application/Application.csproj b/Application/Application.csproj
index 647d439..a11bca2 100644
--- a/Application/Application.csproj
+++ b/Application/Application.csproj
@@ -11,11 +11,14 @@
+
+
+
diff --git a/Application/Common/Behaviours/LoggingBehaviour.cs b/Application/Common/Behaviours/LoggingBehaviour.cs
new file mode 100644
index 0000000..89f20fa
--- /dev/null
+++ b/Application/Common/Behaviours/LoggingBehaviour.cs
@@ -0,0 +1,24 @@
+using MediatR.Pipeline;
+using Microsoft.Extensions.Logging;
+
+namespace Application.Common.Behaviours;
+
+public class LoggingBehaviour : IRequestPreProcessor where TRequest : notnull
+{
+ private readonly ILogger logger;
+
+ // ReSharper disable once ContextualLoggerProblem
+ public LoggingBehaviour(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ public Task Process(TRequest request, CancellationToken cancellationToken)
+ {
+ var requestName = typeof(TRequest).Name;
+
+ logger.LogInformation("Received request: {RequestName}, content: {Request}", requestName, request);
+
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/Application/Common/Behaviours/UnhandledExceptionBehaviour.cs b/Application/Common/Behaviours/UnhandledExceptionBehaviour.cs
new file mode 100644
index 0000000..b3b6155
--- /dev/null
+++ b/Application/Common/Behaviours/UnhandledExceptionBehaviour.cs
@@ -0,0 +1,33 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+
+namespace Application.Common.Behaviours;
+
+public class UnhandledExceptionBehaviour : IPipelineBehavior
+ where TRequest : IRequest
+{
+ private readonly ILogger logger;
+
+ // ReSharper disable once ContextualLoggerProblem
+ public UnhandledExceptionBehaviour(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+ public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken)
+ {
+ try
+ {
+ return await next();
+ }
+ catch (Exception ex)
+ {
+ var requestName = typeof(TRequest).Name;
+
+ logger.LogError(ex, "Unhandled Exception for Request {Name} {@Request}", requestName, request);
+
+ throw;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Application/Common/Exceptions/CardExistsException.cs b/Application/Common/Exceptions/CardExistsException.cs
new file mode 100644
index 0000000..f41b018
--- /dev/null
+++ b/Application/Common/Exceptions/CardExistsException.cs
@@ -0,0 +1,8 @@
+namespace Application.Common.Exceptions;
+
+public class CardExistsException : Exception
+{
+ public CardExistsException(string? message) : base(message)
+ {
+ }
+}
\ No newline at end of file
diff --git a/Application/Common/Extensions/XmlSerializationExtensions.cs b/Application/Common/Extensions/XmlSerializationExtensions.cs
new file mode 100644
index 0000000..5e5efc2
--- /dev/null
+++ b/Application/Common/Extensions/XmlSerializationExtensions.cs
@@ -0,0 +1,17 @@
+using ChoETL;
+using Throw;
+
+namespace Application.Common;
+
+public static class XmlSerializationExtensions
+{
+ public static T DeserializeCardData(this string source) where T : class
+ {
+ using var reader = new ChoXmlReader(new StringReader(source)).WithXPath("/root/data");
+
+ var result = reader.Read();
+ result.ThrowIfNull();
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/Application/Common/Models/ServiceError.cs b/Application/Common/Models/ServiceError.cs
new file mode 100644
index 0000000..3fb8248
--- /dev/null
+++ b/Application/Common/Models/ServiceError.cs
@@ -0,0 +1,140 @@
+namespace Application.Common.Models;
+
+///
+/// All errors contained in ServiceResult objects must return an error of this type
+/// Error codes allow the caller to easily identify the received error and take action.
+/// Error messages allow the caller to easily show error messages to the end user.
+///
+/// Taken from https://github.com/iayti/CleanArchitecture/blob/master/src/Common/CleanArchitecture.Application/Common/Models/ServiceError.cs
+///
+[Serializable]
+public class ServiceError
+{
+ ///
+ /// CTOR
+ ///
+ public ServiceError(string message, int code)
+ {
+ Message = message;
+ Code = code;
+ }
+
+ public ServiceError()
+ {
+ }
+
+ ///
+ /// Human readable error message
+ ///
+ public string Message { get; } = string.Empty;
+
+ ///
+ /// Machine readable error code
+ ///
+ public int Code { get; }
+
+ ///
+ /// Default error for when we receive an exception
+ ///
+ public static ServiceError DefaultError => new("An unknown exception occured.", 999);
+
+ ///
+ /// Default validation error. Use this for invalid parameters in controller actions and service methods.
+ ///
+ public static ServiceError ModelStateError(string validationError)
+ {
+ return new ServiceError(validationError, 998);
+ }
+
+ ///
+ /// Use this for unauthorized responses.
+ ///
+ public static ServiceError ForbiddenError => new("You are not authorized to call this action.", 998);
+
+ ///
+ /// Use this to send a custom error message
+ ///
+ public static ServiceError CustomMessage(string errorMessage)
+ {
+ return new ServiceError(errorMessage, 997);
+ }
+
+ public static ServiceError UserNotFound => new("User with this id does not exist", 996);
+
+ public static ServiceError UserFailedToCreate => new("Failed to create User.", 995);
+
+ public static ServiceError Canceled => new("The request canceled successfully!", 994);
+
+ public static ServiceError NotFound => new("The specified resource was not found.", 990);
+
+ public static ServiceError ValidationFormat => new("Request object format is not true.", 901);
+
+ public static ServiceError Validation => new("One or more validation errors occurred.", 900);
+
+ public static ServiceError SearchAtLeastOneCharacter =>
+ new("Search parameter must have at least one character!", 898);
+
+ ///
+ /// Default error for when we receive an exception
+ ///
+ public static ServiceError ServiceProviderNotFound =>
+ new("Service Provider with this name does not exist.", 700);
+
+ public static ServiceError ServiceProvider => new("Service Provider failed to return as expected.", 600);
+
+ public static ServiceError DateTimeFormatError =>
+ new("Date format is not true. Date format must be like yyyy-MM-dd (2019-07-19)", 500);
+
+ #region Override Equals Operator
+
+ ///
+ /// Use this to compare if two errors are equal
+ /// Ref: https://msdn.microsoft.com/ru-ru/library/ms173147(v=vs.80).aspx
+ ///
+ public override bool Equals(object? obj)
+ {
+ // If parameter cannot be cast to ServiceError or is null return false.
+ var error = obj as ServiceError;
+
+ // Return true if the error codes match. False if the object we're comparing to is null
+ // or if it has a different code.
+ return Code == error?.Code;
+ }
+
+ public bool Equals(ServiceError error)
+ {
+ // Return true if the error codes match. False if the object we're comparing to is null
+ // or if it has a different code.
+ return Code == error?.Code;
+ }
+
+ public override int GetHashCode()
+ {
+ return Code;
+ }
+
+ public static bool operator ==(ServiceError? a, ServiceError? b)
+ {
+ // If both are null, or both are same instance, return true.
+ if (ReferenceEquals(a, b))
+ {
+ return true;
+ }
+
+ // If one is null, but not both, return false.
+ if (a is null || b is null)
+ {
+ return false;
+ }
+
+ // Return true if the fields match:
+ return a.Equals(b);
+ }
+
+ public static bool operator !=(ServiceError a, ServiceError b)
+ {
+ return !(a == b);
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Application/Common/Models/ServiceResult.cs b/Application/Common/Models/ServiceResult.cs
new file mode 100644
index 0000000..849f197
--- /dev/null
+++ b/Application/Common/Models/ServiceResult.cs
@@ -0,0 +1,65 @@
+namespace Application.Common.Models;
+
+///
+/// A standard response for service calls.
+///
+/// Return data type
+public class ServiceResult : ServiceResult
+{
+ public T? Data { get; set; }
+
+ public ServiceResult(T? data)
+ {
+ Data = data;
+ }
+
+ public ServiceResult(T? data, ServiceError error) : base(error)
+ {
+ Data = data;
+ }
+
+ public ServiceResult(ServiceError error) : base(error)
+ {
+
+ }
+}
+
+public class ServiceResult
+{
+ public bool Succeeded => Error == null;
+
+ public ServiceError? Error { get; set; }
+
+ public ServiceResult(ServiceError? error)
+ {
+ error ??= ServiceError.DefaultError;
+
+ Error = error;
+ }
+
+ public ServiceResult() { }
+
+ #region Helper Methods
+
+ public static ServiceResult Failed(ServiceError error)
+ {
+ return new ServiceResult(error);
+ }
+
+ public static ServiceResult Failed(ServiceError error)
+ {
+ return new ServiceResult(error);
+ }
+
+ public static ServiceResult Failed(T data, ServiceError error)
+ {
+ return new ServiceResult(data, error);
+ }
+
+ public static ServiceResult Success(T data)
+ {
+ return new ServiceResult(data);
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Application/DependencyInjection.cs b/Application/DependencyInjection.cs
index 6ccba01..6e630d0 100644
--- a/Application/DependencyInjection.cs
+++ b/Application/DependencyInjection.cs
@@ -1,4 +1,7 @@
using System.Reflection;
+using Application.Common.Behaviours;
+using Application.Game.Card;
+using Application.Interfaces;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
@@ -10,6 +13,8 @@ public static class DependencyInjection
{
services.AddMediatR(Assembly.GetExecutingAssembly());
+ services.AddScoped();
+ services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>));
return services;
}
}
\ No newline at end of file
diff --git a/Application/Dto/CardDto.cs b/Application/Dto/CardDto.cs
index 74f1d57..26b1516 100644
--- a/Application/Dto/CardDto.cs
+++ b/Application/Dto/CardDto.cs
@@ -1,4 +1,5 @@
-using System.Xml.Serialization;
+using System.ComponentModel;
+using System.Xml.Serialization;
namespace Application.Dto;
@@ -8,6 +9,7 @@ public class CardDto
public long CardId { get; set; }
[XmlElement(ElementName = "player_name")]
+ [DefaultValue("")]
public string PlayerName { get; set; } = string.Empty;
[XmlElement("score_i1")]
@@ -23,11 +25,14 @@ public class CardDto
public long Fcol3 { get; set; }
[XmlElement("achieve_status")]
+ [DefaultValue("")]
public string AchieveStatus { get; set; } = string.Empty;
[XmlElement("created")]
+ [DefaultValue("")]
public string Created { get; set; } = string.Empty;
[XmlElement("modified")]
+ [DefaultValue("")]
public string Modified { get; set; } = string.Empty;
}
\ No newline at end of file
diff --git a/Application/Game/Card/CardDependencyAggregate.cs b/Application/Game/Card/CardDependencyAggregate.cs
new file mode 100644
index 0000000..1f9d580
--- /dev/null
+++ b/Application/Game/Card/CardDependencyAggregate.cs
@@ -0,0 +1,15 @@
+using Application.Interfaces;
+
+namespace Application.Game.Card;
+
+public class CardDependencyAggregate : ICardDependencyAggregate
+{
+ public CardDependencyAggregate(ICardDbContext cardDbContext, IMusicDbContext musicDbContext)
+ {
+ CardDbContext = cardDbContext;
+ MusicDbContext = musicDbContext;
+ }
+
+ public ICardDbContext CardDbContext { get; }
+ public IMusicDbContext MusicDbContext { get; }
+}
\ No newline at end of file
diff --git a/Application/Game/Card/CardRegisterCommand.cs b/Application/Game/Card/CardRegisterCommand.cs
new file mode 100644
index 0000000..1d94fc5
--- /dev/null
+++ b/Application/Game/Card/CardRegisterCommand.cs
@@ -0,0 +1,36 @@
+using Application.Common;
+using Application.Common.Exceptions;
+using Application.Common.Models;
+using Application.Dto;
+using Application.Interfaces;
+using Application.Mappers;
+using MediatR;
+using Microsoft.Extensions.Logging;
+using Throw;
+
+namespace Application.Game.Card;
+
+public record CardRegisterCommand(long CardId, string Data) : IRequestWrapper;
+
+public class CardRegisterCommandHandler : CardRequestHandlerBase
+{
+ public CardRegisterCommandHandler(ICardDependencyAggregate aggregate) : base(aggregate)
+ {
+ }
+
+ public override async Task> Handle(CardRegisterCommand request, CancellationToken cancellationToken)
+ {
+ var exists = CardDbContext.CardMains.Any(card => card.CardId == request.CardId);
+ if (!exists)
+ {
+ return ServiceResult.Failed(ServiceError.CustomMessage($"Card {request.CardId} already exists!"));
+ }
+
+ var card = request.Data.DeserializeCardData().CardDtoToCardMain();
+ card.CardId = request.CardId;
+ CardDbContext.CardMains.Add(card);
+ await CardDbContext.SaveChangesAsync(cancellationToken);
+
+ return new ServiceResult(request.Data);
+ }
+}
\ No newline at end of file
diff --git a/Application/Game/Card/CardRequest.cs b/Application/Game/Card/CardRequest.cs
new file mode 100644
index 0000000..f95ee8d
--- /dev/null
+++ b/Application/Game/Card/CardRequest.cs
@@ -0,0 +1,23 @@
+using Domain;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
+
+namespace Application.Game.Card;
+
+public class CardRequest
+{
+ [ModelBinder(Name = "mac_addr")]
+ public string Mac { get; set; } = string.Empty;
+
+ [ModelBinder(Name = "cmd_str")]
+ public int CardCommandType { get; set; }
+
+ [ModelBinder(Name = "type")]
+ public int CardRequestType { get; set; }
+
+ [ModelBinder(Name = "card_no")]
+ public long CardId { get; set; }
+
+ [ModelBinder(Name = "data")]
+ public string Data { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/Application/Game/Card/CardRequestHandlerBase.cs b/Application/Game/Card/CardRequestHandlerBase.cs
new file mode 100644
index 0000000..5e0d925
--- /dev/null
+++ b/Application/Game/Card/CardRequestHandlerBase.cs
@@ -0,0 +1,21 @@
+using Application.Common.Models;
+using Application.Interfaces;
+using MediatR;
+using Microsoft.Extensions.Logging;
+
+namespace Application.Game.Card;
+
+public abstract class CardRequestHandlerBase: IRequestHandlerWrapper
+ where TIn : IRequestWrapper
+{
+ public ICardDbContext CardDbContext { get; }
+ public IMusicDbContext MusicDbContext { get; }
+
+ public CardRequestHandlerBase(ICardDependencyAggregate aggregate)
+ {
+ CardDbContext = aggregate.CardDbContext;
+ MusicDbContext = aggregate.MusicDbContext;
+ }
+
+ public abstract Task> Handle(TIn request, CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/Application/Interfaces/ICardDependencyAggregate.cs b/Application/Interfaces/ICardDependencyAggregate.cs
new file mode 100644
index 0000000..6944404
--- /dev/null
+++ b/Application/Interfaces/ICardDependencyAggregate.cs
@@ -0,0 +1,9 @@
+using Microsoft.Extensions.Logging;
+
+namespace Application.Interfaces;
+
+public interface ICardDependencyAggregate
+{
+ ICardDbContext CardDbContext { get; }
+ IMusicDbContext MusicDbContext { get; }
+}
\ No newline at end of file
diff --git a/Application/Interfaces/IRequestWrapper.cs b/Application/Interfaces/IRequestWrapper.cs
new file mode 100644
index 0000000..79c50a3
--- /dev/null
+++ b/Application/Interfaces/IRequestWrapper.cs
@@ -0,0 +1,14 @@
+using Application.Common.Models;
+using MediatR;
+
+namespace Application.Interfaces;
+
+public interface IRequestWrapper : IRequest>
+{
+
+}
+
+public interface IRequestHandlerWrapper : IRequestHandler> where TIn : IRequestWrapper
+{
+
+}
\ No newline at end of file
diff --git a/Application/Mappers/CardMapper.cs b/Application/Mappers/CardMapper.cs
index ad86f71..8779e56 100644
--- a/Application/Mappers/CardMapper.cs
+++ b/Application/Mappers/CardMapper.cs
@@ -8,4 +8,6 @@ namespace Application.Mappers;
public static partial class CardMapper
{
public static partial CardDto CardMainToCardDto(this CardMain cardMain);
+
+ public static partial CardMain CardDtoToCardMain(this CardDto cardDto);
}
\ No newline at end of file
diff --git a/Domain/Config/GameConfig.cs b/Domain/Config/GameConfig.cs
new file mode 100644
index 0000000..c121938
--- /dev/null
+++ b/Domain/Config/GameConfig.cs
@@ -0,0 +1,20 @@
+namespace Domain.Config;
+
+public class GameConfig
+{
+ public const string GAME_SECTION = "Game";
+
+ public int AvatarCount { get; set; }
+
+ public int NavigatorCount { get; set; }
+
+ public int ItemCount { get; set; }
+
+ public int SkinCount { get; set; }
+
+ public int SeCount { get; set; }
+
+ public int TitleCount { get; set; }
+
+ public List UnlockableSongIds { get; set; } = new();
+}
\ No newline at end of file
diff --git a/Domain/Enums/CardCommandType.cs b/Domain/Enums/CardCommandType.cs
new file mode 100644
index 0000000..2bb09ff
--- /dev/null
+++ b/Domain/Enums/CardCommandType.cs
@@ -0,0 +1,9 @@
+namespace Domain;
+
+public enum CardCommandType
+{
+ CardReadRequest = 256,
+ CardWriteRequest = 768,
+ RegisterRequest = 512,
+ ReissueRequest = 1536
+}
\ No newline at end of file
diff --git a/Domain/Enums/CardRequestType.cs b/Domain/Enums/CardRequestType.cs
new file mode 100644
index 0000000..c593f45
--- /dev/null
+++ b/Domain/Enums/CardRequestType.cs
@@ -0,0 +1,55 @@
+namespace Domain;
+
+public enum CardRequestType
+{
+ #region Read
+ ReadCard = 259,
+ ReadCardDetail = 260,
+ ReadCardDetails = 261,
+ ReadCardBData = 264,
+ ReadAvatar = 418,
+ ReadItem = 420,
+ ReadSkin = 422,
+ ReadTitle = 424,
+ ReadMusic = 428,
+ ReadEventReward = 441,
+ ReadNavigator = 443,
+ ReadMusicExtra = 465,
+ ReadMusicAou = 467,
+ ReadCoin = 468,
+ ReadUnlockReward = 507,
+ ReadUnlockKeynum = 509,
+ ReadSoundEffect = 8458,
+ ReadGetMessage = 8461,
+ ReadCond = 8465,
+ ReadTotalTrophy = 8468,
+ #endregion
+
+ #region Session
+ GetSession = 401,
+ StartSession = 402,
+ #endregion
+
+
+ #region Write
+ WriteCard = 771,
+ WriteCardDetail = 772,
+ WriteCardBData = 776,
+ WriteAvatar = 929,
+ WriteItem = 931,
+ WriteTitle = 935,
+ WriteMusicDetail = 941,
+ WriteNavigator = 954,
+ WriteCoin = 980,
+ WriteSkin = 933,
+ WriteUnlockKeynum = 1020,
+ WriteSoundEffect = 8969,
+ #endregion
+
+
+ #region Online Matching
+ StartOnlineMatching = 8705,
+ UpdateOnlineMatching = 8961,
+ UploadOnlineMatchingResult = 8709,
+ #endregion
+}
\ No newline at end of file
diff --git a/Domain/Enums/CardReturnCode.cs b/Domain/Enums/CardReturnCode.cs
new file mode 100644
index 0000000..341e4f8
--- /dev/null
+++ b/Domain/Enums/CardReturnCode.cs
@@ -0,0 +1,27 @@
+namespace Domain;
+
+public enum CardReturnCode
+{
+ ///
+ /// Normal
+ /// 処理は正常に完了しました in debug string
+ ///
+ Ok = 1,
+
+ ///
+ /// New card
+ /// 未登録のカードです in debug string
+ ///
+ CardNotRegistered = 23,
+
+ ///
+ /// Not reissue, to determine whether it is a new card or reissued card
+ /// 再発行予約がありません in debug string
+ ///
+ NotReissue = 27,
+
+ ///
+ /// Server side validation error
+ ///
+ Unknown = 999
+}
\ No newline at end of file
diff --git a/Infrastructure/Migrations/20230208132952_Initial.cs b/Infrastructure/Migrations/20230208132952_Initial.cs
index 9ea1b7d..1af7824 100644
--- a/Infrastructure/Migrations/20230208132952_Initial.cs
+++ b/Infrastructure/Migrations/20230208132952_Initial.cs
@@ -10,77 +10,89 @@ namespace Infrastructure.Migrations
///
protected override void Up(MigrationBuilder migrationBuilder)
{
- migrationBuilder.CreateTable(
- name: "card_bdata",
- columns: table => new
- {
- cardid = table.Column(name: "card_id", type: "INTEGER", nullable: false),
- bdata = table.Column(type: "TEXT", nullable: true),
- bdatasize = table.Column(name: "bdata_size", type: "INTEGER", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_card_bdata", x => x.cardid);
- });
+ if (!MigrationHelper.Exists("card_bdata"))
+ {
+ migrationBuilder.CreateTable(
+ name: "card_bdata",
+ columns: table => new
+ {
+ cardid = table.Column(name: "card_id", type: "INTEGER", nullable: false),
+ bdata = table.Column(type: "TEXT", nullable: true),
+ bdatasize = table.Column(name: "bdata_size", type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_card_bdata", x => x.cardid);
+ });
+ }
- migrationBuilder.CreateTable(
- name: "card_detail",
- columns: table => new
- {
- cardid = table.Column(name: "card_id", type: "INTEGER", nullable: false),
- pcol1 = table.Column(type: "INTEGER", nullable: false),
- pcol2 = table.Column(type: "INTEGER", nullable: false),
- pcol3 = table.Column(type: "INTEGER", nullable: false),
- scorei1 = table.Column(name: "score_i1", type: "INTEGER", nullable: false),
- scoreui1 = table.Column(name: "score_ui1", type: "INTEGER", nullable: false),
- scoreui2 = table.Column(name: "score_ui2", type: "INTEGER", nullable: false),
- scoreui3 = table.Column(name: "score_ui3", type: "INTEGER", nullable: false),
- scoreui4 = table.Column(name: "score_ui4", type: "INTEGER", nullable: false),
- scoreui5 = table.Column(name: "score_ui5", type: "INTEGER", nullable: false),
- scoreui6 = table.Column(name: "score_ui6", type: "INTEGER", nullable: false),
- scorebi1 = table.Column(name: "score_bi1", type: "INTEGER", nullable: false),
- lastplaytenpoid = table.Column(name: "last_play_tenpo_id", type: "TEXT", nullable: true),
- fcol1 = table.Column(type: "INTEGER", nullable: false),
- fcol2 = table.Column(type: "INTEGER", nullable: false),
- fcol3 = table.Column(type: "INTEGER", nullable: false),
- lastplaytime = table.Column(name: "last_play_time", type: "INTEGER", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_card_detail", x => new { x.cardid, x.pcol1, x.pcol2, x.pcol3 });
- });
+ if (!MigrationHelper.Exists("card_detail"))
+ {
+ migrationBuilder.CreateTable(
+ name: "card_detail",
+ columns: table => new
+ {
+ cardid = table.Column(name: "card_id", type: "INTEGER", nullable: false),
+ pcol1 = table.Column(type: "INTEGER", nullable: false),
+ pcol2 = table.Column(type: "INTEGER", nullable: false),
+ pcol3 = table.Column(type: "INTEGER", nullable: false),
+ scorei1 = table.Column(name: "score_i1", type: "INTEGER", nullable: false),
+ scoreui1 = table.Column(name: "score_ui1", type: "INTEGER", nullable: false),
+ scoreui2 = table.Column(name: "score_ui2", type: "INTEGER", nullable: false),
+ scoreui3 = table.Column(name: "score_ui3", type: "INTEGER", nullable: false),
+ scoreui4 = table.Column(name: "score_ui4", type: "INTEGER", nullable: false),
+ scoreui5 = table.Column(name: "score_ui5", type: "INTEGER", nullable: false),
+ scoreui6 = table.Column(name: "score_ui6", type: "INTEGER", nullable: false),
+ scorebi1 = table.Column(name: "score_bi1", type: "INTEGER", nullable: false),
+ lastplaytenpoid = table.Column(name: "last_play_tenpo_id", type: "TEXT", nullable: true),
+ fcol1 = table.Column(type: "INTEGER", nullable: false),
+ fcol2 = table.Column(type: "INTEGER", nullable: false),
+ fcol3 = table.Column(type: "INTEGER", nullable: false),
+ lastplaytime = table.Column(name: "last_play_time", type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_card_detail", x => new { x.cardid, x.pcol1, x.pcol2, x.pcol3 });
+ });
+ }
- migrationBuilder.CreateTable(
- name: "card_main",
- columns: table => new
- {
- cardid = table.Column(name: "card_id", type: "INTEGER", nullable: false),
- playername = table.Column(name: "player_name", type: "TEXT", nullable: false),
- scorei1 = table.Column(name: "score_i1", type: "INTEGER", nullable: false),
- fcol1 = table.Column(type: "INTEGER", nullable: false),
- fcol2 = table.Column(type: "INTEGER", nullable: false),
- fcol3 = table.Column(type: "INTEGER", nullable: false),
- achievestatus = table.Column(name: "achieve_status", type: "TEXT", nullable: false),
- created = table.Column(type: "TEXT", nullable: true),
- modified = table.Column(type: "TEXT", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_card_main", x => x.cardid);
- });
+ if (!MigrationHelper.Exists("card_main"))
+ {
+ migrationBuilder.CreateTable(
+ name: "card_main",
+ columns: table => new
+ {
+ cardid = table.Column(name: "card_id", type: "INTEGER", nullable: false),
+ playername = table.Column(name: "player_name", type: "TEXT", nullable: false),
+ scorei1 = table.Column(name: "score_i1", type: "INTEGER", nullable: false),
+ fcol1 = table.Column(type: "INTEGER", nullable: false),
+ fcol2 = table.Column(type: "INTEGER", nullable: false),
+ fcol3 = table.Column(type: "INTEGER", nullable: false),
+ achievestatus = table.Column(name: "achieve_status", type: "TEXT", nullable: false),
+ created = table.Column(type: "TEXT", nullable: true),
+ modified = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_card_main", x => x.cardid);
+ });
+ }
- migrationBuilder.CreateTable(
- name: "CardPlayCount",
- columns: table => new
- {
- cardid = table.Column(name: "card_id", type: "INTEGER", nullable: false),
- playcount = table.Column(name: "play_count", type: "INTEGER", nullable: false),
- lastplayedtime = table.Column(name: "last_played_time", type: "INTEGER", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_CardPlayCount", x => x.cardid);
- });
+ if (!MigrationHelper.Exists("CardPlayCount"))
+ {
+ migrationBuilder.CreateTable(
+ name: "CardPlayCount",
+ columns: table => new
+ {
+ cardid = table.Column(name: "card_id", type: "INTEGER", nullable: false),
+ playcount = table.Column(name: "play_count", type: "INTEGER", nullable: false),
+ lastplayedtime = table.Column(name: "last_played_time", type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CardPlayCount", x => x.cardid);
+ });
+ }
}
///
diff --git a/Infrastructure/Migrations/MigrationHelper.cs b/Infrastructure/Migrations/MigrationHelper.cs
index 9edc2c0..b51cd32 100644
--- a/Infrastructure/Migrations/MigrationHelper.cs
+++ b/Infrastructure/Migrations/MigrationHelper.cs
@@ -20,14 +20,14 @@ public static class MigrationHelper
return reader.Read()? (long)reader[0] == 1 : false;
}
-
- public static string GetConnectionString()
+
+ private static string GetConnectionString()
{
var builder = new ConfigurationBuilder()
.SetBasePath(PathHelper.ConfigurationPath)
.AddJsonFile("database.json", optional: false, reloadOnChange: false);
- var cardDbName = builder.Build()["CardDbName"];
+ var cardDbName = builder.Build()["CardDbName"] ?? "card.db3";
var cardDbPath = Path.Combine(PathHelper.DatabasePath, cardDbName);
return cardDbPath;
}
diff --git a/MainServer/Configurations/game.json b/MainServer/Configurations/game.json
index e0602fb..1c7a24e 100644
--- a/MainServer/Configurations/game.json
+++ b/MainServer/Configurations/game.json
@@ -1,15 +1,17 @@
{
- "AvatarCount": 356,
- "NavigatorCount": 118,
- "ItemCount": 21,
- "SkinCount": 21,
- "SeCount": 26,
- "TitleCount": 5530,
- "UnlockableSongIds":
- [
- 11, 13, 149, 273, 291, 320, 321, 371, 378, 384, 464, 471, 474, 475, 492,
- 494, 498, 520, 548, 551, 558, 561, 565, 570, 577, 583, 612, 615, 622,
- 632, 659, 666, 668, 670, 672, 676, 680, 682, 685, 686, 697, 700, 701,
- 711, 720, 749, 875, 876, 877
- ]
+ "Game": {
+ "AvatarCount": 356,
+ "NavigatorCount": 118,
+ "ItemCount": 21,
+ "SkinCount": 21,
+ "SeCount": 26,
+ "TitleCount": 5530,
+ "UnlockableSongIds":
+ [
+ 11, 13, 149, 273, 291, 320, 321, 371, 378, 384, 464, 471, 474, 475, 492,
+ 494, 498, 520, 548, 551, 558, 561, 565, 570, 577, 583, 612, 615, 622,
+ 632, 659, 666, 668, 670, 672, 676, 680, 682, 685, 686, 697, 700, 701,
+ 711, 720, 749, 875, 876, 877
+ ]
+ }
}
\ No newline at end of file
diff --git a/MainServer/Controllers/API/TestController.cs b/MainServer/Controllers/API/TestController.cs
index c60d68f..d487e61 100644
--- a/MainServer/Controllers/API/TestController.cs
+++ b/MainServer/Controllers/API/TestController.cs
@@ -20,5 +20,20 @@ namespace MainServer.Controllers.API
{
return context.MusicUnlocks.First();
}
+
+ [HttpPost]
+ public ActionResult TestXmlInputOutput([FromForm(Name = "my_model")]TestModel model,
+ [FromForm(Name = "my_type")]int type)
+ {
+ return Ok($"{model.Name}\n{model.Age}\n{type}");
+ }
}
+
+ public class TestModel
+ {
+ public string Name { get; set; } = string.Empty;
+
+ public int Age { get; set; }
+ }
+
}
diff --git a/MainServer/Controllers/Game/CardController.cs b/MainServer/Controllers/Game/CardController.cs
new file mode 100644
index 0000000..85eb907
--- /dev/null
+++ b/MainServer/Controllers/Game/CardController.cs
@@ -0,0 +1,134 @@
+using System.Net;
+using Application.Common.Models;
+using Application.Game.Card;
+using Domain;
+using Microsoft.AspNetCore.Mvc;
+using Throw;
+
+namespace MainServer.Controllers.Game;
+
+[ApiController]
+[Route("service/card")]
+public class CardController : BaseController
+{
+ [HttpPost("cardn.cgi")]
+ public async Task> CardService([FromForm]CardRequest request)
+ {
+ var cardRequestType = (CardRequestType)request.CardRequestType;
+ var cardCommandType = (CardCommandType)request.CardCommandType;
+
+ cardCommandType.Throw().IfOutOfRange();
+ if (cardCommandType is CardCommandType.CardReadRequest or CardCommandType.CardWriteRequest)
+ {
+ cardRequestType.Throw().IfOutOfRange();
+ }
+
+ request.Data = WebUtility.UrlDecode(request.Data);
+ var result = ServiceResult.Failed(ServiceError.DefaultError);
+ switch (cardCommandType)
+ {
+ case CardCommandType.CardReadRequest:
+ {
+ switch (cardRequestType)
+ {
+ case CardRequestType.ReadCard:
+ break;
+ case CardRequestType.ReadCardDetail:
+ break;
+ case CardRequestType.ReadCardDetails:
+ break;
+ case CardRequestType.ReadCardBData:
+ break;
+ case CardRequestType.ReadAvatar:
+ break;
+ case CardRequestType.ReadItem:
+ break;
+ case CardRequestType.ReadSkin:
+ break;
+ case CardRequestType.ReadTitle:
+ break;
+ case CardRequestType.ReadMusic:
+ break;
+ case CardRequestType.ReadEventReward:
+ break;
+ case CardRequestType.ReadNavigator:
+ break;
+ case CardRequestType.ReadMusicExtra:
+ break;
+ case CardRequestType.ReadMusicAou:
+ break;
+ case CardRequestType.ReadCoin:
+ break;
+ case CardRequestType.ReadUnlockReward:
+ break;
+ case CardRequestType.ReadUnlockKeynum:
+ break;
+ case CardRequestType.ReadSoundEffect:
+ break;
+ case CardRequestType.ReadGetMessage:
+ break;
+ case CardRequestType.ReadCond:
+ break;
+ case CardRequestType.ReadTotalTrophy:
+ break;
+ case CardRequestType.GetSession:
+ break;
+ case CardRequestType.StartSession:
+ break;
+ case CardRequestType.WriteCard:
+
+ break;
+ case CardRequestType.WriteCardDetail:
+ break;
+ case CardRequestType.WriteCardBData:
+ break;
+ case CardRequestType.WriteAvatar:
+ break;
+ case CardRequestType.WriteItem:
+ break;
+ case CardRequestType.WriteTitle:
+ break;
+ case CardRequestType.WriteMusicDetail:
+ break;
+ case CardRequestType.WriteNavigator:
+ break;
+ case CardRequestType.WriteCoin:
+ break;
+ case CardRequestType.WriteSkin:
+ break;
+ case CardRequestType.WriteUnlockKeynum:
+ break;
+ case CardRequestType.WriteSoundEffect:
+ break;
+ case CardRequestType.StartOnlineMatching:
+ break;
+ case CardRequestType.UpdateOnlineMatching:
+ break;
+ case CardRequestType.UploadOnlineMatchingResult:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(message: "Should not happen", paramName:null);
+ }
+ break;
+ }
+ case CardCommandType.CardWriteRequest:
+ break;
+ case CardCommandType.RegisterRequest:
+ result = await Mediator.Send(new CardRegisterCommand(request.CardId, request.Data));
+ break;
+ case CardCommandType.ReissueRequest:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(message: "Should not happen", paramName:null);
+ }
+
+ if (result.Succeeded)
+ {
+ return Ok(result.Data);
+ }
+
+ var errorMessage = $"{(int)CardReturnCode.Unknown}\n" +
+ $"{result.Error!.Message}";
+ return Ok(errorMessage);
+ }
+}
\ No newline at end of file
diff --git a/MainServer/Filters/ApiExceptionFilterAttributes.cs b/MainServer/Filters/ApiExceptionFilterAttributes.cs
new file mode 100644
index 0000000..9dfe2bf
--- /dev/null
+++ b/MainServer/Filters/ApiExceptionFilterAttributes.cs
@@ -0,0 +1,81 @@
+using System.Diagnostics;
+using Application.Common.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace MainServer.Filters;
+
+///
+/// Exception filters
+///
+public class ApiExceptionFilterService : ExceptionFilterAttribute
+{
+ private readonly IDictionary> exceptionHandlers;
+
+ private readonly ILogger logger;
+
+ ///
+ /// Constructor
+ ///
+ public ApiExceptionFilterService(ILogger logger)
+ {
+ this.logger = logger;
+ // Register known exception types and handlers.
+ exceptionHandlers = new Dictionary>
+ {
+ { typeof(ArgumentOutOfRangeException), HandleArgumentOutOfRangeException }
+ };
+ }
+
+ ///
+ /// On exception event
+ ///
+ ///
+ public override void OnException(ExceptionContext context)
+ {
+ HandleException(context);
+
+ base.OnException(context);
+ }
+
+ private void HandleException(ExceptionContext context)
+ {
+ var type = context.Exception.GetType();
+ if (exceptionHandlers.ContainsKey(type))
+ {
+ exceptionHandlers[type].Invoke(context);
+ return;
+ }
+
+ HandleUnknownException(context);
+ }
+
+ private static void HandleUnknownException(ExceptionContext context)
+ {
+ var details = ServiceResult.Failed(ServiceError.DefaultError);
+
+ context.Result = new ObjectResult(details)
+ {
+ StatusCode = StatusCodes.Status500InternalServerError
+ };
+
+ context.ExceptionHandled = true;
+ }
+
+ private void HandleArgumentOutOfRangeException(ExceptionContext context)
+ {
+ logger.LogError(context.Exception, "");
+ var exception = context.Exception as ArgumentOutOfRangeException;
+ Debug.Assert(exception != null, nameof(exception) + " != null");
+
+ var variable = exception.ParamName ?? "Unknown";
+ var details = ServiceResult.Failed(ServiceError.CustomMessage($"Argument {variable} out of bounds!"));
+
+ context.Result = new ObjectResult(details)
+ {
+ StatusCode = StatusCodes.Status400BadRequest
+ };
+
+ context.ExceptionHandled = true;
+ }
+}
\ No newline at end of file
diff --git a/MainServer/MainServer.csproj b/MainServer/MainServer.csproj
index 3bab9c5..b0f561c 100644
--- a/MainServer/MainServer.csproj
+++ b/MainServer/MainServer.csproj
@@ -43,6 +43,7 @@
+
diff --git a/MainServer/Program.cs b/MainServer/Program.cs
index bbc7643..d493464 100644
--- a/MainServer/Program.cs
+++ b/MainServer/Program.cs
@@ -5,6 +5,7 @@ using Domain.Config;
using Infrastructure;
using Infrastructure.Common;
using Infrastructure.Persistence;
+using MainServer.Filters;
using Microsoft.EntityFrameworkCore;
using Serilog;
using Serilog.Extensions.Logging;
@@ -38,6 +39,8 @@ try
builder.Configuration.GetSection(EventConfig.EVENT_SECTION));
builder.Services.Configure(
builder.Configuration.GetSection(RelayConfig.RELAY_SECTION));
+ builder.Services.Configure(
+ builder.Configuration.GetSection(GameConfig.GAME_SECTION));
var serverIp = builder.Configuration["ServerIp"] ?? "127.0.0.1";
var certificateManager = new CertificateService(serverIp, new SerilogLoggerFactory(Log.Logger).CreateLogger(""));
@@ -51,7 +54,9 @@ try
configuration.WriteTo.Console().ReadFrom.Configuration(context.Configuration);
});
- builder.Services.AddControllers().AddXmlSerializerFormatters();
+ builder.Services.AddControllers(options =>
+ options.Filters.Add());
+
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
@@ -80,21 +85,23 @@ try
var eventService = app.Services.GetService();
eventService.ThrowIfNull();
eventService.InitializeEvents();
-
- // Configure the HTTP request pipeline.
+
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
+ // app.UseExceptionHandler();
+ app.UseStaticFiles();
+
app.MapControllers();
app.Run();
}
catch (Exception ex)
{
- Log.Fatal(ex, "Unhandled exception");
+ Log.Fatal(ex, "Unhandled exception in startup");
}
finally
{