GnsSharp.Steamworks.Win64 0.1.0-alpha.2

This is a prerelease version of GnsSharp.Steamworks.Win64.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package GnsSharp.Steamworks.Win64 --version 0.1.0-alpha.2
                    
NuGet\Install-Package GnsSharp.Steamworks.Win64 -Version 0.1.0-alpha.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GnsSharp.Steamworks.Win64" Version="0.1.0-alpha.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GnsSharp.Steamworks.Win64" Version="0.1.0-alpha.2" />
                    
Directory.Packages.props
<PackageReference Include="GnsSharp.Steamworks.Win64" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GnsSharp.Steamworks.Win64 --version 0.1.0-alpha.2
                    
#r "nuget: GnsSharp.Steamworks.Win64, 0.1.0-alpha.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=GnsSharp.Steamworks.Win64&version=0.1.0-alpha.2&prerelease
                    
Install GnsSharp.Steamworks.Win64 as a Cake Addin
#tool nuget:?package=GnsSharp.Steamworks.Win64&version=0.1.0-alpha.2&prerelease
                    
Install GnsSharp.Steamworks.Win64 as a Cake Tool

GnsSharp

GnsSharp is a C# binding for the ValveSoftware/GameNetworkingSockets.

It supports both the stand-alone open source GameNetworkingSockets, and the Steamworks SDK version of it,
So you can switch between those without too much hassle.

This project is heavily WIP. API might change, things might break or not work at all;
Use it at your own risk.

Why?

None of the existing C# bindings support switching between stand-alone GNS and Steamworks SDK version of it.
So, I'm creating one for my own needs.

Scope

Currently, I've only ported the APIs that are compatible for both the stand-alone and Steamworks versions.
This includes ISteamNetworkingSockets, ISteamNetworkingUtils and steamnetworkingtypes header.

This means that there's no support for the Steamworks exclusive features at all (e.g. matchmaking, steam friends...).
This might change in the future if I need those.

Documentation

Most of the APIs are almost the same as the original GNS, so you can refer to the official Steam Networking Docs to figure out how to use them.

Usage

Setup

Stand-alone GameNetworkingSockets
  1. Build the open source GameNetworkingSockets.
    • As of writing, the latest is commit 725e273
    • Refer to the BUILDING.md on GameNetworkingSockets for details.
      • If you're using Developer Powershell for VS 2022 on Windows, do note that it defaults to x86 environment, which obviously doesn't work when building for the AMD64.
        You need to switch to AMD64 environment manually with following:
        Enter-VsDevShell -DevCmdArguments "-arch=x64 -host_arch=x64" -VsInstallPath "C:/Program Files/Microsoft Visual Studio/2022/Community" -SkipAutomaticLocation
        
  2. Copy the native library files to your executable's build directory.
    • On Windows, you should copy all the dependent dlls along with the GameNetworkingSockets.dll.
Steamworks SDK
  1. Download the Steamworks SDK from the Steamworks partner site.
  2. Copy the native library files from the sdk/redistributable_bin/ to your executable's build directory.

Basic Examples

<details> <summary>Self connect example</summary>

using GnsSharp;

using System.Runtime.InteropServices;
using System.Text;

#pragma warning disable CS0162 // Unreachable code (because of `GnsSharpCore.Backend` check)

// Load the native library depending on your platform
string nativeLibraryPath = Path.Join(AppContext.BaseDirectory,
       "GameNetworkingSockets.dll"         /* Open source GNS for Windows */
    // "libGameNetworkingSockets.so"       /* Open source GNS for Linux */
    // "libGameNetworkingSockets.dylib"    /* Open source GNS for macOS */
    // "steam_api64.dll"                   /* Steamworks SDK for Windows (AMD64) */
    // "steam_api.dll"                     /* Steamworks SDK for Windows (x86) */
    // "libsteam_api.so"                   /* Steamworks SDK for Linux */
    // "libsteam_api.dylib"                /* Steamworks SDK for macOS */
);

IntPtr nativeLibrary = NativeLibrary.Load(nativeLibraryPath);

