Fix Sppass which uses GET instead of POST

This commit is contained in:
Kayori
2026-09-29 09:23:05 +02:00
parent 3f5eff30da
commit 10f804fa3f
9 changed files with 86 additions and 46 deletions
@@ -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(
+4 -11
View File
@@ -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)
{
@@ -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;
}
@@ -0,0 +1,23 @@
using Abstractions.Handlers;
using Server.Models.Response;
namespace Server.Handlers.Common;
public class KeepAlivePcbTrackerHandler : HandlerWithoutRequest<KeepAlivePcbTrackerResponse>
{
public override void Configure()
{
Module("pcbtracker");
Method("keepalive");
}
public override KeepAlivePcbTrackerResponse Handle(GameModel model)
{
var pcbEvent = new KeepAlivePcbTrackerResponse()
{
Status = 0
};
return pcbEvent;
}
}
@@ -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;
}
@@ -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; }
}
@@ -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; } = "";
}
@@ -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; }
}
+11 -3
View File
@@ -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<Program> 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<IResult> 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();
}