From d94bef2e4f88cc9f8329269bde4a979916d57a52 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 24 Jul 2013 21:35:08 -0400 Subject: [PATCH] First pile of work. --- steamshim.cpp | 316 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 steamshim.cpp diff --git a/steamshim.cpp b/steamshim.cpp new file mode 100644 index 0000000..0ae6f8d --- /dev/null +++ b/steamshim.cpp @@ -0,0 +1,316 @@ +#define GAME_LAUNCH_NAME "steelstorm" +#ifndef GAME_LAUNCH_NAME +#error Please define your game exe name. +#endif + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN 1 +#include +typedef PROCESS_INFORMATION ProcessType; +typedef HANDLE PipeType; +#define NULLPIPE NULL +#else +#include +#include +#include +#include +#include +#include +#include +typedef pid_t ProcessType; +typedef int PipeType; +#define NULLPIPE -1 +#endif + +#include "steam/steam_api.h" + +/* platform-specific mainline calls this. */ +static int mainline(void); + +/* Windows and Unix implementations of this stuff below. */ +static void fail(const char *err); +static bool writePipe(PipeType fd, const void *buf, const unsigned int _len); +static int readPipe(PipeType fd, void *buf, const unsigned int _len); +static bool createPipes(PipeType *pPipeParentRead, PipeType *pPipeParentWrite, + PipeType *pPipeChildRead, PipeType *pPipeChildWrite); +static void closePipe(PipeType fd); +static bool setEnvVar(const char *key, const char *val); +static bool launchChild(ProcessType *pid); +static int closeProcess(ProcessType *pid); + +#ifdef _WIN32 +static void fail(const char *err) +{ + MessageBoxA(NULL, err, "ERROR", MB_ICONERROR | MB_OK); + ExitProcess(1); +} // fail + +static bool writePipe(PipeType fd, const void *buf, const unsigned int _len) +{ + const DWORD len = (DWORD) _len; + DWORD bw = 0; + return ((WriteFile(fd, buf, len, &bw, NULL) != 0) && (bw == len)); +} // writePipe + +static int readPipe(PipeType fd, void *buf, const unsigned int _len) +{ + const DWORD len = (DWORD) _len; + DWORD br = 0; + return ReadFile(fd, buf, len, &br, NULL) ? (int) br : -1; +} // readPipe + +static bool createPipes(PipeType *pPipeParentRead, PipeType *pPipeParentWrite, + PipeType *pPipeChildRead, PipeType *pPipeChildWrite) +{ + SECURITY_ATTRIBUTES pipeAttr; + + pipeAttr.nLength = sizeof (pipeAttr); + pipeAttr.lpSecurityDescriptor = NULL; + pipeAttr.bInheritHandle = TRUE; + if (!CreatePipe(pPipeParentRead, pPipeChildWrite, &pipeAttr, 0)) + return 0; + + pipeAttr.nLength = sizeof (pipeAttr); + pipeAttr.lpSecurityDescriptor = NULL; + pipeAttr.bInheritHandle = TRUE; + if (!CreatePipe(pPipeChildRead, pPipeParentWrite, &pipeAttr, 0)) + { + CloseHandle(*pPipeParentRead); + CloseHandle(*pPipeChildWrite); + return 0; + } // if + + return 1; +} // createPipes + +static void closePipe(PipeType fd) +{ + CloseHandle(fd); +} // closePipe + +static bool setEnvVar(const char *key, const char *val) +{ + return (SetEnvironmentVariableA(key, val) != 0); +} // setEnvVar + +static bool launchChild(ProcessType *pid); +{ + return (CreateProcessW(TEXT(".\\") TEXT(GAME_LAUNCH_NAME) TEXT(".exe"), + GetCommandLineW(), NULL, NULL, TRUE, 0, NULL, + NULL, NULL, pid) != 0); +} // launchChild + +static int closeProcess(ProcessType *pid) +{ + CloseHandle(pid->hProcess); + CloseHandle(pid->hThread); + return 0; +} // closeProcess + +int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) +{ + mainline(); + ExitProcess(0); + return 0; // just in case. +} // WinMain + + +#else // everyone else that isn't Windows. + +static void fail(const char *err) +{ + // !!! FIXME: zenity or something. + fprintf(stderr, "%s\n", err); + _exit(1); +} // fail + +static bool writePipe(PipeType fd, const void *buf, const unsigned int _len) +{ + const ssize_t len = (ssize_t) _len; + ssize_t bw; + while (((bw = write(fd, buf, len)) == -1) && (errno == EINTR)) { /*spin*/ } + return (bw == len); +} // writePipe + +static int readPipe(PipeType fd, void *buf, const unsigned int _len) +{ + const ssize_t len = (ssize_t) _len; + ssize_t br; + while (((br = read(fd, buf, len)) == -1) && (errno == EINTR)) { /*spin*/ } + return (int) br; +} // readPipe + +static bool createPipes(PipeType *pPipeParentRead, PipeType *pPipeParentWrite, + PipeType *pPipeChildRead, PipeType *pPipeChildWrite) +{ + int fds[2]; + if (pipe(fds) == -1) + return 0; + *pPipeParentRead = fds[0]; + *pPipeChildWrite = fds[1]; + + if (pipe(fds) == -1) + { + close(*pPipeParentRead); + close(*pPipeChildWrite); + return 0; + } // if + + *pPipeChildRead = fds[0]; + *pPipeParentWrite = fds[1]; + + return 1; +} // createPipes + +static void closePipe(PipeType fd) +{ + close(fd); +} // closePipe + +static bool setEnvVar(const char *key, const char *val) +{ + return (setenv(key, val, 1) != -1); +} // setEnvVar + +static int GArgc = 0; +static char **GArgv = NULL; + +static bool launchChild(ProcessType *pid) +{ + *pid = fork(); + if (*pid == -1) // failed + return false; + else if (*pid != 0) // we're the parent + return true; // we'll let the pipe fail if this didn't work. + + // we're the child. + GArgv[0] = strdup("./" GAME_LAUNCH_NAME); + execvp(GArgv[0], GArgv); + // still here? It failed! Terminate, closing child's ends of the pipes. + _exit(1); +} // launchChild + +static int closeProcess(ProcessType *pid) +{ + int rc; + while ((waitpid(*pid, &rc, 0) == -1) && (errno == EINTR)) { /*spin*/ } + if (!WIFEXITED(rc)) + return 1; // oh well. + return WEXITSTATUS(rc); +} // closeProcess + +int main(int argc, char **argv) +{ + signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + GArgc = argc; + GArgv = argv; + return mainline(); +} // main + +#endif + + +// THE ACTUAL PROGRAM. + +static ISteamUserStats *GStats = NULL; +static bool GStatsAvailable = false; + +typedef enum ShimCmds +{ + SHIMCMD_SETACHIEVEMENT, + SHIMCMD_GETACHIEVEMENT, + SHIMCMD_SETSTAT, + SHIMCMD_GETSTAT, +} ShimCmds; + +static void processCommand(char *buf, const unsigned int buflen, PipeType fd) +{ + int cmd; + + if (buflen == 0) + return; + + cmd = (int) *(buf++); + switch (cmd) + { + case SHIMCMD_SETACHIEVEMENT: break; + case SHIMCMD_GETACHIEVEMENT: break; + case SHIMCMD_SETSTAT: break; + case SHIMCMD_GETSTAT: break; + } // switch +} // processCommand + +static void processCommands(PipeType pipeParentRead, PipeType pipeParentWrite) +{ + char buf[128]; + ssize_t br; + + // read() blocks. + while ((br = readPipe(pipeParentRead, buf, sizeof (buf))) > 0) + processCommand(buf, (unsigned int) br, pipeParentWrite); +} // processCommands + +static bool setEnvironmentVars(PipeType pipeChildRead, PipeType pipeChildWrite) +{ + char buf[64]; + snprintf(buf, sizeof (buf), "%llu", (long long unsigned) pipeChildRead); + if (!setEnvVar("STEAMSHIM_READHANDLE", buf)) + return false; + + snprintf(buf, sizeof (buf), "%llu", (long long unsigned) pipeChildWrite); + if (!setEnvVar("STEAMSHIM_WRITEHANDLE", buf)) + return false; + + return true; +} // setEnvironmentVars + +static bool initSteamworks(void) +{ + if (!SteamAPI_Init()) + return 0; + + GStats = SteamUserStats(); + if (GStats) + GStats->RequestCurrentStats(); + + return 1; +} // initSteamworks + +static int mainline(void) +{ + PipeType pipeParentRead = NULLPIPE; + PipeType pipeParentWrite = NULLPIPE; + PipeType pipeChildRead = NULLPIPE; + PipeType pipeChildWrite = NULLPIPE; + ProcessType childPid; + + if (!initSteamworks()) + fail("Failed to initialize Steamworks"); + else if (!createPipes(&pipeParentRead, &pipeParentWrite, &pipeChildRead, &pipeChildWrite)) + fail("Failed to create application pipes"); + else if (!setEnvironmentVars(pipeChildRead, pipeChildWrite)) + fail("Failed to set environment variables"); + else if (!launchChild(&childPid)) + fail("Failed to launch application"); + + // Close the ends of the pipes that the child will use; we don't need them. + closePipe(pipeChildRead); + closePipe(pipeChildWrite); + pipeChildRead = pipeChildWrite = NULLPIPE; + + // Now, we block for instructions until the pipe fails (child closed it or + // terminated/crashed). + processCommands(pipeParentRead, pipeParentWrite); + + // Close our ends of the pipes. + closePipe(pipeParentRead); + closePipe(pipeParentWrite); + + // Wait for the child to terminate, close the child process handles. + return closeProcess(&childPid); +} // mainline + +// end of steamshim.cpp ... +