From 78a1f24ed8a492a27e66e238e2188f7ac618e5c3 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Mon, 13 Jul 2026 20:41:57 -0400 Subject: [PATCH 01/21] Create a new launcher target for Linux. --- src/CMakeLists.txt | 8 +++++ src/launcher_main/CMakeLists.txt | 50 ++++++++++++++++++++++++++++++++ src/launcher_main/main.cpp | 9 +++++- 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/launcher_main/CMakeLists.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 481b1c3ef3..0d66bdc599 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,8 @@ option(NEO_USE_SEPARATE_BUILD_INFO "Use separate build info on Linux" OFF) option(NEO_ENABLE_CPACK "Enable CPack" OFF) option(NEO_USE_MEM_DEBUG "Enable USE_MEM_DEBUG defines" OFF) option(NEO_GENERATE_GAMEDATA "Generate SourceMod gamedata" ${NEO_DEDICATED}) +option(NEO_BUILD_LAUNCHER "Build the Steam mod launcher" OFF) # NOTE Linux only +set(NEO_MOD_APPID "" CACHE STRING "Steam appid for the mod launcher's steam_appid.txt") message(STATUS "Treat compile warnings as errors: ${CMAKE_COMPILE_WARNING_AS_ERROR}") message(STATUS "CI build mode: ${NEO_CI_BUILD}") @@ -74,6 +76,8 @@ message(STATUS "Install game libraries: ${NEO_INSTALL_LIBRARIES}") message(STATUS "Install game resources: ${NEO_INSTALL_RESOURCES}") message(STATUS "Use separate build info on Linux: ${NEO_USE_SEPARATE_BUILD_INFO}") message(STATUS "Enable CPack: ${NEO_ENABLE_CPACK}") +message(STATUS "Build the Steam mod launcher: ${NEO_BUILD_LAUNCHER}") +message(STATUS "Mod launcher appid override: ${NEO_MOD_APPID}") message("") if(NEO_COPY_LIBRARIES) @@ -452,6 +456,10 @@ endif() add_subdirectory(game/server) +if(NEO_BUILD_LAUNCHER AND OS_LINUX AND NOT NEO_DEDICATED) + add_subdirectory(launcher_main) +endif() + if(NEO_COPY_LIBRARIES) set(COPY_ALL_LIBS_FLAG ALL) else() diff --git a/src/launcher_main/CMakeLists.txt b/src/launcher_main/CMakeLists.txt new file mode 100644 index 0000000000..b8f2f56a0e --- /dev/null +++ b/src/launcher_main/CMakeLists.txt @@ -0,0 +1,50 @@ +# Steam mod launcher (Linux). Valve's prebuilt Linux launcher never sets +# SDK_EXEC_DIR before exec'ing hl2.sh, so relative SDK search paths in +# gameinfo.txt silently fail to mount on standalone (non-sourcemods) installs. +# So we build it ourselves. +find_package(SDL2 REQUIRED) + +add_executable(launcher_main + main.cpp +) + +# GetExecutableModName derives the default -game dir from the executable name +set_target_properties(launcher_main + PROPERTIES + OUTPUT_NAME neo +) + +target_include_directories(launcher_main + PRIVATE + ${CMAKE_SOURCE_DIR}/public +) + +target_link_libraries(launcher_main + PRIVATE + SDL2::SDL2 + ${CMAKE_DL_LIBS} +) + +# main.cpp defines __wrap_fopen/__wrap_fopen64 which reference __real_fopen/__real_fopen64 +target_link_options(launcher_main + PRIVATE + -Wl,--wrap=fopen + -Wl,--wrap=fopen64 +) + +if(NEO_MOD_APPID) + target_compile_definitions(launcher_main + PRIVATE + MOD_LAUNCHER + MOD_APPID=${NEO_MOD_APPID} + ) +endif() + +if(NEO_COPY_LIBRARIES) + add_custom_command(TARGET launcher_main POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + ${CMAKE_SOURCE_DIR}/../game/ + VERBATIM + ) +endif() diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index 19dbd35e2d..4bd16e154d 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #define MAX_PATH PATH_MAX #endif @@ -627,6 +628,10 @@ int main( int argc, char *argv[] ) return 1; } + // Tell the engine where the SDK Base install lives so FileSystem_LoadSearchPaths + // resolves relative SDK mounts against it. Same thing the Windows path does above. + setenv( "SDK_EXEC_DIR", szGameInstallDir, 1 ); + char szExecutable[8192]; snprintf(szExecutable, sizeof(szExecutable), "%s/hl2.sh", szGameInstallDir ); @@ -662,7 +667,9 @@ int main( int argc, char *argv[] ) execvp( szExecutable, new_argv.data() ); - return 0; + // execvp only returns on failure + fprintf( stderr, "[Source Mod Launcher] Failed to exec %s: %s\n", szExecutable, strerror( errno ) ); + return 1; } #else From ef5f7ec039c36bf850852e389976c04939995558 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Mon, 13 Jul 2026 21:08:53 -0400 Subject: [PATCH 02/21] Fix launcher build on Linux and stage the standalone install layout Rename the launcher binary to neo_linux64: Linux cannot have both the executable and the game/neo mod directory under the same name at the install root, and GetExecutableModName strips the _linux64 suffix so the default -game dir is still neo. Add src/common to the include path and include tier1/strtools.h before steam_api.h, since our patched matchmakingtypes.h uses V_strcpy_safe. When NEO_COPY_LIBRARIES is on, also stage libsteam_api.so into game/bin/linux64/ where the launcher dlopens it. --- src/launcher_main/CMakeLists.txt | 12 +++++++++++- src/launcher_main/main.cpp | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/launcher_main/CMakeLists.txt b/src/launcher_main/CMakeLists.txt index b8f2f56a0e..58ec7d4a93 100644 --- a/src/launcher_main/CMakeLists.txt +++ b/src/launcher_main/CMakeLists.txt @@ -9,14 +9,17 @@ add_executable(launcher_main ) # GetExecutableModName derives the default -game dir from the executable name +# by stripping from the last underscore: neo_linux64 -> -game neo. The suffix +# also avoids colliding with the game/neo directory at the install root. set_target_properties(launcher_main PROPERTIES - OUTPUT_NAME neo + OUTPUT_NAME neo_linux64 ) target_include_directories(launcher_main PRIVATE ${CMAKE_SOURCE_DIR}/public + ${CMAKE_SOURCE_DIR}/common ) target_link_libraries(launcher_main @@ -41,10 +44,17 @@ if(NEO_MOD_APPID) endif() if(NEO_COPY_LIBRARIES) + # Stage the standalone-install layout: launcher at the install root next to + # neo/, and the steam_api lib where the launcher dlopens it. add_custom_command(TARGET launcher_main POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${CMAKE_SOURCE_DIR}/../game/ + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_SOURCE_DIR}/../game/bin/linux64 + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_SOURCE_DIR}/lib/public/linux64/libsteam_api.so + ${CMAKE_SOURCE_DIR}/../game/bin/linux64/ VERBATIM ) endif() diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index 4bd16e154d..1c4ebe4e58 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -48,6 +48,7 @@ extern "C" { __declspec( dllexport ) int AmdPowerXpressRequestHighPerformance = #endif +#include "tier1/strtools.h" // matchmakingtypes.h (via steam_api.h) uses V_strcpy_safe #include #ifdef _WIN32 From df74497521ed3b17f5bfe56506d83bc0ecf0f11c Mon Sep 17 00:00:00 2001 From: sunmachine Date: Mon, 13 Jul 2026 21:53:47 -0400 Subject: [PATCH 03/21] Ignore the Linux launcher binary and its runtime steam_appid.txt --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index ee09bd1a5c..1d80cca7ab 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,10 @@ out /game/neo/screenshots +# Mod launcher binary and the appid file it writes at runtime +/game/neo_linux64 +/game/steam_appid.txt + # Binary Assets /game/**/bin/** /game/neo/maps From 756ef861c07471a42a032dc01999906462b59c38 Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:02:11 +0100 Subject: [PATCH 04/21] windows build for launcher main added neo ifdefs also --- src/CMakeLists.txt | 5 +- src/game/server/CMakeLists.txt | 5 ++ src/launcher_main/CMakeLists.txt | 20 +++++- src/launcher_main/launcher_main_mod_neo.rc | 72 +++++++++++++++++++++ src/launcher_main/main.cpp | 10 +++ src/launcher_main/res/mod_neo.ico | Bin 0 -> 75327 bytes 6 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 src/launcher_main/launcher_main_mod_neo.rc create mode 100644 src/launcher_main/res/mod_neo.ico diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0d66bdc599..744c358aeb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,7 +51,7 @@ option(NEO_USE_SEPARATE_BUILD_INFO "Use separate build info on Linux" OFF) option(NEO_ENABLE_CPACK "Enable CPack" OFF) option(NEO_USE_MEM_DEBUG "Enable USE_MEM_DEBUG defines" OFF) option(NEO_GENERATE_GAMEDATA "Generate SourceMod gamedata" ${NEO_DEDICATED}) -option(NEO_BUILD_LAUNCHER "Build the Steam mod launcher" OFF) # NOTE Linux only +option(NEO_BUILD_LAUNCHER "Build the Steam mod launcher" ON) set(NEO_MOD_APPID "" CACHE STRING "Steam appid for the mod launcher's steam_appid.txt") message(STATUS "Treat compile warnings as errors: ${CMAKE_COMPILE_WARNING_AS_ERROR}") @@ -215,7 +215,6 @@ if(OS_WINDOWS) _ALLOW_RUNTIME_LIBRARY_MISMATCH _ALLOW_ITERATOR_DEBUG_LEVEL_MISMATCH _ALLOW_MSC_VER_MISMATCH - fopen=dont_use_fopen _MBCS ) @@ -456,7 +455,7 @@ endif() add_subdirectory(game/server) -if(NEO_BUILD_LAUNCHER AND OS_LINUX AND NOT NEO_DEDICATED) +if(NEO_BUILD_LAUNCHER AND NOT NEO_DEDICATED) add_subdirectory(launcher_main) endif() diff --git a/src/game/server/CMakeLists.txt b/src/game/server/CMakeLists.txt index 5f9434ff9a..da026598cc 100644 --- a/src/game/server/CMakeLists.txt +++ b/src/game/server/CMakeLists.txt @@ -99,6 +99,11 @@ if(OS_LINUX OR OS_MACOS) endif() if(OS_WINDOWS) + target_compile_definitions(server + PRIVATE + fopen=dont_use_fopen + ) + target_link_libraries(server PRIVATE winmm.lib diff --git a/src/launcher_main/CMakeLists.txt b/src/launcher_main/CMakeLists.txt index 58ec7d4a93..207ddf6108 100644 --- a/src/launcher_main/CMakeLists.txt +++ b/src/launcher_main/CMakeLists.txt @@ -2,19 +2,35 @@ # SDK_EXEC_DIR before exec'ing hl2.sh, so relative SDK search paths in # gameinfo.txt silently fail to mount on standalone (non-sourcemods) installs. # So we build it ourselves. +if(OS_LINUX) find_package(SDL2 REQUIRED) +endif() + +set(LAUNCHER_SOURCES main.cpp) + +if(OS_WINDOWS) + list(APPEND LAUNCHER_SOURCES launcher_main_mod_neo.rc) +endif() add_executable(launcher_main - main.cpp + ${LAUNCHER_SOURCES} ) # GetExecutableModName derives the default -game dir from the executable name # by stripping from the last underscore: neo_linux64 -> -game neo. The suffix # also avoids colliding with the game/neo directory at the install root. +if(OS_LINUX) set_target_properties(launcher_main PROPERTIES OUTPUT_NAME neo_linux64 ) +elseif(OS_WINDOWS) +set_target_properties(launcher_main + PROPERTIES + OUTPUT_NAME neo_win64 + WIN32_EXECUTABLE TRUE +) +endif() target_include_directories(launcher_main PRIVATE @@ -22,6 +38,7 @@ target_include_directories(launcher_main ${CMAKE_SOURCE_DIR}/common ) +if(OS_LINUX) target_link_libraries(launcher_main PRIVATE SDL2::SDL2 @@ -34,6 +51,7 @@ target_link_options(launcher_main -Wl,--wrap=fopen -Wl,--wrap=fopen64 ) +endif() if(NEO_MOD_APPID) target_compile_definitions(launcher_main diff --git a/src/launcher_main/launcher_main_mod_neo.rc b/src/launcher_main/launcher_main_mod_neo.rc new file mode 100644 index 0000000000..9129d30401 --- /dev/null +++ b/src/launcher_main/launcher_main_mod_neo.rc @@ -0,0 +1,72 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_LAUNCHER ICON DISCARDABLE "res\\mod_neo.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index 1c4ebe4e58..059e657a6b 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -17,7 +17,9 @@ #include #include #include +#ifdef NEO #include +#endif #define MAX_PATH PATH_MAX #endif @@ -48,7 +50,9 @@ extern "C" { __declspec( dllexport ) int AmdPowerXpressRequestHighPerformance = #endif +#ifdef NEO #include "tier1/strtools.h" // matchmakingtypes.h (via steam_api.h) uses V_strcpy_safe +#endif #include #ifdef _WIN32 @@ -629,9 +633,11 @@ int main( int argc, char *argv[] ) return 1; } +#ifdef NEO // Tell the engine where the SDK Base install lives so FileSystem_LoadSearchPaths // resolves relative SDK mounts against it. Same thing the Windows path does above. setenv( "SDK_EXEC_DIR", szGameInstallDir, 1 ); +#endif char szExecutable[8192]; snprintf(szExecutable, sizeof(szExecutable), "%s/hl2.sh", szGameInstallDir ); @@ -668,9 +674,13 @@ int main( int argc, char *argv[] ) execvp( szExecutable, new_argv.data() ); +#ifdef NEO // execvp only returns on failure fprintf( stderr, "[Source Mod Launcher] Failed to exec %s: %s\n", szExecutable, strerror( errno ) ); return 1; +#else + return 0; +#endif } #else diff --git a/src/launcher_main/res/mod_neo.ico b/src/launcher_main/res/mod_neo.ico new file mode 100644 index 0000000000000000000000000000000000000000..6cdb58308f5e893fc9785823feb8bcaee9b08629 GIT binary patch literal 75327 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%0Dxkl1!HlAqw3rMF=C1%)(n|ljXF`7SxUU2 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 B(m((J literal 0 HcmV?d00001 From 3977f72f691019bbcd9ef167b87e276dfd88ecc9 Mon Sep 17 00:00:00 2001 From: DESTROYGIRL <170364626+DESTROYGIRL@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:31:41 +0100 Subject: [PATCH 05/21] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1d80cca7ab..2ac93e819a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ out # Mod launcher binary and the appid file it writes at runtime /game/neo_linux64 +/game/neo_win64.exe /game/steam_appid.txt # Binary Assets From e514b06dd8b114e94af6e8b1322ae0906cfd9cf1 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sat, 18 Jul 2026 12:19:51 -0400 Subject: [PATCH 06/21] Default the launcher appid to NT;RE's steam_appid.txt written by the launcher now identifies the mod without requiring -DNEO_MOD_APPID at configure time. --- src/CMakeLists.txt | 2 +- src/launcher_main/CMakeLists.txt | 7 +++---- src/launcher_main/main.cpp | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 744c358aeb..d06dabcb8a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,7 +52,7 @@ option(NEO_ENABLE_CPACK "Enable CPack" OFF) option(NEO_USE_MEM_DEBUG "Enable USE_MEM_DEBUG defines" OFF) option(NEO_GENERATE_GAMEDATA "Generate SourceMod gamedata" ${NEO_DEDICATED}) option(NEO_BUILD_LAUNCHER "Build the Steam mod launcher" ON) -set(NEO_MOD_APPID "" CACHE STRING "Steam appid for the mod launcher's steam_appid.txt") +set(NEO_MOD_APPID "3172910" CACHE STRING "Steam appid for the mod launcher's steam_appid.txt") message(STATUS "Treat compile warnings as errors: ${CMAKE_COMPILE_WARNING_AS_ERROR}") message(STATUS "CI build mode: ${NEO_CI_BUILD}") diff --git a/src/launcher_main/CMakeLists.txt b/src/launcher_main/CMakeLists.txt index 207ddf6108..1b61a857af 100644 --- a/src/launcher_main/CMakeLists.txt +++ b/src/launcher_main/CMakeLists.txt @@ -1,7 +1,6 @@ -# Steam mod launcher (Linux). Valve's prebuilt Linux launcher never sets -# SDK_EXEC_DIR before exec'ing hl2.sh, so relative SDK search paths in -# gameinfo.txt silently fail to mount on standalone (non-sourcemods) installs. -# So we build it ourselves. +# Steam mod launcher. Runs the game from the mod's own install directory +# using the mod's shipped engine binaries; the Source SDK Base 2013 MP +# install is only an asset dependency, mounted through gameinfo.txt. if(OS_LINUX) find_package(SDL2 REQUIRED) endif() diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index 059e657a6b..f969aa3fb8 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -167,7 +167,7 @@ static bool LoadSteam( const char *pRootDir ) return false; } - // Make a steam_appid.txt now, of just eg. Source SDK 2013 MP for this. + // Write steam_appid.txt so the Steam API identifies this process as the mod. FILE *pFile = fopen( "steam_appid.txt", "w" ); if ( pFile ) { From 73c8cefa4b684b3ec530dce2b3d082f1bdaf9568 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sat, 18 Jul 2026 12:20:12 -0400 Subject: [PATCH 07/21] Add NEO_INSTALL_LAUNCHER to package the launcher Follows the existing NEO_INSTALL_* install/CPack convention; produces a -launcher artifact holding the launcher binary for the install root. steam_api already ships with the engine binaries in bin/. --- src/CMakeLists.txt | 10 ++++++++++ src/cmake/packaging.cmake | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d06dabcb8a..8d53f55bd3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,6 +52,7 @@ option(NEO_ENABLE_CPACK "Enable CPack" OFF) option(NEO_USE_MEM_DEBUG "Enable USE_MEM_DEBUG defines" OFF) option(NEO_GENERATE_GAMEDATA "Generate SourceMod gamedata" ${NEO_DEDICATED}) option(NEO_BUILD_LAUNCHER "Build the Steam mod launcher" ON) +option(NEO_INSTALL_LAUNCHER "Install the Steam mod launcher" OFF) set(NEO_MOD_APPID "3172910" CACHE STRING "Steam appid for the mod launcher's steam_appid.txt") message(STATUS "Treat compile warnings as errors: ${CMAKE_COMPILE_WARNING_AS_ERROR}") @@ -77,6 +78,7 @@ message(STATUS "Install game resources: ${NEO_INSTALL_RESOURCES}") message(STATUS "Use separate build info on Linux: ${NEO_USE_SEPARATE_BUILD_INFO}") message(STATUS "Enable CPack: ${NEO_ENABLE_CPACK}") message(STATUS "Build the Steam mod launcher: ${NEO_BUILD_LAUNCHER}") +message(STATUS "Install the Steam mod launcher: ${NEO_INSTALL_LAUNCHER}") message(STATUS "Mod launcher appid override: ${NEO_MOD_APPID}") message("") @@ -592,6 +594,14 @@ if(NEO_INSTALL_LIBRARIES) endif() endif() +if(NEO_INSTALL_LAUNCHER AND NEO_BUILD_LAUNCHER AND NOT NEO_DEDICATED) + # The launcher sits at the install root, next to the neo/ game directory. + # steam_api ships with the engine binaries in bin/, so only the launcher + # itself is staged here. + set(LAUNCHER_INSTALL_PATH "${INSTALL_PATH_PREFIX}-launcher-${CMAKE_SYSTEM_NAME}-${BUILD_TYPE_NAME}") + install(TARGETS launcher_main RUNTIME DESTINATION "${LAUNCHER_INSTALL_PATH}") +endif() + if(NEO_INSTALL_RESOURCES) install( DIRECTORY diff --git a/src/cmake/packaging.cmake b/src/cmake/packaging.cmake index d37c52cf37..b3ccdf68b9 100644 --- a/src/cmake/packaging.cmake +++ b/src/cmake/packaging.cmake @@ -10,6 +10,10 @@ if(NEO_INSTALL_RESOURCES) set(CPACK_PACKAGE_FILE_NAME "neo-${BUILD_DATE_SHORT}-${GIT_HASH}-resources") endif() +if(NEO_INSTALL_LAUNCHER) + set(CPACK_PACKAGE_FILE_NAME "neo-${BUILD_DATE_SHORT}-${GIT_HASH}-launcher-${CMAKE_SYSTEM_NAME}") +endif() + if(OS_WINDOWS) set(CPACK_GENERATOR "ZIP") else() From 1203e676a6c6371c8b9be44594857f2edba83562 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sat, 18 Jul 2026 12:24:28 -0400 Subject: [PATCH 08/21] Stop treating the SDK Base install as the game root The launcher now loads binaries from its own directory on both platforms. The Source SDK Base 2013 MP install remains a required asset dependency checked at startup, because gameinfo.txt mounts hl2/platform content from it, but it is no longer used as an execution root and SDK_EXEC_DIR is no longer set. Windows also gains the default -game injection the POSIX path already had (neo_win64.exe -> -game \neo). The Windows flow is untested and needs scrutiny before use. --- src/launcher_main/main.cpp | 157 +++++++++++++++++++++++-------------- 1 file changed, 100 insertions(+), 57 deletions(-) diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index f969aa3fb8..b9335f98ac 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #endif #ifdef POSIX @@ -17,6 +19,7 @@ #include #include #include +#include #ifdef NEO #include #endif @@ -194,7 +197,7 @@ static bool LoadSteam( const char *pRootDir ) return true; } -static bool GetGameInstallDir( const char *pRootDir, char *pszBuf, int nBufSize ) +static bool GetSDKInstallDir( const char *pRootDir, char *pszBuf, int nBufSize ) { if ( !LoadSteam( pRootDir ) ) { @@ -236,6 +239,16 @@ static bool GetGameInstallDir( const char *pRootDir, char *pszBuf, int nBufSize return false; } + // Steam reports the app as installed from its manifest alone; the files + // can still be missing (moved library, interrupted uninstall). Mounting + // would fail silently later, so check the directory really exists. + struct stat sdkStat; + if ( stat( pszBuf, &sdkStat ) != 0 || !( sdkStat.st_mode & S_IFDIR ) ) + { + MessageBox( 0, "The Source SDK 2013 Multiplayer (243750) files are missing from disk. Verify or reinstall it in Steam to play this mod.", "Launcher Error", MB_OK ); + return false; + } + return true; } @@ -281,6 +294,55 @@ static char *GetBaseDir( const char *pszBuffer ) return basedir; } +static const char *GetExecutableModName( char *pszExePath ) +{ + static char s_szFinalFilename[ MAX_PATH + 1 ] = "hl2"; + + char szExePath[ MAX_PATH + 1 ]; + strncpy( szExePath, pszExePath, sizeof( szExePath ) ); + szExePath[ MAX_PATH ] = 0; + + if ( !*szExePath ) + return s_szFinalFilename; + + const char *pszLastSeparator = strrchr( szExePath, '/' ); +#ifdef _WIN32 + // Steam and the shell may hand us either separator on Windows. + const char *pszLastBackslash = strrchr( szExePath, '\\' ); + if ( !pszLastSeparator || ( pszLastBackslash && pszLastBackslash > pszLastSeparator ) ) + { + pszLastSeparator = pszLastBackslash; + } +#endif + if ( !pszLastSeparator ) + return s_szFinalFilename; + + strncpy( s_szFinalFilename, pszLastSeparator + 1, sizeof( s_szFinalFilename ) ); + s_szFinalFilename[ MAX_PATH ] = 0; + char *pszLastUnderscore = NULL; + char* pszLastDot = NULL; + for ( char *ch = s_szFinalFilename; *ch != 0; ch++ ) + { + if ( *ch == '_' ) + { + pszLastUnderscore = ch; + } + + if ( *ch == '.' ) + { + pszLastDot = ch; + } + } + + if ( pszLastUnderscore ) + *pszLastUnderscore = 0; + + if ( pszLastDot ) + *pszLastDot = 0; + + return s_szFinalFilename; +} + #ifdef WIN32 #include @@ -453,16 +515,17 @@ int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL // Get the root directory the .exe is in char* pRootDir = GetBaseDir( moduleName ); - const char *pBinaryGameDir = pRootDir; - char szGameInstallDir[4096]; - if ( !GetGameInstallDir( pRootDir, szGameInstallDir, 4096 ) ) + + // NOTE: The Windows flow is untested and needs scrutiny before use. + // The game runs from its own install with its own bin\; the SDK Base + // install is only checked here because gameinfo.txt mounts its assets. + char szSDKInstallDir[4096]; + if ( !GetSDKInstallDir( pRootDir, szSDKInstallDir, sizeof( szSDKInstallDir ) ) ) { return 1; } - pBinaryGameDir = szGameInstallDir; - - SetEnvironmentVariableA( "SDK_EXEC_DIR", szGameInstallDir ); + const char *pBinaryGameDir = pRootDir; #define LAUNCHER_DLL_PATH "%s\\" PLATFORM_BIN_DIR "\\launcher.dll" #define LAUNCHER_PATH "%s\\" PLATFORM_BIN_DIR @@ -492,6 +555,33 @@ int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL return 0; } + // Launch options carry no -game; derive the mod dir from our own + // binary name (neo_win64.exe -> neo) like the POSIX launcher does. + char szNewCmdLine[8192]; + bool bHasGame = false; + { + const char *pszScan = lpCmdLine; + while ( ( pszScan = strstr( pszScan, "-game" ) ) != NULL ) + { + const bool bStartOk = ( pszScan == lpCmdLine ) || ( pszScan[-1] == ' ' ) || ( pszScan[-1] == '\t' ); + const char chEnd = pszScan[5]; + if ( bStartOk && ( chEnd == '\0' || chEnd == ' ' || chEnd == '\t' ) ) + { + bHasGame = true; + break; + } + pszScan += 5; + } + } + + if ( !bHasGame ) + { + _snprintf( szNewCmdLine, sizeof( szNewCmdLine ), "%s -game \"%s\\%s\"", + lpCmdLine, pRootDir, GetExecutableModName( moduleName ) ); + szNewCmdLine[sizeof( szNewCmdLine ) - 1] = '\0'; + lpCmdLine = szNewCmdLine; + } + LauncherMain_t main = (LauncherMain_t)GetProcAddress( launcher, "LauncherMain" ); return main( hInstance, hPrevInstance, lpCmdLine, nCmdShow ); } @@ -574,47 +664,6 @@ static void WaitForDebuggerConnect( int argc, char *argv[], int time ) #endif // !LINUX -static const char *GetExecutableModName( char *pszExePath ) -{ - static char s_szFinalFilename[ MAX_PATH + 1 ] = "hl2"; - - char szExePath[ MAX_PATH + 1 ]; - strncpy( szExePath, pszExePath, sizeof( szExePath ) ); - szExePath[ MAX_PATH ] = 0; - - if ( !*szExePath ) - return s_szFinalFilename; - - const char *pszLastSeparator = strrchr( szExePath, '/' ); - if ( !pszLastSeparator ) - return s_szFinalFilename; - - strncpy( s_szFinalFilename, pszLastSeparator + 1, sizeof( s_szFinalFilename ) ); - s_szFinalFilename[ MAX_PATH ] = 0; - char *pszLastUnderscore = NULL; - char* pszLastDot = NULL; - for ( char *ch = s_szFinalFilename; *ch != 0; ch++ ) - { - if ( *ch == '_' ) - { - pszLastUnderscore = ch; - } - - if ( *ch == '.' ) - { - pszLastDot = ch; - } - } - - if ( pszLastUnderscore ) - *pszLastUnderscore = 0; - - if ( pszLastDot ) - *pszLastDot = 0; - - return s_szFinalFilename; -} - int main( int argc, char *argv[] ) { char moduleName[MAX_PATH]; @@ -627,20 +676,14 @@ int main( int argc, char *argv[] ) const char *pBinaryGameDir = pRootDir; - char szGameInstallDir[4096]; - if ( !GetGameInstallDir( pRootDir, szGameInstallDir, 4096 ) ) + char szSDKInstallDir[4096]; + if ( !GetSDKInstallDir( pRootDir, szSDKInstallDir, 4096 ) ) { return 1; } -#ifdef NEO - // Tell the engine where the SDK Base install lives so FileSystem_LoadSearchPaths - // resolves relative SDK mounts against it. Same thing the Windows path does above. - setenv( "SDK_EXEC_DIR", szGameInstallDir, 1 ); -#endif - char szExecutable[8192]; - snprintf(szExecutable, sizeof(szExecutable), "%s/hl2.sh", szGameInstallDir ); + snprintf(szExecutable, sizeof(szExecutable), "%s/hl2.sh", szSDKInstallDir ); std::vector new_argv; From 24341340667853985088e49cf49859b4973d1636 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sat, 18 Jul 2026 12:31:58 -0400 Subject: [PATCH 09/21] Load the engine in-process on Linux instead of exec'ing hl2.sh The launcher now mirrors the Windows flow: it prepends its own bin/linux64 to LD_LIBRARY_PATH (re-exec'ing itself once, since the dynamic linker reads the variable only at startup), loads launcher.so from the mod's own install, and calls LauncherMain directly. Environment obligations of the stock launch script are replicated: working directory at the install root, fd soft limit raised to 2048, __GL_THREADED_OPTIMIZATIONS, ENABLE_PATHMATCH when pathmatch.inf exists, and re-launch on exit status 42. No SDK Base binaries or scripts are involved in the launch chain anymore. --- src/launcher_main/main.cpp | 100 ++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 23 deletions(-) diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index b9335f98ac..d9e34bba97 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -20,9 +20,9 @@ #include #include #include -#ifdef NEO +#include +#include #include -#endif #define MAX_PATH PATH_MAX #endif @@ -674,20 +674,56 @@ int main( int argc, char *argv[] ) char* pRootDir = GetBaseDir( moduleName ); - const char *pBinaryGameDir = pRootDir; + // Run from the install root: relative engine paths and steam_appid.txt + // both resolve against the working directory. + if ( chdir( pRootDir ) != 0 ) + { + fprintf( stderr, "[NT;RE launcher] Warning: chdir(%s) failed: %s\n", pRootDir, strerror( errno ) ); + } + + // The dynamic linker only reads LD_LIBRARY_PATH at process start, and + // launcher.so's dependencies resolve through it, so prepend our bin dir + // and re-exec ourselves exactly once. + if ( !getenv( "NEO_LAUNCHER_BOOTSTRAPPED" ) ) + { + char szLibPath[8192]; + const char *pszOldPath = getenv( "LD_LIBRARY_PATH" ); + snprintf( szLibPath, sizeof( szLibPath ), "%s/" PLATFORM_BIN_DIR "%s%s", + pRootDir, pszOldPath ? ":" : "", pszOldPath ? pszOldPath : "" ); + setenv( "LD_LIBRARY_PATH", szLibPath, 1 ); + setenv( "NEO_LAUNCHER_BOOTSTRAPPED", "1", 1 ); + + // Same environment the stock launch script sets up. + setenv( "__GL_THREADED_OPTIMIZATIONS", "1", 1 ); + if ( access( "pathmatch.inf", F_OK ) == 0 ) + { + setenv( "ENABLE_PATHMATCH", "1", 1 ); + } + + execv( moduleName, argv ); + fprintf( stderr, "[NT;RE launcher] Failed to re-exec %s: %s\n", moduleName, strerror( errno ) ); + return 1; + } + + // The stock launch script raises the fd limit; the engine leans on it. + struct rlimit fdLimit; + if ( getrlimit( RLIMIT_NOFILE, &fdLimit ) == 0 && fdLimit.rlim_cur < 2048 ) + { + fdLimit.rlim_cur = ( fdLimit.rlim_max == RLIM_INFINITY || fdLimit.rlim_max > 2048 ) ? 2048 : fdLimit.rlim_max; + setrlimit( RLIMIT_NOFILE, &fdLimit ); + } char szSDKInstallDir[4096]; - if ( !GetSDKInstallDir( pRootDir, szSDKInstallDir, 4096 ) ) + if ( !GetSDKInstallDir( pRootDir, szSDKInstallDir, sizeof( szSDKInstallDir ) ) ) { return 1; } - char szExecutable[8192]; - snprintf(szExecutable, sizeof(szExecutable), "%s/hl2.sh", szSDKInstallDir ); + // Build the argument list for LauncherMain, appending a default -game + // derived from our binary name (neo_linux64 -> neo) when absent. std::vector new_argv; - - new_argv.push_back( szExecutable ); + new_argv.push_back( moduleName ); bool bHasGame = false; for ( int i = 1; i < argc; i++ ) @@ -696,34 +732,52 @@ int main( int argc, char *argv[] ) { bHasGame = true; } - - new_argv.push_back(argv[i]); + new_argv.push_back( argv[i] ); } char szGamePath[8192]; if ( !bHasGame ) { - new_argv.push_back("-game"); - + new_argv.push_back( (char *)"-game" ); const char *pModName = GetExecutableModName( moduleName ); snprintf( szGamePath, sizeof( szGamePath ), "%s/%s", pRootDir, pModName ); + printf( "[NT;RE launcher] Launching default game: %s\n", pModName ); + new_argv.push_back( szGamePath ); + } - printf( "[Source Mod Launcher] Launching default game: %s\n", pModName ); + const int nLauncherArgc = (int)new_argv.size(); + new_argv.push_back( NULL ); + + char szLauncherPath[8192]; + snprintf( szLauncherPath, sizeof( szLauncherPath ), "%s/" PLATFORM_BIN_DIR "/launcher.so", pRootDir ); + + void *pLauncherModule = Launcher_LoadModule( szLauncherPath ); + if ( !pLauncherModule ) + { + char szError[sizeof( szLauncherPath ) + 2048]; + snprintf( szError, sizeof( szError ), "Failed to load %s:\n%s", szLauncherPath, dlerror() ); + MessageBox( 0, szError, "Launcher Error", MB_OK ); + return 1; + } - new_argv.push_back(szGamePath); + LauncherMain_t pfnLauncherMain = (LauncherMain_t)Launcher_GetProcAddress( pLauncherModule, "LauncherMain" ); + if ( !pfnLauncherMain ) + { + MessageBox( 0, "LauncherMain not found in launcher.so", "Launcher Error", MB_OK ); + return 1; } - new_argv.push_back(NULL); + const int nStatus = pfnLauncherMain( nLauncherArgc, new_argv.data() ); - execvp( szExecutable, new_argv.data() ); + // The engine requests a relaunch (e.g. video mode changes) with this + // status; the stock launch script loops on it, we re-exec fresh. + if ( nStatus == 42 ) + { + execv( moduleName, argv ); + fprintf( stderr, "[NT;RE launcher] Failed to re-exec %s after engine restart request: %s\n", moduleName, strerror( errno ) ); + } -#ifdef NEO - // execvp only returns on failure - fprintf( stderr, "[Source Mod Launcher] Failed to exec %s: %s\n", szExecutable, strerror( errno ) ); - return 1; -#else - return 0; -#endif + return nStatus; } #else From 2d80e90c5852392df936694e68eae1cacac0e910 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sat, 18 Jul 2026 12:35:53 -0400 Subject: [PATCH 10/21] Work around the engine's case-mangled SDK mount path on Linux The engine opens |appid_243750| search paths with the final word of the SDK Base 2013 Multiplayer folder name lowercased, which silently mounts nothing on case-sensitive filesystems (observed via strace: every SDK-content open used ".../Source SDK Base 2013 multiplayer/..."). The launcher now creates a symlink under that mangled name pointing at the real install when the path does not already resolve. Failure to create it degrades to a warning. --- src/launcher_main/main.cpp | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index d9e34bba97..b498a3b7df 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -663,6 +663,53 @@ static void WaitForDebuggerConnect( int argc, char *argv[], int time ) #endif // !LINUX +// The engine resolves |appid_243750| gameinfo mounts by opening the SDK Base +// 2013 Multiplayer install path with the final word of the folder name +// lowercased ("...2013 multiplayer"). On case-sensitive filesystems that path +// does not exist, so every SDK asset mount fails silently. Give the mangled +// name a symlink to the real install so those mounts resolve. +static void CreateSDKCaseShim( const char *pszSDKInstallDir ) +{ + static const char szExpectedName[] = "Source SDK Base 2013 Multiplayer"; + static const char szMangledName[] = "Source SDK Base 2013 multiplayer"; + + const char *pszLeaf = strrchr( pszSDKInstallDir, '/' ); + if ( !pszLeaf || strcmp( pszLeaf + 1, szExpectedName ) != 0 ) + { + // Unexpected install folder name; the engine's mangling is only + // known for the stock name, so do nothing. + return; + } + + char szMangledPath[4096]; + snprintf( szMangledPath, sizeof( szMangledPath ), "%.*s/%s", + (int)( pszLeaf - pszSDKInstallDir ), pszSDKInstallDir, szMangledName ); + + // Already resolves on case-insensitive filesystems or if the link exists. + if ( access( szMangledPath, F_OK ) == 0 ) + { + return; + } + + // A leftover link can dangle (SDK moved or reinstalled elsewhere); + // replace it instead of failing with EEXIST. Anything that is not a + // symlink was not created by us, so leave it alone. + struct stat linkStat; + if ( lstat( szMangledPath, &linkStat ) == 0 ) + { + if ( !S_ISLNK( linkStat.st_mode ) ) + { + return; + } + unlink( szMangledPath ); + } + + if ( symlink( pszSDKInstallDir, szMangledPath ) != 0 ) + { + fprintf( stderr, "[NT;RE launcher] Warning: couldn't create '%s' -> '%s' (%s); SDK content may fail to mount.\n", + szMangledPath, pszSDKInstallDir, strerror( errno ) ); + } +} int main( int argc, char *argv[] ) { @@ -719,6 +766,7 @@ int main( int argc, char *argv[] ) return 1; } + CreateSDKCaseShim( szSDKInstallDir ); // Build the argument list for LauncherMain, appending a default -game // derived from our binary name (neo_linux64 -> neo) when absent. From 31230ac628514e9809a97143bbe2958efe587ed6 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sat, 18 Jul 2026 12:45:11 -0400 Subject: [PATCH 11/21] Terminate the executable path read from /proc/self/exe readlink does not NUL-terminate its buffer. The result feeds GetBaseDir, execv, and argv[0], so an unterminated path could carry stack garbage into every downstream path computation. --- src/launcher_main/main.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index b498a3b7df..175c41198f 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -592,7 +592,13 @@ int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL bool GetExePath( char *pszPath, size_t size ) { - return readlink("/proc/self/exe", pszPath, size ) > 0; + ssize_t nLength = readlink( "/proc/self/exe", pszPath, size - 1 ); + if ( nLength <= 0 ) + { + return false; + } + pszPath[nLength] = '\0'; + return true; } #include From a508dadd2eea7b5525fa78700ac7bcdc39f435d9 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sat, 18 Jul 2026 13:26:17 -0400 Subject: [PATCH 12/21] Honor -wait_for_debugger in the Linux launcher The debugger-wait helpers existed but nothing called them, so the flag did nothing. Wait right after the bootstrap re-exec, before any Steam or engine work, so a debugger can attach to the process that loads the engine. --- src/launcher_main/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index 175c41198f..59068b3198 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -758,6 +758,9 @@ int main( int argc, char *argv[] ) return 1; } + // Give a debugger a chance to attach before anything interesting runs. + WaitForDebuggerConnect( argc, argv, 30 ); + // The stock launch script raises the fd limit; the engine leans on it. struct rlimit fdLimit; if ( getrlimit( RLIMIT_NOFILE, &fdLimit ) == 0 && fdLimit.rlim_cur < 2048 ) From 62a126cd856fe679f80bc1a65f2635d8cb5963c1 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sat, 18 Jul 2026 20:08:37 -0400 Subject: [PATCH 13/21] Wrap launcher divergence from the SDK in NEO guards --- src/launcher_main/main.cpp | 138 ++++++++++++++++++++++++++++++++++--- 1 file changed, 130 insertions(+), 8 deletions(-) diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index 59068b3198..fd089a88ee 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -9,8 +9,10 @@ #include #include #include +#ifdef NEO #include #include +#endif // NEO #endif #ifdef POSIX @@ -19,10 +21,12 @@ #include #include #include +#ifdef NEO #include #include #include #include +#endif // NEO #define MAX_PATH PATH_MAX #endif @@ -170,7 +174,7 @@ static bool LoadSteam( const char *pRootDir ) return false; } - // Write steam_appid.txt so the Steam API identifies this process as the mod. + // Make a steam_appid.txt now, of just eg. Source SDK 2013 MP for this. FILE *pFile = fopen( "steam_appid.txt", "w" ); if ( pFile ) { @@ -197,7 +201,7 @@ static bool LoadSteam( const char *pRootDir ) return true; } -static bool GetSDKInstallDir( const char *pRootDir, char *pszBuf, int nBufSize ) +static bool GetGameInstallDir( const char *pRootDir, char *pszBuf, int nBufSize ) { if ( !LoadSteam( pRootDir ) ) { @@ -239,6 +243,7 @@ static bool GetSDKInstallDir( const char *pRootDir, char *pszBuf, int nBufSize ) return false; } +#ifdef NEO // Steam reports the app as installed from its manifest alone; the files // can still be missing (moved library, interrupted uninstall). Mounting // would fail silently later, so check the directory really exists. @@ -248,7 +253,7 @@ static bool GetSDKInstallDir( const char *pRootDir, char *pszBuf, int nBufSize ) MessageBox( 0, "The Source SDK 2013 Multiplayer (243750) files are missing from disk. Verify or reinstall it in Steam to play this mod.", "Launcher Error", MB_OK ); return false; } - +#endif // NEO return true; } @@ -294,6 +299,7 @@ static char *GetBaseDir( const char *pszBuffer ) return basedir; } +#ifdef NEO static const char *GetExecutableModName( char *pszExePath ) { static char s_szFinalFilename[ MAX_PATH + 1 ] = "hl2"; @@ -342,7 +348,7 @@ static const char *GetExecutableModName( char *pszExePath ) return s_szFinalFilename; } - +#endif // NEO #ifdef WIN32 #include @@ -515,17 +521,29 @@ int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL // Get the root directory the .exe is in char* pRootDir = GetBaseDir( moduleName ); - +#ifdef NEO // NOTE: The Windows flow is untested and needs scrutiny before use. // The game runs from its own install with its own bin\; the SDK Base // install is only checked here because gameinfo.txt mounts its assets. char szSDKInstallDir[4096]; - if ( !GetSDKInstallDir( pRootDir, szSDKInstallDir, sizeof( szSDKInstallDir ) ) ) + if ( !GetGameInstallDir( pRootDir, szSDKInstallDir, sizeof( szSDKInstallDir ) ) ) { return 1; } const char *pBinaryGameDir = pRootDir; +#else + const char *pBinaryGameDir = pRootDir; + char szGameInstallDir[4096]; + if ( !GetGameInstallDir( pRootDir, szGameInstallDir, 4096 ) ) + { + return 1; + } + + pBinaryGameDir = szGameInstallDir; + + SetEnvironmentVariableA( "SDK_EXEC_DIR", szGameInstallDir ); +#endif // NEO #define LAUNCHER_DLL_PATH "%s\\" PLATFORM_BIN_DIR "\\launcher.dll" #define LAUNCHER_PATH "%s\\" PLATFORM_BIN_DIR @@ -555,6 +573,7 @@ int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL return 0; } +#ifdef NEO // Launch options carry no -game; derive the mod dir from our own // binary name (neo_win64.exe -> neo) like the POSIX launcher does. char szNewCmdLine[8192]; @@ -581,7 +600,7 @@ int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL szNewCmdLine[sizeof( szNewCmdLine ) - 1] = '\0'; lpCmdLine = szNewCmdLine; } - +#endif // NEO LauncherMain_t main = (LauncherMain_t)GetProcAddress( launcher, "LauncherMain" ); return main( hInstance, hPrevInstance, lpCmdLine, nCmdShow ); } @@ -592,6 +611,7 @@ int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL bool GetExePath( char *pszPath, size_t size ) { +#ifdef NEO ssize_t nLength = readlink( "/proc/self/exe", pszPath, size - 1 ); if ( nLength <= 0 ) { @@ -599,6 +619,9 @@ bool GetExePath( char *pszPath, size_t size ) } pszPath[nLength] = '\0'; return true; +#else + return readlink("/proc/self/exe", pszPath, size ) > 0; +#endif // NEO } #include @@ -669,6 +692,7 @@ static void WaitForDebuggerConnect( int argc, char *argv[], int time ) #endif // !LINUX +#ifdef NEO // The engine resolves |appid_243750| gameinfo mounts by opening the SDK Base // 2013 Multiplayer install path with the final word of the folder name // lowercased ("...2013 multiplayer"). On case-sensitive filesystems that path @@ -716,9 +740,53 @@ static void CreateSDKCaseShim( const char *pszSDKInstallDir ) szMangledPath, pszSDKInstallDir, strerror( errno ) ); } } +#else + +static const char *GetExecutableModName( char *pszExePath ) +{ + static char s_szFinalFilename[ MAX_PATH + 1 ] = "hl2"; + + char szExePath[ MAX_PATH + 1 ]; + strncpy( szExePath, pszExePath, sizeof( szExePath ) ); + szExePath[ MAX_PATH ] = 0; + + if ( !*szExePath ) + return s_szFinalFilename; + + const char *pszLastSeparator = strrchr( szExePath, '/' ); + if ( !pszLastSeparator ) + return s_szFinalFilename; + + strncpy( s_szFinalFilename, pszLastSeparator + 1, sizeof( s_szFinalFilename ) ); + s_szFinalFilename[ MAX_PATH ] = 0; + char *pszLastUnderscore = NULL; + char* pszLastDot = NULL; + for ( char *ch = s_szFinalFilename; *ch != 0; ch++ ) + { + if ( *ch == '_' ) + { + pszLastUnderscore = ch; + } + + if ( *ch == '.' ) + { + pszLastDot = ch; + } + } + + if ( pszLastUnderscore ) + *pszLastUnderscore = 0; + + if ( pszLastDot ) + *pszLastDot = 0; + + return s_szFinalFilename; +} +#endif // NEO int main( int argc, char *argv[] ) { +#ifdef NEO char moduleName[MAX_PATH]; if ( !GetExePath( moduleName, sizeof( moduleName ) ) ) { @@ -770,7 +838,7 @@ int main( int argc, char *argv[] ) } char szSDKInstallDir[4096]; - if ( !GetSDKInstallDir( pRootDir, szSDKInstallDir, sizeof( szSDKInstallDir ) ) ) + if ( !GetGameInstallDir( pRootDir, szSDKInstallDir, sizeof( szSDKInstallDir ) ) ) { return 1; } @@ -835,6 +903,60 @@ int main( int argc, char *argv[] ) } return nStatus; +#else + char moduleName[MAX_PATH]; + if ( !GetExePath( moduleName, sizeof( moduleName ) ) ) + { + return 1; + } + + char* pRootDir = GetBaseDir( moduleName ); + + const char *pBinaryGameDir = pRootDir; + + char szGameInstallDir[4096]; + if ( !GetGameInstallDir( pRootDir, szGameInstallDir, 4096 ) ) + { + return 1; + } + + char szExecutable[8192]; + snprintf(szExecutable, sizeof(szExecutable), "%s/hl2.sh", szGameInstallDir ); + + std::vector new_argv; + + new_argv.push_back( szExecutable ); + + bool bHasGame = false; + for ( int i = 1; i < argc; i++ ) + { + if ( !strcmp( argv[i], "-game" ) ) + { + bHasGame = true; + } + + new_argv.push_back(argv[i]); + } + + char szGamePath[8192]; + if ( !bHasGame ) + { + new_argv.push_back("-game"); + + const char *pModName = GetExecutableModName( moduleName ); + snprintf( szGamePath, sizeof( szGamePath ), "%s/%s", pRootDir, pModName ); + + printf( "[Source Mod Launcher] Launching default game: %s\n", pModName ); + + new_argv.push_back(szGamePath); + } + + new_argv.push_back(NULL); + + execvp( szExecutable, new_argv.data() ); + + return 0; +#endif // NEO } #else From b91a9b91d760b9147fc39887f37ba8110242bf85 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sat, 18 Jul 2026 20:51:23 -0400 Subject: [PATCH 14/21] Let the engine surface the missing-SDK install flow --- src/launcher_main/main.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index fd089a88ee..82e826f82b 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -108,6 +108,12 @@ static const AppId_t k_unMyModAppid = k_unSDK2013MPAppId; #endif static bool s_bInittedSteam = false; +#ifdef NEO +// Set when Steam reports the SDK Base app as not installed. The launch +// continues anyway: the engine detects the missing dependency at gameinfo +// mount and raises Steam's install dialog, same as the retail flow. +static bool s_bSDKNotInstalled = false; +#endif // NEO #ifdef _WIN32 static HMODULE s_SteamModule; @@ -239,7 +245,13 @@ static bool GetGameInstallDir( const char *pRootDir, char *pszBuf, int nBufSize if ( unLength == 0 ) { +#ifdef NEO + // No error box: letting the engine run produces its own prompt plus + // the Steam install dialog for the missing SDK. + s_bSDKNotInstalled = true; +#else MessageBox( 0, "Source SDK 2013 Multiplayer (243750) must be installed to launch this mod.", "Launcher Error", MB_OK ); +#endif // NEO return false; } @@ -526,7 +538,7 @@ int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL // The game runs from its own install with its own bin\; the SDK Base // install is only checked here because gameinfo.txt mounts its assets. char szSDKInstallDir[4096]; - if ( !GetGameInstallDir( pRootDir, szSDKInstallDir, sizeof( szSDKInstallDir ) ) ) + if ( !GetGameInstallDir( pRootDir, szSDKInstallDir, sizeof( szSDKInstallDir ) ) && !s_bSDKNotInstalled ) { return 1; } @@ -838,13 +850,15 @@ int main( int argc, char *argv[] ) } char szSDKInstallDir[4096]; - if ( !GetGameInstallDir( pRootDir, szSDKInstallDir, sizeof( szSDKInstallDir ) ) ) + if ( GetGameInstallDir( pRootDir, szSDKInstallDir, sizeof( szSDKInstallDir ) ) ) + { + CreateSDKCaseShim( szSDKInstallDir ); + } + else if ( !s_bSDKNotInstalled ) { return 1; } - CreateSDKCaseShim( szSDKInstallDir ); - // Build the argument list for LauncherMain, appending a default -game // derived from our binary name (neo_linux64 -> neo) when absent. std::vector new_argv; From c79f7a35e84408cac9d6fd935cba224652482f17 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sat, 18 Jul 2026 21:12:59 -0400 Subject: [PATCH 15/21] Guard the launcher staging copies per-OS --- src/launcher_main/CMakeLists.txt | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/launcher_main/CMakeLists.txt b/src/launcher_main/CMakeLists.txt index 1b61a857af..e1171d0e71 100644 --- a/src/launcher_main/CMakeLists.txt +++ b/src/launcher_main/CMakeLists.txt @@ -62,16 +62,25 @@ endif() if(NEO_COPY_LIBRARIES) # Stage the standalone-install layout: launcher at the install root next to - # neo/, and the steam_api lib where the launcher dlopens it. + # neo/, and the steam_api lib where the launcher dlopens it (game/bin, the + # engine binary dir). add_custom_command(TARGET launcher_main POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${CMAKE_SOURCE_DIR}/../game/ - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_SOURCE_DIR}/../game/bin/linux64 - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_SOURCE_DIR}/lib/public/linux64/libsteam_api.so - ${CMAKE_SOURCE_DIR}/../game/bin/linux64/ VERBATIM ) + # The repo only carries the Linux steam_api runtime; on Windows + # lib/public/x64 has the import lib, and the launcher loads + # bin/x64/steam_api64.dll from a Source SDK Base / depot install. + if(OS_LINUX) + add_custom_command(TARGET launcher_main POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_SOURCE_DIR}/../game/bin/linux64 + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_SOURCE_DIR}/lib/public/linux64/libsteam_api.so + ${CMAKE_SOURCE_DIR}/../game/bin/linux64/ + VERBATIM + ) + endif() endif() From fa05a5a383bab2c26ca24012170255375bf583c8 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sun, 19 Jul 2026 07:57:29 -0400 Subject: [PATCH 16/21] Drop the stale Windows-flow warning --- src/launcher_main/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/launcher_main/main.cpp b/src/launcher_main/main.cpp index 82e826f82b..e57a6bb671 100644 --- a/src/launcher_main/main.cpp +++ b/src/launcher_main/main.cpp @@ -534,7 +534,6 @@ int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdL // Get the root directory the .exe is in char* pRootDir = GetBaseDir( moduleName ); #ifdef NEO - // NOTE: The Windows flow is untested and needs scrutiny before use. // The game runs from its own install with its own bin\; the SDK Base // install is only checked here because gameinfo.txt mounts its assets. char szSDKInstallDir[4096]; From ccdf5d5d8f6902ba552f8beef5b4bc53f8cac52a Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sun, 19 Jul 2026 11:24:10 -0400 Subject: [PATCH 17/21] Tidy launcher CMake indentation and paths --- src/launcher_main/CMakeLists.txt | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/launcher_main/CMakeLists.txt b/src/launcher_main/CMakeLists.txt index e1171d0e71..aa21edc9a0 100644 --- a/src/launcher_main/CMakeLists.txt +++ b/src/launcher_main/CMakeLists.txt @@ -2,7 +2,7 @@ # using the mod's shipped engine binaries; the Source SDK Base 2013 MP # install is only an asset dependency, mounted through gameinfo.txt. if(OS_LINUX) -find_package(SDL2 REQUIRED) + find_package(SDL2 REQUIRED) endif() set(LAUNCHER_SOURCES main.cpp) @@ -19,16 +19,16 @@ add_executable(launcher_main # by stripping from the last underscore: neo_linux64 -> -game neo. The suffix # also avoids colliding with the game/neo directory at the install root. if(OS_LINUX) -set_target_properties(launcher_main - PROPERTIES - OUTPUT_NAME neo_linux64 -) + set_target_properties(launcher_main + PROPERTIES + OUTPUT_NAME neo_linux64 + ) elseif(OS_WINDOWS) -set_target_properties(launcher_main - PROPERTIES - OUTPUT_NAME neo_win64 - WIN32_EXECUTABLE TRUE -) + set_target_properties(launcher_main + PROPERTIES + OUTPUT_NAME neo_win64 + WIN32_EXECUTABLE TRUE + ) endif() target_include_directories(launcher_main @@ -38,18 +38,18 @@ target_include_directories(launcher_main ) if(OS_LINUX) -target_link_libraries(launcher_main - PRIVATE - SDL2::SDL2 - ${CMAKE_DL_LIBS} -) + target_link_libraries(launcher_main + PRIVATE + SDL2::SDL2 + ${CMAKE_DL_LIBS} + ) -# main.cpp defines __wrap_fopen/__wrap_fopen64 which reference __real_fopen/__real_fopen64 -target_link_options(launcher_main - PRIVATE - -Wl,--wrap=fopen - -Wl,--wrap=fopen64 -) + # main.cpp defines __wrap_fopen/__wrap_fopen64 which reference __real_fopen/__real_fopen64 + target_link_options(launcher_main + PRIVATE + -Wl,--wrap=fopen + -Wl,--wrap=fopen64 + ) endif() if(NEO_MOD_APPID) @@ -76,7 +76,7 @@ if(NEO_COPY_LIBRARIES) if(OS_LINUX) add_custom_command(TARGET launcher_main POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_SOURCE_DIR}/../game/bin/linux64 + ${CMAKE_SOURCE_DIR}/../game/bin/linux64/ COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/lib/public/linux64/libsteam_api.so ${CMAKE_SOURCE_DIR}/../game/bin/linux64/ From a92d91979232ae33362d9b84f1f9384678a515f7 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sun, 19 Jul 2026 11:28:23 -0400 Subject: [PATCH 18/21] Scope the fopen guard to tier1 on Windows --- src/tier1/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tier1/CMakeLists.txt b/src/tier1/CMakeLists.txt index 87f2ce6a92..395eb35985 100644 --- a/src/tier1/CMakeLists.txt +++ b/src/tier1/CMakeLists.txt @@ -30,6 +30,7 @@ target_include_directories(tier1 ) if(OS_WINDOWS) + target_compile_definitions(tier1 PRIVATE fopen=dont_use_fopen) target_link_libraries(tier1 PRIVATE Rpcrt4.lib Ws2_32.lib) elseif(OS_LINUX) target_link_libraries(tier1 PUBLIC From e2eb409238a4e6abeb9763cbc24eebac9b2a3f06 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sun, 19 Jul 2026 11:43:12 -0400 Subject: [PATCH 19/21] Build and upload the launcher package in CI --- .github/workflows/cibuild.yml | 19 +++++++++++++++++++ src/CMakeLists.txt | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index 9f436289c5..acb494c989 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -86,6 +86,7 @@ jobs: -DNEO_COPY_LIBRARIES=OFF -DNEO_USE_SEPARATE_BUILD_INFO=ON -DNEO_INSTALL_LIBRARIES=ON + -DNEO_INSTALL_LAUNCHER=ON -DNEO_INSTALL_RESOURCES=OFF -DNEO_EXTRA_ASSETS_ALT_PATH=${{ github.workspace }}/neoAssets @@ -131,6 +132,7 @@ jobs: echo "dedicated=$(find . -regex '\.\/neo-\w*-\w*-dedicated-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" echo "dedicated_debuginfo=$(find . -regex '\.\/neo-\w*-\w*-dedicated-debuginfo-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" echo "gamedata=$(find . -regex '\.\/neo-\w*-\w*-gamedata' -printf '%f')" >> "$GITHUB_ENV" + echo "launcher=$(find . -regex '\.\/neo-\w*-\w*-launcher-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" - name: Upload libraries uses: actions/upload-artifact@v7 @@ -168,6 +170,13 @@ jobs: path: ${{ env.install_dir }}/${{ env.gamedata }} if-no-files-found: error + - name: Upload launcher + uses: actions/upload-artifact@v7 + with: + name: ${{ env.launcher }} + path: ${{ env.install_dir }}/${{ env.launcher }} + if-no-files-found: error + windows-build: name: ${{ matrix.platform.display_name }} ${{ matrix.preset_build_type.display_name }} @@ -241,6 +250,7 @@ jobs: -DNEO_COPY_LIBRARIES=OFF -DNEO_USE_SEPARATE_BUILD_INFO=ON -DNEO_INSTALL_LIBRARIES=ON + -DNEO_INSTALL_LAUNCHER=ON -DNEO_INSTALL_RESOURCES=OFF -DNEO_EXTRA_ASSETS_ALT_PATH=${{ github.workspace }}/neoAssets @@ -286,6 +296,7 @@ jobs: echo "dedicated=$(find . -regex '\.\/neo-\w*-\w*-dedicated-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" echo "dedicated_debuginfo=$(find . -regex '\.\/neo-\w*-\w*-dedicated-debuginfo-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" echo "gamedata=$(find . -regex '\.\/neo-\w*-\w*-gamedata' -printf '%f')" >> "$GITHUB_ENV" + echo "launcher=$(find . -regex '\.\/neo-\w*-\w*-launcher-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" - name: Upload libraries uses: actions/upload-artifact@v7 @@ -315,6 +326,13 @@ jobs: path: ${{ env.install_dir }}/${{ env.dedicated_debuginfo }} if-no-files-found: error + - name: Upload launcher + uses: actions/upload-artifact@v7 + with: + name: ${{ env.launcher }} + path: ${{ env.install_dir }}/${{ env.launcher }} + if-no-files-found: error + pack-resources: name: Windows Native Resources @@ -358,6 +376,7 @@ jobs: -DNEO_COPY_LIBRARIES=OFF -DNEO_INSTALL_LIBRARIES=OFF -DNEO_INSTALL_RESOURCES=ON + -DNEO_INSTALL_LAUNCHER=OFF - name: Install resources run: cmake --install ${{ env.build_dir }} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8d53f55bd3..ac9b4618e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -47,12 +47,12 @@ else () endif () option(NEO_INSTALL_LIBRARIES "Install game libraries" OFF) option(NEO_INSTALL_RESOURCES "Install game resources" OFF) +option(NEO_INSTALL_LAUNCHER "Install the Steam mod launcher" OFF) option(NEO_USE_SEPARATE_BUILD_INFO "Use separate build info on Linux" OFF) option(NEO_ENABLE_CPACK "Enable CPack" OFF) option(NEO_USE_MEM_DEBUG "Enable USE_MEM_DEBUG defines" OFF) option(NEO_GENERATE_GAMEDATA "Generate SourceMod gamedata" ${NEO_DEDICATED}) option(NEO_BUILD_LAUNCHER "Build the Steam mod launcher" ON) -option(NEO_INSTALL_LAUNCHER "Install the Steam mod launcher" OFF) set(NEO_MOD_APPID "3172910" CACHE STRING "Steam appid for the mod launcher's steam_appid.txt") message(STATUS "Treat compile warnings as errors: ${CMAKE_COMPILE_WARNING_AS_ERROR}") @@ -75,10 +75,10 @@ message(STATUS "Alternate location to fetch the extra assets: ${NEO_EXTRA_ASSETS message(STATUS "Directory for output libraries: ${NEO_OUTPUT_LIBRARY_PATH}") message(STATUS "Install game libraries: ${NEO_INSTALL_LIBRARIES}") message(STATUS "Install game resources: ${NEO_INSTALL_RESOURCES}") +message(STATUS "Install the Steam mod launcher: ${NEO_INSTALL_LAUNCHER}") message(STATUS "Use separate build info on Linux: ${NEO_USE_SEPARATE_BUILD_INFO}") message(STATUS "Enable CPack: ${NEO_ENABLE_CPACK}") message(STATUS "Build the Steam mod launcher: ${NEO_BUILD_LAUNCHER}") -message(STATUS "Install the Steam mod launcher: ${NEO_INSTALL_LAUNCHER}") message(STATUS "Mod launcher appid override: ${NEO_MOD_APPID}") message("") From c3ef868191a5d49f65b88e8b26b570fa7bdb1be1 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sun, 19 Jul 2026 12:05:13 -0400 Subject: [PATCH 20/21] Split and ship the launcher debug information --- .github/workflows/cibuild.yml | 16 ++++++++++++++++ src/CMakeLists.txt | 28 +++++++++++++++++++++++++++- src/cmake/utils.cmake | 14 +++++++++++--- src/launcher_main/CMakeLists.txt | 4 ++++ 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml index acb494c989..dbac5a4179 100644 --- a/.github/workflows/cibuild.yml +++ b/.github/workflows/cibuild.yml @@ -133,6 +133,7 @@ jobs: echo "dedicated_debuginfo=$(find . -regex '\.\/neo-\w*-\w*-dedicated-debuginfo-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" echo "gamedata=$(find . -regex '\.\/neo-\w*-\w*-gamedata' -printf '%f')" >> "$GITHUB_ENV" echo "launcher=$(find . -regex '\.\/neo-\w*-\w*-launcher-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" + echo "launcher_debuginfo=$(find . -regex '\.\/neo-\w*-\w*-launcher-debuginfo-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" - name: Upload libraries uses: actions/upload-artifact@v7 @@ -177,6 +178,13 @@ jobs: path: ${{ env.install_dir }}/${{ env.launcher }} if-no-files-found: error + - name: Upload launcher debug information + uses: actions/upload-artifact@v7 + with: + name: ${{ env.launcher_debuginfo }} + path: ${{ env.install_dir }}/${{ env.launcher_debuginfo }} + if-no-files-found: error + windows-build: name: ${{ matrix.platform.display_name }} ${{ matrix.preset_build_type.display_name }} @@ -297,6 +305,7 @@ jobs: echo "dedicated_debuginfo=$(find . -regex '\.\/neo-\w*-\w*-dedicated-debuginfo-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" echo "gamedata=$(find . -regex '\.\/neo-\w*-\w*-gamedata' -printf '%f')" >> "$GITHUB_ENV" echo "launcher=$(find . -regex '\.\/neo-\w*-\w*-launcher-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" + echo "launcher_debuginfo=$(find . -regex '\.\/neo-\w*-\w*-launcher-debuginfo-\w*-\w*' -printf '%f')" >> "$GITHUB_ENV" - name: Upload libraries uses: actions/upload-artifact@v7 @@ -333,6 +342,13 @@ jobs: path: ${{ env.install_dir }}/${{ env.launcher }} if-no-files-found: error + - name: Upload launcher debug information + uses: actions/upload-artifact@v7 + with: + name: ${{ env.launcher_debuginfo }} + path: ${{ env.install_dir }}/${{ env.launcher_debuginfo }} + if-no-files-found: error + pack-resources: name: Windows Native Resources diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ac9b4618e8..cd36b56868 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -599,7 +599,33 @@ if(NEO_INSTALL_LAUNCHER AND NEO_BUILD_LAUNCHER AND NOT NEO_DEDICATED) # steam_api ships with the engine binaries in bin/, so only the launcher # itself is staged here. set(LAUNCHER_INSTALL_PATH "${INSTALL_PATH_PREFIX}-launcher-${CMAKE_SYSTEM_NAME}-${BUILD_TYPE_NAME}") - install(TARGETS launcher_main RUNTIME DESTINATION "${LAUNCHER_INSTALL_PATH}") + + if(NEO_USE_SEPARATE_BUILD_INFO) + set(LAUNCHER_DEBUG_INFO_INSTALL_PATH "${INSTALL_PATH_PREFIX}-launcher-debuginfo-${CMAKE_SYSTEM_NAME}-${BUILD_TYPE_NAME}") + + if(OS_WINDOWS) + install(TARGETS launcher_main RUNTIME DESTINATION "${LAUNCHER_INSTALL_PATH}") + + install( + FILES "$" + DESTINATION "${LAUNCHER_DEBUG_INFO_INSTALL_PATH}" + ) + else() + get_target_property(LAUNCHER_STRIPPED_FILE launcher_main STRIPPED_FILE) + install( + PROGRAMS ${LAUNCHER_STRIPPED_FILE} + DESTINATION "${LAUNCHER_INSTALL_PATH}" + ) + + get_target_property(LAUNCHER_SPLIT_DEBUG_INFO_FILE launcher_main SPLIT_DEBUG_INFO_FILE) + install( + FILES ${LAUNCHER_SPLIT_DEBUG_INFO_FILE} + DESTINATION "${LAUNCHER_DEBUG_INFO_INSTALL_PATH}" + ) + endif() + else() + install(TARGETS launcher_main RUNTIME DESTINATION "${LAUNCHER_INSTALL_PATH}") + endif() endif() if(NEO_INSTALL_RESOURCES) diff --git a/src/cmake/utils.cmake b/src/cmake/utils.cmake index 6b18258b07..7de2927b74 100644 --- a/src/cmake/utils.cmake +++ b/src/cmake/utils.cmake @@ -134,9 +134,17 @@ function(split_debug_information) set(TARGET_OUTPUT_NAME "${PARSED_ARGS_TARGET}") endif() - set(SPLITDEBUG_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_OUTPUT_NAME}.so") - set(SPLITDEBUG_TARGET "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUBDIRECTORY}/${TARGET_OUTPUT_NAME}.so") - set(SPLITDEBUG_TARGET_DEBUG "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUBDIRECTORY}/${TARGET_OUTPUT_NAME}.so.debug") + # Shared libraries carry the .so suffix; executables have none. + get_target_property(TARGET_TYPE ${PARSED_ARGS_TARGET} TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + set(SPLITDEBUG_FILE_NAME "${TARGET_OUTPUT_NAME}") + else() + set(SPLITDEBUG_FILE_NAME "${TARGET_OUTPUT_NAME}.so") + endif() + + set(SPLITDEBUG_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${SPLITDEBUG_FILE_NAME}") + set(SPLITDEBUG_TARGET "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUBDIRECTORY}/${SPLITDEBUG_FILE_NAME}") + set(SPLITDEBUG_TARGET_DEBUG "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUBDIRECTORY}/${SPLITDEBUG_FILE_NAME}.debug") add_custom_target( ${PARSED_ARGS_TARGET}_split_debug_information diff --git a/src/launcher_main/CMakeLists.txt b/src/launcher_main/CMakeLists.txt index aa21edc9a0..09003c64be 100644 --- a/src/launcher_main/CMakeLists.txt +++ b/src/launcher_main/CMakeLists.txt @@ -31,6 +31,10 @@ elseif(OS_WINDOWS) ) endif() +if(NEO_USE_SEPARATE_BUILD_INFO) + split_debug_information(TARGET launcher_main) +endif() + target_include_directories(launcher_main PRIVATE ${CMAKE_SOURCE_DIR}/public From 4e267dd06113d2197b60f74aa2c39e9fa4f1e061 Mon Sep 17 00:00:00 2001 From: sunmachine Date: Sun, 19 Jul 2026 12:28:50 -0400 Subject: [PATCH 21/21] Derive split-debug names from target prefix and suffix --- src/cmake/utils.cmake | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/cmake/utils.cmake b/src/cmake/utils.cmake index 7de2927b74..056a659186 100644 --- a/src/cmake/utils.cmake +++ b/src/cmake/utils.cmake @@ -134,17 +134,33 @@ function(split_debug_information) set(TARGET_OUTPUT_NAME "${PARSED_ARGS_TARGET}") endif() - # Shared libraries carry the .so suffix; executables have none. - get_target_property(TARGET_TYPE ${PARSED_ARGS_TARGET} TYPE) - if(TARGET_TYPE STREQUAL "EXECUTABLE") - set(SPLITDEBUG_FILE_NAME "${TARGET_OUTPUT_NAME}") - else() - set(SPLITDEBUG_FILE_NAME "${TARGET_OUTPUT_NAME}.so") + get_target_property(TARGET_PREFIX ${PARSED_ARGS_TARGET} PREFIX) + if("${TARGET_PREFIX}" STREQUAL "TARGET_PREFIX-NOTFOUND") + get_target_property(TARGET_TYPE ${PARSED_ARGS_TARGET} TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + set(TARGET_PREFIX "") + elseif(TARGET_TYPE STREQUAL "SHARED_LIBRARY") + set(TARGET_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}") + else() + message(WARNING "Unknown TARGET_TYPE value") + endif() + endif() + + get_target_property(TARGET_SUFFIX ${PARSED_ARGS_TARGET} SUFFIX) + if("${TARGET_SUFFIX}" STREQUAL "TARGET_SUFFIX-NOTFOUND") + get_target_property(TARGET_TYPE ${PARSED_ARGS_TARGET} TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + set(TARGET_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}") + elseif(TARGET_TYPE STREQUAL "SHARED_LIBRARY") + set(TARGET_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}") + else() + message(WARNING "Unknown TARGET_TYPE value") + endif() endif() - set(SPLITDEBUG_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${SPLITDEBUG_FILE_NAME}") - set(SPLITDEBUG_TARGET "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUBDIRECTORY}/${SPLITDEBUG_FILE_NAME}") - set(SPLITDEBUG_TARGET_DEBUG "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUBDIRECTORY}/${SPLITDEBUG_FILE_NAME}.debug") + set(SPLITDEBUG_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_PREFIX}${TARGET_OUTPUT_NAME}${TARGET_SUFFIX}") + set(SPLITDEBUG_TARGET "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUBDIRECTORY}/${TARGET_PREFIX}${TARGET_OUTPUT_NAME}${TARGET_SUFFIX}") + set(SPLITDEBUG_TARGET_DEBUG "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUBDIRECTORY}/${TARGET_PREFIX}${TARGET_OUTPUT_NAME}${TARGET_SUFFIX}.debug") add_custom_target( ${PARSED_ARGS_TARGET}_split_debug_information