From 36bc6a449748ca943235857d2fb52f6ca993eb73 Mon Sep 17 00:00:00 2001 From: SoftCoder Date: Sat, 23 Sep 2017 17:15:44 -0700 Subject: [PATCH] - initial work to integrate theam SDK with megaglest --- source/glest_game/CMakeLists.txt | 1 + source/glest_game/main/main.cpp | 96 ++- source/glest_game/menu/menu_state_root.cpp | 12 + source/glest_game/menu/menu_state_root.h | 1 + source/glest_game/steam/steam.cpp | 159 ++++ source/glest_game/steam/steam.h | 27 + source/glest_game/steamshim/steamshim_child.c | 463 +++++++++++ source/glest_game/steamshim/steamshim_child.h | 57 ++ .../include/platform/sdl/platform_main.h | 5 + source/steamshim_parent/Makefile | 49 ++ source/steamshim_parent/build.sh | 10 + source/steamshim_parent/steam_appid.txt | 1 + source/steamshim_parent/steamshim_parent.cpp | 787 ++++++++++++++++++ 13 files changed, 1656 insertions(+), 12 deletions(-) create mode 100644 source/glest_game/steam/steam.cpp create mode 100644 source/glest_game/steam/steam.h create mode 100644 source/glest_game/steamshim/steamshim_child.c create mode 100644 source/glest_game/steamshim/steamshim_child.h create mode 100644 source/steamshim_parent/Makefile create mode 100755 source/steamshim_parent/build.sh create mode 100644 source/steamshim_parent/steam_appid.txt create mode 100644 source/steamshim_parent/steamshim_parent.cpp diff --git a/source/glest_game/CMakeLists.txt b/source/glest_game/CMakeLists.txt index a2237ed0..7d743a7d 100644 --- a/source/glest_game/CMakeLists.txt +++ b/source/glest_game/CMakeLists.txt @@ -186,6 +186,7 @@ IF(BUILD_MEGAGLEST) menu network sound + steam steamshim type_instances types diff --git a/source/glest_game/main/main.cpp b/source/glest_game/main/main.cpp index e5adc6c1..d4cc4fd6 100644 --- a/source/glest_game/main/main.cpp +++ b/source/glest_game/main/main.cpp @@ -25,6 +25,9 @@ #include #include + +#include "steamshim_child.h" +#include "steam.h" #include "game.h" #include "main_menu.h" #include "program.h" @@ -142,6 +145,34 @@ class NavtiveLanguageNameListCacheGenerator : public SimpleTaskCallbackInterface } }; +/* +static void printEvent(const STEAMSHIM_Event *e) +{ + if (!e) return; + + printf("CHILD EVENT: "); + switch (e->type) + { + #define PRINTGOTEVENT(x) case SHIMEVENT_##x: printf("%s(", #x); break + PRINTGOTEVENT(BYE); + PRINTGOTEVENT(STATSRECEIVED); + PRINTGOTEVENT(STATSSTORED); + PRINTGOTEVENT(SETACHIEVEMENT); + PRINTGOTEVENT(GETACHIEVEMENT); + PRINTGOTEVENT(RESETSTATS); + PRINTGOTEVENT(SETSTATI); + PRINTGOTEVENT(GETSTATI); + PRINTGOTEVENT(SETSTATF); + PRINTGOTEVENT(GETSTATF); + #undef PRINTGOTEVENT + default: printf("UNKNOWN("); break; + } + + printf("%sokay, ival=%d, fval=%f, time=%llu, name='%s').\n", + e->okay ? "" : "!", e->ivalue, e->fvalue, e->epochsecs, e->name); +} +*/ + // ===================================================== // class ExceptionHandler // ===================================================== @@ -3295,19 +3326,27 @@ void ShowINISettings(int argc, char **argv,Config &config,Config &configKeys) { } } -void setupSteamSettings(){ +void setupSteamSettings(bool steamEnabled) { bool needToSaveConfig=false; Config &config = Config::getInstance(); - string steamPlayerName = safeCharPtrCopy(getenv("SteamAppUser"),100); - if( steamPlayerName=="") return;// not a steam launch - string currentPLayerName=config.getString("NetPlayerName",""); - if( currentPLayerName=="newbie" || currentPLayerName=="" ){ - config.setString("NetPlayerName",steamPlayerName); - needToSaveConfig=true; - } + config.setBool("SteamEnabled",steamEnabled,true); + if(steamEnabled) { + printf("*NOTE: Steam Integration Enabled.\n"); + //string steamPlayerName = safeCharPtrCopy(getenv("SteamAppUser"),100); + //if( steamPlayerName=="") return;// not a steam launch + Steam steam; + string steamPlayerName = steam.userName(); + string steamLang = steam.lang(); + printf("Steam Integration Enabled!\nSteam User Name is [%s] Language is [%s]\n", steamPlayerName.c_str(), steamLang.c_str()); - if( needToSaveConfig == true ){ - config.save(); + string currentPLayerName = config.getString("NetPlayerName",""); + if( currentPLayerName == "newbie" || currentPLayerName == "" ) { + config.setString("NetPlayerName",steamPlayerName); + needToSaveConfig=true; + } + if( needToSaveConfig == true ) { + config.save(); + } } } @@ -4201,6 +4240,18 @@ int glestMain(int argc, char** argv) { return 2; } +// if(hasCommandArgument(argc, argv,GAME_ARGS[GAME_ARG_STEAM]) == true) { +// STEAMSHIM_requestStats(); +// while (STEAMSHIM_alive()) { +// const STEAMSHIM_Event *e = STEAMSHIM_pump(); +// printEvent(e); +// if (e && e->type == SHIMEVENT_STATSRECEIVED) { +// break; +// } +// usleep(100 * 1000); +// } // while +// } + if( hasCommandArgument(argc, argv,string(GAME_ARGS[GAME_ARG_MASTERSERVER_MODE])) == true) { //isMasterServerModeEnabled = true; //Window::setMasterserverMode(isMasterServerModeEnabled); @@ -4539,7 +4590,7 @@ int glestMain(int argc, char** argv) { Config &config = Config::getInstance(); setupGameItemPaths(argc, argv, &config); - setupSteamSettings(); + setupSteamSettings(hasCommandArgument(argc, argv,GAME_ARGS[GAME_ARG_STEAM])); if(config.getString("PlayerId","") == "") { char uuid_str[38]; @@ -4985,6 +5036,12 @@ int glestMain(int argc, char** argv) { } else { + if(hasCommandArgument(argc, argv,GAME_ARGS[GAME_ARG_STEAM]) == true) { + Steam steam; + string steamUser = steam.userName(); + string steamLang = steam.lang(); + printf("Steam Integration Enabled!\nSteam User Name is [%s] Language is [%s]\n", steamUser.c_str(), steamLang.c_str()); + } #ifdef _WIN32 int localeBufferSize = GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_SISO639LANGNAME, NULL, 0); wchar_t *sysLocale = new wchar_t[localeBufferSize]; @@ -5986,6 +6043,14 @@ __try { int result = 0; IRCThread::setGlobalCacheContainerName(GameConstants::ircClientCacheLookupKey); + + if(hasCommandArgument(argc, argv,GAME_ARGS[GAME_ARG_STEAM]) == true) { + if (!STEAMSHIM_init()) { + printf("Child init failed, terminating.\n"); + return 42; + } + } + result = glestMain(argc, argv); if(SystemFlags::VERBOSE_MODE_ENABLED) printf("In [%s::%s Line: %d]\n",__FILE__,__FUNCTION__,__LINE__); @@ -6003,10 +6068,17 @@ __try { SDL_Quit(); } + if(hasCommandArgument(argc, argv,GAME_ARGS[GAME_ARG_STEAM]) == true) { + STEAMSHIM_deinit(); + } + if(SystemFlags::VERBOSE_MODE_ENABLED) printf("In [%s::%s Line: %d]\n",__FILE__,__FUNCTION__,__LINE__); return result; #ifdef WIN32_STACK_TRACE -} __except(stackdumper(0, GetExceptionInformation(),true), EXCEPTION_CONTINUE_SEARCH) { return 0; } +} +__except(stackdumper(0, GetExceptionInformation(),true), EXCEPTION_CONTINUE_SEARCH) { + return 0; +} #endif } diff --git a/source/glest_game/menu/menu_state_root.cpp b/source/glest_game/menu/menu_state_root.cpp index 84376cb5..51c58c05 100644 --- a/source/glest_game/menu/menu_state_root.cpp +++ b/source/glest_game/menu/menu_state_root.cpp @@ -25,6 +25,7 @@ #include "network_message.h" #include "socket.h" #include "auto_test.h" +#include "steam.h" #include #include "leak_dumper.h" @@ -46,6 +47,7 @@ MenuStateRoot::MenuStateRoot(Program *program, MainMenu *mainMenu) : buttonAbout("MainMenu","buttonAbout"), buttonExit("MainMenu","buttonExit"), labelVersion("MainMenu","labelVersion"), + labelGreeting("MainMenu","labelGreeting"), mainMessageBox("MainMenu","mainMessageBox"), errorMessageBox("MainMenu","errorMessageBox"), @@ -76,6 +78,15 @@ MenuStateRoot::MenuStateRoot(Program *program, MainMenu *mainMenu) : labelVersion.setText(glestVersionString + " [" + getGITRevisionString() + "]"); } + labelGreeting.init(labelVersion.getX(), labelVersion.getY()-16); + labelGreeting.setText(""); + Config &config = Config::getInstance(); + if(config.getBool("SteamEnabled")) { + Steam steam; + string steamPlayerName = steam.userName(); + labelGreeting.setText("Welcome Steam Player: " + steamPlayerName); + } + yPos-=55; //buttonNewGame.registerGraphicComponent(containerName,"buttonNewGame"); buttonNewGame.init(buttonXPosition, yPos, buttonWidth); @@ -581,6 +592,7 @@ void MenuStateRoot::render() { renderer.renderButton(&buttonAbout); renderer.renderButton(&buttonExit); renderer.renderLabel(&labelVersion); + renderer.renderLabel(&labelGreeting); renderer.renderConsole(&console); diff --git a/source/glest_game/menu/menu_state_root.h b/source/glest_game/menu/menu_state_root.h index 71811fa4..29e5f2f0 100644 --- a/source/glest_game/menu/menu_state_root.h +++ b/source/glest_game/menu/menu_state_root.h @@ -36,6 +36,7 @@ private: GraphicButton buttonAbout; GraphicButton buttonExit; GraphicLabel labelVersion; + GraphicLabel labelGreeting; GraphicMessageBox mainMessageBox; GraphicMessageBox errorMessageBox; diff --git a/source/glest_game/steam/steam.cpp b/source/glest_game/steam/steam.cpp new file mode 100644 index 00000000..8b78a45b --- /dev/null +++ b/source/glest_game/steam/steam.cpp @@ -0,0 +1,159 @@ +#include "steam.h" + +#include +#include +#include "steamshim_child.h" + +/* Achievements */ +static const char *const achievementNames[] = { +// "X", +}; +//#define NUM_ACHIEVEMENTS (sizeof(achievementNames) / sizeof(achievementNames[0])) + +/* Language map */ +static inline std::map gen_langToCode() +{ + std::map map; + map["brazilian"] = "pt_BR"; + map["bulgarian"] = "bg"; + map["czech"] = "cz"; + map["danish"] = "da"; + map["dutch"] = "nl"; + map["english"] = "en"; + map["finnish"] = "fi"; + map["french"] = "fr"; + map["german"] = "de"; + map["greek"] = "el"; + map["hungarian"] = "hu"; + map["italian"] = "it"; + map["japanese"] = "ja"; + map["koreana"] = "ko"; + map["korean"] = "ko"; + map["norwegian"] = "no"; + map["polish"] = "pl"; + map["portuguese"] = "pt"; + map["romanian"] = "ro"; + map["russian"] = "ru"; + map["schinese"] = "zh_CN"; + map["spanish"] = "es"; + map["swedish"] = "sv"; + map["tchinese"] = "zh_TW"; + map["thai"] = "th"; + map["turkish"] = "tr"; + map["ukrainian"] = "uk"; + return map; +} +static const std::map langToCode = gen_langToCode(); + +static std::string steamToIsoLang(const char *steamLang) +{ + //printf("Steam language [%s]\n",steamLang); + std::map::const_iterator it = langToCode.find(steamLang); + if (it != langToCode.end()) + return it->second; + return "en"; +} + +/* SteamPrivate */ +struct SteamPrivate +{ + //std::map achievements; + + std::string userName; + std::string lang; + + SteamPrivate() + { + 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) + { + /* Handle events */ + switch (e->type) + { +// case SHIMEVENT_GETACHIEVEMENT: +// updateAchievement(e->name, e->ivalue); +// break; + case SHIMEVENT_GETPERSONANAME: + userName = e->name; + break; + case SHIMEVENT_GETCURRENTGAMELANGUAGE: + lang = steamToIsoLang(e->name); + default: + break; + } + } + } + + bool initialized() + { + return !userName.empty() + && !lang.empty(); + //&& achievements.size() == NUM_ACHIEVEMENTS; + } +}; + +/* Steam */ +Steam::Steam() + : p(new SteamPrivate()) +{ +} + +Steam::~Steam() +{ + delete p; +} + +const std::string &Steam::userName() const +{ + return p->userName; +} + +const std::string &Steam::lang() const +{ + return p->lang; +} + +//void Steam::unlock(const char *name) +//{ +// p->setAchievement(name, true); +//} +// +//void Steam::lock(const char *name) +//{ +// p->setAchievement(name, false); +//} +// +//bool Steam::isUnlocked(const char *name) +//{ +// return p->isAchievementSet(name); +//} diff --git a/source/glest_game/steam/steam.h b/source/glest_game/steam/steam.h new file mode 100644 index 00000000..5d39f12b --- /dev/null +++ b/source/glest_game/steam/steam.h @@ -0,0 +1,27 @@ +#ifndef STEAM_H +#define STEAM_H + +#include + +struct SteamPrivate; + +class Steam +{ +public: +// void unlock(const char *name); +// void lock(const char *name); +// bool isUnlocked(const char *name); + + const std::string &userName() const; + const std::string &lang() const; + + Steam(); + ~Steam(); + +private: + //friend struct SharedStatePrivate; + + SteamPrivate *p; +}; + +#endif // STEAM_H diff --git a/source/glest_game/steamshim/steamshim_child.c b/source/glest_game/steamshim/steamshim_child.c new file mode 100644 index 00000000..be456a6e --- /dev/null +++ b/source/glest_game/steamshim/steamshim_child.c @@ -0,0 +1,463 @@ + +#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 rc = GetEnvironmentVariableA(key, buf, buflen); + /* rc doesn't count null char, hence "<". */ + return ((rc > 0) && (rc < buflen)) ? buf : NULL; +} /* 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]; + + if (!getEnvVar("STEAMSHIM_READHANDLE", buf, sizeof (buf))) + return 0; + GPipeRead = (PipeType) strtoull(buf, 0, 10); + + if (!getEnvVar("STEAMSHIM_WRITEHANDLE", buf, sizeof (buf))) + return 0; + GPipeWrite = (PipeType) strtoull(buf, 0, 10); + + return ((GPipeRead != NULLPIPE) && (GPipeWrite != NULLPIPE)); +} /* initPipes */ + + +int STEAMSHIM_init(void) +{ + printf("Initializing Steam Shim...\n"); + dbgpipe("Child init start.\n"); + if (!initPipes()) + { + dbgpipe("Child init failed.\n"); + return 0; + } /* if */ + +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + + printf("Init of Steam Shim successful!\n"); + 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/source/glest_game/steamshim/steamshim_child.h b/source/glest_game/steamshim/steamshim_child.h new file mode 100644 index 00000000..0e3d3a9d --- /dev/null +++ b/source/glest_game/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/source/shared_lib/include/platform/sdl/platform_main.h b/source/shared_lib/include/platform/sdl/platform_main.h index c469c672..ca920efc 100644 --- a/source/shared_lib/include/platform/sdl/platform_main.h +++ b/source/shared_lib/include/platform/sdl/platform_main.h @@ -98,6 +98,7 @@ const char *GAME_ARGS[] = { "--enable-new-protocol", "--create-data-archives", + "--steam", "--verbose" @@ -182,6 +183,7 @@ enum GAME_ARG_TYPE { GAME_ARG_ENABLE_NEW_PROTOCOL, GAME_ARG_CREATE_DATA_ARCHIVES, + GAME_ARG_STEAM, GAME_ARG_VERBOSE_MODE, @@ -462,6 +464,9 @@ void printParameterHelp(const char *argv0, bool foundInvalidArgs) { printf("\n\n \tWhere y = include_main to include main (non mod) data."); printf("\n\n \texample: %s %s=all",extractFileFromDirectoryPath(argv0).c_str(),GAME_ARGS[GAME_ARG_CREATE_DATA_ARCHIVES]); + printf("\n\n%s=x=y ",GAME_ARGS[GAME_ARG_STEAM]); + printf("\n\n \tRun with Steam Client Integration."); + printf("\n\n%s \t\tDisplays verbose information in the console.",GAME_ARGS[GAME_ARG_VERBOSE_MODE]); printf("\n\n"); } diff --git a/source/steamshim_parent/Makefile b/source/steamshim_parent/Makefile new file mode 100644 index 00000000..fa268242 --- /dev/null +++ b/source/steamshim_parent/Makefile @@ -0,0 +1,49 @@ +TARGET := megaglest_shim +GAME_LAUNCH_NAME ?= megaglest + +ifndef STEAMWORKS +# STEAMWORKS ?= /home/softcoder/Code/steamworks_sdk/sdk +$(error STEAMWORKS is not set) +endif + +CXX ?= clang++ +WINDRES ?= windres +HOST ?= linux64 +FLAGS := -I$(STEAMWORKS)/public -DGAME_LAUNCH_NAME=\"$(GAME_LAUNCH_NAME)\" -Wall + +ifeq ($(HOST),w32) + FLAGS += -L$(STEAMWORKS)/redistributable_bin +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 +else ifeq ($(HOST),w32) + FLAGS += -mwindows +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/source/steamshim_parent/build.sh b/source/steamshim_parent/build.sh new file mode 100755 index 00000000..0954a45c --- /dev/null +++ b/source/steamshim_parent/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Use this script to build MegaGlest using cmake +# ---------------------------------------------------------------------------- +# Written by Mark Vejvoda +# Copyright (c) 2011-2013 Mark Vejvoda under GNU GPL v3.0+ + +# ---------------------------------------------------------------------------- +rm steamshim +#make STEAMWORKS?=/home/softcoder/Code/steamworks_sdk/sdk +make $@ diff --git a/source/steamshim_parent/steam_appid.txt b/source/steamshim_parent/steam_appid.txt new file mode 100644 index 00000000..3c203d6b --- /dev/null +++ b/source/steamshim_parent/steam_appid.txt @@ -0,0 +1 @@ +578870 diff --git a/source/steamshim_parent/steamshim_parent.cpp b/source/steamshim_parent/steamshim_parent.cpp new file mode 100644 index 00000000..5330e4aa --- /dev/null +++ b/source/steamshim_parent/steamshim_parent.cpp @@ -0,0 +1,787 @@ +#ifndef GAME_LAUNCH_NAME +#define GAME_LAUNCH_NAME "megaglest_shim" +#endif + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN 1 +#define UNICODE +#include +typedef PROCESS_INFORMATION ProcessType; +typedef HANDLE PipeType; +#define NULLPIPE NULL +#define LLUFMT "%I64u" +#else +#include +#include +#include +#include +#include +#include +#include +typedef pid_t ProcessType; +typedef int PipeType; +#define NULLPIPE -1 +#define LLUFMT "%llu" +#endif +#include + +#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 LPWSTR genCommandLine() +{ + // Construct a command line with the appropriate filename + LPWSTR cmdline = GetCommandLineW(); + + // Find the index of the first argument after 0 + int iFirstArg = -1; + bool quote = false; + bool whitespace = false; + for (int i = 0; cmdline[i]; ++i) + { + if (cmdline[i] == '"' && (i == 0 || cmdline[i-1] != '\\')) + { + quote = !quote; + whitespace = false; + } + else if (!quote && (cmdline[i] == ' ' || cmdline[i] == '\t')) + { + whitespace = true; + } + else + { + if (whitespace) + { + iFirstArg = i; + break; + } + whitespace = false; + } + } + + // If it doesn't exist, that must mean there are no arguments, + // so just return GAME_LAUNCH_NAME + if (iFirstArg == -1) + return _wcsdup(TEXT("\".\\" GAME_LAUNCH_NAME ".exe\"")); + + // Create the new string + // (`".\.exe" ` == +9 + LPWSTR newcmdline = (LPWSTR)malloc(sizeof(TEXT(GAME_LAUNCH_NAME)) + + sizeof(WCHAR) * (wcslen(cmdline) - iFirstArg + 9)); + wsprintf(newcmdline, TEXT("\".\\" GAME_LAUNCH_NAME ".exe\" %s"), cmdline + iFirstArg); + return newcmdline; +} + +static bool launchChild(ProcessType *pid) +{ + STARTUPINFOW si; + memset(&si, 0, sizeof(si)); + return CreateProcessW(TEXT(".\\" GAME_LAUNCH_NAME ".exe"), + genCommandLine(), NULL, NULL, TRUE, 0, NULL, + NULL, &si, pid); +} // 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; + fcntl(fds[0], F_SETFL, 0); + fcntl(fds[1], F_SETFL, 0); + *pPipeParentRead = fds[0]; + *pPipeChildWrite = fds[1]; + + if (pipe(fds) == -1) + { + close(*pPipeParentRead); + close(*pPipeChildWrite); + return 0; + } // if + + fcntl(fds[0], F_SETFL, 0); + fcntl(fds[1], F_SETFL, 0); + *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) +{ + dbgpipe("***IN launchChild START.\n"); + + *pid = fork(); + + dbgpipe("***IN launchChild pid = %d.\n",*pid); + 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); + + dbgpipe("***IN launchChild [%s].\n",GArgv[0]); + + int ret = execvp(GArgv[0], GArgv); + + dbgpipe("***IN launchChild [%s] got ret = %d errorno = %d.\n",GArgv[0],ret,errno); + // 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) +{ + printf("#1 =============> Megaglest Parent starting mainline.\n"); + + 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 " LLUFMT ").\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) +{ + dbgpipe("***IN OnUserStatsReceived GAppID = %d, GUserID = %lld\n",GAppID,GUserID); + + 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) +{ + dbgpipe("***IN OnUserStatsStored GAppID = %d, GUserID = %lld\n",GAppID,GUserID); + + 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) +{ + dbgpipe("***IN processCommand GAppID = %d, GUserID = %lld\n",GAppID,GUserID); + + 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; + + dbgpipe("***START processCommands.\n"); + + // this read blocks. + while (!quit && ((br = readPipe(pipeParentRead, buf, sizeof (buf))) > 0)) + { + dbgpipe("***IN processCommands readPipe found something.\n"); + while (br > 0) + { + const int cmdlen = (int) buf[0]; + if ((br-1) >= cmdlen) + { + dbgpipe("***IN processCommands processCommand.\n"); + 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. + { + dbgpipe("***IN processCommands readPipe#2....\n"); + const int morebr = readPipe(pipeParentRead, buf+br, sizeof (buf) - br); + if (morebr <= 0) + { + quit = true; // uhoh. + break; + } // if + br += morebr; + } // else + } // while + } // while + dbgpipe("***END processCommands.\n"); +} // processCommands + +static bool setEnvironmentVars(PipeType pipeChildRead, PipeType pipeChildWrite) +{ + char buf[64]; + snprintf(buf, sizeof (buf), LLUFMT, (unsigned long long) pipeChildRead); + if (!setEnvVar("STEAMSHIM_READHANDLE", buf)) + return false; + + snprintf(buf, sizeof (buf), LLUFMT, (unsigned long long) pipeChildWrite); + if (!setEnvVar("STEAMSHIM_WRITEHANDLE", buf)) + return false; + + return true; +} // setEnvironmentVars + +static bool initSteamworks(PipeType fd) +{ + dbgpipe("***IN initSteamworks Start\n"); + + // 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; + + dbgpipe("***IN initSteamworks GAppID = %d, GUserID = %lld\n",GAppID,GUserID); + + GSteamBridge = new SteamBridge(fd); + + return 1; +} // initSteamworks + +static void deinitSteamworks(void) +{ + dbgpipe("***IN deinitSteamworks GAppID = %d, GUserID = %lld\n",GAppID,GUserID); + + 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; + + printf("#2 =============> Megaglest Parent starting mainline.\n"); + +#ifdef STEAMSHIM_DEBUG + printf("=============> Megaglest Parent starting mainline.\n"); + +#endif + 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"); + + dbgpipe("Parent ending mainline\n"); + + // 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 ...