From a4de9d9f10a3186eec3833c7b5437c4d0920fddd Mon Sep 17 00:00:00 2001 From: Mathew Velasquez Date: Mon, 29 Feb 2016 00:34:27 -0500 Subject: [PATCH] integrated steamshim --- .gitignore | 7 +- binding-mri/steam-binding.cpp | 50 +- mkxp.pro | 28 +- src/graphics.cpp | 1 + src/main.cpp | 6 +- src/steam.cpp | 100 ++-- src/steam.h | 45 +- steamshim/steamshim_child.c | 469 +++++++++++++++++ steamshim/steamshim_child.h | 57 +++ steamshim_parent/Makefile | 42 ++ steamshim_parent/resources.rc | 2 + steamshim_parent/steamshim_parent.cpp | 696 ++++++++++++++++++++++++++ 12 files changed, 1356 insertions(+), 147 deletions(-) create mode 100644 steamshim/steamshim_child.c create mode 100644 steamshim/steamshim_child.h create mode 100644 steamshim_parent/Makefile create mode 100644 steamshim_parent/resources.rc create mode 100644 steamshim_parent/steamshim_parent.cpp diff --git a/.gitignore b/.gitignore index f228f00..357070e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,12 @@ *.pro.* *.bak *.xxd +*.exe -Makefile* +/Makefile* -Game -Game.exe +/oneshot +/steamshim_parent/steamshim /build diff --git a/binding-mri/steam-binding.cpp b/binding-mri/steam-binding.cpp index 367ee52..d00ec53 100644 --- a/binding-mri/steam-binding.cpp +++ b/binding-mri/steam-binding.cpp @@ -7,21 +7,6 @@ #include "binding-types.h" #include "debugwriter.h" -#ifdef STEAM -static ID achievementIds[Achievement::MAX]; - -static Achievement::ID toAchievementId(ID id) -{ - /* Crude but effective */ - for (int i = 0; i < Achievement::MAX; ++i) - { - if (achievementIds[i] == id) - return static_cast(i); - } - return Achievement::Invalid; -} -#endif - RB_METHOD(steamEnabled) { RB_UNUSED_PARAM; @@ -37,15 +22,11 @@ RB_METHOD(steamUnlock) { RB_UNUSED_PARAM; - ID rbid; - rb_get_args(argc, argv, "n", &rbid RB_ARG_END); + const char *name; + rb_get_args(argc, argv, "z", &name RB_ARG_END); #ifdef STEAM - Achievement::ID id = toAchievementId(rbid); - if (id == Achievement::Invalid) - rb_raise(rb_eNameError, "unlocked nonexistent achievement"); - else - shState->steam().unlock(id); + shState->steam().unlock(name); #endif return Qnil; } @@ -54,15 +35,11 @@ RB_METHOD(steamLock) { RB_UNUSED_PARAM; - ID rbid; - rb_get_args(argc, argv, "n", &rbid RB_ARG_END); + const char *name; + rb_get_args(argc, argv, "z", &name RB_ARG_END); #ifdef STEAM - Achievement::ID id = toAchievementId(rbid); - if (id == Achievement::Invalid) - rb_raise(rb_eNameError, "locked nonexistent achievement"); - else - shState->steam().lock(id); + shState->steam().lock(name); #endif return Qnil; } @@ -71,14 +48,11 @@ RB_METHOD(steamUnlocked) { RB_UNUSED_PARAM; - ID rbid; - rb_get_args(argc, argv, "n", &rbid RB_ARG_END); + const char *name; + rb_get_args(argc, argv, "z", &name RB_ARG_END); #ifdef STEAM - Achievement::ID id = toAchievementId(rbid); - if (id == Achievement::Invalid) - return Qfalse; - return shState->steam().isUnlocked(id) ? Qtrue : Qfalse; + return shState->steam().isUnlocked(name) ? Qtrue : Qfalse; #else return Qfalse; #endif @@ -100,10 +74,4 @@ void steamBindingInit() _rb_define_module_function(module, "unlock", steamUnlock); _rb_define_module_function(module, "lock", steamLock); _rb_define_module_function(module, "unlocked?", steamUnlocked); - -#ifdef STEAM - /* Cache achievement ids */ - for (int i = 0; i < Achievement::MAX; ++i) - achievementIds[i] = rb_intern(Achievement::names[i]); -#endif } diff --git a/mkxp.pro b/mkxp.pro index e52231c..85f2a1b 100644 --- a/mkxp.pro +++ b/mkxp.pro @@ -2,7 +2,7 @@ TEMPLATE = app QT = -TARGET = Game +TARGET = oneshot DEPENDPATH += src shader assets INCLUDEPATH += . src @@ -15,13 +15,6 @@ isEmpty(BINDING) { BINDING = MRI } -isEmpty(STEAMWORKS) { - STEAMWORKS = /home/mathew/Software/Steamworks -} -isEmpty(STEAMARCH) { - STEAMARCH = 64 -} - contains(BINDING, MRI) { contains(_HAVE_BINDING, YES) { error("Only one binding may be selected") @@ -58,16 +51,10 @@ unix { CONFIG -= app_bundle INCLUDEPATH += $$QMAKE_MAC_SDK_PATH/System/Library/Frameworks/OpenAL.framework/Versions/A/Headers LIBS += -framework OpenAL - STEAM { - LIBS += -L$$STEAMWORKS/redistributable_bin/osx32 -lsteam_api - } } !macx: { PKGCONFIG += openal zlib INCLUDEPATH += /usr/include/AL /usr/local/include/AL - STEAM { - LIBS += -L$$STEAMWORKS/redistributable_bin/linux$$STEAMARCH -lsteam_api - } } } @@ -75,14 +62,6 @@ win32 { QMAKE_CXXFLAGS += -std=gnu++11 QMAKE_LFLAGS += -std=gnu++11 - STEAM { - equals(STEAMARCH, 64) { - LIBS += -L$$STEAMWORKS/redistributable_bin/win64 -lsteam_api64 - } else { - LIBS += -L$$STEAMWORKS/redistributable_bin -lsteam_api - } - } - PKGCONFIG += sigc++-2.0 pixman-1 zlib \ sdl2 SDL2_image SDL2_ttf openal SDL_sound vorbisfile freetype2 LIBS += -lphysfs -lsecur32 @@ -116,7 +95,6 @@ isEmpty(BOOST_LIB_SUFFIX) { LIBS += -lboost_program_options$$BOOST_LIB_SUFFIX STEAM { - INCLUDEPATH += $$STEAMWORKS/public DEFINES += STEAM } @@ -226,8 +204,8 @@ SOURCES += \ binding-mri/steam-binding.cpp STEAM { - HEADERS += src/steam.h - SOURCES += src/steam.cpp + HEADERS += src/steam.h steamshim/steamshim_child.h + SOURCES += src/steam.cpp steamshim/steamshim_child.c } EMBED = \ diff --git a/src/graphics.cpp b/src/graphics.cpp index 651be5a..ef70267 100644 --- a/src/graphics.cpp +++ b/src/graphics.cpp @@ -595,6 +595,7 @@ struct GraphicsPrivate void swapGLBuffer() { fpsLimiter.delay(); + FBO::unbind(); SDL_GL_SwapWindow(threadData->window); ++frameCount; diff --git a/src/main.cpp b/src/main.cpp index 17d108d..465facd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,7 +43,7 @@ #include "icon.png.xxd" #ifdef STEAM -#include +#include "steamshim/steamshim_child.h" #else #include "gamecontrollerdb.txt.xxd" #endif @@ -188,7 +188,7 @@ int main(int argc, char *argv[]) } #ifdef STEAM - if (!SteamAPI_Init()) + if (!STEAMSHIM_init()) { showInitError("Could not initialize Steamworks API"); return 0; @@ -377,7 +377,7 @@ int main(int argc, char *argv[]) SDL_Quit(); #ifdef STEAM - SteamAPI_Shutdown(); + STEAMSHIM_deinit(); #endif return 0; diff --git a/src/steam.cpp b/src/steam.cpp index f69bafb..fcadcea 100644 --- a/src/steam.cpp +++ b/src/steam.cpp @@ -1,47 +1,81 @@ #include "steam.h" #include "debugwriter.h" -#include +#include +#include +#include "steamshim/steamshim_child.h" /* Achievements */ -const char *const Achievement::names[Achievement::MAX] = { +static const char *const achievementNames[] = { "SaveWorld", "SaveNiko", }; - -void Achievement::update() -{ - SteamUserStats()->GetAchievement(name, &unlocked); -} - -void Achievement::unlock() -{ - unlocked = true; - SteamUserStats()->SetAchievement(name); - SteamUserStats()->StoreStats(); -} - -void Achievement::lock() -{ - unlocked = false; - SteamUserStats()->ClearAchievement(name); - SteamUserStats()->StoreStats(); -} +#define NUM_ACHIEVEMENTS (sizeof(achievementNames) / sizeof(achievementNames[0])) /* SteamPrivate */ struct SteamPrivate { - Achievement achievements[Achievement::MAX]; + std::map achievements; - int appid; std::string userName; SteamPrivate() - : appid(SteamUtils()->GetAppID()), - userName(SteamFriends()->GetPersonaName()) { - for (int i = 0; i < Achievement::MAX; ++i) - achievements[i] = Achievement(Achievement::names[i]); + STEAMSHIM_getPersonaName(); + STEAMSHIM_getCurrentGameLanguage(); + for (size_t i = 0; i < NUM_ACHIEVEMENTS; ++i) + STEAMSHIM_getAchievement(achievementNames[i]); + while (!initialized()) + { + SDL_Delay(100); + update(); + } + } + + void setAchievement(const char *name, bool set) + { + achievements[name] = set; + STEAMSHIM_setAchievement(name, set); + STEAMSHIM_storeStats(); + } + + void updateAchievement(const char *name, bool isSet) + { + achievements[name] = isSet; + } + + bool isAchievementSet(const char *name) + { + return achievements[name]; + } + + void update() + { + const STEAMSHIM_Event *e; + while ((e = STEAMSHIM_pump()) != 0) + { + /* Skip erroneous events */ + if (!e->okay) + continue; + /* Handle events */ + switch (e->type) + { + case SHIMEVENT_GETACHIEVEMENT: + updateAchievement(e->name, e->ivalue); + break; + case SHIMEVENT_GETPERSONANAME: + userName = e->name; + break; + default: + break; + } + } + } + + bool initialized() + { + return !userName.empty() + && achievements.size() == NUM_ACHIEVEMENTS; } }; @@ -61,17 +95,17 @@ const std::string &Steam::userName() const return p->userName; } -void Steam::unlock(Achievement::ID id) +void Steam::unlock(const char *name) { - p->achievements[id].unlock(); + p->setAchievement(name, true); } -void Steam::lock(Achievement::ID id) +void Steam::lock(const char *name) { - p->achievements[id].lock(); + p->setAchievement(name, false); } -bool Steam::isUnlocked(Achievement::ID id) +bool Steam::isUnlocked(const char *name) { - return p->achievements[id].isUnlocked(); + return p->isAchievementSet(name); } diff --git a/src/steam.h b/src/steam.h index 940df0f..bd03143 100644 --- a/src/steam.h +++ b/src/steam.h @@ -5,51 +5,12 @@ struct SteamPrivate; -class Achievement -{ -public: - enum ID - { - Invalid = -1, - - SaveWorld = 0, - SaveNiko, - - MAX - }; - - static const char *const names[MAX]; - - void unlock(); - void lock(); - bool isUnlocked() { return unlocked; } - -private: - const char *name; - bool unlocked; - - friend struct SteamPrivate; - - Achievement() - : name(0), - unlocked(false) - {} - Achievement(const char *name) - : name(name), - unlocked(false) - { - update(); - } - - void update(); -}; - class Steam { public: - void unlock(Achievement::ID id); - void lock(Achievement::ID id); - bool isUnlocked(Achievement::ID id); + void unlock(const char *name); + void lock(const char *name); + bool isUnlocked(const char *name); const std::string &userName() const; diff --git a/steamshim/steamshim_child.c b/steamshim/steamshim_child.c new file mode 100644 index 0000000..4c089d7 --- /dev/null +++ b/steamshim/steamshim_child.c @@ -0,0 +1,469 @@ + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN 1 +#include +typedef HANDLE PipeType; +#define NULLPIPE NULL +typedef unsigned __int8 uint8; +typedef __int32 int32; +typedef unsigned __int64 uint64; +#else +#include +#include +#include +#include +#include +#include +#include +typedef uint8_t uint8; +typedef int32_t int32; +typedef uint64_t uint64; +typedef int PipeType; +#define NULLPIPE -1 +#endif +#include + +#include "steamshim_child.h" + +#ifdef STEAMSHIM_DEBUG +#define dbgpipe printf +#else +static inline void dbgpipe(const char *fmt, ...) {} +#endif + +static int writePipe(PipeType fd, const void *buf, const unsigned int _len); +static int readPipe(PipeType fd, void *buf, const unsigned int _len); +static void closePipe(PipeType fd); +static char *getEnvVar(const char *key, char *buf, const size_t buflen); +static int pipeReady(PipeType fd); + + +#ifdef _WIN32 + +static int pipeReady(PipeType fd) +{ + DWORD avail = 0; + return (PeekNamedPipe(fd, NULL, 0, NULL, &avail, NULL) && (avail > 0)); +} /* pipeReady */ + +static int 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 void closePipe(PipeType fd) +{ + CloseHandle(fd); +} /* closePipe */ + +static char *getEnvVar(const char *key, char *buf, const size_t _buflen) +{ + const DWORD buflen = (DWORD) _buflen; + const DWORD rc = GetEnvironmentVariableA(key, buf, buflen); + /* rc doesn't count null char, hence "<". */ + return ((rc > 0) && (rc < buflen)) ? NULL : buf; +} /* getEnvVar */ + +#else + +static int pipeReady(PipeType fd) +{ + int rc; + struct pollfd pfd = { fd, POLLIN | POLLERR | POLLHUP, 0 }; + while (((rc = poll(&pfd, 1, 0)) == -1) && (errno == EINTR)) { /*spin*/ } + return (rc == 1); +} /* pipeReady */ + +static int 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 void closePipe(PipeType fd) +{ + close(fd); +} /* closePipe */ + +static char *getEnvVar(const char *key, char *buf, const size_t buflen) +{ + const char *envr = getenv(key); + if (!envr || (strlen(envr) >= buflen)) + return NULL; + strcpy(buf, envr); + return buf; +} /* getEnvVar */ + +#endif + + +static PipeType GPipeRead = NULLPIPE; +static PipeType GPipeWrite = NULLPIPE; + +typedef enum ShimCmd +{ + SHIMCMD_BYE, + SHIMCMD_PUMP, + SHIMCMD_REQUESTSTATS, + SHIMCMD_STORESTATS, + SHIMCMD_SETACHIEVEMENT, + SHIMCMD_GETACHIEVEMENT, + SHIMCMD_RESETSTATS, + SHIMCMD_SETSTATI, + SHIMCMD_GETSTATI, + SHIMCMD_SETSTATF, + SHIMCMD_GETSTATF, + SHIMCMD_GETPERSONANAME, + SHIMCMD_GETCURRENTGAMELANGUAGE, +} ShimCmd; + +static int write1ByteCmd(const uint8 b1) +{ + const uint8 buf[] = { 1, b1 }; + return writePipe(GPipeWrite, buf, sizeof (buf)); +} /* write1ByteCmd */ + +static int write2ByteCmd(const uint8 b1, const uint8 b2) +{ + const uint8 buf[] = { 2, b1, b2 }; + return writePipe(GPipeWrite, buf, sizeof (buf)); +} /* write2ByteCmd */ + +static inline int writeBye(void) +{ + dbgpipe("Child sending SHIMCMD_BYE().\n"); + return write1ByteCmd(SHIMCMD_BYE); +} // writeBye + +static int initPipes(void) +{ + char buf[64]; + unsigned long long val; + + if (!getEnvVar("STEAMSHIM_READHANDLE", buf, sizeof (buf))) + return 0; + else if ((val = strtoull(buf, 0, 10)) == 0) + return 0; + else + GPipeRead = (PipeType) val; + + if (!getEnvVar("STEAMSHIM_WRITEHANDLE", buf, sizeof (buf))) + return 0; + else if ((val = strtoull(buf, 0, 10)) == 0) + return 0; + else + GPipeWrite = (PipeType) val; + + return ((GPipeRead != NULLPIPE) && (GPipeWrite != NULLPIPE)); +} /* initPipes */ + + +int STEAMSHIM_init(void) +{ + dbgpipe("Child init start.\n"); + if (!initPipes()) + { + dbgpipe("Child init failed.\n"); + return 0; + } /* if */ + +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + + dbgpipe("Child init success!\n"); + return 1; +} /* STEAMSHIM_init */ + +void STEAMSHIM_deinit(void) +{ + dbgpipe("Child deinit.\n"); + if (GPipeWrite != NULLPIPE) + { + writeBye(); + closePipe(GPipeWrite); + } /* if */ + + if (GPipeRead != NULLPIPE) + closePipe(GPipeRead); + + GPipeRead = GPipeWrite = NULLPIPE; + +#ifndef _WIN32 + signal(SIGPIPE, SIG_DFL); +#endif +} /* STEAMSHIM_deinit */ + +static inline int isAlive(void) +{ + return ((GPipeRead != NULLPIPE) && (GPipeWrite != NULLPIPE)); +} /* isAlive */ + +static inline int isDead(void) +{ + return !isAlive(); +} /* isDead */ + +int STEAMSHIM_alive(void) +{ + return isAlive(); +} /* STEAMSHIM_alive */ + +static const STEAMSHIM_Event *processEvent(const uint8 *buf, size_t buflen) +{ + static STEAMSHIM_Event event; + const STEAMSHIM_EventType type = (STEAMSHIM_EventType) *(buf++); + buflen--; + + memset(&event, '\0', sizeof (event)); + event.type = type; + event.okay = 1; + + #ifdef STEAMSHIM_DEBUG + if (0) {} + #define PRINTGOTEVENT(x) else if (type == x) printf("Child got " #x ".\n") + PRINTGOTEVENT(SHIMEVENT_BYE); + PRINTGOTEVENT(SHIMEVENT_STATSRECEIVED); + PRINTGOTEVENT(SHIMEVENT_STATSSTORED); + PRINTGOTEVENT(SHIMEVENT_SETACHIEVEMENT); + PRINTGOTEVENT(SHIMEVENT_GETACHIEVEMENT); + PRINTGOTEVENT(SHIMEVENT_RESETSTATS); + PRINTGOTEVENT(SHIMEVENT_SETSTATI); + PRINTGOTEVENT(SHIMEVENT_GETSTATI); + PRINTGOTEVENT(SHIMEVENT_SETSTATF); + PRINTGOTEVENT(SHIMEVENT_GETSTATF); + PRINTGOTEVENT(SHIMEVENT_GETPERSONANAME); + PRINTGOTEVENT(SHIMEVENT_GETCURRENTGAMELANGUAGE); + #undef PRINTGOTEVENT + else printf("Child got unknown shimevent %d.\n", (int) type); + #endif + + switch (type) + { + case SHIMEVENT_BYE: + break; + + case SHIMEVENT_STATSRECEIVED: + case SHIMEVENT_STATSSTORED: + if (!buflen) return NULL; + event.okay = *(buf++) ? 1 : 0; + break; + + case SHIMEVENT_SETACHIEVEMENT: + if (buflen < 3) return NULL; + event.ivalue = *(buf++) ? 1 : 0; + event.okay = *(buf++) ? 1 : 0; + strcpy(event.name, (const char *) buf); + break; + + case SHIMEVENT_GETACHIEVEMENT: + if (buflen < 10) return NULL; + event.ivalue = (int) *(buf++); + if (event.ivalue == 2) + event.ivalue = event.okay = 0; + event.epochsecs = (long long unsigned) *((uint64 *) buf); + buf += sizeof (uint64); + strcpy(event.name, (const char *) buf); + break; + + case SHIMEVENT_RESETSTATS: + if (buflen != 2) return NULL; + event.ivalue = *(buf++) ? 1 : 0; + event.okay = *(buf++) ? 1 : 0; + break; + + case SHIMEVENT_SETSTATI: + case SHIMEVENT_GETSTATI: + event.okay = *(buf++) ? 1 : 0; + event.ivalue = (int) *((int32 *) buf); + buf += sizeof (int32); + strcpy(event.name, (const char *) buf); + break; + + case SHIMEVENT_SETSTATF: + case SHIMEVENT_GETSTATF: + event.okay = *(buf++) ? 1 : 0; + event.fvalue = (int) *((float *) buf); + buf += sizeof (float); + strcpy(event.name, (const char *) buf); + break; + + case SHIMEVENT_GETPERSONANAME: + case SHIMEVENT_GETCURRENTGAMELANGUAGE: + strcpy(event.name, (const char *) buf); + break; + + default: /* uh oh */ + return NULL; + } /* switch */ + + return &event; +} /* processEvent */ + +const STEAMSHIM_Event *STEAMSHIM_pump(void) +{ + static uint8 buf[256]; + static int br = 0; + int evlen = (br > 0) ? ((int) buf[0]) : 0; + + if (isDead()) + return NULL; + + if (br <= evlen) /* we have an incomplete commmand. Try to read more. */ + { + if (pipeReady(GPipeRead)) + { + const int morebr = readPipe(GPipeRead, buf + br, sizeof (buf) - br); + if (morebr > 0) + br += morebr; + else /* uh oh */ + { + dbgpipe("Child readPipe failed! Shutting down.\n"); + STEAMSHIM_deinit(); /* kill it all. */ + } /* else */ + } /* if */ + } /* if */ + + if (evlen && (br > evlen)) + { + const STEAMSHIM_Event *retval = processEvent(buf+1, evlen); + br -= evlen + 1; + if (br > 0) + memmove(buf, buf+evlen+1, br); + return retval; + } /* if */ + + /* Run Steam event loop. */ + if (br == 0) + { + dbgpipe("Child sending SHIMCMD_PUMP().\n"); + write1ByteCmd(SHIMCMD_PUMP); + } /* if */ + + return NULL; +} /* STEAMSHIM_pump */ + +void STEAMSHIM_requestStats(void) +{ + if (isDead()) return; + dbgpipe("Child sending SHIMCMD_REQUESTSTATS().\n"); + write1ByteCmd(SHIMCMD_REQUESTSTATS); +} /* STEAMSHIM_requestStats */ + +void STEAMSHIM_storeStats(void) +{ + if (isDead()) return; + dbgpipe("Child sending SHIMCMD_STORESTATS().\n"); + write1ByteCmd(SHIMCMD_STORESTATS); +} /* STEAMSHIM_storeStats */ + +void STEAMSHIM_setAchievement(const char *name, const int enable) +{ + uint8 buf[256]; + uint8 *ptr = buf+1; + if (isDead()) return; + dbgpipe("Child sending SHIMCMD_SETACHIEVEMENT('%s', %senable).\n", name, enable ? "" : "!"); + *(ptr++) = (uint8) SHIMCMD_SETACHIEVEMENT; + *(ptr++) = enable ? 1 : 0; + strcpy((char *) ptr, name); + ptr += strlen(name) + 1; + buf[0] = (uint8) ((ptr-1) - buf); + writePipe(GPipeWrite, buf, buf[0] + 1); +} /* STEAMSHIM_setAchievement */ + +void STEAMSHIM_getAchievement(const char *name) +{ + uint8 buf[256]; + uint8 *ptr = buf+1; + if (isDead()) return; + dbgpipe("Child sending SHIMCMD_GETACHIEVEMENT('%s').\n", name); + *(ptr++) = (uint8) SHIMCMD_GETACHIEVEMENT; + strcpy((char *) ptr, name); + ptr += strlen(name) + 1; + buf[0] = (uint8) ((ptr-1) - buf); + writePipe(GPipeWrite, buf, buf[0] + 1); +} /* STEAMSHIM_getAchievement */ + +void STEAMSHIM_resetStats(const int bAlsoAchievements) +{ + if (isDead()) return; + dbgpipe("Child sending SHIMCMD_RESETSTATS(%salsoAchievements).\n", bAlsoAchievements ? "" : "!"); + write2ByteCmd(SHIMCMD_RESETSTATS, bAlsoAchievements ? 1 : 0); +} /* STEAMSHIM_resetStats */ + +static void writeStatThing(const ShimCmd cmd, const char *name, const void *val, const size_t vallen) +{ + uint8 buf[256]; + uint8 *ptr = buf+1; + if (isDead()) return; + *(ptr++) = (uint8) cmd; + if (vallen) + { + memcpy(ptr, val, vallen); + ptr += vallen; + } /* if */ + strcpy((char *) ptr, name); + ptr += strlen(name) + 1; + buf[0] = (uint8) ((ptr-1) - buf); + writePipe(GPipeWrite, buf, buf[0] + 1); +} /* writeStatThing */ + +void STEAMSHIM_setStatI(const char *name, const int _val) +{ + const int32 val = (int32) _val; + dbgpipe("Child sending SHIMCMD_SETSTATI('%s', val %d).\n", name, val); + writeStatThing(SHIMCMD_SETSTATI, name, &val, sizeof (val)); +} /* STEAMSHIM_setStatI */ + +void STEAMSHIM_getStatI(const char *name) +{ + dbgpipe("Child sending SHIMCMD_GETSTATI('%s').\n", name); + writeStatThing(SHIMCMD_GETSTATI, name, NULL, 0); +} /* STEAMSHIM_getStatI */ + +void STEAMSHIM_setStatF(const char *name, const float val) +{ + dbgpipe("Child sending SHIMCMD_SETSTATF('%s', val %f).\n", name, val); + writeStatThing(SHIMCMD_SETSTATF, name, &val, sizeof (val)); +} /* STEAMSHIM_setStatF */ + +void STEAMSHIM_getStatF(const char *name) +{ + dbgpipe("Child sending SHIMCMD_GETSTATF('%s').\n", name); + writeStatThing(SHIMCMD_GETSTATF, name, NULL, 0); +} /* STEAMSHIM_getStatF */ + +void STEAMSHIM_getPersonaName() +{ + if (isDead()) return; + dbgpipe("Child sending SHIMCMD_GETPERSONANAME().\n"); + write1ByteCmd(SHIMCMD_GETPERSONANAME); +} /* STEAMSHIM_getPersonaName */ + +void STEAMSHIM_getCurrentGameLanguage() +{ + if (isDead()) return; + dbgpipe("Child sending SHIMCMD_GETCURRENTGAMELANGUAGE().\n"); + write1ByteCmd(SHIMCMD_GETCURRENTGAMELANGUAGE); +} /* STEAMSHIM_getCurrentGameLanguage */ + +/* end of steamshim_child.c ... */ diff --git a/steamshim/steamshim_child.h b/steamshim/steamshim_child.h new file mode 100644 index 0000000..0e3d3a9 --- /dev/null +++ b/steamshim/steamshim_child.h @@ -0,0 +1,57 @@ +#ifndef _INCL_STEAMSHIM_CHILD_H_ +#define _INCL_STEAMSHIM_CHILD_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum STEAMSHIM_EventType +{ + SHIMEVENT_BYE, + SHIMEVENT_STATSRECEIVED, + SHIMEVENT_STATSSTORED, + SHIMEVENT_SETACHIEVEMENT, + SHIMEVENT_GETACHIEVEMENT, + SHIMEVENT_RESETSTATS, + SHIMEVENT_SETSTATI, + SHIMEVENT_GETSTATI, + SHIMEVENT_SETSTATF, + SHIMEVENT_GETSTATF, + SHIMEVENT_GETPERSONANAME, + SHIMEVENT_GETCURRENTGAMELANGUAGE, +} STEAMSHIM_EventType; + +/* not all of these fields make sense in a given event. */ +typedef struct STEAMSHIM_Event +{ + STEAMSHIM_EventType type; + int okay; + int ivalue; + float fvalue; + unsigned long long epochsecs; + char name[256]; +} STEAMSHIM_Event; + +int STEAMSHIM_init(void); /* non-zero on success, zero on failure. */ +void STEAMSHIM_deinit(void); +int STEAMSHIM_alive(void); +const STEAMSHIM_Event *STEAMSHIM_pump(void); +void STEAMSHIM_requestStats(void); +void STEAMSHIM_storeStats(void); +void STEAMSHIM_setAchievement(const char *name, const int enable); +void STEAMSHIM_getAchievement(const char *name); +void STEAMSHIM_resetStats(const int bAlsoAchievements); +void STEAMSHIM_setStatI(const char *name, const int _val); +void STEAMSHIM_getStatI(const char *name); +void STEAMSHIM_setStatF(const char *name, const float val); +void STEAMSHIM_getStatF(const char *name); +void STEAMSHIM_getPersonaName(); +void STEAMSHIM_getCurrentGameLanguage(); + +#ifdef __cplusplus +} +#endif + +#endif /* include-once blocker */ + +/* end of steamshim_child.h ... */ diff --git a/steamshim_parent/Makefile b/steamshim_parent/Makefile new file mode 100644 index 0000000..ca8201a --- /dev/null +++ b/steamshim_parent/Makefile @@ -0,0 +1,42 @@ +TARGET := steamshim +GAME_LAUNCH_NAME ?= oneshot + +CXX ?= clang++ +WINDRES ?= windres +HOST ?= linux64 +FLAGS := -I$(STEAMWORKS)/public -DGAME_LAUNCH_NAME=\"$(GAME_LAUNCH_NAME)\" + +ifeq ($(HOST),w32) + FLAGS += -L$(STEAMWORKS)/redistributable_bin -mwindows +else ifeq ($(HOST),linux32) + FLAGS += -L$(STEAMWORKS)/redistributable_bin/linux32 -m32 +else ifeq ($(HOST),linux64) + FLAGS += -L$(STEAMWORKS)/redistributable_bin/linux64 -m64 +else ifeq ($(HOST),osx) + FLAGS += -L$(STEAMWORKS)/redistributable_bin/osx32 +endif + +FLAGS += -lsteam_api + +ifeq ($(DEBUG),1) + FLAGS += -DSTEAMSHIM_DEBUG +endif + +SRC := steamshim_parent.cpp +RES := resources.rc +RESOBJ := resources.o + +ifeq ($(HOST),w32) +$(TARGET).exe: $(SRC) $(RESOBJ) + $(CXX) $^ -o $@ $(FLAGS) +$(RESOBJ): $(RES) + $(WINDRES) $< $@ +else +$(TARGET): $(SRC) + $(CXX) $^ -o $@ $(FLAGS) +endif + +clean: + rm -f $(TARGET) $(TARGET).exe $(RESOBJ) + +.PHONY: clean diff --git a/steamshim_parent/resources.rc b/steamshim_parent/resources.rc new file mode 100644 index 0000000..8ec361b --- /dev/null +++ b/steamshim_parent/resources.rc @@ -0,0 +1,2 @@ +#include +MAINICON ICON "../assets/icon.ico" diff --git a/steamshim_parent/steamshim_parent.cpp b/steamshim_parent/steamshim_parent.cpp new file mode 100644 index 0000000..4fe85d6 --- /dev/null +++ b/steamshim_parent/steamshim_parent.cpp @@ -0,0 +1,696 @@ +#ifndef GAME_LAUNCH_NAME +#define GAME_LAUNCH_NAME "oneshot" +#endif + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN 1 +#define UNICODE +#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" + +#ifdef STEAMSHIM_DEBUG +#define dbgpipe printf +#else +static inline void dbgpipe(const char *fmt, ...) {} +#endif + +/* 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 = 0; + 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); + GArgc = argc; + GArgv = argv; + return mainline(); +} // main + +#endif + + +// THE ACTUAL PROGRAM. + +class SteamBridge; + +static ISteamUserStats *GSteamStats = NULL; +static ISteamUtils *GSteamUtils = NULL; +static ISteamUser *GSteamUser = NULL; +static ISteamFriends *GSteamFriends = NULL; +static ISteamApps *GSteamApps = NULL; +static AppId_t GAppID = 0; +static uint64 GUserID = 0; +static SteamBridge *GSteamBridge = NULL; + +class SteamBridge +{ +public: + SteamBridge(PipeType _fd); + STEAM_CALLBACK(SteamBridge, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived); + STEAM_CALLBACK(SteamBridge, OnUserStatsStored, UserStatsStored_t, m_CallbackUserStatsStored); + +private: + PipeType fd; +}; + +typedef enum ShimCmd +{ + SHIMCMD_BYE, + SHIMCMD_PUMP, + SHIMCMD_REQUESTSTATS, + SHIMCMD_STORESTATS, + SHIMCMD_SETACHIEVEMENT, + SHIMCMD_GETACHIEVEMENT, + SHIMCMD_RESETSTATS, + SHIMCMD_SETSTATI, + SHIMCMD_GETSTATI, + SHIMCMD_SETSTATF, + SHIMCMD_GETSTATF, + SHIMCMD_GETPERSONANAME, + SHIMCMD_GETCURRENTGAMELANGUAGE, +} ShimCmd; + +typedef enum ShimEvent +{ + SHIMEVENT_BYE, + SHIMEVENT_STATSRECEIVED, + SHIMEVENT_STATSSTORED, + SHIMEVENT_SETACHIEVEMENT, + SHIMEVENT_GETACHIEVEMENT, + SHIMEVENT_RESETSTATS, + SHIMEVENT_SETSTATI, + SHIMEVENT_GETSTATI, + SHIMEVENT_SETSTATF, + SHIMEVENT_GETSTATF, + SHIMEVENT_GETPERSONANAME, + SHIMEVENT_GETCURRENTGAMELANGUAGE, +} ShimEvent; + +static bool write1ByteCmd(PipeType fd, const uint8 b1) +{ + const uint8 buf[] = { 1, b1 }; + return writePipe(fd, buf, sizeof (buf)); +} // write1ByteCmd + +static bool write2ByteCmd(PipeType fd, const uint8 b1, const uint8 b2) +{ + const uint8 buf[] = { 2, b1, b2 }; + return writePipe(fd, buf, sizeof (buf)); +} // write2ByteCmd + +static bool write3ByteCmd(PipeType fd, const uint8 b1, const uint8 b2, const uint8 b3) +{ + const uint8 buf[] = { 3, b1, b2, b3 }; + return writePipe(fd, buf, sizeof (buf)); +} // write3ByteCmd + +static bool writeString(PipeType fd, ShimEvent event, const char *str) +{ + uint8 buf[256]; + buf[0] = strlen(str) + 2; + buf[1] = (uint8) event; + strcpy((char *) buf + 2, str); + return writePipe(fd, buf, buf[0] + 1); +} // writeString + +static inline bool writeBye(PipeType fd) +{ + dbgpipe("Parent sending SHIMEVENT_BYE().\n"); + return write1ByteCmd(fd, SHIMEVENT_BYE); +} // writeBye + +static inline bool writeStatsReceived(PipeType fd, const bool okay) +{ + dbgpipe("Parent sending SHIMEVENT_STATSRECEIVED(%sokay).\n", okay ? "" : "!"); + return write2ByteCmd(fd, SHIMEVENT_STATSRECEIVED, okay ? 1 : 0); +} // writeStatsReceived + +static inline bool writeStatsStored(PipeType fd, const bool okay) +{ + dbgpipe("Parent sending SHIMEVENT_STATSSTORED(%sokay).\n", okay ? "" : "!"); + return write2ByteCmd(fd, SHIMEVENT_STATSSTORED, okay ? 1 : 0); +} // writeStatsStored + +static bool writeAchievementSet(PipeType fd, const char *name, const bool enable, const bool okay) +{ + uint8 buf[256]; + uint8 *ptr = buf+1; + dbgpipe("Parent sending SHIMEVENT_SETACHIEVEMENT('%s', %senable, %sokay).\n", name, enable ? "" : "!", okay ? "" : "!"); + *(ptr++) = (uint8) SHIMEVENT_SETACHIEVEMENT; + *(ptr++) = enable ? 1 : 0; + *(ptr++) = okay ? 1 : 0; + strcpy((char *) ptr, name); + ptr += strlen(name) + 1; + buf[0] = (uint8) ((ptr-1) - buf); + return writePipe(fd, buf, buf[0] + 1); +} // writeAchievementSet + +static bool writeAchievementGet(PipeType fd, const char *name, const int status, const uint64 time) +{ + uint8 buf[256]; + uint8 *ptr = buf+1; + dbgpipe("Parent sending SHIMEVENT_GETACHIEVEMENT('%s', status %d, time %llu).\n", name, status, (unsigned long long) time); + *(ptr++) = (uint8) SHIMEVENT_GETACHIEVEMENT; + *(ptr++) = (uint8) status; + memcpy(ptr, &time, sizeof (time)); + ptr += sizeof (time); + strcpy((char *) ptr, name); + ptr += strlen(name) + 1; + buf[0] = (uint8) ((ptr-1) - buf); + return writePipe(fd, buf, buf[0] + 1); +} // writeAchievementGet + +static inline bool writeResetStats(PipeType fd, const bool alsoAch, const bool okay) +{ + dbgpipe("Parent sending SHIMEVENT_RESETSTATS(%salsoAchievements, %sokay).\n", alsoAch ? "" : "!", okay ? "" : "!"); + return write3ByteCmd(fd, SHIMEVENT_RESETSTATS, alsoAch ? 1 : 0, okay ? 1 : 0); +} // writeResetStats + +static bool writeStatThing(PipeType fd, const ShimEvent ev, const char *name, const void *val, const size_t vallen, const bool okay) +{ + uint8 buf[256]; + uint8 *ptr = buf+1; + *(ptr++) = (uint8) ev; + *(ptr++) = okay ? 1 : 0; + memcpy(ptr, val, vallen); + ptr += vallen; + strcpy((char *) ptr, name); + ptr += strlen(name) + 1; + buf[0] = (uint8) ((ptr-1) - buf); + return writePipe(fd, buf, buf[0] + 1); +} // writeStatThing + +static inline bool writeSetStatI(PipeType fd, const char *name, const int32 val, const bool okay) +{ + dbgpipe("Parent sending SHIMEVENT_SETSTATI('%s', val %d, %sokay).\n", name, (int) val, okay ? "" : "!"); + return writeStatThing(fd, SHIMEVENT_SETSTATI, name, &val, sizeof (val), okay); +} // writeSetStatI + +static inline bool writeSetStatF(PipeType fd, const char *name, const float val, const bool okay) +{ + dbgpipe("Parent sending SHIMEVENT_SETSTATF('%s', val %f, %sokay).\n", name, val, okay ? "" : "!"); + return writeStatThing(fd, SHIMEVENT_SETSTATF, name, &val, sizeof (val), okay); +} // writeSetStatF + +static inline bool writeGetStatI(PipeType fd, const char *name, const int32 val, const bool okay) +{ + dbgpipe("Parent sending SHIMEVENT_GETSTATI('%s', val %d, %sokay).\n", name, (int) val, okay ? "" : "!"); + return writeStatThing(fd, SHIMEVENT_GETSTATI, name, &val, sizeof (val), okay); +} // writeGetStatI + +static inline bool writeGetStatF(PipeType fd, const char *name, const float val, const bool okay) +{ + dbgpipe("Parent sending SHIMEVENT_GETSTATF('%s', val %f, %sokay).\n", name, val, okay ? "" : "!"); + return writeStatThing(fd, SHIMEVENT_GETSTATF, name, &val, sizeof (val), okay); +} // writeGetStatF + + + +SteamBridge::SteamBridge(PipeType _fd) + : m_CallbackUserStatsReceived( this, &SteamBridge::OnUserStatsReceived ) + , m_CallbackUserStatsStored( this, &SteamBridge::OnUserStatsStored ) + , fd(_fd) +{ +} // SteamBridge::SteamBridge + +void SteamBridge::OnUserStatsReceived(UserStatsReceived_t *pCallback) +{ + if (GAppID != pCallback->m_nGameID) return; + if (GUserID != pCallback->m_steamIDUser.ConvertToUint64()) return; + writeStatsReceived(fd, pCallback->m_eResult == k_EResultOK); +} // SteamBridge::OnUserStatsReceived + +void SteamBridge::OnUserStatsStored(UserStatsStored_t *pCallback) +{ + if (GAppID != pCallback->m_nGameID) return; + writeStatsStored(fd, pCallback->m_eResult == k_EResultOK); +} // SteamBridge::OnUserStatsStored + + +static bool processCommand(const uint8 *buf, unsigned int buflen, PipeType fd) +{ + if (buflen == 0) + return true; + + const ShimCmd cmd = (ShimCmd) *(buf++); + buflen--; + + #if STEAMSHIM_DEBUG + if (false) {} + #define PRINTGOTCMD(x) else if (cmd == x) printf("Parent got " #x ".\n") + PRINTGOTCMD(SHIMCMD_BYE); + PRINTGOTCMD(SHIMCMD_PUMP); + PRINTGOTCMD(SHIMCMD_REQUESTSTATS); + PRINTGOTCMD(SHIMCMD_STORESTATS); + PRINTGOTCMD(SHIMCMD_SETACHIEVEMENT); + PRINTGOTCMD(SHIMCMD_GETACHIEVEMENT); + PRINTGOTCMD(SHIMCMD_RESETSTATS); + PRINTGOTCMD(SHIMCMD_SETSTATI); + PRINTGOTCMD(SHIMCMD_GETSTATI); + PRINTGOTCMD(SHIMCMD_SETSTATF); + PRINTGOTCMD(SHIMCMD_GETSTATF); + PRINTGOTCMD(SHIMCMD_GETPERSONANAME); + PRINTGOTCMD(SHIMCMD_GETCURRENTGAMELANGUAGE); + #undef PRINTGOTCMD + else printf("Parent got unknown shimcmd %d.\n", (int) cmd); + #endif + + switch (cmd) + { + case SHIMCMD_PUMP: + SteamAPI_RunCallbacks(); + break; + + case SHIMCMD_BYE: + writeBye(fd); + return false; + + case SHIMCMD_REQUESTSTATS: + if ((!GSteamStats) || (!GSteamStats->RequestCurrentStats())) + writeStatsReceived(fd, false); + // callback later. + break; + + case SHIMCMD_STORESTATS: + if ((!GSteamStats) || (!GSteamStats->StoreStats())) + writeStatsStored(fd, false); + // callback later. + break; + + case SHIMCMD_SETACHIEVEMENT: + if (buflen >= 2) + { + const bool enable = (*(buf++) != 0); + const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. + if (!GSteamStats) + writeAchievementSet(fd, name, enable, false); + else if (enable && !GSteamStats->SetAchievement(name)) + writeAchievementSet(fd, name, enable, false); + else if (!enable && !GSteamStats->ClearAchievement(name)) + writeAchievementSet(fd, name, enable, false); + else + writeAchievementSet(fd, name, enable, true); + } // if + break; + + case SHIMCMD_GETACHIEVEMENT: + if (buflen) + { + const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. + bool ach = false; + uint32 t = 0; + if ((GSteamStats) && (GSteamStats->GetAchievementAndUnlockTime(name, &ach, &t))) + writeAchievementGet(fd, name, ach ? 1 : 0, t); + else + writeAchievementGet(fd, name, 2, 0); + } // if + break; + + case SHIMCMD_RESETSTATS: + if (buflen) + { + const bool alsoAch = (*(buf++) != 0); + writeResetStats(fd, alsoAch, (GSteamStats) && (GSteamStats->ResetAllStats(alsoAch))); + } // if + break; + + case SHIMCMD_SETSTATI: + if (buflen >= 5) + { + const int32 val = *((int32 *) buf); + buf += sizeof (int32); + const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. + writeSetStatI(fd, name, val, (GSteamStats) && (GSteamStats->SetStat(name, val))); + } // if + break; + + case SHIMCMD_GETSTATI: + if (buflen) + { + const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. + int32 val = 0; + if ((GSteamStats) && (GSteamStats->GetStat(name, &val))) + writeGetStatI(fd, name, val, true); + else + writeGetStatI(fd, name, 0, false); + } // if + break; + + case SHIMCMD_SETSTATF: + if (buflen >= 5) + { + const float val = *((float *) buf); + buf += sizeof (float); + const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. + writeSetStatF(fd, name, val, (GSteamStats) && (GSteamStats->SetStat(name, val))); + } // if + break; + + case SHIMCMD_GETSTATF: + if (buflen) + { + const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. + float val = 0; + if ((GSteamStats) && (GSteamStats->GetStat(name, &val))) + writeGetStatF(fd, name, val, true); + else + writeGetStatF(fd, name, 0.0f, false); + } // if + break; + + case SHIMCMD_GETPERSONANAME: + dbgpipe("Parent sending SHIMEVENT_GETPERSONANAME.\n"); + writeString(fd, SHIMEVENT_GETPERSONANAME, GSteamFriends->GetPersonaName()); + break; + + case SHIMCMD_GETCURRENTGAMELANGUAGE: + dbgpipe("Parent sending SHIMEVENT_GETCURRENTGAMELANGUAGE.\n"); + writeString(fd, SHIMEVENT_GETCURRENTGAMELANGUAGE, GSteamApps->GetCurrentGameLanguage()); + break; + } // switch + + return true; // keep going. +} // processCommand + +static void processCommands(PipeType pipeParentRead, PipeType pipeParentWrite) +{ + bool quit = false; + uint8 buf[256]; + int br; + + // this read blocks. + while (!quit && ((br = readPipe(pipeParentRead, buf, sizeof (buf))) > 0)) + { + while (br > 0) + { + const int cmdlen = (int) buf[0]; + if ((br-1) >= cmdlen) + { + if (!processCommand(buf+1, cmdlen, pipeParentWrite)) + { + quit = true; + break; + } // if + + br -= cmdlen + 1; + if (br > 0) + memmove(buf, buf+cmdlen+1, br); + } // if + else // get more data. + { + const int morebr = readPipe(pipeParentRead, buf+br, sizeof (buf) - br); + if (morebr <= 0) + { + quit = true; // uhoh. + break; + } // if + br += morebr; + } // else + } // while + } // while +} // processCommands + +static bool setEnvironmentVars(PipeType pipeChildRead, PipeType pipeChildWrite) +{ + char buf[64]; + snprintf(buf, sizeof (buf), "%llu", (unsigned long long) pipeChildRead); + if (!setEnvVar("STEAMSHIM_READHANDLE", buf)) + return false; + + snprintf(buf, sizeof (buf), "%llu", (unsigned long long) pipeChildWrite); + if (!setEnvVar("STEAMSHIM_WRITEHANDLE", buf)) + return false; + + return true; +} // setEnvironmentVars + +static bool initSteamworks(PipeType fd) +{ + // this can fail for many reasons: + // - you forgot a steam_appid.txt in the current working directory. + // - you don't have Steam running + // - you don't own the game listed in steam_appid.txt + if (!SteamAPI_Init()) + return 0; + + GSteamStats = SteamUserStats(); + GSteamUtils = SteamUtils(); + GSteamUser = SteamUser(); + GSteamFriends = SteamFriends(); + GSteamApps = SteamApps(); + + GAppID = GSteamUtils ? GSteamUtils->GetAppID() : 0; + GUserID = GSteamUser ? GSteamUser->GetSteamID().ConvertToUint64() : 0; + GSteamBridge = new SteamBridge(fd); + + return 1; +} // initSteamworks + +static void deinitSteamworks(void) +{ + SteamAPI_Shutdown(); + delete GSteamBridge; + GSteamBridge = NULL; + GSteamStats = NULL; + GSteamUtils= NULL; + GSteamUser = NULL; +} // deinitSteamworks + +static int mainline(void) +{ + PipeType pipeParentRead = NULLPIPE; + PipeType pipeParentWrite = NULLPIPE; + PipeType pipeChildRead = NULLPIPE; + PipeType pipeChildWrite = NULLPIPE; + ProcessType childPid; + + dbgpipe("Parent starting mainline.\n"); + + if (!createPipes(&pipeParentRead, &pipeParentWrite, &pipeChildRead, &pipeChildWrite)) + fail("Failed to create application pipes"); + else if (!initSteamworks(pipeParentWrite)) + fail("Failed to initialize Steamworks"); + 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; + + dbgpipe("Parent in command processing loop.\n"); + + // Now, we block for instructions until the pipe fails (child closed it or + // terminated/crashed). + processCommands(pipeParentRead, pipeParentWrite); + + dbgpipe("Parent shutting down.\n"); + + // Close our ends of the pipes. + writeBye(pipeParentWrite); + closePipe(pipeParentRead); + closePipe(pipeParentWrite); + + deinitSteamworks(); + + dbgpipe("Parent waiting on child process.\n"); + + // Wait for the child to terminate, close the child process handles. + const int retval = closeProcess(&childPid); + + dbgpipe("Parent exiting mainline (child exit code %d).\n", retval); + + return retval; +} // mainline + +// end of steamshim_parent.cpp ...