Integrate Steamshim

GPL doesn't actually allow direct linking with Steam.
Thank god for OneShot.
This commit is contained in:
Struma 2020-03-02 03:52:42 -05:00 committed by Roza
parent c4ac1b2ec7
commit 88abd71dfb
14 changed files with 1409 additions and 174 deletions

View file

@ -92,7 +92,7 @@ void fileIntBindingInit();
void MiniFFIBindingInit();
#endif
#ifdef HAVE_STEAMWORKS
#ifdef HAVE_STEAMSHIM
void CUSLBindingInit();
#endif
@ -142,7 +142,7 @@ static void mriBindingInit() {
MiniFFIBindingInit();
#endif
#ifdef HAVE_STEAMWORKS
#ifdef HAVE_STEAMSHIM
CUSLBindingInit();
#endif

View file

@ -1,12 +1,35 @@
#include "binding-util.h"
#include <steam/isteamapps.h>
#include <steam/isteamuserstats.h>
#include "steamshim_child.h"
// This is a simple implementation of Steam's user stats.
// Basically, a C++ version of this:
// https://github.com/GMMan/RGSS_SteamUserStatsLite
#define STEAMSHIM_GETV(t, v, d) \
while (STEAMSHIM_alive()) { \
const STEAMSHIM_Event *e = STEAMSHIM_pump(); \
if (e && e->type == t) { \
d = e->v; \
break; \
} \
}
// May or may not be expanded in the future.
#define STEAMSHIM_GETV_EXP(t, exp) \
while (STEAMSHIM_alive()) { \
const STEAMSHIM_Event *e = STEAMSHIM_pump(); \
if (e && e->type == t) { \
exp; \
break; \
} \
}
#define STEAMSHIM_GET_OK(t, d) STEAMSHIM_GETV(t, okay, d)
#define STEAMSHIM_GETV_AND_OK(t, v, dst, ok) \
while (STEAMSHIM_alive()) { \
const STEAMSHIM_Event *e = STEAMSHIM_pump(); \
if (e && e->type == t) { \
dst = e->v; \
ok = e->okay; \
break; \
} \
}
RB_METHOD(CUSLSetStat) {
RB_UNUSED_PARAM;
@ -17,10 +40,11 @@ RB_METHOD(CUSLSetStat) {
bool ret;
if (RB_TYPE_P(stat, RUBY_T_FLOAT)) {
ret =
SteamUserStats()->SetStat(RSTRING_PTR(name), (float)RFLOAT_VALUE(stat));
STEAMSHIM_setStatF(RSTRING_PTR(name), (float)RFLOAT_VALUE(stat));
STEAMSHIM_GET_OK(SHIMEVENT_SETSTATF, ret);
} else if (RB_TYPE_P(stat, RUBY_T_FIXNUM)) {
ret = SteamUserStats()->SetStat(RSTRING_PTR(name), (int)NUM2INT(stat));
STEAMSHIM_setStatI(RSTRING_PTR(name), (int)NUM2INT(stat));
STEAMSHIM_GET_OK(SHIMEVENT_SETSTATI, ret);
} else {
rb_raise(rb_eTypeError,
"Statistic value must be either an integer or float.");
@ -28,7 +52,7 @@ RB_METHOD(CUSLSetStat) {
return rb_bool_new(ret);
}
RB_METHOD(CUSLGetStat) {
RB_METHOD(CUSLGetStatI) {
RB_UNUSED_PARAM;
VALUE name;
@ -36,30 +60,34 @@ RB_METHOD(CUSLGetStat) {
SafeStringValue(name);
int resi;
float resf;
bool valid;
if (SteamUserStats()->GetStat(RSTRING_PTR(name), &resi)) {
return INT2NUM(resi);
} else if (SteamUserStats()->GetStat(RSTRING_PTR(name), &resf)) {
return rb_float_new(resf);
} else {
STEAMSHIM_getStatI(RSTRING_PTR(name));
STEAMSHIM_GETV_AND_OK(SHIMEVENT_GETSTATI, ivalue, resi, valid);
if (!valid)
return Qnil;
}
return INT2NUM(resi);
}
RB_METHOD(CUSLUpdateAvgRateStat) {
RB_METHOD(CUSLGetStatF) {
RB_UNUSED_PARAM;
VALUE name, sessioncount, sessionlength;
rb_scan_args(argc, argv, "3", &name, &sessioncount, &sessionlength);
VALUE name;
rb_scan_args(argc, argv, "1", &name);
SafeStringValue(name);
Check_Type(sessioncount, RUBY_T_FLOAT);
Check_Type(sessionlength, RUBY_T_FLOAT);
bool ret = SteamUserStats()->UpdateAvgRateStat(
RSTRING_PTR(name), RFLOAT_VALUE(sessioncount), NUM2DBL(sessionlength));
float resf;
bool valid;
return rb_bool_new(ret);
STEAMSHIM_getStatI(RSTRING_PTR(name));
STEAMSHIM_GETV_AND_OK(SHIMEVENT_GETSTATI, fvalue, resf, valid);
if (!valid)
return Qnil;
return rb_float_new(resf);
}
RB_METHOD(CUSLGetAchievement) {
@ -70,7 +98,10 @@ RB_METHOD(CUSLGetAchievement) {
SafeStringValue(name);
bool ret;
bool valid = SteamUserStats()->GetAchievement(RSTRING_PTR(name), &ret);
bool valid;
STEAMSHIM_getAchievement(RSTRING_PTR(name));
STEAMSHIM_GETV_AND_OK(SHIMEVENT_GETACHIEVEMENT, ivalue, ret, valid);
if (!valid)
return Qnil;
@ -85,7 +116,9 @@ RB_METHOD(CUSLSetAchievement) {
rb_scan_args(argc, argv, "1", &name);
SafeStringValue(name);
bool ret = SteamUserStats()->SetAchievement(RSTRING_PTR(name));
bool ret;
STEAMSHIM_setAchievement(RSTRING_PTR(name), true);
STEAMSHIM_GET_OK(SHIMEVENT_SETACHIEVEMENT, ret);
return rb_bool_new(ret);
}
@ -97,7 +130,9 @@ RB_METHOD(CUSLClearAchievement) {
rb_scan_args(argc, argv, "1", &name);
SafeStringValue(name);
bool ret = SteamUserStats()->ClearAchievement(RSTRING_PTR(name));
bool ret;
STEAMSHIM_setAchievement(RSTRING_PTR(name), false);
STEAMSHIM_GET_OK(SHIMEVENT_SETACHIEVEMENT, ret);
return rb_bool_new(ret);
}
@ -110,10 +145,17 @@ RB_METHOD(CUSLGetAchievementAndUnlockTime) {
SafeStringValue(name);
bool achieved;
unsigned int time;
unsigned long long time;
bool valid;
if (!SteamUserStats()->GetAchievementAndUnlockTime(RSTRING_PTR(name),
&achieved, &time))
STEAMSHIM_getAchievement(RSTRING_PTR(name));
STEAMSHIM_GETV_EXP(SHIMEVENT_GETACHIEVEMENT, {
valid = e->okay;
achieved = e->ivalue;
time = e->epochsecs;
});
if (!valid)
return Qnil;
VALUE ret = rb_ary_new();
@ -124,66 +166,20 @@ RB_METHOD(CUSLGetAchievementAndUnlockTime) {
return ret;
}
VALUE t = rb_funcall(rb_cTime, rb_intern("at"), 1, UINT2NUM(time));
VALUE t = rb_funcall(rb_cTime, rb_intern("at"), 1, ULL2NUM(time));
rb_ary_push(ret, t);
return ret;
}
RB_METHOD(CUSLGetAchievementDisplayAttribute) {
RB_UNUSED_PARAM;
VALUE name, key;
rb_scan_args(argc, argv, "2", &name, &key);
SafeStringValue(name);
SafeStringValue(key);
const char *ret = SteamUserStats()->GetAchievementDisplayAttribute(
RSTRING_PTR(name), RSTRING_PTR(key));
return rb_str_new_cstr(ret);
}
RB_METHOD(CUSLIndicateAchievementProgress) {
RB_UNUSED_PARAM;
VALUE name, progress, max;
rb_scan_args(argc, argv, "3", &name, &progress, &max);
SafeStringValue(name);
bool ret = SteamUserStats()->IndicateAchievementProgress(
RSTRING_PTR(name), NUM2UINT(progress), NUM2UINT(max));
if (ret)
return rb_bool_new(SteamUserStats()->StoreStats());
return rb_bool_new(ret);
}
RB_METHOD(CUSLGetNumAchievements) {
RB_UNUSED_PARAM;
rb_check_argc(argc, 0);
return UINT2NUM(SteamUserStats()->GetNumAchievements());
}
RB_METHOD(CUSLGetAchievementName) {
RB_UNUSED_PARAM;
VALUE index;
rb_scan_args(argc, argv, "1", &index);
return rb_str_new_cstr(SteamUserStats()->GetAchievementName(NUM2UINT(index)));
}
RB_METHOD(CUSLStoreStats) {
RB_UNUSED_PARAM;
rb_check_argc(argc, 0);
bool ok;
return rb_bool_new(SteamUserStats()->StoreStats());
STEAMSHIM_storeStats();
STEAMSHIM_GET_OK(SHIMEVENT_STATSSTORED, ok);
return rb_bool_new(ok);
}
RB_METHOD(CUSLResetAllStats) {
@ -193,53 +189,27 @@ RB_METHOD(CUSLResetAllStats) {
rb_get_args(argc, argv, "b", &achievementsToo);
return rb_bool_new(SteamUserStats()->ResetAllStats(achievementsToo));
}
RB_METHOD(CUSLIsSubscribed) {
RB_UNUSED_PARAM;
rb_check_argc(argc, 0);
return rb_bool_new(SteamApps()->BIsSubscribed());
}
RB_METHOD(CUSLIsDlcInstalled) {
RB_UNUSED_PARAM;
VALUE appid;
rb_scan_args(argc, argv, "1", &appid);
return rb_bool_new(SteamApps()->BIsDlcInstalled(NUM2UINT(appid)));
STEAMSHIM_resetStats(achievementsToo);
STEAMSHIM_GET_OK(SHIMEVENT_RESETSTATS, achievementsToo);
return rb_bool_new(achievementsToo);
}
void CUSLBindingInit() {
SteamUserStats()->RequestCurrentStats();
STEAMSHIM_requestStats();
VALUE mSteamLite = rb_define_module("SteamLite");
_rb_define_module_function(mSteamLite, "subscribed?", CUSLIsSubscribed);
_rb_define_module_function(mSteamLite, "dlc_installed?", CUSLIsDlcInstalled);
_rb_define_module_function(mSteamLite, "get_stat", CUSLGetStat);
_rb_define_module_function(mSteamLite, "get_stat_i", CUSLGetStatI);
_rb_define_module_function(mSteamLite, "get_stat_f", CUSLGetStatF);
_rb_define_module_function(mSteamLite, "set_stat", CUSLSetStat);
_rb_define_module_function(mSteamLite, "update_avg_rate_stat",
CUSLUpdateAvgRateStat);
_rb_define_module_function(mSteamLite, "get_achievement", CUSLGetAchievement);
_rb_define_module_function(mSteamLite, "set_achievement", CUSLSetAchievement);
_rb_define_module_function(mSteamLite, "clear_achievement",
CUSLClearAchievement);
_rb_define_module_function(mSteamLite, "get_achievement_and_unlock_time",
CUSLGetAchievementAndUnlockTime);
_rb_define_module_function(mSteamLite, "get_achievement_display_attribute",
CUSLGetAchievementDisplayAttribute);
_rb_define_module_function(mSteamLite, "indicate_achievement_progress",
CUSLIndicateAchievementProgress);
_rb_define_module_function(mSteamLite, "get_num_achievements",
CUSLGetNumAchievements);
_rb_define_module_function(mSteamLite, "get_achievement_name",
CUSLGetAchievementName);
_rb_define_module_function(mSteamLite, "reset_all_stats", CUSLResetAllStats);
}

View file

@ -1,4 +1,4 @@
project('mkxp-z', 'cpp', 'objc', 'objcpp', version: '1.2.0', meson_version: '>=0.47.0', default_options: ['cpp_std=c++11', 'buildtype=release', 'warning_level=0'])
project('mkxp-z', 'c', 'cpp', 'objc', 'objcpp', version: '1.2.0', meson_version: '>=0.47.0', default_options: ['cpp_std=c++11', 'buildtype=release', 'warning_level=0'])
minimum_macos_version = get_option('macos_min_version')
@ -53,18 +53,9 @@ if steamworks_path != ''
steamlib = compilers['cpp'].find_library(libname, required: false, dirs: [steam_libpath])
if steamlib.found() == true
appid = get_option('steam_appid')
if appid == ''
appid = '0'
endif
if appid == '0'
warning('Steam support is enabled, but steam_appid is not set.')
warning('Please make sure to set the corresponding option in the configuration file.')
endif
global_args += ['-I@0@/public'.format(steamworks_path),
'-DHAVE_STEAMWORKS',
'-DSTEAM_APPID=' + appid]
global_dependencies += steamlib
global_include_dirs += include_directories('steamshim')
global_args += '-DHAVE_STEAMSHIM'
global_sources += 'steamshim/steamshim_child.c'
steamworks = true
endif
endif
@ -160,14 +151,30 @@ elif host_system == 'darwin'
subdir('macos')
add_project_arguments('-stdlib=libc++', language: ['cpp','objcpp'])
add_project_arguments('-std=c++11', language: 'objcpp') # Meson's cpp_std doesn't work on ObjC for some reason
global_args += '-mmacosx-version-min='+minimum_macos_version
global_link_args += '-mmacosx-version-min='+minimum_macos_version
add_project_arguments('-mmacosx-version-min='+minimum_macos_version, language: ['cpp', 'objc', 'objcpp'])
add_project_link_arguments('-mmacosx-version-min='+minimum_macos_version, language: ['cpp', 'objc', 'objcpp'])
else
subdir('linux')
add_project_arguments('-std=c++11', language: 'objcpp')
endif
executable(meson.project_name(),
exe_name = meson.project_name()
if steamworks == true
exe_name = meson.project_name() + '_child'
la = ''
if build_static == true
la = '-static-libgcc -static-libstdc++'
endif
executable(meson.project_name(),
sources: files('steamshim/steamshim_parent.cpp'),
dependencies: steamlib,
include_directories: (steamworks_path + '/public'),
cpp_args: '-DGAME_LAUNCH_NAME="' + exe_name + '"',
link_args: la.split(),
install: (host_system != 'windows'))
endif
executable(exe_name,
sources: global_sources,
dependencies: global_dependencies,
include_directories: global_include_dirs,

View file

@ -19,5 +19,4 @@ option('workdir_current', type: 'boolean', value: false, description: 'Keep curr
option('static_executable', type: 'boolean', value: false, description: 'Build a static executable (Windows-only)')
option('independent_appimage', type: 'boolean', value: true, description: 'Set current directory to the original location of the AppImage when contained within one')
option('steamworks_path', type: 'string', value: '', description: 'Path to Steamshim')
option('steam_appid', type: 'string', value: '0', description: 'Steam application ID.')
option('appimagekit_path', type: 'string', value: '', description: 'Path to AppImageKit, used for building AppImages')

@ -1 +0,0 @@
Subproject commit 67345b80ebb429ecc2aeda94c478b3bcc5f7888e

View file

@ -83,10 +83,6 @@ struct Config {
bool useScriptNames;
#ifdef HAVE_STEAMWORKS
unsigned int steamAppId;
#endif
std::string customScript;
std::vector<std::string> preloadScripts;
std::vector<std::string> rtps;

View file

@ -78,9 +78,6 @@ void Config::read(int argc, char *argv[]) {
@"customScript" : @"",
@"pathCache" : @true,
@"encryptedGraphics" : @false,
#ifdef HAVE_STEAMWORKS
@"steamAppId" : @0,
#endif
@"useScriptNames" : @1,
@"preloadScript" : @[],
@"RTP" : @[],
@ -181,9 +178,6 @@ void Config::read(int argc, char *argv[]) {
glVersion.major = 3;
glVersion.minor = 3;
}
#ifdef HAVE_STEAMWORKS
SET_OPT(steamAppId, uInt32Value);
#endif
}
static void setupScreenSize(Config &conf) {

View file

@ -44,8 +44,8 @@
#include <SDL_timer.h>
#include <SDL_video.h>
#ifdef HAVE_STEAMWORKS
#include "steam/steam_api.h"
#ifdef HAVE_STEAMSHIM
#include "steamshim_child.h"
#endif
#include <algorithm>
@ -586,9 +586,9 @@ void Graphics::update() {
p->checkShutDownReset();
p->checkSyncLock();
#ifdef HAVE_STEAMWORKS
if (SteamAPI_IsSteamRunning())
SteamAPI_RunCallbacks();
#ifdef HAVE_STEAMSHIM
if (STEAMSHIM_alive())
STEAMSHIM_pump();
#endif
if (p->frozen)

View file

@ -46,8 +46,8 @@
#endif
#import <ObjFW/ObjFW.h>
#ifdef HAVE_STEAMWORKS
#import <steam/steam_api.h>
#ifdef HAVE_STEAMSHIM
#import "steamshim_child.h"
#endif
#import "icon.png.xxd"
@ -125,7 +125,8 @@ static void printRgssVersion(int ver) {
const char *const makers[] = {"", "XP", "VX", "VX Ace"};
char buf[128];
snprintf(buf, sizeof(buf), "RGSS version %d (RPG Maker %s)", ver, makers[ver]);
snprintf(buf, sizeof(buf), "RGSS version %d (RPG Maker %s)", ver,
makers[ver]);
Debug() << buf;
}
@ -231,25 +232,10 @@ int main(int argc, char *argv[]) {
conf.readGameINI();
#ifdef HAVE_STEAMWORKS
#if STEAM_APPID == 0
if (!conf.steamAppId) {
showInitError("Steam AppID is not set. The application cannot continue launching.");
SDL_Quit();
return 0;
}
if (SteamAPI_RestartAppIfNecessary(conf.steamAppId))
#else
if (SteamAPI_RestartAppIfNecessary(STEAM_APPID))
#endif
{
SDL_Quit();
return 0;
}
if (!SteamAPI_Init()) {
showInitError("Steamworks failed to initialize.");
#ifdef HAVE_STEAMSHIM
if (!STEAMSHIM_init()) {
showInitError("Failed to initialize Steamworks. The application cannot "
"continue launching.");
SDL_Quit();
return 0;
}
@ -267,6 +253,10 @@ int main(int argc, char *argv[]) {
SDL_GetError());
SDL_Quit();
#ifdef HAVE_STEAMSHIM
STEAMSHIM_deinit();
#endif
return 0;
}
@ -276,6 +266,10 @@ int main(int argc, char *argv[]) {
IMG_Quit();
SDL_Quit();
#ifdef HAVE_STEAMSHIM
STEAMSHIM_deinit();
#endif
return 0;
}
@ -286,6 +280,10 @@ int main(int argc, char *argv[]) {
IMG_Quit();
SDL_Quit();
#ifdef HAVE_STEAMSHIM
STEAMSHIM_deinit();
#endif
return 0;
}
#if defined(__WINDOWS__)
@ -316,6 +314,10 @@ int main(int argc, char *argv[]) {
if (!win) {
showInitError(std::string("Error creating window: ") + SDL_GetError());
#ifdef HAVE_STEAMSHIM
STEAMSHIM_deinit();
#endif
return 0;
}
@ -336,6 +338,9 @@ int main(int argc, char *argv[]) {
IMG_Quit();
SDL_Quit();
#ifdef HAVE_STEAMSHIM
STEAMSHIM_deinit();
#endif
return 0;
}
@ -417,8 +422,8 @@ int main(int argc, char *argv[]) {
WSACleanup();
#endif
#ifdef HAVE_STEAMWORKS
SteamAPI_Shutdown();
#ifdef HAVE_STEAMSHIM
STEAMSHIM_deinit();
#endif
Sound_Quit();
TTF_Quit();

27
steamshim/LICENSE.txt Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2013-2015 Ryan C. Gordon.
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from
the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Ryan C. Gordon <icculus@icculus.org>
(Please note that this project needs access to the Steamworks SDK to be
useful, which is distributed separately and has its own, different license.)

66
steamshim/README.txt Normal file
View file

@ -0,0 +1,66 @@
What is this?
- This is a small piece of code, written in C++, that acts as a bridge between
a child process and the Steamworks SDK. The child process links against a
small piece of code, written in C, to facilitate communication with the
bridge.
What would I ever need this for?
- You have a GPL-licensed game and can't link directly against the Steamworks
SDK. The child process links against the simple, open source C code, which
talks to the open source C++ code via a pipe, which talks to Steamworks. You
can now add Steam achievements to your game without violating the GPL.
How does it work?
- You get a copy of the Steamworks SDK, and link steamshim_parent.cpp against
it. You ship that program and the steam_api.dll (or whatever) with your game.
- The parent process (the C++ code) gets launched as if it were your game. It
initializes Steamworks, creates some pipes and launches your actual game, then
waits on your game to talk to it over those pipes.
- Your game links against steamshim_child.c and at some point #includes
steamshim_child.h to use this functionality.
- Your game calls STEAMSHIM_init() at startup to prepare the system. It can
then make other STEAMSHIM_*() calls to interact with the parent process and
Steamworks.
- Your game, once a frame, calls STEAMSHIM_pump() until it returns NULL. Each
time it doesn't return NULL is a new message (event) from the parent process.
Often times, this is results from a command you asked the parent process to
do earlier, or some other out-of-band event from Steamworks.
- Your game, when shutting down normally, calls STEAMSHIM_deinit(), so the
parent process knows you're going away. It says goodbye, shuts down the pipe,
waits for your game to terminate, and then terminates itself.
Is this all of Steamworks?
- No! It's actually just enough to deal with stats and achievements right now,
but it can definitely be extended to offer more things. Take a look at
How do I get the Steamworks SDK?
- Go to https://partner.steamgames.com/ and login with your Steam account. You
can agree to some terms and then download the SDK.
Is there an example?
- That would be testapp.c. This example expects you to own Postal 1 on Steam and
will RESET ALL YOUR ACHIEVEMENTS, so be careful running it. But hey, if you
lose your work, it's a good exercise in SteamShim usage to put them back
again. :)
Questions? Ask me.
--ryan.
icculus@icculus.org

444
steamshim/steamshim_child.c Normal file
View file

@ -0,0 +1,444 @@
#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 <stdlib.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 "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 ... */

View file

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

View file

@ -0,0 +1,674 @@
//#define GAME_LAUNCH_NAME "testapp"
#ifndef GAME_LAUNCH_NAME
#error Please define your game exe name.
#endif
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN 1
#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"
#define DEBUGPIPE 1
#if DEBUGPIPE
#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.
char buf[350] = {0};
strncpy(buf, GArgv[0], sizeof(buf));
strcat(buf, "_child");
GArgv[0] = buf;
//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 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;
typedef enum ShimEvent
{
SHIMEVENT_BYE,
SHIMEVENT_STATSRECEIVED,
SHIMEVENT_STATSSTORED,
SHIMEVENT_SETACHIEVEMENT,
SHIMEVENT_GETACHIEVEMENT,
SHIMEVENT_RESETSTATS,
SHIMEVENT_SETSTATI,
SHIMEVENT_GETSTATI,
SHIMEVENT_SETSTATF,
SHIMEVENT_GETSTATF,
} 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 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 DEBUGPIPE
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);
#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;
} // 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();
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 ...