// /flashservices/gateway endpoint for roomsel.swf (com.pogo.roomsel.RoomTabs). // Register in Program.cs: app.MapFlashGateway(); using ReboundEmulator.Amf; using ReboundEmulator.Core.Rooms; namespace ReboundEmulator; public static class FlashGateway { static string _gameHost = "127.0.0.1"; static int _gamePort = 11200; static string[] RuleOrder(string gameCode) => [.. RoomManager.ForGame(gameCode) // all Word Riot rooms .Select(r => r.RuleId) // "classic", "rally", "advanced", "classic", ... .Distinct() // each mode once .OrderBy(r => r, StringComparer.Ordinal)]; // "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); var rooms = RoomManager.ForGame(gameCode).ToArray(); return new { errorMsg = (string?)null, // non-null = SWF shows the error tabs = rooms.Select(r => r.TabId).Distinct() .OrderBy(t => t) .Select((tabId, i) => new { tabId, index = i }) .ToArray(), rooms = rooms.Select(r => new { key = r.Key, humanName = r.HumanName, ruleId = r.RuleId, tabId = r.TabId, numPeople = r.PlayerCount, softCapacity = r.SoftCapacity, live = r.Live, }).ToArray(), rules = Array.Empty(), // stored, never read by the SWF ruleTabs = RuleOrder(gameCode).Select((id, i) => new { id, index = i }) }; }, // RoomTabs.goRoom -> connectRoom(roomKey, gameCode, siteCode, lkey) ["com.pogo.flash.FlashRoomConnect.connectRoom"] = args => RoomInfo(RoomManager.Find(Str(args, 0))), // RoomTabs.playNow -> playNow(gameCode, siteCode, lkey, tabIdx, ruleIdx) ["com.pogo.flash.FlashRoomConnect.playNow"] = args => { string gameCode = Str(args, 0); int ruleIdx = args.Count > 4 && args[4] is double d ? (int)d : -1; var rules = RuleOrder(gameCode); string? ruleId = ruleIdx >= 0 && ruleIdx < rules.Length ? rules[ruleIdx] : null; var pick = RoomManager.ForGame(gameCode) .Where(r => r.IsJoinable && (ruleId is null || r.RuleId == ruleId)) .OrderByDescending(r => r.PlayerCount) // fill busy rooms first .FirstOrDefault(); return RoomInfo(pick, notFound: "No rooms are available right now."); } }; // roomInfo becomes FSCommand goRoom "humanName '..' key '..' host '..' port '..'"; the exe connects TCP to host:port static object RoomInfo(Room? room, string notFound = "That room no longer exists.") { if (room is null) return new { errorMsg = notFound }; if (!room.IsJoinable) return new { errorMsg = "That room is full." }; return new { errorMsg = (string?)null, roomInfo = new { humanName = room.HumanName, key = room.Key, host = _gameHost, port = _gamePort }, }; } public static void MapFlashGateway(this WebApplication app) { _gameHost = app.Configuration.GetValue("GameServerHost", "127.0.0.1")!; _gamePort = app.Configuration.GetValue("GameServerPort", 11200); 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(); 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) { // 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() ?? "" : ""; }