From f92e5e900eef391b1c1600023e4bc1dde8f0122b Mon Sep 17 00:00:00 2001 From: Kp Date: Sat, 3 Jun 2023 23:13:26 +0000 Subject: [PATCH] Recover from invalid control_center_triggers.num_links Use of `partial_range` will trap invalid counts, but will throw an exception and terminate the program. Change the logic to log a diagnostic and clamp the count to the maximum legal value, so that users can play the level. The level may still be impossible to complete, if the triggers within the valid range do not open an exit door. However, level authors always have the ability to get that wrong, so the logic with recovery is better than the logic without it. Also, add a new diagnostic for invalid side in a control center trigger, and a new diagnostic for invalid segment in a control center trigger. For the latter, add a special case to reduce the severity when playing `Descent 2: Vertigo`, since there is a known bad trigger in a Vertigo level, and users cannot fix it. --- similar/main/cntrlcen.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/similar/main/cntrlcen.cpp b/similar/main/cntrlcen.cpp index 6d8df3448..366e28592 100644 --- a/similar/main/cntrlcen.cpp +++ b/similar/main/cntrlcen.cpp @@ -592,10 +592,16 @@ void control_center_triggers_read(control_center_triggers &cct, PHYSFS_File *fp) { const v1_control_center_triggers v1cct{fp}; cct = {}; - const auto num_links = v1cct.num_links; + const std::size_t num_links{v1cct.num_links}; if (unlikely(!num_links)) return; - const auto &&cct_input_range = zip(partial_range(v1cct.seg, num_links), v1cct.side); + /* num_links is derived from level data, which may be invalid. + */ + constexpr std::size_t maximum_allowed_links{std::size(v1cct.seg)}; + const auto clamped_num_links{std::min(num_links, maximum_allowed_links)}; + if (unlikely(clamped_num_links != num_links)) + LevelError("too many control center triggers (found %" DXX_PRI_size_type ", max %" DXX_PRI_size_type "); ignoring excess triggers.", num_links, maximum_allowed_links); + const auto &&cct_input_range = zip(partial_range(v1cct.seg, clamped_num_links), v1cct.side); const auto &&cct_output_range = zip(cct.seg, cct.side); auto oi = cct_output_range.begin(); auto ii = cct_input_range.begin(); @@ -607,7 +613,10 @@ void control_center_triggers_read(control_center_triggers &cct, PHYSFS_File *fp) const auto &&[iseg, iside] = *ii; const auto si = build_sidenum_from_untrusted(iside); if (!si) + { + LevelError("invalid segment side %u in control center trigger; ignoring.", iside); continue; + } /* Descent 2: Vertigo level 10 specifies an invalid control center trigger. * seg[0] is 0x257, but the level only has 0x1ae segments defined. * Attempting to access segment 0x257 is undefined behavior, and will crash @@ -615,7 +624,16 @@ void control_center_triggers_read(control_center_triggers &cct, PHYSFS_File *fp) * structure at load time. */ if (iseg >= segment_count) + { + /* When playing Vertigo, only log at verbose level, since users + * cannot fix the level. + * + * For any other mission, log at urgent level, so that level + * authors will fix their level. + */ + LevelErrorV(!strcmp(Current_mission->briefing_text_filename, "d2x.txb") ? CON_VERBOSE : CON_URGENT, "invalid segment index %u in control center trigger; ignoring.", iseg); continue; + } auto &&[oseg, oside] = *oi; ++ oi; oseg = iseg;