// /flashservices/gateway endpoint for roomsel.swf (com.pogo.roomsel.RoomTabs). // Register in Program.cs: app.MapFlashGateway(); namespace NotPogo.Amf; public static class FlashGateway { // "Service.method" -> handler(args). Args arrive as AMF0 values: strings, doubles, etc. static readonly Dictionary, object?>> Handlers = new() { // RoomTabs.getRoomFeed -> generateFeed(gameCode, siteCode) ["com.pogo.flash.FlashRoomsFeed.generateFeed"] = args => { string gameCode = Str(args, 0), siteCode = Str(args, 1); return new { errorMsg = (string?)null, // non-null = SWF shows the error tabs = new[] { new { tabId = 1, index = 0 } }, rooms = new[] { // shown only if tabId == selected tab AND ruleId == gRuleId (omit ruleId if noruleid) // clickable only if numPeople < softCapacity && live new { key = "wr_classic_1", humanName = "NotPogo WR Classic", numPeople = 0, softCapacity = 50, live = true, tabId = 1, ruleId = "classic" }, new { key = "wr_rally_1", humanName = "NotPogo WR Rally", numPeople = 0, softCapacity = 50, live = true, tabId = 1, ruleId = "rally" }, new { key = "wr_advanced_1", humanName = "NotPogo WR Expert", numPeople = 0, softCapacity = 50, live = true, tabId = 1, ruleId = "advanced" }, }, rules = Array.Empty(), // stored, never read by the SWF ruleTabs = new[] { new { id = "classic", index = 0 }, new { id = "rally", index = 1 }, new { id = "advanced", index = 2 }, }, // [{ id = , index = n }] -> ruleIdx for Play Now }; }, // RoomTabs.goRoom -> connectRoom(roomKey, gameCode, siteCode, lkey) ["com.pogo.flash.FlashRoomConnect.connectRoom"] = args => RoomInfo(Str(args, 0)), // RoomTabs.playNow -> playNow(gameCode, siteCode, lkey, tabIdx, ruleIdx) ["com.pogo.flash.FlashRoomConnect.playNow"] = args => RoomInfo("wr_room_1"), // pick a room server-side }; // roomInfo becomes FSCommand goRoom "humanName '..' key '..' host '..' port '..'"; the exe connects TCP to host:port static object RoomInfo(string roomKey) => new { errorMsg = (string?)null, // null or "NO ERROR" = success roomInfo = new { humanName = "NotPogo Room", key = roomKey, host = "downld.pogo.com", port = 11200 }, }; public static void MapFlashGateway(this IEndpointRouteBuilder app) { app.MapPost("/flashservices/gateway", async (HttpContext ctx, ILoggerFactory lf) => { var log = lf.CreateLogger("FlashGateway"); using var ms = new MemoryStream(); await ctx.Request.Body.CopyToAsync(ms); var request = AmfPacket.Read(ms.ToArray()); var reply = new AmfPacket { Version = request.Version }; // Flash may batch several calls into one POST; answer each, echoing its response URI. foreach (var msg in request.Messages) { var args = msg.Body as List ?? new List(); log.LogInformation("AMF call {Target}({Args}) -> {Response}", msg.Target, string.Join(", ", args), msg.Response); try { if (!Handlers.TryGetValue(msg.Target, out var handler)) throw new InvalidOperationException($"No such service method: {msg.Target}"); reply.Messages.Add(new AmfMessage(msg.Response + "/onResult", "null", handler(args))); } catch (Exception ex) { log.LogWarning(ex, "AMF call {Target} failed", msg.Target); // mx.remoting turns this into a Fault; "description" becomes faultstring reply.Messages.Add(new AmfMessage(msg.Response + "/onStatus", "null", new { level = "error", code = "SERVER.PROCESSING", description = ex.Message, details = "", type = ex.GetType().Name, })); } } var bytes = reply.Write(); ctx.Response.ContentType = "application/x-amf"; ctx.Response.ContentLength = bytes.Length; await ctx.Response.Body.WriteAsync(bytes); }); } static string Str(List a, int i) => i < a.Count ? a[i]?.ToString() ?? "" : ""; }