Skip to content

Commit 6103c21

Browse files
chief1983claude
andcommitted
Fix standalone crash when client disconnects before mission loads
When a client connects to a standalone server and backs out before a mission is loaded, multi_standalone_reset_all() calls game_level_close() which fires OnMissionAboutToEnd/OnMissionEnd scripting hooks. If any mod script calls mn.evaluateSEXP() from those hooks, alloc_sexp() crashes because the SEXP system was never initialized (Locked_sexp_true/false are still -1, Sexp_nodes is nullptr). Guard multi_standalone_reset_all() to only call game_level_close() when GM_IN_MISSION is set. Additionally, guard evaluateSEXP, evaluateNumericSEXP, and runSEXP to gracefully return with a warning when the SEXP system is not initialized, rather than crashing. Fixes #7353 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 43a475a commit 6103c21

2 files changed

Lines changed: 22 additions & 2 deletions

File tree

code/network/multi.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,8 +1671,13 @@ void multi_standalone_reset_all()
16711671
// NETLOG
16721672
ml_string(NOX("Standalone resetting"));
16731673

1674-
// shut all game stuff down
1675-
game_level_close();
1674+
// shut all game stuff down -- but only if a mission was actually in progress,
1675+
// otherwise scripting hooks in game_level_close() may fire into uninitialized
1676+
// subsystems (e.g. SEXP nodes never allocated because no mission was loaded)
1677+
if (Game_mode & GM_IN_MISSION) {
1678+
game_level_close();
1679+
Game_mode &= ~GM_IN_MISSION;
1680+
}
16761681

16771682
// reinitialize the gui
16781683
std_reset_standalone_gui();

code/scripting/api/libs/mission.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ ADE_FUNC(evaluateSEXP, l_Mission, "string", "Runs the defined SEXP script, and r
201201
if(!ade_get_args(L, "s", &s))
202202
return ADE_RETURN_FALSE;
203203

204+
if (Sexp_nodes == nullptr) {
205+
Warning(LOCATION, "evaluateSEXP called before SEXP system initialized; returning false");
206+
return ADE_RETURN_FALSE;
207+
}
208+
204209
r_val = run_sexp(s);
205210

206211
if (r_val == SEXP_TRUE)
@@ -218,6 +223,11 @@ ADE_FUNC(evaluateNumericSEXP, l_Mission, "string", "Runs the defined SEXP script
218223
if (!ade_get_args(L, "s", &s))
219224
return ade_set_args(L, "i", 0);
220225

226+
if (Sexp_nodes == nullptr) {
227+
Warning(LOCATION, "evaluateNumericSEXP called before SEXP system initialized; returning NaN");
228+
return ade_set_args(L, "f", std::numeric_limits<float>::quiet_NaN());
229+
}
230+
221231
r_val = run_sexp(s, true, &got_nan);
222232

223233
if (got_nan)
@@ -235,6 +245,11 @@ ADE_FUNC(runSEXP, l_Mission, "string", "Runs the defined SEXP script within a `w
235245
if(!ade_get_args(L, "s", &s))
236246
return ADE_RETURN_FALSE;
237247

248+
if (Sexp_nodes == nullptr) {
249+
Warning(LOCATION, "runSEXP called before SEXP system initialized; returning false");
250+
return ADE_RETURN_FALSE;
251+
}
252+
238253
while (is_white_space(*s))
239254
s++;
240255
if (*s != '(')

0 commit comments

Comments
 (0)