Stuff works

This commit is contained in:
gamer147
2024-09-08 10:27:12 -04:00
parent 7e4bce9ac5
commit ac3b002d74
14 changed files with 157 additions and 87 deletions

View File

@@ -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
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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()
};
}
}

View 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;
}
}

View File

@@ -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;

View File

@@ -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")]

View File

@@ -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();
}

View File

@@ -0,0 +1,7 @@
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Requests;
public class IndexRequest : BaseRequest
{
public string Carrier { get; set; }
public string CardMasterHash { get; set; }
}

View File

@@ -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")]

View File

@@ -0,0 +1,6 @@
namespace SVSim.EmulatedEntrypoint.Models.Dtos.Responses;
public class IndexResponse
{
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;