29. Module steam

The steam plugin integrates Steamworks SDK into mini-mbm games, exposing achievements, stats, leaderboards, cloud saves, overlay dialogs, and DLC detection to Lua.

Important

Steam is a desktop platform. This plugin is supported on Windows, Linux, and macOS only. It is not available on Android or iOS.

Note

Load the plugin with require "steam" (or require "libsteam") inside onInitScene(). Always check steam.isReady() before calling any other function — it returns false if the Steam client is not running or the user does not own the game.

29.1. Prerequisites

  1. Steamworks Partner Accountenrol here. Each title uses its own App ID; the plugin never hard-codes it.

  2. Steamworks SDK — download from the partner portal under Developer Tools → Steamworks SDK. Extract it locally (e.g. /home/user/steamworks_sdk or C:\steamworks_sdk).

  3. Steam client — must be running on the development machine during testing.

  4. steam_appid.txt — create a plain-text file containing only your App ID (e.g. 480) and place it next to the mini-mbm executable for development runs. Retail builds launched via the Steam client receive the App ID automatically.

29.2. Building

29.2.1. Linux / macOS (CMake)

mkdir -p build/linux_debug && cd build/linux_debug
cmake ../.. \
  -DPLAT=Linux \
  -DUSE_ALL=1 \
  -DUSE_STEAM=1 \
  -DSTEAMWORKS_SDK_PATH=/path/to/steamworks_sdk \
  -DCMAKE_BUILD_TYPE=Debug
make -j$(nproc)

Replace Linux with MacOs for macOS, and Debug with Release for distribution builds.

29.2.2. Windows (Visual Studio 2022)

  1. Open platform-msvs/mini-mbm.sln in Visual Studio 2022.

  2. Set the STEAMWORKS_SDK_PATH environment variable before opening Visual Studio:

    • Open the Start menu and search for “Edit the system environment variables”.

    • Click Environment Variables…, then under User variables click New.

    • Name: STEAMWORKS_SDK_PATH Value: C:\steamworks_sdk (your actual path).

    • Click OK on all dialogs, then restart Visual Studio.

  3. The steam project is disabled by default in Configuration Manager (no SDK headers available without the SDK). Enable it: go to Build → Configuration Manager and check the Build checkbox for the steam project.

  4. Build the steam project. copy-steam-dll.bat runs automatically after a successful build and copies the correct Steam DLL (steam_api64.dll or steam_api.dll) into the output folder.

  5. Place steam_appid.txt (containing only your App ID number, e.g. 480) next to mini-mbm.exe.

29.2.3. Windows (CMake + MinGW)

mkdir build\mingw_debug && cd build\mingw_debug
cmake ..\.. -G "MinGW Makefiles" ^
  -DPLAT=Windows ^
  -DUSE_ALL=1 ^
  -DUSE_STEAM=1 ^
  -DSTEAMWORKS_SDK_PATH=C:\steamworks_sdk ^
  -DCMAKE_BUILD_TYPE=Debug
mingw32-make -j%NUMBER_OF_PROCESSORS%

29.3. Runtime Deployment

The Steam shared library is copied to the output directory automatically as a post-build step. For final distribution, include the Steam library in your shipped game folder:

Platform

Library to ship

Windows x64

redistributable_bin\win64\steam_api64.dll

Windows x86

redistributable_bin\steam_api.dll

Linux x64

redistributable_bin/linux64/libsteam_api.so

macOS

redistributable_bin/osx/libsteam_api.dylib

29.4. Lua API

29.4.1. Lifecycle & Info

steam.isReady

Returns true if the Steam client is running and the plugin initialised successfully. Call this first — all other functions return nil (or false) when Steam is not ready.

Returns

boolean

Example:

local steam = require "steam"

function onInitScene()
    if not steam.isReady() then
        print("Steam not available")
        return
    end
    print("Steam OK, player:", steam.getPlayerName())
end
steam.getPlayerName

Returns the logged-in Steam user’s display name.

Returns

string

steam.getAppId

Returns the game’s unique Steam App ID.

Returns

integer

steam.getCurrentLanguage

Returns the user’s game language preference (e.g. "english").

Returns

string

29.4.2. Achievements

