Skip to content

Datanana-com/beamworks-php

Repository files navigation

beamworks-php — PHP FFI bindings for Valve's Steamworks SDK

Packagist Version PHP Version CI License

Use Steamworks from PHP 8.5+ with zero C extensions — just PHP's built-in ext-ffi and the redistributable Steamworks SDK binary.

Quick start

composer require datanana-com/beamworks-php
# then put the Steam redistributable next to your composer.json:
#   Windows: steam_api64.dll   ·   Linux: libsteam_api.so   (see Setup)
use DatananaCom\BeamworksPhp\SteamClient;

$client = SteamClient::create();
$client->init(480);                       // 480 = Spacewar, Valve's test appid

echo $client->user()->getSteamId() . "\n";

$client->shutdown();

That's the whole shape: create()init() → use the typed accessors (user(), stats(), friends(), apps(), utils()) → shutdown(). You never touch the FFI layer directly.

Contents

Requirements

Requirement Detail
PHP 8.5+, 64-bit only (32-bit is not supported)
Extension ext-ffi enabled (ffi.enable=1)
Steamworks SDK 1.64 — needs Steamworks partner access
OS Linux x64 or Windows x64 (macOS best-effort)
Runtime The Steam client must be running for live calls

Installation

composer require datanana-com/beamworks-php

The package ships the prebuilt cdef header, PHP enums, and callback registry (in generated/), so there is nothing to generate — composer require is enough on the PHP side.

Setup — supply the Steam binary

The one thing the package can't bundle is Valve's redistributable binary (their license forbids it). Provide it in any one of these ways:

Method What you do Example
A — project root Drop the binary next to your composer.json ./steam_api64.dll (Win) · ./libsteam_api.so (Linux)
B — env var Point STEAMWORKS_LIB_PATH at the binary export STEAMWORKS_LIB_PATH=/abs/path/to/steam_api64.dll
C — SDK layout Keep the SDK's redistributable_bin/ under sdk/ in your project root sdk/redistributable_bin/win64/steam_api64.dll

Resolution order: an explicit path passed to SteamFFI::load()STEAMWORKS_LIB_PATH → auto-detection (project root, then the sdk/ subtree). If none is found, create() throws SteamLibraryNotFoundException with the same guidance.

Usage

The lifecycle, at a glance:

create() ──▶ init(appId) ──▶ start() ──▶ ┌─ loop ─────────────┐ ──▶ shutdown()
                                         │ tick(); usleep(...) │
                                         └────────────────────┘

start() / tick() are only needed when you consume callbacks (below). For one-shot reads like identity, create() + init() is enough.

Identity and login check

use DatananaCom\BeamworksPhp\SteamClient;

$client = SteamClient::create();
$client->init(480);

if ($client->user()->isLoggedOn()) {
    echo "Logged in as SteamID: " . $client->user()->getSteamId() . "\n";
}

Unlock an achievement

use DatananaCom\BeamworksPhp\SteamClient;

$client = SteamClient::create();
$client->init(480);

$stats = $client->stats();
$ok = $stats->setAchievement('ACH_WIN_ONE_GAME') && $stats->storeStats();

Important: Steam auto-requests the local user's stats during init(), but they arrive asynchronously via the UserStatsReceived_t callback. setAchievement() / storeStats() only take effect once those stats have been received. In a real app, pump the callback loop and wait for UserStatsReceived before writing (see below). Always check the boolean return.

Listen to callbacks (PSR-14)

Callbacks are delivered as PSR-14 events while the pump runs:

tick() ──▶ Fiber resume ──▶ ManualDispatch RunFrame / GetNextCallback
       ──▶ SteamCallbackEvent (PSR-14) ──▶ your listener
use DatananaCom\BeamworksPhp\Events\SteamCallbackEvent;
use DatananaCom\BeamworksPhp\Events\SteamCallbackId;
use DatananaCom\BeamworksPhp\Events\SteamEventDispatcher;
use DatananaCom\BeamworksPhp\SteamClient;

$received = false;

$dispatcher = new SteamEventDispatcher();
$dispatcher->addListener(SteamCallbackEvent::class, function (SteamCallbackEvent $event) use (&$received): void {
    if ($event->callbackId === SteamCallbackId::UserStatsReceived->value) {
        $received = true;
    }
});

$client = SteamClient::create($dispatcher);
$client->init(480);
$client->start();                 // spawns the Fiber pump; nothing is delivered until tick()

while (!$received) {              // wait for the stats Steam requested at init()
    $client->tick();
    usleep(16_000);               // ~60 fps
}

