Platform/Menu-FN: Overhaul of game update handling. New Nuclide specific API to query update package states that avoids string comparisons and much more.

This commit is contained in:
Marco Cawthorne 2023-08-18 17:49:10 -07:00
parent 8422ddd26b
commit 412c8f984e
Signed by: eukara
GPG Key ID: CE2032F0A2882A22
10 changed files with 645 additions and 229 deletions

View File

@ -958,7 +958,7 @@ msgid "IDS_CONFIGURE_GOREHELP"
msgstr "Disable visuals inappropriate for younger players and multiplayer."
msgid "IDS_CONFIGURE_AUTOPATCHHELP"
msgstr "Download the latest version of %s."
msgstr "Download updates for %s."
msgid "IDS_CHAT_NOSERVERS"
msgstr "Could not locate any Frag-Net servers."
@ -1652,3 +1652,33 @@ msgstr "Do you want to uncompress the files for game '%s'?"
msgid "IDS_FAVSVRS_CORRUPT"
msgstr "The server data file favsvrs.dat appears to be corrupt.\n\nYou can request a new list of servers by pressing the Update button.\n\nDo you want to remove the corrupt file (you will have to re-enter your 'favorites' if you remove the file)?"
msgid "UPDATE_DISABLED"
msgstr "Disabled"
msgid "UPDATE_ENABLED"
msgstr "Enabled"
msgid "UPDATE_CORRUPT"
msgstr "Corrupt"
msgid "UPDATE_NOTINSTALLED"
msgstr "Not installed"
msgid "UPDATE_PENDING_INSTALL"
msgstr "Install (pending)"
msgid "UPDATE_PENDING_REINSTALL"
msgstr "Reinstall (pending)"
msgid "UPDATE_PENDING_UNINSTALL"
msgstr "Uninstall (pending)"
msgid "UPDATE_PENDING_AUTOINSTALL"
msgstr "Auto-install (pending)"
msgid "UPDATE_PENDING_DISABLE"
msgstr "Disable (pending)"
msgid "UPDATE_PENDING_RETAIN"
msgstr "Retain (pending)"

View File

