Skip to content

menduz/zig-steamworks

Repository files navigation

zig-steamworks

Zig bindings for Steamworks, compatible with v1.57 (March 2023).

The bindings are autogenerated from the C bindings and are compatible with

  • macOS (both Apple Silicon and Intel)
  • linux (x86_64)
  • windows (x86_64)

This project uses Zig 0.11.0-dev

Including the Steamworks SDK

Due to licence agreements, you must bring your own SDK. This project assumes it is located in the ./steamworks folder. But you can have it anywhere else.

Linking the Steamworks libraries.

// build.zig
const zig_steamworks = @import("zig-steamworks/linker.zig");

fn main() !void { 
  // ...
  const exe = b.addExecutable( ... );

  // link steamworks to this executable
  _ = try zig_steamworks.linkSteamLibrary(b, exe, "steamworks");
  //                   relative path to steamworks ^^^^^^^^^^

  // you must include the steam_appid.txt file in your final directory
  try zig_steamworks.copy(comptime zig_steamworks.thisDir() ++ "/src", "zig-out/bin", "steam_appid.txt");
}

Build it

For convenience, this repository offers already built bindings for Zig in the src folder. Until Zig modules and package manager are mature, the recommended course of action is to copy these files into your project.

To re-build the files run the following command

node generate.js path_to_steamworks/public/steam/steam_api.json

Example

const std = @import("std");
const steam = @import("steam.zig");

/// callback hook for debug text emitted from the Steam API
pub fn SteamAPIDebugTextHook(nSeverity: c_int, pchDebugText: [*c]const u8) callconv(.C) void {
    // if you're running in the debugger, only warnings (nSeverity >= 1) will be sent
    // if you add -debug_steamapi to the command-line, a lot of extra informational messages will also be sent
    std.debug.print("SteamAPIDebugTextHook sev:{} msg: {s}\n", .{ nSeverity, pchDebugText });
}

/// get an authentication ticket for our user
fn authTicket(identity: *steam.SteamNetworkingIdentity) !void {
    var rgchToken: *[2048]u8 = try std.heap.c_allocator.create([2048]u8);
    var unTokenLen: c_uint = 0;
    var m_hAuthTicket = steam.SteamUser().GetAuthSessionTicket(rgchToken, 2048, &unTokenLen, identity);
    std.debug.print("GetAuthSessionTicket={} len={} token={s}", .{ m_hAuthTicket, unTokenLen, rgchToken });
}

pub fn main() !void {
    if (steam.SteamAPI_RestartAppIfNecessary(480)) {
        // if Steam is not running or the game wasn't started through Steam, SteamAPI_RestartAppIfNecessary starts the
        // local Steam client and also launches this game again.

        // Once you get a public Steam AppID assigned for this game, you need to replace k_uAppIdInvalid with it and
        // removed steam_appid.txt from the game depot.
        @panic("SteamAPI_RestartAppIfNecessary");
    }

    if (steam.SteamAPI_Init()) {
        std.debug.print("Steam initialized correctly. \n", .{});
    } else {
        @panic("Steam did not init\n");
    }

    steam.SteamClient().SetWarningMessageHook(SteamAPIDebugTextHook);

    defer steam.SteamAPI_Shutdown();

    std.debug.print("User {?}\n", .{steam.SteamUser().GetSteamID()});

    var sock = steam.SteamNetworkingSockets_SteamAPI();

    var pInfo: *steam.SteamNetworkingFakeIPResult_t = try std.heap.c_allocator.create(steam.SteamNetworkingFakeIPResult_t);
    defer std.heap.c_allocator.destroy(pInfo);

    sock.GetFakeIP(0, pInfo);
    std.debug.print("GetFakeIP: {}\n", .{pInfo});
    if (!pInfo.m_identity.IsEqualTo(pInfo.m_identity)) @panic("not equal");

    var pDetails: *steam.SteamNetAuthenticationStatus_t = try std.heap.c_allocator.create(steam.SteamNetAuthenticationStatus_t);
    defer std.heap.c_allocator.destroy(pDetails);

    var connectionStatus = sock.GetAuthenticationStatus(pDetails);
    std.debug.print("GetAuthenticationStatus: {} {}\n", .{ connectionStatus, pDetails });

    var pIdentity: *steam.SteamNetworkingIdentity = try std.heap.c_allocator.create(steam.SteamNetworkingIdentity);
    std.heap.c_allocator.destroy(pIdentity);
    var r = sock.GetIdentity(pIdentity);
    std.debug.print("GetIdentity={} {}\n", .{ r, pIdentity });

    try authTicket(pIdentity);
}

Alignment

Steamworks is packed with pragma packs, I've tried dozens of approaches even brute forcing all structs, but getting the right alignment for every struct was impossible. So I took the good-enough-and-not-so-elegant path of delegating the alignment to the C compiler entirely and then forcing Zig to copy everything with the generated code. In effect, you will see a lot of align(...) in the generated code.

In practice both Mac and Linux alignments are the same.

Please do the following steps for each steamworks release that is added to this repo. Mind the diff in the steamworks headers to remove the private fields (search for // ZIG:).

To generate the linux alignment file from aarch64 Mac hosts

# open a docker container using x86_64
docker run -v $(pwd):/mnt -w /mnt --rm -it --platform linux/amd64 mcr.microsoft.com/devcontainers/typescript-node:0-18 zsh
# install zig
sudo bash .devcontainer/install-zig.sh
# run the script and grab a coffee
bash ./create-align-info-linux.sh

To generate the mac alignment file from Mac

# run the script
bash ./create-align-info-mac.sh

To generate the mac alignment file from Windows

# run the script
.\create-align-info-mac.bat