From 708f15c3844378adad40826471b04f899acb2483 Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Sun, 14 Jun 2026 22:06:07 -0400 Subject: [PATCH 01/12] steam_networking_sockets: implement legacy SteamNetConnectionInfo001 poll The old ISteamNetworkingSockets001-011 interfaces polled connection state via SteamNetConnectionInfo001_t through an unimplemented stub returning false, so UE5's SteamSockets driver never saw the connection reach Connected and never sent data (120s timeout). Implement set_steamnetconnectioninfo_001 (from sns_fix 2589077); keeps a SPLITUX-DEBUG wire trace. --- dll/dll/steam_networking_sockets.h | 1 + dll/steam_networking_sockets.cpp | 60 ++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/dll/dll/steam_networking_sockets.h b/dll/dll/steam_networking_sockets.h index 036376d33..13eb1b47e 100644 --- a/dll/dll/steam_networking_sockets.h +++ b/dll/dll/steam_networking_sockets.h @@ -115,6 +115,7 @@ public ISteamNetworkingSockets ESteamNetworkingConnectionState convert_status(enum connect_socket_status old_status); void set_steamnetconnectioninfo(std::map::iterator connect_socket, SteamNetConnectionInfo_t *pInfo); + void set_steamnetconnectioninfo_001(std::map::iterator connect_socket, SteamNetConnectionInfo001_t* pInfo); void launch_callback(HSteamNetConnection m_hConn, enum connect_socket_status old_status); diff --git a/dll/steam_networking_sockets.cpp b/dll/steam_networking_sockets.cpp index c2bf3504a..982b021d9 100644 --- a/dll/steam_networking_sockets.cpp +++ b/dll/steam_networking_sockets.cpp @@ -132,6 +132,13 @@ bool Steam_Networking_Sockets::send_packet_new_connection(HSteamNetConnection m_ msg.mutable_networking_sockets()->set_connection_id_from(connect_socket->first); msg.mutable_networking_sockets()->set_connection_id(connect_socket->second.remote_id); + // SPLITUX-DEBUG: what we put on the wire for REQUEST/ACCEPTED. + PRINT_DEBUG("SPLITUX send_new_conn: my_socket=%u status=%d -> type=%s conn_id(remote target)=%u conn_id_from(me)=%u dest=%" PRIu64, + connect_socket->first, (int)connect_socket->second.status, + connect_socket->second.status == CONNECT_SOCKET_CONNECTED ? "ACCEPTED" : "REQUEST", + connect_socket->second.remote_id, connect_socket->first, + connect_socket->second.remote_identity.GetSteamID64()); + uint64_t steam_id = connect_socket->second.remote_identity.GetSteamID64(); if (steam_id) { msg.set_dest_id(steam_id); @@ -201,6 +208,30 @@ void Steam_Networking_Sockets::set_steamnetconnectioninfo(std::map::iterator connect_socket, SteamNetConnectionInfo001_t* pInfo) +{ + pInfo->m_steamIDRemote = connect_socket->second.remote_identity.GetSteamID(); + pInfo->m_nUserData = connect_socket->second.user_data; + pInfo->m_hListenSocket = connect_socket->second.listen_socket_id; + if (connect_socket->second.real_port != SNS_DISABLED_PORT) { + pInfo->m_unIPRemote = network->getIP(connect_socket->second.remote_identity.GetSteamID()); + pInfo->m_unPortRemote = connect_socket->first; + } + + pInfo->m_idPOPRemote = 0; + pInfo->m_idPOPRelay = 0; + pInfo->m_eState = convert_status(connect_socket->second.status); + pInfo->m_eEndReason = 0; //TODO + pInfo->m_szEndDebug[0] = 0; + + //Note some games might not allocate a struct the whole size of SteamNetConnectionInfo_t when calling GetConnectionInfo + //keep this in mind in future interface updates +} + void Steam_Networking_Sockets::launch_callback(HSteamNetConnection m_hConn, enum connect_socket_status old_status) { auto connect_socket = sbcs->connect_sockets.find(m_hConn); @@ -576,7 +607,7 @@ bool Steam_Networking_Sockets::CloseListenSocket( HSteamListenSocket hSocket, co { PRINT_DEBUG("old"); std::lock_guard lock(global_mutex); - return false; + return CloseListenSocket(hSocket); } /// Destroy a listen socket. All the connections that were accepting on the listen @@ -993,9 +1024,16 @@ int Steam_Networking_Sockets::ReceiveMessagesOnListenSocket( HSteamListenSocket /// Returns information about the specified connection. bool Steam_Networking_Sockets::GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo001_t *pInfo ) { - PRINT_DEBUG_TODO(); + PRINT_DEBUG("001 %u %i", hConn, pInfo == NULL); std::lock_guard lock(global_mutex); - return false; + + if (!pInfo) return false; + + auto connect_socket = sbcs->connect_sockets.find(hConn); + if (connect_socket == sbcs->connect_sockets.end()) return false; + + set_steamnetconnectioninfo_001(connect_socket, pInfo); + return true; } @@ -2077,6 +2115,15 @@ void Steam_Networking_Sockets::Callback(Common_Message *msg) } } else if (msg->networking_sockets().type() == Networking_Sockets::CONNECTION_ACCEPTED) { + // SPLITUX-DEBUG: trace why same-host two-instance P2P accept doesn't take. + PRINT_DEBUG("SPLITUX ACCEPTED rx: conn_id(local target)=%" PRIu64 " conn_id_from(remote)=%" PRIu64 " src=%" PRIu64 " #sockets=%zu", + msg->networking_sockets().connection_id(), msg->networking_sockets().connection_id_from(), + msg->source_id(), sbcs->connect_sockets.size()); + for (auto &cs : sbcs->connect_sockets) { + PRINT_DEBUG("SPLITUX have socket id=%u status=%d remote_steam=%" PRIu64 " remote_id=%u vport=%d", + cs.first, (int)cs.second.status, cs.second.remote_identity.GetSteamID64(), + cs.second.remote_id, cs.second.virtual_port); + } auto connect_socket = sbcs->connect_sockets.find(static_cast(msg->networking_sockets().connection_id())); if (connect_socket != sbcs->connect_sockets.end()) { if (connect_socket->second.remote_identity.GetSteamID64() == 0) { @@ -2087,7 +2134,14 @@ void Steam_Networking_Sockets::Callback(Common_Message *msg) connect_socket->second.remote_id = static_cast(msg->networking_sockets().connection_id_from()); connect_socket->second.status = CONNECT_SOCKET_CONNECTED; launch_callback(connect_socket->first, CONNECT_SOCKET_CONNECTING); + PRINT_DEBUG("SPLITUX ACCEPTED matched -> socket %u now CONNECTED", connect_socket->first); + } else { + PRINT_DEBUG("SPLITUX ACCEPTED found socket %u but NOT transitioned (remote_steam=%" PRIu64 " vs src=%" PRIu64 ", status=%d)", + connect_socket->first, connect_socket->second.remote_identity.GetSteamID64(), + msg->source_id(), (int)connect_socket->second.status); } + } else { + PRINT_DEBUG("SPLITUX ACCEPTED NO socket matched conn_id=%" PRIu64, msg->networking_sockets().connection_id()); } } else if (msg->networking_sockets().type() == Networking_Sockets::DATA) { auto connect_socket = sbcs->connect_sockets.find(static_cast(msg->networking_sockets().connection_id())); From 514eb9092168ab2086c6c90786a8e1e1129b733b Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Mon, 15 Jun 2026 10:25:14 -0400 Subject: [PATCH 02/12] dbg_log: opt-in logging in release builds via GSE_FORCE_LOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Release builds previously compiled out ALL logging: PRINT_DEBUG expanded to nothing (EMU_RELEASE_BUILD), the global dbg_logger and every dbg_log method body were stripped. That makes a shipped emulator impossible to observe (e.g. LAN lobby matchmaking) without a separate debug build. Compile logging into release too, gated at runtime by the GSE_FORCE_LOG env var (dbg_log::is_active): debug builds always log; release logs only when the var is set. PRINT_DEBUG now wraps its body in 'if (dbg_logger.is_active())' so an inactive logger costs one cached bool check and runs NO side effects — in particular it must not call PRINT_DEBUG_CLEANUP/WSASetLastError, which would clobber the game's last socket error on every call. open() is the single runtime gate; all write paths check out_file, so inactive logging no-ops. Lets us turn on full goldberg logs (STEAM_LOG_.log next to the exe) on a release DLL by exporting GSE_FORCE_LOG=1, with zero impact when unset. --- dll/base.cpp | 6 ++-- dll/dll/common_includes.h | 57 ++++++++++++++++++----------------- helpers/dbg_log.cpp | 60 ++++++++++++++----------------------- helpers/dbg_log/dbg_log.hpp | 6 ++++ 4 files changed, 63 insertions(+), 66 deletions(-) diff --git a/dll/base.cpp b/dll/base.cpp index 6131ead78..d0ce3f3b6 100644 --- a/dll/base.cpp +++ b/dll/base.cpp @@ -28,9 +28,11 @@ std::recursive_mutex global_mutex{}; extern const std::chrono::time_point startup_counter = std::chrono::high_resolution_clock::now(); extern const std::chrono::time_point startup_time = std::chrono::system_clock::now(); -#ifndef EMU_RELEASE_BUILD +// Defined in release too so logging can be activated at runtime via GSE_FORCE_LOG +// (dbg_log::is_active). The constructor only builds the path string; the file is not +// opened unless logging actually activates, so release builds with logging off pay +// nothing beyond a static string + one cached env check. dbg_log dbg_logger(get_full_program_path() + "STEAM_LOG_" + std::to_string(common_helpers::rand_number(UINT32_MAX)) + ".log"); -#endif #ifdef __WINDOWS__ diff --git a/dll/dll/common_includes.h b/dll/dll/common_includes.h index 1457155ee..a476c4a0e 100644 --- a/dll/dll/common_includes.h +++ b/dll/dll/common_includes.h @@ -165,36 +165,39 @@ static inline void reset_LastError() #endif // PRINT_DEBUG definition -#ifndef EMU_RELEASE_BUILD - #include "dbg_log/dbg_log.hpp" - // we need this for printf specifiers for intptr_t such as PRIdPTR - #include - - #if defined(__WINDOWS__) - #define PRINT_DEBUG_TID() (long long)GetCurrentThreadId() - #define PRINT_DEBUG_CLEANUP() WSASetLastError(0) - #elif defined(__LINUX__) - #include // syscall - - #define PRINT_DEBUG_TID() (long long)syscall(SYS_gettid) - #define PRINT_DEBUG_CLEANUP() (void)0 - #else - #warning "Unrecognized OS" - - #define PRINT_DEBUG_TID() (long long)0 - #define PRINT_DEBUG_CLEANUP() (void)0 - #endif +// Logging is compiled into release builds too, but only ACTIVATES at runtime when +// the GSE_FORCE_LOG env var is set (see dbg_log::is_active). This lets us observe a +// shipped (release) emulator — e.g. LAN lobby matchmaking — without a debug build. +// When inactive, PRINT_DEBUG is a single cached bool check: no formatting and, in +// particular, NO PRINT_DEBUG_CLEANUP (WSASetLastError(0) would otherwise clobber the +// game's last socket error on every call). Debug builds: always active. +#include "dbg_log/dbg_log.hpp" +// we need this for printf specifiers for intptr_t such as PRIdPTR +#include + +#if defined(__WINDOWS__) + #define PRINT_DEBUG_TID() (long long)GetCurrentThreadId() + #define PRINT_DEBUG_CLEANUP() WSASetLastError(0) +#elif defined(__LINUX__) + #include // syscall - extern dbg_log dbg_logger; + #define PRINT_DEBUG_TID() (long long)syscall(SYS_gettid) + #define PRINT_DEBUG_CLEANUP() (void)0 +#else + #warning "Unrecognized OS" + + #define PRINT_DEBUG_TID() (long long)0 + #define PRINT_DEBUG_CLEANUP() (void)0 +#endif - #define PRINT_DEBUG(a, ...) do { \ - dbg_logger.write("[tid %lld] %s " a, PRINT_DEBUG_TID(), EMU_FUNC_NAME, ##__VA_ARGS__); \ - PRINT_DEBUG_CLEANUP(); \ - } while (0) +extern dbg_log dbg_logger; -#else // EMU_RELEASE_BUILD - #define PRINT_DEBUG(...) -#endif // EMU_RELEASE_BUILD +#define PRINT_DEBUG(a, ...) do { \ + if (dbg_logger.is_active()) { \ + dbg_logger.write("[tid %lld] %s " a, PRINT_DEBUG_TID(), EMU_FUNC_NAME, ##__VA_ARGS__); \ + PRINT_DEBUG_CLEANUP(); \ + } \ +} while (0) // function entry #define PRINT_DEBUG_ENTRY() PRINT_DEBUG("") diff --git a/helpers/dbg_log.cpp b/helpers/dbg_log.cpp index 732eaf32d..d1ddf277c 100644 --- a/helpers/dbg_log.cpp +++ b/helpers/dbg_log.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -13,10 +14,28 @@ #include "common_helpers/os_detector.h" -void dbg_log::open() +// Logging is compiled into release builds, but only ACTIVATES when GSE_FORCE_LOG is +// set in the environment (any non-empty value other than "0"). Debug builds always +// log. The single runtime gate lives here (and is mirrored by PRINT_DEBUG so an +// inactive logger does no work and has no side effects); every write path goes +// through open(), so when inactive out_file stays null and all writes no-op. +bool dbg_log::is_active() { #ifndef EMU_RELEASE_BUILD - if (!out_file && filepath.size()) { + return true; +#else + static int cached = -1; + if (cached < 0) { + const char* e = std::getenv("GSE_FORCE_LOG"); + cached = (e && e[0] && e[0] != '0') ? 1 : 0; + } + return cached == 1; +#endif +} + +void dbg_log::open() +{ + if (is_active() && !out_file && filepath.size()) { // https://en.cppreference.com/w/cpp/filesystem/path/u8path const auto fsp = std::filesystem::u8path(filepath); @@ -27,9 +46,6 @@ void dbg_log::open() #endif } - -#endif - } void dbg_log::write_stamp() @@ -47,53 +63,33 @@ void dbg_log::write_stamp() dbg_log::dbg_log(std::string_view path) { - -#ifndef EMU_RELEASE_BUILD + // Store the path unconditionally; open() decides whether to actually open it + // (release: only when GSE_FORCE_LOG is set). Storing a string is free. filepath = path; -#endif - } dbg_log::dbg_log(std::wstring_view path) { - -#ifndef EMU_RELEASE_BUILD filepath = common_helpers::to_str(path); -#endif - } dbg_log::~dbg_log() { - -#ifndef EMU_RELEASE_BUILD close(); -#endif - } void dbg_log::write(const std::string &str) { - -#ifndef EMU_RELEASE_BUILD write("%s", str.c_str()); -#endif - } void dbg_log::write(const std::wstring &str) { - -#ifndef EMU_RELEASE_BUILD write("%s", str.c_str()); -#endif - } void dbg_log::write(const char *fmt, ...) { - -#ifndef EMU_RELEASE_BUILD std::lock_guard lk(f_mtx); open(); if (out_file) { @@ -107,14 +103,10 @@ void dbg_log::write(const char *fmt, ...) std::fprintf(out_file, "\n"); std::fflush(out_file); } -#endif - } void dbg_log::write(const wchar_t *fmt, ...) { - -#ifndef EMU_RELEASE_BUILD std::lock_guard lk(f_mtx); open(); if (out_file) { @@ -128,20 +120,14 @@ void dbg_log::write(const wchar_t *fmt, ...) std::fprintf(out_file, "\n"); std::fflush(out_file); } -#endif - } void dbg_log::close() { - -#ifndef EMU_RELEASE_BUILD std::lock_guard lk(f_mtx); if (out_file) { std::fprintf(out_file, "\nLog file closed\n\n"); std::fclose(out_file); out_file = nullptr; } -#endif - } diff --git a/helpers/dbg_log/dbg_log.hpp b/helpers/dbg_log/dbg_log.hpp index 8dd59c811..532ecc272 100644 --- a/helpers/dbg_log/dbg_log.hpp +++ b/helpers/dbg_log/dbg_log.hpp @@ -29,4 +29,10 @@ class dbg_log void write(const wchar_t* fmt, ...); void close(); + + // True when logging should actually happen: always in debug builds; in release + // builds only when the GSE_FORCE_LOG env var is set (cached on first call). + // PRINT_DEBUG gates its whole body on this so an inactive logger has ~zero cost + // and no side effects. + bool is_active(); }; From d3b0bb656591e556c701d20e015b7cb958b80c85 Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Wed, 17 Jun 2026 11:45:04 -0400 Subject: [PATCH 03/12] steam_client: return null instead of fatal-exit on unknown interface version The interface getters in steam_client_interface_getter.cpp called report_missing_impl_and_exit for any version they did not hardcode, which std::exit()'d the whole game. Real steamclient returns null for unknown versions, so emulate that: all 35 getters now log via report_missing_impl and return nullptr. Also drop the blocking 'Missing interface' MessageBox from report_missing_impl (it could fatally stall games under Proton/Wine); it is now a pure non-blocking file logger. Legacy report_missing_impl_and_exit kept but unused. Also fix emu-build-all-win.yml: the ')' inside the 'echo ... (%%A, release)' closed the FOR do( block early, so every per-arch target silently failed and only lib_steam_old shipped. Parens -> brackets. --- .github/workflows/emu-build-all-win.yml | 2 +- dll/steam_client_interface_getter.cpp | 113 ++++++++++++++++-------- 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 8c0365c01..e146d2808 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -94,7 +94,7 @@ jobs: for %%P in (api_regular api_experimental steamclient_experimental_stub steamclient_experimental steamclient_experimental_loader steamclient_experimental_extra lib_game_overlay_renderer lib_steamnetworkingsockets tool_lobby_connect tool_generate_interfaces) do ( for %%A in (x64 Win32) do ( - echo === Building %%P (%%A, release) === + echo === Building %%P [%%A release] === msbuild /nologo /t:%%P /m /v:m /p:Configuration=release,Platform=%%A gbe.sln if errorlevel 1 echo Warning: %%P %%A release build failed ) diff --git a/dll/steam_client_interface_getter.cpp b/dll/steam_client_interface_getter.cpp index 264628a09..36eb7091d 100644 --- a/dll/steam_client_interface_getter.cpp +++ b/dll/steam_client_interface_getter.cpp @@ -30,7 +30,8 @@ ISteamBilling *Steam_Client::GetISteamBilling( HSteamUser hSteamUser, HSteamPipe return reinterpret_cast(static_cast(steam_billing)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } void *Steam_Client::GetISteamBilling_old( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) @@ -49,7 +50,8 @@ ISteamAppDisableUpdate *Steam_Client::GetISteamAppDisableUpdate( HSteamUser hSte return reinterpret_cast(static_cast(steam_app_disable_update)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // retrieves the ISteamTimeline interface associated with the handle @@ -68,7 +70,8 @@ ISteamTimeline *Steam_Client::GetISteamTimeline( HSteamUser hSteamUser, HSteamPi return reinterpret_cast(static_cast(steam_timeline)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // retrieves the ISteamGameStats interface associated with the handle @@ -89,7 +92,8 @@ ISteamGameStats *Steam_Client::GetISteamGameStats( HSteamUser hSteamUser, HSteam return reinterpret_cast(static_cast(steam_gamestats_tmp)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // retrieves the ISteamUser interface associated with the handle @@ -173,7 +177,8 @@ ISteamUser *Steam_Client::GetISteamUser( HSteamUser hSteamUser, HSteamPipe hStea return reinterpret_cast(static_cast(steam_user_tmp)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // retrieves the ISteamGameServer interface associated with the handle @@ -218,7 +223,8 @@ ISteamGameServer *Steam_Client::GetISteamGameServer( HSteamUser hSteamUser, HSte return reinterpret_cast(static_cast(steam_gameserver)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // returns the ISteamFriends interface @@ -265,7 +271,8 @@ ISteamFriends *Steam_Client::GetISteamFriends( HSteamUser hSteamUser, HSteamPipe return reinterpret_cast(static_cast(steam_friends)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // returns the ISteamUtils interface @@ -305,7 +312,8 @@ ISteamUtils *Steam_Client::GetISteamUtils( HSteamPipe hSteamPipe, const char *pc return reinterpret_cast(static_cast(steam_utils_temp)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // returns the ISteamMatchmaking interface @@ -334,7 +342,8 @@ ISteamMatchmaking *Steam_Client::GetISteamMatchmaking( HSteamUser hSteamUser, HS return reinterpret_cast(static_cast(steam_matchmaking)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // returns the ISteamMatchmakingServers interface @@ -349,7 +358,8 @@ ISteamMatchmakingServers *Steam_Client::GetISteamMatchmakingServers( HSteamUser return reinterpret_cast(static_cast(steam_matchmaking_servers)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // returns the a generic interface @@ -530,7 +540,8 @@ void *Steam_Client::GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe } PRINT_DEBUG("No interface: %s", pchVersion); - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // returns the ISteamUserStats interface @@ -567,7 +578,8 @@ ISteamUserStats *Steam_Client::GetISteamUserStats( HSteamUser hSteamUser, HSteam return reinterpret_cast(static_cast(steam_user_stats)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // returns the ISteamGameServerStats interface @@ -580,7 +592,8 @@ ISteamGameServerStats *Steam_Client::GetISteamGameServerStats( HSteamUser hSteam return reinterpret_cast(static_cast(steam_gameserverstats)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // returns apps interface @@ -614,7 +627,8 @@ ISteamApps *Steam_Client::GetISteamApps( HSteamUser hSteamUser, HSteamPipe hStea return reinterpret_cast(static_cast(steam_apps_temp)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // networking @@ -645,7 +659,8 @@ ISteamNetworking *Steam_Client::GetISteamNetworking( HSteamUser hSteamUser, HSte return reinterpret_cast(static_cast(steam_networking_temp)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // remote storage @@ -688,7 +703,8 @@ ISteamRemoteStorage *Steam_Client::GetISteamRemoteStorage( HSteamUser hSteamuser return reinterpret_cast(static_cast(steam_remote_storage)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // user screenshots @@ -705,7 +721,8 @@ ISteamScreenshots *Steam_Client::GetISteamScreenshots( HSteamUser hSteamuser, HS return reinterpret_cast(static_cast(steam_screenshots)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } @@ -730,7 +747,8 @@ ISteamHTTP *Steam_Client::GetISteamHTTP( HSteamUser hSteamuser, HSteamPipe hStea return reinterpret_cast(static_cast(steam_http_temp)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // Deprecated - the ISteamUnifiedMessages interface is no longer intended for public consumption. @@ -743,7 +761,8 @@ void *Steam_Client::DEPRECATED_GetISteamUnifiedMessages( HSteamUser hSteamuser, return reinterpret_cast(static_cast(steam_unified_messages)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } ISteamUnifiedMessages *Steam_Client::GetISteamUnifiedMessages( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) @@ -755,7 +774,8 @@ ISteamUnifiedMessages *Steam_Client::GetISteamUnifiedMessages( HSteamUser hSteam return reinterpret_cast(static_cast(steam_unified_messages)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // Exposes the ISteamController interface @@ -784,7 +804,8 @@ ISteamController *Steam_Client::GetISteamController( HSteamUser hSteamUser, HSte return reinterpret_cast(static_cast(steam_controller)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // Exposes the ISteamUGC interface @@ -847,7 +868,8 @@ ISteamUGC *Steam_Client::GetISteamUGC( HSteamUser hSteamUser, HSteamPipe hSteamP return reinterpret_cast(static_cast(steam_ugc_temp)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // returns app list interface, only available on specially registered apps @@ -860,7 +882,8 @@ ISteamAppList *Steam_Client::GetISteamAppList( HSteamUser hSteamUser, HSteamPipe return reinterpret_cast(static_cast(steam_applist)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // Music Player @@ -873,7 +896,8 @@ ISteamMusic *Steam_Client::GetISteamMusic( HSteamUser hSteamuser, HSteamPipe hSt return reinterpret_cast(static_cast(steam_music)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // Music Player Remote @@ -886,7 +910,8 @@ ISteamMusicRemote *Steam_Client::GetISteamMusicRemote(HSteamUser hSteamuser, HSt return reinterpret_cast(static_cast(steam_musicremote)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // html page display @@ -907,7 +932,8 @@ ISteamHTMLSurface *Steam_Client::GetISteamHTMLSurface(HSteamUser hSteamuser, HSt return reinterpret_cast(static_cast(steam_HTMLsurface)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // inventory @@ -931,7 +957,8 @@ ISteamInventory *Steam_Client::GetISteamInventory( HSteamUser hSteamuser, HSteam return reinterpret_cast(static_cast(steam_inventory_temp)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // Video @@ -950,7 +977,8 @@ ISteamVideo *Steam_Client::GetISteamVideo( HSteamUser hSteamuser, HSteamPipe hSt return reinterpret_cast(static_cast(steam_video)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // Parental controls @@ -963,7 +991,8 @@ ISteamParentalSettings *Steam_Client::GetISteamParentalSettings( HSteamUser hSte return reinterpret_cast(static_cast(steam_parental)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } ISteamMasterServerUpdater *Steam_Client::GetISteamMasterServerUpdater( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) @@ -975,7 +1004,8 @@ ISteamMasterServerUpdater *Steam_Client::GetISteamMasterServerUpdater( HSteamUse return reinterpret_cast(static_cast(steam_masterserver_updater)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } ISteamContentServer *Steam_Client::GetISteamContentServer( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) @@ -995,7 +1025,8 @@ ISteamGameSearch *Steam_Client::GetISteamGameSearch( HSteamUser hSteamuser, HSte return reinterpret_cast(static_cast(steam_game_search)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // Exposes the Steam Input interface for controller support @@ -1014,7 +1045,8 @@ ISteamInput *Steam_Client::GetISteamInput( HSteamUser hSteamUser, HSteamPipe hSt return reinterpret_cast(static_cast(steam_controller)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } // Steam Parties interface @@ -1027,7 +1059,8 @@ ISteamParties *Steam_Client::GetISteamParties( HSteamUser hSteamUser, HSteamPipe return reinterpret_cast(static_cast(steam_parties)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } ISteamRemotePlay *Steam_Client::GetISteamRemotePlay( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) @@ -1043,7 +1076,8 @@ ISteamRemotePlay *Steam_Client::GetISteamRemotePlay( HSteamUser hSteamUser, HSte return reinterpret_cast(static_cast(steam_remoteplay)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } ISteamAppTicket *Steam_Client::GetAppTicket( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) @@ -1055,7 +1089,8 @@ ISteamAppTicket *Steam_Client::GetAppTicket( HSteamUser hSteamUser, HSteamPipe h return reinterpret_cast(static_cast(steam_app_ticket)); } - report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); + report_missing_impl(pchVersion, EMU_FUNC_NAME); + return nullptr; // unknown interface version: emulate real steamclient (null), never exit } void Steam_Client::report_missing_impl(std::string_view itf, std::string_view caller) @@ -1095,11 +1130,13 @@ void Steam_Client::report_missing_impl(std::string_view itf, std::string_view ca } catch(...) { } -#if defined(__WINDOWS__) - MessageBoxA(nullptr, ss.str().c_str(), "Missing interface", MB_OK); -#endif + // NOTE: no MessageBox here. The blocking "Missing interface" modal could + // fatally stall/kill games under Proton/Wine. report_missing_impl is now a + // pure (non-blocking, non-exiting) logger; callers decide how to recover. } +// Legacy: log + hard-exit. No longer used by the interface getters (they now +// return null like real steamclient on an unknown version) — kept for compat. void Steam_Client::report_missing_impl_and_exit(std::string_view itf, std::string_view caller) { report_missing_impl(itf, caller); From 75c283ad0734848214efa998ca9230fc36d1ccba Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Wed, 17 Jun 2026 12:40:51 -0400 Subject: [PATCH 04/12] ci: fix release pipeline so steam_api DLLs actually build + ship Three pre-existing breakages stopped the Windows release from producing any steam_api dll (only the no-longer-relevant Steam.dll shipped): 1. Stale deps cache. emu-deps-win.yml skipped rebuild whenever protobuf existed, so a partial cache (protobuf present, zlibstatic.lib missing, stale ImGui / InGameOverlay) was treated as complete forever -> api_regular failed LNK1181 'cannot open input file zlibstatic.lib' and api_experimental failed compiling steam_overlay.cpp against old ImGui/InGameOverlay headers. Fix: add a force_rebuild input that wipes the cache, and strengthen the 'already built' check to also require zlib\install64\lib\zlibstatic.lib so a partial cache refreshes itself. 2. lib_steam_old link error. The GSE_FORCE_LOG refactor (514eb909) made PRINT_DEBUG reference the global dbg_logger in release builds too, and un-gated its definition in base.cpp -- but steam_old_lib/dllmain.cpp still defined dbg_logger only under !EMU_RELEASE_BUILD, so release linked with an unresolved external. Define it unconditionally, matching base.cpp. 3. Build step aborted before packaging. A trailing warn-only target failure (dev-only tool signing, lib_steam_old) leaked a non-zero exit and skipped packaging/release. End the build step with exit /b 0 and add a 'Verify shippable DLLs built' gate that fails the job only when the four regular/experimental steam_api(64).dll outputs are missing. --- .github/workflows/emu-build-all-win.yml | 26 ++++++++++++++++++++++ .github/workflows/emu-deps-win.yml | 29 ++++++++++++++++++++++--- steam_old_lib/dllmain.cpp | 10 ++++----- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index e146d2808..323cad3be 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -104,6 +104,32 @@ jobs: msbuild /nologo /t:lib_steam_old /m /v:m /p:Configuration=release,Platform=Win32 gbe.sln if errorlevel 1 echo Warning: lib_steam_old Win32 release build failed + rem Per-target failures above are warn-only (e.g. signing of a dev-only tool, + rem or lib_steam_old). Don't let them abort the job here; the next step gates + rem the release on the actual shippable DLLs existing. + exit /b 0 + + - name: "Verify shippable DLLs built" + shell: cmd + working-directory: "${{ github.workspace }}" + run: | + setlocal enabledelayedexpansion + set "OUT=%GITHUB_WORKSPACE%\build\win\vs2022\release" + set MISSING=0 + for %%F in ( + "regular\x64\steam_api64.dll" + "regular\x86\steam_api.dll" + "experimental\x64\steam_api64.dll" + "experimental\x86\steam_api.dll" + ) do ( + if exist "%OUT%\%%~F" ( echo OK: %%~F ) else ( echo MISSING: %%~F & set MISSING=1 ) + ) + if "!MISSING!"=="1" ( + echo One or more shippable steam_api DLLs are missing - failing the build. + exit /b 1 + ) + echo All shippable steam_api DLLs present. + - name: "Package build (release)" shell: cmd working-directory: "${{ github.workspace }}" diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index 8cf613082..c08ed292b 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -2,7 +2,19 @@ name: "Emu third-party dependencies (Windows)" on: workflow_call: + inputs: + force_rebuild: + description: "Wipe the local deps cache and rebuild from scratch" + required: false + type: boolean + default: false workflow_dispatch: + inputs: + force_rebuild: + description: "Wipe the local deps cache and rebuild from scratch" + required: false + type: boolean + default: false permissions: contents: "write" @@ -23,12 +35,23 @@ jobs: id: "local-deps-check" shell: cmd run: | - if exist "%LOCAL_DEPS_DIR%\vs2022\protobuf\install64" ( + if /I "${{ inputs.force_rebuild }}"=="true" ( + echo Force rebuild requested - wiping %LOCAL_DEPS_DIR%\vs2022 + if exist "%LOCAL_DEPS_DIR%\vs2022" rmdir /s /q "%LOCAL_DEPS_DIR%\vs2022" + ) + rem Require markers from multiple deps, not just protobuf: a partial cache + rem (e.g. protobuf present but zlibstatic.lib missing) used to pass this + rem check and never refresh, which broke api_regular linking. Treat a + rem missing critical lib as "needs rebuild". + set DEPS_OK=1 + if not exist "%LOCAL_DEPS_DIR%\vs2022\protobuf\install64" set DEPS_OK=0 + if not exist "%LOCAL_DEPS_DIR%\vs2022\zlib\install64\lib\zlibstatic.lib" set DEPS_OK=0 + if "%DEPS_OK%"=="1" ( echo deps_exist=true>>%GITHUB_OUTPUT% - echo Deps already exist at %LOCAL_DEPS_DIR% + echo Deps already exist and look complete at %LOCAL_DEPS_DIR% ) else ( echo deps_exist=false>>%GITHUB_OUTPUT% - echo Deps not found, will build + echo Deps missing or incomplete, will build ) - name: "Checkout branch" diff --git a/steam_old_lib/dllmain.cpp b/steam_old_lib/dllmain.cpp index 73fd974be..545fab2c0 100644 --- a/steam_old_lib/dllmain.cpp +++ b/steam_old_lib/dllmain.cpp @@ -18,12 +18,13 @@ #include // WinVerifyTrust -#ifndef EMU_RELEASE_BUILD #include "dbg_log/dbg_log.hpp" -#endif - -#ifndef EMU_RELEASE_BUILD +// Define the global dbg_logger unconditionally: PRINT_DEBUG (common_includes.h) +// references it in release builds too now (the GSE_FORCE_LOG refactor gates logging +// at runtime via dbg_log::is_active(), not at compile time). base.cpp defines it the +// same way; leaving this under EMU_RELEASE_BUILD made lib_steam_old fail to link in +// release (LNK2019 unresolved dbg_logger). dbg_log dbg_logger( []{ static wchar_t dll_path[8192]{}; @@ -33,7 +34,6 @@ dbg_log dbg_logger( + "STEAM_OLD_LOG_" + std::to_string(common_helpers::rand_number(UINT32_MAX)) + ".log"; }() ); -#endif static bool dll_loaded = false; From abf86df13850278daa73d63ef18abbaa95e4c599 Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Wed, 17 Jun 2026 12:46:14 -0400 Subject: [PATCH 05/12] deps: drop CURL_CA_FALLBACK (OpenSSL-only) so clean curl build configures A from-scratch deps rebuild failed at curl CMake configure: 'CURL_CA_FALLBACK only works with OpenSSL' (CMakeLists.txt:1409). We build curl with MbedTLS (CURL_USE_OPENSSL=OFF), and newer curl hard-errors when CURL_CA_FALLBACK is set without OpenSSL. The flag is an OpenSSL-only CA-cert fallback feature and is meaningless with MbedTLS, so drop it. Latent since the Jan deps bump; masked because the persistent per-runner caches predate it and clean rebuilds were never exercised. --- premake5-deps.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/premake5-deps.lua b/premake5-deps.lua index 2c5c9b888..7bf946896 100644 --- a/premake5-deps.lua +++ b/premake5-deps.lua @@ -666,7 +666,9 @@ if _OPTIONS["build-curl"] or _OPTIONS["all-build"] then "CURL_USE_MBEDTLS=ON", -- "CURL_USE_SCHANNEL=ON", - "CURL_CA_FALLBACK=ON", + -- CURL_CA_FALLBACK is OpenSSL-only; newer curl hard-errors when it is set + -- without CURL_USE_OPENSSL ("CURL_CA_FALLBACK only works with OpenSSL"), + -- which broke clean deps rebuilds. We build curl with MbedTLS, so omit it. -- fix building on Arch Linux "CURL_USE_LIBSSH2=OFF", From 77e8f3a0ad25a95f86c32526aa8104217b37518f Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Wed, 17 Jun 2026 12:57:27 -0400 Subject: [PATCH 06/12] deps: mirror zlib zs.lib -> zlibstatic.lib so protobuf/curl link on clean build The current zlib installs its static library as zs.lib, but wild_zlib points protobuf and curl at ZLIB_LIBRARY=.../zlibstatic.lib, so a from-scratch deps build failed: protobuf protoc LNK1181 'cannot open input file zlibstatic.lib'. Warm runners hid it because an older build had left a real zlibstatic.lib in the (uncleaned) install dir. Copy zs.lib -> zlibstatic.lib right after each zlib build so the hardcoded path resolves regardless of runner state. --- premake5-deps.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/premake5-deps.lua b/premake5-deps.lua index 7bf946896..4da27fb60 100644 --- a/premake5-deps.lua +++ b/premake5-deps.lua @@ -535,11 +535,27 @@ if _OPTIONS["build-zlib"] or _OPTIONS["all-build"] then local zlib_common_defs = { "ZLIB_BUILD_EXAMPLES=OFF", } + -- The current zlib installs its static lib as 'zs.lib', but downstream deps + -- (protobuf, curl) are pointed at ZLIB_LIBRARY=.../zlibstatic.lib by wild_zlib. + -- Mirror zs.lib -> zlibstatic.lib after each build so those hardcoded paths + -- resolve on a clean build (a stale zlibstatic.lib from older builds used to + -- hide this on already-warm runners). + local function mirror_zlib_static(arch_iden) + local libdir = path.join(deps_dir, 'zlib', 'install' .. arch_iden, 'lib') + local zs = path.join(libdir, 'zs.lib') + local zstatic = path.join(libdir, 'zlibstatic.lib') + if os.isfile(zs) then + print('mirroring ' .. zs .. ' -> ' .. zstatic) + os.copyfile(zs, zstatic) + end + end if _OPTIONS["32-build"] then cmake_build('zlib', true, zlib_common_defs) + mirror_zlib_static('32') end if _OPTIONS["64-build"] then cmake_build('zlib', false, zlib_common_defs) + mirror_zlib_static('64') end end From 603d06e96d23409ae9a97f5dffa1ac5aa98da273 Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Wed, 17 Jun 2026 13:09:20 -0400 Subject: [PATCH 07/12] deps: skip abseil test helpers (MAX_PATH) + best-effort long paths A clean protobuf build failed with MSB3491: abseil's fetched test helper random_internal_distribution_test_util produces a build/.tlog path that exceeds Windows' 260-char MAX_PATH. Pass BUILD_TESTING=OFF so protobuf's bundled abseil skips test targets entirely (we don't need them), and add a best-effort LongPathsEnabled registry step (ignored if the runner lacks admin) as general protection for other deep dep paths. --- .github/workflows/emu-deps-win.yml | 8 ++++++++ premake5-deps.lua | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index c08ed292b..542f0b8a0 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -83,6 +83,14 @@ jobs: ref: "third-party/deps/common" path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common" + - name: "Enable long paths (best-effort)" + if: steps.local-deps-check.outputs.deps_exist != 'true' + shell: cmd + run: | + rem Deeply nested dep build paths (abseil etc.) can exceed Windows' 260-char + rem MAX_PATH. Best-effort enable long paths; ignore failure if not admin. + reg add "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f || ver > nul + - name: "Build deps" if: steps.local-deps-check.outputs.deps_exist != 'true' shell: cmd diff --git a/premake5-deps.lua b/premake5-deps.lua index 4da27fb60..63c351b7b 100644 --- a/premake5-deps.lua +++ b/premake5-deps.lua @@ -721,6 +721,12 @@ end if _OPTIONS["build-protobuf"] or _OPTIONS["all-build"] then local proto_common_defs = { "ABSL_PROPAGATE_CXX_STD=ON", + -- Skip test targets in protobuf AND its fetched abseil. Besides being + -- pointless to build, abseil's test helpers (e.g. + -- random_internal_distribution_test_util) produce extremely deep + -- build/.tlog paths that blow past Windows' 260-char MAX_PATH (MSB3491) + -- on a clean build. + "BUILD_TESTING=OFF", "protobuf_BUILD_PROTOBUF_BINARIES=ON", "protobuf_BUILD_PROTOC_BINARIES=ON", "protobuf_BUILD_LIBPROTOC=ON", From 33ec04c4861ef8f4cf5206b676deeb408dfed5e1 Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Wed, 17 Jun 2026 13:42:54 -0400 Subject: [PATCH 08/12] build: reconcile abseil link list with current abseil + harden release gate api_regular/api_experimental failed to link on a clean build: LNK1181 cannot open 'absl_bad_any_cast_impl.lib'. premake5.lua's hardcoded abseil lib list was stale vs the abseil protobuf now builds. Drop libs newer abseil no longer produces (absl_bad_any_cast_impl, absl_bad_optional_access, absl_bad_variant_access -> std::; absl_log_entry) and rename absl_random_internal_pool_urbg -> absl_random_internal_entropy_pool. Also harden the release path: wipe build/win/vs2022/release before building (the job checks out clean:false, so stale DLLs from a prior build were lingering and being shipped), and fix the verify gate (set "MISSING=1" + 'if defined' instead of a trailing-space-prone string compare that false-passed). --- .github/workflows/emu-build-all-win.yml | 11 ++++++++--- premake5.lua | 10 +++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 323cad3be..23d4134ec 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -92,6 +92,11 @@ jobs: @echo off setlocal enabledelayedexpansion + rem The build job checks out with clean:false, so stale DLLs from a prior + rem build linger. Wipe the release output dir first so a target that fails + rem to build this run cannot ship a leftover DLL (false success). + if exist "%GITHUB_WORKSPACE%\build\win\vs2022\release" rmdir /s /q "%GITHUB_WORKSPACE%\build\win\vs2022\release" + for %%P in (api_regular api_experimental steamclient_experimental_stub steamclient_experimental steamclient_experimental_loader steamclient_experimental_extra lib_game_overlay_renderer lib_steamnetworkingsockets tool_lobby_connect tool_generate_interfaces) do ( for %%A in (x64 Win32) do ( echo === Building %%P [%%A release] === @@ -115,16 +120,16 @@ jobs: run: | setlocal enabledelayedexpansion set "OUT=%GITHUB_WORKSPACE%\build\win\vs2022\release" - set MISSING=0 + set "MISSING=" for %%F in ( "regular\x64\steam_api64.dll" "regular\x86\steam_api.dll" "experimental\x64\steam_api64.dll" "experimental\x86\steam_api.dll" ) do ( - if exist "%OUT%\%%~F" ( echo OK: %%~F ) else ( echo MISSING: %%~F & set MISSING=1 ) + if exist "%OUT%\%%~F" ( echo OK: %%~F ) else ( echo MISSING: %%~F & set "MISSING=1" ) ) - if "!MISSING!"=="1" ( + if defined MISSING ( echo One or more shippable steam_api DLLs are missing - failing the build. exit /b 1 ) diff --git a/premake5.lua b/premake5.lua index e70176b58..00bff33bc 100644 --- a/premake5.lua +++ b/premake5.lua @@ -303,9 +303,8 @@ local deps_link = { -- add protobuf libs table_append(deps_link, { lib_prefix .. "protobuf-lite" .. static_postfix, - "absl_bad_any_cast_impl" .. static_postfix, - "absl_bad_optional_access" .. static_postfix, - "absl_bad_variant_access" .. static_postfix, + -- newer abseil dropped absl_bad_any_cast_impl / absl_bad_optional_access / + -- absl_bad_variant_access (any/optional/variant -> std::); do not link them. "absl_base" .. static_postfix, "absl_city" .. static_postfix, "absl_civil_time" .. static_postfix, @@ -344,7 +343,7 @@ table_append(deps_link, { "absl_int128" .. static_postfix, "absl_kernel_timeout_internal" .. static_postfix, "absl_leak_check" .. static_postfix, - "absl_log_entry" .. static_postfix, + -- absl_log_entry removed: not produced by current abseil. "absl_log_flags" .. static_postfix, "absl_log_globals" .. static_postfix, "absl_log_initialize" .. static_postfix, @@ -367,7 +366,8 @@ table_append(deps_link, { "absl_random_distributions" .. static_postfix, "absl_random_internal_distribution_test_util" .. static_postfix, "absl_random_internal_platform" .. static_postfix, - "absl_random_internal_pool_urbg" .. static_postfix, + -- newer abseil renamed pool_urbg -> entropy_pool. + "absl_random_internal_entropy_pool" .. static_postfix, "absl_random_internal_randen" .. static_postfix, "absl_random_internal_randen_hwaes" .. static_postfix, "absl_random_internal_randen_hwaes_impl" .. static_postfix, From b7a9725033dc361d0cc2318fd23174f3d2fa8f39 Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Wed, 17 Jun 2026 14:12:04 -0400 Subject: [PATCH 09/12] overlay: port to bundled ImGui 1.92 / current InGameOverlay; build clean api_experimental failed to compile against the pinned ingame_overlay's bundled ImGui 1.92 + newer InGameOverlay (upstream gbe_fork has the same skew; it only built on warm caches). Port the two affected call sites: - ImGui 1.92 removed ImFontConfig::GlyphExtraSpacing (ImVec2) -> use GlyphExtraAdvanceX (float, X only); drop the unsupported Y spacing. - InGameOverlay dropped RendererResource_t::SetAutoLoad(); autoload is hook-wide now and GetResourceId() triggers load. Remove the per-resource call. Also set the build job checkout to clean:true: api_regular/api_experimental were linking a stale base_gcmessages.pb.obj (a proto long removed from the repo but left in the clean:false workspace), which dragged in unresolved full-protobuf symbols. net.proto is LITE_RUNTIME and links protobuf-lite fine once the ghost object is gone. --- .github/workflows/emu-build-all-win.yml | 6 +++++- overlay_experimental/steam_overlay.cpp | 10 ++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 23d4134ec..4f89e5bb8 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -48,7 +48,11 @@ jobs: - name: "Checkout branch" uses: actions/checkout@v4 with: - clean: false + # clean:true so stale generated/build artifacts from prior runs (e.g. a + # removed base_gcmessages.pb.* or old steam_api DLLs) can't get compiled, + # linked, or shipped. Deps live in C:\gbe-deps (junctioned later), so a + # clean checkout doesn't force a deps rebuild. + clean: true - name: "Setup MSBuild" uses: microsoft/setup-msbuild@v2 diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index f62947f7b..2f0cf795c 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -244,9 +244,10 @@ void Steam_Overlay::create_fonts() font_cfg.OversampleH = 1; font_cfg.OversampleV = 1; font_cfg.SizePixels = font_size; - // non-latin characters look ugly and squeezed without this horizontal spacing - font_cfg.GlyphExtraSpacing.x = settings->overlay_appearance.font_glyph_extra_spacing_x; - font_cfg.GlyphExtraSpacing.y = settings->overlay_appearance.font_glyph_extra_spacing_y; + // non-latin characters look ugly and squeezed without this horizontal spacing. + // ImGui 1.92 removed ImFontConfig::GlyphExtraSpacing (ImVec2) in favour of + // GlyphExtraAdvanceX (float, X axis only); Y spacing is no longer supported. + font_cfg.GlyphExtraAdvanceX = settings->overlay_appearance.font_glyph_extra_spacing_x; for (const auto &ach : achievements) { font_builder.AddText(ach.title.c_str()); @@ -1250,7 +1251,8 @@ bool Steam_Overlay::try_load_ach_icon(Overlay_Achievement &ach, bool achieved, b auto image_info = settings->get_image(icon_handle); if (image_info) { int icon_size = static_cast(settings->overlay_appearance.icon_size); - icon_rsrc->SetAutoLoad(InGameOverlay::ResourceAutoLoad_t::OnUse); + // InGameOverlay dropped per-resource SetAutoLoad(); autoload is now hook-wide + // (default Batch) and GetResourceId() triggers the load on use. icon_rsrc->AttachResource((void*)image_info->data.c_str(), icon_size, icon_size); PRINT_DEBUG("'%s' (result=%i)", ach.name.c_str(), (int)icon_rsrc->GetResourceId() != 0); From 9a570288b680803a097c84ba2bb417dd7f8fced8 Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Wed, 17 Jun 2026 14:28:16 -0400 Subject: [PATCH 10/12] build: drop flaky dosstub/winsign, link ntdll, finish ImGui 1.92 overlay port Remaining clean-build failures after the deps/abseil/protobuf fixes: - api_regular x32 linked steam_api.dll fine, then the --dosstub post-build (file_dos_stub_x32.exe) crashed (exit -1) and removed it, cascading into a SignTool 'file not found'. Drop --dosstub and --winsign from the premake call: the emu DLLs don't need a replaced DOS stub or signature. (Keep --winrsrc.) - api_experimental link: ImGui 1.92 removed ImFontAtlas::Build() (atlas builds lazily now) -> drop the call; and the newer InGameOverlay (mini_detour/system) needs NtQueryInformationThread/Process -> link ntdll. - Verify gate checked the wrong 32-bit dir (x86); the build outputs to x32 (package_win.bat renames to x86 later) -> gate x32. --- .github/workflows/emu-build-all-win.yml | 11 ++++++++--- overlay_experimental/steam_overlay.cpp | 5 +++-- premake5.lua | 3 +++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 4f89e5bb8..41db5ebf5 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -87,7 +87,11 @@ jobs: shell: cmd working-directory: "${{ github.workspace }}" run: | - "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5.lua --genproto --emubuild=${{ github.sha }} --dosstub --winrsrc --winsign --os=windows vs2022 + rem No --dosstub/--winsign: that post-build tooling (file_dos_stub_x32.exe / + rem sign_helper.bat) crashes on the 32-bit DLL and removes the just-linked + rem output, and the emu DLLs don't need a replaced DOS stub or a code + rem signature to function. Keep --winrsrc (version resource) which is fine. + "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5.lua --genproto --emubuild=${{ github.sha }} --winrsrc --os=windows vs2022 - name: "Build all targets" shell: cmd @@ -125,11 +129,12 @@ jobs: setlocal enabledelayedexpansion set "OUT=%GITHUB_WORKSPACE%\build\win\vs2022\release" set "MISSING=" + rem 32-bit output dir is x32 (package_win.bat renames it to x86 later). for %%F in ( "regular\x64\steam_api64.dll" - "regular\x86\steam_api.dll" + "regular\x32\steam_api.dll" "experimental\x64\steam_api64.dll" - "experimental\x86\steam_api.dll" + "experimental\x32\steam_api.dll" ) do ( if exist "%OUT%\%%~F" ( echo OK: %%~F ) else ( echo MISSING: %%~F & set "MISSING=1" ) ) diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index 2f0cf795c..a506b2460 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -312,8 +312,9 @@ void Steam_Overlay::create_fonts() font_notif = font_default = font; stats.font = font; - bool res = fonts_atlas.Build(); - PRINT_DEBUG("created fonts atlas (result=%i)", (int)res); + // ImGui 1.92 removed ImFontAtlas::Build(); the atlas is built lazily by the + // renderer backend (InGameOverlay) on first use, so no explicit build call. + PRINT_DEBUG("created fonts atlas"); reset_LastError(); } diff --git a/premake5.lua b/premake5.lua index 00bff33bc..884689f4a 100644 --- a/premake5.lua +++ b/premake5.lua @@ -407,6 +407,9 @@ local common_link_win = { "Winmm" .. static_postfix, "Bcrypt" .. static_postfix, "Dbghelp" .. static_postfix, + -- ntdll: NtQueryInformationThread/Process used by the newer InGameOverlay + -- (mini_detour / system libs). + "ntdll" .. static_postfix, -- gamepad "Xinput" .. static_postfix, -- imgui / overlay From dbc40e37c8484906b39ea7257ddd5b0a3cbb7a4a Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Wed, 17 Jun 2026 15:09:45 -0400 Subject: [PATCH 11/12] ci: include SDL3-static.lib in deps cache completeness check dev's collective fixes added SDL3 as a linked dependency, but the warm deps cache (built before SDL was added) lacks SDL3-static.lib, so api_regular/experimental fail LNK1181. Add it to the cache-completeness check so a stale cache rebuilds. --- .github/workflows/emu-deps-win.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/emu-deps-win.yml b/.github/workflows/emu-deps-win.yml index 542f0b8a0..007d7f6d0 100644 --- a/.github/workflows/emu-deps-win.yml +++ b/.github/workflows/emu-deps-win.yml @@ -46,6 +46,7 @@ jobs: set DEPS_OK=1 if not exist "%LOCAL_DEPS_DIR%\vs2022\protobuf\install64" set DEPS_OK=0 if not exist "%LOCAL_DEPS_DIR%\vs2022\zlib\install64\lib\zlibstatic.lib" set DEPS_OK=0 + if not exist "%LOCAL_DEPS_DIR%\vs2022\sdl\install64\lib\SDL3-static.lib" set DEPS_OK=0 if "%DEPS_OK%"=="1" ( echo deps_exist=true>>%GITHUB_OUTPUT% echo Deps already exist and look complete at %LOCAL_DEPS_DIR% From bb663b0669cc9c16981dc3930d7f31a7da1beaad Mon Sep 17 00:00:00 2001 From: gabrielgad Date: Wed, 17 Jun 2026 15:54:06 -0400 Subject: [PATCH 12/12] ci: gate checks x86 dir for 32-bit DLLs (matches premake output) After merging dev's premake, 32-bit DLLs emit to release/{regular,experimental}/x86 (not x32). The verify gate still checked x32 and false-failed even though all four DLLs built. Point it at x86. --- .github/workflows/emu-build-all-win.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/emu-build-all-win.yml b/.github/workflows/emu-build-all-win.yml index 41db5ebf5..485349f3c 100644 --- a/.github/workflows/emu-build-all-win.yml +++ b/.github/workflows/emu-build-all-win.yml @@ -129,12 +129,12 @@ jobs: setlocal enabledelayedexpansion set "OUT=%GITHUB_WORKSPACE%\build\win\vs2022\release" set "MISSING=" - rem 32-bit output dir is x32 (package_win.bat renames it to x86 later). + rem premake emits 32-bit to the x86 dir (and 64-bit to x64). for %%F in ( "regular\x64\steam_api64.dll" - "regular\x32\steam_api.dll" + "regular\x86\steam_api.dll" "experimental\x64\steam_api64.dll" - "experimental\x32\steam_api.dll" + "experimental\x86\steam_api.dll" ) do ( if exist "%OUT%\%%~F" ( echo OK: %%~F ) else ( echo MISSING: %%~F & set "MISSING=1" ) )