// Initialize GNS or SteamAPI
bool initialized = false;
string? errMsg = null;
if (GnsSharpCore.Backend == GnsSharpCore.BackendKind.OpenSource)
{
    initialized = GameNetworkingSockets.Init(out errMsg);
}
else if (GnsSharpCore.Backend == GnsSharpCore.BackendKind.Steamworks)
{
    // For test environment, write `480` in `steam_appid.txt`, and put it next to your executable.
    // And you must be running Steam client on your PC.
    initialized = (SteamAPI.InitEx(out errMsg) == ESteamAPIInitResult.OK);
}

if (!initialized)
{
    Console.WriteLine(errMsg!);
    throw new Exception(errMsg!);
}

// Run callbacks as a seperate task
CancellationTokenSource cancelTokenSrc = new();
CancellationToken cancelToken = cancelTokenSrc.Token;
Task callbackRunner;

if (GnsSharpCore.Backend == GnsSharpCore.BackendKind.OpenSource)
{
    callbackRunner = Task.Run(async () =>
    {
        while (!cancelToken.IsCancellationRequested)
        {
            ISteamNetworkingSockets.User!.RunCallbacks();
            await Task.Delay(16, cancelToken);
        }
    }, cancelToken);
}
else if (GnsSharpCore.Backend == GnsSharpCore.BackendKind.Steamworks)
{
    callbackRunner = Task.Run(async () =>
    {
        while (!cancelToken.IsCancellationRequested)
        {
            SteamAPI.RunCallbacks();
            await Task.Delay(16, cancelToken);
        }
    }, cancelToken);
}

// Setup the debug output delegate
// (For every callback delegate, including this one,
// it should be stored somewhere safe to prevent it from garbage collected.)
FSteamNetworkingSocketsDebugOutput debugOutput = (ESteamNetworkingSocketsDebugOutputType level, string msg) =>
{
    Console.WriteLine($"[{level}] {msg}");
};

ISteamNetworkingUtils.User!.SetDebugOutputFunction(ESteamNetworkingSocketsDebugOutputType.Everything, debugOutput);

// Setup listen address: IPv6 any address & port 43000
SteamNetworkingIPAddr addr = default;
addr.ParseString("[::]:43000");

int serverClosing = 0;

// Setup listen socket connection status changed callback
FnSteamNetConnectionStatusChanged listenStatusChanged = (ref SteamNetConnectionStatusChangedCallback_t status) =>
{
    switch (status.Info.State)
    {
        case ESteamNetworkingConnectionState.Connecting:
            ISteamNetworkingSockets.User!.AcceptConnection(status.Conn);
            Console.WriteLine("Server has accepted the connection from client!");
            break;

        case ESteamNetworkingConnectionState.ClosedByPeer:
        case ESteamNetworkingConnectionState.ProblemDetectedLocally:
            StringBuilder builder = new();
            builder.Append($"Server: #{status.Conn} disconnected");
            if (status.Info.EndDebug != null)
                builder.Append($": {status.Info.EndDebug}");
            Console.WriteLine(builder.ToString());

            // Server side also need to close the connection to clean up resources
            ISteamNetworkingSockets.User!.CloseConnection(status.Conn, 0, "Server's closing too!", false);

            Interlocked.Exchange(ref serverClosing, 1);
            break;
    }
};

Span<SteamNetworkingConfigValue_t> serverConfigs = stackalloc SteamNetworkingConfigValue_t[1];
serverConfigs[0].SetPtr(ESteamNetworkingConfigValue.Callback_ConnectionStatusChanged,
                        Marshal.GetFunctionPointerForDelegate(listenStatusChanged));

// Server: Start listening
HSteamListenSocket listener = ISteamNetworkingSockets.User!.CreateListenSocketIP(in addr, serverConfigs);

serverConfigs[0].Dispose(); // Dispose config after usage, actually not required unless `SetString()` is used

