my Steamworks programming diary

Programming related discussions related to game research
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

my Steamworks programming diary

Post by aluigi »

It's from some weeks that I'm using Steamworks and I would like to share some info and rants about it.

If you don't know it, Steamworks is a framework/SDK that allows to control Steam and its functionalities just like your games do, so interaction with friends, networking, matchmaking, music and more:
http://partner.steamgames.com

I will continue to update the thread with all the strange, funny and interesting things I will notice so stay tuned and add your personal experience if you want.


Visual Studio only
Steamworks uses the Microsoft fastcall for the API and so there is no way to use Mingw/GCC.


Documentation
Basically the documentation is made just by the comments in the header files :)
There is an example game (SpaceWars) but sometimes it's not enough to understand how something works or should work.


Quickly how it works
Your program uses the local steam_api.dll as way to communicate with Steam.exe via IPC (mutex, file mapping, shared memory and so on), basically there is a part of code that is executed by this local dll and another (core) part executed in Steam.exe, by steamclient.dll.


You need an AppID
You need to specify the ID of a game for using Steamworks.
There are two ways to do that:
  • creation of the local file steam_appid.txt containing the number
  • setting of SteamAppId and SteamGameId environament variables


Go with the env vars!
When you use the steam_appid.txt method, Steam automatically creates the two environment variables and they have the priority during the initialization.
So if you create steam_appid.txt with id 480, call SteamAPI_Init, call SteamAPI_Shutdown, and then you want to use appid 10... you can't, because Steamworks will continue to use appid 480.
So use ever the environment.


callbacks
Some API are async, so you call them and then you must wait the response from a callback.
The problem is that they are not callbacks easy to use like in C, they are objects.
So something like "set callback; call API; wait callback;" is not possible, you must create an object that contains both the caller and callback and you must initialize (new), use and maybe destroy it (delete).
If you are not used to C++ and objects you will find it very annoying.


Frames
The callbacks are frame-related.
Basically when you use a callback you must call SteamAPI_RunCallbacks or RunFrame every X milliseconds.
It's made to be game compatible but the concept is quite strange, it's like the implemented IPC method is blocked instead of async.


Some API are weird
An example of API that doesn't have much sense is GetInstalledApps that requires two arguments:
  • an array of integers to use as destination
  • the highest appid number
Yes, the second number is just that, and considering that the appid are not sequential like 1,2,3,4 it means that you must just guess it and you must allocate a giant array to be able to cover all your apps avoiding overflows.
Yeah I know that probably it's impossible for someone to have 1000000 games, anyway...


Sometimes you need to own a game to query it, other times not
This is something that I find very weird, basically there are some features of Steamworks that can be used only if you own the game.
For example if you don't have Left 4 Dead 2, you cannot retrieve the list of online lobbies.
But if you don't have Serious Sam HD (appid 41010), you can retrieve the list without problems.
That's quite confusing, really.


Some API don't work
Some API, when called, return an error or an empty result.
That's the case of GetAppName and GetAppInstallDir of SteamAppList, but there are also a couple in the matchmaking part that do the same.


Use-after-free (no security bug)
Just for fun, nothing serious so don't worry:

Code: Select all

SteamAPI_Init();
for(i = 0; i < 10; i++) {
    SteamClient()->BReleaseSteamPipe(i);
}
SteamController()->Shutdown();
cyanic
Posts: 13
Joined: Wed Aug 13, 2014 1:44 am

Re: my Steamworks programming diary

Post by cyanic »

aluigi wrote:An example of API that doesn't have much sense is GetInstalledApps that requires two arguments:
  • an array of integers to use as destination
  • the highest appid number
Yes, the second number is just that, and considering that the appid are not sequential like 1,2,3,4 it means that you must just guess it and you must allocate a giant array to be able to cover all your apps avoiding overflows.
Yeah I know that probably it's impossible for someone to have 1000000 games, anyway...

Hmm, it would have made sense if it allowed you to specify a starting number. I can see using it to enumerate appids in batches of 1000 or so. However, if you read the comment right above the declaration:

Code: Select all

