changeset 973:7fb8f62a9ea5

add: api to interact with steam
author sam <sam@basx.dev>
date Thu, 04 Apr 2024 17:21:56 +0700
parents 6f71f765a546
children 2a5e7b54736e
files libs/libsteam_api.so libs/steam_api.dll semicongine.nim semicongine/build.nim semicongine/engine.nim semicongine/steam.nim
diffstat 6 files changed, 102 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
Binary file libs/libsteam_api.so has changed
Binary file libs/steam_api.dll has changed
--- a/semicongine.nim	Wed Apr 03 21:07:33 2024 +0700
+++ b/semicongine.nim	Thu Apr 04 17:21:56 2024 +0700
@@ -17,6 +17,7 @@
 import semicongine/renderer
 import semicongine/resources
 import semicongine/settings
+import semicongine/steam
 import semicongine/text
 import semicongine/platform/window
 import semicongine/vulkan
@@ -34,6 +35,7 @@
 export renderer
 export resources
 export settings
+export steam
 export text
 export window
 export vulkan
--- a/semicongine/build.nim	Wed Apr 03 21:07:33 2024 +0700
+++ b/semicongine/build.nim	Thu Apr 04 17:21:56 2024 +0700
@@ -9,7 +9,15 @@
 
 const BLENDER_CONVERT_SCRIPT = currentSourcePath().parentDir().parentDir().joinPath("tools/blender_gltf_converter.py")
 const STEAMCMD_ZIP = currentSourcePath().parentDir().parentDir().joinPath("tools/steamcmd.zip")
-const STEAM_DIR_NAME = "steam"
+const STEAMBUILD_DIR_NAME = "steam"
+
+var STEAMLIB: string
+if defined(linux):
+  STEAMLIB = currentSourcePath().parentDir().parentDir().joinPath("libs/libsteam_api.so")
+elif defined(windows):
+  STEAMLIB = currentSourcePath().parentDir().parentDir().joinPath("libs/steam_api.dll")
+else:
+  raise newException(Exception, "Unsupported platform")
 
 proc semicongine_builddir*(buildname: string, builddir = "./build"): string =
   var platformDir = "unkown"
@@ -30,8 +38,9 @@
     switch("define", "VK_USE_PLATFORM_WIN32_KHR")
     switch("app", "gui")
   switch("outdir", semicongine_builddir(buildname, builddir = builddir))
+  switch("passL", "-Wl,-rpath,'$ORIGIN'") # adds directory of executable to dynlib search path
 
-proc semicongine_pack*(outdir: string, bundleType: string, resourceRoot: string) =
+proc semicongine_pack*(outdir: string, bundleType: string, resourceRoot: string, withSteam: bool) =
   switch("define", "PACKAGETYPE=" & bundleType)
 
   outdir.rmDir()
@@ -52,6 +61,8 @@
           exec &"powershell Compress-Archive * {outputfile}"
   elif bundleType == "exe":
     switch("define", "BUILD_RESOURCEROOT=" & joinPath(getCurrentDir(), resourceRoot)) # required for in-exe packing of resources, must be absolute
+  if withSteam:
+    STEAMLIB.cpFile(outdir.joinPath(STEAMLIB.extractFilename))
 
 proc semicongine_zip*(dir: string) =
   withdir dir.parentDir:
@@ -120,7 +131,7 @@
   if not defined(linux):
     echo "steam uploads must be done on linux for now"
     return
-  let steamdir = thisDir().joinPath(STEAM_DIR_NAME)
+  let steamdir = thisDir().joinPath(STEAMBUILD_DIR_NAME)
   if not dirExists(steamdir):
     steamdir.mkDir
     let zipFilename = STEAMCMD_ZIP.extractFilename
@@ -131,6 +142,6 @@
       exec "steamcmd/steamcmd.sh +quit" # self-update steamcmd
 
   let
-    steamcmd = STEAM_DIR_NAME.joinPath("steamcmd").joinPath("steamcmd.sh")
+    steamcmd = STEAMBUILD_DIR_NAME.joinPath("steamcmd").joinPath("steamcmd.sh")
     scriptPath = "..".joinPath("..").joinPath(buildscript)
   exec &"./{steamcmd} +login \"{steamaccount}\" \"{password}\" +run_app_build {scriptPath} +quit"
