Files
questshock/engine-patches/04-desktop-fix-critter-sprite-scale.patch
T
ml ab2b778f44
build / build (push) Successful in 3m36s
Add AI-upscaled critter/object art as a new MDL mod type
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/.
2026-09-27 20:52:35 +02:00

223 lines
9.8 KiB
Diff

--- a/src/GameSrc/objsim.c 2026-09-27 19:44:17.014441306 +0200
+++ b/src/GameSrc/objsim.c 2026-09-27 20:44:23.147083066 +0200
@@ -1867,6 +1867,40 @@
return (OK);
}
+// Every critter view's base-game bitmap size, keyed by resource ID - filled
+// in by cache_critter_native_size() below while objart2.res/objart3.res's
+// base-game data is still the active resolver for these IDs (before
+// LoadModFiles() hands priority to a mod again), read back by
+// get_critter_native_size() in gameobj.c. A mod that replaces a critter's
+// art with a different-resolution bitmap changes how much detail is visible,
+// not the object's on-screen size: g3_bitmap_common() (Bitmap.c) uses this
+// cached size instead of the (possibly modded, possibly larger or smaller)
+// bitmap's own width/height when computing the sprite's world-space extent.
+// Id is uint16_t, so a flat array indexed by it needs no bounds check.
+static short s_critterNativeW[65536];
+static short s_critterNativeH[65536];
+
+static void cache_critter_native_size(Id id) {
+ FrameDesc *f;
+ if (s_critterNativeW[id] != 0)
+ return;
+ f = (FrameDesc *)RefLock(MKREF(id, 0));
+ if (f == NULL)
+ return;
+ s_critterNativeW[id] = f->bm.w;
+ s_critterNativeH[id] = f->bm.h;
+ RefUnlock(MKREF(id, 0));
+}
+
+// w/h are left untouched (so a caller can default to 0, meaning "no cached
+// native size, use the bitmap's own dimensions") if id was never cached.
+void get_critter_native_size(Id id, short *w, short *h) {
+ if (s_critterNativeW[id] == 0)
+ return;
+ *w = s_critterNativeW[id];
+ *h = s_critterNativeH[id];
+}
+
errtype obj_set_secondary_properties() {
char i, j;
RefTable *prt;
@@ -1878,6 +1912,28 @@
// Warning(("Problem opening object art cache!\n"));
return (ERR_NOEFFECT);
}
+
+ // Snapshot every critter view's base-game bitmap size while it's still
+ // the active resolver for these IDs, before LoadModFiles() below hands
+ // priority back to a mod - see cache_critter_native_size() and its own
+ // comment. Full 8-view postures need every view individually (their IDs
+ // are 8 consecutive resource IDs, one per view); front-only postures are
+ // a single ID (matching the frame-count loop below).
+ for (i = 0; i < NUM_CRITTER; i++) {
+ if (CritterProps[i].views > 1) {
+ for (j = 0; j < NUM_CRITTER_POSTURES; j++) {
+ if (j >= FIRST_FRONT_POSTURE) {
+ cache_critter_native_size(posture_bases[j] + i);
+ } else {
+ int v;
+ for (v = 0; v < 8; v++) {
+ cache_critter_native_size(critter_id_table[i] + posture_bases[j] + v);
+ }
+ }
+ }
+ }
+ }
+
// 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
--- a/src/GameSrc/Headers/objsim.h 2026-07-19 13:11:51.000000000 +0200
+++ b/src/GameSrc/Headers/objsim.h 2026-09-27 19:39:39.585009491 +0200
@@ -108,6 +108,7 @@
errtype obj_load_properties();
errtype obj_create_player(ObjLoc *plr_loc);
Ref ref_from_critter_data(ObjID oid, int triple, byte posture, short frame, short view);
+void get_critter_native_size(Id id, short *w, short *h);
errtype obj_zero_unused();
grs_bitmap *obj_get_model_data(ObjID id, fix *x, fix *y, fix *z, grs_bitmap *bm2, Ref *ref1, Ref *ref2);
errtype obj_model_hack(ObjID id, uchar *hack_x, uchar *hack_y, uchar *hack_z, uchar *hack_type);
--- a/src/GameSrc/gameobj.c 2026-07-19 13:11:51.000000000 +0200
+++ b/src/GameSrc/gameobj.c 2026-09-27 20:44:35.195019539 +0200
@@ -104,7 +104,7 @@
void draw_ice(void);
void draw_ice_wall(void);
void _fr_draw_tmtile(grs_bitmap *draw_bm, int col_val, g3s_phandle *plst, uchar dblface, uchar use_lighting);
-void _fr_draw_bitmap(grs_bitmap *draw_bm, int dist, int sc, int anch_x, int anch_y);
+void _fr_draw_bitmap(grs_bitmap *draw_bm, int dist, int sc, int anch_x, int anch_y, short native_w, short native_h);
int munge_val(int val, int range, int delta) {
int base = range - delta;
@@ -575,7 +575,7 @@
// note this always has show_obj's p for p and _fdt_dist for dist.. perhaps shouldnt pass them
//#pragma disable_message(202)
-void _fr_draw_bitmap(grs_bitmap *draw_bm, int dist, int sc, int anch_x, int anch_y) {
+void _fr_draw_bitmap(grs_bitmap *draw_bm, int dist, int sc, int anch_x, int anch_y, short native_w, short native_h) {
#ifdef SMOOTH_BITMAPS
grs_canvas tmp_can;
grs_bitmap tmp_bm;
@@ -597,7 +597,7 @@
if ((anch_x <= 0) && (anch_y <= 0))
bitmap_verts = g3_light_bitmap(draw_bm, anchor);
else
- bitmap_verts = g3_light_anchor_bitmap(draw_bm, anchor, anch_x, anch_y);
+ bitmap_verts = g3_light_anchor_bitmap(draw_bm, anchor, anch_x, anch_y, native_w, native_h);
if (sc)
g3_set_bitmap_scale(fix_make(0, (int)(2048 / 3)), fix_make(0, (int)(2048 / 3)));
@@ -855,7 +855,7 @@
if ((anchors_3d[o3drep].x > 0) || (anchors_3d[o3drep].y > 0))
_o_rad = 0;
_fr_draw_bitmap(bitmaps_3d[o3drep], _fdt_dist, ObjProps[objtrip].flags & MY_IM_LARGE, anchors_3d[o3drep].x,
- anchors_3d[o3drep].y);
+ anchors_3d[o3drep].y, 0, 0);
if (global_fullmap->cyber)
if (obj_ICE_ICE_BABY(_fr_cobj))
draw_ice();
@@ -995,7 +995,7 @@
trgp = tpdata->bits + tele_bm.h * tele_bm.w;
LG_memcpy(dstp, srcp, trgp - srcp);
- _fr_draw_bitmap(&tele_bm, _fdt_dist, FALSE, anch.ul.x, anch.ul.y);
+ _fr_draw_bitmap(&tele_bm, _fdt_dist, FALSE, anch.ul.x, anch.ul.y, 0, 0);
release_critter_bitmap_fast(ref);
return;
}
@@ -1016,11 +1016,15 @@
#endif
break;
}
- _fr_draw_bitmap(tpdata, _fdt_dist, 0, anch.ul.x, anch.ul.y);
+ {
+ short native_w = 0, native_h = 0;
+ get_critter_native_size(REFID(ref), &native_w, &native_h);
+ _fr_draw_bitmap(tpdata, _fdt_dist, 0, anch.ul.x, anch.ul.y, native_w, native_h);
+ }
release_critter_bitmap_fast(ref);
} else {
tpdata = bitmaps_3d[o3drep + view];
- _fr_draw_bitmap(tpdata, _fdt_dist, 0, -1, -1);
+ _fr_draw_bitmap(tpdata, _fdt_dist, 0, -1, -1, 0, 0);
}
} break;
--- a/src/Libraries/3D/Source/Bitmap.c 2026-07-19 13:11:51.000000000 +0200
+++ b/src/Libraries/3D/Source/Bitmap.c 2026-09-27 20:44:47.378954871 +0200
@@ -88,6 +88,14 @@
// fix _g3d_bitmap_y_iscale = 0x010000;
long _g3d_bitmap_u_anchor = 0;
long _g3d_bitmap_v_anchor = 0;
+// Non-zero overrides the bitmap's own w/h in g3_bitmap_common()'s world-space
+// size computation below, decoupling a bitmap's on-screen size from its
+// actual pixel dimensions - set by g3_light_anchor_bitmap(), consumed and
+// reset back to 0 by g3_bitmap_common() so it never leaks into an unrelated
+// bitmap drawn via a different entry point (g3_bitmap()/g3_light_bitmap()/
+// do_bitmap() never set these).
+short _g3d_bitmap_w_override = 0;
+short _g3d_bitmap_h_override = 0;
fix _g3d_roll_matrix[6];
uchar *_g3d_bitmap_clut;
int _g3d_light_flag;
@@ -132,10 +140,13 @@
return (g3_bitmap_common(bm, (g3s_phandle)p));
}
-grs_vertex **g3_light_anchor_bitmap(grs_bitmap *bm, g3s_phandle p, short u_anchor, short v_anchor) {
+grs_vertex **g3_light_anchor_bitmap(grs_bitmap *bm, g3s_phandle p, short u_anchor, short v_anchor, short w_override,
+ short h_override) {
_g3d_light_flag = 2;
_g3d_bitmap_u_anchor = u_anchor;
_g3d_bitmap_v_anchor = v_anchor;
+ _g3d_bitmap_w_override = w_override;
+ _g3d_bitmap_h_override = h_override;
_g3d_bitmap_clut = (p->i & 0x00ff00) + grd_screen->ltab;
_g3d_bitmap_poly = vpl;
return (g3_bitmap_common(bm, p));
@@ -185,6 +196,15 @@
long rm2;
long rm3;
+ // Read and reset right at entry, before any early return below - a
+ // pending override must never survive to affect a later, unrelated
+ // bitmap draw that doesn't set it itself (see _g3d_bitmap_w_override's
+ // own comment).
+ short w_override = _g3d_bitmap_w_override;
+ short h_override = _g3d_bitmap_h_override;
+ _g3d_bitmap_w_override = 0;
+ _g3d_bitmap_h_override = 0;
+
#ifdef stereo_on
if (_g3d_stereo & 1) {
@@ -209,9 +229,11 @@
if ((p->codes & CC_CLIP_OVERFLOW) != 0)
return (0L);
- // copy a few things into locals
- bm_w = bm->w;
- bm_h = bm->h;
+ // copy a few things into locals - w_override/h_override, if set (see
+ // _g3d_bitmap_w_override's own comment), take the place of the bitmap's
+ // own pixel dimensions here.
+ bm_w = w_override ? w_override : bm->w;
+ bm_h = h_override ? h_override : bm->h;
tempL = p->gZ; // mov eax,[edi].z
tempL <<= 8; // sal eax,8 ;should be
--- a/src/Libraries/3D/Source/3d.h 2026-07-19 13:11:51.000000000 +0200
+++ b/src/Libraries/3D/Source/3d.h 2026-09-27 19:42:26.528424908 +0200
@@ -720,7 +720,8 @@
grs_vertex **g3_light_bitmap(grs_bitmap *bm, g3s_phandle p);
// MLA #pragma aux g3_light_bitmap "*" parm [esi] [edi] modify [eax ebx ecx
// edx];
-grs_vertex **g3_light_anchor_bitmap(grs_bitmap *bm, g3s_phandle p, short u_anchor, short v_anchor);
+grs_vertex **g3_light_anchor_bitmap(grs_bitmap *bm, g3s_phandle p, short u_anchor, short v_anchor, short w_override,
+ short h_override);
// MLA #pragma aux g3_light_anchor_bitmap "*" parm [esi] [edi] [eax] [edx]
// modify [eax ebx ecx edx];
grs_vertex **g3_full_light_bitmap(grs_bitmap *bm, grs_vertex **p);