//-----------------------------------------------------------------------------
// Purpose: This is a restricted interface that can only be used by previously approved apps,
//   contact your Steam Account Manager if you believe you need access to this API.
//   This interface lets you detect installed apps for the local Steam client, useful for debugging tools
//   to offer lists of apps to debug via Steam.
//-----------------------------------------------------------------------------

the API was designed for debugging tools, so they should have a known maximum number when calling the function.

For others' reference: you can see the Steam API header files without subjecting yourself to an NDA. Simply check the Source SDK code on GitHub.
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: my Steamworks programming diary

Post by aluigi »

Hope 1000000 is enough, on steamdb the highest number seems to be 31xxxx.
There are other ways to get the list but this one was quite easy.
raykingnihong
Posts: 71
Joined: Fri Oct 24, 2014 3:13 pm

Re: my Steamworks programming diary

Post by raykingnihong »

aluigi wrote:It's from some weeks that I'm using Steamworks and I would like to share some info and rants about it.

If you don't know it, Steamworks is a framework/SDK that allows to control Steam and its functionalities just like your games do, so interaction with friends, networking, matchmaking, music and more:
http://partner.steamgames.com

I will continue to update the thread with all the strange, funny and interesting things I will notice so stay tuned and add your personal experience if you want.


Visual Studio only
Steamworks uses the Microsoft fastcall for the API and so there is no way to use Mingw/GCC.


Documentation
Basically the documentation is made just by the comments in the header files :)
There is an example game (SpaceWars) but sometimes it's not enough to understand how something works or should work.


Quickly how it works
Your program uses the local steam_api.dll as way to communicate with Steam.exe via IPC (mutex, file mapping, shared memory and so on), basically there is a part of code that is executed by this local dll and another (core) part executed in Steam.exe, by steamclient.dll.


You need an AppID
You need to specify the ID of a game for using Steamworks.
There are two ways to do that:
  • creation of the local file steam_appid.txt containing the number
  • setting of SteamAppId and SteamGameId environament variables


Go with the env vars!
When you use the steam_appid.txt method, Steam automatically creates the two environment variables and they have the priority during the initialization.
So if you create steam_appid.txt with id 480, call SteamAPI_Init, call SteamAPI_Shutdown, and then you want to use appid 10... you can't, because Steamworks will continue to use appid 480.
So use ever the environment.


callbacks
Some API are async, so you call them and then you must wait the response from a callback.
The problem is that they are not callbacks easy to use like in C, they are objects.
So something like "set callback; call API; wait callback;" is not possible, you must create an object that contains both the caller and callback and you must initialize (new), use and maybe destroy it (delete).
If you are not used to C++ and objects you will find it very annoying.


Frames
The callbacks are frame-related.
Basically when you use a callback you must call SteamAPI_RunCallbacks or RunFrame every X milliseconds.
It's made to be game compatible but the concept is quite strange, it's like the implemented IPC method is blocked instead of async.


Some API are weird
An example of API that doesn't have much sense is GetInstalledApps that requires two arguments:
  • an array of integers to use as destination
  • the highest appid number
Yes, the second number is just that, and considering that the appid are not sequential like 1,2,3,4 it means that you must just guess it and you must allocate a giant array to be able to cover all your apps avoiding overflows.
Yeah I know that probably it's impossible for someone to have 1000000 games, anyway...


Sometimes you need to own a game to query it, other times not
This is something that I find very weird, basically there are some features of Steamworks that can be used only if you own the game.
For example if you don't have Left 4 Dead 2, you cannot retrieve the list of online lobbies.
But if you don't have Serious Sam HD (appid 41010), you can retrieve the list without problems.
That's quite confusing, really.


Some API don't work
Some API, when called, return an error or an empty result.
That's the case of GetAppName and GetAppInstallDir of SteamAppList, but there are also a couple in the matchmaking part that do the same.


Use-after-free (no security bug)
Just for fun, nothing serious so don't worry:

Code: Select all

SteamAPI_Init();
for(i = 0; i < 10; i++) {
    SteamClient()->BReleaseSteamPipe(i);
}
SteamController()->Shutdown();
Very good tutorial, I learned, thanks aluigi