integrated steamshim

This commit is contained in:
Mathew Velasquez 2016-02-29 00:34:27 -05:00
parent f44138015b
commit a4de9d9f10
12 changed files with 1356 additions and 147 deletions

7
.gitignore vendored
View file

@ -2,11 +2,12 @@
*.pro.*
*.bak
*.xxd
*.exe
Makefile*
/Makefile*
Game
Game.exe
/oneshot
/steamshim_parent/steamshim
/build

View file

@ -7,21 +7,6 @@
#include "binding-types.h"
#include "debugwriter.h"
#ifdef STEAM
static ID achievementIds[Achievement::MAX];
static Achievement::ID toAchievementId(ID id)
{
/* Crude but effective */
for (int i = 0; i < Achievement::MAX; ++i)
{
if (achievementIds[i] == id)
return static_cast<Achievement::ID>(i);
}
return Achievement::Invalid;
}
#endif
RB_METHOD(steamEnabled)
{
RB_UNUSED_PARAM;
@ -37,15 +22,11 @@ RB_METHOD(steamUnlock)
{
RB_UNUSED_PARAM;
ID rbid;
rb_get_args(argc, argv, "n", &rbid RB_ARG_END);
const char *name;
rb_get_args(argc, argv, "z", &name RB_ARG_END);
#ifdef STEAM
Achievement::ID id = toAchievementId(rbid);
if (id == Achievement::Invalid)
rb_raise(rb_eNameError, "unlocked nonexistent achievement");
else
shState->steam().unlock(id);
shState->steam().unlock(name);
#endif
return Qnil;
}
@ -54,15 +35,11 @@ RB_METHOD(steamLock)
{
RB_UNUSED_PARAM;
ID rbid;
rb_get_args(argc, argv, "n", &rbid RB_ARG_END);
const char *name;
rb_get_args(argc, argv, "z", &name RB_ARG_END);
#ifdef STEAM
Achievement::ID id = toAchievementId(rbid);
if (id == Achievement::Invalid)
rb_raise(rb_eNameError, "locked nonexistent achievement");
else
shState->steam().lock(id);
shState->steam().lock(name);
#endif
return Qnil;
}
@ -71,14 +48,11 @@ RB_METHOD(steamUnlocked)
{
RB_UNUSED_PARAM;
ID rbid;
rb_get_args(argc, argv, "n", &rbid RB_ARG_END);
const char *name;
rb_get_args(argc, argv, "z", &name RB_ARG_END);
#ifdef STEAM
Achievement::ID id = toAchievementId(rbid);
if (id == Achievement::Invalid)
return Qfalse;
return shState->steam().isUnlocked(id) ? Qtrue : Qfalse;
return shState->steam().isUnlocked(name) ? Qtrue : Qfalse;
#else
return Qfalse;
#endif
@ -100,10 +74,4 @@ void steamBindingInit()
_rb_define_module_function(module, "unlock", steamUnlock);
_rb_define_module_function(module, "lock", steamLock);
_rb_define_module_function(module, "unlocked?", steamUnlocked);
#ifdef STEAM
/* Cache achievement ids */
for (int i = 0; i < Achievement::MAX; ++i)
achievementIds[i] = rb_intern(Achievement::names[i]);
#endif
}

View file

@ -2,7 +2,7 @@
TEMPLATE = app
QT =
TARGET = Game
TARGET = oneshot
DEPENDPATH += src shader assets
INCLUDEPATH += . src
@ -15,13 +15,6 @@ isEmpty(BINDING) {
BINDING = MRI
}
isEmpty(STEAMWORKS) {
STEAMWORKS = /home/mathew/Software/Steamworks
}
isEmpty(STEAMARCH) {
STEAMARCH = 64
}
contains(BINDING, MRI) {
contains(_HAVE_BINDING, YES) {
error("Only one binding may be selected")
@ -58,16 +51,10 @@ unix {
CONFIG -= app_bundle
INCLUDEPATH += $$QMAKE_MAC_SDK_PATH/System/Library/Frameworks/OpenAL.framework/Versions/A/Headers
LIBS += -framework OpenAL
STEAM {
LIBS += -L$$STEAMWORKS/redistributable_bin/osx32 -lsteam_api
}
}
!macx: {
PKGCONFIG += openal zlib
INCLUDEPATH += /usr/include/AL /usr/local/include/AL
STEAM {
LIBS += -L$$STEAMWORKS/redistributable_bin/linux$$STEAMARCH -lsteam_api
}
}
}
@ -75,14 +62,6 @@ win32 {
QMAKE_CXXFLAGS += -std=gnu++11
QMAKE_LFLAGS += -std=gnu++11
STEAM {
equals(STEAMARCH, 64) {
LIBS += -L$$STEAMWORKS/redistributable_bin/win64 -lsteam_api64
} else {
LIBS += -L$$STEAMWORKS/redistributable_bin -lsteam_api
}
}
PKGCONFIG += sigc++-2.0 pixman-1 zlib \
sdl2 SDL2_image SDL2_ttf openal SDL_sound vorbisfile freetype2
LIBS += -lphysfs -lsecur32
@ -116,7 +95,6 @@ isEmpty(BOOST_LIB_SUFFIX) {
LIBS += -lboost_program_options$$BOOST_LIB_SUFFIX
STEAM {
INCLUDEPATH += $$STEAMWORKS/public
DEFINES += STEAM
}
@ -226,8 +204,8 @@ SOURCES += \
binding-mri/steam-binding.cpp
STEAM {
HEADERS += src/steam.h
SOURCES += src/steam.cpp
HEADERS += src/steam.h steamshim/steamshim_child.h
SOURCES += src/steam.cpp steamshim/steamshim_child.c
}
EMBED = \

View file

@ -595,6 +595,7 @@ struct GraphicsPrivate
void swapGLBuffer()
{
fpsLimiter.delay();
FBO::unbind();
SDL_GL_SwapWindow(threadData->window);
++frameCount;

View file

@ -43,7 +43,7 @@
#include "icon.png.xxd"
#ifdef STEAM
#include <steam/steam_api.h>
#include "steamshim/steamshim_child.h"
#else
#include "gamecontrollerdb.txt.xxd"
#endif
@ -188,7 +188,7 @@ int main(int argc, char *argv[])
}
#ifdef STEAM
if (!SteamAPI_Init())
if (!STEAMSHIM_init())
{
showInitError("Could not initialize Steamworks API");
return 0;
@ -377,7 +377,7 @@ int main(int argc, char *argv[])
SDL_Quit();
#ifdef STEAM
SteamAPI_Shutdown();
STEAMSHIM_deinit();
#endif
return 0;

View file

@ -1,47 +1,81 @@
#include "steam.h"
#include "debugwriter.h"
#include <steam/steam_api.h>
#include <map>
#include <SDL2/SDL.h>
#include "steamshim/steamshim_child.h"
/* Achievements */
const char *const Achievement::names[Achievement::MAX] = {
static const char *const achievementNames[] = {
"SaveWorld",
"SaveNiko",
};
void Achievement::update()
{
SteamUserStats()->GetAchievement(name, &unlocked);
}
void Achievement::unlock()
{
unlocked = true;
SteamUserStats()->SetAchievement(name);
SteamUserStats()->StoreStats();
}
void Achievement::lock()
{
unlocked = false;
SteamUserStats()->ClearAchievement(name);
SteamUserStats()->StoreStats();
}
#define NUM_ACHIEVEMENTS (sizeof(achievementNames) / sizeof(achievementNames[0]))
/* SteamPrivate */
struct SteamPrivate
{
Achievement achievements[Achievement::MAX];
std::map<std::string, bool> achievements;
int appid;
std::string userName;
SteamPrivate()
: appid(SteamUtils()->GetAppID()),
userName(SteamFriends()->GetPersonaName())
{
for (int i = 0; i < Achievement::MAX; ++i)
achievements[i] = Achievement(Achievement::names[i]);
STEAMSHIM_getPersonaName();
STEAMSHIM_getCurrentGameLanguage();
for (size_t i = 0; i < NUM_ACHIEVEMENTS; ++i)
STEAMSHIM_getAchievement(achievementNames[i]);
while (!initialized())
{
SDL_Delay(100);
update();
}
}
void setAchievement(const char *name, bool set)
{
achievements[name] = set;
STEAMSHIM_setAchievement(name, set);
STEAMSHIM_storeStats();
}
void updateAchievement(const char *name, bool isSet)
{
achievements[name] = isSet;
}
bool isAchievementSet(const char *name)
{
return achievements[name];
}
void update()
{
const STEAMSHIM_Event *e;
while ((e = STEAMSHIM_pump()) != 0)
{
/* Skip erroneous events */
if (!e->okay)
continue;
/* Handle events */
switch (e->type)
{
case SHIMEVENT_GETACHIEVEMENT:
updateAchievement(e->name, e->ivalue);
break;
case SHIMEVENT_GETPERSONANAME:
userName = e->name;
break;
default:
break;
}
}
}
bool initialized()
{
return !userName.empty()
&& achievements.size() == NUM_ACHIEVEMENTS;
}
};
@ -61,17 +95,17 @@ const std::string &Steam::userName() const
return p->userName;
}
void Steam::unlock(Achievement::ID id)
void Steam::unlock(const char *name)
{
p->achievements[id].unlock();
p->setAchievement(name, true);
}
void Steam::lock(Achievement::ID id)
void Steam::lock(const char *name)
{
p->achievements[id].lock();
p->setAchievement(name, false);
}
bool Steam::isUnlocked(Achievement::ID id)
bool Steam::isUnlocked(const char *name)
{
return p->achievements[id].isUnlocked();
return p->isAchievementSet(name);
}

View file

@ -5,51 +5,12 @@
struct SteamPrivate;
class Achievement
{
public:
enum ID
{
Invalid = -1,
SaveWorld = 0,
SaveNiko,
MAX
};
static const char *const names[MAX];
void unlock();
void lock();
bool isUnlocked() { return unlocked; }
private:
const char *name;
bool unlocked;
friend struct SteamPrivate;
Achievement()
: name(0),
unlocked(false)
{}
Achievement(const char *name)
: name(name),
unlocked(false)
{
update();
}
void update();
};
class Steam
{
public:
void unlock(Achievement::ID id);
void lock(Achievement::ID id);
bool isUnlocked(Achievement::ID id);
void unlock(const char *name);
void lock(const char *name);
bool isUnlocked(const char *name);
const std::string &userName() const;

469
steamshim/steamshim_child.c Normal file
View file

@ -0,0 +1,469 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
typedef HANDLE PipeType;
#define NULLPIPE NULL
typedef unsigned __int8 uint8;
typedef __int32 int32;
typedef unsigned __int64 uint64;
#else
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
typedef uint8_t uint8;
typedef int32_t int32;
typedef uint64_t uint64;
typedef int PipeType;
#define NULLPIPE -1
#endif
#include <stdlib.h>
#include "steamshim_child.h"
#ifdef STEAMSHIM_DEBUG
#define dbgpipe printf
#else
static inline void dbgpipe(const char *fmt, ...) {}
#endif
static int writePipe(PipeType fd, const void *buf, const unsigned int _len);
static int readPipe(PipeType fd, void *buf, const unsigned int _len);
static void closePipe(PipeType fd);
static char *getEnvVar(const char *key, char *buf, const size_t buflen);
static int pipeReady(PipeType fd);
#ifdef _WIN32
static int pipeReady(PipeType fd)
{
DWORD avail = 0;
return (PeekNamedPipe(fd, NULL, 0, NULL, &avail, NULL) && (avail > 0));
} /* pipeReady */
static int writePipe(PipeType fd, const void *buf, const unsigned int _len)
{
const DWORD len = (DWORD) _len;
DWORD bw = 0;
return ((WriteFile(fd, buf, len, &bw, NULL) != 0) && (bw == len));
} /* writePipe */
static int readPipe(PipeType fd, void *buf, const unsigned int _len)
{
const DWORD len = (DWORD) _len;
DWORD br = 0;
return ReadFile(fd, buf, len, &br, NULL) ? (int) br : -1;
} /* readPipe */
static void closePipe(PipeType fd)
{
CloseHandle(fd);
} /* closePipe */
static char *getEnvVar(const char *key, char *buf, const size_t _buflen)
{
const DWORD buflen = (DWORD) _buflen;
const DWORD rc = GetEnvironmentVariableA(key, buf, buflen);
/* rc doesn't count null char, hence "<". */
return ((rc > 0) && (rc < buflen)) ? NULL : buf;
} /* getEnvVar */
#else
static int pipeReady(PipeType fd)
{
int rc;
struct pollfd pfd = { fd, POLLIN | POLLERR | POLLHUP, 0 };
while (((rc = poll(&pfd, 1, 0)) == -1) && (errno == EINTR)) { /*spin*/ }
return (rc == 1);
} /* pipeReady */
static int writePipe(PipeType fd, const void *buf, const unsigned int _len)
{
const ssize_t len = (ssize_t) _len;
ssize_t bw;
while (((bw = write(fd, buf, len)) == -1) && (errno == EINTR)) { /*spin*/ }
return (bw == len);
} /* writePipe */
static int readPipe(PipeType fd, void *buf, const unsigned int _len)
{
const ssize_t len = (ssize_t) _len;
ssize_t br;
while (((br = read(fd, buf, len)) == -1) && (errno == EINTR)) { /*spin*/ }
return (int) br;
} /* readPipe */
static void closePipe(PipeType fd)
{
close(fd);
} /* closePipe */
static char *getEnvVar(const char *key, char *buf, const size_t buflen)
{
const char *envr = getenv(key);
if (!envr || (strlen(envr) >= buflen))
return NULL;
strcpy(buf, envr);
return buf;
} /* getEnvVar */
#endif
static PipeType GPipeRead = NULLPIPE;
static PipeType GPipeWrite = NULLPIPE;
typedef enum ShimCmd
{
SHIMCMD_BYE,
SHIMCMD_PUMP,
SHIMCMD_REQUESTSTATS,
SHIMCMD_STORESTATS,
SHIMCMD_SETACHIEVEMENT,
SHIMCMD_GETACHIEVEMENT,
SHIMCMD_RESETSTATS,
SHIMCMD_SETSTATI,
SHIMCMD_GETSTATI,
SHIMCMD_SETSTATF,
SHIMCMD_GETSTATF,
SHIMCMD_GETPERSONANAME,
SHIMCMD_GETCURRENTGAMELANGUAGE,
} ShimCmd;
static int write1ByteCmd(const uint8 b1)
{
const uint8 buf[] = { 1, b1 };
return writePipe(GPipeWrite, buf, sizeof (buf));
} /* write1ByteCmd */
static int write2ByteCmd(const uint8 b1, const uint8 b2)
{
const uint8 buf[] = { 2, b1, b2 };
return writePipe(GPipeWrite, buf, sizeof (buf));
} /* write2ByteCmd */
static inline int writeBye(void)
{
dbgpipe("Child sending SHIMCMD_BYE().\n");
return write1ByteCmd(SHIMCMD_BYE);
} // writeBye
static int initPipes(void)
{
char buf[64];
unsigned long long val;
if (!getEnvVar("STEAMSHIM_READHANDLE", buf, sizeof (buf)))
return 0;
else if ((val = strtoull(buf, 0, 10)) == 0)
return 0;
else
GPipeRead = (PipeType) val;
if (!getEnvVar("STEAMSHIM_WRITEHANDLE", buf, sizeof (buf)))
return 0;
else if ((val = strtoull(buf, 0, 10)) == 0)
return 0;
else
GPipeWrite = (PipeType) val;
return ((GPipeRead != NULLPIPE) && (GPipeWrite != NULLPIPE));
} /* initPipes */
int STEAMSHIM_init(void)
{
dbgpipe("Child init start.\n");
if (!initPipes())
{
dbgpipe("Child init failed.\n");
return 0;
} /* if */
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
#endif
dbgpipe("Child init success!\n");
return 1;
} /* STEAMSHIM_init */
void STEAMSHIM_deinit(void)
{
dbgpipe("Child deinit.\n");
if (GPipeWrite != NULLPIPE)
{
writeBye();
closePipe(GPipeWrite);
} /* if */
if (GPipeRead != NULLPIPE)
closePipe(GPipeRead);
GPipeRead = GPipeWrite = NULLPIPE;
#ifndef _WIN32
signal(SIGPIPE, SIG_DFL);
#endif
} /* STEAMSHIM_deinit */
static inline int isAlive(void)
{
return ((GPipeRead != NULLPIPE) && (GPipeWrite != NULLPIPE));
} /* isAlive */
static inline int isDead(void)
{
return !isAlive();
} /* isDead */
int STEAMSHIM_alive(void)
{
return isAlive();
} /* STEAMSHIM_alive */
static const STEAMSHIM_Event *processEvent(const uint8 *buf, size_t buflen)
{
static STEAMSHIM_Event event;
const STEAMSHIM_EventType type = (STEAMSHIM_EventType) *(buf++);
buflen--;
memset(&event, '\0', sizeof (event));
event.type = type;
event.okay = 1;
#ifdef STEAMSHIM_DEBUG
if (0) {}
#define PRINTGOTEVENT(x) else if (type == x) printf("Child got " #x ".\n")
PRINTGOTEVENT(SHIMEVENT_BYE);
PRINTGOTEVENT(SHIMEVENT_STATSRECEIVED);
PRINTGOTEVENT(SHIMEVENT_STATSSTORED);
PRINTGOTEVENT(SHIMEVENT_SETACHIEVEMENT);
PRINTGOTEVENT(SHIMEVENT_GETACHIEVEMENT);
PRINTGOTEVENT(SHIMEVENT_RESETSTATS);
PRINTGOTEVENT(SHIMEVENT_SETSTATI);
PRINTGOTEVENT(SHIMEVENT_GETSTATI);
PRINTGOTEVENT(SHIMEVENT_SETSTATF);
PRINTGOTEVENT(SHIMEVENT_GETSTATF);
PRINTGOTEVENT(SHIMEVENT_GETPERSONANAME);
PRINTGOTEVENT(SHIMEVENT_GETCURRENTGAMELANGUAGE);
#undef PRINTGOTEVENT
else printf("Child got unknown shimevent %d.\n", (int) type);
#endif
switch (type)
{
case SHIMEVENT_BYE:
break;
case SHIMEVENT_STATSRECEIVED:
case SHIMEVENT_STATSSTORED:
if (!buflen) return NULL;
event.okay = *(buf++) ? 1 : 0;
break;
case SHIMEVENT_SETACHIEVEMENT:
if (buflen < 3) return NULL;
event.ivalue = *(buf++) ? 1 : 0;
event.okay = *(buf++) ? 1 : 0;
strcpy(event.name, (const char *) buf);
break;
case SHIMEVENT_GETACHIEVEMENT:
if (buflen < 10) return NULL;
event.ivalue = (int) *(buf++);
if (event.ivalue == 2)
event.ivalue = event.okay = 0;
event.epochsecs = (long long unsigned) *((uint64 *) buf);
buf += sizeof (uint64);
strcpy(event.name, (const char *) buf);
break;
case SHIMEVENT_RESETSTATS:
if (buflen != 2) return NULL;
event.ivalue = *(buf++) ? 1 : 0;
event.okay = *(buf++) ? 1 : 0;
break;
case SHIMEVENT_SETSTATI:
case SHIMEVENT_GETSTATI:
event.okay = *(buf++) ? 1 : 0;
event.ivalue = (int) *((int32 *) buf);
buf += sizeof (int32);
strcpy(event.name, (const char *) buf);
break;
case SHIMEVENT_SETSTATF:
case SHIMEVENT_GETSTATF:
event.okay = *(buf++) ? 1 : 0;
event.fvalue = (int) *((float *) buf);
buf += sizeof (float);
strcpy(event.name, (const char *) buf);
break;
case SHIMEVENT_GETPERSONANAME:
case SHIMEVENT_GETCURRENTGAMELANGUAGE:
strcpy(event.name, (const char *) buf);
break;
default: /* uh oh */
return NULL;
} /* switch */
return &event;
} /* processEvent */
const STEAMSHIM_Event *STEAMSHIM_pump(void)
{
static uint8 buf[256];
static int br = 0;
int evlen = (br > 0) ? ((int) buf[0]) : 0;
if (isDead())
return NULL;
if (br <= evlen) /* we have an incomplete commmand. Try to read more. */
{
if (pipeReady(GPipeRead))
{
const int morebr = readPipe(GPipeRead, buf + br, sizeof (buf) - br);
if (morebr > 0)
br += morebr;
else /* uh oh */
{
dbgpipe("Child readPipe failed! Shutting down.\n");
STEAMSHIM_deinit(); /* kill it all. */
} /* else */
} /* if */
} /* if */
if (evlen && (br > evlen))
{
const STEAMSHIM_Event *retval = processEvent(buf+1, evlen);
br -= evlen + 1;
if (br > 0)
memmove(buf, buf+evlen+1, br);
return retval;
} /* if */
/* Run Steam event loop. */
if (br == 0)
{
dbgpipe("Child sending SHIMCMD_PUMP().\n");
write1ByteCmd(SHIMCMD_PUMP);
} /* if */
return NULL;
} /* STEAMSHIM_pump */
void STEAMSHIM_requestStats(void)
{
if (isDead()) return;
dbgpipe("Child sending SHIMCMD_REQUESTSTATS().\n");
write1ByteCmd(SHIMCMD_REQUESTSTATS);
} /* STEAMSHIM_requestStats */
void STEAMSHIM_storeStats(void)
{
if (isDead()) return;
dbgpipe("Child sending SHIMCMD_STORESTATS().\n");
write1ByteCmd(SHIMCMD_STORESTATS);
} /* STEAMSHIM_storeStats */
void STEAMSHIM_setAchievement(const char *name, const int enable)
{
uint8 buf[256];
uint8 *ptr = buf+1;
if (isDead()) return;
dbgpipe("Child sending SHIMCMD_SETACHIEVEMENT('%s', %senable).\n", name, enable ? "" : "!");
*(ptr++) = (uint8) SHIMCMD_SETACHIEVEMENT;
*(ptr++) = enable ? 1 : 0;
strcpy((char *) ptr, name);
ptr += strlen(name) + 1;
buf[0] = (uint8) ((ptr-1) - buf);
writePipe(GPipeWrite, buf, buf[0] + 1);
} /* STEAMSHIM_setAchievement */
void STEAMSHIM_getAchievement(const char *name)
{
uint8 buf[256];
uint8 *ptr = buf+1;
if (isDead()) return;
dbgpipe("Child sending SHIMCMD_GETACHIEVEMENT('%s').\n", name);
*(ptr++) = (uint8) SHIMCMD_GETACHIEVEMENT;
strcpy((char *) ptr, name);
ptr += strlen(name) + 1;
buf[0] = (uint8) ((ptr-1) - buf);
writePipe(GPipeWrite, buf, buf[0] + 1);
} /* STEAMSHIM_getAchievement */
void STEAMSHIM_resetStats(const int bAlsoAchievements)
{
if (isDead()) return;
dbgpipe("Child sending SHIMCMD_RESETSTATS(%salsoAchievements).\n", bAlsoAchievements ? "" : "!");
write2ByteCmd(SHIMCMD_RESETSTATS, bAlsoAchievements ? 1 : 0);
} /* STEAMSHIM_resetStats */
static void writeStatThing(const ShimCmd cmd, const char *name, const void *val, const size_t vallen)
{
uint8 buf[256];
uint8 *ptr = buf+1;
if (isDead()) return;
*(ptr++) = (uint8) cmd;
if (vallen)
{
memcpy(ptr, val, vallen);
ptr += vallen;
} /* if */
strcpy((char *) ptr, name);
ptr += strlen(name) + 1;
buf[0] = (uint8) ((ptr-1) - buf);
writePipe(GPipeWrite, buf, buf[0] + 1);
} /* writeStatThing */
void STEAMSHIM_setStatI(const char *name, const int _val)
{
const int32 val = (int32) _val;
dbgpipe("Child sending SHIMCMD_SETSTATI('%s', val %d).\n", name, val);
writeStatThing(SHIMCMD_SETSTATI, name, &val, sizeof (val));
} /* STEAMSHIM_setStatI */
void STEAMSHIM_getStatI(const char *name)
{
dbgpipe("Child sending SHIMCMD_GETSTATI('%s').\n", name);
writeStatThing(SHIMCMD_GETSTATI, name, NULL, 0);
} /* STEAMSHIM_getStatI */
void STEAMSHIM_setStatF(const char *name, const float val)
{
dbgpipe("Child sending SHIMCMD_SETSTATF('%s', val %f).\n", name, val);
writeStatThing(SHIMCMD_SETSTATF, name, &val, sizeof (val));
} /* STEAMSHIM_setStatF */
void STEAMSHIM_getStatF(const char *name)
{
dbgpipe("Child sending SHIMCMD_GETSTATF('%s').\n", name);
writeStatThing(SHIMCMD_GETSTATF, name, NULL, 0);
} /* STEAMSHIM_getStatF */
void STEAMSHIM_getPersonaName()
{
if (isDead()) return;
dbgpipe("Child sending SHIMCMD_GETPERSONANAME().\n");
write1ByteCmd(SHIMCMD_GETPERSONANAME);
} /* STEAMSHIM_getPersonaName */
void STEAMSHIM_getCurrentGameLanguage()
{
if (isDead()) return;
dbgpipe("Child sending SHIMCMD_GETCURRENTGAMELANGUAGE().\n");
write1ByteCmd(SHIMCMD_GETCURRENTGAMELANGUAGE);
} /* STEAMSHIM_getCurrentGameLanguage */
/* end of steamshim_child.c ... */

View file

@ -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 ... */

42
steamshim_parent/Makefile Normal file
View file

@ -0,0 +1,42 @@
TARGET := steamshim
GAME_LAUNCH_NAME ?= oneshot
CXX ?= clang++
WINDRES ?= windres
HOST ?= linux64
FLAGS := -I$(STEAMWORKS)/public -DGAME_LAUNCH_NAME=\"$(GAME_LAUNCH_NAME)\"
ifeq ($(HOST),w32)
FLAGS += -L$(STEAMWORKS)/redistributable_bin -mwindows
else ifeq ($(HOST),linux32)
FLAGS += -L$(STEAMWORKS)/redistributable_bin/linux32 -m32
else ifeq ($(HOST),linux64)
FLAGS += -L$(STEAMWORKS)/redistributable_bin/linux64 -m64
else ifeq ($(HOST),osx)
FLAGS += -L$(STEAMWORKS)/redistributable_bin/osx32
endif
FLAGS += -lsteam_api
ifeq ($(DEBUG),1)
FLAGS += -DSTEAMSHIM_DEBUG
endif
SRC := steamshim_parent.cpp
RES := resources.rc
RESOBJ := resources.o
ifeq ($(HOST),w32)
$(TARGET).exe: $(SRC) $(RESOBJ)
$(CXX) $^ -o $@ $(FLAGS)
$(RESOBJ): $(RES)
$(WINDRES) $< $@
else
$(TARGET): $(SRC)
$(CXX) $^ -o $@ $(FLAGS)
endif
clean:
rm -f $(TARGET) $(TARGET).exe $(RESOBJ)
.PHONY: clean

View file

@ -0,0 +1,2 @@
#include <winuser.h>
MAINICON ICON "../assets/icon.ico"

View file

@ -0,0 +1,696 @@
#ifndef GAME_LAUNCH_NAME
#define GAME_LAUNCH_NAME "oneshot"
#endif
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN 1
#define UNICODE
#include <windows.h>
typedef PROCESS_INFORMATION ProcessType;
typedef HANDLE PipeType;
#define NULLPIPE NULL
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>
typedef pid_t ProcessType;
typedef int PipeType;
#define NULLPIPE -1
#endif
#include "steam/steam_api.h"
#ifdef STEAMSHIM_DEBUG
#define dbgpipe printf
#else
static inline void dbgpipe(const char *fmt, ...) {}
#endif
/* platform-specific mainline calls this. */
static int mainline(void);
/* Windows and Unix implementations of this stuff below. */
static void fail(const char *err);
static bool writePipe(PipeType fd, const void *buf, const unsigned int _len);
static int readPipe(PipeType fd, void *buf, const unsigned int _len);
static bool createPipes(PipeType *pPipeParentRead, PipeType *pPipeParentWrite,
PipeType *pPipeChildRead, PipeType *pPipeChildWrite);
static void closePipe(PipeType fd);
static bool setEnvVar(const char *key, const char *val);
static bool launchChild(ProcessType *pid);
static int closeProcess(ProcessType *pid);
#ifdef _WIN32
static void fail(const char *err)
{
MessageBoxA(NULL, err, "ERROR", MB_ICONERROR | MB_OK);
ExitProcess(1);
} // fail
static bool writePipe(PipeType fd, const void *buf, const unsigned int _len)
{
const DWORD len = (DWORD) _len;
DWORD bw = 0;
return ((WriteFile(fd, buf, len, &bw, NULL) != 0) && (bw == len));
} // writePipe
static int readPipe(PipeType fd, void *buf, const unsigned int _len)
{
const DWORD len = (DWORD) _len;
DWORD br = 0;
return ReadFile(fd, buf, len, &br, NULL) ? (int) br : -1;
} // readPipe
static bool createPipes(PipeType *pPipeParentRead, PipeType *pPipeParentWrite,
PipeType *pPipeChildRead, PipeType *pPipeChildWrite)
{
SECURITY_ATTRIBUTES pipeAttr;
pipeAttr.nLength = sizeof (pipeAttr);
pipeAttr.lpSecurityDescriptor = NULL;
pipeAttr.bInheritHandle = TRUE;
if (!CreatePipe(pPipeParentRead, pPipeChildWrite, &pipeAttr, 0))
return 0;
pipeAttr.nLength = sizeof (pipeAttr);
pipeAttr.lpSecurityDescriptor = NULL;
pipeAttr.bInheritHandle = TRUE;
if (!CreatePipe(pPipeChildRead, pPipeParentWrite, &pipeAttr, 0))
{
CloseHandle(*pPipeParentRead);
CloseHandle(*pPipeChildWrite);
return 0;
} // if
return 1;
} // createPipes
static void closePipe(PipeType fd)
{
CloseHandle(fd);
} // closePipe
static bool setEnvVar(const char *key, const char *val)
{
return (SetEnvironmentVariableA(key, val) != 0);
} // setEnvVar
static bool launchChild(ProcessType *pid)
{
return (CreateProcessW(TEXT(".\\") TEXT(GAME_LAUNCH_NAME) TEXT(".exe"),
GetCommandLineW(), NULL, NULL, TRUE, 0, NULL,
NULL, NULL, pid) != 0);
} // launchChild
static int closeProcess(ProcessType *pid)
{
CloseHandle(pid->hProcess);
CloseHandle(pid->hThread);
return 0;
} // closeProcess
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
mainline();
ExitProcess(0);
return 0; // just in case.
} // WinMain
#else // everyone else that isn't Windows.
static void fail(const char *err)
{
// !!! FIXME: zenity or something.
fprintf(stderr, "%s\n", err);
_exit(1);
} // fail
static bool writePipe(PipeType fd, const void *buf, const unsigned int _len)
{
const ssize_t len = (ssize_t) _len;
ssize_t bw;
while (((bw = write(fd, buf, len)) == -1) && (errno == EINTR)) { /*spin*/ }
return (bw == len);
} // writePipe
static int readPipe(PipeType fd, void *buf, const unsigned int _len)
{
const ssize_t len = (ssize_t) _len;
ssize_t br;
while (((br = read(fd, buf, len)) == -1) && (errno == EINTR)) { /*spin*/ }
return (int) br;
} // readPipe
static bool createPipes(PipeType *pPipeParentRead, PipeType *pPipeParentWrite,
PipeType *pPipeChildRead, PipeType *pPipeChildWrite)
{
int fds[2];
if (pipe(fds) == -1)
return 0;
*pPipeParentRead = fds[0];
*pPipeChildWrite = fds[1];
if (pipe(fds) == -1)
{
close(*pPipeParentRead);
close(*pPipeChildWrite);
return 0;
} // if
*pPipeChildRead = fds[0];
*pPipeParentWrite = fds[1];
return 1;
} // createPipes
static void closePipe(PipeType fd)
{
close(fd);
} // closePipe
static bool setEnvVar(const char *key, const char *val)
{
return (setenv(key, val, 1) != -1);
} // setEnvVar
static int GArgc = 0;
static char **GArgv = NULL;
static bool launchChild(ProcessType *pid)
{
*pid = fork();
if (*pid == -1) // failed
return false;
else if (*pid != 0) // we're the parent
return true; // we'll let the pipe fail if this didn't work.
// we're the child.
GArgv[0] = strdup("./" GAME_LAUNCH_NAME);
execvp(GArgv[0], GArgv);
// still here? It failed! Terminate, closing child's ends of the pipes.
_exit(1);
} // launchChild
static int closeProcess(ProcessType *pid)
{
int rc = 0;
while ((waitpid(*pid, &rc, 0) == -1) && (errno == EINTR)) { /*spin*/ }
if (!WIFEXITED(rc))
return 1; // oh well.
return WEXITSTATUS(rc);
} // closeProcess
int main(int argc, char **argv)
{
signal(SIGPIPE, SIG_IGN);
GArgc = argc;
GArgv = argv;
return mainline();
} // main
#endif
// THE ACTUAL PROGRAM.
class SteamBridge;
static ISteamUserStats *GSteamStats = NULL;
static ISteamUtils *GSteamUtils = NULL;
static ISteamUser *GSteamUser = NULL;
static ISteamFriends *GSteamFriends = NULL;
static ISteamApps *GSteamApps = NULL;
static AppId_t GAppID = 0;
static uint64 GUserID = 0;
static SteamBridge *GSteamBridge = NULL;
class SteamBridge
{
public:
SteamBridge(PipeType _fd);
STEAM_CALLBACK(SteamBridge, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived);
STEAM_CALLBACK(SteamBridge, OnUserStatsStored, UserStatsStored_t, m_CallbackUserStatsStored);
private:
PipeType fd;
};
typedef enum ShimCmd
{
SHIMCMD_BYE,
SHIMCMD_PUMP,
SHIMCMD_REQUESTSTATS,
SHIMCMD_STORESTATS,
SHIMCMD_SETACHIEVEMENT,
SHIMCMD_GETACHIEVEMENT,
SHIMCMD_RESETSTATS,
SHIMCMD_SETSTATI,
SHIMCMD_GETSTATI,
SHIMCMD_SETSTATF,
SHIMCMD_GETSTATF,
SHIMCMD_GETPERSONANAME,
SHIMCMD_GETCURRENTGAMELANGUAGE,
} ShimCmd;
typedef enum ShimEvent
{
SHIMEVENT_BYE,
SHIMEVENT_STATSRECEIVED,
SHIMEVENT_STATSSTORED,
SHIMEVENT_SETACHIEVEMENT,
SHIMEVENT_GETACHIEVEMENT,
SHIMEVENT_RESETSTATS,
SHIMEVENT_SETSTATI,
SHIMEVENT_GETSTATI,
SHIMEVENT_SETSTATF,
SHIMEVENT_GETSTATF,
SHIMEVENT_GETPERSONANAME,
SHIMEVENT_GETCURRENTGAMELANGUAGE,
} ShimEvent;
static bool write1ByteCmd(PipeType fd, const uint8 b1)
{
const uint8 buf[] = { 1, b1 };
return writePipe(fd, buf, sizeof (buf));
} // write1ByteCmd
static bool write2ByteCmd(PipeType fd, const uint8 b1, const uint8 b2)
{
const uint8 buf[] = { 2, b1, b2 };
return writePipe(fd, buf, sizeof (buf));
} // write2ByteCmd
static bool write3ByteCmd(PipeType fd, const uint8 b1, const uint8 b2, const uint8 b3)
{
const uint8 buf[] = { 3, b1, b2, b3 };
return writePipe(fd, buf, sizeof (buf));
} // write3ByteCmd
static bool writeString(PipeType fd, ShimEvent event, const char *str)
{
uint8 buf[256];
buf[0] = strlen(str) + 2;
buf[1] = (uint8) event;
strcpy((char *) buf + 2, str);
return writePipe(fd, buf, buf[0] + 1);
} // writeString
static inline bool writeBye(PipeType fd)
{
dbgpipe("Parent sending SHIMEVENT_BYE().\n");
return write1ByteCmd(fd, SHIMEVENT_BYE);
} // writeBye
static inline bool writeStatsReceived(PipeType fd, const bool okay)
{
dbgpipe("Parent sending SHIMEVENT_STATSRECEIVED(%sokay).\n", okay ? "" : "!");
return write2ByteCmd(fd, SHIMEVENT_STATSRECEIVED, okay ? 1 : 0);
} // writeStatsReceived
static inline bool writeStatsStored(PipeType fd, const bool okay)
{
dbgpipe("Parent sending SHIMEVENT_STATSSTORED(%sokay).\n", okay ? "" : "!");
return write2ByteCmd(fd, SHIMEVENT_STATSSTORED, okay ? 1 : 0);
} // writeStatsStored
static bool writeAchievementSet(PipeType fd, const char *name, const bool enable, const bool okay)
{
uint8 buf[256];
uint8 *ptr = buf+1;
dbgpipe("Parent sending SHIMEVENT_SETACHIEVEMENT('%s', %senable, %sokay).\n", name, enable ? "" : "!", okay ? "" : "!");
*(ptr++) = (uint8) SHIMEVENT_SETACHIEVEMENT;
*(ptr++) = enable ? 1 : 0;
*(ptr++) = okay ? 1 : 0;
strcpy((char *) ptr, name);
ptr += strlen(name) + 1;
buf[0] = (uint8) ((ptr-1) - buf);
return writePipe(fd, buf, buf[0] + 1);
} // writeAchievementSet
static bool writeAchievementGet(PipeType fd, const char *name, const int status, const uint64 time)
{
uint8 buf[256];
uint8 *ptr = buf+1;
dbgpipe("Parent sending SHIMEVENT_GETACHIEVEMENT('%s', status %d, time %llu).\n", name, status, (unsigned long long) time);
*(ptr++) = (uint8) SHIMEVENT_GETACHIEVEMENT;
*(ptr++) = (uint8) status;
memcpy(ptr, &time, sizeof (time));
ptr += sizeof (time);
strcpy((char *) ptr, name);
ptr += strlen(name) + 1;
buf[0] = (uint8) ((ptr-1) - buf);
return writePipe(fd, buf, buf[0] + 1);
} // writeAchievementGet
static inline bool writeResetStats(PipeType fd, const bool alsoAch, const bool okay)
{
dbgpipe("Parent sending SHIMEVENT_RESETSTATS(%salsoAchievements, %sokay).\n", alsoAch ? "" : "!", okay ? "" : "!");
return write3ByteCmd(fd, SHIMEVENT_RESETSTATS, alsoAch ? 1 : 0, okay ? 1 : 0);
} // writeResetStats
static bool writeStatThing(PipeType fd, const ShimEvent ev, const char *name, const void *val, const size_t vallen, const bool okay)
{
uint8 buf[256];
uint8 *ptr = buf+1;
*(ptr++) = (uint8) ev;
*(ptr++) = okay ? 1 : 0;
memcpy(ptr, val, vallen);
ptr += vallen;
strcpy((char *) ptr, name);
ptr += strlen(name) + 1;
buf[0] = (uint8) ((ptr-1) - buf);
return writePipe(fd, buf, buf[0] + 1);
} // writeStatThing
static inline bool writeSetStatI(PipeType fd, const char *name, const int32 val, const bool okay)
{
dbgpipe("Parent sending SHIMEVENT_SETSTATI('%s', val %d, %sokay).\n", name, (int) val, okay ? "" : "!");
return writeStatThing(fd, SHIMEVENT_SETSTATI, name, &val, sizeof (val), okay);
} // writeSetStatI
static inline bool writeSetStatF(PipeType fd, const char *name, const float val, const bool okay)
{
dbgpipe("Parent sending SHIMEVENT_SETSTATF('%s', val %f, %sokay).\n", name, val, okay ? "" : "!");
return writeStatThing(fd, SHIMEVENT_SETSTATF, name, &val, sizeof (val), okay);
} // writeSetStatF
static inline bool writeGetStatI(PipeType fd, const char *name, const int32 val, const bool okay)
{
dbgpipe("Parent sending SHIMEVENT_GETSTATI('%s', val %d, %sokay).\n", name, (int) val, okay ? "" : "!");
return writeStatThing(fd, SHIMEVENT_GETSTATI, name, &val, sizeof (val), okay);
} // writeGetStatI
static inline bool writeGetStatF(PipeType fd, const char *name, const float val, const bool okay)
{
dbgpipe("Parent sending SHIMEVENT_GETSTATF('%s', val %f, %sokay).\n", name, val, okay ? "" : "!");
return writeStatThing(fd, SHIMEVENT_GETSTATF, name, &val, sizeof (val), okay);
} // writeGetStatF
SteamBridge::SteamBridge(PipeType _fd)
: m_CallbackUserStatsReceived( this, &SteamBridge::OnUserStatsReceived )
, m_CallbackUserStatsStored( this, &SteamBridge::OnUserStatsStored )
, fd(_fd)
{
} // SteamBridge::SteamBridge
void SteamBridge::OnUserStatsReceived(UserStatsReceived_t *pCallback)
{
if (GAppID != pCallback->m_nGameID) return;
if (GUserID != pCallback->m_steamIDUser.ConvertToUint64()) return;
writeStatsReceived(fd, pCallback->m_eResult == k_EResultOK);
} // SteamBridge::OnUserStatsReceived
void SteamBridge::OnUserStatsStored(UserStatsStored_t *pCallback)
{
if (GAppID != pCallback->m_nGameID) return;
writeStatsStored(fd, pCallback->m_eResult == k_EResultOK);
} // SteamBridge::OnUserStatsStored
static bool processCommand(const uint8 *buf, unsigned int buflen, PipeType fd)
{
if (buflen == 0)
return true;
const ShimCmd cmd = (ShimCmd) *(buf++);
buflen--;
#if STEAMSHIM_DEBUG
if (false) {}
#define PRINTGOTCMD(x) else if (cmd == x) printf("Parent got " #x ".\n")
PRINTGOTCMD(SHIMCMD_BYE);
PRINTGOTCMD(SHIMCMD_PUMP);
PRINTGOTCMD(SHIMCMD_REQUESTSTATS);
PRINTGOTCMD(SHIMCMD_STORESTATS);
PRINTGOTCMD(SHIMCMD_SETACHIEVEMENT);
PRINTGOTCMD(SHIMCMD_GETACHIEVEMENT);
PRINTGOTCMD(SHIMCMD_RESETSTATS);
PRINTGOTCMD(SHIMCMD_SETSTATI);
PRINTGOTCMD(SHIMCMD_GETSTATI);
PRINTGOTCMD(SHIMCMD_SETSTATF);
PRINTGOTCMD(SHIMCMD_GETSTATF);
PRINTGOTCMD(SHIMCMD_GETPERSONANAME);
PRINTGOTCMD(SHIMCMD_GETCURRENTGAMELANGUAGE);
#undef PRINTGOTCMD
else printf("Parent got unknown shimcmd %d.\n", (int) cmd);
#endif
switch (cmd)
{
case SHIMCMD_PUMP:
SteamAPI_RunCallbacks();
break;
case SHIMCMD_BYE:
writeBye(fd);
return false;
case SHIMCMD_REQUESTSTATS:
if ((!GSteamStats) || (!GSteamStats->RequestCurrentStats()))
writeStatsReceived(fd, false);
// callback later.
break;
case SHIMCMD_STORESTATS:
if ((!GSteamStats) || (!GSteamStats->StoreStats()))
writeStatsStored(fd, false);
// callback later.
break;
case SHIMCMD_SETACHIEVEMENT:
if (buflen >= 2)
{
const bool enable = (*(buf++) != 0);
const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible.
if (!GSteamStats)
writeAchievementSet(fd, name, enable, false);
else if (enable && !GSteamStats->SetAchievement(name))
writeAchievementSet(fd, name, enable, false);
else if (!enable && !GSteamStats->ClearAchievement(name))
writeAchievementSet(fd, name, enable, false);
else
writeAchievementSet(fd, name, enable, true);
} // if
break;
case SHIMCMD_GETACHIEVEMENT:
if (buflen)
{
const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible.
bool ach = false;
uint32 t = 0;
if ((GSteamStats) && (GSteamStats->GetAchievementAndUnlockTime(name, &ach, &t)))
writeAchievementGet(fd, name, ach ? 1 : 0, t);
else
writeAchievementGet(fd, name, 2, 0);
} // if
break;
case SHIMCMD_RESETSTATS:
if (buflen)
{
const bool alsoAch = (*(buf++) != 0);
writeResetStats(fd, alsoAch, (GSteamStats) && (GSteamStats->ResetAllStats(alsoAch)));
} // if
break;
case SHIMCMD_SETSTATI:
if (buflen >= 5)
{
const int32 val = *((int32 *) buf);
buf += sizeof (int32);
const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible.
writeSetStatI(fd, name, val, (GSteamStats) && (GSteamStats->SetStat(name, val)));
} // if
break;
case SHIMCMD_GETSTATI:
if (buflen)
{
const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible.
int32 val = 0;
if ((GSteamStats) && (GSteamStats->GetStat(name, &val)))
writeGetStatI(fd, name, val, true);
else
writeGetStatI(fd, name, 0, false);
} // if
break;
case SHIMCMD_SETSTATF:
if (buflen >= 5)
{
const float val = *((float *) buf);
buf += sizeof (float);
const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible.
writeSetStatF(fd, name, val, (GSteamStats) && (GSteamStats->SetStat(name, val)));
} // if
break;
case SHIMCMD_GETSTATF:
if (buflen)
{
const char *name = (const char *) buf; // !!! FIXME: buffer overflow possible.
float val = 0;
if ((GSteamStats) && (GSteamStats->GetStat(name, &val)))
writeGetStatF(fd, name, val, true);
else
writeGetStatF(fd, name, 0.0f, false);
} // if
break;
case SHIMCMD_GETPERSONANAME:
dbgpipe("Parent sending SHIMEVENT_GETPERSONANAME.\n");
writeString(fd, SHIMEVENT_GETPERSONANAME, GSteamFriends->GetPersonaName());
break;
case SHIMCMD_GETCURRENTGAMELANGUAGE:
dbgpipe("Parent sending SHIMEVENT_GETCURRENTGAMELANGUAGE.\n");
writeString(fd, SHIMEVENT_GETCURRENTGAMELANGUAGE, GSteamApps->GetCurrentGameLanguage());
break;
} // switch
return true; // keep going.
} // processCommand
static void processCommands(PipeType pipeParentRead, PipeType pipeParentWrite)
{
bool quit = false;
uint8 buf[256];
int br;
// this read blocks.
while (!quit && ((br = readPipe(pipeParentRead, buf, sizeof (buf))) > 0))
{
while (br > 0)
{
const int cmdlen = (int) buf[0];
if ((br-1) >= cmdlen)
{
if (!processCommand(buf+1, cmdlen, pipeParentWrite))
{
quit = true;
break;
} // if
br -= cmdlen + 1;
if (br > 0)
memmove(buf, buf+cmdlen+1, br);
} // if
else // get more data.
{
const int morebr = readPipe(pipeParentRead, buf+br, sizeof (buf) - br);
if (morebr <= 0)
{
quit = true; // uhoh.
break;
} // if
br += morebr;
} // else
} // while
} // while
} // processCommands
static bool setEnvironmentVars(PipeType pipeChildRead, PipeType pipeChildWrite)
{
char buf[64];
snprintf(buf, sizeof (buf), "%llu", (unsigned long long) pipeChildRead);
if (!setEnvVar("STEAMSHIM_READHANDLE", buf))
return false;
snprintf(buf, sizeof (buf), "%llu", (unsigned long long) pipeChildWrite);
if (!setEnvVar("STEAMSHIM_WRITEHANDLE", buf))
return false;
return true;
} // setEnvironmentVars
static bool initSteamworks(PipeType fd)
{
// this can fail for many reasons:
// - you forgot a steam_appid.txt in the current working directory.
// - you don't have Steam running
// - you don't own the game listed in steam_appid.txt
if (!SteamAPI_Init())
return 0;
GSteamStats = SteamUserStats();
GSteamUtils = SteamUtils();
GSteamUser = SteamUser();
GSteamFriends = SteamFriends();
GSteamApps = SteamApps();
GAppID = GSteamUtils ? GSteamUtils->GetAppID() : 0;
GUserID = GSteamUser ? GSteamUser->GetSteamID().ConvertToUint64() : 0;
GSteamBridge = new SteamBridge(fd);
return 1;
} // initSteamworks
static void deinitSteamworks(void)
{
SteamAPI_Shutdown();
delete GSteamBridge;
GSteamBridge = NULL;
GSteamStats = NULL;
GSteamUtils= NULL;
GSteamUser = NULL;
} // deinitSteamworks
static int mainline(void)
{
PipeType pipeParentRead = NULLPIPE;
PipeType pipeParentWrite = NULLPIPE;
PipeType pipeChildRead = NULLPIPE;
PipeType pipeChildWrite = NULLPIPE;
ProcessType childPid;
dbgpipe("Parent starting mainline.\n");
if (!createPipes(&pipeParentRead, &pipeParentWrite, &pipeChildRead, &pipeChildWrite))
fail("Failed to create application pipes");
else if (!initSteamworks(pipeParentWrite))
fail("Failed to initialize Steamworks");
else if (!setEnvironmentVars(pipeChildRead, pipeChildWrite))
fail("Failed to set environment variables");
else if (!launchChild(&childPid))
fail("Failed to launch application");
// Close the ends of the pipes that the child will use; we don't need them.
closePipe(pipeChildRead);
closePipe(pipeChildWrite);
pipeChildRead = pipeChildWrite = NULLPIPE;
dbgpipe("Parent in command processing loop.\n");
// Now, we block for instructions until the pipe fails (child closed it or
// terminated/crashed).
processCommands(pipeParentRead, pipeParentWrite);
dbgpipe("Parent shutting down.\n");
// Close our ends of the pipes.
writeBye(pipeParentWrite);
closePipe(pipeParentRead);
closePipe(pipeParentWrite);
deinitSteamworks();
dbgpipe("Parent waiting on child process.\n");
// Wait for the child to terminate, close the child process handles.
const int retval = closeProcess(&childPid);
dbgpipe("Parent exiting mainline (child exit code %d).\n", retval);
return retval;
} // mainline
// end of steamshim_parent.cpp ...