├── steam_appid.txt ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE.txt ├── make.sh ├── steamshim_child.h ├── testapp.c ├── README.md ├── steamshim_child.c └── steamshim_parent.cpp /steam_appid.txt: -------------------------------------------------------------------------------- 1 | 232770 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [icculus] 2 | patreon: icculus 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sdk 2 | testapp 3 | steamshim 4 | testapp.dSYM 5 | steamshim.dSYM 6 | steam_api.dll 7 | libsteam_api.dylib 8 | libsteam_api.so 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2021 Ryan C. Gordon . 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # You shouldn't be building this here, you should be building this stuff as 4 | # part of your project, so this is just an example shell script and not a 5 | # formal makefile or whatever. 6 | 7 | if [ ! -d "sdk" ]; then 8 | echo "This script expects the Steamworks SDK to be unpacked here." 1>&2 9 | echo "When unzipped, it makes an 'sdk' directory. Put that here." 1>&2 10 | exit 1 11 | fi 12 | 13 | set -e 14 | set -x 15 | 16 | OSTYPE=`uname -s` 17 | if [ "$OSTYPE" = "Linux" ]; then 18 | g++ -o steamshim -Wall -O0 -ggdb3 steamshim_parent.cpp -I sdk/public sdk/redistributable_bin/linux64/libsteam_api.so 19 | gcc -o testapp -Wall -O0 -ggdb3 testapp.c steamshim_child.c 20 | elif [ "$OSTYPE" = "Darwin" ]; then 21 | clang++ -o steamshim -Wall -O0 -ggdb3 steamshim_parent.cpp -I sdk/public sdk/redistributable_bin/osx32/libsteam_api.dylib 22 | clang -o testapp -Wall -O0 -ggdb3 testapp.c steamshim_child.c 23 | else 24 | echo "write me" 1>&2 25 | exit 1 26 | fi 27 | 28 | set +e 29 | set +x 30 | echo "" 31 | echo "" 32 | echo "" 33 | echo "HEY, PAY ATTENTION." 34 | echo "" 35 | echo "By default, this test app will RESET ALL YOUR ACHIEVEMENTS in Postal 1," 36 | echo " assuming you own that game on Steam. If you don't own that game on" 37 | echo " Steam, this test app won't work. Please plan accordingly." 38 | echo "" 39 | echo "" 40 | echo "" 41 | echo 'You can run "steamshim" now, which will launch "testapp" by default and' 42 | echo 'NUKE ALL YOUR ACHIEVEMENTS in Postal 1. Seriously, watch out.' 43 | echo "" 44 | 45 | exit 0 46 | 47 | -------------------------------------------------------------------------------- /steamshim_child.h: -------------------------------------------------------------------------------- 1 | #ifndef _INCL_STEAMSHIM_CHILD_H_ 2 | #define _INCL_STEAMSHIM_CHILD_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | typedef enum STEAMSHIM_EventType 9 | { 10 | SHIMEVENT_BYE, 11 | SHIMEVENT_STATSRECEIVED, 12 | SHIMEVENT_STATSSTORED, 13 | SHIMEVENT_SETACHIEVEMENT, 14 | SHIMEVENT_GETACHIEVEMENT, 15 | SHIMEVENT_RESETSTATS, 16 | SHIMEVENT_SETSTATI, 17 | SHIMEVENT_GETSTATI, 18 | SHIMEVENT_SETSTATF, 19 | SHIMEVENT_GETSTATF, 20 | } STEAMSHIM_EventType; 21 | 22 | /* not all of these fields make sense in a given event. */ 23 | typedef struct STEAMSHIM_Event 24 | { 25 | STEAMSHIM_EventType type; 26 | int okay; 27 | int ivalue; 28 | float fvalue; 29 | unsigned long long epochsecs; 30 | char name[256]; 31 | } STEAMSHIM_Event; 32 | 33 | int STEAMSHIM_init(void); /* non-zero on success, zero on failure. */ 34 | void STEAMSHIM_deinit(void); 35 | int STEAMSHIM_alive(void); 36 | const STEAMSHIM_Event *STEAMSHIM_pump(void); 37 | void STEAMSHIM_requestStats(void); 38 | void STEAMSHIM_storeStats(void); 39 | void STEAMSHIM_setAchievement(const char *name, const int enable); 40 | void STEAMSHIM_getAchievement(const char *name); 41 | void STEAMSHIM_resetStats(const int bAlsoAchievements); 42 | void STEAMSHIM_setStatI(const char *name, const int _val); 43 | void STEAMSHIM_getStatI(const char *name); 44 | void STEAMSHIM_setStatF(const char *name, const float val); 45 | void STEAMSHIM_getStatF(const char *name); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif /* include-once blocker */ 52 | 53 | /* end of steamshim_child.h ... */ 54 | 55 | -------------------------------------------------------------------------------- /testapp.c: -------------------------------------------------------------------------------- 1 | // This example assumes you own Postal 1 on Steam... 2 | // 3 | // http://store.steampowered.com/app/232770 4 | // 5 | // ...and it will RESET ALL YOUR ACHIEVEMENTS for that game, so BE CAREFUL 6 | // before running this! 7 | 8 | #include "steamshim_child.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | static void printEvent(const STEAMSHIM_Event *e) 15 | { 16 | if (!e) return; 17 | 18 | printf("CHILD EVENT: "); 19 | switch (e->type) 20 | { 21 | #define PRINTGOTEVENT(x) case SHIMEVENT_##x: printf("%s(", #x); break 22 | PRINTGOTEVENT(BYE); 23 | PRINTGOTEVENT(STATSRECEIVED); 24 | PRINTGOTEVENT(STATSSTORED); 25 | PRINTGOTEVENT(SETACHIEVEMENT); 26 | PRINTGOTEVENT(GETACHIEVEMENT); 27 | PRINTGOTEVENT(RESETSTATS); 28 | PRINTGOTEVENT(SETSTATI); 29 | PRINTGOTEVENT(GETSTATI); 30 | PRINTGOTEVENT(SETSTATF); 31 | PRINTGOTEVENT(GETSTATF); 32 | #undef PRINTGOTEVENT 33 | default: printf("UNKNOWN("); break; 34 | } /* switch */ 35 | 36 | printf("%sokay, ival=%d, fval=%f, time=%llu, name='%s').\n", 37 | e->okay ? "" : "!", e->ivalue, e->fvalue, e->epochsecs, e->name); 38 | } /* printEvent */ 39 | 40 | int main(int argc, char **argv) 41 | { 42 | const int retval = (int) time(NULL) % 127; 43 | int i; 44 | 45 | printf("Child argv (argc=%d):\n", argc); 46 | for (i = 0; i <= argc; i++) 47 | printf(" - '%s'\n", argv[i]); 48 | printf("\n"); 49 | 50 | if (!STEAMSHIM_init()) 51 | { 52 | printf("Child init failed, terminating.\n"); 53 | return 42; 54 | } /* if */ 55 | 56 | STEAMSHIM_requestStats(); 57 | while (STEAMSHIM_alive()) 58 | { 59 | const STEAMSHIM_Event *e = STEAMSHIM_pump(); 60 | printEvent(e); 61 | if (e && e->type == SHIMEVENT_STATSRECEIVED) 62 | break; 63 | usleep(100 * 1000); 64 | } // while 65 | 66 | STEAMSHIM_getStatI("BulletsFired"); 67 | STEAMSHIM_getAchievement("KILL_FIRST_VICTIM"); 68 | 69 | STEAMSHIM_resetStats(1); 70 | STEAMSHIM_storeStats(); 71 | 72 | STEAMSHIM_setAchievement("KILL_FIRST_VICTIM", 1); 73 | STEAMSHIM_getAchievement("KILL_FIRST_VICTIM"); 74 | STEAMSHIM_setStatI("BulletsFired", 22); 75 | STEAMSHIM_storeStats(); 76 | 77 | { 78 | time_t x = time(NULL) + 5; 79 | 80 | while ( STEAMSHIM_alive() && (time(NULL) < x) ) 81 | { 82 | const STEAMSHIM_Event *e = STEAMSHIM_pump(); 83 | printEvent(e); 84 | usleep(100 * 1000); 85 | } // while 86 | } 87 | 88 | STEAMSHIM_deinit(); 89 | 90 | sleep(3); 91 | 92 | printf("Child returning %d\n", retval); 93 | return retval; 94 | } /* main */ 95 | 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # steamshim 2 | 3 | ## What is this? 4 | 5 | This is a small piece of code, written in C++, that acts as a bridge between 6 | a child process and the Steamworks SDK. The child process links against a 7 | small piece of code, written in C, to facilitate communication with the 8 | bridge. 9 | 10 | 11 | ## What would I ever need this for? 12 | 13 | You have a GPL-licensed game and can't link directly against the Steamworks 14 | SDK. The child process links against the simple, open source C code, which 15 | talks to the open source C++ code via a pipe, which talks to Steamworks. You 16 | can now add Steam achievements to your game without violating the GPL. 17 | 18 | 19 | ## How does it work? 20 | 21 | - You get a copy of the Steamworks SDK, and link steamshim_parent.cpp against 22 | it. You ship that program and the steam_api.dll (or whatever) with your game. 23 | - The parent process (the C++ code) gets launched as if it were your game. It 24 | initializes Steamworks, creates some pipes and launches your actual game, then 25 | waits on your game to talk to it over those pipes. 26 | - Your game links against steamshim_child.c and at some point 27 | `#include "steamshim_child.h"` to use this functionality. 28 | - Your game calls `STEAMSHIM_init()` at startup to prepare the system. It can 29 | then make other `STEAMSHIM_*()` calls to interact with the parent process and 30 | Steamworks. 31 | - Your game, once a frame, calls `STEAMSHIM_pump()` until it returns NULL. Each 32 | time it doesn't return NULL is a new message (event) from the parent process. 33 | Often times, this is results from a command you asked the parent process to 34 | do earlier, or some other out-of-band event from Steamworks. 35 | - Your game, when shutting down normally, calls `STEAMSHIM_deinit()`, so the 36 | parent process knows you're going away. It says goodbye, shuts down the pipe, 37 | waits for your game to terminate, and then terminates itself. 38 | 39 | 40 | ## Is this all of Steamworks? 41 | 42 | - No! It's actually just enough to deal with stats and achievements right now, 43 | but it can definitely be extended to offer more things. Take a look at 44 | 45 | 46 | ## How do I get the Steamworks SDK? 47 | 48 | - Go to https://partner.steamgames.com/ and login with your Steam account. You 49 | can agree to some terms and then download the SDK. 50 | 51 | 52 | ## Is there an example? 53 | 54 | - That would be testapp.c. This example expects you to own Postal 1 on Steam and 55 | will RESET ALL YOUR ACHIEVEMENTS, so be careful running it. But hey, if you 56 | lose your work, it's a good exercise in SteamShim usage to put them back 57 | again. :) 58 | 59 | 60 | ## I have other questions! 61 | 62 | Ask me. File [a GitHub issue](https://github.com/icculus/steamshim/issues) 63 | or just email me directly: icculus@icculus.org 64 | 65 | --ryan. 66 | 67 | -------------------------------------------------------------------------------- /steamshim_child.c: -------------------------------------------------------------------------------- 1 | 2 | #ifdef _WIN32 3 | #define WIN32_LEAN_AND_MEAN 1 4 | #include 5 | typedef HANDLE PipeType; 6 | #define NULLPIPE NULL 7 | typedef unsigned __int8 uint8; 8 | typedef __int32 int32; 9 | typedef unsigned __int64 uint64; 10 | #else 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | typedef uint8_t uint8; 20 | typedef int32_t int32; 21 | typedef uint64_t uint64; 22 | typedef int PipeType; 23 | #define NULLPIPE -1 24 | #endif 25 | 26 | #include "steamshim_child.h" 27 | 28 | #define DEBUGPIPE 1 29 | #if DEBUGPIPE 30 | #define dbgpipe printf 31 | #else 32 | static inline void dbgpipe(const char *fmt, ...) {} 33 | #endif 34 | 35 | static int writePipe(PipeType fd, const void *buf, const unsigned int _len); 36 | static int readPipe(PipeType fd, void *buf, const unsigned int _len); 37 | static void closePipe(PipeType fd); 38 | static char *getEnvVar(const char *key, char *buf, const size_t buflen); 39 | static int pipeReady(PipeType fd); 40 | 41 | 42 | #ifdef _WIN32 43 | 44 | static int pipeReady(PipeType fd) 45 | { 46 | DWORD avail = 0; 47 | return (PeekNamedPipe(fd, NULL, 0, NULL, &avail, NULL) && (avail > 0)); 48 | } /* pipeReady */ 49 | 50 | static int writePipe(PipeType fd, const void *buf, const unsigned int _len) 51 | { 52 | const DWORD len = (DWORD) _len; 53 | DWORD bw = 0; 54 | return ((WriteFile(fd, buf, len, &bw, NULL) != 0) && (bw == len)); 55 | } /* writePipe */ 56 | 57 | static int readPipe(PipeType fd, void *buf, const unsigned int _len) 58 | { 59 | const DWORD len = (DWORD) _len; 60 | DWORD br = 0; 61 | return ReadFile(fd, buf, len, &br, NULL) ? (int) br : -1; 62 | } /* readPipe */ 63 | 64 | static void closePipe(PipeType fd) 65 | { 66 | CloseHandle(fd); 67 | } /* closePipe */ 68 | 69 | static char *getEnvVar(const char *key, char *buf, const size_t _buflen) 70 | { 71 | const DWORD buflen = (DWORD) _buflen; 72 | const DWORD rc = GetEnvironmentVariableA(key, val, buflen); 73 | /* rc doesn't count null char, hence "<". */ 74 | return ((rc > 0) && (rc < buflen)) ? NULL : buf; 75 | } /* getEnvVar */ 76 | 77 | #else 78 | 79 | static int pipeReady(PipeType fd) 80 | { 81 | int rc; 82 | struct pollfd pfd = { fd, POLLIN | POLLERR | POLLHUP, 0 }; 83 | while (((rc = poll(&pfd, 1, 0)) == -1) && (errno == EINTR)) { /*spin*/ } 84 | return (rc == 1); 85 | } /* pipeReady */ 86 | 87 | static int writePipe(PipeType fd, const void *buf, const unsigned int _len) 88 | { 89 | const ssize_t len = (ssize_t) _len; 90 | ssize_t bw; 91 | while (((bw = write(fd, buf, len)) == -1) && (errno == EINTR)) { /*spin*/ } 92 | return (bw == len); 93 | } /* writePipe */ 94 | 95 | static int readPipe(PipeType fd, void *buf, const unsigned int _len) 96 | { 97 | const ssize_t len = (ssize_t) _len; 98 | ssize_t br; 99 | while (((br = read(fd, buf, len)) == -1) && (errno == EINTR)) { /*spin*/ } 100 | return (int) br; 101 | } /* readPipe */ 102 | 103 | static void closePipe(PipeType fd) 104 | { 105 | close(fd); 106 | } /* closePipe */ 107 | 108 | static char *getEnvVar(const char *key, char *buf, const size_t buflen) 109 | { 110 | const char *envr = getenv(key); 111 | if (!envr || (strlen(envr) >= buflen)) 112 | return NULL; 113 | strcpy(buf, envr); 114 | return buf; 115 | } /* getEnvVar */ 116 | 117 | #endif 118 | 119 | 120 | static PipeType GPipeRead = NULLPIPE; 121 | static PipeType GPipeWrite = NULLPIPE; 122 | 123 | typedef enum ShimCmd 124 | { 125 | SHIMCMD_BYE, 126 | SHIMCMD_PUMP, 127 | SHIMCMD_REQUESTSTATS, 128 | SHIMCMD_STORESTATS, 129 | SHIMCMD_SETACHIEVEMENT, 130 | SHIMCMD_GETACHIEVEMENT, 131 | SHIMCMD_RESETSTATS, 132 | SHIMCMD_SETSTATI, 133 | SHIMCMD_GETSTATI, 134 | SHIMCMD_SETSTATF, 135 | SHIMCMD_GETSTATF, 136 | } ShimCmd; 137 | 138 | static int write1ByteCmd(const uint8 b1) 139 | { 140 | const uint8 buf[] = { 1, b1 }; 141 | return writePipe(GPipeWrite, buf, sizeof (buf)); 142 | } /* write1ByteCmd */ 143 | 144 | static int write2ByteCmd(const uint8 b1, const uint8 b2) 145 | { 146 | const uint8 buf[] = { 2, b1, b2 }; 147 | return writePipe(GPipeWrite, buf, sizeof (buf)); 148 | } /* write2ByteCmd */ 149 | 150 | static inline int writeBye(void) 151 | { 152 | dbgpipe("Child sending SHIMCMD_BYE().\n"); 153 | return write1ByteCmd(SHIMCMD_BYE); 154 | } // writeBye 155 | 156 | static int initPipes(void) 157 | { 158 | char buf[64]; 159 | unsigned long long val; 160 | 161 | if (!getEnvVar("STEAMSHIM_READHANDLE", buf, sizeof (buf))) 162 | return 0; 163 | else if (sscanf(buf, "%llu", &val) != 1) 164 | return 0; 165 | else 166 | GPipeRead = (PipeType) val; 167 | 168 | if (!getEnvVar("STEAMSHIM_WRITEHANDLE", buf, sizeof (buf))) 169 | return 0; 170 | else if (sscanf(buf, "%llu", &val) != 1) 171 | return 0; 172 | else 173 | GPipeWrite = (PipeType) val; 174 | 175 | return ((GPipeRead != NULLPIPE) && (GPipeWrite != NULLPIPE)); 176 | } /* initPipes */ 177 | 178 | 179 | int STEAMSHIM_init(void) 180 | { 181 | dbgpipe("Child init start.\n"); 182 | if (!initPipes()) 183 | { 184 | dbgpipe("Child init failed.\n"); 185 | return 0; 186 | } /* if */ 187 | 188 | signal(SIGPIPE, SIG_IGN); 189 | 190 | dbgpipe("Child init success!\n"); 191 | return 1; 192 | } /* STEAMSHIM_init */ 193 | 194 | void STEAMSHIM_deinit(void) 195 | { 196 | dbgpipe("Child deinit.\n"); 197 | if (GPipeWrite != NULLPIPE) 198 | { 199 | writeBye(); 200 | closePipe(GPipeWrite); 201 | } /* if */ 202 | 203 | if (GPipeRead != NULLPIPE) 204 | closePipe(GPipeRead); 205 | 206 | GPipeRead = GPipeWrite = NULLPIPE; 207 | 208 | signal(SIGPIPE, SIG_DFL); 209 | } /* STEAMSHIM_deinit */ 210 | 211 | static inline int isAlive(void) 212 | { 213 | return ((GPipeRead != NULLPIPE) && (GPipeWrite != NULLPIPE)); 214 | } /* isAlive */ 215 | 216 | static inline int isDead(void) 217 | { 218 | return !isAlive(); 219 | } /* isDead */ 220 | 221 | int STEAMSHIM_alive(void) 222 | { 223 | return isAlive(); 224 | } /* STEAMSHIM_alive */ 225 | 226 | static const STEAMSHIM_Event *processEvent(const uint8 *buf, size_t buflen) 227 | { 228 | static STEAMSHIM_Event event; 229 | const STEAMSHIM_EventType type = (STEAMSHIM_EventType) *(buf++); 230 | buflen--; 231 | 232 | memset(&event, '\0', sizeof (event)); 233 | event.type = type; 234 | event.okay = 1; 235 | 236 | #if DEBUGPIPE 237 | if (0) {} 238 | #define PRINTGOTEVENT(x) else if (type == x) printf("Child got " #x ".\n") 239 | PRINTGOTEVENT(SHIMEVENT_BYE); 240 | PRINTGOTEVENT(SHIMEVENT_STATSRECEIVED); 241 | PRINTGOTEVENT(SHIMEVENT_STATSSTORED); 242 | PRINTGOTEVENT(SHIMEVENT_SETACHIEVEMENT); 243 | PRINTGOTEVENT(SHIMEVENT_GETACHIEVEMENT); 244 | PRINTGOTEVENT(SHIMEVENT_RESETSTATS); 245 | PRINTGOTEVENT(SHIMEVENT_SETSTATI); 246 | PRINTGOTEVENT(SHIMEVENT_GETSTATI); 247 | PRINTGOTEVENT(SHIMEVENT_SETSTATF); 248 | PRINTGOTEVENT(SHIMEVENT_GETSTATF); 249 | #undef PRINTGOTEVENT 250 | else printf("Child got unknown shimevent %d.\n", (int) type); 251 | #endif 252 | 253 | switch (type) 254 | { 255 | case SHIMEVENT_BYE: 256 | break; 257 | 258 | case SHIMEVENT_STATSRECEIVED: 259 | case SHIMEVENT_STATSSTORED: 260 | if (!buflen) return NULL; 261 | event.okay = *(buf++) ? 1 : 0; 262 | break; 263 | 264 | case SHIMEVENT_SETACHIEVEMENT: 265 | if (buflen < 3) return NULL; 266 | event.ivalue = *(buf++) ? 1 : 0; 267 | event.okay = *(buf++) ? 1 : 0; 268 | strcpy(event.name, (const char *) buf); 269 | break; 270 | 271 | case SHIMEVENT_GETACHIEVEMENT: 272 | if (buflen < 10) return NULL; 273 | event.ivalue = (int) *(buf++); 274 | if (event.ivalue == 2) 275 | event.ivalue = event.okay = 0; 276 | event.epochsecs = (long long unsigned) *((uint64 *) buf); 277 | buf += sizeof (uint64); 278 | strcpy(event.name, (const char *) buf); 279 | break; 280 | 281 | case SHIMEVENT_RESETSTATS: 282 | if (buflen != 2) return NULL; 283 | event.ivalue = *(buf++) ? 1 : 0; 284 | event.okay = *(buf++) ? 1 : 0; 285 | break; 286 | 287 | case SHIMEVENT_SETSTATI: 288 | case SHIMEVENT_GETSTATI: 289 | event.okay = *(buf++) ? 1 : 0; 290 | event.ivalue = (int) *((int32 *) buf); 291 | buf += sizeof (int32); 292 | strcpy(event.name, (const char *) buf); 293 | break; 294 | 295 | case SHIMEVENT_SETSTATF: 296 | case SHIMEVENT_GETSTATF: 297 | event.okay = *(buf++) ? 1 : 0; 298 | event.fvalue = (int) *((float *) buf); 299 | buf += sizeof (float); 300 | strcpy(event.name, (const char *) buf); 301 | break; 302 | 303 | default: /* uh oh */ 304 | return NULL; 305 | } /* switch */ 306 | 307 | return &event; 308 | } /* processEvent */ 309 | 310 | const STEAMSHIM_Event *STEAMSHIM_pump(void) 311 | { 312 | static uint8 buf[256]; 313 | static int br = 0; 314 | int evlen = (br > 0) ? ((int) buf[0]) : 0; 315 | 316 | if (isDead()) 317 | return NULL; 318 | 319 | if (br <= evlen) /* we have an incomplete commmand. Try to read more. */ 320 | { 321 | if (pipeReady(GPipeRead)) 322 | { 323 | const int morebr = readPipe(GPipeRead, buf + br, sizeof (buf) - br); 324 | if (morebr > 0) 325 | br += morebr; 326 | else /* uh oh */ 327 | { 328 | dbgpipe("Child readPipe failed! Shutting down.\n"); 329 | STEAMSHIM_deinit(); /* kill it all. */ 330 | } /* else */ 331 | } /* if */ 332 | } /* if */ 333 | 334 | if (evlen && (br > evlen)) 335 | { 336 | const STEAMSHIM_Event *retval = processEvent(buf+1, evlen); 337 | br -= evlen + 1; 338 | if (br > 0) 339 | memmove(buf, buf+evlen+1, br); 340 | return retval; 341 | } /* if */ 342 | 343 | /* Run Steam event loop. */ 344 | if (br == 0) 345 | { 346 | dbgpipe("Child sending SHIMCMD_PUMP().\n"); 347 | write1ByteCmd(SHIMCMD_PUMP); 348 | } /* if */ 349 | 350 | return NULL; 351 | } /* STEAMSHIM_pump */ 352 | 353 | void STEAMSHIM_requestStats(void) 354 | { 355 | if (isDead()) return; 356 | dbgpipe("Child sending SHIMCMD_REQUESTSTATS().\n"); 357 | write1ByteCmd(SHIMCMD_REQUESTSTATS); 358 | } /* STEAMSHIM_requestStats */ 359 | 360 | void STEAMSHIM_storeStats(void) 361 | { 362 | if (isDead()) return; 363 | dbgpipe("Child sending SHIMCMD_STORESTATS().\n"); 364 | write1ByteCmd(SHIMCMD_STORESTATS); 365 | } /* STEAMSHIM_storeStats */ 366 | 367 | void STEAMSHIM_setAchievement(const char *name, const int enable) 368 | { 369 | uint8 buf[256]; 370 | uint8 *ptr = buf+1; 371 | if (isDead()) return; 372 | dbgpipe("Child sending SHIMCMD_SETACHIEVEMENT('%s', %senable).\n", name, enable ? "" : "!"); 373 | *(ptr++) = (uint8) SHIMCMD_SETACHIEVEMENT; 374 | *(ptr++) = enable ? 1 : 0; 375 | strcpy((char *) ptr, name); 376 | ptr += strlen(name) + 1; 377 | buf[0] = (uint8) ((ptr-1) - buf); 378 | writePipe(GPipeWrite, buf, buf[0] + 1); 379 | } /* STEAMSHIM_setAchievement */ 380 | 381 | void STEAMSHIM_getAchievement(const char *name) 382 | { 383 | uint8 buf[256]; 384 | uint8 *ptr = buf+1; 385 | if (isDead()) return; 386 | dbgpipe("Child sending SHIMCMD_GETACHIEVEMENT('%s').\n", name); 387 | *(ptr++) = (uint8) SHIMCMD_GETACHIEVEMENT; 388 | strcpy((char *) ptr, name); 389 | ptr += strlen(name) + 1; 390 | buf[0] = (uint8) ((ptr-1) - buf); 391 | writePipe(GPipeWrite, buf, buf[0] + 1); 392 | } /* STEAMSHIM_getAchievement */ 393 | 394 | void STEAMSHIM_resetStats(const int bAlsoAchievements) 395 | { 396 | if (isDead()) return; 397 | dbgpipe("Child sending SHIMCMD_RESETSTATS(%salsoAchievements).\n", bAlsoAchievements ? "" : "!"); 398 | write2ByteCmd(SHIMCMD_RESETSTATS, bAlsoAchievements ? 1 : 0); 399 | } /* STEAMSHIM_resetStats */ 400 | 401 | static void writeStatThing(const ShimCmd cmd, const char *name, const void *val, const size_t vallen) 402 | { 403 | uint8 buf[256]; 404 | uint8 *ptr = buf+1; 405 | if (isDead()) return; 406 | *(ptr++) = (uint8) cmd; 407 | if (vallen) 408 | { 409 | memcpy(ptr, val, vallen); 410 | ptr += vallen; 411 | } /* if */ 412 | strcpy((char *) ptr, name); 413 | ptr += strlen(name) + 1; 414 | buf[0] = (uint8) ((ptr-1) - buf); 415 | writePipe(GPipeWrite, buf, buf[0] + 1); 416 | } /* writeStatThing */ 417 | 418 | void STEAMSHIM_setStatI(const char *name, const int _val) 419 | { 420 | const int32 val = (int32) _val; 421 | dbgpipe("Child sending SHIMCMD_SETSTATI('%s', val %d).\n", name, val); 422 | writeStatThing(SHIMCMD_SETSTATI, name, &val, sizeof (val)); 423 | } /* STEAMSHIM_setStatI */ 424 | 425 | void STEAMSHIM_getStatI(const char *name) 426 | { 427 | dbgpipe("Child sending SHIMCMD_GETSTATI('%s').\n", name); 428 | writeStatThing(SHIMCMD_GETSTATI, name, NULL, 0); 429 | } /* STEAMSHIM_getStatI */ 430 | 431 | void STEAMSHIM_setStatF(const char *name, const float val) 432 | { 433 | dbgpipe("Child sending SHIMCMD_SETSTATF('%s', val %f).\n", name, val); 434 | writeStatThing(SHIMCMD_SETSTATF, name, &val, sizeof (val)); 435 | } /* STEAMSHIM_setStatF */ 436 | 437 | void STEAMSHIM_getStatF(const char *name) 438 | { 439 | dbgpipe("Child sending SHIMCMD_GETSTATF('%s').\n", name); 440 | writeStatThing(SHIMCMD_GETSTATF, name, NULL, 0); 441 | } /* STEAMSHIM_getStatF */ 442 | 443 | /* end of steamshim_child.c ... */ 444 | 445 | -------------------------------------------------------------------------------- /steamshim_parent.cpp: -------------------------------------------------------------------------------- 1 | #define GAME_LAUNCH_NAME "testapp" 2 | #ifndef GAME_LAUNCH_NAME 3 | #error Please define your game exe name. 4 | #endif 5 | 6 | #ifdef _WIN32 7 | #define WIN32_LEAN_AND_MEAN 1 8 | #include 9 | typedef PROCESS_INFORMATION ProcessType; 10 | typedef HANDLE PipeType; 11 | #define NULLPIPE NULL 12 | #else 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | typedef pid_t ProcessType; 21 | typedef int PipeType; 22 | #define NULLPIPE -1 23 | #endif 24 | 25 | #include "steam/steam_api.h" 26 | 27 | #define DEBUGPIPE 1 28 | #if DEBUGPIPE 29 | #define dbgpipe printf 30 | #else 31 | static inline void dbgpipe(const char *fmt, ...) {} 32 | #endif 33 | 34 | /* platform-specific mainline calls this. */ 35 | static int mainline(void); 36 | 37 | /* Windows and Unix implementations of this stuff below. */ 38 | static void fail(const char *err); 39 | static bool writePipe(PipeType fd, const void *buf, const unsigned int _len); 40 | static int readPipe(PipeType fd, void *buf, const unsigned int _len); 41 | static bool createPipes(PipeType *pPipeParentRead, PipeType *pPipeParentWrite, 42 | PipeType *pPipeChildRead, PipeType *pPipeChildWrite); 43 | static void closePipe(PipeType fd); 44 | static bool setEnvVar(const char *key, const char *val); 45 | static bool launchChild(ProcessType *pid); 46 | static int closeProcess(ProcessType *pid); 47 | 48 | #ifdef _WIN32 49 | static void fail(const char *err) 50 | { 51 | MessageBoxA(NULL, err, "ERROR", MB_ICONERROR | MB_OK); 52 | ExitProcess(1); 53 | } // fail 54 | 55 | static bool writePipe(PipeType fd, const void *buf, const unsigned int _len) 56 | { 57 | const DWORD len = (DWORD) _len; 58 | DWORD bw = 0; 59 | return ((WriteFile(fd, buf, len, &bw, NULL) != 0) && (bw == len)); 60 | } // writePipe 61 | 62 | static int readPipe(PipeType fd, void *buf, const unsigned int _len) 63 | { 64 | const DWORD len = (DWORD) _len; 65 | DWORD br = 0; 66 | return ReadFile(fd, buf, len, &br, NULL) ? (int) br : -1; 67 | } // readPipe 68 | 69 | static bool createPipes(PipeType *pPipeParentRead, PipeType *pPipeParentWrite, 70 | PipeType *pPipeChildRead, PipeType *pPipeChildWrite) 71 | { 72 | SECURITY_ATTRIBUTES pipeAttr; 73 | 74 | pipeAttr.nLength = sizeof (pipeAttr); 75 | pipeAttr.lpSecurityDescriptor = NULL; 76 | pipeAttr.bInheritHandle = TRUE; 77 | if (!CreatePipe(pPipeParentRead, pPipeChildWrite, &pipeAttr, 0)) 78 | return 0; 79 | 80 | pipeAttr.nLength = sizeof (pipeAttr); 81 | pipeAttr.lpSecurityDescriptor = NULL; 82 | pipeAttr.bInheritHandle = TRUE; 83 | if (!CreatePipe(pPipeChildRead, pPipeParentWrite, &pipeAttr, 0)) 84 | { 85 | CloseHandle(*pPipeParentRead); 86 | CloseHandle(*pPipeChildWrite); 87 | return 0; 88 | } // if 89 | 90 | return 1; 91 | } // createPipes 92 | 93 | static void closePipe(PipeType fd) 94 | { 95 | CloseHandle(fd); 96 | } // closePipe 97 | 98 | static bool setEnvVar(const char *key, const char *val) 99 | { 100 | return (SetEnvironmentVariableA(key, val) != 0); 101 | } // setEnvVar 102 | 103 | static bool launchChild(ProcessType *pid); 104 | { 105 | return (CreateProcessW(TEXT(".\\") TEXT(GAME_LAUNCH_NAME) TEXT(".exe"), 106 | GetCommandLineW(), NULL, NULL, TRUE, 0, NULL, 107 | NULL, NULL, pid) != 0); 108 | } // launchChild 109 | 110 | static int closeProcess(ProcessType *pid) 111 | { 112 | CloseHandle(pid->hProcess); 113 | CloseHandle(pid->hThread); 114 | return 0; 115 | } // closeProcess 116 | 117 | int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 118 | LPSTR lpCmdLine, int nCmdShow) 119 | { 120 | mainline(); 121 | ExitProcess(0); 122 | return 0; // just in case. 123 | } // WinMain 124 | 125 | 126 | #else // everyone else that isn't Windows. 127 | 128 | static void fail(const char *err) 129 | { 130 | // !!! FIXME: zenity or something. 131 | fprintf(stderr, "%s\n", err); 132 | _exit(1); 133 | } // fail 134 | 135 | static bool writePipe(PipeType fd, const void *buf, const unsigned int _len) 136 | { 137 | const ssize_t len = (ssize_t) _len; 138 | ssize_t bw; 139 | while (((bw = write(fd, buf, len)) == -1) && (errno == EINTR)) { /*spin*/ } 140 | return (bw == len); 141 | } // writePipe 142 | 143 | static int readPipe(PipeType fd, void *buf, const unsigned int _len) 144 | { 145 | const ssize_t len = (ssize_t) _len; 146 | ssize_t br; 147 | while (((br = read(fd, buf, len)) == -1) && (errno == EINTR)) { /*spin*/ } 148 | return (int) br; 149 | } // readPipe 150 | 151 | static bool createPipes(PipeType *pPipeParentRead, PipeType *pPipeParentWrite, 152 | PipeType *pPipeChildRead, PipeType *pPipeChildWrite) 153 | { 154 | int fds[2]; 155 | if (pipe(fds) == -1) 156 | return 0; 157 | *pPipeParentRead = fds[0]; 158 | *pPipeChildWrite = fds[1]; 159 | 160 | if (pipe(fds) == -1) 161 | { 162 | close(*pPipeParentRead); 163 | close(*pPipeChildWrite); 164 | return 0; 165 | } // if 166 | 167 | *pPipeChildRead = fds[0]; 168 | *pPipeParentWrite = fds[1]; 169 | 170 | return 1; 171 | } // createPipes 172 | 173 | static void closePipe(PipeType fd) 174 | { 175 | close(fd); 176 | } // closePipe 177 | 178 | static bool setEnvVar(const char *key, const char *val) 179 | { 180 | return (setenv(key, val, 1) != -1); 181 | } // setEnvVar 182 | 183 | static int GArgc = 0; 184 | static char **GArgv = NULL; 185 | 186 | static bool launchChild(ProcessType *pid) 187 | { 188 | *pid = fork(); 189 | if (*pid == -1) // failed 190 | return false; 191 | else if (*pid != 0) // we're the parent 192 | return true; // we'll let the pipe fail if this didn't work. 193 | 194 | // we're the child. 195 | GArgv[0] = strdup("./" GAME_LAUNCH_NAME); 196 | execvp(GArgv[0], GArgv); 197 | // still here? It failed! Terminate, closing child's ends of the pipes. 198 | _exit(1); 199 | } // launchChild 200 | 201 | static int closeProcess(ProcessType *pid) 202 | { 203 | int rc = 0; 204 | while ((waitpid(*pid, &rc, 0) == -1) && (errno == EINTR)) { /*spin*/ } 205 | if (!WIFEXITED(rc)) 206 | return 1; // oh well. 207 | return WEXITSTATUS(rc); 208 | } // closeProcess 209 | 210 | int main(int argc, char **argv) 211 | { 212 | signal(SIGPIPE, SIG_IGN); 213 | GArgc = argc; 214 | GArgv = argv; 215 | return mainline(); 216 | } // main 217 | 218 | #endif 219 | 220 | 221 | // THE ACTUAL PROGRAM. 222 | 223 | class SteamBridge; 224 | 225 | static ISteamUserStats *GSteamStats = NULL; 226 | static ISteamUtils *GSteamUtils = NULL; 227 | static ISteamUser *GSteamUser = NULL; 228 | static AppId_t GAppID = 0; 229 | static uint64 GUserID = 0; 230 | static SteamBridge *GSteamBridge = NULL; 231 | 232 | class SteamBridge 233 | { 234 | public: 235 | SteamBridge(PipeType _fd); 236 | STEAM_CALLBACK(SteamBridge, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived); 237 | STEAM_CALLBACK(SteamBridge, OnUserStatsStored, UserStatsStored_t, m_CallbackUserStatsStored); 238 | 239 | private: 240 | PipeType fd; 241 | }; 242 | 243 | typedef enum ShimCmd 244 | { 245 | SHIMCMD_BYE, 246 | SHIMCMD_PUMP, 247 | SHIMCMD_REQUESTSTATS, 248 | SHIMCMD_STORESTATS, 249 | SHIMCMD_SETACHIEVEMENT, 250 | SHIMCMD_GETACHIEVEMENT, 251 | SHIMCMD_RESETSTATS, 252 | SHIMCMD_SETSTATI, 253 | SHIMCMD_GETSTATI, 254 | SHIMCMD_SETSTATF, 255 | SHIMCMD_GETSTATF, 256 | } ShimCmd; 257 | 258 | typedef enum ShimEvent 259 | { 260 | SHIMEVENT_BYE, 261 | SHIMEVENT_STATSRECEIVED, 262 | SHIMEVENT_STATSSTORED, 263 | SHIMEVENT_SETACHIEVEMENT, 264 | SHIMEVENT_GETACHIEVEMENT, 265 | SHIMEVENT_RESETSTATS, 266 | SHIMEVENT_SETSTATI, 267 | SHIMEVENT_GETSTATI, 268 | SHIMEVENT_SETSTATF, 269 | SHIMEVENT_GETSTATF, 270 | } ShimEvent; 271 | 272 | static bool write1ByteCmd(PipeType fd, const uint8 b1) 273 | { 274 | const uint8 buf[] = { 1, b1 }; 275 | return writePipe(fd, buf, sizeof (buf)); 276 | } // write1ByteCmd 277 | 278 | static bool write2ByteCmd(PipeType fd, const uint8 b1, const uint8 b2) 279 | { 280 | const uint8 buf[] = { 2, b1, b2 }; 281 | return writePipe(fd, buf, sizeof (buf)); 282 | } // write2ByteCmd 283 | 284 | static bool write3ByteCmd(PipeType fd, const uint8 b1, const uint8 b2, const uint8 b3) 285 | { 286 | const uint8 buf[] = { 3, b1, b2, b3 }; 287 | return writePipe(fd, buf, sizeof (buf)); 288 | } // write3ByteCmd 289 | 290 | 291 | static inline bool writeBye(PipeType fd) 292 | { 293 | dbgpipe("Parent sending SHIMEVENT_BYE().\n"); 294 | return write1ByteCmd(fd, SHIMEVENT_BYE); 295 | } // writeBye 296 | 297 | static inline bool writeStatsReceived(PipeType fd, const bool okay) 298 | { 299 | dbgpipe("Parent sending SHIMEVENT_STATSRECEIVED(%sokay).\n", okay ? "" : "!"); 300 | return write2ByteCmd(fd, SHIMEVENT_STATSRECEIVED, okay ? 1 : 0); 301 | } // writeStatsReceived 302 | 303 | static inline bool writeStatsStored(PipeType fd, const bool okay) 304 | { 305 | dbgpipe("Parent sending SHIMEVENT_STATSSTORED(%sokay).\n", okay ? "" : "!"); 306 | return write2ByteCmd(fd, SHIMEVENT_STATSSTORED, okay ? 1 : 0); 307 | } // writeStatsStored 308 | 309 | static bool writeAchievementSet(PipeType fd, const char *name, const bool enable, const bool okay) 310 | { 311 | uint8 buf[256]; 312 | uint8 *ptr = buf+1; 313 | dbgpipe("Parent sending SHIMEVENT_SETACHIEVEMENT('%s', %senable, %sokay).\n", name, enable ? "" : "!", okay ? "" : "!"); 314 | *(ptr++) = (uint8) SHIMEVENT_SETACHIEVEMENT; 315 | *(ptr++) = enable ? 1 : 0; 316 | *(ptr++) = okay ? 1 : 0; 317 | strcpy((char *) ptr, name); 318 | ptr += strlen(name) + 1; 319 | buf[0] = (uint8) ((ptr-1) - buf); 320 | return writePipe(fd, buf, buf[0] + 1); 321 | } // writeAchievementSet 322 | 323 | static bool writeAchievementGet(PipeType fd, const char *name, const int status, const uint64 time) 324 | { 325 | uint8 buf[256]; 326 | uint8 *ptr = buf+1; 327 | dbgpipe("Parent sending SHIMEVENT_GETACHIEVEMENT('%s', status %d, time %llu).\n", name, status, (unsigned long long) time); 328 | *(ptr++) = (uint8) SHIMEVENT_GETACHIEVEMENT; 329 | *(ptr++) = (uint8) status; 330 | memcpy(ptr, &time, sizeof (time)); 331 | ptr += sizeof (time); 332 | strcpy((char *) ptr, name); 333 | ptr += strlen(name) + 1; 334 | buf[0] = (uint8) ((ptr-1) - buf); 335 | return writePipe(fd, buf, buf[0] + 1); 336 | } // writeAchievementGet 337 | 338 | static inline bool writeResetStats(PipeType fd, const bool alsoAch, const bool okay) 339 | { 340 | dbgpipe("Parent sending SHIMEVENT_RESETSTATS(%salsoAchievements, %sokay).\n", alsoAch ? "" : "!", okay ? "" : "!"); 341 | return write3ByteCmd(fd, SHIMEVENT_RESETSTATS, alsoAch ? 1 : 0, okay ? 1 : 0); 342 | } // writeResetStats 343 | 344 | static bool writeStatThing(PipeType fd, const ShimEvent ev, const char *name, const void *val, const size_t vallen, const bool okay) 345 | { 346 | uint8 buf[256]; 347 | uint8 *ptr = buf+1; 348 | *(ptr++) = (uint8) ev; 349 | *(ptr++) = okay ? 1 : 0; 350 | memcpy(ptr, val, vallen); 351 | ptr += vallen; 352 | strcpy((char *) ptr, name); 353 | ptr += strlen(name) + 1; 354 | buf[0] = (uint8) ((ptr-1) - buf); 355 | return writePipe(fd, buf, buf[0] + 1); 356 | } // writeStatThing 357 | 358 | static inline bool writeSetStatI(PipeType fd, const char *name, const int32 val, const bool okay) 359 | { 360 | dbgpipe("Parent sending SHIMEVENT_SETSTATI('%s', val %d, %sokay).\n", name, (int) val, okay ? "" : "!"); 361 | return writeStatThing(fd, SHIMEVENT_SETSTATI, name, &val, sizeof (val), okay); 362 | } // writeSetStatI 363 | 364 | static inline bool writeSetStatF(PipeType fd, const char *name, const float val, const bool okay) 365 | { 366 | dbgpipe("Parent sending SHIMEVENT_SETSTATF('%s', val %f, %sokay).\n", name, val, okay ? "" : "!"); 367 | return writeStatThing(fd, SHIMEVENT_SETSTATF, name, &val, sizeof (val), okay); 368 | } // writeSetStatF 369 | 370 | static inline bool writeGetStatI(PipeType fd, const char *name, const int32 val, const bool okay) 371 | { 372 | dbgpipe("Parent sending SHIMEVENT_GETSTATI('%s', val %d, %sokay).\n", name, (int) val, okay ? "" : "!"); 373 | return writeStatThing(fd, SHIMEVENT_GETSTATI, name, &val, sizeof (val), okay); 374 | } // writeGetStatI 375 | 376 | static inline bool writeGetStatF(PipeType fd, const char *name, const float val, const bool okay) 377 | { 378 | dbgpipe("Parent sending SHIMEVENT_GETSTATF('%s', val %f, %sokay).\n", name, val, okay ? "" : "!"); 379 | return writeStatThing(fd, SHIMEVENT_GETSTATF, name, &val, sizeof (val), okay); 380 | } // writeGetStatF 381 | 382 | 383 | 384 | SteamBridge::SteamBridge(PipeType _fd) 385 | : m_CallbackUserStatsReceived( this, &SteamBridge::OnUserStatsReceived ) 386 | , m_CallbackUserStatsStored( this, &SteamBridge::OnUserStatsStored ) 387 | , fd(_fd) 388 | { 389 | } // SteamBridge::SteamBridge 390 | 391 | void SteamBridge::OnUserStatsReceived(UserStatsReceived_t *pCallback) 392 | { 393 | if (GAppID != pCallback->m_nGameID) return; 394 | if (GUserID != pCallback->m_steamIDUser.ConvertToUint64()) return; 395 | writeStatsReceived(fd, pCallback->m_eResult == k_EResultOK); 396 | } // SteamBridge::OnUserStatsReceived 397 | 398 | void SteamBridge::OnUserStatsStored(UserStatsStored_t *pCallback) 399 | { 400 | if (GAppID != pCallback->m_nGameID) return; 401 | writeStatsStored(fd, pCallback->m_eResult == k_EResultOK); 402 | } // SteamBridge::OnUserStatsStored 403 | 404 | 405 | static bool processCommand(const uint8 *buf, unsigned int buflen, PipeType fd) 406 | { 407 | if (buflen == 0) 408 | return true; 409 | 410 | const ShimCmd cmd = (ShimCmd) *(buf++); 411 | buflen--; 412 | 413 | #if DEBUGPIPE 414 | if (false) {} 415 | #define PRINTGOTCMD(x) else if (cmd == x) printf("Parent got " #x ".\n") 416 | PRINTGOTCMD(SHIMCMD_BYE); 417 | PRINTGOTCMD(SHIMCMD_PUMP); 418 | PRINTGOTCMD(SHIMCMD_REQUESTSTATS); 419 | PRINTGOTCMD(SHIMCMD_STORESTATS); 420 | PRINTGOTCMD(SHIMCMD_SETACHIEVEMENT); 421 | PRINTGOTCMD(SHIMCMD_GETACHIEVEMENT); 422 | PRINTGOTCMD(SHIMCMD_RESETSTATS); 423 | PRINTGOTCMD(SHIMCMD_SETSTATI); 424 | PRINTGOTCMD(SHIMCMD_GETSTATI); 425 | PRINTGOTCMD(SHIMCMD_SETSTATF); 426 | PRINTGOTCMD(SHIMCMD_GETSTATF); 427 | #undef PRINTGOTCMD 428 | else printf("Parent got unknown shimcmd %d.\n", (int) cmd); 429 | #endif 430 | 431 | switch (cmd) 432 | { 433 | case SHIMCMD_PUMP: 434 | SteamAPI_RunCallbacks(); 435 | break; 436 | 437 | case SHIMCMD_BYE: 438 | writeBye(fd); 439 | return false; 440 | 441 | case SHIMCMD_REQUESTSTATS: 442 | if ((!GSteamStats) || (!GSteamStats->RequestCurrentStats())) 443 | writeStatsReceived(fd, false); 444 | // callback later. 445 | break; 446 | 447 | case SHIMCMD_STORESTATS: 448 | if ((!GSteamStats) || (!GSteamStats->StoreStats())) 449 | writeStatsStored(fd, false); 450 | // callback later. 451 | break; 452 | 453 | case SHIMCMD_SETACHIEVEMENT: 454 | if (buflen >= 2) 455 | { 456 | const bool enable = (*(buf++) != 0); 457 | const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. 458 | if (!GSteamStats) 459 | writeAchievementSet(fd, name, enable, false); 460 | else if (enable && !GSteamStats->SetAchievement(name)) 461 | writeAchievementSet(fd, name, enable, false); 462 | else if (!enable && !GSteamStats->ClearAchievement(name)) 463 | writeAchievementSet(fd, name, enable, false); 464 | else 465 | writeAchievementSet(fd, name, enable, true); 466 | } // if 467 | break; 468 | 469 | case SHIMCMD_GETACHIEVEMENT: 470 | if (buflen) 471 | { 472 | const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. 473 | bool ach = false; 474 | uint32 t = 0; 475 | if ((GSteamStats) && (GSteamStats->GetAchievementAndUnlockTime(name, &ach, &t))) 476 | writeAchievementGet(fd, name, ach ? 1 : 0, t); 477 | else 478 | writeAchievementGet(fd, name, 2, 0); 479 | } // if 480 | break; 481 | 482 | case SHIMCMD_RESETSTATS: 483 | if (buflen) 484 | { 485 | const bool alsoAch = (*(buf++) != 0); 486 | writeResetStats(fd, alsoAch, (GSteamStats) && (GSteamStats->ResetAllStats(alsoAch))); 487 | } // if 488 | break; 489 | 490 | case SHIMCMD_SETSTATI: 491 | if (buflen >= 5) 492 | { 493 | const int32 val = *((int32 *) buf); 494 | buf += sizeof (int32); 495 | const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. 496 | writeSetStatI(fd, name, val, (GSteamStats) && (GSteamStats->SetStat(name, val))); 497 | } // if 498 | break; 499 | 500 | case SHIMCMD_GETSTATI: 501 | if (buflen) 502 | { 503 | const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. 504 | int32 val = 0; 505 | if ((GSteamStats) && (GSteamStats->GetStat(name, &val))) 506 | writeGetStatI(fd, name, val, true); 507 | else 508 | writeGetStatI(fd, name, 0, false); 509 | } // if 510 | break; 511 | 512 | case SHIMCMD_SETSTATF: 513 | if (buflen >= 5) 514 | { 515 | const float val = *((float *) buf); 516 | buf += sizeof (float); 517 | const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. 518 | writeSetStatF(fd, name, val, (GSteamStats) && (GSteamStats->SetStat(name, val))); 519 | } // if 520 | break; 521 | 522 | case SHIMCMD_GETSTATF: 523 | if (buflen) 524 | { 525 | const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible. 526 | float val = 0; 527 | if ((GSteamStats) && (GSteamStats->GetStat(name, &val))) 528 | writeGetStatF(fd, name, val, true); 529 | else 530 | writeGetStatF(fd, name, 0.0f, false); 531 | } // if 532 | break; 533 | } // switch 534 | 535 | return true; // keep going. 536 | } // processCommand 537 | 538 | static void processCommands(PipeType pipeParentRead, PipeType pipeParentWrite) 539 | { 540 | bool quit = false; 541 | uint8 buf[256]; 542 | int br; 543 | 544 | // this read blocks. 545 | while (!quit && ((br = readPipe(pipeParentRead, buf, sizeof (buf))) > 0)) 546 | { 547 | while (br > 0) 548 | { 549 | const int cmdlen = (int) buf[0]; 550 | if ((br-1) >= cmdlen) 551 | { 552 | if (!processCommand(buf+1, cmdlen, pipeParentWrite)) 553 | { 554 | quit = true; 555 | break; 556 | } // if 557 | 558 | br -= cmdlen + 1; 559 | if (br > 0) 560 | memmove(buf, buf+cmdlen+1, br); 561 | } // if 562 | else // get more data. 563 | { 564 | const int morebr = readPipe(pipeParentRead, buf+br, sizeof (buf) - br); 565 | if (morebr <= 0) 566 | { 567 | quit = true; // uhoh. 568 | break; 569 | } // if 570 | br += morebr; 571 | } // else 572 | } // while 573 | } // while 574 | } // processCommands 575 | 576 | static bool setEnvironmentVars(PipeType pipeChildRead, PipeType pipeChildWrite) 577 | { 578 | char buf[64]; 579 | snprintf(buf, sizeof (buf), "%llu", (unsigned long long) pipeChildRead); 580 | if (!setEnvVar("STEAMSHIM_READHANDLE", buf)) 581 | return false; 582 | 583 | snprintf(buf, sizeof (buf), "%llu", (unsigned long long) pipeChildWrite); 584 | if (!setEnvVar("STEAMSHIM_WRITEHANDLE", buf)) 585 | return false; 586 | 587 | return true; 588 | } // setEnvironmentVars 589 | 590 | static bool initSteamworks(PipeType fd) 591 | { 592 | // this can fail for many reasons: 593 | // - you forgot a steam_appid.txt in the current working directory. 594 | // - you don't have Steam running 595 | // - you don't own the game listed in steam_appid.txt 596 | if (!SteamAPI_Init()) 597 | return 0; 598 | 599 | GSteamStats = SteamUserStats(); 600 | GSteamUtils = SteamUtils(); 601 | GSteamUser = SteamUser(); 602 | 603 | GAppID = GSteamUtils ? GSteamUtils->GetAppID() : 0; 604 | GUserID = GSteamUser ? GSteamUser->GetSteamID().ConvertToUint64() : 0; 605 | GSteamBridge = new SteamBridge(fd); 606 | 607 | return 1; 608 | } // initSteamworks 609 | 610 | static void deinitSteamworks(void) 611 | { 612 | SteamAPI_Shutdown(); 613 | delete GSteamBridge; 614 | GSteamBridge = NULL; 615 | GSteamStats = NULL; 616 | GSteamUtils= NULL; 617 | GSteamUser = NULL; 618 | } // deinitSteamworks 619 | 620 | static int mainline(void) 621 | { 622 | PipeType pipeParentRead = NULLPIPE; 623 | PipeType pipeParentWrite = NULLPIPE; 624 | PipeType pipeChildRead = NULLPIPE; 625 | PipeType pipeChildWrite = NULLPIPE; 626 | ProcessType childPid; 627 | 628 | dbgpipe("Parent starting mainline.\n"); 629 | 630 | if (!createPipes(&pipeParentRead, &pipeParentWrite, &pipeChildRead, &pipeChildWrite)) 631 | fail("Failed to create application pipes"); 632 | else if (!initSteamworks(pipeParentWrite)) 633 | fail("Failed to initialize Steamworks"); 634 | else if (!setEnvironmentVars(pipeChildRead, pipeChildWrite)) 635 | fail("Failed to set environment variables"); 636 | else if (!launchChild(&childPid)) 637 | fail("Failed to launch application"); 638 | 639 | // Close the ends of the pipes that the child will use; we don't need them. 640 | closePipe(pipeChildRead); 641 | closePipe(pipeChildWrite); 642 | pipeChildRead = pipeChildWrite = NULLPIPE; 643 | 644 | dbgpipe("Parent in command processing loop.\n"); 645 | 646 | // Now, we block for instructions until the pipe fails (child closed it or 647 | // terminated/crashed). 648 | processCommands(pipeParentRead, pipeParentWrite); 649 | 650 | dbgpipe("Parent shutting down.\n"); 651 | 652 | // Close our ends of the pipes. 653 | writeBye(pipeParentWrite); 654 | closePipe(pipeParentRead); 655 | closePipe(pipeParentWrite); 656 | 657 | deinitSteamworks(); 658 | 659 | dbgpipe("Parent waiting on child process.\n"); 660 | 661 | // Wait for the child to terminate, close the child process handles. 662 | const int retval = closeProcess(&childPid); 663 | 664 | dbgpipe("Parent exiting mainline (child exit code %d).\n", retval); 665 | 666 | return retval; 667 | } // mainline 668 | 669 | // end of steamshim_parent.cpp ... 670 | 671 | --------------------------------------------------------------------------------