Stuff works
This commit is contained in:
@@ -10,10 +10,16 @@ public class Viewer : BaseEntity<ulong>
|
||||
/// <summary>
|
||||
/// This user's name displayed in game.
|
||||
/// </summary>
|
||||
public string DisplayName { get; set; }
|
||||
public string DisplayName { get; set; } = String.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// This user's short identifier.
|
||||
/// </summary>
|
||||
public ulong ShortUdid { get; set; }
|
||||
|
||||
#region Navigation Properties
|
||||
|
||||
public List<SocialAccountConnection> SocialAccountConnections { get; set; } = new List<SocialAccountConnection>();
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -5,4 +5,5 @@ namespace SVSim.Database.Repositories.Viewer;
|
||||
public interface IViewerRepository
|
||||
{
|
||||
Task<Models.Viewer?> GetViewerBySocialConnection(SocialAccountType accountType, ulong socialId);
|
||||
Task<Models.Viewer?> GetViewerWithSocials(ulong id);
|
||||
}
|
||||
@@ -15,10 +15,16 @@ public class ViewerRepository : IViewerRepository
|
||||
|
||||
public async Task<Models.Viewer?> GetViewerBySocialConnection(SocialAccountType accountType, ulong socialId)
|
||||
{
|
||||
return _dbContext.Set<SocialAccountConnection>()
|
||||
return (await _dbContext.Set<SocialAccountConnection>()
|
||||
.AsNoTracking()
|
||||
.Include(sac => sac.Viewer)
|
||||
.FirstOrDefault(sac => sac.AccountType == accountType && sac.AccountId == socialId)
|
||||
.FirstOrDefaultAsync(sac => sac.AccountType == accountType && sac.AccountId == socialId))
|
||||
?.Viewer;
|
||||
}
|
||||
|
||||
public async Task<Models.Viewer?> GetViewerWithSocials(ulong id)
|
||||
{
|
||||
return await _dbContext.Set<Models.Viewer>().AsNoTracking().Include(viewer => viewer.SocialAccountConnections)
|
||||
.FirstOrDefaultAsync(viewer => viewer.Id == id);
|
||||
}
|
||||
}
|
||||
@@ -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<CheckController> logger)
|
||||
public CheckController(ILogger<CheckController> logger, IViewerRepository viewerRepository)
|
||||
{
|
||||
_logger = logger;
|
||||
_viewerRepository = viewerRepository;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("special_title")]
|
||||
public async Task<DataWrapper<SpecialTitleCheckResponse>> SpecialTitleCheck(SpecialTitleCheckRequest request)
|
||||
public async Task<SpecialTitleCheckResponse> SpecialTitleCheck(SpecialTitleCheckRequest request)
|
||||
{
|
||||
int titleId = Random.Shared.Next(8, 33);
|
||||
var res = new DataWrapper<SpecialTitleCheckResponse>
|
||||
{
|
||||
Data = new SpecialTitleCheckResponse
|
||||
var res = new SpecialTitleCheckResponse
|
||||
{
|
||||
TitleImageId = titleId,
|
||||
TitleSoundId = titleId
|
||||
},
|
||||
DataHeaders = new DataHeaders
|
||||
{
|
||||
ShortUdid = 411054851,
|
||||
ViewerId = 906243102,
|
||||
Sid = string.Empty,
|
||||
Servertime = DateTime.UtcNow.Ticks,
|
||||
ResultCode = 1
|
||||
}
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
[HttpPost("game_start")]
|
||||
public async Task<DataWrapper<GameStartResponse>> GameStart(GameStartRequest request)
|
||||
public async Task<GameStartResponse> GameStart(GameStartRequest request)
|
||||
{
|
||||
return new DataWrapper<GameStartResponse>()
|
||||
{
|
||||
DataHeaders = new DataHeaders
|
||||
{
|
||||
ShortUdid = 411054851,
|
||||
ViewerId = 906243102,
|
||||
Sid = string.Empty,
|
||||
Servertime = DateTime.UtcNow.Ticks,
|
||||
ResultCode = 1
|
||||
},
|
||||
Data = new GameStartResponse()
|
||||
Viewer? viewer = await _viewerRepository.GetViewerWithSocials(HttpContext.GetViewer().Id);
|
||||
return new GameStartResponse()
|
||||
{
|
||||
IsSetTransitionPassword = true,
|
||||
KorAuthorityId = default,
|
||||
KorAuthorityState = default,
|
||||
NowRank = new Dictionary<string, string>()
|
||||
{
|
||||
{"1", "RankName_010"},
|
||||
{"2", "RankName_010"},
|
||||
{"4", "RankName_017"}
|
||||
{ "1", "RankName_010" },
|
||||
{ "2", "RankName_010" },
|
||||
{ "4", "RankName_017" }
|
||||
},
|
||||
NowName = "combusty7",
|
||||
NowName = viewer.DisplayName,
|
||||
PolicyState = default,
|
||||
PolicyId = default,
|
||||
NowTutorialStep = "100",
|
||||
NowViewerId = 906243102,
|
||||
NowViewerId = viewer.Id,
|
||||
TosId = default,
|
||||
TosState = default,
|
||||
TransitionAccountData = new List<TransitionAccountData>()
|
||||
TransitionAccountData = viewer.SocialAccountConnections.Select(sac => new TransitionAccountData
|
||||
{
|
||||
new TransitionAccountData()
|
||||
{
|
||||
ConnectedViewerId = "906243102",
|
||||
SocialAccountType = "5",
|
||||
SocialAccountId = "76561197970830305"
|
||||
}
|
||||
}
|
||||
}
|
||||
ConnectedViewerId = viewer.Id.ToString(),
|
||||
SocialAccountId = sac.AccountId.ToString(),
|
||||
SocialAccountType = ((int)sac.AccountType).ToString()
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
24
SVSim.EmulatedEntrypoint/Extensions/HttpContextExtensions.cs
Normal file
24
SVSim.EmulatedEntrypoint/Extensions/HttpContextExtensions.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -62,7 +65,9 @@ public class ShadowverseTranslationMiddleware : IMiddleware
|
||||
|
||||
await next.Invoke(context);
|
||||
|
||||
// Convert the response into a messagepack, encrypt it
|
||||
Viewer? viewer = context.GetViewer();
|
||||
|
||||
// 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<DataWrapper>(wrappedResponseData);
|
||||
packedData = Encryption.Encrypt(packedData, udid);
|
||||
await originalResponsebody.WriteAsync(Encoding.UTF8.GetBytes(Convert.ToBase64String(packedData)));
|
||||
context.Response.Body = originalResponsebody;
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -2,11 +2,21 @@ using MessagePack;
|
||||
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class DataWrapper<T>
|
||||
public class DataWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional data about the request, response and user.
|
||||
/// </summary>
|
||||
[Key("data_headers")]
|
||||
public DataHeaders DataHeaders { get; set; }
|
||||
public DataHeaders DataHeaders { get; set; } = new DataHeaders();
|
||||
|
||||
/// <summary>
|
||||
/// The response data from the endpoint.
|
||||
/// </summary>
|
||||
[Key("data")]
|
||||
public T Data { get; set; }
|
||||
public object Data { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
|
||||
|
||||
public class IndexRequest : BaseRequest
|
||||
{
|
||||
public string Carrier { get; set; }
|
||||
public string CardMasterHash { get; set; }
|
||||
}
|
||||
@@ -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")]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses;
|
||||
|
||||
public class IndexResponse
|
||||
{
|
||||
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public static class Encryption
|
||||
/// <returns>the decrypted bytes</returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,19 +27,29 @@ public class SteamSessionAuthenticationHandler : AuthenticationHandler<SteamAuth
|
||||
protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
byte[] requestBytes;
|
||||
try
|
||||
{
|
||||
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<BaseRequest>(requestString);
|
||||
|
||||
// Reset request stream
|
||||
Request.Body.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if (requestJson is null)
|
||||
{
|
||||
return AuthenticateResult.Fail("Invalid request body.");
|
||||
@@ -59,8 +70,11 @@ public class SteamSessionAuthenticationHandler : AuthenticationHandler<SteamAuth
|
||||
return AuthenticateResult.Fail("User not found.");
|
||||
}
|
||||
|
||||
// Add viewer to context
|
||||
Context.SetViewer(viewer);
|
||||
|
||||
// Build identity
|
||||
ClaimsIdentity identity = new ClaimsIdentity();
|
||||
ClaimsIdentity identity = new ClaimsIdentity(SteamAuthenticationConstants.SchemeName);
|
||||
identity.AddClaim(new Claim(ClaimTypes.Name, viewer.DisplayName));
|
||||
identity.AddClaim(new Claim(ShadowverseClaimTypes.ShortUdidClaim, viewer.ShortUdid.ToString()));
|
||||
identity.AddClaim(new Claim(ShadowverseClaimTypes.ViewerIdClaim, viewer.Id.ToString()));
|
||||
@@ -68,7 +82,7 @@ public class SteamSessionAuthenticationHandler : AuthenticationHandler<SteamAuth
|
||||
|
||||
// Build and return final ticket
|
||||
AuthenticationTicket ticket =
|
||||
new AuthenticationTicket(new ClaimsPrincipal(), SteamAuthenticationConstants.SchemeName);
|
||||
new AuthenticationTicket(new ClaimsPrincipal(identity), SteamAuthenticationConstants.SchemeName);
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class SteamSessionService : IDisposable
|
||||
var steamCheckResults = SteamServer.BeginAuthSession(ticketBytes.ToArray(), new SteamId { Value = steamId });
|
||||
if (steamCheckResults)
|
||||
{
|
||||
_validatedSessionTickets.TryAdd(ticket, storedSteamId);
|
||||
_validatedSessionTickets.TryAdd(ticket, steamId);
|
||||
}
|
||||
|
||||
return steamCheckResults;
|
||||
|
||||
Reference in New Issue
Block a user