diff --git a/Makefile b/Makefile index 590c68b1..0ed8d71f 100755 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ debug: FORCE meson setup build_dbg -Dbuildtype=debugoptimized meson install --only-changed -C build_dbg + cp build_dbg/cell . && chmod +x cell fast: FORCE meson setup build_fast diff --git a/meson.build b/meson.build index 9d568ba4..3306a5c1 100644 --- a/meson.build +++ b/meson.build @@ -267,8 +267,12 @@ else deps += qr_dep endif -# Storefront SDK support -storefront = get_option('storefront') +# Always build for Steam unless it's Emscripten +if host_machine.system() == 'emscripten' + storefront = 'none' +else + storefront = 'steam' +endif if storefront == 'steam' steam_sdk_path = meson.current_source_dir() / 'sdk' diff --git a/source/cell.c b/source/cell.c index f8a99d1e..8881cadb 100644 --- a/source/cell.c +++ b/source/cell.c @@ -11,6 +11,8 @@ #include #include +#include + #ifdef __APPLE__ #include #include diff --git a/source/cell.h b/source/cell.h index fb769bfa..1d3b1350 100644 --- a/source/cell.h +++ b/source/cell.h @@ -7,12 +7,12 @@ #include "qjs_blob.h" #include "blob.h" -#include - #ifdef __cplusplus extern "C" { #endif +typedef struct mi_heap_s mi_heap_t; + /* Letter type for unified message queue */ typedef enum { LETTER_BLOB, /* Blob message */ diff --git a/source/qjs_blob.h b/source/qjs_blob.h index 2fdf222e..e791fdcb 100644 --- a/source/qjs_blob.h +++ b/source/qjs_blob.h @@ -4,6 +4,10 @@ #include "cell.h" #include "blob.h" +#ifdef __cplusplus +extern "C" { +#endif + JSValue js_blob_use(JSContext *ctx); // makes a new stone blob from data, copying the data over @@ -14,4 +18,8 @@ void *js_get_blob_data(JSContext *js, size_t *size, JSValue v); int js_is_blob(JSContext *js, JSValue v); +#ifdef __cplusplus +} +#endif + #endif diff --git a/source/qjs_steam.cpp b/source/qjs_steam.cpp index bab8875b..5d358a37 100644 --- a/source/qjs_steam.cpp +++ b/source/qjs_steam.cpp @@ -1,14 +1,70 @@ #ifndef NSTEAM -extern "C" { +// Include C headers first to get types #include "qjs_steam.h" #include "jsffi.h" -} +#include "qjs_blob.h" +// C++ headers #include #include #include +// Steam ID wrapper class +static JSClassID js_steamid_id; + +typedef struct { + uint64_t id; +} steamid_t; + +static void js_steamid_finalizer(JSRuntime *rt, JSValue val) { + steamid_t *sid = (steamid_t*)JS_GetOpaque(val, js_steamid_id); + if (sid) js_free_rt(rt, sid); +} + +static JSClassDef js_steamid_class = { + .class_name = "SteamID", + .finalizer = js_steamid_finalizer, +}; + +static steamid_t *js2steamid(JSContext *js, JSValue val) { + if (JS_GetClassID(val) != js_steamid_id) return NULL; + return (steamid_t*)JS_GetOpaque(val, js_steamid_id); +} + +static JSValue steamid2js(JSContext *js, uint64_t id) { + steamid_t *sid = (steamid_t*)js_mallocz(js, sizeof(steamid_t)); + if (!sid) return JS_ThrowOutOfMemory(js); + + sid->id = id; + + JSValue obj = JS_NewObjectClass(js, js_steamid_id); + JS_SetOpaque(obj, sid); + return obj; +} + +static JSValue js_steamid_toString(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + steamid_t *sid = js2steamid(js, self); + if (!sid) return JS_ThrowTypeError(js, "Expected SteamID"); + + char buf[32]; + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)sid->id); + return JS_NewString(js, buf); +} + +static JSValue js_steamid_toNumber(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + steamid_t *sid = js2steamid(js, self); + if (!sid) return JS_ThrowTypeError(js, "Expected SteamID"); + + // Return as a safe JS number (may lose precision for very large IDs) + return JS_NewFloat64(js, (double)sid->id); +} + +static const JSCFunctionListEntry js_steamid_proto_funcs[] = { + JS_CFUNC_DEF("toString", 0, js_steamid_toString), + JS_CFUNC_DEF("toNumber", 0, js_steamid_toNumber), +}; + // Steam interface pointers static ISteamUserStats *steam_stats = NULL; static ISteamApps *steam_apps = NULL; @@ -17,29 +73,83 @@ static ISteamUGC *steam_ugc = NULL; static ISteamUser *steam_user = NULL; static ISteamFriends *steam_friends = NULL; static ISteamUtils *steam_utils = NULL; +static ISteamMatchmaking *steam_matchmaking = NULL; +static ISteamNetworking *steam_networking = NULL; +static ISteamScreenshots *steam_screenshots = NULL; +static ISteamHTTP *steam_http = NULL; +static ISteamInput *steam_input = NULL; +static ISteamInventory *steam_inventory = NULL; +static ISteamMusic *steam_music = NULL; static bool steam_initialized = false; +extern "C" { + // STEAM INITIALIZATION JSC_CCALL(steam_init, if (steam_initialized) { return JS_NewBool(js, true); } - SteamErrMsg err; - steam_initialized = SteamAPI_InitEx(&err); + ESteamAPIInitResult init_result = SteamAPI_InitFlat(nullptr); - if (!steam_initialized) { - return JS_ThrowInternalError(js, "Failed to initialize Steam: %s", err); + switch (init_result) { + case k_ESteamAPIInitResult_OK: + steam_initialized = true; + break; + + case k_ESteamAPIInitResult_NoSteamClient: + return JS_ThrowInternalError(js, "Failed to initialize Steam: Steam client is not running or not logged in"); + + case k_ESteamAPIInitResult_VersionMismatch: + return JS_ThrowInternalError(js, "Failed to initialize Steam: Steam client version is out of date"); + + case k_ESteamAPIInitResult_FailedGeneric: + default: + return JS_ThrowInternalError(js, "Failed to initialize Steam: Generic failure (check steam_appid.txt file exists and contains valid AppID)"); } - // Get interface pointers - steam_stats = SteamAPI_SteamUserStats(); + // Get interface pointers - must be called after SteamAPI_InitFlat succeeds + steam_user = SteamAPI_SteamUser(); + steam_utils = SteamAPI_SteamUtils(); + steam_friends = SteamAPI_SteamFriends(); steam_apps = SteamAPI_SteamApps(); + steam_stats = SteamAPI_SteamUserStats(); steam_remote = SteamAPI_SteamRemoteStorage(); steam_ugc = SteamAPI_SteamUGC(); - steam_user = SteamAPI_SteamUser(); - steam_friends = SteamAPI_SteamFriends(); - steam_utils = SteamAPI_SteamUtils(); + steam_matchmaking = SteamAPI_SteamMatchmaking(); + steam_networking = SteamAPI_SteamNetworking(); + steam_screenshots = SteamAPI_SteamScreenshots(); + steam_http = SteamAPI_SteamHTTP(); + steam_input = SteamAPI_SteamInput(); + steam_inventory = SteamAPI_SteamInventory(); + steam_music = SteamAPI_SteamMusic(); + + + // Verify critical interfaces were obtained + if (!steam_user) { + steam_initialized = false; + SteamAPI_Shutdown(); + return JS_ThrowInternalError(js, "Failed to get Steam user interface"); + } + + if (!steam_utils) { + steam_initialized = false; + SteamAPI_Shutdown(); + return JS_ThrowInternalError(js, "Failed to get Steam utils interface"); + } + + if (!steam_friends) { + steam_initialized = false; + SteamAPI_Shutdown(); + return JS_ThrowInternalError(js, "Failed to get Steam friends interface"); + } + + // Verify user is logged in + if (!SteamAPI_ISteamUser_BLoggedOn(steam_user)) { + steam_initialized = false; + SteamAPI_Shutdown(); + return JS_ThrowInternalError(js, "Steam user is not logged in"); + } return JS_NewBool(js, steam_initialized); ) @@ -63,7 +173,7 @@ JSC_CCALL(steam_run_callbacks, JSC_CCALL(stats_request, if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); - bool success = SteamAPI_ISteamUserStats_RequestCurrentStats(steam_stats); + bool success = SteamAPI_ISteamUserStats_RequestUserStats(steam_stats, SteamAPI_ISteamUser_GetSteamID(steam_user)); return JS_NewBool(js, success); ) @@ -190,6 +300,497 @@ JSC_CCALL(achievement_name, return JS_NewString(js, name); ) +JSC_CCALL(achievement_get_and_unlock_time, + 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; + uint32 unlock_time; + bool success = SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime(steam_stats, name, &achieved, &unlock_time); + JS_FreeCString(js, name); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "achieved", JS_NewBool(js, achieved)); + JS_SetPropertyStr(js, obj, "unlock_time", JS_NewUint32(js, unlock_time)); + return obj; +) + +JSC_CCALL(achievement_get_icon, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + int icon = SteamAPI_ISteamUserStats_GetAchievementIcon(steam_stats, name); + JS_FreeCString(js, name); + + return JS_NewInt32(js, icon); +) + +JSC_CCALL(achievement_get_display_attribute, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + const char *key = JS_ToCString(js, argv[1]); + if (!key) { + JS_FreeCString(js, name); + return JS_EXCEPTION; + } + + const char *value = SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(steam_stats, name, key); + JS_FreeCString(js, name); + JS_FreeCString(js, key); + + return JS_NewString(js, value ? value : ""); +) + +JSC_CCALL(achievement_indicate_progress, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + uint32 cur_progress, max_progress; + JS_ToUint32(js, &cur_progress, argv[1]); + JS_ToUint32(js, &max_progress, argv[2]); + + bool success = SteamAPI_ISteamUserStats_IndicateAchievementProgress(steam_stats, name, cur_progress, max_progress); + JS_FreeCString(js, name); + + return JS_NewBool(js, success); +) + +JSC_CCALL(achievement_get_user_achievement, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + const char *name = JS_ToCString(js, argv[1]); + if (!name) return JS_EXCEPTION; + + bool achieved; + bool success = SteamAPI_ISteamUserStats_GetUserAchievement(steam_stats, sid->id, name, &achieved); + JS_FreeCString(js, name); + + if (!success) return JS_NULL; + return JS_NewBool(js, achieved); +) + +JSC_CCALL(achievement_get_user_achievement_and_unlock_time, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + const char *name = JS_ToCString(js, argv[1]); + if (!name) return JS_EXCEPTION; + + bool achieved; + uint32 unlock_time; + bool success = SteamAPI_ISteamUserStats_GetUserAchievementAndUnlockTime(steam_stats, sid->id, name, &achieved, &unlock_time); + JS_FreeCString(js, name); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "achieved", JS_NewBool(js, achieved)); + JS_SetPropertyStr(js, obj, "unlock_time", JS_NewUint32(js, unlock_time)); + return obj; +) + +JSC_CCALL(achievement_get_achieved_percent, + 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 percent; + bool success = SteamAPI_ISteamUserStats_GetAchievementAchievedPercent(steam_stats, name, &percent); + JS_FreeCString(js, name); + + if (!success) return JS_NULL; + return JS_NewFloat64(js, percent); +) + +JSC_CCALL(achievement_get_progress_limits_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 min_progress, max_progress; + bool success = SteamAPI_ISteamUserStats_GetAchievementProgressLimitsInt32(steam_stats, name, &min_progress, &max_progress); + JS_FreeCString(js, name); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "min_progress", JS_NewInt32(js, min_progress)); + JS_SetPropertyStr(js, obj, "max_progress", JS_NewInt32(js, max_progress)); + return obj; +) + +JSC_CCALL(achievement_get_progress_limits_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 min_progress, max_progress; + bool success = SteamAPI_ISteamUserStats_GetAchievementProgressLimitsFloat(steam_stats, name, &min_progress, &max_progress); + JS_FreeCString(js, name); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "min_progress", JS_NewFloat64(js, min_progress)); + JS_SetPropertyStr(js, obj, "max_progress", JS_NewFloat64(js, max_progress)); + return obj; +) + +JSC_CCALL(stats_update_avg_rate, + 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 count_this_session, session_length; + JS_ToFloat64(js, &count_this_session, argv[1]); + JS_ToFloat64(js, &session_length, argv[2]); + + bool success = SteamAPI_ISteamUserStats_UpdateAvgRateStat(steam_stats, name, (float)count_this_session, session_length); + JS_FreeCString(js, name); + + return JS_NewBool(js, success); +) + +JSC_CCALL(stats_get_user_stat_int, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + const char *name = JS_ToCString(js, argv[1]); + if (!name) return JS_EXCEPTION; + + int32 value; + bool success = SteamAPI_ISteamUserStats_GetUserStatInt32(steam_stats, sid->id, name, &value); + JS_FreeCString(js, name); + + if (!success) return JS_NULL; + return JS_NewInt32(js, value); +) + +JSC_CCALL(stats_get_user_stat_float, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + const char *name = JS_ToCString(js, argv[1]); + if (!name) return JS_EXCEPTION; + + float value; + bool success = SteamAPI_ISteamUserStats_GetUserStatFloat(steam_stats, sid->id, name, &value); + JS_FreeCString(js, name); + + if (!success) return JS_NULL; + return JS_NewFloat64(js, value); +) + +JSC_CCALL(stats_reset_all, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + bool achievements_too = JS_ToBool(js, argv[0]); + + bool success = SteamAPI_ISteamUserStats_ResetAllStats(steam_stats, achievements_too); + return JS_NewBool(js, success); +) + +JSC_CCALL(leaderboard_find_or_create, + 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 sort_method, display_type; + JS_ToInt32(js, &sort_method, argv[1]); + JS_ToInt32(js, &display_type, argv[2]); + + SteamAPICall_t call = SteamAPI_ISteamUserStats_FindOrCreateLeaderboard(steam_stats, name, (ELeaderboardSortMethod)sort_method, (ELeaderboardDisplayType)display_type); + JS_FreeCString(js, name); + + return steamid2js(js, call); +) + +JSC_CCALL(leaderboard_find, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + SteamAPICall_t call = SteamAPI_ISteamUserStats_FindLeaderboard(steam_stats, name); + JS_FreeCString(js, name); + + return steamid2js(js, call); +) + +JSC_CCALL(leaderboard_get_name, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint64 leaderboard; + JS_ToIndex(js, &leaderboard, argv[0]); + + const char *name = SteamAPI_ISteamUserStats_GetLeaderboardName(steam_stats, (SteamLeaderboard_t)leaderboard); + return JS_NewString(js, name ? name : ""); +) + +JSC_CCALL(leaderboard_get_entry_count, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint64 leaderboard; + JS_ToIndex(js, &leaderboard, argv[0]); + + int count = SteamAPI_ISteamUserStats_GetLeaderboardEntryCount(steam_stats, (SteamLeaderboard_t)leaderboard); + return JS_NewInt32(js, count); +) + +JSC_CCALL(leaderboard_get_sort_method, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint64 leaderboard; + JS_ToIndex(js, &leaderboard, argv[0]); + + ELeaderboardSortMethod method = SteamAPI_ISteamUserStats_GetLeaderboardSortMethod(steam_stats, (SteamLeaderboard_t)leaderboard); + return JS_NewInt32(js, method); +) + +JSC_CCALL(leaderboard_get_display_type, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint64 leaderboard; + JS_ToIndex(js, &leaderboard, argv[0]); + + ELeaderboardDisplayType type = SteamAPI_ISteamUserStats_GetLeaderboardDisplayType(steam_stats, (SteamLeaderboard_t)leaderboard); + return JS_NewInt32(js, type); +) + +JSC_CCALL(leaderboard_download_entries, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint64 leaderboard; + JS_ToIndex(js, &leaderboard, argv[0]); + + int32 request_type, range_start, range_end; + JS_ToInt32(js, &request_type, argv[1]); + JS_ToInt32(js, &range_start, argv[2]); + JS_ToInt32(js, &range_end, argv[3]); + + SteamAPICall_t call = SteamAPI_ISteamUserStats_DownloadLeaderboardEntries(steam_stats, (SteamLeaderboard_t)leaderboard, (ELeaderboardDataRequest)request_type, range_start, range_end); + return steamid2js(js, call); +) + +JSC_CCALL(leaderboard_download_entries_for_users, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint64 leaderboard; + JS_ToIndex(js, &leaderboard, argv[0]); + + // For simplicity, assume single user for now - proper implementation would handle array + steamid_t *sid = js2steamid(js, argv[1]); + if (!sid) return JS_ThrowTypeError(js, "Second argument must be a SteamID"); + + CSteamID user_id = CSteamID(sid->id); + SteamAPICall_t call = SteamAPI_ISteamUserStats_DownloadLeaderboardEntriesForUsers(steam_stats, (SteamLeaderboard_t)leaderboard, &user_id, 1); + + return steamid2js(js, call); +) + +JSC_CCALL(leaderboard_get_downloaded_entry, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint64 entries; + JS_ToIndex(js, &entries, argv[0]); + + int32 index; + JS_ToInt32(js, &index, argv[1]); + + LeaderboardEntry_t entry; + int32 details[32]; // Max details buffer + bool success = SteamAPI_ISteamUserStats_GetDownloadedLeaderboardEntry(steam_stats, (SteamLeaderboardEntries_t)entries, index, &entry, details, 32); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "steam_id", steamid2js(js, entry.m_steamIDUser.ConvertToUint64())); + JS_SetPropertyStr(js, obj, "global_rank", JS_NewInt32(js, entry.m_nGlobalRank)); + JS_SetPropertyStr(js, obj, "score", JS_NewInt32(js, entry.m_nScore)); + JS_SetPropertyStr(js, obj, "details_count", JS_NewInt32(js, entry.m_cDetails)); + return obj; +) + +JSC_CCALL(leaderboard_upload_score, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint64 leaderboard; + JS_ToIndex(js, &leaderboard, argv[0]); + + int32 upload_method, score; + JS_ToInt32(js, &upload_method, argv[1]); + JS_ToInt32(js, &score, argv[2]); + + SteamAPICall_t call = SteamAPI_ISteamUserStats_UploadLeaderboardScore(steam_stats, (SteamLeaderboard_t)leaderboard, (ELeaderboardUploadScoreMethod)upload_method, score, NULL, 0); + return steamid2js(js, call); +) + +JSC_CCALL(leaderboard_attach_ugc, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + uint64 leaderboard, ugc_handle; + JS_ToIndex(js, &leaderboard, argv[0]); + JS_ToIndex(js, &ugc_handle, argv[1]); + + SteamAPICall_t call = SteamAPI_ISteamUserStats_AttachLeaderboardUGC(steam_stats, (SteamLeaderboard_t)leaderboard, (UGCHandle_t)ugc_handle); + return steamid2js(js, call); +) + +JSC_CCALL(stats_get_number_of_current_players, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + SteamAPICall_t call = SteamAPI_ISteamUserStats_GetNumberOfCurrentPlayers(steam_stats); + return steamid2js(js, call); +) + +JSC_CCALL(stats_request_global_achievement_percentages, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + SteamAPICall_t call = SteamAPI_ISteamUserStats_RequestGlobalAchievementPercentages(steam_stats); + return steamid2js(js, call); +) + +JSC_CCALL(achievement_get_most_achieved_info, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + char name[256]; + float percent; + bool achieved; + + int result = SteamAPI_ISteamUserStats_GetMostAchievedAchievementInfo(steam_stats, name, 256, &percent, &achieved); + + if (result <= 0) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "name", JS_NewString(js, name)); + JS_SetPropertyStr(js, obj, "percent", JS_NewFloat64(js, percent)); + JS_SetPropertyStr(js, obj, "achieved", JS_NewBool(js, achieved)); + return obj; +) + +JSC_CCALL(achievement_get_next_most_achieved_info, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + int32 iterator_previous; + JS_ToInt32(js, &iterator_previous, argv[0]); + + char name[256]; + float percent; + bool achieved; + + int result = SteamAPI_ISteamUserStats_GetNextMostAchievedAchievementInfo(steam_stats, iterator_previous, name, 256, &percent, &achieved); + + if (result <= 0) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "name", JS_NewString(js, name)); + JS_SetPropertyStr(js, obj, "percent", JS_NewFloat64(js, percent)); + JS_SetPropertyStr(js, obj, "achieved", JS_NewBool(js, achieved)); + JS_SetPropertyStr(js, obj, "iterator", JS_NewInt32(js, result)); + return obj; +) + +JSC_CCALL(stats_request_global, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + int32 history_days; + JS_ToInt32(js, &history_days, argv[0]); + + SteamAPICall_t call = SteamAPI_ISteamUserStats_RequestGlobalStats(steam_stats, history_days); + return steamid2js(js, call); +) + +JSC_CCALL(stats_get_global_stat_int64, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + int64 value; + bool success = SteamAPI_ISteamUserStats_GetGlobalStatInt64(steam_stats, name, &value); + JS_FreeCString(js, name); + + if (!success) return JS_NULL; + return steamid2js(js, value); +) + +JSC_CCALL(stats_get_global_stat_double, + 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 value; + bool success = SteamAPI_ISteamUserStats_GetGlobalStatDouble(steam_stats, name, &value); + JS_FreeCString(js, name); + + if (!success) return JS_NULL; + return JS_NewFloat64(js, value); +) + +JSC_CCALL(stats_get_global_stat_history_int64, + if (!steam_stats) return JS_ThrowInternalError(js, "Steam stats not initialized"); + + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + + int64 data[100]; // Buffer for history data + int32 result = SteamAPI_ISteamUserStats_GetGlobalStatHistoryInt64(steam_stats, name, data, sizeof(data)); + JS_FreeCString(js, name); + + if (result <= 0) return JS_NULL; + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < result; i++) { + JS_SetPropertyUint32(js, arr, i, steamid2js(js, data[i])); + } + return arr; +) + +JSC_CCALL(stats_get_global_stat_history_double, + 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 data[100]; // Buffer for history data + int32 result = SteamAPI_ISteamUserStats_GetGlobalStatHistoryDouble(steam_stats, name, data, sizeof(data)); + JS_FreeCString(js, name); + + if (result <= 0) return JS_NULL; + + JSValue arr = JS_NewArray(js); + for (int i = 0; i < result; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewFloat64(js, data[i])); + } + return arr; +) + // APPS JSC_CCALL(app_id, if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); @@ -202,7 +803,7 @@ 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); + return steamid2js(js, owner); ) JSC_CCALL(app_installed, @@ -239,6 +840,318 @@ JSC_CCALL(app_dlc_installed, return JS_NewBool(js, installed); ) +JSC_CCALL(app_is_low_violence, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + bool low_violence = SteamAPI_ISteamApps_BIsLowViolence(steam_apps); + return JS_NewBool(js, low_violence); +) + +JSC_CCALL(app_is_cybercafe, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + bool cybercafe = SteamAPI_ISteamApps_BIsCybercafe(steam_apps); + return JS_NewBool(js, cybercafe); +) + +JSC_CCALL(app_is_vac_banned, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + bool vac_banned = SteamAPI_ISteamApps_BIsVACBanned(steam_apps); + return JS_NewBool(js, vac_banned); +) + +JSC_CCALL(app_available_languages, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + const char *languages = SteamAPI_ISteamApps_GetAvailableGameLanguages(steam_apps); + return JS_NewString(js, languages ? languages : ""); +) + +JSC_CCALL(app_is_subscribed_app, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + AppId_t appid; + JS_ToUint32(js, &appid, argv[0]); + + bool subscribed = SteamAPI_ISteamApps_BIsSubscribedApp(steam_apps, appid); + return JS_NewBool(js, subscribed); +) + +JSC_CCALL(app_earliest_purchase_time, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + AppId_t appid; + JS_ToUint32(js, &appid, argv[0]); + + uint32 time = SteamAPI_ISteamApps_GetEarliestPurchaseUnixTime(steam_apps, appid); + return JS_NewUint32(js, time); +) + +JSC_CCALL(app_is_subscribed_from_free_weekend, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + bool free_weekend = SteamAPI_ISteamApps_BIsSubscribedFromFreeWeekend(steam_apps); + return JS_NewBool(js, free_weekend); +) + +JSC_CCALL(app_dlc_count, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + int count = SteamAPI_ISteamApps_GetDLCCount(steam_apps); + return JS_NewInt32(js, count); +) + +JSC_CCALL(app_get_dlc_data_by_index, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + int index; + JS_ToInt32(js, &index, argv[0]); + + AppId_t appid; + bool available; + char name[256]; + + bool success = SteamAPI_ISteamApps_BGetDLCDataByIndex(steam_apps, index, &appid, &available, name, sizeof(name)); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "appid", JS_NewUint32(js, appid)); + JS_SetPropertyStr(js, obj, "available", JS_NewBool(js, available)); + JS_SetPropertyStr(js, obj, "name", JS_NewString(js, name)); + return obj; +) + +JSC_CCALL(app_install_dlc, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + AppId_t appid; + JS_ToUint32(js, &appid, argv[0]); + + SteamAPI_ISteamApps_InstallDLC(steam_apps, appid); + return JS_NULL; +) + +JSC_CCALL(app_uninstall_dlc, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + AppId_t appid; + JS_ToUint32(js, &appid, argv[0]); + + SteamAPI_ISteamApps_UninstallDLC(steam_apps, appid); + return JS_NULL; +) + +JSC_CCALL(app_request_proof_of_purchase_key, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + AppId_t appid; + JS_ToUint32(js, &appid, argv[0]); + + SteamAPI_ISteamApps_RequestAppProofOfPurchaseKey(steam_apps, appid); + return JS_NULL; +) + +JSC_CCALL(app_get_current_beta_name, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + char beta_name[256]; + bool has_beta = SteamAPI_ISteamApps_GetCurrentBetaName(steam_apps, beta_name, sizeof(beta_name)); + + if (!has_beta) return JS_NULL; + return JS_NewString(js, beta_name); +) + +JSC_CCALL(app_mark_content_corrupt, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + bool missing_files_only = false; + if (argc > 0) { + missing_files_only = JS_ToBool(js, argv[0]); + } + + bool success = SteamAPI_ISteamApps_MarkContentCorrupt(steam_apps, missing_files_only); + return JS_NewBool(js, success); +) + +JSC_CCALL(app_get_installed_depots, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + AppId_t appid; + JS_ToUint32(js, &appid, argv[0]); + + const uint32 max_depots = 32; + DepotId_t depots[max_depots]; + + uint32 count = SteamAPI_ISteamApps_GetInstalledDepots(steam_apps, appid, depots, max_depots); + + JSValue arr = JS_NewArray(js); + for (uint32 i = 0; i < count; i++) { + JS_SetPropertyUint32(js, arr, i, JS_NewUint32(js, depots[i])); + } + return arr; +) + +JSC_CCALL(app_get_install_dir, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + AppId_t appid; + JS_ToUint32(js, &appid, argv[0]); + + char folder[512]; + uint32 size = SteamAPI_ISteamApps_GetAppInstallDir(steam_apps, appid, folder, sizeof(folder)); + + if (size == 0) return JS_NULL; + return JS_NewString(js, folder); +) + +JSC_CCALL(app_get_launch_query_param, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + const char *key = JS_ToCString(js, argv[0]); + if (!key) return JS_ThrowTypeError(js, "Expected string parameter"); + + const char *value = SteamAPI_ISteamApps_GetLaunchQueryParam(steam_apps, key); + JS_FreeCString(js, key); + + if (!value) return JS_NULL; + return JS_NewString(js, value); +) + +JSC_CCALL(app_get_dlc_download_progress, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + AppId_t appid; + JS_ToUint32(js, &appid, argv[0]); + + uint64 bytes_downloaded, bytes_total; + bool success = SteamAPI_ISteamApps_GetDlcDownloadProgress(steam_apps, appid, &bytes_downloaded, &bytes_total); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "downloaded", steamid2js(js, bytes_downloaded)); + JS_SetPropertyStr(js, obj, "total", steamid2js(js, bytes_total)); + return obj; +) + +JSC_CCALL(app_get_build_id, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + int build_id = SteamAPI_ISteamApps_GetAppBuildId(steam_apps); + return JS_NewInt32(js, build_id); +) + +JSC_CCALL(app_request_all_proof_of_purchase_keys, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + SteamAPI_ISteamApps_RequestAllProofOfPurchaseKeys(steam_apps); + return JS_NULL; +) + +JSC_CCALL(app_get_file_details, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + const char *filename = JS_ToCString(js, argv[0]); + if (!filename) return JS_ThrowTypeError(js, "Expected string parameter"); + + SteamAPICall_t call = SteamAPI_ISteamApps_GetFileDetails(steam_apps, filename); + JS_FreeCString(js, filename); + + return steamid2js(js, call); +) + +JSC_CCALL(app_get_launch_command_line, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + char command_line[1024]; + int size = SteamAPI_ISteamApps_GetLaunchCommandLine(steam_apps, command_line, sizeof(command_line)); + + if (size <= 0) return JS_NULL; + return JS_NewString(js, command_line); +) + +JSC_CCALL(app_is_subscribed_from_family_sharing, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + bool family_sharing = SteamAPI_ISteamApps_BIsSubscribedFromFamilySharing(steam_apps); + return JS_NewBool(js, family_sharing); +) + +JSC_CCALL(app_is_timed_trial, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + uint32 seconds_allowed, seconds_played; + bool is_trial = SteamAPI_ISteamApps_BIsTimedTrial(steam_apps, &seconds_allowed, &seconds_played); + + if (!is_trial) return JS_NewBool(js, false); + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "is_trial", JS_NewBool(js, true)); + JS_SetPropertyStr(js, obj, "seconds_allowed", JS_NewUint32(js, seconds_allowed)); + JS_SetPropertyStr(js, obj, "seconds_played", JS_NewUint32(js, seconds_played)); + return obj; +) + +JSC_CCALL(app_set_dlc_context, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + AppId_t appid; + JS_ToUint32(js, &appid, argv[0]); + + bool success = SteamAPI_ISteamApps_SetDlcContext(steam_apps, appid); + return JS_NewBool(js, success); +) + +JSC_CCALL(app_get_num_betas, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + int available, private_betas; + int total = SteamAPI_ISteamApps_GetNumBetas(steam_apps, &available, &private_betas); + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "total", JS_NewInt32(js, total)); + JS_SetPropertyStr(js, obj, "available", JS_NewInt32(js, available)); + JS_SetPropertyStr(js, obj, "private", JS_NewInt32(js, private_betas)); + return obj; +) + +JSC_CCALL(app_get_beta_info, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + int beta_index; + JS_ToInt32(js, &beta_index, argv[0]); + + uint32 flags, build_id; + char beta_name[128], description[512]; + + bool success = SteamAPI_ISteamApps_GetBetaInfo(steam_apps, beta_index, &flags, &build_id, + beta_name, sizeof(beta_name), + description, sizeof(description)); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "flags", JS_NewUint32(js, flags)); + JS_SetPropertyStr(js, obj, "build_id", JS_NewUint32(js, build_id)); + JS_SetPropertyStr(js, obj, "name", JS_NewString(js, beta_name)); + JS_SetPropertyStr(js, obj, "description", JS_NewString(js, description)); + return obj; +) + +JSC_CCALL(app_set_active_beta, + if (!steam_apps) return JS_ThrowInternalError(js, "Steam apps not initialized"); + + const char *beta_name = JS_ToCString(js, argv[0]); + if (!beta_name) return JS_ThrowTypeError(js, "Expected string parameter"); + + bool success = SteamAPI_ISteamApps_SetActiveBeta(steam_apps, beta_name); + JS_FreeCString(js, beta_name); + + return JS_NewBool(js, success); +) + // USER JSC_CCALL(user_logged_on, if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); @@ -251,7 +1164,7 @@ 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); + return steamid2js(js, id); ) JSC_CCALL(user_level, @@ -261,6 +1174,382 @@ JSC_CCALL(user_level, return JS_NewInt32(js, level); ) +JSC_CCALL(user_hsteam_user, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + HSteamUser user = SteamAPI_ISteamUser_GetHSteamUser(steam_user); + return JS_NewUint32(js, user); +) + +JSC_CCALL(user_get_user_data_folder, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + char buffer[1024]; + bool success = SteamAPI_ISteamUser_GetUserDataFolder(steam_user, buffer, sizeof(buffer)); + + if (!success) return JS_NULL; + return JS_NewString(js, buffer); +) + +JSC_CCALL(user_start_voice_recording, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + SteamAPI_ISteamUser_StartVoiceRecording(steam_user); + return JS_NULL; +) + +JSC_CCALL(user_stop_voice_recording, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + SteamAPI_ISteamUser_StopVoiceRecording(steam_user); + return JS_NULL; +) + +JSC_CCALL(user_get_available_voice, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + uint32 compressed, uncompressed_deprecated; + uint32 sample_rate = 11025; + if (argc > 0) JS_ToUint32(js, &sample_rate, argv[0]); + + EVoiceResult result = SteamAPI_ISteamUser_GetAvailableVoice(steam_user, &compressed, &uncompressed_deprecated, sample_rate); + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "result", JS_NewInt32(js, result)); + JS_SetPropertyStr(js, obj, "compressed_bytes", JS_NewUint32(js, compressed)); + return obj; +) + +JSC_CCALL(user_get_voice, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + uint32 buffer_size = 8192; + if (argc > 0) JS_ToUint32(js, &buffer_size, argv[0]); + + void *buffer = js_malloc(js, buffer_size); + if (!buffer) return JS_ThrowOutOfMemory(js); + + uint32 bytes_written; + EVoiceResult result = SteamAPI_ISteamUser_GetVoice(steam_user, true, buffer, buffer_size, &bytes_written, false, NULL, 0, NULL, 0); + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "result", JS_NewInt32(js, result)); + + if (result == k_EVoiceResultOK && bytes_written > 0) { + JSValue blob = js_new_blob_stoned_copy(js, buffer, bytes_written); + JS_SetPropertyStr(js, obj, "data", blob); + JS_SetPropertyStr(js, obj, "bytes_written", JS_NewUint32(js, bytes_written)); + } + + js_free(js, buffer); + return obj; +) + +JSC_CCALL(user_decompress_voice, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + size_t compressed_size; + void *compressed_data = js_get_blob_data(js, &compressed_size, argv[0]); + if (!compressed_data) return JS_ThrowTypeError(js, "First argument must be a blob"); + + uint32 dest_buffer_size = 22050 * 2; + uint32 sample_rate = 22050; + if (argc > 1) JS_ToUint32(js, &dest_buffer_size, argv[1]); + if (argc > 2) JS_ToUint32(js, &sample_rate, argv[2]); + + void *dest_buffer = js_malloc(js, dest_buffer_size); + if (!dest_buffer) return JS_ThrowOutOfMemory(js); + + uint32 bytes_written; + EVoiceResult result = SteamAPI_ISteamUser_DecompressVoice(steam_user, compressed_data, compressed_size, dest_buffer, dest_buffer_size, &bytes_written, sample_rate); + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "result", JS_NewInt32(js, result)); + + if (result == k_EVoiceResultOK && bytes_written > 0) { + JSValue blob = js_new_blob_stoned_copy(js, dest_buffer, bytes_written); + JS_SetPropertyStr(js, obj, "data", blob); + JS_SetPropertyStr(js, obj, "bytes_written", JS_NewUint32(js, bytes_written)); + } + + js_free(js, dest_buffer); + return obj; +) + +JSC_CCALL(user_get_voice_optimal_sample_rate, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + uint32 rate = SteamAPI_ISteamUser_GetVoiceOptimalSampleRate(steam_user); + return JS_NewUint32(js, rate); +) + +JSC_CCALL(user_get_auth_session_ticket, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + uint8_t ticket[1024]; + uint32 ticket_size; + + HAuthTicket auth_ticket = SteamAPI_ISteamUser_GetAuthSessionTicket(steam_user, ticket, sizeof(ticket), &ticket_size, NULL); + + if (auth_ticket == k_HAuthTicketInvalid) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "auth_ticket", JS_NewUint32(js, auth_ticket)); + + if (ticket_size > 0) { + JSValue blob = js_new_blob_stoned_copy(js, ticket, ticket_size); + JS_SetPropertyStr(js, obj, "ticket_data", blob); + JS_SetPropertyStr(js, obj, "ticket_size", JS_NewUint32(js, ticket_size)); + } + + return obj; +) + +JSC_CCALL(user_get_auth_ticket_for_web_api, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + const char *identity = JS_ToCString(js, argv[0]); + if (!identity) return JS_EXCEPTION; + + HAuthTicket auth_ticket = SteamAPI_ISteamUser_GetAuthTicketForWebApi(steam_user, identity); + JS_FreeCString(js, identity); + + if (auth_ticket == k_HAuthTicketInvalid) return JS_NULL; + return JS_NewUint32(js, auth_ticket); +) + +JSC_CCALL(user_begin_auth_session, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + size_t ticket_size; + void *ticket_data = js_get_blob_data(js, &ticket_size, argv[0]); + if (!ticket_data) return JS_ThrowTypeError(js, "First argument must be a blob"); + + steamid_t *sid = js2steamid(js, argv[1]); + if (!sid) return JS_ThrowTypeError(js, "Second argument must be a SteamID"); + + EBeginAuthSessionResult result = SteamAPI_ISteamUser_BeginAuthSession(steam_user, ticket_data, ticket_size, sid->id); + return JS_NewInt32(js, result); +) + +JSC_CCALL(user_end_auth_session, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + SteamAPI_ISteamUser_EndAuthSession(steam_user, sid->id); + return JS_NULL; +) + +JSC_CCALL(user_cancel_auth_ticket, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + uint32 auth_ticket; + JS_ToUint32(js, &auth_ticket, argv[0]); + + SteamAPI_ISteamUser_CancelAuthTicket(steam_user, auth_ticket); + return JS_NULL; +) + +JSC_CCALL(user_has_license_for_app, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + uint32 app_id; + JS_ToUint32(js, &app_id, argv[1]); + + EUserHasLicenseForAppResult result = SteamAPI_ISteamUser_UserHasLicenseForApp(steam_user, sid->id, app_id); + return JS_NewInt32(js, result); +) + +JSC_CCALL(user_is_behind_nat, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + bool behind_nat = SteamAPI_ISteamUser_BIsBehindNAT(steam_user); + return JS_NewBool(js, behind_nat); +) + +JSC_CCALL(user_advertise_game, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + uint32 ip_server, port_server; + JS_ToUint32(js, &ip_server, argv[1]); + JS_ToUint32(js, &port_server, argv[2]); + + SteamAPI_ISteamUser_AdvertiseGame(steam_user, sid->id, ip_server, (uint16)port_server); + return JS_NULL; +) + +JSC_CCALL(user_request_encrypted_app_ticket, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + size_t data_size = 0; + void *data = NULL; + + if (argc > 0 && !JS_IsNull(argv[0])) { + data = js_get_blob_data(js, &data_size, argv[0]); + if (!data) return JS_ThrowTypeError(js, "First argument must be a blob or null"); + } + + SteamAPICall_t call = SteamAPI_ISteamUser_RequestEncryptedAppTicket(steam_user, data, data_size); + return steamid2js(js, call); +) + +JSC_CCALL(user_get_encrypted_app_ticket, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + uint8_t ticket[1024]; + uint32 ticket_size; + + bool success = SteamAPI_ISteamUser_GetEncryptedAppTicket(steam_user, ticket, sizeof(ticket), &ticket_size); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + if (ticket_size > 0) { + JSValue blob = js_new_blob_stoned_copy(js, ticket, ticket_size); + JS_SetPropertyStr(js, obj, "ticket_data", blob); + JS_SetPropertyStr(js, obj, "ticket_size", JS_NewUint32(js, ticket_size)); + } + + return obj; +) + +JSC_CCALL(user_get_game_badge_level, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + int32 series; + JS_ToInt32(js, &series, argv[0]); + + bool foil = argc > 1 ? JS_ToBool(js, argv[1]) : false; + + int level = SteamAPI_ISteamUser_GetGameBadgeLevel(steam_user, series, foil); + return JS_NewInt32(js, level); +) + +JSC_CCALL(user_request_store_auth_url, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + const char *redirect_url = JS_ToCString(js, argv[0]); + if (!redirect_url) return JS_EXCEPTION; + + SteamAPICall_t call = SteamAPI_ISteamUser_RequestStoreAuthURL(steam_user, redirect_url); + JS_FreeCString(js, redirect_url); + + return steamid2js(js, call); +) + +JSC_CCALL(user_is_phone_verified, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + bool verified = SteamAPI_ISteamUser_BIsPhoneVerified(steam_user); + return JS_NewBool(js, verified); +) + +JSC_CCALL(user_is_two_factor_enabled, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + bool enabled = SteamAPI_ISteamUser_BIsTwoFactorEnabled(steam_user); + return JS_NewBool(js, enabled); +) + +JSC_CCALL(user_is_phone_identifying, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + bool identifying = SteamAPI_ISteamUser_BIsPhoneIdentifying(steam_user); + return JS_NewBool(js, identifying); +) + +JSC_CCALL(user_is_phone_requiring_verification, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + bool requiring = SteamAPI_ISteamUser_BIsPhoneRequiringVerification(steam_user); + return JS_NewBool(js, requiring); +) + +JSC_CCALL(user_get_market_eligibility, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + SteamAPICall_t call = SteamAPI_ISteamUser_GetMarketEligibility(steam_user); + return steamid2js(js, call); +) + +JSC_CCALL(user_get_duration_control, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + SteamAPICall_t call = SteamAPI_ISteamUser_GetDurationControl(steam_user); + return steamid2js(js, call); +) + +JSC_CCALL(user_set_duration_control_online_state, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + int32 state; + JS_ToInt32(js, &state, argv[0]); + + bool success = SteamAPI_ISteamUser_BSetDurationControlOnlineState(steam_user, (EDurationControlOnlineState)state); + return JS_NewBool(js, success); +) + +JSC_CCALL(user_track_app_usage_event, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + uint64 game_id; + JS_ToIndex(js, &game_id, argv[0]); + + int32 app_usage_event; + JS_ToInt32(js, &app_usage_event, argv[1]); + + const char *extra_info = argc > 2 ? JS_ToCString(js, argv[2]) : ""; + + SteamAPI_ISteamUser_TrackAppUsageEvent(steam_user, game_id, app_usage_event, extra_info); + + if (argc > 2) JS_FreeCString(js, extra_info); + return JS_NULL; +) + +JSC_CCALL(user_initiate_game_connection_deprecated, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + steamid_t *server_sid = js2steamid(js, argv[0]); + if (!server_sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + uint32 ip_server, port_server; + JS_ToUint32(js, &ip_server, argv[1]); + JS_ToUint32(js, &port_server, argv[2]); + + bool secure = argc > 3 ? JS_ToBool(js, argv[3]) : false; + + uint8_t auth_blob[1024]; + int result = SteamAPI_ISteamUser_InitiateGameConnection_DEPRECATED(steam_user, auth_blob, sizeof(auth_blob), server_sid->id, ip_server, (uint16)port_server, secure); + + if (result <= 0) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JSValue blob = js_new_blob_stoned_copy(js, auth_blob, result); + JS_SetPropertyStr(js, obj, "auth_blob", blob); + JS_SetPropertyStr(js, obj, "size", JS_NewInt32(js, result)); + + return obj; +) + +JSC_CCALL(user_terminate_game_connection_deprecated, + if (!steam_user) return JS_ThrowInternalError(js, "Steam user not initialized"); + + uint32 ip_server, port_server; + JS_ToUint32(js, &ip_server, argv[0]); + JS_ToUint32(js, &port_server, argv[1]); + + SteamAPI_ISteamUser_TerminateGameConnection_DEPRECATED(steam_user, ip_server, (uint16)port_server); + return JS_NULL; +) + // FRIENDS JSC_CCALL(friends_name, if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); @@ -287,6 +1576,130 @@ JSC_CCALL(friends_state, return JS_NewString(js, state_str); ) +JSC_CCALL(friends_count, + if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); + + int flags = k_EFriendFlagImmediate; + if (argc > 0) { + JS_ToInt32(js, &flags, argv[0]); + } + + int count = SteamAPI_ISteamFriends_GetFriendCount(steam_friends, flags); + return JS_NewInt32(js, count); +) + +JSC_CCALL(friends_get_friend, + if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); + + int index; + JS_ToInt32(js, &index, argv[0]); + + int flags = k_EFriendFlagImmediate; + if (argc > 1) { + JS_ToInt32(js, &flags, argv[1]); + } + + uint64_steamid friendID = SteamAPI_ISteamFriends_GetFriendByIndex(steam_friends, index, flags); + if (friendID == 0) return JS_NULL; + + return steamid2js(js, friendID); +) + +JSC_CCALL(friends_get_friend_name, + if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); + + uint64_steamid friendID; + if (argc > 0 && JS_IsObject(argv[0])) { + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "Expected SteamID"); + friendID = sid->id; + } else { + double val; + JS_ToFloat64(js, &val, argv[0]); + friendID = (uint64_steamid)val; + } + + const char *name = SteamAPI_ISteamFriends_GetFriendPersonaName(steam_friends, friendID); + return JS_NewString(js, name ? name : ""); +) + +JSC_CCALL(friends_activate_overlay, + if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); + + const char *dialog = JS_ToCString(js, argv[0]); + if (!dialog) return JS_EXCEPTION; + + SteamAPI_ISteamFriends_ActivateGameOverlay(steam_friends, dialog); + JS_FreeCString(js, dialog); + + return JS_NULL; +) + +JSC_CCALL(friends_activate_overlay_to_user, + if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); + + const char *dialog = JS_ToCString(js, argv[0]); + if (!dialog) return JS_EXCEPTION; + + uint64_steamid userID; + if (argc > 1 && JS_IsObject(argv[1])) { + steamid_t *sid = js2steamid(js, argv[1]); + if (!sid) { + JS_FreeCString(js, dialog); + return JS_ThrowTypeError(js, "Expected SteamID"); + } + userID = sid->id; + } else { + double val; + JS_ToFloat64(js, &val, argv[1]); + userID = (uint64_steamid)val; + } + + SteamAPI_ISteamFriends_ActivateGameOverlayToUser(steam_friends, dialog, userID); + JS_FreeCString(js, dialog); + + return JS_NULL; +) + +JSC_CCALL(friends_activate_overlay_to_web, + if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); + + const char *url = JS_ToCString(js, argv[0]); + if (!url) return JS_EXCEPTION; + + SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage(steam_friends, url, k_EActivateGameOverlayToWebPageMode_Default); + JS_FreeCString(js, url); + + return JS_NULL; +) + +JSC_CCALL(friends_set_rich_presence, + if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); + + const char *key = JS_ToCString(js, argv[0]); + if (!key) return JS_EXCEPTION; + + const char *value = JS_ToCString(js, argv[1]); + if (!value) { + JS_FreeCString(js, key); + return JS_EXCEPTION; + } + + bool success = SteamAPI_ISteamFriends_SetRichPresence(steam_friends, key, value); + + JS_FreeCString(js, key); + JS_FreeCString(js, value); + + return JS_NewBool(js, success); +) + +JSC_CCALL(friends_clear_rich_presence, + if (!steam_friends) return JS_ThrowInternalError(js, "Steam friends not initialized"); + + SteamAPI_ISteamFriends_ClearRichPresence(steam_friends); + return JS_NULL; +) + // CLOUD STORAGE JSC_CCALL(cloud_enabled_app, if (!steam_remote) return JS_ThrowInternalError(js, "Steam remote storage not initialized"); @@ -319,8 +1732,8 @@ JSC_CCALL(cloud_quota, if (!success) return JS_NULL; JSValue obj = JS_NewObject(js); - JS_SetPropertyStr(js, obj, "total", JS_NewBigUint64(js, total)); - JS_SetPropertyStr(js, obj, "available", JS_NewBigUint64(js, available)); + JS_SetPropertyStr(js, obj, "total", JS_NewFloat64(js, (double)total)); + JS_SetPropertyStr(js, obj, "available", JS_NewFloat64(js, (double)available)); return obj; ) @@ -341,7 +1754,7 @@ JSC_CCALL(cloud_write, } data = (uint8_t*)str; } else { - data = js_get_blob_data(js, &data_len, argv[1]); + data = (uint8_t*)js_get_blob_data(js, &data_len, argv[1]); if (!data) { JS_FreeCString(js, filename); return JS_ThrowTypeError(js, "Second argument must be string or ArrayBuffer"); @@ -413,63 +1826,1369 @@ JSC_CCALL(cloud_exists, return JS_NewBool(js, exists); ) +// EXPANDED STEAM UTILS +JSC_CCALL(utils_overlay_enabled, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool enabled = SteamAPI_ISteamUtils_IsOverlayEnabled(steam_utils); + return JS_NewBool(js, enabled); +) + +JSC_CCALL(utils_big_picture_mode, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool bigPicture = SteamAPI_ISteamUtils_IsSteamInBigPictureMode(steam_utils); + return JS_NewBool(js, bigPicture); +) + +JSC_CCALL(utils_vr_mode, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool vrMode = SteamAPI_ISteamUtils_IsSteamRunningInVR(steam_utils); + return JS_NewBool(js, vrMode); +) + +JSC_CCALL(utils_steam_deck, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool steamDeck = SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck(steam_utils); + return JS_NewBool(js, steamDeck); +) + +JSC_CCALL(utils_battery_power, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint8 battery = SteamAPI_ISteamUtils_GetCurrentBatteryPower(steam_utils); + return JS_NewUint32(js, battery); +) + +JSC_CCALL(utils_seconds_since_app_active, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 seconds = SteamAPI_ISteamUtils_GetSecondsSinceAppActive(steam_utils); + return JS_NewUint32(js, seconds); +) + +JSC_CCALL(utils_ip_country, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + const char *country = SteamAPI_ISteamUtils_GetIPCountry(steam_utils); + return JS_NewString(js, country ? country : ""); +) + +JSC_CCALL(utils_ui_language, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + const char *language = SteamAPI_ISteamUtils_GetSteamUILanguage(steam_utils); + return JS_NewString(js, language ? language : "english"); +) + +JSC_CCALL(utils_get_seconds_since_computer_active, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 seconds = SteamAPI_ISteamUtils_GetSecondsSinceComputerActive(steam_utils); + return JS_NewUint32(js, seconds); +) + +JSC_CCALL(utils_get_connected_universe, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + EUniverse universe = SteamAPI_ISteamUtils_GetConnectedUniverse(steam_utils); + return JS_NewUint32(js, universe); +) + +JSC_CCALL(utils_get_server_real_time, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 server_time = SteamAPI_ISteamUtils_GetServerRealTime(steam_utils); + return JS_NewUint32(js, server_time); +) + +JSC_CCALL(utils_get_image_size, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + int image_handle; + JS_ToInt32(js, &image_handle, argv[0]); + + uint32 width, height; + bool success = SteamAPI_ISteamUtils_GetImageSize(steam_utils, image_handle, &width, &height); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "width", JS_NewUint32(js, width)); + JS_SetPropertyStr(js, obj, "height", JS_NewUint32(js, height)); + return obj; +) + +JSC_CCALL(utils_get_image_rgba, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + int image_handle; + JS_ToInt32(js, &image_handle, argv[0]); + + // First get the image size to allocate buffer + uint32 width, height; + if (!SteamAPI_ISteamUtils_GetImageSize(steam_utils, image_handle, &width, &height)) { + return JS_NULL; + } + + uint32 buffer_size = width * height * 4; // RGBA = 4 bytes per pixel + uint8_t *buffer = (uint8_t*)js_malloc(js, buffer_size); + if (!buffer) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUtils_GetImageRGBA(steam_utils, image_handle, buffer, buffer_size); + + if (!success) { + js_free(js, buffer); + return JS_NULL; + } + + JSValue result = js_new_blob_stoned_copy(js, buffer, buffer_size); + js_free(js, buffer); + return result; +) + +JSC_CCALL(utils_set_overlay_notification_position, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 position; + JS_ToUint32(js, &position, argv[0]); + + SteamAPI_ISteamUtils_SetOverlayNotificationPosition(steam_utils, (ENotificationPosition)position); + return JS_NULL; +) + +JSC_CCALL(utils_is_api_call_completed, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + double call_double; + JS_ToFloat64(js, &call_double, argv[0]); + SteamAPICall_t call = (SteamAPICall_t)call_double; + + bool failed; + bool completed = SteamAPI_ISteamUtils_IsAPICallCompleted(steam_utils, call, &failed); + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "completed", JS_NewBool(js, completed)); + JS_SetPropertyStr(js, obj, "failed", JS_NewBool(js, failed)); + return obj; +) + +JSC_CCALL(utils_get_api_call_failure_reason, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + double call_double; + JS_ToFloat64(js, &call_double, argv[0]); + SteamAPICall_t call = (SteamAPICall_t)call_double; + + ESteamAPICallFailure reason = SteamAPI_ISteamUtils_GetAPICallFailureReason(steam_utils, call); + return JS_NewUint32(js, reason); +) + +JSC_CCALL(utils_get_ipc_call_count, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 count = SteamAPI_ISteamUtils_GetIPCCallCount(steam_utils); + return JS_NewUint32(js, count); +) + +JSC_CCALL(utils_overlay_needs_present, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool needs_present = SteamAPI_ISteamUtils_BOverlayNeedsPresent(steam_utils); + return JS_NewBool(js, needs_present); +) + +JSC_CCALL(utils_check_file_signature, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + const char *filename = JS_ToCString(js, argv[0]); + if (!filename) return JS_EXCEPTION; + + SteamAPICall_t call = SteamAPI_ISteamUtils_CheckFileSignature(steam_utils, filename); + JS_FreeCString(js, filename); + + return JS_NewFloat64(js, (double)call); +) + +JSC_CCALL(utils_show_gamepad_text_input, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 input_mode, line_mode, char_max; + JS_ToUint32(js, &input_mode, argv[0]); + JS_ToUint32(js, &line_mode, argv[1]); + + const char *description = JS_ToCString(js, argv[2]); + if (!description) return JS_EXCEPTION; + + JS_ToUint32(js, &char_max, argv[3]); + + const char *existing_text = argc > 4 ? JS_ToCString(js, argv[4]) : ""; + + bool success = SteamAPI_ISteamUtils_ShowGamepadTextInput(steam_utils, (EGamepadTextInputMode)input_mode, (EGamepadTextInputLineMode)line_mode, description, char_max, existing_text); + + JS_FreeCString(js, description); + if (argc > 4) JS_FreeCString(js, existing_text); + + return JS_NewBool(js, success); +) + +JSC_CCALL(utils_get_entered_gamepad_text_length, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 length = SteamAPI_ISteamUtils_GetEnteredGamepadTextLength(steam_utils); + return JS_NewUint32(js, length); +) + +JSC_CCALL(utils_get_entered_gamepad_text_input, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 length = SteamAPI_ISteamUtils_GetEnteredGamepadTextLength(steam_utils); + if (length == 0) return JS_NewString(js, ""); + + char *buffer = (char*)js_malloc(js, length + 1); + if (!buffer) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUtils_GetEnteredGamepadTextInput(steam_utils, buffer, length + 1); + + if (!success) { + js_free(js, buffer); + return JS_NewString(js, ""); + } + + JSValue result = JS_NewString(js, buffer); + js_free(js, buffer); + return result; +) + +JSC_CCALL(utils_set_overlay_notification_inset, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + int horizontal_inset, vertical_inset; + JS_ToInt32(js, &horizontal_inset, argv[0]); + JS_ToInt32(js, &vertical_inset, argv[1]); + + SteamAPI_ISteamUtils_SetOverlayNotificationInset(steam_utils, horizontal_inset, vertical_inset); + return JS_NULL; +) + +JSC_CCALL(utils_start_vr_dashboard, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + SteamAPI_ISteamUtils_StartVRDashboard(steam_utils); + return JS_NULL; +) + +JSC_CCALL(utils_is_vr_headset_streaming_enabled, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool enabled = SteamAPI_ISteamUtils_IsVRHeadsetStreamingEnabled(steam_utils); + return JS_NewBool(js, enabled); +) + +JSC_CCALL(utils_set_vr_headset_streaming_enabled, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool enabled = JS_ToBool(js, argv[0]); + SteamAPI_ISteamUtils_SetVRHeadsetStreamingEnabled(steam_utils, enabled); + return JS_NULL; +) + +JSC_CCALL(utils_is_steam_china_launcher, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool is_china = SteamAPI_ISteamUtils_IsSteamChinaLauncher(steam_utils); + return JS_NewBool(js, is_china); +) + +JSC_CCALL(utils_init_filter_text, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 filter_options; + JS_ToUint32(js, &filter_options, argv[0]); + + bool success = SteamAPI_ISteamUtils_InitFilterText(steam_utils, filter_options); + return JS_NewBool(js, success); +) + +JSC_CCALL(utils_filter_text, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 context; + JS_ToUint32(js, &context, argv[0]); + + steamid_t *sid = js2steamid(js, argv[1]); + if (!sid) return JS_ThrowTypeError(js, "Second argument must be a SteamID"); + + const char *input_message = JS_ToCString(js, argv[2]); + if (!input_message) return JS_EXCEPTION; + + // Allocate buffer for filtered text (same size as input should be sufficient) + size_t input_len = strlen(input_message); + uint32 buffer_size = input_len + 1; + char *filtered_buffer = (char*)js_malloc(js, buffer_size); + if (!filtered_buffer) { + JS_FreeCString(js, input_message); + return JS_EXCEPTION; + } + + int result = SteamAPI_ISteamUtils_FilterText(steam_utils, (ETextFilteringContext)context, sid->id, input_message, filtered_buffer, buffer_size); + + JS_FreeCString(js, input_message); + + if (result < 0) { + js_free(js, filtered_buffer); + return JS_NULL; + } + + JSValue js_result = JS_NewString(js, filtered_buffer); + js_free(js, filtered_buffer); + return js_result; +) + +JSC_CCALL(utils_get_ipv6_connectivity_state, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 protocol; + JS_ToUint32(js, &protocol, argv[0]); + + ESteamIPv6ConnectivityState state = SteamAPI_ISteamUtils_GetIPv6ConnectivityState(steam_utils, (ESteamIPv6ConnectivityProtocol)protocol); + return JS_NewUint32(js, state); +) + +JSC_CCALL(utils_show_floating_gamepad_text_input, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + uint32 keyboard_mode; + int x_pos, y_pos, width, height; + JS_ToUint32(js, &keyboard_mode, argv[0]); + JS_ToInt32(js, &x_pos, argv[1]); + JS_ToInt32(js, &y_pos, argv[2]); + JS_ToInt32(js, &width, argv[3]); + JS_ToInt32(js, &height, argv[4]); + + bool success = SteamAPI_ISteamUtils_ShowFloatingGamepadTextInput(steam_utils, (EFloatingGamepadTextInputMode)keyboard_mode, x_pos, y_pos, width, height); + return JS_NewBool(js, success); +) + +JSC_CCALL(utils_set_game_launcher_mode, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool launcher_mode = JS_ToBool(js, argv[0]); + SteamAPI_ISteamUtils_SetGameLauncherMode(steam_utils, launcher_mode); + return JS_NULL; +) + +JSC_CCALL(utils_dismiss_floating_gamepad_text_input, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool success = SteamAPI_ISteamUtils_DismissFloatingGamepadTextInput(steam_utils); + return JS_NewBool(js, success); +) + +JSC_CCALL(utils_dismiss_gamepad_text_input, + if (!steam_utils) return JS_ThrowInternalError(js, "Steam utils not initialized"); + + bool success = SteamAPI_ISteamUtils_DismissGamepadTextInput(steam_utils); + return JS_NewBool(js, success); +) + +// SCREENSHOTS API +JSC_CCALL(screenshots_write, + if (!steam_screenshots) return JS_ThrowInternalError(js, "Steam screenshots not initialized"); + + const char *filename = JS_ToCString(js, argv[0]); + if (!filename) return JS_EXCEPTION; + + uint32 width, height; + JS_ToUint32(js, &width, argv[1]); + JS_ToUint32(js, &height, argv[2]); + + // Get image data from ArrayBuffer + size_t data_len; + uint8_t *data = (uint8_t*)js_get_blob_data(js, &data_len, argv[3]); + if (!data) { + JS_FreeCString(js, filename); + return JS_ThrowTypeError(js, "Fourth argument must be ArrayBuffer containing RGB data"); + } + + ScreenshotHandle handle = SteamAPI_ISteamScreenshots_WriteScreenshot(steam_screenshots, data, data_len, width, height); + JS_FreeCString(js, filename); + + return JS_NewUint32(js, handle); +) + +JSC_CCALL(screenshots_trigger, + if (!steam_screenshots) return JS_ThrowInternalError(js, "Steam screenshots not initialized"); + + SteamAPI_ISteamScreenshots_TriggerScreenshot(steam_screenshots); + return JS_NULL; +) + +// NETWORKING API +JSC_CCALL(networking_send_p2p, + if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized"); + + // Get SteamID + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + // Get data + size_t data_len; + uint8_t *data = (uint8_t*)js_get_blob_data(js, &data_len, argv[1]); + if (!data) return JS_ThrowTypeError(js, "Second argument must be ArrayBuffer"); + + // Get send type (optional, defaults to reliable) + EP2PSend send_type = k_EP2PSendReliable; + if (argc > 2) { + uint32_t type; + JS_ToUint32(js, &type, argv[2]); + send_type = (EP2PSend)type; + } + + // Get channel (optional, defaults to 0) + int channel = 0; + if (argc > 3) { + JS_ToInt32(js, &channel, argv[3]); + } + + bool success = SteamAPI_ISteamNetworking_SendP2PPacket(steam_networking, sid->id, data, data_len, send_type, channel); + return JS_NewBool(js, success); +) + +JSC_CCALL(networking_read_p2p, + if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized"); + + // Get channel (optional, defaults to 0) + int channel = 0; + if (argc > 0) { + JS_ToInt32(js, &channel, argv[0]); + } + + uint32 msg_size; + if (!SteamAPI_ISteamNetworking_IsP2PPacketAvailable(steam_networking, &msg_size, channel)) { + return JS_NULL; // No packet available + } + + uint8_t *buffer = (uint8_t*)js_malloc(js, msg_size); + if (!buffer) return JS_EXCEPTION; + + CSteamID remote_steamid; + uint32 bytes_read; + bool success = SteamAPI_ISteamNetworking_ReadP2PPacket(steam_networking, buffer, msg_size, &bytes_read, &remote_steamid, channel); + + if (!success) { + js_free(js, buffer); + return JS_NULL; + } + + JSValue result = JS_NewObject(js); + JSValue data_buffer = js_new_blob_stoned_copy(js, buffer, bytes_read); + JSValue steamid_obj = steamid2js(js, remote_steamid.ConvertToUint64()); + + JS_SetPropertyStr(js, result, "data", data_buffer); + JS_SetPropertyStr(js, result, "steamid", steamid_obj); + JS_SetPropertyStr(js, result, "size", JS_NewUint32(js, bytes_read)); + + js_free(js, buffer); + return result; +) + +JSC_CCALL(networking_accept_p2p, + if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + bool success = SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser(steam_networking, sid->id); + return JS_NewBool(js, success); +) + +JSC_CCALL(networking_close_p2p, + if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + // Get channel (optional, defaults to 0) + int channel = 0; + if (argc > 1) { + JS_ToInt32(js, &channel, argv[1]); + } + + bool success = SteamAPI_ISteamNetworking_CloseP2PSessionWithUser(steam_networking, sid->id); + return JS_NewBool(js, success); +) + +JSC_CCALL(networking_close_p2p_channel, + if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + int channel; + JS_ToInt32(js, &channel, argv[1]); + + bool success = SteamAPI_ISteamNetworking_CloseP2PChannelWithUser(steam_networking, sid->id, channel); + return JS_NewBool(js, success); +) + +JSC_CCALL(networking_p2p_session_state, + if (!steam_networking) return JS_ThrowInternalError(js, "Steam networking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + P2PSessionState_t state; + if (!SteamAPI_ISteamNetworking_GetP2PSessionState(steam_networking, sid->id, &state)) { + return JS_NULL; + } + + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "connection_active", JS_NewBool(js, state.m_bConnectionActive)); + JS_SetPropertyStr(js, result, "connecting", JS_NewBool(js, state.m_bConnecting)); + JS_SetPropertyStr(js, result, "session_error", JS_NewUint32(js, state.m_eP2PSessionError)); + JS_SetPropertyStr(js, result, "using_relay", JS_NewBool(js, state.m_bUsingRelay)); + JS_SetPropertyStr(js, result, "bytes_queued_for_send", JS_NewInt32(js, state.m_nBytesQueuedForSend)); + JS_SetPropertyStr(js, result, "packets_queued_for_send", JS_NewInt32(js, state.m_nPacketsQueuedForSend)); + JS_SetPropertyStr(js, result, "remote_ip", JS_NewUint32(js, state.m_nRemoteIP)); + JS_SetPropertyStr(js, result, "remote_port", JS_NewUint32(js, state.m_nRemotePort)); + + return result; +) + +// MATCHMAKING API +JSC_CCALL(matchmaking_create_lobby, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + // Get lobby type (defaults to public) + ELobbyType lobby_type = k_ELobbyTypePublic; + if (argc > 0) { + uint32_t type; + JS_ToUint32(js, &type, argv[0]); + lobby_type = (ELobbyType)type; + } + + // Get max members (defaults to 4) + int max_members = 4; + if (argc > 1) { + JS_ToInt32(js, &max_members, argv[1]); + } + + SteamAPICall_t call = SteamAPI_ISteamMatchmaking_CreateLobby(steam_matchmaking, lobby_type, max_members); + return JS_NewFloat64(js, (double)call); +) + +JSC_CCALL(matchmaking_join_lobby, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + SteamAPICall_t call = SteamAPI_ISteamMatchmaking_JoinLobby(steam_matchmaking, sid->id); + return JS_NewFloat64(js, (double)call); +) + +JSC_CCALL(matchmaking_leave_lobby, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a SteamID"); + + SteamAPI_ISteamMatchmaking_LeaveLobby(steam_matchmaking, sid->id); + return JS_NULL; +) + +JSC_CCALL(matchmaking_invite_user, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *lobby_sid = js2steamid(js, argv[0]); + if (!lobby_sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID"); + + steamid_t *user_sid = js2steamid(js, argv[1]); + if (!user_sid) return JS_ThrowTypeError(js, "Second argument must be a user SteamID"); + + bool success = SteamAPI_ISteamMatchmaking_InviteUserToLobby(steam_matchmaking, lobby_sid->id, user_sid->id); + return JS_NewBool(js, success); +) + +JSC_CCALL(matchmaking_lobby_member_count, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID"); + + int count = SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(steam_matchmaking, sid->id); + return JS_NewInt32(js, count); +) + +JSC_CCALL(matchmaking_lobby_member_by_index, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID"); + + int index; + JS_ToInt32(js, &index, argv[1]); + + uint64_steamid member = SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex(steam_matchmaking, sid->id, index); + return steamid2js(js, member); +) + +JSC_CCALL(matchmaking_lobby_owner, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID"); + + uint64_steamid owner = SteamAPI_ISteamMatchmaking_GetLobbyOwner(steam_matchmaking, sid->id); + return steamid2js(js, owner); +) + +JSC_CCALL(matchmaking_set_lobby_data, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID"); + + const char *key = JS_ToCString(js, argv[1]); + if (!key) return JS_EXCEPTION; + + const char *value = JS_ToCString(js, argv[2]); + if (!value) { + JS_FreeCString(js, key); + return JS_EXCEPTION; + } + + bool success = SteamAPI_ISteamMatchmaking_SetLobbyData(steam_matchmaking, sid->id, key, value); + + JS_FreeCString(js, key); + JS_FreeCString(js, value); + + return JS_NewBool(js, success); +) + +JSC_CCALL(matchmaking_get_lobby_data, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID"); + + const char *key = JS_ToCString(js, argv[1]); + if (!key) return JS_EXCEPTION; + + const char *value = SteamAPI_ISteamMatchmaking_GetLobbyData(steam_matchmaking, sid->id, key); + JS_FreeCString(js, key); + + return JS_NewString(js, value ? value : ""); +) + +JSC_CCALL(matchmaking_set_lobby_member_data, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID"); + + const char *key = JS_ToCString(js, argv[1]); + if (!key) return JS_EXCEPTION; + + const char *value = JS_ToCString(js, argv[2]); + if (!value) { + JS_FreeCString(js, key); + return JS_EXCEPTION; + } + + SteamAPI_ISteamMatchmaking_SetLobbyMemberData(steam_matchmaking, sid->id, key, value); + + JS_FreeCString(js, key); + JS_FreeCString(js, value); + + return JS_NULL; +) + +JSC_CCALL(matchmaking_get_lobby_member_data, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *lobby_sid = js2steamid(js, argv[0]); + if (!lobby_sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID"); + + steamid_t *user_sid = js2steamid(js, argv[1]); + if (!user_sid) return JS_ThrowTypeError(js, "Second argument must be a user SteamID"); + + const char *key = JS_ToCString(js, argv[2]); + if (!key) return JS_EXCEPTION; + + const char *value = SteamAPI_ISteamMatchmaking_GetLobbyMemberData(steam_matchmaking, lobby_sid->id, user_sid->id, key); + JS_FreeCString(js, key); + + return JS_NewString(js, value ? value : ""); +) + +JSC_CCALL(matchmaking_send_lobby_chat_msg, + if (!steam_matchmaking) return JS_ThrowInternalError(js, "Steam matchmaking not initialized"); + + steamid_t *sid = js2steamid(js, argv[0]); + if (!sid) return JS_ThrowTypeError(js, "First argument must be a lobby SteamID"); + + size_t msg_len; + const char *message = JS_ToCStringLen(js, &msg_len, argv[1]); + if (!message) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamMatchmaking_SendLobbyChatMsg(steam_matchmaking, sid->id, message, msg_len); + JS_FreeCString(js, message); + + return JS_NewBool(js, success); +) + +// UGC (User Generated Content) API +JSC_CCALL(ugc_create_query_user_ugc_request, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + uint32 account_id, list_type, matching_type, sort_order, creator_app_id, consumer_app_id, page; + JS_ToUint32(js, &account_id, argv[0]); + JS_ToUint32(js, &list_type, argv[1]); + JS_ToUint32(js, &matching_type, argv[2]); + JS_ToUint32(js, &sort_order, argv[3]); + JS_ToUint32(js, &creator_app_id, argv[4]); + JS_ToUint32(js, &consumer_app_id, argv[5]); + JS_ToUint32(js, &page, argv[6]); + + UGCQueryHandle_t handle = SteamAPI_ISteamUGC_CreateQueryUserUGCRequest(steam_ugc, account_id, (EUserUGCList)list_type, (EUGCMatchingUGCType)matching_type, (EUserUGCListSortOrder)sort_order, creator_app_id, consumer_app_id, page); + return JS_NewFloat64(js, (double)handle); +) + +JSC_CCALL(ugc_create_query_all_ugc_request_page, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + uint32 query_type, matching_type, creator_app_id, consumer_app_id, page; + JS_ToUint32(js, &query_type, argv[0]); + JS_ToUint32(js, &matching_type, argv[1]); + JS_ToUint32(js, &creator_app_id, argv[2]); + JS_ToUint32(js, &consumer_app_id, argv[3]); + JS_ToUint32(js, &page, argv[4]); + + UGCQueryHandle_t handle = SteamAPI_ISteamUGC_CreateQueryAllUGCRequestPage(steam_ugc, (EUGCQuery)query_type, (EUGCMatchingUGCType)matching_type, creator_app_id, consumer_app_id, page); + return JS_NewFloat64(js, (double)handle); +) + +JSC_CCALL(ugc_create_query_ugc_details_request, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + // Get array of published file IDs + if (!JS_IsArray(js, argv[0])) return JS_ThrowTypeError(js, "First argument must be an array of published file IDs"); + + JSValue length_val = JS_GetPropertyStr(js, argv[0], "length"); + uint32 num_ids; + JS_ToUint32(js, &num_ids, length_val); + JS_FreeValue(js, length_val); + + if (num_ids == 0) return JS_ThrowRangeError(js, "Array must contain at least one published file ID"); + + PublishedFileId_t *file_ids = (PublishedFileId_t*)js_malloc(js, sizeof(PublishedFileId_t) * num_ids); + if (!file_ids) return JS_EXCEPTION; + + for (uint32 i = 0; i < num_ids; i++) { + JSValue id_val = JS_GetPropertyUint32(js, argv[0], i); + double id_double; + JS_ToFloat64(js, &id_double, id_val); + file_ids[i] = (PublishedFileId_t)id_double; + JS_FreeValue(js, id_val); + } + + UGCQueryHandle_t handle = SteamAPI_ISteamUGC_CreateQueryUGCDetailsRequest(steam_ugc, file_ids, num_ids); + js_free(js, file_ids); + + return JS_NewFloat64(js, (double)handle); +) + +JSC_CCALL(ugc_send_query_ugc_request, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double; + + SteamAPICall_t call = SteamAPI_ISteamUGC_SendQueryUGCRequest(steam_ugc, handle); + return JS_NewFloat64(js, (double)call); +) + +JSC_CCALL(ugc_release_query_ugc_request, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double; + + bool success = SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(steam_ugc, handle); + return JS_NewBool(js, success); +) + +JSC_CCALL(ugc_add_required_tag, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double; + + const char *tag = JS_ToCString(js, argv[1]); + if (!tag) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUGC_AddRequiredTag(steam_ugc, handle, tag); + JS_FreeCString(js, tag); + + return JS_NewBool(js, success); +) + +JSC_CCALL(ugc_add_excluded_tag, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double; + + const char *tag = JS_ToCString(js, argv[1]); + if (!tag) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUGC_AddExcludedTag(steam_ugc, handle, tag); + JS_FreeCString(js, tag); + + return JS_NewBool(js, success); +) + +JSC_CCALL(ugc_set_search_text, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double; + + const char *search_text = JS_ToCString(js, argv[1]); + if (!search_text) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUGC_SetSearchText(steam_ugc, handle, search_text); + JS_FreeCString(js, search_text); + + return JS_NewBool(js, success); +) + +JSC_CCALL(ugc_set_language, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCQueryHandle_t handle = (UGCQueryHandle_t)handle_double; + + const char *language = JS_ToCString(js, argv[1]); + if (!language) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUGC_SetLanguage(steam_ugc, handle, language); + JS_FreeCString(js, language); + + return JS_NewBool(js, success); +) + +JSC_CCALL(ugc_subscribe_item, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double file_id; + JS_ToFloat64(js, &file_id, argv[0]); + + SteamAPICall_t call = SteamAPI_ISteamUGC_SubscribeItem(steam_ugc, (PublishedFileId_t)file_id); + return JS_NewFloat64(js, (double)call); +) + +JSC_CCALL(ugc_unsubscribe_item, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double file_id; + JS_ToFloat64(js, &file_id, argv[0]); + + SteamAPICall_t call = SteamAPI_ISteamUGC_UnsubscribeItem(steam_ugc, (PublishedFileId_t)file_id); + return JS_NewFloat64(js, (double)call); +) + +JSC_CCALL(ugc_get_num_subscribed_items, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + bool include_locally_disabled = argc > 0 ? JS_ToBool(js, argv[0]) : false; + + uint32 count = SteamAPI_ISteamUGC_GetNumSubscribedItems(steam_ugc, include_locally_disabled); + return JS_NewUint32(js, count); +) + +JSC_CCALL(ugc_get_subscribed_items, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + uint32 max_entries; + JS_ToUint32(js, &max_entries, argv[0]); + + bool include_locally_disabled = argc > 1 ? JS_ToBool(js, argv[1]) : false; + + if (max_entries == 0) return JS_NewArray(js); + + PublishedFileId_t *file_ids = (PublishedFileId_t*)js_malloc(js, sizeof(PublishedFileId_t) * max_entries); + if (!file_ids) return JS_EXCEPTION; + + uint32 actual_count = SteamAPI_ISteamUGC_GetSubscribedItems(steam_ugc, file_ids, max_entries, include_locally_disabled); + + JSValue result = JS_NewArray(js); + for (uint32 i = 0; i < actual_count; i++) { + JS_SetPropertyUint32(js, result, i, JS_NewFloat64(js, (double)file_ids[i])); + } + + js_free(js, file_ids); + return result; +) + +JSC_CCALL(ugc_get_item_state, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double file_id; + JS_ToFloat64(js, &file_id, argv[0]); + + uint32 state = SteamAPI_ISteamUGC_GetItemState(steam_ugc, (PublishedFileId_t)file_id); + return JS_NewUint32(js, state); +) + +JSC_CCALL(ugc_get_item_install_info, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double file_id; + JS_ToFloat64(js, &file_id, argv[0]); + + uint64 size_on_disk; + char folder[1024]; + uint32 timestamp; + + bool success = SteamAPI_ISteamUGC_GetItemInstallInfo(steam_ugc, (PublishedFileId_t)file_id, &size_on_disk, folder, sizeof(folder), ×tamp); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "size_on_disk", JS_NewFloat64(js, (double)size_on_disk)); + JS_SetPropertyStr(js, obj, "folder", JS_NewString(js, folder)); + JS_SetPropertyStr(js, obj, "timestamp", JS_NewUint32(js, timestamp)); + return obj; +) + +JSC_CCALL(ugc_get_item_download_info, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double file_id; + JS_ToFloat64(js, &file_id, argv[0]); + + uint64 bytes_downloaded, bytes_total; + + bool success = SteamAPI_ISteamUGC_GetItemDownloadInfo(steam_ugc, (PublishedFileId_t)file_id, &bytes_downloaded, &bytes_total); + + if (!success) return JS_NULL; + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "bytes_downloaded", JS_NewFloat64(js, (double)bytes_downloaded)); + JS_SetPropertyStr(js, obj, "bytes_total", JS_NewFloat64(js, (double)bytes_total)); + return obj; +) + +JSC_CCALL(ugc_download_item, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double file_id; + JS_ToFloat64(js, &file_id, argv[0]); + + bool high_priority = argc > 1 ? JS_ToBool(js, argv[1]) : false; + + bool success = SteamAPI_ISteamUGC_DownloadItem(steam_ugc, (PublishedFileId_t)file_id, high_priority); + return JS_NewBool(js, success); +) + +JSC_CCALL(ugc_create_item, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + uint32 consumer_app_id, file_type; + JS_ToUint32(js, &consumer_app_id, argv[0]); + JS_ToUint32(js, &file_type, argv[1]); + + SteamAPICall_t call = SteamAPI_ISteamUGC_CreateItem(steam_ugc, consumer_app_id, (EWorkshopFileType)file_type); + return JS_NewFloat64(js, (double)call); +) + +JSC_CCALL(ugc_start_item_update, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + uint32 consumer_app_id; + double file_id; + JS_ToUint32(js, &consumer_app_id, argv[0]); + JS_ToFloat64(js, &file_id, argv[1]); + + UGCUpdateHandle_t handle = SteamAPI_ISteamUGC_StartItemUpdate(steam_ugc, consumer_app_id, (PublishedFileId_t)file_id); + return JS_NewFloat64(js, (double)handle); +) + +JSC_CCALL(ugc_set_item_title, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double; + + const char *title = JS_ToCString(js, argv[1]); + if (!title) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUGC_SetItemTitle(steam_ugc, handle, title); + JS_FreeCString(js, title); + + return JS_NewBool(js, success); +) + +JSC_CCALL(ugc_set_item_description, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double; + + const char *description = JS_ToCString(js, argv[1]); + if (!description) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUGC_SetItemDescription(steam_ugc, handle, description); + JS_FreeCString(js, description); + + return JS_NewBool(js, success); +) + +JSC_CCALL(ugc_set_item_content, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double; + + const char *content_folder = JS_ToCString(js, argv[1]); + if (!content_folder) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUGC_SetItemContent(steam_ugc, handle, content_folder); + JS_FreeCString(js, content_folder); + + return JS_NewBool(js, success); +) + +JSC_CCALL(ugc_set_item_preview, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double; + + const char *preview_file = JS_ToCString(js, argv[1]); + if (!preview_file) return JS_EXCEPTION; + + bool success = SteamAPI_ISteamUGC_SetItemPreview(steam_ugc, handle, preview_file); + JS_FreeCString(js, preview_file); + + return JS_NewBool(js, success); +) + +JSC_CCALL(ugc_submit_item_update, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double; + + const char *change_note = argc > 1 ? JS_ToCString(js, argv[1]) : NULL; + + SteamAPICall_t call = SteamAPI_ISteamUGC_SubmitItemUpdate(steam_ugc, handle, change_note); + + if (change_note) JS_FreeCString(js, change_note); + + return JS_NewFloat64(js, (double)call); +) + +JSC_CCALL(ugc_get_item_update_progress, + if (!steam_ugc) return JS_ThrowInternalError(js, "Steam UGC not initialized"); + + double handle_double; + JS_ToFloat64(js, &handle_double, argv[0]); + UGCUpdateHandle_t handle = (UGCUpdateHandle_t)handle_double; + + uint64 bytes_processed, bytes_total; + + EItemUpdateStatus status = SteamAPI_ISteamUGC_GetItemUpdateProgress(steam_ugc, handle, &bytes_processed, &bytes_total); + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "status", JS_NewUint32(js, status)); + JS_SetPropertyStr(js, obj, "bytes_processed", JS_NewFloat64(js, (double)bytes_processed)); + JS_SetPropertyStr(js, obj, "bytes_total", JS_NewFloat64(js, (double)bytes_total)); + return obj; +) + // 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), + MIST_FUNC_DEF(stats, request, 0), + MIST_FUNC_DEF(stats, store, 0), + MIST_FUNC_DEF(stats, get_int, 1), + MIST_FUNC_DEF(stats, get_float, 1), + MIST_FUNC_DEF(stats, set_int, 2), + MIST_FUNC_DEF(stats, set_float, 2), + MIST_FUNC_DEF(stats, update_avg_rate, 3), + MIST_FUNC_DEF(stats, get_user_stat_int, 2), + MIST_FUNC_DEF(stats, get_user_stat_float, 2), + MIST_FUNC_DEF(stats, reset_all, 1), + MIST_FUNC_DEF(stats, get_number_of_current_players, 0), + MIST_FUNC_DEF(stats, request_global_achievement_percentages, 0), + MIST_FUNC_DEF(stats, request_global, 1), + MIST_FUNC_DEF(stats, get_global_stat_int64, 1), + MIST_FUNC_DEF(stats, get_global_stat_double, 1), + MIST_FUNC_DEF(stats, get_global_stat_history_int64, 1), + MIST_FUNC_DEF(stats, get_global_stat_history_double, 1), }; 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), + MIST_FUNC_DEF(achievement, get, 1), + MIST_FUNC_DEF(achievement, set, 1), + MIST_FUNC_DEF(achievement, clear, 1), + MIST_FUNC_DEF(achievement, count, 0), + MIST_FUNC_DEF(achievement, name, 1), + MIST_FUNC_DEF(achievement, get_and_unlock_time, 1), + MIST_FUNC_DEF(achievement, get_icon, 1), + MIST_FUNC_DEF(achievement, get_display_attribute, 2), + MIST_FUNC_DEF(achievement, indicate_progress, 3), + MIST_FUNC_DEF(achievement, get_user_achievement, 2), + MIST_FUNC_DEF(achievement, get_user_achievement_and_unlock_time, 2), + MIST_FUNC_DEF(achievement, get_achieved_percent, 1), + MIST_FUNC_DEF(achievement, get_progress_limits_int, 1), + MIST_FUNC_DEF(achievement, get_progress_limits_float, 1), + MIST_FUNC_DEF(achievement, get_most_achieved_info, 0), + MIST_FUNC_DEF(achievement, get_next_most_achieved_info, 1), +}; + +static const JSCFunctionListEntry js_steam_leaderboard_funcs[] = { + MIST_FUNC_DEF(leaderboard, find_or_create, 3), + MIST_FUNC_DEF(leaderboard, find, 1), + MIST_FUNC_DEF(leaderboard, get_name, 1), + MIST_FUNC_DEF(leaderboard, get_entry_count, 1), + MIST_FUNC_DEF(leaderboard, get_sort_method, 1), + MIST_FUNC_DEF(leaderboard, get_display_type, 1), + MIST_FUNC_DEF(leaderboard, download_entries, 4), + MIST_FUNC_DEF(leaderboard, download_entries_for_users, 2), + MIST_FUNC_DEF(leaderboard, get_downloaded_entry, 2), + MIST_FUNC_DEF(leaderboard, upload_score, 3), + MIST_FUNC_DEF(leaderboard, attach_ugc, 2), }; 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), + MIST_FUNC_DEF(app, id, 0), + MIST_FUNC_DEF(app, owner, 0), + MIST_FUNC_DEF(app, installed, 1), + MIST_FUNC_DEF(app, subscribed, 0), + MIST_FUNC_DEF(app, language, 0), + MIST_FUNC_DEF(app, dlc_installed, 1), + MIST_FUNC_DEF(app, is_low_violence, 0), + MIST_FUNC_DEF(app, is_cybercafe, 0), + MIST_FUNC_DEF(app, is_vac_banned, 0), + MIST_FUNC_DEF(app, available_languages, 0), + MIST_FUNC_DEF(app, is_subscribed_app, 1), + MIST_FUNC_DEF(app, earliest_purchase_time, 1), + MIST_FUNC_DEF(app, is_subscribed_from_free_weekend, 0), + MIST_FUNC_DEF(app, dlc_count, 0), + MIST_FUNC_DEF(app, get_dlc_data_by_index, 1), + MIST_FUNC_DEF(app, install_dlc, 1), + MIST_FUNC_DEF(app, uninstall_dlc, 1), + MIST_FUNC_DEF(app, request_proof_of_purchase_key, 1), + MIST_FUNC_DEF(app, get_current_beta_name, 0), + MIST_FUNC_DEF(app, mark_content_corrupt, 1), + MIST_FUNC_DEF(app, get_installed_depots, 1), + MIST_FUNC_DEF(app, get_install_dir, 1), + MIST_FUNC_DEF(app, get_launch_query_param, 1), + MIST_FUNC_DEF(app, get_dlc_download_progress, 1), + MIST_FUNC_DEF(app, get_build_id, 0), + MIST_FUNC_DEF(app, request_all_proof_of_purchase_keys, 0), + MIST_FUNC_DEF(app, get_file_details, 1), + MIST_FUNC_DEF(app, get_launch_command_line, 0), + MIST_FUNC_DEF(app, is_subscribed_from_family_sharing, 0), + MIST_FUNC_DEF(app, is_timed_trial, 0), + MIST_FUNC_DEF(app, set_dlc_context, 1), + MIST_FUNC_DEF(app, get_num_betas, 0), + MIST_FUNC_DEF(app, get_beta_info, 1), + MIST_FUNC_DEF(app, set_active_beta, 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), + MIST_FUNC_DEF(user, logged_on, 0), + MIST_FUNC_DEF(user, steam_id, 0), + MIST_FUNC_DEF(user, level, 0), + MIST_FUNC_DEF(user, hsteam_user, 0), + MIST_FUNC_DEF(user, get_user_data_folder, 0), + MIST_FUNC_DEF(user, start_voice_recording, 0), + MIST_FUNC_DEF(user, stop_voice_recording, 0), + MIST_FUNC_DEF(user, get_available_voice, 1), + MIST_FUNC_DEF(user, get_voice, 1), + MIST_FUNC_DEF(user, decompress_voice, 3), + MIST_FUNC_DEF(user, get_voice_optimal_sample_rate, 0), + MIST_FUNC_DEF(user, get_auth_session_ticket, 0), + MIST_FUNC_DEF(user, get_auth_ticket_for_web_api, 1), + MIST_FUNC_DEF(user, begin_auth_session, 2), + MIST_FUNC_DEF(user, end_auth_session, 1), + MIST_FUNC_DEF(user, cancel_auth_ticket, 1), + MIST_FUNC_DEF(user, has_license_for_app, 2), + MIST_FUNC_DEF(user, is_behind_nat, 0), + MIST_FUNC_DEF(user, advertise_game, 3), + MIST_FUNC_DEF(user, request_encrypted_app_ticket, 1), + MIST_FUNC_DEF(user, get_encrypted_app_ticket, 0), + MIST_FUNC_DEF(user, get_game_badge_level, 2), + MIST_FUNC_DEF(user, request_store_auth_url, 1), + MIST_FUNC_DEF(user, is_phone_verified, 0), + MIST_FUNC_DEF(user, is_two_factor_enabled, 0), + MIST_FUNC_DEF(user, is_phone_identifying, 0), + MIST_FUNC_DEF(user, is_phone_requiring_verification, 0), + MIST_FUNC_DEF(user, get_market_eligibility, 0), + MIST_FUNC_DEF(user, get_duration_control, 0), + MIST_FUNC_DEF(user, set_duration_control_online_state, 1), + MIST_FUNC_DEF(user, track_app_usage_event, 3), + MIST_FUNC_DEF(user, initiate_game_connection_deprecated, 4), + MIST_FUNC_DEF(user, terminate_game_connection_deprecated, 2), }; static const JSCFunctionListEntry js_steam_friends_funcs[] = { - MIST_FUNC_DEF(steam, friends_name, 0), - MIST_FUNC_DEF(steam, friends_state, 0), + MIST_FUNC_DEF(friends, name, 0), + MIST_FUNC_DEF(friends, state, 0), + MIST_FUNC_DEF(friends, count, 1), + MIST_FUNC_DEF(friends, get_friend, 2), + MIST_FUNC_DEF(friends, get_friend_name, 1), + MIST_FUNC_DEF(friends, activate_overlay, 1), + MIST_FUNC_DEF(friends, activate_overlay_to_user, 2), + MIST_FUNC_DEF(friends, activate_overlay_to_web, 1), + MIST_FUNC_DEF(friends, set_rich_presence, 2), + MIST_FUNC_DEF(friends, clear_rich_presence, 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), + MIST_FUNC_DEF(cloud, enabled_app, 0), + MIST_FUNC_DEF(cloud, enabled_account, 0), + MIST_FUNC_DEF(cloud, enable, 1), + MIST_FUNC_DEF(cloud, quota, 0), + MIST_FUNC_DEF(cloud, write, 2), + MIST_FUNC_DEF(cloud, read, 1), + MIST_FUNC_DEF(cloud, delete, 1), + MIST_FUNC_DEF(cloud, exists, 1), +}; + +static const JSCFunctionListEntry js_steam_utils_funcs[] = { + MIST_FUNC_DEF(utils, overlay_enabled, 0), + MIST_FUNC_DEF(utils, big_picture_mode, 0), + MIST_FUNC_DEF(utils, vr_mode, 0), + MIST_FUNC_DEF(utils, steam_deck, 0), + MIST_FUNC_DEF(utils, battery_power, 0), + MIST_FUNC_DEF(utils, seconds_since_app_active, 0), + MIST_FUNC_DEF(utils, ip_country, 0), + MIST_FUNC_DEF(utils, ui_language, 0), + MIST_FUNC_DEF(utils, get_seconds_since_computer_active, 0), + MIST_FUNC_DEF(utils, get_connected_universe, 0), + MIST_FUNC_DEF(utils, get_server_real_time, 0), + MIST_FUNC_DEF(utils, get_image_size, 1), + MIST_FUNC_DEF(utils, get_image_rgba, 1), + MIST_FUNC_DEF(utils, set_overlay_notification_position, 1), + MIST_FUNC_DEF(utils, is_api_call_completed, 1), + MIST_FUNC_DEF(utils, get_api_call_failure_reason, 1), + MIST_FUNC_DEF(utils, get_ipc_call_count, 0), + MIST_FUNC_DEF(utils, overlay_needs_present, 0), + MIST_FUNC_DEF(utils, check_file_signature, 1), + MIST_FUNC_DEF(utils, show_gamepad_text_input, 5), + MIST_FUNC_DEF(utils, get_entered_gamepad_text_length, 0), + MIST_FUNC_DEF(utils, get_entered_gamepad_text_input, 0), + MIST_FUNC_DEF(utils, set_overlay_notification_inset, 2), + MIST_FUNC_DEF(utils, start_vr_dashboard, 0), + MIST_FUNC_DEF(utils, is_vr_headset_streaming_enabled, 0), + MIST_FUNC_DEF(utils, set_vr_headset_streaming_enabled, 1), + MIST_FUNC_DEF(utils, is_steam_china_launcher, 0), + MIST_FUNC_DEF(utils, init_filter_text, 1), + MIST_FUNC_DEF(utils, filter_text, 3), + MIST_FUNC_DEF(utils, get_ipv6_connectivity_state, 1), + MIST_FUNC_DEF(utils, show_floating_gamepad_text_input, 5), + MIST_FUNC_DEF(utils, set_game_launcher_mode, 1), + MIST_FUNC_DEF(utils, dismiss_floating_gamepad_text_input, 0), + MIST_FUNC_DEF(utils, dismiss_gamepad_text_input, 0), +}; + +static const JSCFunctionListEntry js_steam_screenshots_funcs[] = { + MIST_FUNC_DEF(screenshots, write, 4), + MIST_FUNC_DEF(screenshots, trigger, 0), +}; + +static const JSCFunctionListEntry js_steam_networking_funcs[] = { + MIST_FUNC_DEF(networking, send_p2p, 4), + MIST_FUNC_DEF(networking, read_p2p, 1), + MIST_FUNC_DEF(networking, accept_p2p, 1), + MIST_FUNC_DEF(networking, close_p2p, 2), + MIST_FUNC_DEF(networking, close_p2p_channel, 2), + MIST_FUNC_DEF(networking, p2p_session_state, 1), +}; + +static const JSCFunctionListEntry js_steam_matchmaking_funcs[] = { + MIST_FUNC_DEF(matchmaking, create_lobby, 2), + MIST_FUNC_DEF(matchmaking, join_lobby, 1), + MIST_FUNC_DEF(matchmaking, leave_lobby, 1), + MIST_FUNC_DEF(matchmaking, invite_user, 2), + MIST_FUNC_DEF(matchmaking, lobby_member_count, 1), + MIST_FUNC_DEF(matchmaking, lobby_member_by_index, 2), + MIST_FUNC_DEF(matchmaking, lobby_owner, 1), + MIST_FUNC_DEF(matchmaking, set_lobby_data, 3), + MIST_FUNC_DEF(matchmaking, get_lobby_data, 2), + MIST_FUNC_DEF(matchmaking, set_lobby_member_data, 3), + MIST_FUNC_DEF(matchmaking, get_lobby_member_data, 3), + MIST_FUNC_DEF(matchmaking, send_lobby_chat_msg, 2), +}; + +static const JSCFunctionListEntry js_steam_ugc_funcs[] = { + MIST_FUNC_DEF(ugc, create_query_user_ugc_request, 7), + MIST_FUNC_DEF(ugc, create_query_all_ugc_request_page, 5), + MIST_FUNC_DEF(ugc, create_query_ugc_details_request, 1), + MIST_FUNC_DEF(ugc, send_query_ugc_request, 1), + MIST_FUNC_DEF(ugc, release_query_ugc_request, 1), + MIST_FUNC_DEF(ugc, add_required_tag, 2), + MIST_FUNC_DEF(ugc, add_excluded_tag, 2), + MIST_FUNC_DEF(ugc, set_search_text, 2), + MIST_FUNC_DEF(ugc, set_language, 2), + MIST_FUNC_DEF(ugc, subscribe_item, 1), + MIST_FUNC_DEF(ugc, unsubscribe_item, 1), + MIST_FUNC_DEF(ugc, get_num_subscribed_items, 1), + MIST_FUNC_DEF(ugc, get_subscribed_items, 2), + MIST_FUNC_DEF(ugc, get_item_state, 1), + MIST_FUNC_DEF(ugc, get_item_install_info, 1), + MIST_FUNC_DEF(ugc, get_item_download_info, 1), + MIST_FUNC_DEF(ugc, download_item, 2), + MIST_FUNC_DEF(ugc, create_item, 2), + MIST_FUNC_DEF(ugc, start_item_update, 2), + MIST_FUNC_DEF(ugc, set_item_title, 2), + MIST_FUNC_DEF(ugc, set_item_description, 2), + MIST_FUNC_DEF(ugc, set_item_content, 2), + MIST_FUNC_DEF(ugc, set_item_preview, 2), + MIST_FUNC_DEF(ugc, submit_item_update, 2), + MIST_FUNC_DEF(ugc, get_item_update_progress, 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), + MIST_FUNC_DEF(steam, init, 0), + MIST_FUNC_DEF(steam, shutdown, 0), + MIST_FUNC_DEF(steam, run_callbacks, 0), }; -extern "C" JSValue js_steam_use(JSContext *js) { +JSValue js_steam_use(JSContext *js) { + // Register SteamID class if not already done + static int steamid_class_initialized = 0; + if (!steamid_class_initialized) { + JS_NewClassID(&js_steamid_id); + JS_NewClass(JS_GetRuntime(js), js_steamid_id, &js_steamid_class); + + JSValue steamid_proto = JS_NewObject(js); + JS_SetPropertyFunctionList(js, steamid_proto, js_steamid_proto_funcs, countof(js_steamid_proto_funcs)); + JS_SetClassProto(js, js_steamid_id, steamid_proto); + + steamid_class_initialized = 1; + } + JSValue steam = JS_NewObject(js); JS_SetPropertyFunctionList(js, steam, js_steam_funcs, countof(js_steam_funcs)); @@ -483,6 +3202,11 @@ extern "C" JSValue js_steam_use(JSContext *js) { JS_SetPropertyFunctionList(js, achievement, js_steam_achievement_funcs, countof(js_steam_achievement_funcs)); JS_SetPropertyStr(js, steam, "achievement", achievement); + // Leaderboard sub-object + JSValue leaderboard = JS_NewObject(js); + JS_SetPropertyFunctionList(js, leaderboard, js_steam_leaderboard_funcs, countof(js_steam_leaderboard_funcs)); + JS_SetPropertyStr(js, steam, "leaderboard", leaderboard); + // App sub-object JSValue app = JS_NewObject(js); JS_SetPropertyFunctionList(js, app, js_steam_app_funcs, countof(js_steam_app_funcs)); @@ -503,9 +3227,36 @@ extern "C" JSValue js_steam_use(JSContext *js) { JS_SetPropertyFunctionList(js, cloud, js_steam_cloud_funcs, countof(js_steam_cloud_funcs)); JS_SetPropertyStr(js, steam, "cloud", cloud); + // Utils sub-object + JSValue utils = JS_NewObject(js); + JS_SetPropertyFunctionList(js, utils, js_steam_utils_funcs, countof(js_steam_utils_funcs)); + JS_SetPropertyStr(js, steam, "utils", utils); + + // Screenshots sub-object + JSValue screenshots = JS_NewObject(js); + JS_SetPropertyFunctionList(js, screenshots, js_steam_screenshots_funcs, countof(js_steam_screenshots_funcs)); + JS_SetPropertyStr(js, steam, "screenshots", screenshots); + + // Networking sub-object + JSValue networking = JS_NewObject(js); + JS_SetPropertyFunctionList(js, networking, js_steam_networking_funcs, countof(js_steam_networking_funcs)); + JS_SetPropertyStr(js, steam, "networking", networking); + + // Matchmaking sub-object + JSValue matchmaking = JS_NewObject(js); + JS_SetPropertyFunctionList(js, matchmaking, js_steam_matchmaking_funcs, countof(js_steam_matchmaking_funcs)); + JS_SetPropertyStr(js, steam, "matchmaking", matchmaking); + + // UGC sub-object + JSValue ugc = JS_NewObject(js); + JS_SetPropertyFunctionList(js, ugc, js_steam_ugc_funcs, countof(js_steam_ugc_funcs)); + JS_SetPropertyStr(js, steam, "ugc", ugc); + return steam; } +} // extern "C" + #else // Stub when Steam is disabled extern "C" JSValue js_steam_use(JSContext *js) { diff --git a/source/qjs_steam.h b/source/qjs_steam.h index 59722356..94e4e7db 100644 --- a/source/qjs_steam.h +++ b/source/qjs_steam.h @@ -3,6 +3,14 @@ #include "cell.h" +#ifdef __cplusplus +extern "C" { +#endif + JSValue js_steam_use(JSContext *js); +#ifdef __cplusplus +} +#endif + #endif // QJS_STEAM_H \ No newline at end of file diff --git a/source/render.h b/source/render.h index 8d412705..6064418a 100644 --- a/source/render.h +++ b/source/render.h @@ -50,10 +50,6 @@ static inline rgba vec2rgba(HMM_Vec4 v) { } // rectangles are always defined with [x,y] in the bottom left -struct rect { - float x,y,w,h; -}; - typedef SDL_FRect rect; typedef SDL_Rect irect;