From a43ebcdd642437078e7fdefa8e2eeeed5e95180f Mon Sep 17 00:00:00 2001 From: Mark Kropf Date: Thu, 18 Jun 2026 19:58:36 +0100 Subject: [PATCH 01/13] SolLua: use sol::lua_nil instead of the sol::nil alias A handful of SolLua bindings still referenced sol::nil / sol::type::nil while the rest of the same files already use sol::lua_nil. sol::nil is a thin alias for lua_nil (see lib/sol2/sol.hpp) and collides with the `nil` macro that Objective-C headers define, so lua_nil is the portable spelling. This brings the remaining spots in line with the prevailing convention. Extracted from cc3cad907f in #2991; cross-platform, no behavior change. --- rts/Rml/SolLua/bind/Context.cpp | 6 +++--- rts/Rml/SolLua/bind/Element.cpp | 2 +- rts/Rml/SolLua/bind/bind.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rts/Rml/SolLua/bind/Context.cpp b/rts/Rml/SolLua/bind/Context.cpp index db0dcb8960b..bd40fd4e535 100644 --- a/rts/Rml/SolLua/bind/Context.cpp +++ b/rts/Rml/SolLua/bind/Context.cpp @@ -131,7 +131,7 @@ struct lua_iterator_state sol::state_view l{s}; int index = 0; int count = keytable.size(); - while (keytable.get(++index).get_type() != sol::type::nil && index <= count) { + while (keytable.get(++index).get_type() != sol::type::lua_nil && index <= count) { this->keys.emplace_back(sol::object(l, sol::in_place, index)); } } else { @@ -199,7 +199,7 @@ createNewIndexFunction(std::shared_ptr data, const } if (value.is()) { auto value_raw = value.as().raw_get("__raw"); - if (value_raw != sol::nil && value_raw.is()) { + if (value_raw != sol::lua_nil && value_raw.is()) { // new value is a datamodel proxy, so get the underlying table to assign prop.as().raw_set(solkey, value_raw.as().call(value)); } else { @@ -290,7 +290,7 @@ sol::table openDataModel(Rml::Context& self, const Rml::String& name, sol::objec } if (value.is()) { auto value_raw = value.as().raw_get("__raw"); - if (value_raw != sol::nil && value_raw.is()) { + if (value_raw != sol::lua_nil && value_raw.is()) { // new value is a datamodel proxy, so get the underlying table to assign data->Table.raw_set(key, value_raw.as().call(value)); } else { diff --git a/rts/Rml/SolLua/bind/Element.cpp b/rts/Rml/SolLua/bind/Element.cpp index e2edf19159a..3bf3ecc7fea 100644 --- a/rts/Rml/SolLua/bind/Element.cpp +++ b/rts/Rml/SolLua/bind/Element.cpp @@ -158,7 +158,7 @@ namespace Rml::SolLua void Set(const sol::this_state L, const std::string& name, const sol::object& value) { - if (value.get_type() == sol::type::nil) { + if (value.get_type() == sol::type::lua_nil) { m_element->RemoveProperty(name); return; } diff --git a/rts/Rml/SolLua/bind/bind.cpp b/rts/Rml/SolLua/bind/bind.cpp index 21eabe9d46c..98ce9fec37c 100644 --- a/rts/Rml/SolLua/bind/bind.cpp +++ b/rts/Rml/SolLua/bind/bind.cpp @@ -39,7 +39,7 @@ namespace Rml::SolLua sol::object makeObjectFromVariant(const Rml::Variant* variant, sol::state_view s) { - if (!variant) return sol::make_object(s, sol::nil); + if (!variant) return sol::make_object(s, sol::lua_nil); switch (variant->GetType()) { @@ -69,10 +69,10 @@ namespace Rml::SolLua case Rml::Variant::VOIDPTR: return sol::make_object(s, variant->Get()); default: - return sol::make_object(s, sol::nil); + return sol::make_object(s, sol::lua_nil); } - return sol::make_object(s, sol::nil); + return sol::make_object(s, sol::lua_nil); } } // end namespace Rml::SolLua From 3e83e20c2805b09c1165442e4df428fcb935a7a5 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Thu, 18 Jun 2026 20:17:54 +0100 Subject: [PATCH 02/13] float3: drop redundant direct streflop_cond.h include float3.h included lib/streflop/streflop_cond.h directly, ahead of FastMath.h. FastMath.h defines MATH_SQRT_OVERRIDE before it includes streflop_cond.h (so streflop does not define its own math::sqrt(float) -- FastMath provides a faster one). The direct include meant streflop_cond.h could be processed before that override was set. Drop the direct include; FastMath.h pulls in streflop_cond.h transitively with the override in place. creg_cond.h keeps its original position -- it does not pull in streflop, so it does not need to move. Surfaced by the macOS port (#2991, commit cc3cad907f). Co-authored-by: Mark Kropf --- rts/System/float3.h | 1 - 1 file changed, 1 deletion(-) diff --git a/rts/System/float3.h b/rts/System/float3.h index f096c112c0d..bf47dd90c2a 100644 --- a/rts/System/float3.h +++ b/rts/System/float3.h @@ -9,7 +9,6 @@ #include #include "System/BranchPrediction.h" -#include "lib/streflop/streflop_cond.h" #include "System/creg/creg_cond.h" #include "System/FastMath.h" #include "System/type2.h" From 517608f4076b2071742badd6a90eab3dc6262828 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Fri, 19 Jun 2026 00:58:16 +0100 Subject: [PATCH 03/13] SafeUtil: include and directly (#3025) `SafeUtil.h` uses `` for `std::addressof`, and `` for `std::is_trivially_copyable` / `std::is_trivially_constructible_v`, but pulled them in only transitively. Include them directly so the header is self-contained and does not rely on include order elsewhere. Also use `std::is_trivially_default_constructible` for the default-construction. Extracted from cc3cad907f in #2991; cross-platform, no behavior change. Co-authored-by: Mark Kropf Co-authored-by: sprunk --- rts/System/SafeUtil.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rts/System/SafeUtil.h b/rts/System/SafeUtil.h index ce19b3f9738..9fbf946cc82 100644 --- a/rts/System/SafeUtil.h +++ b/rts/System/SafeUtil.h @@ -5,6 +5,8 @@ #include #include +#include +#include namespace spring { template inline void SafeDestruct(T*& p) @@ -112,8 +114,8 @@ namespace spring { static_assert(sizeof(TIn) == sizeof(TOut), "Types must match sizes"); static_assert(std::is_trivially_copyable::value , "Requires TriviallyCopyable input"); static_assert(std::is_trivially_copyable::value, "Requires TriviallyCopyable output"); - static_assert(std::is_trivially_constructible_v, - "This implementation additionally requires destination type to be trivially constructible"); + static_assert(std::is_trivially_default_constructible::value, + "This implementation additionally requires destination type to be trivially default-constructible"); TOut t2; std::memcpy(std::addressof(t2), std::addressof(t1), sizeof(TIn)); From 6318fb6c445daddeb1c9f4dea27e69bfb13847e1 Mon Sep 17 00:00:00 2001 From: iamaperson000 Date: Thu, 18 Jun 2026 19:49:28 +0100 Subject: [PATCH 04/13] LuaTextures: log glTexImage failures instead of returning nil silently LuaTextures::Create returned an empty string on glTexImage failure with no diagnostics. Log target/size/format/dataFormat/dataType/glError so texture-creation failures can be diagnosed. Extracted from 1e75080731 in #2991; cross-platform, no behavior change beyond the added log. --- rts/Lua/LuaTextures.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rts/Lua/LuaTextures.cpp b/rts/Lua/LuaTextures.cpp index 8d3a083dbef..0ae691d63c0 100644 --- a/rts/Lua/LuaTextures.cpp +++ b/rts/Lua/LuaTextures.cpp @@ -90,7 +90,9 @@ std::string LuaTextures::Create(const Texture& tex) } break; } - if (glGetError() != GL_NO_ERROR) { + if (const GLenum texErr = glGetError(); texErr != GL_NO_ERROR) { + LOG_L(L_ERROR, "[LuaTextures::%s] glTexImage failed: target=0x%x size=%dx%d fmt=0x%x dataFmt=0x%x dataType=0x%x border=%d glError=0x%x", + __func__, tex.target, tex.xsize, tex.ysize, tex.format, dataFormat, dataType, tex.border, texErr); glDeleteTextures(1, &texID); glBindTexture(tex.target, currentBinding); return ""; From 54348ff04d3fa6d1583224d3568cd2cf5825a2ab Mon Sep 17 00:00:00 2001 From: bruno-dasilva <8520801+bruno-dasilva@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:11:02 -0700 Subject: [PATCH 05/13] fix: guard against already-dead reclaim targets (#3020) Engine crash thread: https://discordapp.com/channels/549281623154229250/1516994684591931535 Bugged Scenario: A reclaimer A is guarding another reclaimer B, while both reclaiming the same wreck. A can be notified of wreck death BEFORE the guarded unit B is notified, leading to trying to reclaim the same (actively-deleting) wreck because B's reference hasn't been cleaned up yet. This leads to an eventual segfault. So: the short term/easy fix is to skip reclaiming of a dead/dying target. --- rts/Sim/Units/CommandAI/BuilderCAI.cpp | 4 ++-- rts/Sim/Units/UnitTypes/Builder.cpp | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/rts/Sim/Units/CommandAI/BuilderCAI.cpp b/rts/Sim/Units/CommandAI/BuilderCAI.cpp index b747e342615..e1faddb7dd6 100644 --- a/rts/Sim/Units/CommandAI/BuilderCAI.cpp +++ b/rts/Sim/Units/CommandAI/BuilderCAI.cpp @@ -891,13 +891,13 @@ void CBuilderCAI::ExecuteGuard(Command& c) StopSlowGuard(); } return; - } else if (b->curReclaim && owner->unitDef->canReclaim) { + } else if (b->curReclaim && !b->curReclaim->detached && owner->unitDef->canReclaim) { StopSlowGuard(); if (!ReclaimObject(b->curReclaim)) { StopMove(); } return; - } else if (b->curResurrect && owner->unitDef->canResurrect) { + } else if (b->curResurrect && !b->curResurrect->detached && owner->unitDef->canResurrect) { StopSlowGuard(); if (!ResurrectObject(b->curResurrect)) { StopMove(); diff --git a/rts/Sim/Units/UnitTypes/Builder.cpp b/rts/Sim/Units/UnitTypes/Builder.cpp index 4cf3240b1cd..deb8c846b73 100644 --- a/rts/Sim/Units/UnitTypes/Builder.cpp +++ b/rts/Sim/Units/UnitTypes/Builder.cpp @@ -605,6 +605,15 @@ void CBuilder::SetRepairTarget(CUnit* target) void CBuilder::SetReclaimTarget(CSolidObject* target) { RECOIL_DETAILED_TRACY_ZONE; + // A target already being destroyed (detached) cannot have a death dependence + // registered (AddDeathDependence no-ops on detached objects), which would leave + // curReclaim dangling. This happens when ~CObject's DependentDied cascade re-enters + // reclaim logic mid-deletion (e.g. CBuilderCAI::ExecuteGuard reading a guardee's + // stale curReclaim) on the very feature being freed. Refuse the dead target. + assert(target != nullptr); + if (target->detached) + return; + if (dynamic_cast(target) != nullptr && !static_cast(target)->def->reclaimable) return; @@ -630,6 +639,11 @@ void CBuilder::SetReclaimTarget(CSolidObject* target) void CBuilder::SetResurrectTarget(CFeature* target) { RECOIL_DETAILED_TRACY_ZONE; + // see SetReclaimTarget: never depend on an object that is already being destroyed + assert(target != nullptr); + if (target->detached) + return; + if (curResurrect == target || target->udef == nullptr) return; @@ -646,6 +660,11 @@ void CBuilder::SetResurrectTarget(CFeature* target) void CBuilder::SetCaptureTarget(CUnit* target) { RECOIL_DETAILED_TRACY_ZONE; + // see SetReclaimTarget: never depend on an object that is already being destroyed + assert(target != nullptr); + if (target->detached) + return; + if (target == curCapture) return; From e91a58f5c57dcdd25df3e51aac911ce3b481179f Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Sat, 20 Jun 2026 14:14:07 +0100 Subject: [PATCH 06/13] CUtils: use const dirent* for the scandir selector on all platforms util_fileSelector was declared with a non-const struct dirent* on __APPLE__ and const elsewhere. macOS scandir() expects the selector argument as int(*)(const struct dirent*), so the non-const Apple variant failed to compile under GCC: Util.c:500: error: passing argument 3 of 'scandir' from incompatible pointer type Both branches were otherwise identical, so drop the __APPLE__ split and use const struct dirent* unconditionally (matches POSIX scandir). No effect on Linux/Windows. Assisted by Claude Code; verified by compiling the macOS headless build. --- AI/Wrappers/CUtils/Util.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/AI/Wrappers/CUtils/Util.c b/AI/Wrappers/CUtils/Util.c index c33d2e418e8..4764a619900 100644 --- a/AI/Wrappers/CUtils/Util.c +++ b/AI/Wrappers/CUtils/Util.c @@ -487,11 +487,7 @@ static void util_initFileSelector(const char* suffix) { fileSelectorSuffix = suffix; } -#if defined(__APPLE__) -static int util_fileSelector(struct dirent* fileDesc) { -#else static int util_fileSelector(const struct dirent* fileDesc) { -#endif return util_endsWith(fileDesc->d_name, fileSelectorSuffix); } From 580100a0dfeb3fb2f6203ad0cf7bf077f212b6d4 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Sat, 20 Jun 2026 14:12:15 +0100 Subject: [PATCH 07/13] legacy: only require X11 on non-Apple UNIX The legacy build did find_package(X11 REQUIRED) under a plain if(UNIX) guard. macOS is UNIX in CMake but does not use X11 (it uses Cocoa), so configuring the engine on macOS failed at find_package(X11). An if(APPLE) block already follows for Foundation, so exclude Apple from the X11 branch: if(UNIX AND NOT APPLE). Surfaced configuring the spring-headless target on macOS (which reuses the legacy Game target). No effect on Linux/Windows. Assisted by Claude Code; verified by configuring the macOS build. --- rts/builds/legacy/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rts/builds/legacy/CMakeLists.txt b/rts/builds/legacy/CMakeLists.txt index cbcc906f83d..f1d19ede7e3 100644 --- a/rts/builds/legacy/CMakeLists.txt +++ b/rts/builds/legacy/CMakeLists.txt @@ -49,7 +49,7 @@ find_freetype_hack() # hack to find different named freetype.dll find_package_static(Freetype 2.8.1 REQUIRED) list(APPEND engineLibraries Freetype::Freetype) -if (UNIX) +if (UNIX AND NOT APPLE) find_package(X11 REQUIRED) target_link_libraries(Game PRIVATE X11::Xcursor) list(APPEND engineLibraries ${X11_Xcursor_LIB} ${X11_X11_LIB}) From d13dcd341361637cdf1ab4e65b0e8d6fa7b5ca21 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Sat, 20 Jun 2026 13:10:24 +0100 Subject: [PATCH 08/13] FindSevenZip: also accept the 7zz binary name Modern 7-Zip ships its CLI as '7zz' (Homebrew's 'sevenzip' formula installs /opt/homebrew/bin/7zz; recent Linux distros likewise package '7zz'). FindSevenZip only searched for '7z'/'7za', so configure failed with 'Could NOT find SevenZip (missing: SEVENZIP_BIN)' on such systems. Add '7zz' to the searched NAMES. No effect where 7z/7za already exist. --- rts/build/cmake/FindSevenZip.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rts/build/cmake/FindSevenZip.cmake b/rts/build/cmake/FindSevenZip.cmake index 1077ec7b17e..0e3e4002a67 100644 --- a/rts/build/cmake/FindSevenZip.cmake +++ b/rts/build/cmake/FindSevenZip.cmake @@ -23,7 +23,7 @@ ENDIF (SEVENZIP_BIN) set(progfilesx86 "ProgramFiles(x86)") find_program(SEVENZIP_BIN - NAMES 7z 7za + NAMES 7z 7za 7zz HINTS "${MINGWDIR}" "${MINGWLIBS}/bin" "$ENV{${progfilesx86}}/7-zip" "$ENV{ProgramFiles}/7-zip" "$ENV{ProgramW6432}/7-zip" PATH_SUFFIXES bin DOC "7zip executable" From 0da81b433a8c4e228a4ac02ac2e943bd0c5aa078 Mon Sep 17 00:00:00 2001 From: Tom J Nowell Date: Fri, 19 Jun 2026 23:55:16 +0100 Subject: [PATCH 09/13] macOS: remove dead SDL 1.2-era SDLMain.{m,h} rts/System/Platform/Mac/SDLMain.m and SDLMain.h are the classic SDL 1.2 Cocoa main wrapper (the Darrell Walisser / Max Horn template). They are: - not listed in any CMakeLists (never compiled) - not #included by any source file - built on Carbon (), which is 32-bit-only and unavailable on modern macOS / Apple Silicon SDL2 provides its own SDL_main, so this wrapper is obsolete. Remove the dead files so the macOS platform layer reflects what is actually built. --- rts/System/Platform/Mac/SDLMain.h | 18 -- rts/System/Platform/Mac/SDLMain.m | 299 ------------------------------ 2 files changed, 317 deletions(-) delete mode 100644 rts/System/Platform/Mac/SDLMain.h delete mode 100644 rts/System/Platform/Mac/SDLMain.m diff --git a/rts/System/Platform/Mac/SDLMain.h b/rts/System/Platform/Mac/SDLMain.h deleted file mode 100644 index 995d036c3f0..00000000000 --- a/rts/System/Platform/Mac/SDLMain.h +++ /dev/null @@ -1,18 +0,0 @@ -/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ - -/* - * SDLMain.m - main entry point for our Cocoa-ized SDL app - * Initial Version: Darrell Walisser - * Non-NIB-Code & other changes: Max Horn - * Feel free to customize this file to suit your needs. - */ - -#ifdef __APPLE__ - -#import - -@interface SDLMain : NSObject -@end - -#endif - diff --git a/rts/System/Platform/Mac/SDLMain.m b/rts/System/Platform/Mac/SDLMain.m deleted file mode 100644 index 2fea69074ad..00000000000 --- a/rts/System/Platform/Mac/SDLMain.m +++ /dev/null @@ -1,299 +0,0 @@ -/* SDLMain.m - main entry point for our Cocoa-ized SDL app - Initial Version: Darrell Walisser - Non-NIB-Code & other changes: Max Horn - - Feel free to customize this file to suit your needs -*/ - -#import "SDL.h" -#import "SDLMain.h" -#import /* for MAXPATHLEN */ -#import -#import - -/* Use this flag to determine whether we use SDLMain.nib or not */ -#define SDL_USE_NIB_FILE 0 - - -static int gArgc; -static char **gArgv; -static BOOL gFinderLaunch; - -//extern NSAutoreleasePool *pool; -//void PreInitMac(); - -void MacMessageBox(const char *msg, const char *caption, unsigned int flags){ - NSAlert *alert = [[[NSAlert alloc] init] autorelease]; - [alert addButtonWithTitle:@"OK"]; - [alert setMessageText:[NSString stringWithCString:caption]]; - [alert setInformativeText:[NSString stringWithCString:msg]]; - [alert setAlertStyle:NSWarningAlertStyle]; - [alert runModal]; -} - -#if SDL_USE_NIB_FILE -/* A helper category for NSString */ -@interface NSString (ReplaceSubString) -- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; -@end -#else -/* An internal Apple class used to setup Apple menus */ -@interface NSAppleMenuController:NSObject {} -- (void)controlMenu:(NSMenu *)aMenu; -@end -#endif - -@interface SDLApplication : NSApplication -@end - -@implementation SDLApplication -/* Invoked from the Quit menu item */ -- (void)terminate:(id)sender -{ - /* Post a SDL_QUIT event */ - SDL_Event event; - event.type = SDL_QUIT; - SDL_PushEvent(&event); -} -@end - - -/* The main class of the application, the application's delegate */ -@implementation SDLMain - -/* Set the working directory to the .app's parent directory */ -- (void) setupWorkingDirectory:(BOOL)shouldChdir -{ - char parentdir[MAXPATHLEN]; - char *c; - - strncpy ( parentdir, gArgv[0], sizeof(parentdir) ); - c = (char*) parentdir; - - while (*c != '\0') /* go to end */ - c++; - - while (*c != '/') /* back up to parent */ - c--; - - *c++ = '\0'; /* cut off last part (binary name) */ - - if (shouldChdir) - { - assert ( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */ - assert ( chdir ("../../../") == 0 ); /* chdir to the .app's parent */ - } -} - -#if SDL_USE_NIB_FILE - -/* Fix menu to contain the real app name instead of "SDL App" */ -- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName -{ - NSRange aRange; - NSEnumerator *enumerator; - NSMenuItem *menuItem; - - aRange = [[aMenu title] rangeOfString:@"SDL App"]; - if (aRange.length != 0) - [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; - - enumerator = [[aMenu itemArray] objectEnumerator]; - while ((menuItem = [enumerator nextObject])) - { - aRange = [[menuItem title] rangeOfString:@"SDL App"]; - if (aRange.length != 0) - [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; - if ([menuItem hasSubmenu]) - [self fixMenu:[menuItem submenu] withAppName:appName]; - } - [ aMenu sizeToFit ]; -} - -#else - -void setupAppleMenu(void) -{ - /* warning: this code is very odd */ - NSAppleMenuController *appleMenuController; - NSMenu *appleMenu; - NSMenuItem *appleMenuItem; - - appleMenuController = [[NSAppleMenuController alloc] init]; - appleMenu = [[NSMenu alloc] initWithTitle:@""]; - appleMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; - - [appleMenuItem setSubmenu:appleMenu]; - - /* yes, we do need to add it and then remove it -- - if you don't add it, it doesn't get displayed - if you don't remove it, you have an extra, titleless item in the menubar - when you remove it, it appears to stick around - very, very odd */ - [[NSApp mainMenu] addItem:appleMenuItem]; - [appleMenuController controlMenu:appleMenu]; - [[NSApp mainMenu] removeItem:appleMenuItem]; - [appleMenu release]; - [appleMenuItem release]; -} - -/* Create a window menu */ -void setupWindowMenu(void) -{ - NSMenu *windowMenu; - NSMenuItem *windowMenuItem; - NSMenuItem *menuItem; - - - windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; - - /* "Minimize" item */ - menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; - [windowMenu addItem:menuItem]; - [menuItem release]; - - /* Put menu into the menubar */ - windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; - [windowMenuItem setSubmenu:windowMenu]; - [[NSApp mainMenu] addItem:windowMenuItem]; - - /* Tell the application object that this is now the window menu */ - [NSApp setWindowsMenu:windowMenu]; - - /* Finally give up our references to the objects */ - [windowMenu release]; - [windowMenuItem release]; -} - -/* Replacement for NSApplicationMain */ -void CustomApplicationMain () -{ - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - SDLMain *sdlMain; - //PreInitMac(); - - /* Ensure the application object is initialised */ - [SDLApplication sharedApplication]; - - /* Set up the menubar */ - [NSApp setMainMenu:[[NSMenu alloc] init]]; - setupAppleMenu(); - setupWindowMenu(); - - /* Create SDLMain and make it the app delegate */ - sdlMain = [[SDLMain alloc] init]; - [NSApp setDelegate:sdlMain]; - - /* Bring the app to foreground */ - ProcessSerialNumber psn; - GetCurrentProcess(&psn); - TransformProcessType(&psn,kProcessTransformToForegroundApplication); - - /* Start the main event loop */ - [NSApp run]; - - [sdlMain release]; - [pool release]; -} - -#endif - -/* Called when the internal event loop has just started running */ -- (void) applicationDidFinishLaunching: (NSNotification *) note -{ - int status; - - /* Set the working directory to the .app's parent directory */ - [self setupWorkingDirectory:gFinderLaunch]; - -#if SDL_USE_NIB_FILE - /* Set the main menu to contain the real app name instead of "SDL App" */ - [self fixMenu:[NSApp mainMenu] withAppName:[[NSProcessInfo processInfo] processName]]; -#endif - - /* Hand off to main application code */ - status = SDL_main (gArgc, gArgv); - - /* We're done, thank you for playing */ - exit(status); -} -@end - - -@implementation NSString (ReplaceSubString) - -- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString -{ - unsigned int bufferSize; - unsigned int selfLen = [self length]; - unsigned int aStringLen = [aString length]; - unichar *buffer; - NSRange localRange; - NSString *result; - - bufferSize = selfLen + aStringLen - aRange.length; - buffer = NSAllocateMemoryPages(bufferSize*sizeof(unichar)); - - /* Get first part into buffer */ - localRange.location = 0; - localRange.length = aRange.location; - [self getCharacters:buffer range:localRange]; - - /* Get middle part into buffer */ - localRange.location = 0; - localRange.length = aStringLen; - [aString getCharacters:(buffer+aRange.location) range:localRange]; - - /* Get last part into buffer */ - localRange.location = aRange.location + aRange.length; - localRange.length = selfLen - localRange.location; - [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; - - /* Build output string */ - result = [NSString stringWithCharacters:buffer length:bufferSize]; - - NSDeallocateMemoryPages(buffer, bufferSize); - - return result; -} - -@end - - - -#ifdef main -# undef main -#endif - - -/* Main entry point to executable - should *not* be SDL_main! */ -int main (int argc, char **argv) -{ - - /* Copy the arguments into a global variable */ - int i; - - /* This is passed if we are launched by double-clicking */ - if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { - gArgc = 1; - gFinderLaunch = YES; - } else { - gArgc = argc; - gFinderLaunch = NO; - } - gArgv = (char**) malloc (sizeof(*gArgv) * (gArgc+1)); - assert (gArgv != NULL); - for (i = 0; i < gArgc; i++) - gArgv[i] = argv[i]; - gArgv[i] = NULL; - -#if SDL_USE_NIB_FILE - [SDLApplication poseAsClass:[NSApplication class]]; - NSApplicationMain (); -#else - CustomApplicationMain (); -#endif - return 0; -} - - From 83642b066f755ac182ef97ea540569c07e59d2ff Mon Sep 17 00:00:00 2001 From: Scary le Poo Date: Sun, 21 Jun 2026 13:12:50 -0700 Subject: [PATCH 10/13] Fix SplinterFaction card link format Updated the link for the SplinterFaction card to include the 'https://' prefix. This has been broken for a very long time. I've asked Skyrbunny to fix it multiple times, but it has never gotten fixed. --- doc/site/content/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/site/content/_index.md b/doc/site/content/_index.md index fcbbe83428f..0d0a8d3b14b 100644 --- a/doc/site/content/_index.md +++ b/doc/site/content/_index.md @@ -19,7 +19,7 @@ draft = false {{< /cards >}} {{< cards >}} {{< card title="Tech Annihilation" image="/showcase/ta.jpeg" link="https://github.com/techannihilation/TA" >}} -{{< card title="SplinterFaction" image="showcase/splinter_faction.jpg" link="splinterfaction.info" >}} +{{< card title="SplinterFaction" image="showcase/splinter_faction.jpg" link="https://splinterfaction.info" >}} {{< card title="Mechcommander: Legacy" image="/showcase/mcl.jpg" link="https://github.com/SpringMCLegacy/SpringMCLegacy/wiki" >}} {{< /cards >}} From 13b6804534543c061d0eb8c06af438cb7d2a1c64 Mon Sep 17 00:00:00 2001 From: bruno-dasilva <8520801+bruno-dasilva@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:38:03 -0700 Subject: [PATCH 11/13] fix: update section min-level in place instead of appending duplicates (#3052) setMinLevel() only searched for an existing entry on the "set back to default" (erase) path. Setting a section to a *non-default* level appended a new row unconditionally, so repeatedly changing one section's level (e.g. via Spring.SetLogSectionFilterLevel) accumulated duplicate entries and eventually filled the fixed 64-slot sectionMinLevels table -- after which every section-level change silently failed with "too many section-levels". Fix: Look the section up before appending and, if it already has an entry, update it in place. This bounds the table at one entry per section. This likely never happens in practice but this was found while writing other tests Co-authored-by: Bruno Da Silva Co-authored-by: Claude Opus 4.8 (1M context) --- rts/System/Log/DefaultFilter.cpp | 21 +++++++++++++------ test/engine/System/Log/TestILog.cpp | 31 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/rts/System/Log/DefaultFilter.cpp b/rts/System/Log/DefaultFilter.cpp index cf0c3939d29..3ec33410cf1 100644 --- a/rts/System/Log/DefaultFilter.cpp +++ b/rts/System/Log/DefaultFilter.cpp @@ -157,13 +157,15 @@ void log_filter_section_setMinLevel(int level, const char* section) // (same string but will not become garbage) section = *registeredSection; - if (level == log_filter_section_getDefaultMinLevel(section)) { - using P = decltype(log_filter::sectionMinLevels)::value_type; - - const auto sectionComparer = [](const P& a, const P& b) { return (log_filter_section_compare()(a.first, b.first)); }; - const auto sectionMinLevel = std::lower_bound(secLvls.begin(), secLvls.begin() + log_filter::numLevels, P{section, 0}, sectionComparer); + // locate any existing override for this section (the array is kept sorted) + using P = decltype(log_filter::sectionMinLevels)::value_type; + const auto sectionComparer = [](const P& a, const P& b) { return (log_filter_section_compare()(a.first, b.first)); }; + const auto sectionMinLevel = std::lower_bound(secLvls.begin(), secLvls.begin() + log_filter::numLevels, P{section, 0}, sectionComparer); + const bool exists = (sectionMinLevel != (secLvls.begin() + log_filter::numLevels) && strcmp(sectionMinLevel->first, section) == 0); - if (sectionMinLevel == (secLvls.begin() + log_filter::numLevels) || strcmp(sectionMinLevel->first, section) != 0) + if (level == log_filter_section_getDefaultMinLevel(section)) { + // back to default: drop any existing override (nothing to do otherwise) + if (!exists) return; // erase @@ -175,6 +177,13 @@ void log_filter_section_setMinLevel(int level, const char* section) return; } + // non-default: update any existing override in-place + if (exists) { + sectionMinLevel->second = level; + return; + } + + // add a net new override secLvls[log_filter::numLevels++] = {section, level}; // swap into position diff --git a/test/engine/System/Log/TestILog.cpp b/test/engine/System/Log/TestILog.cpp index cb8d4b3e4a1..7b7d1345f0c 100644 --- a/test/engine/System/Log/TestILog.cpp +++ b/test/engine/System/Log/TestILog.cpp @@ -3,6 +3,7 @@ #include "System/Log/FileSink.h" #include "System/Log/StreamSink.h" #include "System/Log/LogUtil.h" +#include "System/Log/DefaultFilter.h" #include @@ -231,3 +232,33 @@ TEST_CASE("IsEnabled") TLOG_SL( "other-one-time-section", L_DEBUG, "Testing LOG_IS_ENABLED_S"); } + +// Regression for the duplicate-entry leak in log_filter_section_setMinLevel. +// Setting a section to a non-default level used to *append* a new row every call +// instead of updating the existing one, so repeated changes to one section filled +// the fixed-size sectionMinLevels table and then made *all* section-level changes +// silently fail ("too many section-levels"). +TEST_CASE("SectionMinLevelNoDuplicateLeak") +{ + // non-default levels for these (non-default) sections; restored at the end + const int savedDefined = log_filter_section_getMinLevel(LOG_SECTION_DEFINED); + const int savedOneTime = log_filter_section_getMinLevel(LOG_SECTION_ONE_TIME_0); + + // hammer one section far more than the table could ever hold + for (int i = 0; i < 300; ++i) + log_filter_section_setMinLevel((i & 1) ? LOG_LEVEL_WARNING : LOG_LEVEL_ERROR, LOG_SECTION_DEFINED); + + // the most recent value wins (a single, updated-in-place entry) + log_filter_section_setMinLevel(LOG_LEVEL_ERROR, LOG_SECTION_DEFINED); + CHECK(log_filter_section_getMinLevel(LOG_SECTION_DEFINED) == LOG_LEVEL_ERROR); + + // and a *different* section must still be settable: with the old append bug + // the table is saturated by now and this set would be dropped + log_filter_section_setMinLevel(LOG_LEVEL_WARNING, LOG_SECTION_ONE_TIME_0); + CHECK(log_filter_section_getMinLevel(LOG_SECTION_ONE_TIME_0) == LOG_LEVEL_WARNING); + + // restore original levels (setting back to default takes the erase path) + log_filter_section_setMinLevel(savedDefined, LOG_SECTION_DEFINED); + log_filter_section_setMinLevel(savedOneTime, LOG_SECTION_ONE_TIME_0); +} + From 1caa3c52612c9741238f7a086e6d8696c7e77289 Mon Sep 17 00:00:00 2001 From: devgit283 Date: Tue, 23 Jun 2026 09:29:18 +0200 Subject: [PATCH 12/13] Convert `u8string_view` to `string` using explicit size The original code relied on implicit null-termination which is not guaranteed. --- rts/System/FileSystem/FileSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rts/System/FileSystem/FileSystem.cpp b/rts/System/FileSystem/FileSystem.cpp index d1471810f48..f319d2199ca 100644 --- a/rts/System/FileSystem/FileSystem.cpp +++ b/rts/System/FileSystem/FileSystem.cpp @@ -61,7 +61,7 @@ namespace Impl { return std::string(reinterpret_cast(utf8.c_str())); } RECOIL_FORCE_INLINE std::string StoreUTF8AsString(const std::u8string_view& utf8) { - return std::string(reinterpret_cast(utf8.data())); + return std::string(reinterpret_cast(utf8.data()), utf8.size()); } RECOIL_FORCE_INLINE std::string StorePathAsString(const fs::path& path) { return StoreUTF8AsString(path.u8string()); From 2186ee490c027eb2993ce2f67b98d4978ded142e Mon Sep 17 00:00:00 2001 From: devgit283 Date: Fri, 5 Jun 2026 21:57:29 +0200 Subject: [PATCH 13/13] Fix inconsistent class/struct forward declarations Aligns forward declarations with definitions to silence MSVC warnings. --- rts/Rendering/IconHandler.h | 2 +- rts/Sim/Misc/SmoothHeightMesh.h | 2 +- rts/Sim/MoveTypes/Components/MoveTypesComponents.h | 4 ++-- rts/Sim/MoveTypes/MoveDefHandler.h | 2 +- rts/Sim/MoveTypes/Utils/UnitTrapCheckUtils.h | 4 ++-- rts/Sim/Path/HAPFS/PathSearch.h | 2 +- rts/Sim/Path/QTPFS/Components/PathSpeedModInfo.h | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/rts/Rendering/IconHandler.h b/rts/Rendering/IconHandler.h index 6c67749d739..ec0e5bce6e3 100644 --- a/rts/Rendering/IconHandler.h +++ b/rts/Rendering/IconHandler.h @@ -13,7 +13,7 @@ #include "Rendering/GL/RenderBuffersFwd.h" #include "Rendering/Textures/TextureAtlas.h" -class UnitDef; +struct UnitDef; class CTextureRenderAtlas; namespace icon { diff --git a/rts/Sim/Misc/SmoothHeightMesh.h b/rts/Sim/Misc/SmoothHeightMesh.h index 888686af138..91866ddfb6c 100644 --- a/rts/Sim/Misc/SmoothHeightMesh.h +++ b/rts/Sim/Misc/SmoothHeightMesh.h @@ -22,7 +22,7 @@ namespace SmoothHeightMeshNamespace { */ class SmoothHeightMesh { - friend class SmoothHeightMeshDrawer; + friend struct SmoothHeightMeshDrawer; public: diff --git a/rts/Sim/MoveTypes/Components/MoveTypesComponents.h b/rts/Sim/MoveTypes/Components/MoveTypesComponents.h index b0d1d8b4265..fa7380707bb 100644 --- a/rts/Sim/MoveTypes/Components/MoveTypesComponents.h +++ b/rts/Sim/MoveTypes/Components/MoveTypesComponents.h @@ -7,8 +7,8 @@ #include "System/Ecs/Components/BaseComponents.h" #include -struct CUnit; -struct CFeature; +class CUnit; +class CFeature; namespace MoveTypes { diff --git a/rts/Sim/MoveTypes/MoveDefHandler.h b/rts/Sim/MoveTypes/MoveDefHandler.h index fbec124d13c..9c8d1db5601 100644 --- a/rts/Sim/MoveTypes/MoveDefHandler.h +++ b/rts/Sim/MoveTypes/MoveDefHandler.h @@ -18,7 +18,7 @@ class CUnit; class LuaTable; namespace MoveTypes { - class CheckCollisionQuery; + struct CheckCollisionQuery; } namespace MoveDefs { diff --git a/rts/Sim/MoveTypes/Utils/UnitTrapCheckUtils.h b/rts/Sim/MoveTypes/Utils/UnitTrapCheckUtils.h index 7db24ab3835..da7354b01fb 100644 --- a/rts/Sim/MoveTypes/Utils/UnitTrapCheckUtils.h +++ b/rts/Sim/MoveTypes/Utils/UnitTrapCheckUtils.h @@ -3,8 +3,8 @@ #ifndef UNIT_TRAP_CHECK_UTILS_H__ #define UNIT_TRAP_CHECK_UTILS_H__ -struct CFeature; -struct CUnit; +class CFeature; +class CUnit; namespace MoveTypes { void RegisterFeatureForUnitTrapCheck(CFeature* object); diff --git a/rts/Sim/Path/HAPFS/PathSearch.h b/rts/Sim/Path/HAPFS/PathSearch.h index 1fae37cc5dd..fdb3bfc7d85 100644 --- a/rts/Sim/Path/HAPFS/PathSearch.h +++ b/rts/Sim/Path/HAPFS/PathSearch.h @@ -6,7 +6,7 @@ #include "System/float3.h" class CSolidObject; -class MoveDef; +struct MoveDef; namespace HAPFS { struct PathSearch { diff --git a/rts/Sim/Path/QTPFS/Components/PathSpeedModInfo.h b/rts/Sim/Path/QTPFS/Components/PathSpeedModInfo.h index 19f7c9285cc..4bde1a878b5 100644 --- a/rts/Sim/Path/QTPFS/Components/PathSpeedModInfo.h +++ b/rts/Sim/Path/QTPFS/Components/PathSpeedModInfo.h @@ -12,7 +12,7 @@ namespace QTPFS { -class INode; +struct INode; struct NodeLayerSpeedInfoSweep { static constexpr std::size_t page_size = MoveDefHandler::MAX_MOVE_DEFS;