Fix PASELI recharge config crash and profile/schema mismatches

- GetConfigEaChargeHandler: was declared as Handler<EmptyRequest, T>,
  which made HandlerService attempt to XML-deserialize an EmptyRequest
  against the incoming <eacharge> root, crashing on every getconf call.
  Switched to HandlerWithoutRequest<T>, matching every other
  parameterless handler (e.g. SppassOpenHandler).

- ChargeMachinePlugin.DoesProfileExist: was a synchronous Func<bool>,
  but PluginService.DoesProfileExistAsync casts the DynamicInvoke result
  to Task<bool>, throwing InvalidCastException on every card inquire.
  Now returns Task.FromResult(true) - a PASELI profile always exists
  once a user is created, so this plugin never needs to report "no
  profile" here.

- EaChargeConfigResponse: dropped the multibills/readagreement fields
  entirely and fixed articlelist's XML shape. A real getconf response
  (captured via spice2x's automap against the genuine Konami server)
  has neither field at all, and wraps articlelist entries in <item>
  children rather than repeating sibling <articlelist> elements. The
  denomination-button limitation this was chasing turned out to be an
  unrelated spice2x bill-validator emulation gap (see spice2x issue
  #123), not anything server-side.

- GetConfigEaChargeHandler: DOCMT_* article types now send an empty
  articledoc, matching the real server - the disclosure/agreement text
  is rendered client-side from a bundled asset, not delivered over the
  wire. Sending real text there produced a solid black screen on a
  real cabinet instead of the disclosure text.

Claude-Session: https://claude.ai/code/session_012FiU5Qe8fbePFxraxWHSd5
This commit is contained in:
Kayori
2026-09-06 23:54:15 +02:00
parent 12fbeb84f0
commit 68b77bb9c3
3 changed files with 22 additions and 21 deletions
@@ -18,7 +18,7 @@ public class ChargeMachinePlugin : IMedusaPlugin
public int? MaxVer => null;
public Encoding? ForcedEncoding => null;
public PluginUiManifest? UiManifest => null;
public Delegate DoesProfileExist => () => false;
public Delegate DoesProfileExist => () => Task.FromResult(true);
public Task OnBuilderInitialize(WebApplicationBuilder builder) => Task.CompletedTask;
@@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore;
namespace MedusaPluginChargeMachine.Handlers.LA9;
public class GetConfigEaChargeHandler(ChargeMachineDbContext db) : Handler<EmptyRequest, EaChargeConfigResponse>
public class GetConfigEaChargeHandler(ChargeMachineDbContext db) : HandlerWithoutRequest<EaChargeConfigResponse>
{
public override void Configure()
{
@@ -14,15 +14,23 @@ public class GetConfigEaChargeHandler(ChargeMachineDbContext db) : Handler<Empty
Method("getconf");
}
public override async Task<EaChargeConfigResponse> HandleAsync(EmptyRequest _, GameModel model)
public override async Task<EaChargeConfigResponse> HandleAsync(GameModel model)
{
var articles = await db.ArticleContents.ToListAsync();
// Real server ("automap" capture against services.tablanbass.net) sends
// an EMPTY articledoc for every DOCMT_* type - the disclosure/agreement
// text itself is apparently rendered client-side from a bundled local
// asset, not delivered over the wire. Sending real text for DOCMT_*
// here produced a solid black content area on a real cabinet (working
// touch, live server) instead of the disclosure text, so match the
// real server's (empty) behavior instead of guessing at content.
var articleList = articles
.Select(a => new EaChargeConfigChargeArticleItem
{
ArticleType = new XrpcString(a.ArticleType.ToString()),
ArticleDoc = new XrpcString(a.ArticleDoc)
ArticleDoc = new XrpcString(
a.ArticleType.ToString().StartsWith("DOCMT_") ? "" : a.ArticleDoc)
})
.ToList();
@@ -30,8 +38,6 @@ public class GetConfigEaChargeHandler(ChargeMachineDbContext db) : Handler<Empty
{
Status = 0,
Expire = 1200,
MultiBills = new XrpcInt(1),
ReadAgreement = new XrpcInt(0),
ChargeArticle = new EaChargeConfigChargeArticle
{
ArticleRevision = new XrpcString("1"),
@@ -9,22 +9,16 @@ public class EaChargeConfigResponse
{
[XmlAttribute("status")]
public required int Status { get; set; }
[XmlAttribute("expire")]
public required int Expire { get; set; }
[XmlElement("multibills")]
public XrpcInt MultiBills { get; set; }
[XmlElement("readagreement")]
public XrpcInt ReadAgreement { get; set; }
[XmlElement("chargearticle")]
public required EaChargeConfigChargeArticle ChargeArticle { get; set; }
[XmlElement("chargedetail")]
public required EaChargeConfigChargeDetail ChargeDetail { get; set; }
[XmlElement("chargestatus")]
public required EaChargeConfigChargeStatus ChargeStatus { get; set; }
}
@@ -33,8 +27,9 @@ public record EaChargeConfigChargeArticle
{
[XmlElement("articlerevision")]
public required XrpcString ArticleRevision { get; set; }
[XmlElement("articlelist")]
[XmlArray("articlelist")]
[XmlArrayItem("item")]
public required List<EaChargeConfigChargeArticleItem> ArticleList { get; set; }
}
@@ -42,7 +37,7 @@ public record EaChargeConfigChargeArticleItem
{
[XmlElement("articletype")]
public required XrpcString ArticleType { get; set; }
[XmlElement("articledoc")]
public required XrpcString ArticleDoc { get; set; }
}
@@ -51,7 +46,7 @@ public record EaChargeConfigChargeDetail
{
[XmlElement("chargedaily")]
public required XrpcInt ChargeDaily { get; set; }
[XmlElement("chargedailylimit")]
public required XrpcInt ChargeDailyLimit { get; set; }
}
@@ -60,7 +55,7 @@ public record EaChargeConfigChargeStatus
{
[XmlElement("currentcode")]
public required XrpcInt CurrentCode { get; set; }
[XmlElement("lastchargestatus")]
public required XrpcInt LastChargeStatus { get; set; }
}