steam.setAchievement(id)

Unlocks an achievement and immediately syncs it to Steam (no manual storeStats() call needed).

Parameters

stringid — achievement API name as defined in the Steamworks partner dashboard (e.g. "ACH_WIN_FIRST_GAME").

Returns

booleantrue if the unlock and store succeeded.

Example:

steam.setAchievement("ACH_WIN_FIRST_GAME")
steam.clearAchievement(id)

Clears (locks) an achievement. Intended for testing only.

Parameters

stringid — achievement API name.

Returns

booleantrue if the clear and store succeeded.

steam.isAchievementAchieved(id)

Checks whether an achievement is already unlocked.

Parameters

stringid — achievement API name.

Returns

booleantrue if unlocked.

29.4.3. Stats

Note

setStatInt and setStatFloat update the stat locally. Call steam.storeStats() afterwards to flush all pending changes to the Steam servers.

steam.setStatInt(name, value)

Sets an integer stat locally.

Parameters
  • stringname — stat name as defined in the Steamworks partner dashboard.

  • numbervalue — integer value to set.

Returns

booleantrue if the set call succeeded.

steam.setStatFloat(name, value)

Sets a float stat locally.

Parameters
  • stringname — stat name.

  • numbervalue — float value to set.

Returns

booleantrue if the set call succeeded.

steam.getStatInt(name)

Retrieves a cached integer stat value.

Parameters

stringname — stat name.

Returns

integer — cached value (0 if not yet set).

steam.getStatFloat(name)

Retrieves a cached float stat value.

Parameters

stringname — stat name.

Returns

number — cached value (0.0 if not yet set).

steam.storeStats

Flushes all pending stat changes to the Steam servers. Call this after a batch of setStatInt / setStatFloat calls.

Returns

booleantrue if the store call succeeded.

Example:

steam.setStatInt("NumWins", steam.getStatInt("NumWins") + 1)
steam.setStatFloat("FarthestDistance", 1234.5)
steam.storeStats()

29.4.4. Leaderboards (async)

All leaderboard operations are asynchronous. Results are delivered via a Lua callback function; SteamAPI_RunCallbacks() is called automatically each frame inside onLoop.

steam.findLeaderboard(name, callback)

Asynchronously finds a leaderboard by name.

Parameters
  • stringname — leaderboard name as defined in the Steamworks partner dashboard (e.g. "HighScores").

  • functioncallback(handle) — called when the result is ready. handle is an integer (SteamLeaderboard_t cast to int) on success, or nil on failure.

Returns

nil

steam.uploadScore(handle, score[, callback])

Uploads a score to a leaderboard using the KeepBest method (only improves the player’s stored score).

Parameters
  • numberhandle — leaderboard handle returned by the steam.findLeaderboard callback.

  • numberscore — integer score to submit.

  • functioncallback(success)(optional) called when the upload finishes; success is a boolean.

Returns

nil

steam.downloadScores(handle, dataType, startRank, endRank, callback)

Downloads leaderboard entries asynchronously.

Parameters
  • numberhandle — leaderboard handle returned by the steam.findLeaderboard callback.

  • stringdataType — one of "global", "friends", or "around_user".

  • numberstartRank — 1-based rank to start from.

  • numberendRank — 1-based rank to end at.

  • functioncallback(entries) — called with a table of {rank, score, name} records, or nil on failure.

Returns

nil

Example:

steam.findLeaderboard("HighScores", function(handle)
    if not handle then return end

    -- upload current score
    steam.uploadScore(handle, 9999, function(ok)
        print("Upload succeeded:", ok)
    end)

    -- download top-10 global scores
    steam.downloadScores(handle, "global", 1, 10, function(entries)
        if entries then
            for _, e in ipairs(entries) do
                print(string.format("#%d  %s  %d", e.rank, e.name, e.score))
            end
        end
    end)
end)

29.4.5. Cloud Storage (Steam Remote Storage)

steam.cloudWrite(filename, data)

Writes a file to Steam cloud storage.

Parameters
  • stringfilename — e.g. "save.dat".

  • stringdata — file contents as a Lua string (may be binary).

Returns

booleantrue if the write succeeded.

