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/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)] 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()