mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-27 07:59:03 -07:00
Radicade Public Code
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace RadicadeServer.RecNet.Enums
|
||||
{
|
||||
public enum VersionCheckStatus
|
||||
{
|
||||
ValidForPlay = 0,
|
||||
UpdateRequired = 1,
|
||||
ValidForMenu = 2
|
||||
}
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
Reference in New Issue
Block a user