steam.cloudRead(filename)

Reads a file from Steam cloud storage.

Parameters

stringfilename — e.g. "save.dat".

Returns

string on success, nil if the file does not exist.

steam.cloudDelete(filename)

Deletes a file from Steam cloud storage.

Parameters

stringfilename

Returns

booleantrue if the delete succeeded.

steam.cloudFileExists(filename)

Checks whether a file exists in Steam cloud storage.

Parameters

stringfilename

Returns

boolean

steam.isCloudEnabled

Returns true only when both the user’s account setting and the game’s cloud setting are enabled.

Returns

boolean

Example:

if steam.isCloudEnabled() then
    steam.cloudWrite("save.dat", savegame_data)
end

29.4.6. Overlay

steam.activateOverlay(dialog)

Opens a built-in Steam overlay dialog.

Parameters

stringdialog — one of: "achievements", "community", "friends", "store", "stats", "leaderboards", "officialgamegroup".

Returns

nil

Example:

steam.activateOverlay("achievements")
steam.activateOverlayURL(url)

Opens the Steam overlay with a web page.

Parameters

stringurl — e.g. "https://steamcommunity.com/games/480".

Returns

nil

steam.showStore(appId)

Opens the Steam store page for the given App ID inside the overlay.

Parameters

numberappId — Steam App ID.

Returns

nil

29.4.7. DLC

steam.isDlcInstalled(appId)

Checks whether a DLC is currently installed.

Parameters

numberappId — the DLC’s Steam App ID.

Returns

booleantrue if the DLC is installed.

29.5. Full usage example

local steam = require "steam"

function onInitScene()
    if not steam.isReady() then
        print("Steam not available — skipping Steam features")
        return
    end

    -- Player info
    print("Player  :", steam.getPlayerName())
    print("App ID  :", steam.getAppId())
    print("Language:", steam.getCurrentLanguage())

    -- Achievements
    steam.setAchievement("ACH_WIN_FIRST_GAME")
    print("Achieved:", steam.isAchievementAchieved("ACH_WIN_FIRST_GAME"))

    -- Stats
    steam.setStatInt("NumWins", steam.getStatInt("NumWins") + 1)
    steam.setStatFloat("FarthestDistance", 1234.5)
    steam.storeStats()

    -- Leaderboards (async)
    steam.findLeaderboard("HighScores", function(handle)
        if not handle then return end
        steam.uploadScore(handle, 9999)
        steam.downloadScores(handle, "global", 1, 10, function(entries)
            if entries then
                for _, e in ipairs(entries) do
                    print(e.rank, e.name, e.score)
                end
            end
        end)
    end)

    -- Cloud saves
    if steam.isCloudEnabled() then
        steam.cloudWrite("save.dat", "my save data")
        local data = steam.cloudRead("save.dat")
        print("Cloud data:", data)
    end

    -- Overlay
    -- steam.activateOverlay("achievements")

    -- DLC
    print("DLC installed:", steam.isDlcInstalled(1234567))
end

29.6. Security

Warning

Never embed secret keys, server endpoints, or internal API keys in Lua scripts distributed with your game. Use mini-mbm’s built-in AES encryption (mbm.encrypt / mbm.decrypt) to protect sensitive scripts before shipping. See the editor/asset_packager.lua tool for details.

The following Steam data is not sensitive and safe to reference in Lua scripts:

  • App ID (public — visible in Steam URLs)

  • Achievement / stat API names (defined in the Steamworks partner dashboard)

  • Leaderboard names (public)

Steam handles user authentication transparently. Players never enter credentials in the game. steam.isReady() returns false and fails gracefully if the client is not running or the user does not own the game.

29.7. Troubleshooting

Symptom

Likely cause

steam.isReady() returns false

Steam client not running, or steam_appid.txt missing / contains the wrong App ID

Plugin fails to load

steam_api64.dll / libsteam_api.so not placed next to the executable

Build fails — SDK headers not found

STEAMWORKS_SDK_PATH not set, or points to the wrong directory

Achievements not appearing in Steam

Call steam.storeStats() after setting achievements

Leaderboard callback never fires

SteamAPI_RunCallbacks() must run each frame; this happens automatically in onLoop