Add AI-upscaled critter/object art as a new MDL mod type
build / build (push) Successful in 3m36s

Extends the MDL system beyond text overrides: bitmaps/<label>.json
names a model/scale and resource list, and mdlp extracts each
resource's frames, runs them through an ONNX Runtime CPU upscaler
baked into mdlp-image, re-quantizes to the game's palette, and writes
the result back - same build-time pipeline as message overrides.

Adds animgallery, a standalone viewer that renders any data
directory's animated resources as an HTML/GIF gallery with a
checkbox-driven MDL JSON generator, and -only-changed to preview just
what a mod overrides against the base game.

Fixes a real data race in hacked's ChangeNotifier.hashAll() (found via
go build -race causing non-deterministic resource counts), via a new
numbered patch rather than editing the vendored source.

Adds engine-patches/, a general-fixes counterpart to
android/engine-patches/ now staged and applied to the desktop builds
too (previously unpatched): a real GL_UNPACK_ROW_LENGTH stride bug, an
audio error-logging improvement, and two critter-rendering fixes -
mod art was losing to the base game's own re-opened resource files,
and critters rendered at the wrong size/position once that was fixed,
both from how the billboard renderer derives on-screen size and
placement straight from bitmap pixel dimensions.

Updates README.md and AGENTS.md to document engine-patches/.
This commit is contained in:
ml
2026-09-27 20:52:35 +02:00
parent 389abd7de7
commit ab2b778f44
25 changed files with 2009 additions and 35 deletions
@@ -0,0 +1,98 @@
--- a/src/MacSrc/Modding.c 2026-07-19 13:11:51.000000000 +0200
+++ b/src/MacSrc/Modding.c 2026-09-27 19:44:24.718059783 +0200
@@ -27,6 +27,11 @@
char *modding_additional_files[MAX_MOD_FILES];
int num_mod_files;
+// One filenum per modding_additional_files[] entry, or -1 if not open yet -
+// lets LoadModFiles() close a mod file's previous handle before reopening it
+// (see LoadModFiles() below for why it needs to be called more than once).
+static int32_t modding_loaded_filenum[MAX_MOD_FILES];
+
int StringEndsWith(char *src, char *dst) {
char *s = strrchr(src, '.');
@@ -86,6 +91,7 @@
modding_archive_override = NULL;
for (int i = 0; i < MAX_MOD_FILES; i++) {
modding_additional_files[i] = NULL;
+ modding_loaded_filenum[i] = -1;
}
// Now go process args
@@ -135,10 +141,24 @@
}
int LoadModFiles() {
+ // Called again by obj_set_secondary_properties() (objsim.c) after it
+ // re-opens the base game's own object art files, which would otherwise
+ // silently reclaim any resource ID a mod file already overrode here (see
+ // ResReadDirEntries() in resfile.c - opening a file re-registers every
+ // resource ID it contains, last one in wins, with no check for whether
+ // an ID is already claimed). Re-registering means actually re-opening
+ // (there is no "just re-register" primitive), so a mod file already open
+ // from an earlier call is closed first - one open handle per mod file at
+ // a time, rather than leaking a fresh resFile[] slot on every repeat call
+ // (new game, load game - object_data_load() can run more than once per
+ // process).
for (int i = 0; i < num_mod_files; i++) {
if (modding_additional_files[i] != NULL) {
+ if (modding_loaded_filenum[i] >= 0) {
+ ResCloseFile(modding_loaded_filenum[i]);
+ }
printf("Loading mod file %s\n", modding_additional_files[i]);
- ResOpenFile(modding_additional_files[i]);
+ modding_loaded_filenum[i] = ResOpenFile(modding_additional_files[i]);
}
}
--- a/src/GameSrc/objsim.c 2026-07-19 13:11:51.000000000 +0200
+++ b/src/GameSrc/objsim.c 2026-09-27 19:44:17.014441306 +0200
@@ -43,6 +43,7 @@
#include "gettmaps.h"
#include "lvldata.h"
#include "mapflags.h"
+#include "Modding.h"
#include "objart2.h"
#include "objart3.h"
#include "objects.h"
@@ -1877,6 +1878,13 @@
// Warning(("Problem opening object art cache!\n"));
return (ERR_NOEFFECT);
}
+ // The two opens above re-register every resource ID objart2.res/
+ // objart3.res contain, reclaiming any of them a mod file already
+ // overrode (see LoadModFiles() in Modding.c) - restore mod priority
+ // for the reftable reads below. load_dynamic_memory() (cybmem.c) reopens
+ // these same two files again later, persistently, for actual rendering -
+ // restoring priority there too.
+ LoadModFiles();
// Now go and set specific computed data, like frame counts
for (i = 0; i < NUM_CRITTER; i++) {
--- a/src/GameSrc/cybmem.c 2026-07-19 13:11:51.000000000 +0200
+++ b/src/GameSrc/cybmem.c 2026-09-27 19:44:24.718059783 +0200
@@ -33,6 +33,7 @@
#include "textmaps.h"
#include "objcrit.h"
#include "dynmem.h"
+#include "Modding.h"
#include "Shock.h"
#include "sideicon.h"
#include "criterr.h"
@@ -120,6 +121,15 @@
critical_error(CRITERR_RES | 8);
}
+ // The four opens above re-register every resource ID their files
+ // contain, reclaiming any of them a mod file already overrode (see
+ // LoadModFiles() in Modding.c) - these are the persistent handles
+ // actual gameplay renders from (unlike obj_set_secondary_properties()'s
+ // own transient open/close of objart2.res/objart3.res for reading
+ // frame-count metadata), so this is the last point mod priority needs
+ // restoring before rendering.
+ LoadModFiles();
+
loadcount |= mask;
}