From 1c31e4b68c51a8dea5f72e6a6ef7cef3a613b7dd Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sat, 20 Apr 2024 10:23:36 +0100 Subject: [PATCH] Change: Disallow using Action A to load sprites above the baseset unless reserved. (#12435) Using Action A above the baseset is error prone as the sprites are not fixed and can be moved around. Any NewGRF doing so is likely to break in the future, so force it to break instead. --- src/newgrf.cpp | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/newgrf.cpp b/src/newgrf.cpp index 662fc3e38b..77c46b61b0 100644 --- a/src/newgrf.cpp +++ b/src/newgrf.cpp @@ -370,7 +370,7 @@ struct GRFLocation { } }; -static std::map _grm_sprites; +static std::map> _grm_sprites; typedef std::map> GRFLineToSpriteOverride; static GRFLineToSpriteOverride _grf_line_to_action6_sprite_override; @@ -7072,6 +7072,21 @@ static void GRFInfo(ByteReader *buf) Debug(grf, 1, "GRFInfo: Loaded GRFv{} set {:08X} - {} (palette: {}, version: {})", version, BSWAP32(grfid), name, (_cur.grfconfig->palette & GRFP_USE_MASK) ? "Windows" : "DOS", _cur.grfconfig->version); } +/** + * Check if a sprite ID range is within the GRM reversed range for the currently loading NewGRF. + * @param first_sprite First sprite of range. + * @param num_sprites Number of sprites in the range. + * @return True iff the NewGRF has reserved a range equal to or greater than the provided range. + */ +static bool IsGRMReservedSprite(SpriteID first_sprite, uint16_t num_sprites) +{ + for (const auto &grm_sprite : _grm_sprites) { + if (grm_sprite.first.grfid != _cur.grffile->grfid) continue; + if (grm_sprite.second.first <= first_sprite && grm_sprite.second.first + grm_sprite.second.second >= first_sprite + num_sprites) return true; + } + return false; +} + /* Action 0x0A */ static void SpriteReplace(ByteReader *buf) { @@ -7093,6 +7108,18 @@ static void SpriteReplace(ByteReader *buf) i, num_sprites, first_sprite ); + if (first_sprite + num_sprites >= SPR_OPENTTD_BASE) { + /* Outside allowed range, check for GRM sprite reservations. */ + if (!IsGRMReservedSprite(first_sprite, num_sprites)) { + GrfMsg(0, "SpriteReplace: [Set {}] Changing {} sprites, beginning with {}, above limit of {} and not within reserved range, ignoring.", + i, num_sprites, first_sprite, SPR_OPENTTD_BASE); + + /* Load the sprites at the current location so they will do nothing instead of appearing to work. */ + first_sprite = _cur.spriteid; + _cur.spriteid += num_sprites; + } + } + for (uint j = 0; j < num_sprites; j++) { int load_index = first_sprite + j; _cur.nfo_line++; @@ -7460,7 +7487,7 @@ static void ParamSet(ByteReader *buf) /* Reserve space at the current sprite ID */ GrfMsg(4, "ParamSet: GRM: Allocated {} sprites at {}", count, _cur.spriteid); - _grm_sprites[GRFLocation(_cur.grffile->grfid, _cur.nfo_line)] = _cur.spriteid; + _grm_sprites[GRFLocation(_cur.grffile->grfid, _cur.nfo_line)] = std::make_pair(_cur.spriteid, count); _cur.spriteid += count; } } @@ -7494,7 +7521,7 @@ static void ParamSet(ByteReader *buf) switch (op) { case 0: /* Return space reserved during reservation stage */ - src1 = _grm_sprites[GRFLocation(_cur.grffile->grfid, _cur.nfo_line)]; + src1 = _grm_sprites[GRFLocation(_cur.grffile->grfid, _cur.nfo_line)].first; GrfMsg(4, "ParamSet: GRM: Using pre-allocated sprites at {}", src1); break;