@ -138,7 +138,6 @@ m_init(void)
Colors_Init();
Strings_Init();
Updates_Init();
if (GameLibrary_GetInfo(GAMEINFO_GAMEDIR) != "valve") {
m_intro_skip();

View File

@ -38,19 +38,19 @@ up_btndone_start(void)
cvar_set("menu_updating", "0");
localsound("../media/launch_dnmenu1.wav");
g_menupage = PAGE_CONFIGURATION;
localcmd("seta menu_installedpackages 1;cfg_save\n");
localcmd("pkg revert;seta menu_installedpackages 1;cfg_save\n");
}
void
up_btninstall_start(void)
{
Updates_Remove(up_lbUpdates.GetSelected());
Updates_Install(up_lbUpdates.GetSelected());
}
void
up_btnremove_start(void)
{
Updates_Install(up_lbUpdates.GetSelected());
Updates_Destroy(up_lbUpdates.GetSelected());
}
void
@ -76,7 +76,7 @@ up_lbupdates_changed(void)
if (pkgid == -1)
return;
newpic = sprintf(FN_UPDATE_IMGURL, updates[pkgid].name);
newpic = Updates_GetInfo(pkgid, UPDATE_PREVIEWIMAGE);
if not (newpic)
return;
@ -94,7 +94,6 @@ up_sbupdates_changed(int val)
void
menu_updates_refresh(void)
{
Updates_Refresh();
int updateCount = Updates_GetPackageCount();
up_sbUpdates.SetMax(updateCount);
@ -119,19 +118,19 @@ menu_updates_init(void)
up_btnApply = spawn(CMainButton);
up_btnApply.SetImage(BTN_UPDATE);
up_btnApply.SetExecute(up_btnapply_start);
up_btnApply.SetPos(350+96,420+30);
up_btnApply.SetPos(50 + 160,420+13);
Widget_Add(fn_updates, up_btnApply);
up_btnInstall = spawn(CMainButton);
up_btnInstall.SetImage(BTN_INSTALL);
up_btnInstall.SetExecute(up_btninstall_start);
up_btnInstall.SetPos(350,420);
up_btnInstall.SetPos(380,400);
Widget_Add(fn_updates, up_btnInstall);
up_btnDelete = spawn(CMainButton);
up_btnDelete.SetImage(BTN_DELETE);
up_btnDelete.SetExecute(up_btnremove_start);
up_btnDelete.SetPos(350+200,420);
up_btnDelete.SetPos(380,400+30);
Widget_Add(fn_updates, up_btnDelete);
up_frUpdates = spawn(CFrame);
@ -158,59 +157,43 @@ menu_updates_init(void)
up_frPreview.SetPos(350,160);
up_frPreview.SetSize(256+6,128+6);
Widget_Add(fn_updates, up_frPreview);
Updates_Refresh();
#endif
}
/* Drawing */
int g_pkgname_updating;
void
menu_updates_draw(void)
{
#ifndef WEBMENU
static int old_enabled;
float fl = 0;
if (!g_updates_initialized) {
int pkg_ready = 0;
string packages = GameLibrary_GetInfo(GAMEINFO_PACKAGELIST);
/* we have no hard-coded list of supported packages, so query frag-net.com */
if (!packages && !g_pkgname_updating) {
string gamedir = GameLibrary_GetInfo(GAMEINFO_GAMEDIR);
print(sprintf("Querying package names for %s\n", gamedir));
uri_get(sprintf("http://www.frag-net.com/dl/packages_%s", uri_escape(gamedir)), MODSERVER_REQ_PKGNAMES);
g_pkgname_updating = 1;
}
/* don't query packages YET until we get a response */
if (g_pkgname_updating == 1) {
return;
}
/* query until 1 package is ready */
for (int i = 0; (Updates_GetInfo(i, GPMI_NAME)); i++) {
string installed = Updates_GetInfo(i, GPMI_INSTALLED);
/* increment to keep track */
if (installed == "enabled")
old_enabled++;
pkg_ready = 1;
}
if (pkg_ready == 1) {
menu_updates_refresh();
g_updates_initialized = TRUE;
}
/* first draw run */
if (g_updates_initialized == 0) {
Updates_Init();
g_updates_initialized = 2;
return;
}
Widget_Draw(fn_updates);
updaterStatus_t status = Updates_GetUpdaterStatus();
Header_Draw(HEAD_CONFIG);
/* we're still initializing... */
if (g_updates_initialized == 2) {
if (status == UPDATER_PENDING) {
customgame_dlgWait.Draw();
WField_Static(162, 180, "Contacting update server...", 320, 260,
col_prompt_text, 1.0f, 2, font_label_p);
return;
} else if (status == UPDATER_INITIALIZED) {
menu_updates_refresh();
g_updates_initialized = 1;
}
}
Widget_Draw(fn_updates);
drawpic([g_menuofs[0]+550,g_menuofs[1]+10], g_bmp[FN_LOGO],[80,80], [1,1,1], 1.0f, 0);
WLabel_Static(50, 143, "Data files:", 11, 11, [1,1,1],
1.0f, 0, font_arial);
@ -231,37 +214,22 @@ menu_updates_draw(void)
1.0f, 0, font_arial);
int i = up_lbUpdates.GetSelected();
i = updates[i].uid;
fl = 310;
WLabel_Static(420,fl, getpackagemanagerinfo(i, GPMI_AUTHOR), 11, 11, [1,1,1],
WLabel_Static(420,fl, Updates_GetInfo(i, UPDATE_AUTHOR), 11, 11, [1,1,1],
1.0f, 0, font_arial); fl += 18;
WLabel_Static(420,fl, getpackagemanagerinfo(i, GPMI_INSTALLED), 11, 11, [1,1,1],
WLabel_Static(420,fl, Updates_GetInfo(i, UPDATE_STATUSSTRING), 11, 11, [1,1,1],
1.0f, 0, font_arial); fl += 18;
WLabel_Static(420,fl, getpackagemanagerinfo(i, GPMI_LICENSE), 11, 11, [1,1,1],
WLabel_Static(420,fl, Updates_GetInfo(i, UPDATE_LICENSE), 11, 11, [1,1,1],
1.0f, 0, font_arial); fl += 18;
WLabel_Static(420,fl, getpackagemanagerinfo(i, GPMI_WEBSITE), 11, 11, [1,1,1],
WLabel_Static(420,fl, Updates_GetInfo(i, UPDATE_WEBSITE), 11, 11, [1,1,1],
1.0f, 0, font_arial); fl += 18;
WLabel_Static(420,fl, getpackagemanagerinfo(i, GPMI_VERSION), 11, 11, [1,1,1],
WLabel_Static(420,fl, Updates_GetInfo(i, UPDATE_VERSION), 11, 11, [1,1,1],
1.0f, 0, font_arial);
WLabel_Static(350, 143, "Preview:", 11, 11, [1,1,1],
1.0f, 0, font_arial);
/* check if we've got any more packages than upon init */
int new_packages = 0;
for (int b = 0; (getpackagemanagerinfo(b, GPMI_NAME)); b++) {
string installed = getpackagemanagerinfo(b, GPMI_INSTALLED);
/* increment to keep track */
if (installed == "enabled")
new_packages++;
}
if (old_enabled != new_packages) {
old_enabled = new_packages;
localcmd("menu_restart\nmenu_updates\n");
}
if (g_updates_previewpic)
drawpic([g_menuofs[0]+350+3,g_menuofs[1]+160+3], g_updates_previewpic, [256,128], [1,1,1], 1.0f);
#endif

View File

@ -66,6 +66,9 @@ CUpdateList::Draw(void)
for (int i = m_scroll; i < (visible + m_scroll); i++) {
vector colo;
string updateTitle = Updates_GetInfo(i, UPDATE_TITLE);
updateState_t updateState = Updates_GetInfo(i, UPDATE_STATE);
updateAction_t updateAction = Updates_GetInfo(i, UPDATE_ACTION);
if (m_selected == i) {
colo = ML_COL_2;
@ -75,60 +78,55 @@ CUpdateList::Draw(void)
colo = ML_COL_1;
}
int uid = updates[i].uid;
string status = Updates_GetInfo(uid, GPMI_INSTALLED);
switch (status) {
case "":
if (updates[i].installed == "") {
colo = [1,0,0];
} else if (updates[i].installed == "pending") {
colo = [0,1,0];
} else if (updates[i].installed == "enabled") {
colo = colo;
}
switch (updateState) {
case UPDATESTATE_ENABLED:
colo = ML_COL_1;
break;
case "pending":
colo[0] *= 0.5;
colo[1] *= 0.5;
colo[2] *= 0.5;
case UPDATESTATE_CORRUPT:
colo = [1, 0, 0]; /* red */
break;
case "enabled":
colo = colo;
break;
case "present":
colo[0] *= 0.5;
colo[1] *= 0.5;
colo[2] *= 0.5;
break;
case "corrupt":
colo = [1,0,0] * sin(time);
break;
default:
float p = stof(status) / 100;
case UPDATESTATE_PENDING:
float p = Updates_GetInfo(i, UPDATE_DLPERCENTAGE) / 100;
colo = [0,1,0] * p;
drawfill([g_menuofs[0] + m_x, g_menuofs[1] + pos], [m_size[0] * p, 18],
colo, 0.5f);
colo = [0.25,0.25,0.25] + ([0.75,0.75,0.75] * p);
break;
case UPDATESTATE_DISABLED:
case UPDATESTATE_NONE:
colo = [0.5, 0.5, 0.5]; /* grey */
default:
break;
}
/* TODO: make this integrate with the above better */
if (updates[i].installed == "rem") {
if ((time*2) & 1)
colo = [1,0,0];
else
colo = [0,0,0];
} else if (updates[i].installed == "in") {
if ((time*2) & 1)
if ((time*2) & 1) {
switch (updateAction) {
case UPDATEACTION_INSTALL: /* blinking orange */
colo = [1,1,0];
else
colo = [0,0,0];
break;
case UPDATEACTION_REINSTALL:
colo = [0,1,0];
break;
case UPDATEACTION_UNINSTALL: /* blinking red */
colo = [1,0,0];
break;
case UPDATEACTION_AUTOINSTALL: /* blinking orange/grey */
colo = [0.5,0.5,0];
break;
case UPDATEACTION_DISABLE: /* blinking grey/color */
colo = [0.5,0.5,0.5];
break;
case UPDATEACTION_RETAIN:
case UPDATEACTION_NONE:
default:
break;
}
}
/* Game */
WLabel_Static(m_x + 3, pos + 3, updates[i].title, 11, 11, colo,
WLabel_Static(m_x + 3, pos + 3, updateTitle, 11, 11, colo,
1.0f, 0, font_arial);
pos += 18;

View File

@ -23,4 +23,21 @@
#include "servers.h"
#include "tcp.h"
#include "updates.h"
#include "gamelibrary.h"
#include "gamelibrary.h"
/** Definitions for FTE's internal package manager. We don't want you to talk to this one directly within Nuclide. */
typedef enum
{
GPMI_NAME, /**< name of the package, for use with the pkg command. */
GPMI_CATEGORY, /**< category text */
GPMI_TITLE, /**< name of the package, for showing the user. */
GPMI_VERSION, /**< version info (may have multiple with the same name but different versions) */
GPMI_DESCRIPTION, /**< some blurb */
GPMI_LICENSE, /**< what license its distributed under */
GPMI_AUTHOR, /**< name of the person(s) who created it */
GPMI_WEBSITE, /**< where to contribute/find out more info/etc */
GPMI_INSTALLED, /**< current state */
GPMI_ACTION, /**< desired state */
GPMI_AVAILABLE, /**< whether it may be downloaded or not. */
GPMI_FILESIZE, /**< size to download. */
} packageType_t;

View File

@ -61,6 +61,7 @@ float GameLibrary_InstallProgress(void);
int GameLibrary_GetCurrentGame(void);
/** Retrieves fields for a given game. See gameInfo_t for a list of fields you can query. */
__variant GameLibrary_GetGameInfo(int, gameInfo_t);
/** Retrieves fields for the currently running game. See gameInfo_t for a list of fields you can query. */
__variant GameLibrary_GetInfo(gameInfo_t);
typedef enum
@ -68,7 +69,8 @@ typedef enum
GAMEINFO_NONE, /**< No gameinfo available. This is probably the engine making assumptions. */
GAMEINFO_MANIFEST, /**< Game info was read from a manifest within the path. */
GAMEINFO_GITXT, /**< Game info stems from a Source Engine style gameinfo.txt file. */
GAMEINFO_LIBLIST /**< Game info stems from a GoldSrc style liblist.gam file. */
GAMEINFO_LIBLIST, /**< Game info stems from a GoldSrc style liblist.gam file. */
GAMEINFO_PACKAGE,
} gi_type;
typedef struct

View File

@ -14,6 +14,29 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* The GameLibrary concerns itself with everything around what a game is,
how to install, activate and deactivate them. Mods are included in this,
so I'll proceed calling them 'games' or 'custom games'.
A game can be installed through two primary means:
- Manual install, like from a .zip or some installer or archive
- Engine package manager install, through our own user interface
And between these, they can come with different metadata/manifests.
It assumed that every game has either a FTE Manifest description,
a gameinfo.txt (Source Engine format) or liblist.gam (GoldSrc format)
that describes various aspects of the game. Like which version it is, what
map will be loaded when you press 'New Game' and so on.
If that info is not available, some placeholder data will be used instead.
However, games installed via the package manager will at least for the
custom game menus not use the on-disk manifest file, but information
provided by the package manager. Once you switch into said game everything
within will be pulled from a file on disk, such as a liblist.gam or gameinfo.txt.
*/
int g_iModInstallCache;
string g_strModInstallCache;
@ -28,6 +51,27 @@ GameLibrary_Set(int id)
cvar_set("com_fullgamename", games[id].game);
}
static int
GameLibrary_IDForPackageName(string packageName)
{
string f;
for (int i = 0; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
string name;
name = getpackagemanagerinfo(i, GPMI_NAME);
/* Spike started randomly putting version numbers into package names */
f = sprintf("%s=%s", packageName, getpackagemanagerinfo(i, GPMI_VERSION));
if (name == f) {
return i;
}
}
/* no package id whatsoever */
return (-1i);
}
/** Looks for a single file inside a gamedir, including its pk3s and returns a valid filehandle if it is found. */
static filestream
GameLibrary_FindInGameDir(string filename, string gamedirname)
@ -188,7 +232,7 @@ GameLibrary_LibListParse(int id, string strKey, string strValue)
/* newly added with Nuclide */
case "pkgname":
games[id].pkgname = strValue;
games[id].pkgid = Updates_IDForName(games[id].pkgname);
games[id].pkgid = GameLibrary_IDForPackageName(games[id].pkgname);
break;
case "pkgfile":
games[id].pkgfile = strValue;
@ -391,21 +435,61 @@ GameLibrary_SetDefaults(int id, string gamedirname)
#endif
}
/** Checks if a given game directory was installed manually. */
static bool
GameLibrary_CheckLocalPresence(string gameDir)
{
string testPkgDir = __NULL__;
bool returnSuccess = true;
for (int x = 0i; (testPkgDir = getgamedirinfo(x, 0)); x++) {
if (gameDir == testPkgDir) {
return true;
}
}
return false;
}
void
GameLibrary_InitCustom(void)
{
int id;
int foundself = 0;
string gamedirname;
string gamedirname = __NULL__;
gameinfo_count = 0;
int packageinfo_count = 0i;
int c = 0i;
/* first count let's all manually installed mods */
for (id = 0; (gamedirname = getgamedirinfo(id, 0)); id++) {
gameinfo_count++;
}
/* count the package installed mods after */
for (int i = 0; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
string packageName = getpackagemanagerinfo(i, GPMI_NAME);
string installStatus = getpackagemanagerinfo(i, GPMI_INSTALLED);
string prefix = substring(packageName, 0, 3);
/* only care about installed mods (custom games) */
if (prefix == "cg_" && installStatus == "enabled") {
string gameDir = substring(packageName, 3, -1);
tokenizebyseparator(gameDir, "=");
gameDir = argv(0);
/* check if this mod was installed manually already */
if (GameLibrary_CheckLocalPresence(gameDir) == true) {
continue;
}
packageinfo_count++;
}
}
/* re-allocate the game list */
memfree(games);
games = memalloc(sizeof(gameinfo_t) * gameinfo_count);
games = memalloc(sizeof(gameinfo_t) * (gameinfo_count + packageinfo_count));
/* The things we do for frequent flyer mileage. */
if (!games)
@ -429,8 +513,48 @@ GameLibrary_InitCustom(void)
NSLog("[MENU] Found nothing for %s", gamedirname);
games[id].info_type = GAMEINFO_NONE;
}
c = id + 1;
}
/* iterate through all packages again */
for (int i = 0i; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
string packageName = getpackagemanagerinfo(i, GPMI_NAME);
string installStatus = getpackagemanagerinfo(i, GPMI_INSTALLED);
string prefix = substring(packageName, 0, 3);
/* same check as above in the counter */
if (prefix == "cg_" && installStatus == "enabled") {
string gameDir = substring(packageName, 3, -1);
tokenizebyseparator(gameDir, "=");
gameDir = argv(0);
if (GameLibrary_CheckLocalPresence(gameDir) == true) {
continue;
}
string titleString = getpackagemanagerinfo(i, GPMI_TITLE);
string versionString = getpackagemanagerinfo(i, GPMI_VERSION);
string authorString = getpackagemanagerinfo(i, GPMI_AUTHOR);
string sizeString = getpackagemanagerinfo(i, GPMI_FILESIZE);
string websiteString = getpackagemanagerinfo(i, GPMI_WEBSITE);
//print(sprintf("Adding packaged game %S\n", gameDir));
GameLibrary_SetDefaults(c, gameDir);
games[c].game = substring(titleString, 5, -1); /* strip 'Mod: '*/
games[c].url_info = websiteString;
games[c].version = versionString;
games[c].size = (int)stof(sizeString);
games[c].type = "Both";
games[c].info_type = GAMEINFO_PACKAGE;
games[c].pkgname = strcat("cg_", gameDir, ";game_", gameDir, ";");
c++;
}
}
/* now we can pretend that these weren't their own thing */
gameinfo_count += packageinfo_count;
/* we may have some mods, but we're not running any of them. Fatal */
if (gameinfo_current == -1) {
print("^1FATAL ERROR: NO LIBLIST.GAM FOR CURRENT MOD FOUND!\n");
@ -458,7 +582,6 @@ GameLibrary_Init(void)
/* only run this when not in web-client mode */
#ifndef WEBMENU
if (GameLibrary_CheckManifest(id, gamedirname) == 1) {
NSLog("[MENU] Found manifest for %s", gamedirname);
games[id].info_type = GAMEINFO_MANIFEST;
@ -521,8 +644,8 @@ GameLibrary_InstallProgress(void)
int pkgid;
/* package query */
pkgid = Updates_IDForName(argv(i));
st = Updates_GetInfo(pkgid, GPMI_INSTALLED);
pkgid = GameLibrary_IDForPackageName(argv(i));
st = getpackagemanagerinfo(pkgid, GPMI_INSTALLED);
/* filter out statuses so we can calculate percentage */
switch (st) {
@ -555,10 +678,11 @@ static void
GameLibrary_InstallStart(int gameid)
{
int count;
count = tokenize(games[gameid].pkgname);
for (int i = 0; i < count; i++) {
int pkgid = Updates_IDForName(argv(i));
int pkgid = GameLibrary_IDForPackageName(argv(i));
localcmd(sprintf("pkg add %s\n", argv(i)));
print(sprintf("Marking package %s for install.\n",
argv(i)));
@ -575,7 +699,12 @@ GameLibrary_Install(int gameID)
{
string st;
st = Updates_GetInfo(games[gameID].pkgid, GPMI_INSTALLED);
if (gameID >= gameinfo_count || gameID < 0i) {
print(sprintf("GameLibrary_Install: Invalid game id %i!\n", gameID));
return;
}
st = getpackagemanagerinfo(games[gameID].pkgid, GPMI_INSTALLED);
print(st);
print("\n");
@ -593,6 +722,11 @@ GameLibrary_Install(int gameID)
void
GameLibrary_Activate(int gameID)
{
if (gameID >= gameinfo_count || gameID < 0i) {
print(sprintf("GameLibrary_Activate: Invalid game id %i!\n", gameID));
return;
}
GameLibrary_Set(gameID);
if (games[gameID].info_type == GAMEINFO_MANIFEST)
@ -636,10 +770,14 @@ GameLibrary_GetInfo(gameInfo_t infoType)
return GameLibrary_GetGameInfo(gameinfo_current, infoType);
}
/** Retrieves info for a given game. */
__variant
GameLibrary_GetGameInfo(int gameID, gameInfo_t infoType)
{
if (gameID >= gameinfo_count || gameID < 0i) {
print(sprintf("GameLibrary_GetGameInfo: Invalid game id %i!\n", gameID));
return __NULL__;
}
switch (infoType) {
case GAMEINFO_TITLE:
return (string)games[gameID].game;

View File

@ -15,6 +15,28 @@
*/
#ifndef WEBMENU
/* the same as GameLibrary_IDForPackageName */
static int
ModServer_IDForPackageName(string packageName)
{
string f;
for (int i = 0; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
string name;
name = getpackagemanagerinfo(i, GPMI_NAME);
/* Spike started randomly putting version numbers into package names */
f = sprintf("%s=%s", packageName, getpackagemanagerinfo(i, GPMI_VERSION));
if (name == f) {
return i;
}
}
/* no package id whatsoever */
return (-1i);
}
void*
memrealloc(__variant *oldptr, int elementsize, int old_num, int new_num)
{
@ -161,7 +183,7 @@ ModServer_ParseItem(string data)
break;
case "gameinfo_pkgname":
games[id].pkgname = argv(i+1);
games[id].pkgid = Updates_IDForName(games[id].pkgname);
games[id].pkgid = ModServer_IDForPackageName(games[id].pkgname);
break;
default:
break;
@ -169,6 +191,8 @@ ModServer_ParseItem(string data)
}
}
void Updater_URI_Callback(float id, float code, string data, int resourcebytes);
/* Called as an eventual result of the uri_get builtin. */
void
ModServer_URI_Callback(float id, float code, string data, int resourcebytes)
@ -202,8 +226,7 @@ ModServer_URI_Callback(float id, float code, string data, int resourcebytes)
ModServer_ParseItem(data);
break;
case MODSERVER_REQ_PKGNAMES:
games[GameLibrary_GetCurrentGame()].pkgname = data;
g_pkgname_updating = 0;
Updater_URI_Callback(id, code, data, resourcebytes);
break;
default:
print(sprintf("^1ModServer_URI_Callback^7: Unknown request id %d with code %d\n", id, code));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2022 Vera Visions LLC.
* Copyright (c) 2016-2023 Vera Visions LLC.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -14,54 +14,75 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
string(float id, float b) getgamedirinfo = #0;
string(int packageidx, int desiredfield) getpackagemanagerinfo = #0;
/** Different types you can pass to `Updates_GetInfo(...)` to learn details about a given Update entry. */
typedef enum
{
GPMI_NAME, /**< name of the package, for use with the pkg command. */
GPMI_CATEGORY, /**< category text */
GPMI_TITLE, /**< name of the package, for showing the user. */
GPMI_VERSION, /**< version info (may have multiple with the same name but different versions) */
GPMI_DESCRIPTION, /**< some blurb */
GPMI_LICENSE, /**< what license its distributed under */
GPMI_AUTHOR, /**< name of the person(s) who created it */
GPMI_WEBSITE, /**< where to contribute/find out more info/etc */
GPMI_INSTALLED, /**< current state */
GPMI_ACTION, /**< desired state */
GPMI_AVAILABLE, /**< whether it may be downloaded or not. */
GPMI_FILESIZE, /**< size to download. */
UPDATE_NAME, /**< (string) name of the package, for use with the pkg command. */
UPDATE_CATEGORY, /**< (string) category text */
UPDATE_TITLE, /**< (string) name of the package, for showing the user. */
UPDATE_VERSION, /**< (string) version info (may have multiple with the same name but different versions) */
UPDATE_DESCRIPTION, /**< (string) some blurb */
UPDATE_LICENSE, /**< (string) what license its distributed under */
UPDATE_AUTHOR, /**< (string) name of the person(s) who created it */
UPDATE_WEBSITE, /**< (string) where to contribute/find out more info/etc */
UPDATE_STATE, /**< (updateState_t) The current state of the update. */
UPDATE_ACTION, /**< (updateAction_t) Pending action of the update. */
UPDATE_FILESIZE, /**< (int) size to download in bytes. */
UPDATE_PREVIEWIMAGE, /**< (string) Path to a preview image in 4:3 aspect ratio. */
UPDATE_STATUSSTRING, /**< (string) Localizable string that gives you the update status. */
UPDATE_DLPERCENTAGE, /**< (float) Download progress in percent (0-100). */
} updateType_t;
typedef struct
/** Return values from passing UPDATE_STATE to Updates_GetInfo() */
typedef enum
{
string name;
string category;
string title;
string version;
string description;
string license;
string author;
string website;
string installed;
int size;
int uid;
} update_t;
UPDATESTATE_NONE, /**< Update is not installed, or unavailable. */
UPDATESTATE_DISABLED, /**< Update is installed, but disabled. */
UPDATESTATE_ENABLED, /**< Update is installed and enabled. */
UPDATESTATE_CORRUPT, /**< Update on disk is corrupted. */
UPDATESTATE_PENDING /**< Update is pending a change. Usually when we're downloading it. */
} updateState_t;
int g_platform_update_count;
update_t *updates;
/** Return values from passing UPDATE_ACTION to Updates_GetInfo() */
typedef enum
{
UPDATEACTION_NONE, /**< Update is not marked for any change. */
UPDATEACTION_INSTALL, /**< Update marked for installation. */
UPDATEACTION_REINSTALL, /**< Update marked as needing re-installation. */
UPDATEACTION_UNINSTALL, /**< Update marked for removal. */
UPDATEACTION_AUTOINSTALL, /**< Update marked as needing to be installed, due to a dependency. */
UPDATEACTION_DISABLE, /**< Update has been marked for disabling. */
UPDATEACTION_RETAIN /**< Update has been marked as being retained. */
} updateAction_t;
#define FN_UPDATE_IMGURL "http://www.frag-net.com/dl/img/%s.jpg"
/** These are the possible return values from Updates_GetUpdaterStatus().
That way you can put up a loading screen for when the updater is still initiliazing,
or be notified of when an updater is not available at all. */
typedef enum
{
UPDATER_NONE, /**< Nuclide's updater has not been initialized. You need to call Update_Init(). */
UPDATER_UNAVAILABLE, /**< Nuclide's updater is unavailable. This may be due to the update server being offline. */
UPDATER_PENDING, /**< Nuclide's updater is pending. May change to UNAVAILABLE or INITIALIZED. */
UPDATER_INITIALIZED /**< Nuclide's updater is initialized and may have entries. Use Updates_GetUpdateCount() to query how many. */
} updaterStatus_t;
/** Call this in order to contact the update server and fill the list of updates. */
void Updates_Init(void);
void Updates_Refresh(void);
/** Retrieve the status of the updater. See updaterStatus_t for valid return values. */
updaterStatus_t Updates_GetUpdaterStatus(void);
/** Returns the total amount of updates available for the currently running game. */
int Updates_GetPackageCount(void);
int Updates_IDForName(string);
string Updates_NameForID(int);
string Updates_GetInfo(int, updateType_t);
/** Query a package (by ID) for its various info fields. See updateType_t for available fields. */
__variant Updates_GetInfo(int, updateType_t);
/** Returns if our current game has updates available for any installed packages. */
bool Updates_Available(void);
/** Toggle the installation/disabling of an update. May return true/false if it succeeded in marking the package. */
bool Updates_Toggle(int);
/** Mark an update as pending installion. May return true/false if it succeeded in marking the package. */
bool Updates_Install(int);
/** Mark an update as pending deletion. May return true/false if it succeeded in marking the package. */
bool Updates_Remove(int);
/** Mark an update as pending uninstallation. May return true/false if it succeeded in marking the package. */
bool Updates_Destroy(int);
/** Apply all pending changes to packages. May return true/false if it succeeded in doing so. */
bool Updates_ApplyPendingChanges(void);

View File

@ -1,13 +1,72 @@
/** needs to be called upon menu-init, and call Updates_Refresh() if auto-updates
are enabled. if a chooser does not want updates, then we won't. */
/*
* Copyright (c) 2016-2023 Vera Visions LLC.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
typedef struct
{
string name;
string category;
string title;
string version;
string description;
string license;
string author;
string website;
string installed;
updateState_t state;
updateAction_t pending_action;
int size;
int uid;
string preview_image;
float dlpercentage;
} update_t;
string(float id, float b) getgamedirinfo = #0;
string(int packageidx, int desiredfield) getpackagemanagerinfo = #0;
#define FN_UPDATE_IMGURL "http://www.frag-net.com/dl/img/%s.jpg"
int g_platform_update_count;
update_t *updates;
var updaterStatus_t updater_package_status = UPDATER_NONE;
void
Updates_Init(void)
{
/*localcmd("pkg addsource https://www.frag-net.com/pkgs/list\n");*/
print("Update system initialized.\n");
string packages;
/* first, see if our game info sets any packages. */
packages = GameLibrary_GetInfo(GAMEINFO_PACKAGELIST);
/* we have no hard-coded list of supported packages, so query frag-net.com */
if (!packages) {
string gamedir = cvar_string("fs_game");
print(sprintf("Querying package names for %s\n", gamedir));
uri_get(sprintf("http://www.frag-net.com/dl/packages_%s", uri_escape(gamedir)), MODSERVER_REQ_PKGNAMES);
updater_package_status = UPDATER_PENDING;
} else {
updater_package_status = UPDATER_INITIALIZED;
}
}
updaterStatus_t
Updates_GetUpdaterStatus(void)
{
return updater_package_status;
}
/** will return a cached value */
int
Updates_GetPackageCount(void)
{
@ -43,8 +102,68 @@ Updates_IsRecommended(string packageName)
return false;
}
/** called whenever we need to re-initialize the updates struct */
void
static void
Updates_RefreshStateValues(int packageID)
{
int pkgUID = updates[packageID].uid;
string installedState = getpackagemanagerinfo(pkgUID, GPMI_INSTALLED);
string actionState = getpackagemanagerinfo(pkgUID, GPMI_ACTION);
switch (actionState) {
case "user":
updates[packageID].pending_action = UPDATEACTION_INSTALL;
break;
case "reinstall":
updates[packageID].pending_action = UPDATEACTION_REINSTALL;
break;
case "purge":
updates[packageID].pending_action = UPDATEACTION_UNINSTALL;
break;
case "auto":
updates[packageID].pending_action = UPDATEACTION_AUTOINSTALL;
break;
case "disable":
updates[packageID].pending_action = UPDATEACTION_DISABLE;
break;
case "retain":
/*updates[packageID].pending_action = UPDATEACTION_RETAIN;
break;*/
default:
updates[packageID].pending_action = UPDATEACTION_NONE;
}
switch (installedState) {
case "present":
updates[packageID].state = UPDATESTATE_DISABLED;
break;
case "enabled":
updates[packageID].state = UPDATESTATE_ENABLED;
break;
case "corrupt":
updates[packageID].state = UPDATESTATE_CORRUPT;
break;
case "pending":
updates[packageID].state = UPDATESTATE_PENDING;
break;
default:
updates[packageID].state = UPDATESTATE_NONE;
}
updates[packageID].dlpercentage = stof(installedState);
/* HACK: the engine doesn't seem to set pending while installing, so let us do the job then */
if (updates[packageID].dlpercentage > 0)
updates[packageID].state = UPDATESTATE_PENDING;
/* HACK: enabled AND pending installation? smells like an engine bug! */
if (updates[packageID].state == UPDATESTATE_ENABLED) {
if (updates[packageID].pending_action == UPDATEACTION_INSTALL) {
updates[packageID].pending_action = UPDATEACTION_NONE;
}
}
}
static void
Updates_Refresh(void)
{
int c = 0i;
@ -56,7 +175,7 @@ Updates_Refresh(void)
}
/* count all updates that we've got in our package sources */
for (int i = 0i; (Updates_GetInfo(i, GPMI_NAME)); i++) {
for (int i = 0i; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
g_platform_update_count++;
}
@ -67,21 +186,23 @@ Updates_Refresh(void)
int id = i;
/* skip not recommended packages */
if (Updates_IsRecommended(Updates_GetInfo(id, GPMI_NAME)) == false)
if (Updates_IsRecommended(getpackagemanagerinfo(id, GPMI_NAME)) == false)
continue;
updates[c].name = Updates_GetInfo(id, GPMI_NAME);
updates[c].category = Updates_GetInfo(id, GPMI_CATEGORY);
updates[c].title = Updates_GetInfo(id, GPMI_TITLE);
updates[c].version = Updates_GetInfo(id, GPMI_VERSION);
updates[c].description = Updates_GetInfo(id, GPMI_DESCRIPTION);
updates[c].license = Updates_GetInfo(id, GPMI_LICENSE);
updates[c].author = Updates_GetInfo(id, GPMI_AUTHOR);
updates[c].website = Updates_GetInfo(id, GPMI_WEBSITE);
updates[c].installed = Updates_GetInfo(id, GPMI_INSTALLED);
updates[c].size = (int)stof(Updates_GetInfo(id, GPMI_FILESIZE));
updates[c].name = getpackagemanagerinfo(id, GPMI_NAME);
updates[c].category = getpackagemanagerinfo(id, GPMI_CATEGORY);
updates[c].title = getpackagemanagerinfo(id, GPMI_TITLE);
updates[c].version = getpackagemanagerinfo(id, GPMI_VERSION);
updates[c].description = getpackagemanagerinfo(id, GPMI_DESCRIPTION);
updates[c].license = getpackagemanagerinfo(id, GPMI_LICENSE);
updates[c].author = getpackagemanagerinfo(id, GPMI_AUTHOR);
updates[c].website = getpackagemanagerinfo(id, GPMI_WEBSITE);
updates[c].size = (int)stof(getpackagemanagerinfo(id, GPMI_FILESIZE));
updates[c].uid = i;
precache_pic(sprintf(FN_UPDATE_IMGURL, updates[c].name));
updates[c].preview_image = sprintf(FN_UPDATE_IMGURL, updates[c].name);
Updates_RefreshStateValues(c);
c++;
}
@ -89,94 +210,177 @@ Updates_Refresh(void)
g_platform_update_count = c;
}
/** Returns the package ID for a given name. Will return -1 when not available. */
int
Updates_IDForName(string packageName)
{
string f;
for (int i = 0; (getpackagemanagerinfo(i, GPMI_NAME)); i++) {
string name;
name = getpackagemanagerinfo(i, GPMI_NAME);
/* Spike started randomly putting version numbers into package names */
f = sprintf("%s=%s", packageName, getpackagemanagerinfo(i, GPMI_VERSION));
if (name == f) {
return i;
}
}
/* no package id whatsoever */
return (-1i);
}
/** Returns the package name for a given ID. Returns __NULL__ when not available. */
string
Updates_NameForID(int packageID)
{
string packageName = getpackagemanagerinfo(packageID, GPMI_NAME);
if not (packageName)
return __NULL__;
return packageName;
}
/** Query a package (by ID) for its various info fields. See updateType_t for available fields. */
string
__variant
Updates_GetInfo(int packageID, updateType_t fieldType)
{
return getpackagemanagerinfo(packageID, (int)fieldType);
if (packageID >= g_platform_update_count || packageID < 0i) {
print(sprintf("Updates_GetInfo: Invalid package id %i!\n", packageID));
return __NULL__;
}
Updates_RefreshStateValues(packageID);
switch (fieldType) {
case UPDATE_NAME:
return (string)updates[packageID].name;
break;
case UPDATE_CATEGORY:
return (string)updates[packageID].category;
break;
case UPDATE_TITLE:
return (string)updates[packageID].title;
break;
case UPDATE_VERSION:
return (string)updates[packageID].version;
break;
case UPDATE_DESCRIPTION:
return (string)updates[packageID].description;
break;
case UPDATE_LICENSE:
return (string)updates[packageID].license;
break;
case UPDATE_AUTHOR:
return (string)updates[packageID].author;
break;
case UPDATE_WEBSITE:
return (string)updates[packageID].website;
break;
case UPDATE_STATE:
return (updateState_t)updates[packageID].state;
break;
case UPDATE_ACTION:
return (updateAction_t)updates[packageID].pending_action;
break;
case UPDATE_FILESIZE:
return (int)updates[packageID].size;
break;
case UPDATE_PREVIEWIMAGE:
return (string)updates[packageID].preview_image;
break;
case UPDATE_STATUSSTRING:
/* if we have a action, focus on that */
switch (updates[packageID].pending_action) {
case UPDATEACTION_INSTALL:
if (updates[packageID].dlpercentage > 0.0) {
return sprintf("%d %%", updates[packageID].dlpercentage);
} else {
return _("UPDATE_PENDING_INSTALL");
}
break;
case UPDATEACTION_REINSTALL:
return _("UPDATE_PENDING_REINSTALL");
break;
case UPDATEACTION_UNINSTALL:
return _("UPDATE_PENDING_UNINSTALL");
break;
case UPDATEACTION_AUTOINSTALL:
return _("UPDATE_PENDING_AUTOINSTALL");
break;
case UPDATEACTION_DISABLE:
return _("UPDATE_PENDING_DISABLE");
break;
case UPDATEACTION_RETAIN:
return _("UPDATE_PENDING_RETAIN");
break;
default:
switch (updates[packageID].state) {
case UPDATESTATE_DISABLED:
return _("UPDATE_DISABLED");
break;
case UPDATESTATE_ENABLED:
return _("UPDATE_ENABLED");
break;
case UPDATESTATE_CORRUPT:
return _("UPDATE_CORRUPT");
break;
default:
return _("UPDATE_NOTINSTALLED");
}
}
break;
break;
case UPDATE_DLPERCENTAGE:
return updates[packageID].dlpercentage;
break;
default:
return __NULL__;
}
}
/** Returns if our current game has updates available for any installed packages. */
bool
Updates_Available(void)
{
return true;
}
/** Toggle the installation of a package. Will return true if it was done. */
bool
Updates_Toggle(int packageID)
{
switch (updates[packageID].installed) {
case "":
case "rem":
if (packageID >= g_platform_update_count || packageID < 0i) {
print(sprintf("Updates_Toggle: Invalid package id %i!\n", packageID));
return false;
}
switch (updates[packageID].pending_action) {
case UPDATEACTION_INSTALL:
case UPDATEACTION_REINSTALL:
localcmd(sprintf("pkg rem %s\n", updates[packageID].name));
break;
case UPDATEACTION_UNINSTALL:
case UPDATEACTION_DISABLE:
localcmd(sprintf("pkg add %s\n", updates[packageID].name));
updates[packageID].installed = "pending";
break;
default:
localcmd(sprintf("pkg rem %s\n", updates[packageID].name));
updates[packageID].installed = "rem";
if (updates[packageID].state == UPDATESTATE_ENABLED) {
localcmd(sprintf("pkg rem %s\n", updates[packageID].name));
} else {
localcmd(sprintf("pkg add %s\n", updates[packageID].name));
}
break;
}
return true;
}
/** Mark a package as pending installion. May return true/false if it succeeded in marking the package. */
bool
Updates_Install(int packageID)
{
if (packageID >= g_platform_update_count || packageID < 0i) {
print(sprintf("Updates_Install: Invalid package id %i!\n", packageID));
return false;
}
localcmd(sprintf("pkg add %s\n", updates[packageID].name));
updates[packageID].installed = "pending";
print(sprintf("Marking package %s for install.\n", updates[packageID].title));
return true;
}
/** Mark a package as pending deletion. May return true/false if it succeeded in marking the package. */
bool
Updates_Remove(int packageID)
{
if (packageID >= g_platform_update_count || packageID < 0i) {
print(sprintf("Updates_Remove: Invalid package id %i!\n", packageID));
return false;
}
localcmd(sprintf("pkg rem %s\n", updates[packageID].name));
updates[packageID].installed = "rem";
print(sprintf("Marking package %s for 'removal'.\n", updates[packageID].title));
return true;
}
/** Apply all pending changes to packages. May return true/false if it succeeded in doing so. */
bool
Updates_Destroy(int packageID)
{
if (packageID >= g_platform_update_count || packageID < 0i) {
print(sprintf("Updates_Destroy: Invalid package id %i!\n", packageID));
return false;
}
localcmd(sprintf("pkg del %s\n", updates[packageID].name));
print(sprintf("Marking package %s for 'deletion'.\n", updates[packageID].title));
return true;
}
bool
Updates_ApplyPendingChanges(void)
{
@ -184,4 +388,20 @@ Updates_ApplyPendingChanges(void)
localcmd("pkg apply\n");
print("Applying package changes.\n");
return true;
}
void
Updater_URI_Callback(float id, float code, string data, int resourcebytes)
{
if (code == 404) {
string gameDir = cvar_string("fs_game");
games[GameLibrary_GetCurrentGame()].pkgname = strcat("cg_", gameDir, ";game_", gameDir, ";");
} else {
print(sprintf("URI: %d %d %S %i\n", id, code, data, resourcebytes));
games[GameLibrary_GetCurrentGame()].pkgname = data;
}
updater_package_status = UPDATER_INITIALIZED;
Updates_Refresh();
}