๐Ÿ“ฆ about ๐Ÿงป posts

I’ve been working on Facepunch.Steamworks in the background while doing Rust’s workshop integration. Just Prerelease branch is shipping with it now.

The challenge was to make a c# library that works under Unity (and not) without recompiling, and on a bunch of platforms.

Here’s everything that sucked about it.

Pack Size

All the structs in the sdk are packed, like this.

#if defined( VALVE_CALLBACK_PACK_SMALL )
#pragma pack( push, 4 )
#elif defined( VALVE_CALLBACK_PACK_LARGE )
#pragma pack( push, 8 )
#else
#error isteamclient.h must be included
#endif

This creates a split between windows and osx/linux. Structs are 8 byte aligned on Windows, 4 byte aligned on osx/linux. This causes drama when you come to marshal them.

CSteamID

It also seems that on Windows CSteamID stops structs getting packed at all. So you’ll find that any structs that have a CSteamID member (which is a lot of them) need to be treated as unpacked.

Callback

A lot of stuff uses callbacks in the Steamworks SDK. The callback system expects a c++ class. This sucks for c#. Getting it to work means you need to create a class to emulate this:

class CCallbackBase
{
public:
CCallbackBase() { m_nCallbackFlags = 0; m_iCallback = 0; }
// don’t add a virtual destructor because we export this binary interface across dll’s virtual void Run( void *pvParam ) = 0;
virtual void Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall ) = 0;
int GetICallback() { return m_iCallback; }
virtual int GetCallbackSizeBytes() = 0;

protected:
enum { k_ECallbackFlagsRegistered = 0x01, k_ECallbackFlagsGameServer = 0x02 };
uint8 m_nCallbackFlags;
int m_iCallback;
friend class CCallbackMgr;

private:
CCallbackBase( const CCallbackBase& );
CCallbackBase& operator=( const CCallbackBase& );
};

Which ends up looking something like this.

[StructLayout( LayoutKind.Sequential )]
internal class Callback
{
public IntPtr vTablePtr;
public byte CallbackFlags;
public int CallbackId;
}

vTablePtr is a pointer to the vtable – which is implementing the virtual functions.

[StructLayout( LayoutKind.Sequential )]
public class VTable
{
public IntPtr ResultA;
public IntPtr ResultB;
public IntPtr GetSize;
}

The awesome confusing part of the VTable is that the first two functions swap positions on windows.

steam_api_interop.cs

For a long time I was trying to use the steam_api_interop.cs file included with the SDK. Don’t use this. It’s non functional out of the box, it’s using the wrong parameters for a bunch of functions and it’s not packing structs.

Because of this I ended up writing my own generator (which I’m sure is getting a few things wrong, but I’m unit testing as much as I can).

For this I’m using the steam_api.json file and some source code regexes for missing stuff. So as much as steam_api_interop.cs sucks, I’m sure it’s only there to serve as an example of how to use steam_api.json and steam_api_flat.h – which makes this all a million times easier.

Things That Suck

Callbacks can return with “IO Failure” if you call them too soon after Steam Init. This sucks for Unit Tests.

Steam thinks your game is still running until you close the exe. This sucks when working in Unity Editor etc.

question_answer

Add a Comment

An error has occurred. This application may no longer respond until reloaded. Reload ๐Ÿ—™