launch DFHack through steam if DF is run from steam

develop
Myk Taylor 2023-05-19 16:51:39 -07:00
parent 51fa088605
commit a5a6b70a51
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
4 changed files with 138 additions and 49 deletions

@ -1682,8 +1682,10 @@ bool Core::InitSimulationThread()
fatal("cannot bind SDL libraries"); fatal("cannot bind SDL libraries");
return false; return false;
} }
if (DFSteam::init(con)) if (DFSteam::init(con)) {
std::cerr << "Found Steam.\n"; std::cerr << "Found Steam.\n";
DFSteam::launchSteamDFHackIfNecessary(con);
}
std::cerr << "Initializing textures.\n"; std::cerr << "Initializing textures.\n";
Textures::init(con); Textures::init(con);
// create mutex for syncing with interactive tasks // create mutex for syncing with interactive tasks
@ -2291,7 +2293,7 @@ int Core::Shutdown ( void )
allModules.clear(); allModules.clear();
Textures::cleanup(); Textures::cleanup();
DFSDL::cleanup(); DFSDL::cleanup();
DFSteam::cleanup(); DFSteam::cleanup(getConsole());
memset(&(s_mods), 0, sizeof(s_mods)); memset(&(s_mods), 0, sizeof(s_mods));
d.reset(); d.reset();
return -1; return -1;

@ -24,9 +24,9 @@ bool init(DFHack::color_ostream& out);
/** /**
* Call this when DFHack is being unloaded. * 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);
} }
} }

@ -12,6 +12,9 @@ DBG_DECLARE(core, dfsteam, DebugCategory::LINFO);
using namespace DFHack; using namespace DFHack;
static const int DFHACK_STEAM_APPID = 2346660;
static bool g_steam_initialized = false;
static DFLibrary* g_steam_handle = nullptr; static DFLibrary* g_steam_handle = nullptr;
static const std::vector<std::string> STEAM_LIBS { static const std::vector<std::string> STEAM_LIBS {
"steam_api64.dll", "steam_api64.dll",
@ -21,8 +24,32 @@ static const std::vector<std::string> STEAM_LIBS {
bool (*g_SteamAPI_Init)() = nullptr; bool (*g_SteamAPI_Init)() = nullptr;
void (*g_SteamAPI_Shutdown)() = 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; 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) { bool DFSteam::init(color_ostream& out) {
for (auto& lib_str : STEAM_LIBS) { for (auto& lib_str : STEAM_LIBS) {
@ -34,30 +61,19 @@ bool DFSteam::init(color_ostream& out) {
return false; return false;
} }
#define bind(handle, name) \ bind_all(out, g_steam_handle);
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);
// 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()) { if (!g_SteamAPI_Init || !g_SteamAPI_Shutdown || !g_SteamAPI_Init()) {
DEBUG(dfsteam, out).print("steam detected but cannot be initialized\n"); DEBUG(dfsteam, out).print("steam detected but cannot be initialized\n");
return false; 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"); DEBUG(dfsteam, out).print("steam library linked\n");
g_steam_initialized = true;
return true; return true;
} }
void DFSteam::cleanup() { void DFSteam::cleanup(color_ostream& out) {
if (!g_steam_handle) if (!g_steam_handle)
return; return;
@ -66,16 +82,78 @@ void DFSteam::cleanup() {
ClosePlugin(g_steam_handle); ClosePlugin(g_steam_handle);
g_steam_handle = nullptr; g_steam_handle = nullptr;
bind_all(out, nullptr);
g_steam_initialized = false;
} }
bool DFSteam::DFIsSteamRunningOnSteamDeck() { #ifdef WIN32
if (!g_SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck) #include <windows.h>
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; 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; 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");
} }

@ -113,6 +113,24 @@ DWORD findDwarfFortressProcess()
return -1; 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) { int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) {
// initialize steam context // initialize steam context
@ -133,6 +151,9 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
exit(0); exit(0);
} }
if (waitForDF())
exit(0);
bool wine = is_running_on_wine(); bool wine = is_running_on_wine();
if (wine) if (wine)
@ -187,37 +208,25 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
exit(1); 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(); MessageBoxW(NULL, err, NULL, 0);
if (err != NULL) 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); exit(1);
} }
int counter = 0; Sleep(1000);
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);
} }
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); exit(0);
} }