From 10f804fa3f58a93f3b3ea4c4b83cf32ee79fa85e Mon Sep 17 00:00:00 2001 From: Kayori Date: Tue, 29 Sep 2026 09:23:05 +0200 Subject: [PATCH] Fix Sppass which uses GET instead of POST --- Core/Client/src/data/authenticationService.ts | 6 +---- Core/Server/Api/CardlessApi.cs | 15 ++++-------- .../Handlers/Boot/GetServicesHandler.cs | 8 +++---- .../Common/KeepAlivePcbTrackerHandler.cs | 23 +++++++++++++++++++ .../Middlewares/BodyParsingMiddleware.cs | 22 ++++++++++++------ .../Response/KeepAlivePcbTrackerResponse.cs | 10 ++++++++ .../Models/Response/SppassLookupResponse.cs | 17 +++++++------- .../Models/Response/SppassOpenResponse.cs | 17 +++++++------- Core/Server/Program.cs | 14 ++++++++--- 9 files changed, 86 insertions(+), 46 deletions(-) create mode 100644 Core/Server/Handlers/Common/KeepAlivePcbTrackerHandler.cs create mode 100644 Core/Server/Models/Response/KeepAlivePcbTrackerResponse.cs diff --git a/Core/Client/src/data/authenticationService.ts b/Core/Client/src/data/authenticationService.ts index 2d8e790..71fc190 100644 --- a/Core/Client/src/data/authenticationService.ts +++ b/Core/Client/src/data/authenticationService.ts @@ -40,11 +40,7 @@ export class AuthenticationService { store.setRefreshToken(tokenResponse.refreshToken); store.setLoggedIn(true); - if (await UserService.GetUserClaims()) { - return true; - } - - return false; + return await UserService.GetUserClaims(); } static async login( diff --git a/Core/Server/Api/CardlessApi.cs b/Core/Server/Api/CardlessApi.cs index 8b66eb0..11e071c 100644 --- a/Core/Server/Api/CardlessApi.cs +++ b/Core/Server/Api/CardlessApi.cs @@ -45,17 +45,10 @@ public static class CardlessApi return TypedResults.BadRequest(new ApproveSessionResponse { Success = false, Message = "No card registered on this account yet." }); } - // `cardType` has no value confirmed directly from sppass itself - - // decompiling SOUND VOLTEX's sppass module only shows the client - // requiring it be non-empty and parse as base-10, never comparing it - // against a real value. "4" is borrowed from KAMUNITY.AvsTypes.CardType - // (MCARD=0, ICCARD=1, FELICA=2, VIRTUAL=4), KONAMI's shared platform - // card-type enum used identically by two other arcade titles' decompiled - // C# (Polaris Chord, Chase Chase Jokers) - VIRTUAL is the platform's own - // name for "no physical card," an exact semantic match for cardless - // login. Not proven that sppass reuses this enum, so if the cabinet - // rejects it, that's the first assumption to revisit. - var approved = sessionService.TryApprove(token, "4", card.RawId); + // Confirmed against decompiled Assembly-CSharp: CardEntryScene casts + // sppass.lookup's card_type straight into (MCARD=0, + // ICCARD=1, FELICA=2, VIRTUAL=4, CARDLESS=5) and sets Account.IsCardless=true + var approved = sessionService.TryApprove(token, "5", card.RawId); if (!approved) { diff --git a/Core/Server/Handlers/Boot/GetServicesHandler.cs b/Core/Server/Handlers/Boot/GetServicesHandler.cs index f94e25f..8e1f268 100644 --- a/Core/Server/Handlers/Boot/GetServicesHandler.cs +++ b/Core/Server/Handlers/Boot/GetServicesHandler.cs @@ -32,12 +32,12 @@ namespace Server.Handlers.Boot Mode = "operation", Status = 0 }; - + var coreServices = new string[]{ "cardmng", "facility", "message", "numbering", "package", "pcbevent", "pcbtracker", "pkglist", "posevent", "userdata", "userid", "eacoin", "dlstatus", "netlog", "info", "reference", "sidmgr", "local", "local2", "lobby", "slocal", "slocal2", "sglocal", "sglocal2", "lab", "globby", - "slobby", "sglobby", "eacharge" + "slobby", "sglobby", "eacharge", "sppass" }; foreach(var service in coreServices) @@ -48,7 +48,7 @@ namespace Server.Handlers.Boot Url = $"{CommonUrl}/{service}" }); } - + services.Items.Add(new ServiceItem() { Name = "ntp", @@ -60,7 +60,7 @@ namespace Server.Handlers.Boot Name = "keepalive", Url = "http://127.0.0.1:8083/keepalive?pa=127.0.0.1&ia=127.0.0.1&ga=127.0.0.1&ma=127.0.0.1&t1=2&t2=10" }); - + return services; } diff --git a/Core/Server/Handlers/Common/KeepAlivePcbTrackerHandler.cs b/Core/Server/Handlers/Common/KeepAlivePcbTrackerHandler.cs new file mode 100644 index 0000000..b90fdea --- /dev/null +++ b/Core/Server/Handlers/Common/KeepAlivePcbTrackerHandler.cs @@ -0,0 +1,23 @@ +using Abstractions.Handlers; +using Server.Models.Response; + +namespace Server.Handlers.Common; + +public class KeepAlivePcbTrackerHandler : HandlerWithoutRequest +{ + public override void Configure() + { + Module("pcbtracker"); + Method("keepalive"); + } + + public override KeepAlivePcbTrackerResponse Handle(GameModel model) + { + var pcbEvent = new KeepAlivePcbTrackerResponse() + { + Status = 0 + }; + + return pcbEvent; + } +} diff --git a/Core/Server/Middlewares/BodyParsingMiddleware.cs b/Core/Server/Middlewares/BodyParsingMiddleware.cs index 60482ed..0de0e12 100644 --- a/Core/Server/Middlewares/BodyParsingMiddleware.cs +++ b/Core/Server/Middlewares/BodyParsingMiddleware.cs @@ -16,13 +16,22 @@ public class BodyParsingMiddleware(RequestDelegate next) public async Task Invoke(HttpContext context) { - if(context.Request.Method == "GET" || !context.Request.Path.ToString().Contains("eamuse", StringComparison.CurrentCultureIgnoreCase)) + var method = context.Request.Method; + var path = context.Request.Path.ToString(); + + if (!( + (method == "GET" && path.Contains("sppass", StringComparison.InvariantCultureIgnoreCase)) || + (method == "POST" && path.Contains("eamuse", StringComparison.InvariantCultureIgnoreCase)) + )) { await _next(context); return; } - var body = await ParseRequest(context); + var token = context.Request.Query["token"].ToString(); + var body = method == "GET" && !string.IsNullOrEmpty(token) + ? new XDocument(new XElement("call", new XElement("sppass", new XAttribute("token", token)))) + : await ParseRequest(context); context.Items["Encoding"] = body.Declaration?.Encoding; var bodyString = body.ToString(); @@ -41,10 +50,10 @@ public class BodyParsingMiddleware(RequestDelegate next) { var isCompressed = context.Request.Headers["X-Compress"].ToString().Contains("lz77"); var info = context.Request.Headers["X-Eamuse-Info"].FirstOrDefault(); - var contentLength = context.Request.Headers.ContentLength ?? 0; - var data = new byte[(int)contentLength]; - await context.Request.Body.ReadExactlyAsync(data.AsMemory(0, (int)contentLength)); + using var bodyStream = new MemoryStream(); + await context.Request.Body.CopyToAsync(bodyStream); + var data = bodyStream.ToArray(); if(info is not null) { @@ -73,7 +82,7 @@ public class BodyParsingMiddleware(RequestDelegate next) } catch(Exception e) { - //Console.WriteLine(e); + //Console.WriteLine(e); var testData = Encoding.ASCII.GetString(data); if (!string.IsNullOrEmpty(testData)) @@ -83,7 +92,6 @@ public class BodyParsingMiddleware(RequestDelegate next) context.Request.Headers.Append("IsEncoded", "false"); } } - //Data is now xml in konami binary form return returnData; } diff --git a/Core/Server/Models/Response/KeepAlivePcbTrackerResponse.cs b/Core/Server/Models/Response/KeepAlivePcbTrackerResponse.cs new file mode 100644 index 0000000..d3c6fba --- /dev/null +++ b/Core/Server/Models/Response/KeepAlivePcbTrackerResponse.cs @@ -0,0 +1,10 @@ +using System.Xml.Serialization; + +namespace Server.Models.Response; + +[XmlRoot("pcbtracker")] +public class KeepAlivePcbTrackerResponse +{ + [XmlAttribute("status")] + public required int Status { get; set; } +} diff --git a/Core/Server/Models/Response/SppassLookupResponse.cs b/Core/Server/Models/Response/SppassLookupResponse.cs index b43c6b2..29c681c 100644 --- a/Core/Server/Models/Response/SppassLookupResponse.cs +++ b/Core/Server/Models/Response/SppassLookupResponse.cs @@ -1,4 +1,5 @@ using System.Xml.Serialization; +using Abstractions.SerializationTypes; namespace Server.Models.Response; @@ -8,15 +9,15 @@ public class SppassLookupResponse [XmlAttribute("status")] public required int Status { get; set; } - [XmlAttribute("url")] - public required string Url { get; set; } + [XmlElement("url")] + public required XrpcString Url { get; set; } - [XmlAttribute("interval")] - public required int Interval { get; set; } + [XmlElement("interval")] + public required XrpcInt Interval { get; set; } - [XmlAttribute("card_type")] - public string CardType { get; set; } = ""; + [XmlElement("card_type")] + public XrpcString CardType { get; set; } = ""; - [XmlAttribute("card_id")] - public string CardId { get; set; } = ""; + [XmlElement("card_id")] + public XrpcString CardId { get; set; } = ""; } diff --git a/Core/Server/Models/Response/SppassOpenResponse.cs b/Core/Server/Models/Response/SppassOpenResponse.cs index df65ee7..1795f2c 100644 --- a/Core/Server/Models/Response/SppassOpenResponse.cs +++ b/Core/Server/Models/Response/SppassOpenResponse.cs @@ -1,4 +1,5 @@ using System.Xml.Serialization; +using Abstractions.SerializationTypes; namespace Server.Models.Response; @@ -8,15 +9,15 @@ public class SppassOpenResponse [XmlAttribute("status")] public required int Status { get; set; } - [XmlAttribute("token")] - public required string Token { get; set; } + [XmlElement("token")] + public required XrpcString Token { get; set; } - [XmlAttribute("expire_datetime")] - public required string ExpireDatetime { get; set; } + [XmlElement("expire_datetime")] + public required XrpcString ExpireDatetime { get; set; } - [XmlAttribute("url")] - public required string Url { get; set; } + [XmlElement("url")] + public required XrpcString Url { get; set; } - [XmlAttribute("interval")] - public required int Interval { get; set; } + [XmlElement("interval")] + public required XrpcInt Interval { get; set; } } diff --git a/Core/Server/Program.cs b/Core/Server/Program.cs index fdeb6de..69d4eda 100644 --- a/Core/Server/Program.cs +++ b/Core/Server/Program.cs @@ -195,6 +195,15 @@ eamuseGroup.MapPost("/", ([FromQuery] string model, [FromQuery] string? module, return HandleEAmuseRoute(amusementRequest, httpContext, logger, handlerService, pluginService); }); +eamuseGroup.MapGet("/{module}/{model}/{module2}/{method}", (string module, string model, string module2, string method, + HttpContext httpContext, [FromServices] ILogger logger, [FromServices] IHandlerService handlerService, + [FromServices] IPluginService pluginService) => +{ + var amusementRequest = new AmusementRequest() { Model = model, Module = module ?? "", Method = method ?? "" }; + + return HandleEAmuseRoute(amusementRequest, httpContext, logger, handlerService, pluginService); +}); + var graphqlMap = app.MapGraphQL(); app.MapFallbackToFile("/index.html"); @@ -230,11 +239,10 @@ async Task HandleEAmuseRoute(AmusementRequest amusementRequest, HttpCon Console.WriteLine(httpContext.Request.Headers.UserAgent); // Enable buffering to allow multiple reads of the request body httpContext.Request.EnableBuffering(); - var body = ""; // The body is 932 encoded xml - if (httpContext.Request.Body.Length != 0) + string body; + using (var reader = new StreamReader(httpContext.Request.Body, Encoding.GetEncoding(932), false, 1024, true)) { - using var reader = new StreamReader(httpContext.Request.Body, Encoding.GetEncoding(932), false, 1024, true); body = await reader.ReadToEndAsync(); }