#include "Internal.h" #include "modules/DFSteam.h" #include "Debug.h" #include "PluginManager.h" namespace DFHack { 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", "steam_api", // TODO: validate this on OSX "libsteam_api.so" // TODO: validate this on Linux }; 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_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) { char *steam_client_launch = getenv("SteamClientLaunch"); if (!steam_client_launch || strncmp(steam_client_launch, "1", 2) != 0) { DEBUG(dfsteam, out).print("not launched from Steam client; not initializing steam\n"); return false; } for (auto& lib_str : STEAM_LIBS) { if ((g_steam_handle = OpenPlugin(lib_str.c_str()))) break; } if (!g_steam_handle) { DEBUG(dfsteam, out).print("steam library not found; stubbing calls\n"); return false; } bind_all(out, g_steam_handle); if (!g_SteamAPI_Init || !g_SteamAPI_Shutdown || !g_SteamAPI_Init()) { DEBUG(dfsteam, out).print("steam detected but cannot be initialized\n"); return false; } DEBUG(dfsteam, out).print("steam library linked\n"); g_steam_initialized = true; return true; } void DFSteam::cleanup(color_ostream& out) { if (!g_steam_handle) return; if (g_SteamAPI_Shutdown) g_SteamAPI_Shutdown(); ClosePlugin(g_steam_handle); g_steam_handle = nullptr; bind_all(out, nullptr); g_steam_initialized = false; } #ifdef WIN32 #include #include #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; pwine_get_version = (wine_get_version*) GetProcAddress(hntdll, "wine_get_version"); return !!pwine_get_version; } static DWORD findProcess(LPCWSTR name) { PROCESSENTRY32W entry; entry.dwSize = sizeof(PROCESSENTRY32W); const auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); if (!Process32FirstW(snapshot, &entry)) { CloseHandle(snapshot); return -1; } do { std::wstring executableName(entry.szExeFile); if (executableName == name) { CloseHandle(snapshot); return entry.th32ProcessID; } } while (Process32NextW(snapshot, &entry)); CloseHandle(snapshot); return -1; } 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; } if (findProcess(L"launchdf.exe") != -1) { DEBUG(dfsteam, out).print("launchdf.exe already running\n"); return true; } STARTUPINFOW si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); static LPCWSTR procname = L"hack/launchdf.exe"; static const char * env = "\0"; // note that the environment 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(procname, NULL, NULL, NULL, FALSE, 0, (LPVOID)env, 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; } 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; } bool ret = launchDFHack(out); DEBUG(dfsteam, out).print("launching DFHack via Steam: %s\n", ret ? "successful" : "unsuccessful"); }