--- a/semicongine/engine.nim	Wed Apr 03 21:07:33 2024 +0700
+++ b/semicongine/engine.nim	Thu Apr 04 17:21:56 2024 +0700
@@ -22,6 +22,8 @@
 import ./text
 import ./panel
 
+import ./steam
+
 const COUNT_N_RENDERTIMES = 199
 
 type
@@ -71,6 +73,8 @@
     engine.debugger.destroy()
   engine.window.destroy()
   engine.instance.destroy()
+  if SteamAvailable():
+    SteamShutdown()
 
 
 proc initEngine*(
@@ -86,6 +90,12 @@
   echo "Set log level to ", ENGINE_LOGLEVEL
   setLogFilter(ENGINE_LOGLEVEL)
 
+  TrySteamInit()
+  if SteamAvailable():
+    echo "Starting with Steam enabled"
+  else:
+    echo "Starting without Steam enabled"
+
   result.state = Starting
   result.exitHandler = exitHandler
   result.resizeHandler = resizeHandler
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semicongine/steam.nim	Thu Apr 04 17:21:56 2024 +0700
@@ -0,0 +1,75 @@
+import std/dynlib
+import std/strutils
+import std/logging
+
+var
+  steam_api: LibHandle
+  steam_is_loaded = false
+
+when defined(linux):
+  proc dlerror(): cstring {.stdcall, importc.}
+  steam_api = "libsteam_api.so".loadLib()
+  if steam_api == nil:
+    echo dlerror()
+elif defined(windows):
+  steam_api = "steam_api".loadLib()
+
+
+# required to store reference, when calling certain APIs
+type
+  SteamUserStatsRef = ptr object
+var userStats: SteamUserStatsRef
+
+# load function points for steam API
+var
+  Shutdown*: proc() {.stdcall.}
+  Init: proc(msg: ptr array[1024, char]): cint {.stdcall.}
+  SteamUserStats: proc(): SteamUserStatsRef {.stdcall.}
+  RequestCurrentStats: proc(self: SteamUserStatsRef): bool {.stdcall.} # needs to be called before the achievment-stuff
+  ClearAchievement: proc(self: SteamUserStatsRef, pchName: cstring): bool {.stdcall.}
+  SetAchievement: proc(self: SteamUserStatsRef, pchName: cstring): bool {.stdcall.}
+  StoreStats: proc(self: SteamUserStatsRef): bool {.stdcall.}          # needs to be called in order for achievments to be saved
+                                                                       # dynlib-helper function
+proc loadFunc[T](nimFunc: var T, dllFuncName: string) =
+  nimFunc = cast[T](steam_api.checkedSymAddr(dllFuncName))
+if steam_api != nil:
+  loadFunc(Init, "SteamAPI_InitFlat")
+  loadFunc(Shutdown, "SteamAPI_Shutdown")
+  loadFunc(SteamUserStats, "SteamAPI_SteamUserStats_v012")
+  loadFunc(RequestCurrentStats, "SteamAPI_ISteamUserStats_RequestCurrentStats")
+  loadFunc(ClearAchievement, "SteamAPI_ISteamUserStats_ClearAchievement")
+  loadFunc(SetAchievement, "SteamAPI_ISteamUserStats_SetAchievement")
+  loadFunc(StoreStats, "SteamAPI_ISteamUserStats_StoreStats")
+
+
+# nice wrappers for steam API
+
+proc SteamRequestCurrentStats*(): bool =
+  RequestCurrentStats(userStats)
+
+proc SteamClearAchievement*(name: string): bool =
+  userStats.ClearAchievement(name.cstring)
+
+proc SteamSetAchievement*(name: string): bool =
+  userStats.SetAchievement(name.cstring)
+
+proc SteamStoreStats*(name: string): bool =
+  userStats.StoreStats()
+
+proc SteamShutdown*() =
+  Shutdown()
+
+
+# helper funcs
+proc SteamAvailable*(): bool =
+  steam_api != nil and steam_is_loaded
+
+# first function that should be called
+proc TrySteamInit*() =
+  if steam_api != nil and not steam_is_loaded:
+    var msg: array[1024, char]
+    let success = Init(addr msg) == 0
+    warn join(@msg, "")
+    if success:
+      userStats = SteamUserStats()
+      steam_is_loaded = SteamRequestCurrentStats()