diff --git a/examples/steam_example.js b/examples/steam_example.js new file mode 100644 index 00000000..0aec025d --- /dev/null +++ b/examples/steam_example.js @@ -0,0 +1,187 @@ +// Steam Integration Example +// This example shows how to use Steam achievements and stats + +var steam = use("steam"); + +// Achievement names (these should match your Steam app configuration) +var ACHIEVEMENTS = { + FIRST_WIN: "ACH_FIRST_WIN", + PLAY_10_GAMES: "ACH_PLAY_10_GAMES", + HIGH_SCORE: "ACH_HIGH_SCORE_1000" +}; + +// Stat names +var STATS = { + GAMES_PLAYED: "stat_games_played", + TOTAL_SCORE: "stat_total_score", + PLAY_TIME: "stat_play_time" +}; + +var steam_available = false; +var stats_loaded = false; + +// Initialize Steam +function init_steam() { + if (!steam) { + console.log("Steam module not available"); + return false; + } + + console.log("Initializing Steam..."); + steam_available = steam.steam_init(); + + if (steam_available) { + console.log("Steam initialized successfully"); + + // Request current stats/achievements + if (steam.stats.stats_request()) { + console.log("Stats requested"); + stats_loaded = true; + } + } else { + console.log("Failed to initialize Steam"); + } + + return steam_available; +} + +// Update Steam (call this regularly, e.g., once per frame) +function update_steam() { + if (steam_available) { + steam.steam_run_callbacks(); + } +} + +// Unlock an achievement +function unlock_achievement(achievement_name) { + if (!steam_available || !stats_loaded) return false; + + // Check if already unlocked + var unlocked = steam.achievement.achievement_get(achievement_name); + if (unlocked) { + console.log("Achievement already unlocked:", achievement_name); + return true; + } + + // Unlock it + if (steam.achievement.achievement_set(achievement_name)) { + console.log("Achievement unlocked:", achievement_name); + + // Store stats to make it permanent + steam.stats.stats_store(); + return true; + } + + return false; +} + +// Update a stat +function update_stat(stat_name, value, is_float) { + if (!steam_available || !stats_loaded) return false; + + var success; + if (is_float) { + success = steam.stats.stats_set_float(stat_name, value); + } else { + success = steam.stats.stats_set_int(stat_name, value); + } + + if (success) { + console.log("Stat updated:", stat_name, "=", value); + steam.stats.stats_store(); + } + + return success; +} + +// Get a stat value +function get_stat(stat_name, is_float) { + if (!steam_available || !stats_loaded) return 0; + + if (is_float) { + return steam.stats.stats_get_float(stat_name) || 0; + } else { + return steam.stats.stats_get_int(stat_name) || 0; + } +} + +// Example game logic +var games_played = 0; +var total_score = 0; +var current_score = 0; + +function start_game() { + games_played = get_stat(STATS.GAMES_PLAYED, false); + total_score = get_stat(STATS.TOTAL_SCORE, false); + current_score = 0; + + console.log("Starting game #" + (games_played + 1)); +} + +function end_game(score) { + current_score = score; + games_played++; + total_score += score; + + // Update stats + update_stat(STATS.GAMES_PLAYED, games_played, false); + update_stat(STATS.TOTAL_SCORE, total_score, false); + + // Check for achievements + if (games_played === 1) { + unlock_achievement(ACHIEVEMENTS.FIRST_WIN); + } + + if (games_played >= 10) { + unlock_achievement(ACHIEVEMENTS.PLAY_10_GAMES); + } + + if (score >= 1000) { + unlock_achievement(ACHIEVEMENTS.HIGH_SCORE); + } +} + +// Cloud save example +function save_to_cloud(save_data) { + if (!steam_available) return false; + + var json_data = JSON.stringify(save_data); + return steam.cloud.cloud_write("savegame.json", json_data); +} + +function load_from_cloud() { + if (!steam_available) return null; + + var data = steam.cloud.cloud_read("savegame.json"); + if (data) { + // Convert ArrayBuffer to string + var decoder = new TextDecoder(); + var json_str = decoder.decode(data); + return JSON.parse(json_str); + } + + return null; +} + +// Cleanup +function cleanup_steam() { + if (steam_available) { + steam.steam_shutdown(); + console.log("Steam shut down"); + } +} + +// Export the API +module.exports = { + init: init_steam, + update: update_steam, + cleanup: cleanup_steam, + unlock_achievement: unlock_achievement, + update_stat: update_stat, + get_stat: get_stat, + start_game: start_game, + end_game: end_game, + save_to_cloud: save_to_cloud, + load_from_cloud: load_from_cloud, + is_available: function() { return steam_available; } +}; \ No newline at end of file diff --git a/meson.build b/meson.build index b8d573b2..5693409c 100644 --- a/meson.build +++ b/meson.build @@ -8,6 +8,8 @@ libtype = get_option('default_library') link = [] src = [] +fs = import('fs') + add_project_arguments('-pedantic', language: ['c']) git_tag_cmd = run_command('git', 'describe', '--tags', '--abbrev=0', check: false) @@ -157,6 +159,37 @@ endif deps += dependency('soloud', static:true) deps += dependency('libqrencode', static:true) +# Storefront SDK support +storefront = get_option('storefront') +if storefront == 'steam' + steam_sdk_path = meson.current_source_dir() / 'sdk' + + if host_machine.system() == 'darwin' + steam_lib_path = steam_sdk_path / 'redistributable_bin' / 'osx' / 'libsteam_api.dylib' + elif host_machine.system() == 'linux' + steam_lib_path = steam_sdk_path / 'redistributable_bin' / 'linux64' / 'libsteam_api.so' + elif host_machine.system() == 'windows' + steam_lib_path = steam_sdk_path / 'redistributable_bin' / 'win64' / 'steam_api64.lib' + else + steam_lib_path = '' + endif + + if fs.exists(steam_lib_path) + steam_dep = declare_dependency( + include_directories: include_directories('sdk/public'), + link_args: [steam_lib_path] + ) + deps += steam_dep + src += 'qjs_steam.cpp' + message('Steam SDK enabled') + else + error('Steam SDK required but not found at: ' + steam_lib_path) + endif +else + add_project_arguments('-DNSTEAM', language: ['c', 'cpp']) + message('Storefront: ' + storefront) +endif + link_args = link sources = [] src += [ diff --git a/source/jsffi.c b/source/jsffi.c index 0df5e08c..c52d7205 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -48,6 +48,9 @@ #include "qjs_spline.h" #include "qjs_js.h" #include "qjs_debug.h" +#ifndef NSTEAM +#include "qjs_steam.h" +#endif SDL_Window *global_window; @@ -1041,6 +1044,9 @@ JS_FreeValue(js,v); \ } \ JSC_CCALL(os_engine_start, + if (!SDL_Init(SDL_INIT_VIDEO)) + return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError()); + if (SDL_GetCurrentThreadID() != main_thread) return JS_ThrowInternalError(js, "This can only be called from the root actor."); @@ -2870,6 +2876,10 @@ void ffi_load(JSContext *js) arrput(rt->module_registry, MISTLINE(tracy)); #endif +#ifndef NSTEAM + arrput(rt->module_registry, MISTLINE(steam)); +#endif + JSValue globalThis = JS_GetGlobalObject(js); JSValue prosp = JS_NewObject(js); diff --git a/source/qjs_steam.cpp b/source/qjs_steam.cpp new file mode 100644 index 00000000..cfe9b108 --- /dev/null +++ b/source/qjs_steam.cpp @@ -0,0 +1,515 @@ +#ifndef NSTEAM + +extern "C" { +#include "quickjs.h" +#include "qjs_macros.h" +#include "jsffi.h" +} + +#include +#include +#include + +// Steam interface pointers +static ISteamUserStats *steam_stats = NULL; +static ISteamApps *steam_apps = NULL; +static ISteamRemoteStorage *steam_remote = NULL; +static ISteamUGC *steam_ugc = NULL; +static ISteamUser *steam_user = NULL; +static ISteamFriends *steam_friends = NULL; +static ISteamUtils *steam_utils = NULL; +static bool steam_initialized = false; + +// STEAM INITIALIZATION +JSC_CCALL(steam_init, + if (steam_initialized) { + return JS_NewBool(js, true); + } + + SteamErrMsg err; + steam_initialized = SteamAPI_InitEx(&err); + + if (!steam_initialized) { + return JS_ThrowInternalError(js, "Failed to initialize Steam: %s", err); + } + + // Get interface pointers + steam_stats = SteamAPI_SteamUserStats(); + steam_apps = SteamAPI_SteamApps(); + steam_remote = SteamAPI_SteamRemoteStorage(); + steam_ugc = SteamAPI_SteamUGC(); + steam_user = SteamAPI_SteamUser(); + steam_friends = SteamAPI_SteamFriends(); + steam_utils = SteamAPI_SteamUtils(); + + return JS_NewBool(js, steam_initialized); +) + +JSC_CCALL(steam_shutdown, + if (steam_initialized) { + SteamAPI_Shutdown(); + steam_initialized = false; + } + return JS_UNDEFINED; +) + +JSC_CCALL(steam_run_callbacks, + if (steam_initialized) { + SteamAPI_RunCallbacks(); + } + return JS_UNDEFINED; +) + +// USER STATS & ACHIEVEMENTS +JSC_CCALL(stats_request, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + bool success = SteamAPI_ISteamUserStats_RequestCurrentStats(steam_stats); + return JS_NewBool(js, success); +) + +JSC_CCALL(stats_store, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + bool success = SteamAPI_ISteamUserStats_StoreStats(steam_stats); + return JS_NewBool(js, success); +) + +JSC_CCALL(stats_get_int, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + int32 value; + bool success = SteamAPI_ISteamUserStats_GetStatInt32(steam_stats, name, &value); + JS_FreeCString(js, name); + + if (!success) return JS_UNDEFINED; + return JS_NewInt32(js, value); +) + +JSC_CCALL(stats_get_float, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + float value; + bool success = SteamAPI_ISteamUserStats_GetStatFloat(steam_stats, name, &value); + JS_FreeCString(js, name); + + if (!success) return JS_UNDEFINED; + return JS_NewFloat64(js, value); +) + +JSC_CCALL(stats_set_int, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + int32 value; + JS_ToInt32(js, &value, argv[1]); + + bool success = SteamAPI_ISteamUserStats_SetStatInt32(steam_stats, name, value); + JS_FreeCString(js, name); + + return JS_NewBool(js, success); +) + +JSC_CCALL(stats_set_float, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + double dvalue; + JS_ToFloat64(js, &dvalue, argv[1]); + float value = (float)dvalue; + + bool success = SteamAPI_ISteamUserStats_SetStatFloat(steam_stats, name, value); + JS_FreeCString(js, name); + + return JS_NewBool(js, success); +) + +JSC_CCALL(achievement_get, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + bool achieved; + bool success = SteamAPI_ISteamUserStats_GetAchievement(steam_stats, name, &achieved); + JS_FreeCString(js, name); + + if (!success) return JS_UNDEFINED; + return JS_NewBool(js, achieved); +) + +JSC_CCALL(achievement_set, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUserStats_SetAchievement(steam_stats, name); + JS_FreeCString(js, name); + + return JS_NewBool(js, success); +) + +JSC_CCALL(achievement_clear, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUserStats_ClearAchievement(steam_stats, name); + JS_FreeCString(js, name); + + return JS_NewBool(js, success); +) + +JSC_CCALL(achievement_count, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint32 count = SteamAPI_ISteamUserStats_GetNumAchievements(steam_stats); + return JS_NewUint32(js, count); +) + +JSC_CCALL(achievement_name, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint32 index; + JS_ToUint32(js, &index, argv[0]); + + const char *name = SteamAPI_ISteamUserStats_GetAchievementName(steam_stats, index); + if (!name) return JS_UNDEFINED; + + return JS_NewString(js, name); +) + +// APPS +JSC_CCALL(app_id, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + AppId_t appid = SteamAPI_ISteamUtils_GetAppID(steam_utils); + return JS_NewUint32(js, appid); +) + +JSC_CCALL(app_owner, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + uint64_steamid owner = SteamAPI_ISteamApps_GetAppOwner(steam_apps); + return JS_NewBigUint64(js, owner); +) + +JSC_CCALL(app_installed, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + uint32 appid; + JS_ToUint32(js, &appid, argv[0]); + + bool installed = SteamAPI_ISteamApps_BIsAppInstalled(steam_apps, appid); + return JS_NewBool(js, installed); +) + +JSC_CCALL(app_subscribed, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + bool subscribed = SteamAPI_ISteamApps_BIsSubscribed(steam_apps); + return JS_NewBool(js, subscribed); +) + +JSC_CCALL(app_language, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + const char *lang = SteamAPI_ISteamApps_GetCurrentGameLanguage(steam_apps); + return JS_NewString(js, lang ? lang : "english"); +) + +JSC_CCALL(app_dlc_installed, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + AppId_t dlcid; + JS_ToUint32(js, &dlcid, argv[0]); + + bool installed = SteamAPI_ISteamApps_BIsDlcInstalled(steam_apps, dlcid); + return JS_NewBool(js, installed); +) + +// USER +JSC_CCALL(user_logged_on, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + bool logged = SteamAPI_ISteamUser_BLoggedOn(steam_user); + return JS_NewBool(js, logged); +) + +JSC_CCALL(user_steam_id, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + uint64_steamid id = SteamAPI_ISteamUser_GetSteamID(steam_user); + return JS_NewBigUint64(js, id); +) + +JSC_CCALL(user_level, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + int level = SteamAPI_ISteamUser_GetPlayerSteamLevel(steam_user); + return JS_NewInt32(js, level); +) + +// FRIENDS +JSC_CCALL(friends_name, + if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); + + const char *name = SteamAPI_ISteamFriends_GetPersonaName(steam_friends); + return JS_NewString(js, name ? name : ""); +) + +JSC_CCALL(friends_state, + if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); + + EPersonaState state = SteamAPI_ISteamFriends_GetPersonaState(steam_friends); + const char *state_str; + switch(state) { + case k_EPersonaStateOffline: state_str = "offline"; break; + case k_EPersonaStateOnline: state_str = "online"; break; + case k_EPersonaStateBusy: state_str = "busy"; break; + case k_EPersonaStateAway: state_str = "away"; break; + case k_EPersonaStateSnooze: state_str = "snooze"; break; + case k_EPersonaStateLookingToTrade: state_str = "trade"; break; + case k_EPersonaStateLookingToPlay: state_str = "play"; break; + default: state_str = "unknown"; break; + } + return JS_NewString(js, state_str); +) + +// CLOUD STORAGE +JSC_CCALL(cloud_enabled_app, + if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized"); + + bool enabled = SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp(steam_remote); + return JS_NewBool(js, enabled); +) + +JSC_CCALL(cloud_enabled_account, + if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized"); + + bool enabled = SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount(steam_remote); + return JS_NewBool(js, enabled); +) + +JSC_CCALL(cloud_enable, + if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized"); + + bool enable = JS_ToBool(js, argv[0]); + SteamAPI_ISteamRemoteStorage_SetCloudEnabledForApp(steam_remote, enable); + return JS_UNDEFINED; +) + +JSC_CCALL(cloud_quota, + if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized"); + + uint64 total, available; + bool success = SteamAPI_ISteamRemoteStorage_GetQuota(steam_remote, &total, &available); + + if (!success) return JS_UNDEFINED; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "total", JS_NewBigUint64(js, total)); + JS_SetPropertyStr(js, obj, "available", JS_NewBigUint64(js, available)); + return obj; +) + +JSC_CCALL(cloud_write, + if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized"); + + const char *filename = JS_ToCString(js, argv[0]); + if (!filename) return JS_EXCEPTION; + + size_t data_len; + uint8_t *data = NULL; + + if (JS_IsString(argv[1])) { + const char *str = JS_ToCStringLen(js, &data_len, argv[1]); + if (!str) { + JS_FreeCString(js, filename); + return JS_EXCEPTION; + } + data = (uint8_t*)str; + } else { + data = JS_GetArrayBuffer(js, &data_len, argv[1]); + if (!data) { + JS_FreeCString(js, filename); + return JS_ThrowTypeError(js, "Second argument must be string or ArrayBuffer"); + } + } + + bool success = SteamAPI_ISteamRemoteStorage_FileWrite(steam_remote, filename, data, data_len); + + if (JS_IsString(argv[1])) { + JS_FreeCString(js, (const char*)data); + } + JS_FreeCString(js, filename); + + return JS_NewBool(js, success); +) + +JSC_CCALL(cloud_read, + if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized"); + + const char *filename = JS_ToCString(js, argv[0]); + if (!filename) return JS_EXCEPTION; + + int32 size = SteamAPI_ISteamRemoteStorage_GetFileSize(steam_remote, filename); + if (size <= 0) { + JS_FreeCString(js, filename); + return JS_UNDEFINED; + } + + uint8_t *buffer = (uint8_t*)js_malloc(js, size); + if (!buffer) { + JS_FreeCString(js, filename); + return JS_EXCEPTION; + } + + int32 read = SteamAPI_ISteamRemoteStorage_FileRead(steam_remote, filename, buffer, size); + JS_FreeCString(js, filename); + + if (read != size) { + js_free(js, buffer); + return JS_UNDEFINED; + } + + JSValue result = JS_NewArrayBufferCopy(js, buffer, size); + js_free(js, buffer); + return result; +) + +JSC_CCALL(cloud_delete, + if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized"); + + const char *filename = JS_ToCString(js, argv[0]); + if (!filename) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamRemoteStorage_FileDelete(steam_remote, filename); + JS_FreeCString(js, filename); + + return JS_NewBool(js, success); +) + +JSC_CCALL(cloud_exists, + if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized"); + + const char *filename = JS_ToCString(js, argv[0]); + if (!filename) return JS_EXCEPTION; + + bool exists = SteamAPI_ISteamRemoteStorage_FileExists(steam_remote, filename); + JS_FreeCString(js, filename); + + return JS_NewBool(js, exists); +) + +// Function lists for sub-objects +static const JSCFunctionListEntry js_steam_stats_funcs[] = { + MIST_FUNC_DEF(steam, stats_request, 0), + MIST_FUNC_DEF(steam, stats_store, 0), + MIST_FUNC_DEF(steam, stats_get_int, 1), + MIST_FUNC_DEF(steam, stats_get_float, 1), + MIST_FUNC_DEF(steam, stats_set_int, 2), + MIST_FUNC_DEF(steam, stats_set_float, 2), +}; + +static const JSCFunctionListEntry js_steam_achievement_funcs[] = { + MIST_FUNC_DEF(steam, achievement_get, 1), + MIST_FUNC_DEF(steam, achievement_set, 1), + MIST_FUNC_DEF(steam, achievement_clear, 1), + MIST_FUNC_DEF(steam, achievement_count, 0), + MIST_FUNC_DEF(steam, achievement_name, 1), +}; + +static const JSCFunctionListEntry js_steam_app_funcs[] = { + MIST_FUNC_DEF(steam, app_id, 0), + MIST_FUNC_DEF(steam, app_owner, 0), + MIST_FUNC_DEF(steam, app_installed, 1), + MIST_FUNC_DEF(steam, app_subscribed, 0), + MIST_FUNC_DEF(steam, app_language, 0), + MIST_FUNC_DEF(steam, app_dlc_installed, 1), +}; + +static const JSCFunctionListEntry js_steam_user_funcs[] = { + MIST_FUNC_DEF(steam, user_logged_on, 0), + MIST_FUNC_DEF(steam, user_steam_id, 0), + MIST_FUNC_DEF(steam, user_level, 0), +}; + +static const JSCFunctionListEntry js_steam_friends_funcs[] = { + MIST_FUNC_DEF(steam, friends_name, 0), + MIST_FUNC_DEF(steam, friends_state, 0), +}; + +static const JSCFunctionListEntry js_steam_cloud_funcs[] = { + MIST_FUNC_DEF(steam, cloud_enabled_app, 0), + MIST_FUNC_DEF(steam, cloud_enabled_account, 0), + MIST_FUNC_DEF(steam, cloud_enable, 1), + MIST_FUNC_DEF(steam, cloud_quota, 0), + MIST_FUNC_DEF(steam, cloud_write, 2), + MIST_FUNC_DEF(steam, cloud_read, 1), + MIST_FUNC_DEF(steam, cloud_delete, 1), + MIST_FUNC_DEF(steam, cloud_exists, 1), +}; + +// Main Steam API functions +static const JSCFunctionListEntry js_steam_funcs[] = { + MIST_FUNC_DEF(steam, steam_init, 0), + MIST_FUNC_DEF(steam, steam_shutdown, 0), + MIST_FUNC_DEF(steam, steam_run_callbacks, 0), +}; + +extern "C" JSValue js_steam_use(JSContext *js) { + JSValue steam = JS_NewObject(js); + JS_SetPropertyFunctionList(js, steam, js_steam_funcs, countof(js_steam_funcs)); + + // Stats sub-object + JSValue stats = JS_NewObject(js); + JS_SetPropertyFunctionList(js, stats, js_steam_stats_funcs, countof(js_steam_stats_funcs)); + JS_SetPropertyStr(js, steam, "stats", stats); + + // Achievement sub-object + JSValue achievement = JS_NewObject(js); + JS_SetPropertyFunctionList(js, achievement, js_steam_achievement_funcs, countof(js_steam_achievement_funcs)); + JS_SetPropertyStr(js, steam, "achievement", achievement); + + // App sub-object + JSValue app = JS_NewObject(js); + JS_SetPropertyFunctionList(js, app, js_steam_app_funcs, countof(js_steam_app_funcs)); + JS_SetPropertyStr(js, steam, "app", app); + + // User sub-object + JSValue user = JS_NewObject(js); + JS_SetPropertyFunctionList(js, user, js_steam_user_funcs, countof(js_steam_user_funcs)); + JS_SetPropertyStr(js, steam, "user", user); + + // Friends sub-object + JSValue friends = JS_NewObject(js); + JS_SetPropertyFunctionList(js, friends, js_steam_friends_funcs, countof(js_steam_friends_funcs)); + JS_SetPropertyStr(js, steam, "friends", friends); + + // Cloud sub-object + JSValue cloud = JS_NewObject(js); + JS_SetPropertyFunctionList(js, cloud, js_steam_cloud_funcs, countof(js_steam_cloud_funcs)); + JS_SetPropertyStr(js, steam, "cloud", cloud); + + return steam; +} + +#else +// Stub when Steam is disabled +extern "C" JSValue js_steam_use(JSContext *js) { + return JS_UNDEFINED; +} +#endif diff --git a/source/qjs_steam.h b/source/qjs_steam.h new file mode 100644 index 00000000..5ed74b84 --- /dev/null +++ b/source/qjs_steam.h @@ -0,0 +1,8 @@ +#ifndef QJS_STEAM_H +#define QJS_STEAM_H + +#include "quickjs.h" + +JSValue js_steam_use(JSContext *js); + +#endif // QJS_STEAM_H \ No newline at end of file diff --git a/tests/steam.js b/tests/steam.js new file mode 100644 index 00000000..dd7a6db1 --- /dev/null +++ b/tests/steam.js @@ -0,0 +1,40 @@ +var steam = use("steam"); + +console.log("Steam module loaded:", steam); + +if (steam) { + console.log("Steam functions available:"); + console.log("- steam_init:", typeof steam.steam_init); + console.log("- steam_shutdown:", typeof steam.steam_shutdown); + console.log("- steam_run_callbacks:", typeof steam.steam_run_callbacks); + + console.log("\nSteam sub-modules:"); + console.log("- stats:", steam.stats); + console.log("- achievement:", steam.achievement); + console.log("- app:", steam.app); + console.log("- user:", steam.user); + console.log("- friends:", steam.friends); + console.log("- cloud:", steam.cloud); + + // Try to initialize Steam + console.log("\nAttempting to initialize Steam..."); + var init_result = steam.steam_init(); + console.log("Initialization result:", init_result); + + if (init_result) { + // Get some basic info + console.log("\nApp ID:", steam.app.app_id()); + console.log("User logged on:", steam.user.user_logged_on()); + + if (steam.user.user_logged_on()) { + console.log("User name:", steam.friends.friends_name()); + console.log("User state:", steam.friends.friends_state()); + } + + // Shutdown when done + steam.steam_shutdown(); + console.log("Steam shut down"); + } +} else { + console.log("Steam module not available (compiled without Steam support)"); +} \ No newline at end of file