178 lines
7.1 KiB
C#
178 lines
7.1 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using NotPogo.Core;
|
|
using NotPogo.Model;
|
|
using System.IO.Compression;
|
|
using System.Net;
|
|
using System.Reflection.Metadata.Ecma335;
|
|
using System.Text;
|
|
|
|
namespace NotPogo.Controllers
|
|
{
|
|
[Route("/")]
|
|
public class ConfigAndAssetsController(ILogger<ConfigAndAssetsController> logger) : Controller
|
|
{
|
|
private readonly ILogger<ConfigAndAssetsController> _logger = logger;
|
|
|
|
private static readonly string ConfigsRoot = Path.Combine(AppContext.BaseDirectory, "StaticFiles", "Configs");
|
|
private static readonly string MasterPropsPath = Path.Combine(ConfigsRoot, "master.properties");
|
|
|
|
public IActionResult Index() => Ok("Not Pogo Is Running!");
|
|
|
|
// might aswell provide a minimal way to play the games supported
|
|
[HttpGet]
|
|
[Route("/play-game")]
|
|
public IActionResult GamePage(string? name,
|
|
string game,
|
|
string patchName,
|
|
string appletClass,
|
|
string site,
|
|
string locale,
|
|
string hostedGameClass,
|
|
string scrn = null!, // user will get a username assigned if screenName is not set
|
|
bool isSingleApplet = true)
|
|
{
|
|
string baseUrl = $"{Request.Scheme}://{Request.Host.Value}";
|
|
|
|
// Games like waterwheel/bowling ship with isSingleApplet=true: the compiled game
|
|
// code draws its own chat panel as a CHILD COMPONENT of GameHostApplet -- confirmed
|
|
// via a real log: "GameHostApplet-ChatPanel,584,0,192x408", a subcomponent positioned
|
|
// at x=584 inside one 776-wide canvas, not a second applet instance. Giving that
|
|
// applet anything narrower than 776 clips its self-drawn chat panel off-screen,
|
|
// which is exactly the "chat doesn't show up" bug -- so single-applet mode has to
|
|
// stay one 776-wide <applet> tag with no second one alongside it.
|
|
int gameWidth = isSingleApplet ? 776 : 564;
|
|
|
|
string lkey = Sessions.Issue(scrn); // since there isn't a database
|
|
|
|
var gameApplet = new AppletDefinition
|
|
{
|
|
Name = "game",
|
|
Code = appletClass,
|
|
Codebase = baseUrl,
|
|
Archive = $"{baseUrl}/v/19.1.0.25/applet/{game}/{game}-en_US.jar",
|
|
Width = gameWidth,
|
|
Height = 506,
|
|
Parameters = new()
|
|
{
|
|
{ "name", game },
|
|
{ "hosted.game.class", hostedGameClass ?? "" },
|
|
{ "standalone", "false" },
|
|
{ "type", game },
|
|
{ "config", $"client-props.jsp?game={game}" },
|
|
{ "locale", locale },
|
|
{ "site", site },
|
|
{ "rkey", "" },
|
|
{ "unid", "" },
|
|
{ "scrn", scrn! },
|
|
{ "sourceid", "" },
|
|
{ "launchPage", "" },
|
|
{ "lkey", lkey },
|
|
{ "cnid", "" },
|
|
{ "isSingleApplet", isSingleApplet ? "true" : "false" },
|
|
{ "browserInfo", "" },
|
|
{ "debug", "false" },
|
|
{ "fullscreen.enabled", "false" },
|
|
{ "fullscreen.supported", "false" }
|
|
}
|
|
};
|
|
|
|
var applets = new List<AppletDefinition> { gameApplet };
|
|
|
|
if (!isSingleApplet)
|
|
{
|
|
// Only for games actually captured running the split layout (e.g. Lottso
|
|
// Bingo's combined-game-chat.html) -- a genuinely separate ShellApplet instance,
|
|
// packaged in the same jar, params matching that capture exactly: name, ugifts,
|
|
// applet-version, and notably NO "config" param. How it gets its properties
|
|
// without one is still an open question carried over from earlier research
|
|
// (possibly LiveConnect/JS, possibly it reads what the game applet already
|
|
// loaded) -- untested here.
|
|
applets.Add(new AppletDefinition
|
|
{
|
|
Name = "chat",
|
|
Code = "com.pogo.game.client2.shell.ShellApplet",
|
|
Codebase = baseUrl,
|
|
Archive = $"{baseUrl}/v/19.1.0.25/applet/{game}/{game}-en_US.jar",
|
|
Width = 212,
|
|
Height = 506,
|
|
Parameters = new()
|
|
{
|
|
{ "name", "chat" },
|
|
{ "ugifts", "0" },
|
|
{ "applet-version", "19.1.0.25" }
|
|
}
|
|
});
|
|
}
|
|
|
|
var gamePage = new GamePage
|
|
{
|
|
GameName = name ?? game,
|
|
InternalName = game,
|
|
PatchName = patchName,
|
|
Applets = applets
|
|
};
|
|
|
|
return View(gamePage);
|
|
}
|
|
|
|
[HttpGet]
|
|
[Route("/blank.html")]
|
|
public IActionResult GetEmptyPage() => Ok();
|
|
|
|
[HttpGet]
|
|
[Route("/client-props.jsp")]
|
|
public async Task<IActionResult> GetConfigAsync(string game)
|
|
{
|
|
string merged = await BuildMergedPropertiesAsync(game);
|
|
|
|
// client props needs to be gzip compressed for whatever reason (probably to throw off dumpers or something idk)
|
|
byte[] compressed;
|
|
using (var ms = new MemoryStream())
|
|
{
|
|
using (var gzip = new GZipStream(ms, CompressionLevel.Fastest, leaveOpen: true))
|
|
{
|
|
await gzip.WriteAsync(Encoding.UTF8.GetBytes(merged));
|
|
}
|
|
compressed = ms.ToArray();
|
|
}
|
|
|
|
return File(compressed, "text/plain");
|
|
}
|
|
|
|
private async Task<string> BuildMergedPropertiesAsync(string file)
|
|
{
|
|
var props = new Dictionary<string, string>();
|
|
|
|
if (System.IO.File.Exists(MasterPropsPath))
|
|
await LoadPropertiesIntoAsync(MasterPropsPath, props);
|
|
else
|
|
_logger.LogWarning("Master properties file missing at {Path}", MasterPropsPath);
|
|
|
|
var gamePropsPath = Path.Combine(ConfigsRoot, file + ".properties");
|
|
if (System.IO.File.Exists(gamePropsPath))
|
|
await LoadPropertiesIntoAsync(gamePropsPath, props);
|
|
else
|
|
_logger.LogWarning("Game-specific properties file missing at {Path}", gamePropsPath);
|
|
|
|
var sb = new StringBuilder();
|
|
foreach (var (key, value) in props)
|
|
sb.Append(key).Append('=').Append(value).Append('\n');
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static async Task LoadPropertiesIntoAsync(string path, Dictionary<string, string> into)
|
|
{
|
|
foreach (var rawLine in await System.IO.File.ReadAllLinesAsync(path))
|
|
{
|
|
var line = rawLine.Trim();
|
|
if (line.Length == 0 || line.StartsWith('#')) continue;
|
|
|
|
int idx = line.IndexOf('=');
|
|
if (idx <= 0) continue;
|
|
|
|
into[line[..idx].Trim()] = line[(idx + 1)..];
|
|
}
|
|
}
|
|
}
|
|
}
|