132 lines
5.7 KiB
C#
132 lines
5.7 KiB
C#
// /flashservices/gateway endpoint for roomsel.swf (com.pogo.roomsel.RoomTabs).
|
|
// Register in Program.cs: app.MapFlashGateway();
|
|
using NotPogo.Amf;
|
|
|
|
using NotPogo;
|
|
using NotPogo.Core.Rooms;
|
|
|
|
namespace NotPogo;
|
|
|
|
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<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);
|
|
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<object>(), // 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<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() ?? "" : "";
|
|
}
|