// Stats are now loaded — safe to write achievements.
$client->stats()->setAchievement('ACH_WIN_ONE_GAME');
$client->stats()->storeStats();

See docs/callback-guide.md for the async CallResultHandle pattern (registerCallResult → tick → await).

Error handling

Exception Meaning Common cause
SteamLibraryNotFoundException The redistributable couldn't be located Binary missing or misplaced (see Setup)
SteamInitException SteamAPI_Init failed Steam client not running, or wrong appId

All exceptions extend SteamException (a \RuntimeException), so a single catch (\RuntimeException) still works. Both load and init happen inside create()/init(), so wrap both:

use DatananaCom\BeamworksPhp\Exception\SteamException;
use DatananaCom\BeamworksPhp\Exception\SteamInitException;
use DatananaCom\BeamworksPhp\Exception\SteamLibraryNotFoundException;
use DatananaCom\BeamworksPhp\SteamClient;

try {
    $client = SteamClient::create();   // resolves + loads the Steam binary
    $client->init(480);                // starts the Steam API
} catch (SteamLibraryNotFoundException $e) {
    // Binary not found — see Setup (drop it in your project root or set STEAMWORKS_LIB_PATH).
} catch (SteamInitException $e) {
    // Steam not running, or the appId is wrong.
} catch (SteamException $e) {
    // Anything else from the library.
}

Logging (PSR-3)

Pass any PSR-3 logger to observe init, pump, and callback dispatch:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use DatananaCom\BeamworksPhp\SteamClient;

$logger = new Logger('steam');
$logger->pushHandler(new StreamHandler('php://stderr'));

$client = SteamClient::create(logger: $logger);

API coverage

Steam interface Status Accessor Wrapper
User Implemented $client->user() Api\User
Friends Implemented $client->friends() Api\Friends
UserStats Implemented $client->stats() Api\UserStats
Apps Implemented $client->apps() Api\Apps
Utils Implemented $client->utils() Api\Utils
RemoteStorage Implemented $client->remoteStorage() Api\RemoteStorage
Inventory Implemented $client->inventory() Api\Inventory
UGC Implemented $client->ugc() Api\Ugc
Input Implemented $client->input() Api\Input
Screenshots Implemented $client->screenshots() Api\Screenshots
Music Implemented $client->music() Api\Music
RemotePlay Implemented $client->remotePlay() Api\RemotePlay
Timeline Implemented $client->timeline() Api\Timeline
ParentalSettings Implemented $client->parentalSettings() Api\ParentalSettings

Scope: beamworks-php targets client-side Steam features — identity, friends, stats, achievements, DLC, cloud saves, inventory, Workshop, controller input, screenshots, music, Remote Play, timeline, and parental settings. Dedicated-server, networking, and matchmaking interfaces are non-goals; reach for a dedicated solution if you need those.

Architecture

Three layers, each building on the one below. Never skip a layer.

SteamClient          ← lifecycle, Fiber pump, PSR-14 dispatch, typed accessors
    │
SteamApi\*           ← typed PHP wrappers per interface (User, Friends, UserStats, …)
    │                   returns DTOs from src/Dto/; marshals strings/pointers
SteamFFI             ← raw FFI::cdef handle; ->call() and the ->ffi() escape hatch
    │
generated/           ← codegen output: cdef header, PHP enums, CallbackRegistry
                        (committed + shipped — never hand-edit; regenerate via bin/codegen)

See docs/architecture.md for each layer and the golden rules that govern it.

License

MIT — see LICENSE. MIT is a permissive license that Valve lists as compatible with shipping on Steam, matching the MIT licensing of Steamworks.NET and Facepunch.Steamworks. (Earlier releases were GPL-3.0; Valve names GPL as the "best-known example" of a license incompatible with the Steamworks SDK, so a game shipped on Steam could not have linked the GPL version.)

Steamworks SDK: The MIT license covers this project's own PHP code. The files under generated/ (steam_api.cdef.h, SteamTypes.php, CallbackRegistry.php) are derived from Valve's Steamworks SDK and ship in the package so composer require works without running codegen; they remain subject to Valve's own SDK terms — see NOTICE. You must still obtain the SDK through the Steamworks partner portal. The redistributable runtime binaries (steam_api64.dll, libsteam_api.so) are not bundled here — provide them with your own application per Valve's redistribution guidelines.

For maintainers

Contributor workflows — testing, code style, and regenerating generated/ against a new SDK release — live in CONTRIBUTING.md and docs/sdk-sync.md. Consumers never run bin/sync-sdk or bin/codegen; those tools are excluded from the Composer tarball.

About

PHP your way into gaming.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Contributors