ReboundEmulator/Amf/FlashGateway.cs
Moonbase f6e304929f Implement Stubbed CDL Endpoints
Listen On All Addreses Port 80 Alongside 8080 (remove if you cannot launch the emulator)
2026-10-05 13:20:50 -07:00

99 lines
4.7 KiB
C#

// /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<string, Func<List<object?>, 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<object>(), // stored, never read by the SWF
ruleTabs = new[]
{
new { id = "classic", index = 0 },
new { id = "rally", index = 1 },
new { id = "advanced", index = 2 },
}, // [{ id = <ruleId>, 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<object?> ?? new List<object?>();
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<object?> a, int i) => i < a.Count ? a[i]?.ToString() ?? "" : "";
}