From 36b2f65484f5da718a07b581ee40eb0f7da9df51 Mon Sep 17 00:00:00 2001 From: ExylonDerMaster <135547280+ExylonDerMaster@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:07:02 +0200 Subject: [PATCH 1/2] fix(scc): catch panics at the scc_compile FFI boundary scc_compile is called in-process by RED4ext, directly inside the game's executable. A panic anywhere in the compiler (for example one triggered by a script that does not match the current game version) previously unwound straight across the extern C boundary into that native host, which is undefined behavior and crashed the whole game for us instead of just failing the compilation. Wrap the call in catch_unwind and turn a caught panic into a normal SccResult::Error so callers get a regular error message instead of a crash. --- scc/lib/src/api.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/scc/lib/src/api.rs b/scc/lib/src/api.rs index 9ca48e41..fc6d59d3 100644 --- a/scc/lib/src/api.rs +++ b/scc/lib/src/api.rs @@ -64,9 +64,26 @@ pub extern "C" fn scc_settings_disable_error_popup(settings: &mut SccSettings) { settings.show_error_popup = false; } +// A panic deep in the compiler (e.g. triggered by a script that doesn't match the current +// game version) used to unwind straight across this extern "C" boundary into the native host +// (RED4ext) - that's undefined behavior and crashed the whole game for us, instead of just +// failing the compilation with an error message. catch_unwind stops it here and turns it into +// a normal SccResult::Error. #[unsafe(no_mangle)] pub extern "C" fn scc_compile(settings: Box) -> Box { - compile(&settings) + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| compile(&settings))) { + Ok(result) => result, + Err(panic) => { + let message = panic + .downcast_ref::<&str>() + .map(|s| (*s).to_string()) + .or_else(|| panic.downcast_ref::().cloned()) + .unwrap_or_else(|| "unknown internal error".to_string()); + Box::new(SccResult::Error(anyhow::anyhow!( + "internal compiler error (this is a bug, please report it): {message}" + ))) + } + } } #[unsafe(no_mangle)] From 30f81d8b9378599878398d860830125d312b90a3 Mon Sep 17 00:00:00 2001 From: ExylonDerMaster <135547280+ExylonDerMaster@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:07:03 +0200 Subject: [PATCH 2/2] fix: keep scc-lib building on newer rustc, drop stdout duplication Two smaller fixes needed to get the panic-safety change above onto a working build: - Newer rustc misattributes a false-positive "unnecessary parentheses" lint onto the #[bitfield] fields in core/src/definition.rs and core/src/bundle.rs (the fields themselves have no parens - this is a lint bug tied to the modular_bitfield macro expansion). The workspace denies all warnings, so this otherwise breaks the build. Allow it locally in both files. - setup_logger() in scc/lib/src/lib.rs called duplicate_to_stdout(), which assumes a console is attached. scc_compile runs in-process inside the game's GUI executable, which has no console/stdout handle, so the write failed - and flexi_logger's own error-reporting path (which also goes through stdout) failed right after it, which is what actually caused flexi_logger itself to panic. That panic is what motivated the fix in the previous commit; without also removing duplicate_to_stdout here, scc_compile would still fail (now caught gracefully instead of crashing, but still failing) on every in-process invocation. File logging alone is unaffected by this and is all that's meaningful here anyway. --- core/src/bundle.rs | 4 ++++ core/src/definition.rs | 5 +++++ scc/lib/src/lib.rs | 8 ++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/core/src/bundle.rs b/core/src/bundle.rs index 21ed875e..ff7081d4 100644 --- a/core/src/bundle.rs +++ b/core/src/bundle.rs @@ -1,3 +1,7 @@ +// See core/src/definition.rs for why this is here: the #[bitfield] macro (modular_bitfield) +// triggers a false-positive "unnecessary parentheses" lint on this rustc version. +#![allow(unused_parens)] + use std::hash::Hash; use std::io::Seek; use std::marker::PhantomData; diff --git a/core/src/definition.rs b/core/src/definition.rs index 895e1a0f..5539143c 100644 --- a/core/src/definition.rs +++ b/core/src/definition.rs @@ -1,3 +1,8 @@ +// The #[bitfield] macro (modular_bitfield) below generates code that this rustc version +// misreports as "unnecessary parentheses" on the annotated fields themselves; none of the +// fields actually contain parens, so this is a lint false positive tied to the macro expansion. +#![allow(unused_parens)] + use std::path::PathBuf; use std::{fmt, io}; diff --git a/scc/lib/src/lib.rs b/scc/lib/src/lib.rs index f3c7fbbb..3b6ab7cc 100644 --- a/scc/lib/src/lib.rs +++ b/scc/lib/src/lib.rs @@ -9,7 +9,7 @@ use std::{fmt, io, iter, vec}; use anyhow::Context; use api::{SccOutput, SccResult, SccSettings}; use fd_lock::RwLock; -use flexi_logger::{Age, Cleanup, Criterion, Duplicate, FileSpec, LogSpecBuilder, Logger, Naming}; +use flexi_logger::{Age, Cleanup, Criterion, FileSpec, LogSpecBuilder, Logger, Naming}; use hashbrown::{HashMap, HashSet}; use hints::UserHints; use log::LevelFilter; @@ -239,11 +239,15 @@ fn try_compile_files( } } +// FIX (2026-08-03): scc_compile runs in-process inside the game's GUI executable, which has no +// console and thus no valid stdout handle. duplicate_to_stdout tried to write there anyway, and +// when that write failed flexi_logger's own error-reporting path (which also goes through +// stdout) failed too, causing flexi_logger to panic ("error output channel itself is broken"). +// File logging alone is all that's meaningful here anyway - nothing reads scc's stdout in-game. fn setup_logger(r6_dir: &Path) { let file = FileSpec::default().directory(r6_dir.join("logs")).basename("redscript"); Logger::with(LogSpecBuilder::new().default(LevelFilter::Info).build()) .log_to_file(file) - .duplicate_to_stdout(Duplicate::All) .rotate(Criterion::Age(Age::Day), Naming::Timestamps, Cleanup::KeepLogFiles(4)) .format(|out, time, msg| write!(out, "[{} - {}] {}", msg.level(), time.now().to_rfc2822(), msg.args())) .start()