mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-03-28 14:56:22 +01:00
Integrate Steamshim
GPL doesn't actually allow direct linking with Steam. Thank god for OneShot.
This commit is contained in:
parent
c4ac1b2ec7
commit
88abd71dfb
14 changed files with 1409 additions and 174 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
39
meson.build
39
meson.build
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
53
src/main.mm
53
src/main.mm
|
@ -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
27
steamshim/LICENSE.txt
Normal 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
66
steamshim/README.txt
Normal 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
444
steamshim/steamshim_child.c
Normal 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 ... */
|
||||
|
54
steamshim/steamshim_child.h
Normal file
54
steamshim/steamshim_child.h
Normal 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 ... */
|
||||
|
674
steamshim/steamshim_parent.cpp
Normal file
674
steamshim/steamshim_parent.cpp
Normal 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 ...
|
||||
|
Loading…
Add table
Reference in a new issue