From 4382bc57e7e2b1adaad421b54f251bb78c4bde5f Mon Sep 17 00:00:00 2001 From: Jonas Kulla Date: Sun, 20 Sep 2015 19:54:57 +0200 Subject: [PATCH] Add steamshim integration --- binding-mri/binding-mri.cpp | 28 +++ mkxp.pro | 3 + steamshim/steamshim_child.c | 444 ++++++++++++++++++++++++++++++++++++ steamshim/steamshim_child.h | 54 +++++ 4 files changed, 529 insertions(+) create mode 100644 steamshim/steamshim_child.c create mode 100644 steamshim/steamshim_child.h diff --git a/binding-mri/binding-mri.cpp b/binding-mri/binding-mri.cpp index 5cb518e..4b60567 100644 --- a/binding-mri/binding-mri.cpp +++ b/binding-mri/binding-mri.cpp @@ -31,6 +31,8 @@ #include "audio.h" #include "boost-hash.h" +#include "steamshim/steamshim_child.h" + #include #include @@ -313,6 +315,22 @@ RB_METHOD(_kernelCaller) return trace; } +RB_METHOD(_steamAchievementUnlock) +{ + RB_UNUSED_PARAM; + + if (!STEAMSHIM_alive()) + return Qnil; + + const char *achv; + rb_get_args(argc, argv, "z", &achv RB_ARG_END); + + STEAMSHIM_setAchievement(achv, true); + STEAMSHIM_storeStats(); + + return Qnil; +} + static VALUE newStringUTF8(const char *string, long length) { return rb_enc_str_new(string, length, rb_utf8_encoding()); @@ -588,6 +606,12 @@ static void mriBindingExecute() mriBindingInit(); + + STEAMSHIM_init(); + _rb_define_module_function(rb_mKernel, "_steam_achievement_unlock", + _steamAchievementUnlock); + + std::string &customScript = conf.customScript; if (!customScript.empty()) runCustomScript(customScript); @@ -600,6 +624,10 @@ static void mriBindingExecute() ruby_cleanup(0); + + STEAMSHIM_deinit(); + + shState->rtData().rqTermAck.set(); } diff --git a/mkxp.pro b/mkxp.pro index 558727a..0a4aa56 100644 --- a/mkxp.pro +++ b/mkxp.pro @@ -182,6 +182,9 @@ SOURCES += \ src/midisource.cpp \ src/fluid-fun.cpp +HEADERS += steamshim/steamshim_child.h +SOURCES += steamshim/steamshim_child.c + EMBED = \ shader/common.h \ shader/transSimple.frag \ diff --git a/steamshim/steamshim_child.c b/steamshim/steamshim_child.c new file mode 100644 index 0000000..0667cc7 --- /dev/null +++ b/steamshim/steamshim_child.c @@ -0,0 +1,444 @@ + +#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 +#include +typedef uint8_t uint8; +typedef int32_t int32; +typedef uint64_t uint64; +typedef int PipeType; +#define NULLPIPE -1 +#endif + +#include "steamshim_child.h" + +#define DEBUGPIPE 1 +#if DEBUGPIPE +#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, val, 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; + +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 (sscanf(buf, "%llu", &val) != 1) + return 0; + else + GPipeRead = (PipeType) val; + + if (!getEnvVar("STEAMSHIM_WRITEHANDLE", buf, sizeof (buf))) + return 0; + else if (sscanf(buf, "%llu", &val) != 1) + 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 */ + + signal(SIGPIPE, SIG_IGN); + + 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; + + signal(SIGPIPE, SIG_DFL); +} /* 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; + + #if DEBUGPIPE + 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); + #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; + + 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 */ + +/* end of steamshim_child.c ... */ + diff --git a/steamshim/steamshim_child.h b/steamshim/steamshim_child.h new file mode 100644 index 0000000..de356c8 --- /dev/null +++ b/steamshim/steamshim_child.h @@ -0,0 +1,54 @@ +#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, +} 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); + +#ifdef __cplusplus +} +#endif + +#endif /* include-once blocker */ + +/* end of steamshim_child.h ... */ +