# HG changeset patch # User sam # Date 1712226116 -25200 # Node ID 7fb8f62a9ea5ec80b82d0e1f5be2d4e406e24901 # Parent 6f71f765a546adaf17aabeb9b51885bb0d30e8a9 add: api to interact with steam diff -r 6f71f765a546 -r 7fb8f62a9ea5 libs/libsteam_api.so Binary file libs/libsteam_api.so has changed diff -r 6f71f765a546 -r 7fb8f62a9ea5 libs/steam_api.dll Binary file libs/steam_api.dll has changed diff -r 6f71f765a546 -r 7fb8f62a9ea5 semicongine.nim --- 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 diff -r 6f71f765a546 -r 7fb8f62a9ea5 semicongine/build.nim --- 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" diff -r 6f71f765a546 -r 7fb8f62a9ea5 semicongine/engine.nim --- 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 diff -r 6f71f765a546 -r 7fb8f62a9ea5 semicongine/steam.nim --- /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()