diff --git a/library/Core.cpp b/library/Core.cpp index 256493dce..431cd2c9f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1682,8 +1682,10 @@ bool Core::InitSimulationThread() fatal("cannot bind SDL libraries"); return false; } - if (DFSteam::init(con)) + if (DFSteam::init(con)) { std::cerr << "Found Steam.\n"; + DFSteam::launchSteamDFHackIfNecessary(con); + } std::cerr << "Initializing textures.\n"; Textures::init(con); // create mutex for syncing with interactive tasks @@ -2291,7 +2293,7 @@ int Core::Shutdown ( void ) allModules.clear(); Textures::cleanup(); DFSDL::cleanup(); - DFSteam::cleanup(); + DFSteam::cleanup(getConsole()); memset(&(s_mods), 0, sizeof(s_mods)); d.reset(); return -1; diff --git a/library/include/modules/DFSteam.h b/library/include/modules/DFSteam.h index 3144830da..e604294f8 100644 --- a/library/include/modules/DFSteam.h +++ b/library/include/modules/DFSteam.h @@ -24,9 +24,9 @@ bool init(DFHack::color_ostream& out); /** * Call this when DFHack is being unloaded. */ -void cleanup(); +void cleanup(DFHack::color_ostream& out); -DFHACK_EXPORT bool DFIsSteamRunningOnSteamDeck(); +DFHACK_EXPORT void launchSteamDFHackIfNecessary(DFHack::color_ostream& out); } } diff --git a/library/modules/DFSteam.cpp b/library/modules/DFSteam.cpp index b27cc1744..c61d996d1 100644 --- a/library/modules/DFSteam.cpp +++ b/library/modules/DFSteam.cpp @@ -12,6 +12,9 @@ DBG_DECLARE(core, dfsteam, DebugCategory::LINFO); using namespace DFHack; +static const int DFHACK_STEAM_APPID = 2346660; + +static bool g_steam_initialized = false; static DFLibrary* g_steam_handle = nullptr; static const std::vector STEAM_LIBS { "steam_api64.dll", @@ -21,8 +24,32 @@ static const std::vector STEAM_LIBS { bool (*g_SteamAPI_Init)() = nullptr; void (*g_SteamAPI_Shutdown)() = nullptr; +int (*g_SteamAPI_GetHSteamUser)() = nullptr; +bool (*g_SteamAPI_RestartAppIfNecessary)(uint32_t unOwnAppID) = nullptr; void* (*g_SteamInternal_FindOrCreateUserInterface)(int, const char*) = nullptr; -bool (*g_SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck)(void*) = nullptr; +bool (*g_SteamAPI_ISteamApps_BIsAppInstalled)(void *iSteamApps, uint32_t appID) = nullptr; + + +static void bind_all(color_ostream& out, DFLibrary* handle) { +#define bind(name) \ + if (!handle) { \ + g_##name = nullptr; \ + } else { \ + g_##name = (decltype(g_##name))LookupPlugin(handle, #name); \ + if (!g_##name) { \ + WARN(dfsteam, out).print("steam library function not found: " #name "\n"); \ + } \ + } + + bind(SteamAPI_Init); + bind(SteamAPI_Shutdown); + bind(SteamAPI_GetHSteamUser); + bind(SteamInternal_FindOrCreateUserInterface); + bind(SteamAPI_RestartAppIfNecessary); + bind(SteamInternal_FindOrCreateUserInterface); + bind(SteamAPI_ISteamApps_BIsAppInstalled); +#undef bind +} bool DFSteam::init(color_ostream& out) { for (auto& lib_str : STEAM_LIBS) { @@ -34,30 +61,19 @@ bool DFSteam::init(color_ostream& out) { return false; } -#define bind(handle, name) \ - g_##name = (decltype(g_##name))LookupPlugin(handle, #name); \ - if (!g_##name) { \ - WARN(dfsteam, out).print("steam library function not found: " #name "\n"); \ - } - - bind(g_steam_handle, SteamAPI_Init); - bind(g_steam_handle, SteamAPI_Shutdown); + bind_all(out, g_steam_handle); - // TODO: can we remove this initialization of the Steam API once we move to dfhooks? if (!g_SteamAPI_Init || !g_SteamAPI_Shutdown || !g_SteamAPI_Init()) { DEBUG(dfsteam, out).print("steam detected but cannot be initialized\n"); return false; } - bind(g_steam_handle, SteamInternal_FindOrCreateUserInterface); - bind(g_steam_handle, SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck); -#undef bind - DEBUG(dfsteam, out).print("steam library linked\n"); + g_steam_initialized = true; return true; } -void DFSteam::cleanup() { +void DFSteam::cleanup(color_ostream& out) { if (!g_steam_handle) return; @@ -66,16 +82,78 @@ void DFSteam::cleanup() { ClosePlugin(g_steam_handle); g_steam_handle = nullptr; + + bind_all(out, nullptr); + g_steam_initialized = false; } -bool DFSteam::DFIsSteamRunningOnSteamDeck() { - if (!g_SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck) +#ifdef WIN32 +#include +static bool is_running_on_wine() { + typedef const char* (CDECL wine_get_version)(void); + static wine_get_version* pwine_get_version; + HMODULE hntdll = GetModuleHandle("ntdll.dll"); + if(!hntdll) return false; - if (!g_SteamInternal_FindOrCreateUserInterface) + pwine_get_version = (wine_get_version*) GetProcAddress(hntdll, "wine_get_version"); + return !!pwine_get_version; +} + +static bool launchDFHack(color_ostream& out) { + if (is_running_on_wine()) { + DEBUG(dfsteam, out).print("not attempting to re-launch DFHack on wine\n"); return false; + } + + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); - void* SteamUtils = g_SteamInternal_FindOrCreateUserInterface(0, "SteamUtils010"); + // note that the enviornment must be explicitly zeroed out and not NULL, + // otherwise the launched process will inherit this process's environment, + // and the Steam API in the launchdf process will think it is in DF's context. + BOOL res = CreateProcessW(L"hack/launchdf.exe", + NULL, NULL, NULL, FALSE, 0, "\0", NULL, &si, &pi); + + return !!res; +} +#else +static bool launchDFHack(color_ostream& out) { + // TODO once we have a non-Windows build to work with + return false; +} +#endif + +void DFSteam::launchSteamDFHackIfNecessary(color_ostream& out) { + if (!g_steam_initialized || + !g_SteamAPI_GetHSteamUser || + !g_SteamInternal_FindOrCreateUserInterface || + !g_SteamAPI_ISteamApps_BIsAppInstalled) { + DEBUG(dfsteam, out).print("required Steam API calls are unavailable\n"); + return; + } + + if (strncmp(getenv("SteamClientLaunch"), "1", 2)) { + DEBUG(dfsteam, out).print("not launched from Steam client\n"); + return; + } + + void* iSteamApps = g_SteamInternal_FindOrCreateUserInterface(g_SteamAPI_GetHSteamUser(), "STEAMAPPS_INTERFACE_VERSION008"); + if (!iSteamApps) { + DEBUG(dfsteam, out).print("cannot obtain iSteamApps interface\n"); + return; + } + + bool isDFHackInstalled = g_SteamAPI_ISteamApps_BIsAppInstalled(iSteamApps, DFHACK_STEAM_APPID); + if (!isDFHackInstalled) { + DEBUG(dfsteam, out).print("player has not installed DFHack through Steam\n"); + return; + } - return g_SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck(SteamUtils); + bool ret = launchDFHack(out); + DEBUG(dfsteam, out).print("launching DFHack via Steam: %s\n", ret ? "successful" : "unsuccessful"); } diff --git a/package/windows/launchdf.cpp b/package/windows/launchdf.cpp index a84465f53..8e6536743 100644 --- a/package/windows/launchdf.cpp +++ b/package/windows/launchdf.cpp @@ -113,6 +113,24 @@ DWORD findDwarfFortressProcess() return -1; } +bool waitForDF() { + DWORD df_pid = findDwarfFortressProcess(); + + if (df_pid == -1) + return false; + + HANDLE hDF = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, df_pid); + + // in the future open an IPC connection so that we can proxy SteamAPI calls for the DFSteam module + + // this will eventuallyh need to become a loop with a WaitForMultipleObjects call + WaitForSingleObject(hDF, INFINITE); + + CloseHandle(hDF); + + return true; +} + int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) { // initialize steam context @@ -133,6 +151,9 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, exit(0); } + if (waitForDF()) + exit(0); + bool wine = is_running_on_wine(); if (wine) @@ -187,37 +208,25 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, exit(1); } - DWORD df_pid = findDwarfFortressProcess(); + if (waitForDF()) + exit(0); - if (df_pid == -1) + LPCWSTR err = launch_via_steam_windows(); + if (err != NULL) { - LPCWSTR err = launch_via_steam_windows(); - if (err != NULL) + MessageBoxW(NULL, err, NULL, 0); + exit(1); + } + + int counter = 0; + while (!waitForDF()) { + if (counter++ > 60) { - MessageBoxW(NULL, err, NULL, 0); + MessageBoxW(NULL, L"Dwarf Fortress took too long to launch, aborting", NULL, 0); exit(1); } - int counter = 0; - - do { - if (counter++ > 60) - { - MessageBoxW(NULL, L"Dwarf Fortress took too long to launch, aborting", NULL, 0); - exit(1); - } - Sleep(1000); - df_pid = findDwarfFortressProcess(); - } while (df_pid == -1); + Sleep(1000); } - HANDLE hDF = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, df_pid); - - // in the future open an IPC connection so that we can proxy SteamAPI calls for the DFSteam module - - // this will eventuallyh need to become a loop with a WaitForMultipleObjects call - WaitForSingleObject(hDF, INFINITE); - - CloseHandle(hDF); - exit(0); }