Radicade Public Code

This commit is contained in:
JogmansonReal
2026-10-16 23:33:13 -07:00
commit 55175acdf6
832 changed files with 363261 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
using System.Text.Json;
using RadicadeServer.RecNet.Enums;
namespace RadicadeServer.RecNet.VersionCheck
{
public static class GameUpdateVersionCheck
{
public const string AppVersionUpdateNotificationId = "AppVersionUpdate";
public const string AnnouncementConfigKey = "Announcement";
public const string GameUpdateType = "Game Update";
public const int UpdateStage = 1;
public static VersionCheckResult Resolve(string? announcementJson, DateTime nowUtc)
=> HasLiveGameUpdate(announcementJson, nowUtc)
? new VersionCheckResult
{
VersionStatus = VersionCheckStatus.UpdateRequired,
UpdateNotificationStage = UpdateStage
}
: new VersionCheckResult { VersionStatus = VersionCheckStatus.ValidForPlay };
public static bool HasLiveGameUpdate(string? announcementJson, DateTime nowUtc)
{
if (string.IsNullOrWhiteSpace(announcementJson)) return false;
try
{
using var doc = JsonDocument.Parse(announcementJson);
if (doc.RootElement.ValueKind != JsonValueKind.Array) return false;
foreach (var row in doc.RootElement.EnumerateArray())
{
if (row.ValueKind != JsonValueKind.Object) continue;
if (!row.TryGetProperty("type", out var type) || type.ValueKind != JsonValueKind.String) continue;
if (!string.Equals(type.GetString(), GameUpdateType, StringComparison.OrdinalIgnoreCase)) continue;
if (row.TryGetProperty("expires_at", out var expires) && expires.ValueKind == JsonValueKind.String
&& DateTime.TryParse(expires.GetString(), out var expiry) && expiry <= nowUtc)
continue;
return true;
}
}
catch
{
}
return false;
}
}
}
+157
View File
@@ -0,0 +1,157 @@
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace RadicadeServer.API.Launcher
{
[Route("api/launcher")]
public class LauncherBuildAPI : Controller
{
private static readonly string LauncherDirectory = Path.GetFullPath("./Launcher");
private static readonly string LauncherManifestPath = Path.Join(LauncherDirectory, "manifest.json");
private static async Task<JsonObject?> LoadLauncherManifest()
{
if (!System.IO.File.Exists(LauncherManifestPath))
return null;
var raw = await System.IO.File.ReadAllTextAsync(LauncherManifestPath);
return JsonNode.Parse(raw)?.AsObject();
}
private static string? GetString(JsonObject manifest, string key)
{
return manifest[key]?.GetValue<string>();
}
private static string? GetNestedString(JsonObject manifest, string parentKey, string childKey)
{
return manifest[parentKey]?[childKey]?.GetValue<string>();
}
private static bool GetBool(JsonObject manifest, string key, bool fallback = false)
{
return manifest[key]?.GetValue<bool>() ?? fallback;
}
private static IActionResult MissingManifestFile()
{
return new JsonResult(new
{
error = "Launcher manifest file was not found.",
path = LauncherManifestPath
})
{
StatusCode = StatusCodes.Status404NotFound
};
}
private static IActionResult InvalidManifestFile(string message, object? extra = null)
{
return new JsonResult(new
{
error = message,
path = LauncherManifestPath,
extra
})
{
StatusCode = StatusCodes.Status503ServiceUnavailable
};
}
[HttpGet("approved-build")]
public async Task<IActionResult> GetApprovedBuild()
{
if (!System.IO.File.Exists(LauncherManifestPath))
return MissingManifestFile();
try
{
var raw = await System.IO.File.ReadAllTextAsync(LauncherManifestPath);
var manifest = JsonNode.Parse(raw)?.AsObject();
if (manifest == null)
return InvalidManifestFile("Launcher manifest file is not a JSON object.");
var gameVersion = GetString(manifest, "version");
var manifestVersion = GetString(manifest, "manifestVersion");
var gameDownloadUrl = GetNestedString(manifest, "package", "url");
var packageSha256 = GetNestedString(manifest, "package", "sha256");
if (string.IsNullOrWhiteSpace(gameVersion) ||
string.IsNullOrWhiteSpace(manifestVersion) ||
string.IsNullOrWhiteSpace(gameDownloadUrl) ||
string.IsNullOrWhiteSpace(packageSha256))
{
return InvalidManifestFile(
"Approved build metadata is incomplete.",
new
{
missing = new[]
{
string.IsNullOrWhiteSpace(gameVersion) ? "version" : null,
string.IsNullOrWhiteSpace(manifestVersion) ? "manifestVersion" : null,
string.IsNullOrWhiteSpace(gameDownloadUrl) ? "package.url" : null,
string.IsNullOrWhiteSpace(packageSha256) ? "package.sha256" : null
}.Where(x => x != null).ToArray()
});
}
return Ok(new
{
gameVersion,
manifestVersion,
gameDownloadUrl,
packageSha256,
required = GetBool(manifest, "required", true),
launchTicket = GetString(manifest, "launchTicket"),
launchTicketRequired = GetBool(manifest, "launchTicketRequired"),
launchTicketUrl = GetString(manifest, "launchTicketUrl"),
launchTicketAudience = GetString(manifest, "launchTicketAudience"),
launchTicketNonce = GetString(manifest, "launchTicketNonce")
});
}
catch (JsonException ex)
{
return InvalidManifestFile("Launcher manifest file contains invalid JSON.", new { details = ex.Message });
}
}
[HttpGet("manifest.json")]
public async Task<IActionResult> GetManifest()
{
if (!System.IO.File.Exists(LauncherManifestPath))
return MissingManifestFile();
try
{
var raw = await System.IO.File.ReadAllTextAsync(LauncherManifestPath);
var manifest = JsonNode.Parse(raw)?.AsObject();
if (manifest == null)
return InvalidManifestFile("Launcher manifest file is not a JSON object.");
var version = GetString(manifest, "version");
var packageUrl = GetNestedString(manifest, "package", "url");
if (string.IsNullOrWhiteSpace(version) || string.IsNullOrWhiteSpace(packageUrl))
{
return InvalidManifestFile(
"Launcher manifest file is missing required fields.",
new
{
missing = new[]
{
string.IsNullOrWhiteSpace(version) ? "version" : null,
string.IsNullOrWhiteSpace(packageUrl) ? "package.url" : null
}.Where(x => x != null).ToArray()
});
}
return Content(raw, "application/json");
}
catch (JsonException ex)
{
return InvalidManifestFile("Launcher manifest file contains invalid JSON.", new { details = ex.Message });
}
}
}
}
+39
View File
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RadicadeServer.DB;
using RadicadeServer.RecNet.Enums;
using RadicadeServer.RecNet.VersionCheck;
namespace RadicadeServer.API
{
[Route("api/versioncheck/")]
public class VersionCheckAPI(RecDBContext db) : Controller
{
[HttpGet("v4")]
public async Task<IActionResult> Get([FromQuery] string? v, [FromQuery] string? version)
{
var requested = v ?? version;
var query = HttpContext?.Request?.QueryString.Value ?? "";
var announcement = await db.ServerConfig.AsNoTracking()
.FirstOrDefaultAsync(x => x.Key == GameUpdateVersionCheck.AnnouncementConfigKey);
var result = GameUpdateVersionCheck.Resolve(announcement?.Value, DateTime.UtcNow);
return Ok(BuildClientResponse(result));
}
[HttpGet("islandedversions")]
public IActionResult GetIslandedVersions()
{
return Ok(Array.Empty<string>());
}
private static Dictionary<string, object> BuildClientResponse(VersionCheckResult result) => new()
{
["ValidVersion"] = 0,
["VersionStatus"] = (int)result.VersionStatus,
["UpdateNotificationStage"] = result.UpdateNotificationStage,
["IsVersionIslanded"] = result.IsVersionIslanded,
["IsCrossPlayDisabled"] = result.IsCrossPlayDisabled,
};
}
}
+12
View File
@@ -0,0 +1,12 @@
using RadicadeServer.RecNet.Enums;
namespace RadicadeServer.RecNet.VersionCheck
{
public class VersionCheckResult
{
public VersionCheckStatus VersionStatus { get; set; } = VersionCheckStatus.ValidForPlay;
public int UpdateNotificationStage { get; set; } = 0;
public bool IsVersionIslanded { get; set; } = false;
public bool IsCrossPlayDisabled { get; set; } = false;
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace RadicadeServer.RecNet.Enums
{
public enum VersionCheckStatus
{
ValidForPlay = 0,
UpdateRequired = 1,
ValidForMenu = 2
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"version": "1.0.0",
"manifestVersion": "2026.05.11.1",
"required": true,
"notes": "First ever build for the launcher!",
"package": {
"url": "https://drive.usercontent.google.com/download?id=1aNCUeiA0binZhzSZimSgRdF3mPPk0fTx&export=download&authuser=0&confirm=t&uuid=27b3743c-3fa2-4a4a-a1c7-bf566deed31e&at=ALBwUgmyJGAV7jJWbByy0qM6-4dX:1778546735465",
"sha256": "EEBCC7940CE78CAA3F85916E793F150D2149FA051690A064BF232E931EC47DD3",
"size": 5565413113,
"extractSubdirectory": "Radicade"
},
"launchTicket": "",
"launchTicketRequired": false,
"launchTicketUrl": "",
"launchTicketAudience": "",
"launchTicketNonce": "",
"delete": []
}