Need to fix index load issues
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using MessagePack;
|
||||
using MessagePack.Resolvers;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.EmulatedEntrypoint.Constants;
|
||||
using SVSim.EmulatedEntrypoint.Extensions;
|
||||
@@ -23,6 +26,16 @@ public class ShadowverseTranslationMiddleware : IMiddleware
|
||||
private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
|
||||
private readonly ShadowverseSessionService _sessionService;
|
||||
|
||||
// Serialization policy MUST match what AddJsonOptions configured on the controllers, or the
|
||||
// model binder won't find the snake_case keys we write into the synthetic request body and
|
||||
// every request 400s with empty ModelState. WhenWritingNull is irrelevant for request
|
||||
// serialization but kept here for symmetry.
|
||||
private static readonly JsonSerializerOptions ControllerJsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
public ShadowverseTranslationMiddleware(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, ShadowverseSessionService sessionService)
|
||||
{
|
||||
_actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
|
||||
@@ -58,9 +71,12 @@ public class ShadowverseTranslationMiddleware : IMiddleware
|
||||
|
||||
// Decrypt incoming data.
|
||||
requestBytes = Encryption.Decrypt(requestBytes, udid);
|
||||
object? data = MessagePackSerializer.Deserialize(endpointDescriptor.Parameters.FirstOrDefault().ParameterType,
|
||||
requestBytes);
|
||||
string json = JsonConvert.SerializeObject(data);
|
||||
Type requestType = endpointDescriptor.Parameters.FirstOrDefault().ParameterType;
|
||||
object? data = MessagePackSerializer.Deserialize(requestType, requestBytes);
|
||||
// Re-serialize via System.Text.Json with the SAME options the controllers use, so the
|
||||
// model binder sees snake_case keys it can match. Using JsonConvert here writes the
|
||||
// CLR property names (PascalCase) and every property silently binds to default → 400.
|
||||
string json = JsonSerializer.Serialize(data, requestType, ControllerJsonOptions);
|
||||
StringContent newStream = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
context.Request.Body = newStream.ReadAsStream();
|
||||
context.Request.Headers.ContentType = new StringValues("application/json");
|
||||
@@ -68,19 +84,20 @@ public class ShadowverseTranslationMiddleware : IMiddleware
|
||||
await next.Invoke(context);
|
||||
|
||||
Viewer? viewer = context.GetViewer();
|
||||
|
||||
// Grab the response object
|
||||
Type responseType = ((ControllerActionDescriptor)endpointDescriptor).MethodInfo.ReturnType;
|
||||
if (responseType.IsGenericType && responseType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
responseType = responseType.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
// Read the controller's JSON response body. System.Text.Json was configured with
|
||||
// SnakeCaseLower + WhenWritingNull, so the JSON keys are already in the wire shape and
|
||||
// null/optional properties have been omitted. Parse to a JToken tree to preserve that
|
||||
// "absent vs null" information — going back through a typed DTO via JsonConvert would
|
||||
// re-introduce nulls for missing properties and they'd reach the client as msgpack Nil.
|
||||
using MemoryStream responseBytesStream = new MemoryStream();
|
||||
context.Response.Body.Seek(0, SeekOrigin.Begin);
|
||||
await context.Response.Body.CopyToAsync(responseBytesStream);
|
||||
byte[] responseBytes = responseBytesStream.ToArray();
|
||||
object? responseData = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(responseBytes), responseType);
|
||||
|
||||
string responseJson = Encoding.UTF8.GetString(responseBytesStream.ToArray());
|
||||
object? responseData = string.IsNullOrEmpty(responseJson)
|
||||
? null
|
||||
: ConvertJsonTreeToPlainObject(JToken.Parse(responseJson));
|
||||
|
||||
// Wrap the response in a datawrapper
|
||||
DataWrapper wrappedResponseData = new DataWrapper
|
||||
{
|
||||
@@ -88,8 +105,14 @@ public class ShadowverseTranslationMiddleware : IMiddleware
|
||||
DataHeaders = new DataHeaders
|
||||
{
|
||||
Servertime = DateTime.UtcNow.Ticks,
|
||||
Sid =
|
||||
context.Request.Headers[NetworkConstants.SessionIdHeaderName].FirstOrDefault(),
|
||||
// SID intentionally empty. See docs/api-spec/common/envelope.md §"SID
|
||||
// rotation" — the client's SessionId is a hash-on-read property, so echoing
|
||||
// the request's SID poisons its backing field and the next request hashes
|
||||
// the hash, missing our SID→UDID dict and crashing decryption. To rotate
|
||||
// sessions in the future, use the "stable-prefix + counter" pattern from
|
||||
// that doc (Option B), and pre-hash the rotated value to index the map by
|
||||
// what the client will actually send back on the next request.
|
||||
Sid = "",
|
||||
// TODO error handling
|
||||
ResultCode = 1,
|
||||
ShortUdid = viewer.ShortUdid,
|
||||
@@ -97,10 +120,39 @@ public class ShadowverseTranslationMiddleware : IMiddleware
|
||||
}
|
||||
};
|
||||
|
||||
// Convert the response into a messagepack, encrypt it
|
||||
byte[] packedData = MessagePackSerializer.Serialize<DataWrapper>(wrappedResponseData);
|
||||
// Convert the response into a messagepack, encrypt it. ContractlessStandardResolver
|
||||
// walks the DataWrapper's typed properties (DataHeaders) AND the boxed object/list/
|
||||
// primitive tree under Data — emitting only the keys present in the dictionary.
|
||||
var msgPackOptions = MessagePackSerializerOptions.Standard
|
||||
.WithResolver(ContractlessStandardResolver.Instance);
|
||||
byte[] packedData = MessagePackSerializer.Serialize(wrappedResponseData, msgPackOptions);
|
||||
packedData = Encryption.Encrypt(packedData, udid);
|
||||
await originalResponsebody.WriteAsync(Encoding.UTF8.GetBytes(Convert.ToBase64String(packedData)));
|
||||
context.Response.Body = originalResponsebody;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walks a parsed JSON tree into the plain CLR shape MessagePack-CSharp's contractless
|
||||
/// resolver understands: objects → <c>Dictionary<string, object?></c>, arrays →
|
||||
/// <c>List<object?></c>, scalars unboxed to their nearest primitive. Crucially, JSON
|
||||
/// objects that lacked a key DON'T get one in the dictionary — preserving "absent" as a
|
||||
/// distinct state from "null" all the way to the msgpack writer.
|
||||
/// </summary>
|
||||
internal static object? ConvertJsonTreeToPlainObject(JToken? token)
|
||||
{
|
||||
if (token is null || token.Type == JTokenType.Null) return null;
|
||||
return token.Type switch
|
||||
{
|
||||
JTokenType.Object => token.Children<JProperty>()
|
||||
.ToDictionary(p => p.Name, p => ConvertJsonTreeToPlainObject(p.Value)),
|
||||
JTokenType.Array => token.Children().Select(ConvertJsonTreeToPlainObject).ToList(),
|
||||
JTokenType.Integer => token.Value<long>(),
|
||||
JTokenType.Float => token.Value<double>(),
|
||||
JTokenType.String => token.Value<string>(),
|
||||
JTokenType.Boolean => token.Value<bool>(),
|
||||
JTokenType.Date => token.Value<DateTime>(),
|
||||
JTokenType.Bytes => token.Value<byte[]>(),
|
||||
_ => token.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user