From ac3b002d744ae3847365cfc92cf78343f71e5bc1 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sun, 8 Sep 2024 10:27:12 -0400 Subject: [PATCH] Stuff works --- SVSim.Database/Models/Viewer.cs | 8 +- .../Repositories/Viewer/IViewerRepository.cs | 1 + .../Repositories/Viewer/ViewerRepository.cs | 14 ++- .../Controllers/CheckController.cs | 85 +++++++------------ .../Extensions/HttpContextExtensions.cs | 24 ++++++ .../ShadowverseTranslationMiddleware.cs | 27 +++++- .../Models/Dtos/DataHeaders.cs | 4 +- .../Models/Dtos/DataWrapper.cs | 20 +++-- .../Models/Dtos/Requests/IndexRequest.cs | 7 ++ .../Dtos/Responses/GameStartResponse.cs | 2 +- .../Models/Dtos/Responses/IndexResponse.cs | 6 ++ .../Security/Encryption.cs | 14 +-- .../SteamSessionAuthenticationHandler.cs | 30 +++++-- .../Services/SteamSessionService.cs | 2 +- 14 files changed, 157 insertions(+), 87 deletions(-) create mode 100644 SVSim.EmulatedEntrypoint/Extensions/HttpContextExtensions.cs create mode 100644 SVSim.EmulatedEntrypoint/Models/Dtos/Requests/IndexRequest.cs create mode 100644 SVSim.EmulatedEntrypoint/Models/Dtos/Responses/IndexResponse.cs diff --git a/SVSim.Database/Models/Viewer.cs b/SVSim.Database/Models/Viewer.cs index 555ac5d..9c73883 100644 --- a/SVSim.Database/Models/Viewer.cs +++ b/SVSim.Database/Models/Viewer.cs @@ -10,10 +10,16 @@ public class Viewer : BaseEntity /// /// This user's name displayed in game. /// - public string DisplayName { get; set; } + public string DisplayName { get; set; } = String.Empty; /// /// This user's short identifier. /// public ulong ShortUdid { get; set; } + + #region Navigation Properties + + public List SocialAccountConnections { get; set; } = new List(); + + #endregion } \ No newline at end of file diff --git a/SVSim.Database/Repositories/Viewer/IViewerRepository.cs b/SVSim.Database/Repositories/Viewer/IViewerRepository.cs index 4acf5e5..f69080a 100644 --- a/SVSim.Database/Repositories/Viewer/IViewerRepository.cs +++ b/SVSim.Database/Repositories/Viewer/IViewerRepository.cs @@ -5,4 +5,5 @@ namespace SVSim.Database.Repositories.Viewer; public interface IViewerRepository { Task GetViewerBySocialConnection(SocialAccountType accountType, ulong socialId); + Task GetViewerWithSocials(ulong id); } \ No newline at end of file diff --git a/SVSim.Database/Repositories/Viewer/ViewerRepository.cs b/SVSim.Database/Repositories/Viewer/ViewerRepository.cs index a404dcf..0bba929 100644 --- a/SVSim.Database/Repositories/Viewer/ViewerRepository.cs +++ b/SVSim.Database/Repositories/Viewer/ViewerRepository.cs @@ -15,10 +15,16 @@ public class ViewerRepository : IViewerRepository public async Task GetViewerBySocialConnection(SocialAccountType accountType, ulong socialId) { - return _dbContext.Set() - .AsNoTracking() - .Include(sac => sac.Viewer) - .FirstOrDefault(sac => sac.AccountType == accountType && sac.AccountId == socialId) + return (await _dbContext.Set() + .AsNoTracking() + .Include(sac => sac.Viewer) + .FirstOrDefaultAsync(sac => sac.AccountType == accountType && sac.AccountId == socialId)) ?.Viewer; } + + public async Task GetViewerWithSocials(ulong id) + { + return await _dbContext.Set().AsNoTracking().Include(viewer => viewer.SocialAccountConnections) + .FirstOrDefaultAsync(viewer => viewer.Id == id); + } } \ No newline at end of file diff --git a/SVSim.EmulatedEntrypoint/Controllers/CheckController.cs b/SVSim.EmulatedEntrypoint/Controllers/CheckController.cs index bf55905..e98b4c1 100644 --- a/SVSim.EmulatedEntrypoint/Controllers/CheckController.cs +++ b/SVSim.EmulatedEntrypoint/Controllers/CheckController.cs @@ -6,6 +6,9 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using SVSim.Database.Models; +using SVSim.Database.Repositories.Viewer; +using SVSim.EmulatedEntrypoint.Extensions; using SVSim.EmulatedEntrypoint.Models.Dtos; using SVSim.EmulatedEntrypoint.Models.Dtos.Requests; using SVSim.EmulatedEntrypoint.Models.Dtos.Responses; @@ -16,79 +19,57 @@ namespace SVSim.EmulatedEntrypoint.Controllers [ApiController] public class CheckController : SVSimController { - private ILogger _logger; + private readonly ILogger _logger; + private readonly IViewerRepository _viewerRepository; - public CheckController(ILogger logger) + public CheckController(ILogger logger, IViewerRepository viewerRepository) { _logger = logger; + _viewerRepository = viewerRepository; } [AllowAnonymous] [HttpPost("special_title")] - public async Task> SpecialTitleCheck(SpecialTitleCheckRequest request) + public async Task SpecialTitleCheck(SpecialTitleCheckRequest request) { int titleId = Random.Shared.Next(8, 33); - var res = new DataWrapper + var res = new SpecialTitleCheckResponse { - Data = new SpecialTitleCheckResponse - { - TitleImageId = titleId, - TitleSoundId = titleId - }, - DataHeaders = new DataHeaders - { - ShortUdid = 411054851, - ViewerId = 906243102, - Sid = string.Empty, - Servertime = DateTime.UtcNow.Ticks, - ResultCode = 1 - } + TitleImageId = titleId, + TitleSoundId = titleId }; return res; } [HttpPost("game_start")] - public async Task> GameStart(GameStartRequest request) + public async Task GameStart(GameStartRequest request) { - return new DataWrapper() + Viewer? viewer = await _viewerRepository.GetViewerWithSocials(HttpContext.GetViewer().Id); + return new GameStartResponse() { - DataHeaders = new DataHeaders + IsSetTransitionPassword = true, + KorAuthorityId = default, + KorAuthorityState = default, + NowRank = new Dictionary() { - ShortUdid = 411054851, - ViewerId = 906243102, - Sid = string.Empty, - Servertime = DateTime.UtcNow.Ticks, - ResultCode = 1 + { "1", "RankName_010" }, + { "2", "RankName_010" }, + { "4", "RankName_017" } }, - Data = new GameStartResponse() + NowName = viewer.DisplayName, + PolicyState = default, + PolicyId = default, + NowTutorialStep = "100", + NowViewerId = viewer.Id, + TosId = default, + TosState = default, + TransitionAccountData = viewer.SocialAccountConnections.Select(sac => new TransitionAccountData { - IsSetTransitionPassword = true, - KorAuthorityId = default, - KorAuthorityState = default, - NowRank = new Dictionary() - { - {"1", "RankName_010"}, - {"2", "RankName_010"}, - {"4", "RankName_017"} - }, - NowName = "combusty7", - PolicyState = default, - PolicyId = default, - NowTutorialStep = "100", - NowViewerId = 906243102, - TosId = default, - TosState = default, - TransitionAccountData = new List() - { - new TransitionAccountData() - { - ConnectedViewerId = "906243102", - SocialAccountType = "5", - SocialAccountId = "76561197970830305" - } - } - } + ConnectedViewerId = viewer.Id.ToString(), + SocialAccountId = sac.AccountId.ToString(), + SocialAccountType = ((int)sac.AccountType).ToString() + }).ToList() }; } } diff --git a/SVSim.EmulatedEntrypoint/Extensions/HttpContextExtensions.cs b/SVSim.EmulatedEntrypoint/Extensions/HttpContextExtensions.cs new file mode 100644 index 0000000..8e5f4f7 --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Extensions/HttpContextExtensions.cs @@ -0,0 +1,24 @@ +using SVSim.Database.Models; + +namespace SVSim.EmulatedEntrypoint.Extensions; + +public static class HttpContextExtensions +{ + private const string ViewerItemName = "SVSimViewer"; + + public static Viewer? GetViewer(this HttpContext context) + { + if (context.Items.TryGetValue(ViewerItemName, out object? viewer)) + { + return viewer as Viewer; + } + + return null; + } + + public static Viewer SetViewer(this HttpContext context, Viewer viewer) + { + context.Items[ViewerItemName] = viewer; + return viewer; + } +} \ No newline at end of file diff --git a/SVSim.EmulatedEntrypoint/Middlewares/ShadowverseTranslationMiddleware.cs b/SVSim.EmulatedEntrypoint/Middlewares/ShadowverseTranslationMiddleware.cs index cd15527..c26231f 100644 --- a/SVSim.EmulatedEntrypoint/Middlewares/ShadowverseTranslationMiddleware.cs +++ b/SVSim.EmulatedEntrypoint/Middlewares/ShadowverseTranslationMiddleware.cs @@ -4,7 +4,10 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; +using SVSim.Database.Models; using SVSim.EmulatedEntrypoint.Constants; +using SVSim.EmulatedEntrypoint.Extensions; +using SVSim.EmulatedEntrypoint.Models.Dtos; using SVSim.EmulatedEntrypoint.Security; using SVSim.EmulatedEntrypoint.Services; @@ -61,8 +64,10 @@ public class ShadowverseTranslationMiddleware : IMiddleware context.Request.Headers.ContentType = new StringValues("application/json"); await next.Invoke(context); + + Viewer? viewer = context.GetViewer(); - // Convert the response into a messagepack, encrypt it + // Grab the response object var responseType = ((ControllerActionDescriptor)endpointDescriptor).MethodInfo.ReturnType; if (responseType.IsGenericType && responseType.GetGenericTypeDefinition() == typeof(Task<>)) { @@ -73,7 +78,25 @@ public class ShadowverseTranslationMiddleware : IMiddleware await context.Response.Body.CopyToAsync(responseBytesStream); var responseBytes = responseBytesStream.ToArray(); var responseData = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(responseBytes), responseType); - var packedData = MessagePackSerializer.Serialize(responseType, responseData); + + // Wrap the response in a datawrapper + DataWrapper wrappedResponseData = new DataWrapper + { + Data = responseData, + DataHeaders = new DataHeaders + { + Servertime = DateTime.UtcNow.Ticks, + Sid = + context.Request.Headers[NetworkConstants.SessionIdHeaderName].FirstOrDefault(), + // TODO error handling + ResultCode = 1, + ShortUdid = viewer.ShortUdid, + ViewerId = viewer.Id + } + }; + + // Convert the response into a messagepack, encrypt it + var packedData = MessagePackSerializer.Serialize(wrappedResponseData); packedData = Encryption.Encrypt(packedData, udid); await originalResponsebody.WriteAsync(Encoding.UTF8.GetBytes(Convert.ToBase64String(packedData))); context.Response.Body = originalResponsebody; diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/DataHeaders.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/DataHeaders.cs index 4408525..548bba3 100644 --- a/SVSim.EmulatedEntrypoint/Models/Dtos/DataHeaders.cs +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/DataHeaders.cs @@ -6,9 +6,9 @@ namespace SVSim.EmulatedEntrypoint.Models.Dtos; public class DataHeaders { [Key("short_udid")] - public int ShortUdid { get; set; } + public ulong ShortUdid { get; set; } [Key("viewer_id")] - public int ViewerId { get; set; } + public ulong ViewerId { get; set; } [Key("sid")] public string Sid { get; set; } [Key("servertime")] diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/DataWrapper.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/DataWrapper.cs index 5dd8881..73bab6a 100644 --- a/SVSim.EmulatedEntrypoint/Models/Dtos/DataWrapper.cs +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/DataWrapper.cs @@ -2,11 +2,21 @@ using MessagePack; namespace SVSim.EmulatedEntrypoint.Models.Dtos; +/// +/// Wraps responses in the format the official game client expects, with a header section for additional data. Not for manual endpoint use, this wrapping is done automatically in a middleware. +/// [MessagePackObject] -public class DataWrapper +public class DataWrapper { - [Key("data_headers")] - public DataHeaders DataHeaders { get; set; } - [Key("data")] - public T Data { get; set; } + /// + /// Additional data about the request, response and user. + /// + [Key("data_headers")] + public DataHeaders DataHeaders { get; set; } = new DataHeaders(); + + /// + /// The response data from the endpoint. + /// + [Key("data")] + public object Data { get; set; } = new(); } \ No newline at end of file diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/IndexRequest.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/IndexRequest.cs new file mode 100644 index 0000000..92cf9d2 --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Requests/IndexRequest.cs @@ -0,0 +1,7 @@ +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests; + +public class IndexRequest : BaseRequest +{ + public string Carrier { get; set; } + public string CardMasterHash { get; set; } +} \ No newline at end of file diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/GameStartResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/GameStartResponse.cs index f0b5e78..1672b42 100644 --- a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/GameStartResponse.cs +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/GameStartResponse.cs @@ -6,7 +6,7 @@ namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses; public class GameStartResponse { [Key("now_viewer_id")] - public long NowViewerId { get; set; } + public ulong NowViewerId { get; set; } [Key("is_set_transition_password")] public bool IsSetTransitionPassword { get; set; } [Key("now_name")] diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/IndexResponse.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/IndexResponse.cs new file mode 100644 index 0000000..7a3a2f4 --- /dev/null +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/Responses/IndexResponse.cs @@ -0,0 +1,6 @@ +namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses; + +public class IndexResponse +{ + +} \ No newline at end of file diff --git a/SVSim.EmulatedEntrypoint/Security/Encryption.cs b/SVSim.EmulatedEntrypoint/Security/Encryption.cs index de849d3..fda903b 100644 --- a/SVSim.EmulatedEntrypoint/Security/Encryption.cs +++ b/SVSim.EmulatedEntrypoint/Security/Encryption.cs @@ -58,7 +58,7 @@ public static class Encryption /// the decrypted bytes public static byte[] Decrypt(byte[] encryptedData, string udId) { - using (var rj = new RijndaelManaged()) + using (var rj = Aes.Create()) { rj.KeySize = EncryptionKeySize; rj.Mode = EncryptionMode; @@ -71,16 +71,8 @@ public static class Encryption Array.Copy(encryptedData, 0, encryptedValueBytes, 0, encryptedValueBytes.Length); ICryptoTransform transform = rj.CreateDecryptor(keyBytes, rgbIv); byte[] decryptedValueBytes = new byte[encryptedValueBytes.Length]; - using (MemoryStream ms = new MemoryStream(encryptedValueBytes)) - { - using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Read)) - { - cs.CopyTo(decryptedValueBytes); - cs.Flush(); - ms.Flush(); - } - } - return decryptedValueBytes; + rj.Key = keyBytes; + return rj.DecryptCbc(encryptedValueBytes, rgbIv); } } diff --git a/SVSim.EmulatedEntrypoint/Security/SteamSessionAuthentication/SteamSessionAuthenticationHandler.cs b/SVSim.EmulatedEntrypoint/Security/SteamSessionAuthentication/SteamSessionAuthenticationHandler.cs index 532a893..2c3b800 100644 --- a/SVSim.EmulatedEntrypoint/Security/SteamSessionAuthentication/SteamSessionAuthenticationHandler.cs +++ b/SVSim.EmulatedEntrypoint/Security/SteamSessionAuthentication/SteamSessionAuthenticationHandler.cs @@ -8,6 +8,7 @@ using SVSim.Database.Enums; using SVSim.Database.Models; using SVSim.Database.Repositories.Viewer; using SVSim.EmulatedEntrypoint.Constants; +using SVSim.EmulatedEntrypoint.Extensions; using SVSim.EmulatedEntrypoint.Models.Dtos.Requests; using SVSim.EmulatedEntrypoint.Services; @@ -26,18 +27,28 @@ public class SteamSessionAuthenticationHandler : AuthenticationHandler HandleAuthenticateAsync() { byte[] requestBytes; - using (var requestBytesStream = new MemoryStream()) + try { - await Request.Body.CopyToAsync(requestBytesStream); - requestBytes = requestBytesStream.ToArray(); + using (var requestBytesStream = new MemoryStream()) + { + // Reset request stream + Request.Body.Seek(0, SeekOrigin.Begin); + + await Request.Body.CopyToAsync(requestBytesStream); + requestBytes = requestBytesStream.ToArray(); + + // Reset request stream + Request.Body.Seek(0, SeekOrigin.Begin); + } + } + catch (Exception e) + { + return AuthenticateResult.Fail("Failed to read request body."); } // Convert bytes to json string requestString = Encoding.UTF8.GetString(requestBytes); BaseRequest? requestJson = JsonConvert.DeserializeObject(requestString); - - // Reset request stream - Request.Body.Seek(0, SeekOrigin.Begin); if (requestJson is null) { @@ -58,9 +69,12 @@ public class SteamSessionAuthenticationHandler : AuthenticationHandler