// On Steam, in order to properly listen, you need to wait for the authentication to complete
if (GnsSharpCore.Backend == GnsSharpCore.BackendKind.Steamworks)
{
    for (int i = 0; ; ++i)
    {
        ESteamNetworkingAvailability avail =
            ISteamNetworkingSockets.User.GetAuthenticationStatus(out SteamNetAuthenticationStatus_t auth);

        if (avail == ESteamNetworkingAvailability.Current)
            break;
        else if (avail == ESteamNetworkingAvailability.Failed)
        {
            Console.WriteLine($"Auth failed: {auth.DebugMsg}");
            throw new Exception(auth.DebugMsg);
        }

        Console.WriteLine($"Waiting for Steam authentication for {i} seconds... ({avail})");

        await Task.Delay(1000);
    }

    Console.WriteLine("Steam authentication succeeded!");
}

// Setup connect address: IPv6 loopback address & port 43000
addr.ParseString("[::1]:43000");

int clientConnected = 0;

// Setup connect client connection status changed callback
FnSteamNetConnectionStatusChanged clientStatusChanged = (ref SteamNetConnectionStatusChangedCallback_t status) =>
{
    switch (status.Info.State)
    {
        case ESteamNetworkingConnectionState.Connected:
            Console.WriteLine("Client successfully connected to the server!");
            Interlocked.Exchange(ref clientConnected, 1);
            break;

        case ESteamNetworkingConnectionState.ClosedByPeer:
        case ESteamNetworkingConnectionState.ProblemDetectedLocally:
            StringBuilder builder = new();
            builder.Append($"Client: #{status.Conn} disconnected");
            if (status.Info.EndDebug != null)
                builder.Append($": {status.Info.EndDebug}");
            Console.WriteLine(builder.ToString());

            ISteamNetworkingSockets.User.CloseConnection(status.Conn, 0, "Client closing lately?", false);
            break;
    }
};

Span<SteamNetworkingConfigValue_t> clientConfigs = stackalloc SteamNetworkingConfigValue_t[1];
clientConfigs[0].SetPtr(ESteamNetworkingConfigValue.Callback_ConnectionStatusChanged,
                        Marshal.GetFunctionPointerForDelegate(clientStatusChanged));

// Client: Connect to the server
HSteamNetConnection client = ISteamNetworkingSockets.User.ConnectByIPAddress(addr, clientConfigs);

clientConfigs[0].Dispose();

// Wait for the connection to complete
while (clientConnected == 0)
    await Task.Delay(16);

// Close from the client side
ISteamNetworkingSockets.User.CloseConnection(client, 0, "Client's closing!", false);

// Wait for the server side to close the connection
while (serverClosing == 0)
    await Task.Delay(16);

// Stop the callback loop task
cancelTokenSrc.Cancel();
try
{
    await callbackRunner;
}
catch (TaskCanceledException)
{
    Console.WriteLine("Callback loop task stopped!");
}

// De-initialize GNS or SteamAPI
if (GnsSharpCore.Backend == GnsSharpCore.BackendKind.OpenSource)
    GameNetworkingSockets.Kill();
else if (GnsSharpCore.Backend == GnsSharpCore.BackendKind.Steamworks)
    SteamAPI.Shutdown();

// Free the native library
NativeLibrary.Free(nativeLibrary);

</details>

License

GnsSharp is licensed under the MIT License.

This project depends on either the stand-alone GameNetworkingSockets or the Steamworks SDK.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on GnsSharp.Steamworks.Win64:

Package Downloads
NalchiSharp.Steamworks.Win64

Utilities for efficient message sending over Valve's GameNetworkingSockets.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.1.0-alpha.11 119 4/7/2025
0.1.0-alpha.10 106 3/30/2025
0.1.0-alpha.9 92 3/28/2025
0.1.0-alpha.8 96 3/28/2025
0.1.0-alpha.7 433 3/26/2025
0.1.0-alpha.6 117 3/22/2025
0.1.0-alpha.5 71 3/21/2025
0.1.0-alpha.4 119 3/17/2025
0.1.0-alpha.3 103 3/16/2025
0.1.0-alpha.2 102 3/16/2025
0.1.0-alpha.1 47 3/15/2025