Well, I’ve been away for a while, and still don’t have enough time to do anything helpful, but as my personal circumstances have changed recently I thought I would document some of the stuff that I know about Steam, in the hope that it helps others in their work. Sorry if it's a bit rushed - I knocked it up pretty quickly, but wanted to start filling out the Developer forum with some more useful stuff Smile
Please note that I’ve written most of the code directly in this thread, as such it is largely untested and any corrections for typos/ general stupidity are welcome Smile
I have also re-uploaded all of my original tutorials on building a Steam Launcher in C# and you can find them here: http://cs.rin.ru/forum/viewtopic.php?t=43463
A lot of the documentation in this thread is also covered in the latter parts of the tutorials which detail integrating Steam content into the launcher.
As usual, all comments / suggestions are most welcome Wink, but please bear in mind that i'm not around as much as I used to be Very Happy
Steam API revealed
Introduction
This thread is intended to document steam function calls and give practical examples of how they can be used in your own projects. This documentation is applicable to interfacing with a legitimate valve steam.dll file, as well as a steam emulator like SteamEmu by hCUPa. Where differences arise, then these will be explained further.
I will add to this as and when I can, and would also encourage others to participate in this thread if they can expand on any of the documentation.
It is assumed that you have a basic understanding of C#/VB.NET and calling dll functions from your programs. This documentation will not explain variable types, calling conventions or any other basic programming methodology. If you do not have basic programming skills then please read my earlier tutorials on building a content launcher in C# and integrating it with Steam: http://cs.rin.ru/forum/viewtopic.php?t=43463
Getting started
Using the functions contained in steam.dll is pretty much the same as using the functions in any other windows dll. However, there are some basic structures and enums that you should always declare to make your life easier. I will not detail everything in one go, but rather add to this thread as we go along.
It is worth noting at this point that SteamEmu by hCUPa is a very complete steam emulator, so much so that we can actually replace our legitimate steam dll with the SteamEmu dll and our code will still work. There are some exceptions, and even some additions to the things we can do with SteamEmu, and where relevant, they will be highlighted.
Before we start, note that you will need the following namespaces for the code below to compile:
Code:
C# -------------------------------------------------------------------------- -------
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Text;
using System.Runtime.InteropServices;
VB -------------------------------------------------------------------------- -------
Imports System
Imports System.Collections.Generic
Imports System.Windows.Forms
Imports System.Text
Imports System.Runtime.InteropServices
You will also need to set your compiler to compile as x86 code, particularly if you are deloping within a 64bit environment, as we are going to be accessing a 32bit dll.
Steam error handling
Like all things in programming, let’s start with errors! We need to define some stuff to be able to create a Steam error object and report Steam errors as and when they occur. First we need to create some enums to allow us to easily see what errors occurred in a textual and descriptive way.
Code:
C# -------------------------------------------------------------------------- -------
public enum eSteamError
{
eSteamErrorNone = 0,
eSteamErrorUnknown = 1,
eSteamErrorLibraryNotInitialized = 2,
eSteamErrorLibraryAlreadyInitialized = 3,
eSteamErrorConfig = 4,
eSteamErrorContentServerConnect = 5,
eSteamErrorBadHandle = 6,
eSteamErrorHandlesExhausted = 7,
eSteamErrorBadArg = 8,
eSteamErrorNotFound = 9,
eSteamErrorRead = 10,
eSteamErrorEOF = 11,
eSteamErrorSeek = 12,
eSteamErrorCannotWriteNonUserConfigFile = 13,
eSteamErrorCacheOpen = 14,
eSteamErrorCacheRead = 15,
eSteamErrorCacheCorrupted = 16,
eSteamErrorCacheWrite = 17,
eSteamErrorCacheSession = 18,
eSteamErrorCacheInternal = 19,
eSteamErrorCacheBadApp = 20,
eSteamErrorCacheVersion = 21,
eSteamErrorCacheBadFingerPrint = 22,
eSteamErrorNotFinishedProcessing = 23,
eSteamErrorNothingToDo = 24,
eSteamErrorCorruptEncryptedUserIDTicket = 25,
eSteamErrorSocketLibraryNotInitialized = 26,
eSteamErrorFailedToConnectToUserIDTicketValidationServer = 27,
eSteamErrorBadProtocolVersion = 28,
eSteamErrorReplayedUserIDTicketFromClient = 29,
eSteamErrorReceiveResultBufferTooSmall = 30,
eSteamErrorSendFailed = 31,
eSteamErrorReceiveFailed = 32,
eSteamErrorReplayedReplyFromUserIDTicketValidationServer = 33,
eSteamErrorBadSignatureFromUserIDTicketValidationServer = 34,
eSteamErrorValidationStalledSoAborted = 35,
eSteamErrorInvalidUserIDTicket = 36,
eSteamErrorClientLoginRateTooHigh = 37,
eSteamErrorClientWasNeverValidated = 38,
eSteamErrorInternalSendBufferTooSmall = 39,
eSteamErrorInternalReceiveBufferTooSmall = 40,
eSteamErrorUserTicketExpired = 41,
eSteamErrorCDKeyAlreadyInUseOnAnotherClient = 42,
eSteamErrorNotLoggedIn = 101,
eSteamErrorAlreadyExists = 102,
eSteamErrorAlreadySubscribed = 103,
eSteamErrorNotSubscribed = 104,
eSteamErrorAccessDenied = 105,
eSteamErrorFailedToCreateCacheFile = 106,
eSteamErrorCallStalledSoAborted = 107,
eSteamErrorEngineNotRunning = 108,
eSteamErrorEngineConnectionLost = 109,
eSteamErrorLoginFailed = 110,
eSteamErrorAccountPending = 111,
eSteamErrorCacheWasMissingRetry = 112,
eSteamErrorLocalTimeIncorrect = 113,
eSteamErrorNetwork = 200
}
VB -------------------------------------------------------------------------- -------
Public Enum eSteamError
eSteamErrorNone = 0
eSteamErrorUnknown = 1
eSteamErrorLibraryNotInitialized = 2
eSteamErrorLibraryAlreadyInitialized = 3
eSteamErrorConfig = 4
eSteamErrorContentServerConnect = 5
eSteamErrorBadHandle = 6
eSteamErrorHandlesExhausted = 7
eSteamErrorBadArg = 8
eSteamErrorNotFound = 9
eSteamErrorRead = 10
eSteamErrorEOF = 11
eSteamErrorSeek = 12
eSteamErrorCannotWriteNonUserConfigFile = 13
eSteamErrorCacheOpen = 14
eSteamErrorCacheRead = 15
eSteamErrorCacheCorrupted = 16
eSteamErrorCacheWrite = 17
eSteamErrorCacheSession = 18
eSteamErrorCacheInternal = 19
eSteamErrorCacheBadApp = 20
eSteamErrorCacheVersion = 21
eSteamErrorCacheBadFingerPrint = 22
eSteamErrorNotFinishedProcessing = 23
eSteamErrorNothingToDo = 24
eSteamErrorCorruptEncryptedUserIDTicket = 25
eSteamErrorSocketLibraryNotInitialized = 26
eSteamErrorFailedToConnectToUserIDTicketValidationServer = 27
eSteamErrorBadProtocolVersion = 28
eSteamErrorReplayedUserIDTicketFromClient = 29
eSteamErrorReceiveResultBufferTooSmall = 30
eSteamErrorSendFailed = 31
eSteamErrorReceiveFailed = 32
eSteamErrorReplayedReplyFromUserIDTicketValidationServer = 33
eSteamErrorBadSignatureFromUserIDTicketValidationServer = 34
eSteamErrorValidationStalledSoAborted = 35
eSteamErrorInvalidUserIDTicket = 36
eSteamErrorClientLoginRateTooHigh = 37
eSteamErrorClientWasNeverValidated = 38
eSteamErrorInternalSendBufferTooSmall = 39
eSteamErrorInternalReceiveBufferTooSmall = 40
eSteamErrorUserTicketExpired = 41
eSteamErrorCDKeyAlreadyInUseOnAnotherClient = 42
eSteamErrorNotLoggedIn = 101
eSteamErrorAlreadyExists = 102
eSteamErrorAlreadySubscribed = 103
eSteamErrorNotSubscribed = 104
eSteamErrorAccessDenied = 105
eSteamErrorFailedToCreateCacheFile = 106
eSteamErrorCallStalledSoAborted = 107
eSteamErrorEngineNotRunning = 108
eSteamErrorEngineConnectionLost = 109
eSteamErrorLoginFailed = 110
eSteamErrorAccountPending = 111
eSteamErrorCacheWasMissingRetry = 112
eSteamErrorLocalTimeIncorrect = 113
eSteamErrorNetwork = 200
End Enum
Code:
C# -------------------------------------------------------------------------- -------
public enum eDetailedPlatformErrorType
{
eNoDetailedErrorAvailable,
eStandardCerrno,
eWin32LastError,
eWinSockLastError,
eDetailedPlatformErrorCount
}
VB -------------------------------------------------------------------------- -------
Public Enum EDetailedPlatformErrorType
eNoDetailedErrorAvailable
eStandardCerrno
eWin32LastError
eWinSockLastError
eDetailedPlatformErrorCount
End Enum
After we have declared our enums, we can create an error structure in code with the following:
Code:
C# -------------------------------------------------------------------------- -------
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
public struct TSteamError
{
public eSteamError eSteamError;
public eDetailedPlatformErrorType eDetailedErrorType;
public uint ErrCode;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
public string ErrDescription;
}
VB -------------------------------------------------------------------------- -------
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, pack:=4)> _
Public Structure TSteamError
Public eSteamError As eSteamError
Public eDetailedErrorType As EDetailedPlatformErrorType
Public ErrCode As UInteger
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=255)> Public ErrDescription As String
End Structure
When we have this structure in place, we can then create an error variable within our program like so:
Code:
C# -------------------------------------------------------------------------- -------
TSteamError SErr = new TSteamError();
VB -------------------------------------------------------------------------- -------
Dim SErr As New TSteamError()
Steam log-in functions
So, now we can report errors back to our program, let’s look at how we can actually connect with the Steam network. This is one thing that we can do differently in Steam Emu, however first let’s see how we would Log-In to a legitimate Steam account.
To do the Login, we need to add another structure:
Code:
C# -------------------------------------------------------------------------- -------
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
public struct TSteamProgress
{
public int Valid;
public int Percent;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
public string Progress;
}
VB -------------------------------------------------------------------------- -------
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, pack:=4)> _
Public Structure TSteamProgress
Public Valid As Integer
Public Percent As Integer
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=255)> Public Progress As String
End Structure
And use the following functions:
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamStartEngine(ref TSteamError SteamErr);
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamStartup(uint UsingMask, ref TSteamError SteamErr);
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern uint SteamLogin(string User, string Password, int isSecureComputer, ref TSteamError SteamErr);
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern uint SteamSetUser(string User, ref int UserSet, ref TSteamError SteamErr);
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamProcessCall(uint SteamHandle, ref TSteamProgress Progress, ref TSteamError SteamErr);
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamIsLoggedIn(ref int isLoggedIn, ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamStartEngine(ByRef SteamErr As TSteamError) As Integer
End Function
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamStartup(ByVal UsingMask As UInteger, ByRef SteamErr As TSteamError) As Integer
End Function
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamLogin(ByVal User As String, ByVal Password As String, ByVal isSecureComputer As Integer, ByRef SteamErr As TSteamError) As UInteger
End Function
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamSetUser(ByVal User As String, ByRef UserSet As Integer, ByRef SteamErr As TSteamError) As UInteger
End Function
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamProcessCall(ByVal SteamHandle As UInteger, ByRef Progress As TSteamProgress, ByRef SteamError As TSteamError) As Integer
End Function
<DllImport("steam.dll, CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamIsLoggedIn(ByRef isLoggedIn As Integer, ByRef SteamErr As TSteamError) As Integer
End Function
And to cleanup afterwards, we will also need these functions:
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern uint SteamLogout(ref TSteamError SteamErr);
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamCleanup(ref TSteamError SteamErr);
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamShutdownEngine(ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamLogout(ByRef SteamErr As TSteamError) As UInteger
End Function
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamCleanup(ByRef SteamErr As TSteamError) As Integer
End Function
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamShutdownEngine(ByRef SteamErr As TSteamError) As Integer
End Function
After declaring the above function calls, we can login to Steam using something like this:
Code:
C# -------------------------------------------------------------------------- -------
TSteamError SErr = new TSteamError();
int iSteamReturn = 0;
int iUserSet = 0;
int isLoggedIn = 0;
string SteamUserName = "MyUserName";
string SteamUserPassword = "MyPassword";
uint sHandle;
uint startoptions = 15;
iSteamReturn = SteamStartEngine(ref SErr);
iSteamReturn = SteamStartup(startoptions, ref SErr);
TSteamProgress TProgress = new TSteamProgress();
TProgress.Valid = 1;
sHandle = SteamLogin(SteamUserName, SteamUserPassword, 1, ref SErr);
while (SteamProcessCall(sHandle, ref TProgress, ref SErr) == 0)
{
Application.DoEvents();
}
iSteamReturn = SteamIsLoggedIn (ref isLoggedIn, ref SErr);
if (isLoggedIn == 1)
{
sHandle = SteamSetUser(SteamUserName, ref iUserSet, ref SErr);
TProgress = new TSteamProgress();
TProgress.Valid = 1;
while (SteamProcessCall(sHandle, ref TProgress, ref SErr) == 0)
{
Application.DoEvents();
}
if (iUserSet == 1)
{
//Do Something!
}
}
sHandle = SteamLogout(ref SErr);
TProgress = new TSteamProgress();
TProgress.Valid = 1;
while (SteamProcessCall(sHandle, ref TProgress, ref SErr) == 0)
{
Application.DoEvents();
}
iSteamReturn = SteamCleanup(ref SErr);
iSteamReturn = SteamShutdownEngine(ref SErr);
VB -------------------------------------------------------------------------- -------
Dim SErr As New TSteamError()
Dim iSteamReturn As Integer = 0
Dim iUserSet As Integer = 0
Dim isLoggedIn As Integer = 0
Dim SteamUserName As String = "MyUserName"
Dim SteamUserPassword As String = "MyPassword"
Dim sHandle As UInteger
Dim startoptions As UInteger = 15
iSteamReturn = SteamStartEngine(SErr)
iSteamReturn = SteamStartup(startoptions, SErr)
Dim TProgress As New TSteamProgress()
TProgress.Valid = 1
sHandle = SteamLogin(SteamUserName, SteamUserPassword, 1, SErr)
While SteamProcessCall(sHandle, TProgress, SErr) = 0
Application.DoEvents()
End While
iSteamReturn = SteamIsLoggedIn(isLoggedIn, SErr)
If isLoggedIn = 1 Then
sHandle = SteamSetUser(SteamUserName, iUserSet, SErr)
TProgress = New TSteamProgress()
TProgress.Valid = 1
While SteamProcessCall(sHandle, TProgress, SErr) = 0
Application.DoEvents()
End While
If iUserSet = 1 Then
'Do Something!
End If
End If
sHandle = SteamLogout(SErr)
TProgress = New TSteamProgress()
TProgress.Valid = 1
While SteamProcessCall(sHandle, TProgress, SErr) = 0
Application.DoEvents()
End While
iSteamReturn = SteamCleanup(SErr)
iSteamReturn = SteamShutdownEngine(SErr)
To show the code flow, I have left out any relevant error handling, but in the real world you would add a check on your Steam error variable whenever it is passed to an external function like so, and break appropriately:
Code:
C# -------------------------------------------------------------------------- -------
iSteamReturn = SteamStartEngine(ref SErr);
if (SErr.eSteamError != eSteamError.eSteamErrorNone ) { MessageBox.Show("Steam Error : " + SErr.ErrDescription); }
VB -------------------------------------------------------------------------- -------
iSteamReturn = SteamStartEngine(SErr)
If SErr.eSteamError <> eSteamError.eSteamErrorNone Then
MessageBox.Show("Steam Error : " + SErr.ErrDescription)
End If
Now, if you aren’t bothered about logging into steam correctly, and just want to use SteamEmu, then using SteamSetUser on its own will suffice, however it is worth noting that SteamEmu will process all of the above calls correctly, so you could use either method. You must also take care if using SteamSetUser on its own to ensure you start the SteamEngine seperately in your code, whenever it is required.
Steam application functions
Ok, so now we can log in to either Steam (via a valid account) or SteamEmu, let’s see how we can get various information about Steam content out of the clientregistry.blob file.
First we need a structure that returns information about the clientregistry.blob, we can use this to determine how many apps are in the blob file and dimension our arrays accordingly:
Code:
C# -------------------------------------------------------------------------- -------
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
public struct TSteamAppStats
{
public uint NumApps;
public uint MaxNameChars;
public uint MaxVersionLabelChars;
public uint MaxLaunchOptions;
public uint MaxLaunchOptionDescChars;
public uint MaxLaunchOptionCmdLineChars;
public uint MaxNumIcons;
public uint MaxIconSize;
}
VB -------------------------------------------------------------------------- -------
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, pack:=4)> _
Public Structure TSteamAppStats
Public NumApps As UInteger
Public MaxNameChars As UInteger
Public MaxVersionLabelChars As UInteger
Public MaxLaunchOptions As UInteger
Public MaxLaunchOptionDescChars As UInteger
Public MaxLaunchOptionCmdLineChars As UInteger
Public MaxNumIcons As UInteger
Public MaxIconSize As UInteger
End Structure
Now we need an object to hold information about a specific app, like so:
Code:
C# -------------------------------------------------------------------------- -------
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
public struct TSteamApp
{
public string Name;
public uint maxNameChars;
public string latestVersionLabel;
public uint maxLatestVersionLabelChars;
public string currentVersionLabel;
public uint maxCurrentVersionLabelChars;
public string cacheFile;
public uint maxCacheFileChars;
public uint id;
public uint latestVersionId;
public uint currentVersionId;
public uint minCacheFileSizeMB;
public uint maxCacheFileSizeMB;
public uint numLaunchOptions;
public uint numIcons;
public uint numVersions;
public uint numDependencies;
}
VB -------------------------------------------------------------------------- -------
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, pack:=4)> _
Public Structure TSteamApp
Public Name As String
Public maxNameChars As UInteger
Public latestVersionLabel As String
Public maxLatestVersionLabelChars As UInteger
Public currentVersionLabel As String
Public maxCurrentVersionLabelChars As UInteger
Public cacheFile As String
Public maxCacheFileChars As UInteger
Public id As UInteger
Public latestVersionId As UInteger
Public currentVersionId As UInteger
Public minCacheFileSizeMB As UInteger
Public maxCacheFileSizeMB As UInteger
Public numLaunchOptions As UInteger
Public numIcons As UInteger
Public numVersions As UInteger
Public numDependencies As UInteger
End Structure
If we then declare the following functions:
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamGetAppStats(ref TSteamAppStats AppStats, ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamGetAppStats(ByRef AppStats As TSteamAppStats, ByRef SteamErr As TSteamError) As Integer
End Function
The SteamGetAppStats call returns a TSteamAppStats object that tells us how many AppIDs are available, amongst other things.
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamGetAppIds(uint[] AppIDs, uint MaxAppIDs, ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamGetAppIds(ByVal AppIDs() As UInteger, ByVal MaxAppIDs As UInteger, ByRef SteamErr As TSteamError) As Integer
End Function
The SteamGetAppIds call returns an array of all the AppIDs
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamEnumerateApp(uint AppID, ref TSteamApp AppStruc, ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamEnumerateApp(ByVal Appid As UInteger, ByRef ByvalAppStruc As TSteamApp, ByRef SteamErr As TSteamError) As Integer
End Function
The SteamEnumerateApp call returns basic information about the queried AppID
We are now in a position to enumerate all the apps in the local clientregistry.blob file and retrieve information from them like so:
Code:
C# -------------------------------------------------------------------------- -------
TSteamError SErr = new TSteamError();
int iSteamReturn = 0;
TSteamAppStats AppStats = new TSteamAppStats();
iSteamReturn = SteamGetAppStats(ref AppStats, ref SErr);
if (AppStats.NumApps > 0)
{
uint[] RawAppIDs;
RawAppIDs = new uint[AppStats.NumApps];
iSteamReturn = SteamGetAppIds(RawAppIDs, AppStats.NumApps, ref SErr);
foreach (uint RawAppID in RawAppIDs)
{
TSteamApp AppNFO = new TSteamApp();
AppNFO.Name = new String(' ', 255);
AppNFO.maxNameChars = 255;
AppNFO.currentVersionLabel = new String(' ', 255);
AppNFO.maxCurrentVersionLabelChars = 255;
AppNFO.latestVersionLabel = new String(' ', 255);
AppNFO.maxLatestVersionLabelChars = 255;
AppNFO.cacheFile = new String(' ', 255);
AppNFO.maxCacheFileChars = 255;
iSteamReturn = SteamEnumerateApp(RawAppID, ref AppNFO, ref SErr);
//Do Something!
}
}
VB -------------------------------------------------------------------------- -------
Dim SErr As New TSteamError()
Dim iSteamReturn As Integer = 0
Dim AppStats As New TSteamAppStats()
iSteamReturn = SteamGetAppStats(AppStats, SErr)
If AppStats.NumApps > 0 Then
Dim RawAppIDs As UInteger()
RawAppIDs = New UInteger(AppStats.NumApps - 1) {}
iSteamReturn = SteamGetAppIds(RawAppIDs, AppStats.NumApps, SErr)
For Each RawAppID As UInteger In RawAppIDs
Dim AppNFO As New TSteamApp()
AppNFO.Name = New String(" "c, 255)
AppNFO.maxNameChars = 255
AppNFO.currentVersionLabel = New String(" "c, 255)
AppNFO.maxCurrentVersionLabelChars = 255
AppNFO.latestVersionLabel = New String(" "c, 255)
AppNFO.maxLatestVersionLabelChars = 255
AppNFO.cacheFile = New String(" "c, 255)
AppNFO.maxCacheFileChars = 255
iSteamReturn = SteamEnumerateApp(RawAppID, AppNFO, SErr)
'Do Something!
Next
End If
Ok, so now we can enumerate all the AppIDs in the clientregistry.blob file, what other functions can we actually use whilst processing them.
First we can tell if an App is subscribed using the following function, this is handy if you’re writing a legitimate launcher:
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamIsAppSubscribed(uint AppID, ref uint IsAppSubscribed, ref uint IsAppReady, ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamIsAppSubscribed(ByVal appid As UInteger, ByRef IsAppSubscribed As UInteger, ByRef IsAppReady As UInteger, ByRef SteamErr As TSteamError) As Integer
End Function
We can call it like the following example, where we are testing to see if a subscription exists for Half-Life:
Code:
C# -------------------------------------------------------------------------- -------
TSteamError SErr = new TSteamError();
int iSteamReturn = 0;
uint isAppSubscribed = 0;
uint isAppReady = 0;
uint AppID = 70;
iSteamReturn = SteamIsAppSubscribed(AppID, ref isAppSubscribed, ref isAppReady, ref SErr);
if (isAppSubscribed == 1 && isAppReady == 1)
{
//Do Something!
}
VB -------------------------------------------------------------------------- -------
Dim SErr As New TSteamError()
Dim iSteamReturn As Integer = 0
Dim isAppSubscribed As UInteger = 0
Dim isAppReady As UInteger = 0
Dim AppID As UInteger = 70
iSteamReturn = SteamIsAppSubscribed(AppID, isAppSubscribed, isAppReady, SErr)
If isAppSubscribed = 1 AndAlso isAppReady = 1 Then
'Do Something!
End If
If the App is subscribed then we can easily launch it with the following:
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern uint SteamLaunchApp(uint AppId, uint opt, string Args, ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamLaunchApp(ByVal appid As UInteger, ByVal opt As UInteger, ByVal Args As String, ByRef SteamErr As TSteamError) As UInteger
End Function
Which we call like so:
Code:
C# -------------------------------------------------------------------------- -------
TSteamError SErr = new TSteamError();
uint sHandle = 0;
uint AppID = 70;
string sArgs = " -console";
sHandle = SteamLaunchApp(AppID, 0, sArgs, ref SErr);
VB -------------------------------------------------------------------------- -------
Dim SErr As New TSteamError()
Dim sHandle As UInteger = 0
Dim AppID As UInteger = 70
Dim sArgs As String = " -console"
sHandle = SteamLaunchApp(AppID, 0, sArgs, SErr)
SteamLaunchApp is a great way of launching any steam content, because it will do all of the following for you:
Extract minimum footprint files if they do not exist
Refresh minimum footprint files if they are outdated from the GCF
Extract additional dependencies if needed (e.g. the Steam media Player)
Returns a handle to allow code to be written that waits until the App has finished
Launch the content with optional arguments
Though SteamLaunchApp will launch the associated AppID, there are some apps in Steam that have multiple launch options, like a config screen for example. To get these we need to enumerate the AppID’s Launch options, and for this we need another structure:
Code:
C# -------------------------------------------------------------------------- -------
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
public struct TSteamAppLaunchOption
{
public string description;
public uint maxDescChars;
public string cmdLine;
public uint maxCmdLineChars;
public uint index;
public uint iconIndex;
public uint noDesktopShortcut;
public uint noStartMenuShortcut;
public uint isLongRunningUnattended;
}
VB -------------------------------------------------------------------------- -------
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, pack:=4)> _
Public Structure TSteamAppLaunchOption
Public description As String
Public maxDescChars As UInteger
Public cmdLine As String
Public maxCmdLineChars As UInteger
Public index As UInteger
Public iconIndex As UInteger
Public noDesktopShortcut As UInteger
Public noStartMenuShortcut As UInteger
Public isLongRunningUnattended As UInteger
End Structure
And also, the following function call to populate the structure:
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamEnumerateAppLaunchOption(uint Appid, uint LaunchIdx, ref TSteamAppLaunchOption SteamAppLaunchOption, ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamEnumerateAppLaunchOption(ByVal Appid As UInteger, ByVal LaunchIdx As UInteger, ByRef SteamAppLaunchOption As TSteamAppLaunchOption, ByRef SteamErr As TSteamError) As Integer
End Function
We can now do something like this:
Using AppNFO from our SteamEnumerateApp example above:
Code:
C# -------------------------------------------------------------------------- -------
if (AppNFO.numLaunchOptions > 1)
{
for (uint iloop = 1; iloop < AppNFO.numLaunchOptions; iloop++)
{
AppLO.description = new String(' ', 255);
AppLO.maxDescChars = 255;
AppLO.cmdLine = new String(' ', 255);
AppLO.maxCmdLineChars = 255;
AppLO.index = 0;
AppLO.iconIndex = 0;
AppLO.noDesktopShortcut = 0;
AppLO.noStartMenuShortcut = 0;
AppLO.isLongRunningUnattended = 0;
iSteamReturn = SteamEnumerateAppLaunchOption(AppID, iloop, ref AppLO, ref SErr);
//Do Something!
}
}
VB -------------------------------------------------------------------------- -------
If AppNFO.numLaunchOptions > 1 Then
For iloop As UInteger = 1 To AppNFO.numLaunchOptions - 1
Dim AppLO As New TSteamAppLaunchOption()
AppLO.description = New String(" "c, 255)
AppLO.maxDescChars = 255
AppLO.cmdLine = New String(" "c, 255)
AppLO.maxCmdLineChars = 255
AppLO.index = 0
AppLO.iconIndex = 0
AppLO.noDesktopShortcut = 0
AppLO.noStartMenuShortcut = 0
AppLO.isLongRunningUnattended = 0
iSteamReturn = SteamEnumerateAppLaunchOption(AppID, iloop, AppLO, SErr)
'Do Something!
Next
End If
We can then store each additional launch option for use in our program.
We can also get even more detailed information, by using the SteamGetAppUserDefinedInfo function defined below:
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamGetAppUserDefinedInfo(uint AppId, string Key, StringBuilder Buff, uint BuffSize, ref uint ResultSize, ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamGetAppUserDefinedInfo(ByVal AppId As UInteger, ByVal Key As String, ByVal Buff As System.Text.StringBuilder, ByVal BuffSize As UInteger, ByRef ResultSize As UInteger, ByRef SteamErr As TSteamError) As Integer
End Function
We call it like so:
Code:
C# -------------------------------------------------------------------------- -------
TSteamError SErr = new TSteamError();
int iSteamReturn = 0;
uint iBuffSize = 512;
uint iResultSize = iBuffSize;
uint AppID = 70;
StringBuilder statebuff = new StringBuilder((int)iBuffSize, (int)iBuffSize);
iSteamReturn = SteamGetAppUserDefinedInfo(AppID, "state", statebuff, iBuffSize, ref iResultSize, ref SErr);
VB -------------------------------------------------------------------------- -------
Dim SErr As New TSteamError()
Dim iSteamReturn As Integer = 0
Dim iBuffSize As UInteger = 512
Dim iResultSize As UInteger = iBuffSize
Dim AppID As UInteger = 70
Dim statebuff As New StringBuilder(CInt(iBuffSize), CInt(iBuffSize))
iSteamReturn = SteamGetAppUserDefinedInfo(AppID, "state", statebuff, iBuffSize, iResultSize, SErr)
This will return the state of the given AppID, we can then test this to see if, for example, it is "eStateUnavailable" and not to be used, or "eStateTool" – a Steam tool. This test should *not* be case sensitive, as the return result in some instances has been found to be inconsistent
Other things we can recover in this way include:
"icon" – returns the icon name of the given AppID
"MediaFileType" - returns the files media type e.g. if it is a "Movie"
"Homepage" – returns the homepage URL for the AppID
"GameManualURL" – returns a URL for the game Manual
"Developer" – returns a URL for the developer
"DemoOfAppID" – Identifies a demo App.
Though you should note that not all Steam content has all of these options.
Another useful function is SteamGetAppDir as declared below:
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamGetAppDir(uint AppId, StringBuilder Directory, ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamGetAppDir(ByVal AppId As UInteger, ByVal Directory As System.Text.StringBuilder, ByRef SteamErr As TSteamError) As Integer
End Function
This can be used to return the full path of the App directory, or the directory where minimum footprint files are extracted, or that resides in the “common” folder.
This always returns a string relevant to:
Your SteamApps Path
Your current SteamUser
So if my SteamApps folder was:
C:\Games\Steam\SteamApps
And the AppID in question was 70 (Half-Life) and I was logged in as the user “SteamUSER” then this would return:
“C:\Games\Steam\SteamApps\ SteamUSER \Half-Life\”
We can call it like so:
Code:
C# -------------------------------------------------------------------------- -------
TSteamError SErr = new TSteamError();
int iSteamReturn = 0;
uint AppID = 70;
uint iBuffSize = 512;
StringBuilder strAppDir = new StringBuilder((int)iBuffSize);
iSteamReturn = SteamGetAppDir(AppID, strAppDir, ref SErr);
VB -------------------------------------------------------------------------- -------
Dim SErr As New TSteamError()
Dim iSteamReturn As Integer = 0
Dim AppID As UInteger = 70
Dim iBuffSize As UInteger = 512
Dim strAppDir As New StringBuilder(CInt(iBuffSize))
iSteamReturn = SteamGetAppDir(AppID, strAppDir, SErr)
SteamEmu specific functions
hCUPa, being the kind sole that he is, provided developers with a number of SteamEmu specific functions to make some of the more complex tasks we face a lot easier.
Note, that SteamEmu functions do not make use of the Steam Error object.
These are:
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamEmu_GetGcfList(uint AppID, StringBuilder Buf, uint BufSize);
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamEmu_GetWholeGcfCopy(string GCF, string Dest);
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamEmu_GetLocalGcfCopy(string GCF, string Dest);
[DllImport("steam.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern int SteamEmu_GetProperty(string name, StringBuilder Buf, uint BufSize);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamEmu_GetGcfList(ByVal appid As UInteger, ByVal Buf As System.Text.StringBuilder, ByVal BufSize As UInteger) As Integer
End Function
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamEmu_GetWholeGcfCopy(ByVal GCF As String, ByVal Dest As String) As Integer
End Function
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamEmu_GetLocalGcfCopy(ByVal GCF As String, ByVal Dest As String) As Integer
End Function
<DllImport("steam.dll", CallingConvention:=CallingConvention.Cdecl)> _
Public Function SteamEmu_GetProperty(ByVal name As String, ByVal Buf As StringBuilder, ByVal BufSize As UInteger) As Integer
End Function
SteamEmu_GetGcfList will return a string detailing all of the GCFs required for the AppID specified. The string is delimited by semi colons and has a special use in that files proceeded with a “@” symbol indicate that they are optional and therefore not required to launch the content.
We can call it like so:
Code:
C# -------------------------------------------------------------------------- -------
int iSteamReturn = 0;
uint AppID = 70;
uint iBuffSize = 512;
StringBuilder GCFList = new StringBuilder((int)iBuffSize);
iSteamReturn = SteamEmu_GetGcfList(AppID, GCFList, iBuffSize);
VB -------------------------------------------------------------------------- -------
Dim iSteamReturn As Integer = 0
Dim AppID As UInteger = 70
Dim iBuffSize As UInteger = 512
Dim GCFList As New StringBuilder(CInt(iBuffSize))
iSteamReturn = SteamEmu_GetGcfList(AppID, GCFList, iBuffSize)
And we could process the resulting string like so:
Code:
C# -------------------------------------------------------------------------- -------
string[] SEGCFList = GCFList.ToString().Split(";".ToCharArray());
foreach (string sgcf in SEGCFList)
{
if (sgcf.IndexOf("@") == -1 && sgcf != "")
{
//Mandatory file!
}
else
{
//Optional!
}
}
VB -------------------------------------------------------------------------- -------
Dim SEGCFList As String() = GCFList.ToString().Split(";".ToCharArray())
For Each sgcf As String In SEGCFList
If sgcf.IndexOf("@") = -1 AndAlso sgcf <> "" Then
'Mandatory file!
Else
'Optional!
End If
Next
One quirk of SteamEmu_GetGcfList is that it will return an “ncf” file as the extension “gcf”, so you need to look out for that.
SteamEmu_GetLocalGcfCopy and SteamEmu_GetWholeGcfCopy do basically the same thing, except that SteamEmu_GetLocalGcfCopy extracts the required minimum footprint files only, whereas SteamEmu_GetWholeGcfCopy extracts all files from a GCF file.
Call them like so:
Code:
C# -------------------------------------------------------------------------- -------
int iSteamReturn = 0;
string sGCF = "c:\\gcf\\winui.gcf";
string sPath = "c:\\SteamExtracted\\";
iSteamReturn = SteamEmu_GetLocalGcfCopy(sGCF, sPath);
VB -------------------------------------------------------------------------- -------
Dim iSteamReturn As Integer = 0
Dim sGCF As String = "c:\gcf\winui.gcf"
Dim sPath As String = "c:\SteamExtracted\"
iSteamReturn = SteamEmu_GetLocalGcfCopy(sGCF, sPath)
SteamEmu_GetProperty is used to retrieve SteamEmu configuration settings, you can call it like so:
Code:
C# -------------------------------------------------------------------------- -------
uint iBuffSize = 512;
string PropType = "gcf_root";
StringBuilder buff = new StringBuilder((int)iBuffSize);
iSteamReturn = SteamEmu_GetProperty(PropType, buff, iBuffSize);
VB -------------------------------------------------------------------------- -------
Dim iBuffSize As UInteger = 512
Dim PropType As String = "gcf_root"
Dim buff As New StringBuilder(CInt(iBuffSize))
iSteamReturn = SteamEmu_GetProperty(PropType, buff, iBuffSize)
The example above will show the path that SteamEmu is currently using to search for GCF files.
There is one more thing worth mentioning about SteamEmu, and that is its ability to pre-load GCFs defined in an environment variable. This is useful for supporting multi-language content or optional GCF files. Below is an example of how to do this:
Code:
C# -------------------------------------------------------------------------- -------
string AddGCFList = "half-life high definition.gcf";
Environment.SetEnvironmentVariable("SteamEmuPremount", AddGCFList);
VB -------------------------------------------------------------------------- -------
Dim AddGCFList As String = "half-life high definition.gcf"
Environment.SetEnvironmentVariable("SteamEmuPremount", AddGCFList)
You would typically use that code prior to SteamLaunchApp, you should note that you can also define multiple premounts by using a semi-colon seperated string.
Well, that's it for now, please play with all the above, and see what other options and information you can get out of Steam.
Expect more Steam API related tutorials in the future....
EDIT LOG
======================================
07/SEP/07 Sectionalised the text for easier reference prior to API tutorial post 2 going live
15/AUG/07 Added cdecl to all function declarations - VS2005 default is stdcall if unspecified! This is not usually a problem with the calls above, as both calling conventions are right to left and support Varargs (in differing degrees), but it may cause problems with advanced callbacks later and is the correct way to do it.
08/AUG/07 Added information about SteamGetAppUserDefinedInfo returning different case on strings in some situations. thanks g3cka
02/AUG/07 Added information about SteamEmu_GetProperty
01/AUG/07 Added VB code
01/AUG/07 First publication
Last edited by Mitsukarina on Friday 07th September 2007 05:39 pm; edited 33 times in total
Back to top
View user's profile Send private message
Mitsukarina
Super flooder Почетный графоман
Joined: 14 Apr 2006
Posts: 599
New postPosted: Wednesday 01st August 2007 05:50 pm Post subject: Reply with quote
Steam API revealed - Part Two
Steam subscription functions
We now know how to process all the applications that are contained in the clientregistry.blob file, so let's look at how we can do the same thing with subscriptions.
You can think of a subscription as a collection of applications, we query them in the same way that we queried the applications, using very similar methods to retrieve information about the subscription itself along with a list of the associated AppIDs. Once we have this information, we can then retrieve basic information from the subscription record, and enumerate the applications as we did in the first part of this tutorial.
There is a distinct difference between Steam and SteamEmu when it comes to subscriptions, and that is that there is only one subscription that SteamEmu will ever return. This is Subscription 104 - hCUPa's Ultimate Subscription - That is purely a collection of every AppID that is available in the clientregistry.blob file.
Subscriptions are therefore largely irelevant if you only want to write a SteamEmu powered launcher, but if we want to write software that can process legitimate Steam accounts, then we need to understand the subscription calls a little more.
The structures that we require are as follows:
Code:
C# -------------------------------------------------------------------------- -------
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
public struct TSteamSubscriptionStats
{
public uint NumSubscriptions;
public uint MaxNameChars;
public uint MaxApps;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
public struct TSteamSubscription
{
public string Name;
public uint MaxNameChars;
public IntPtr AppIDs;
public uint MaxAppIDs;
public uint ID;
public uint NumApps;
public uint DurationInSeconds;
public uint CostInCents;
public uint IsAutoRenewing;
}
VB -------------------------------------------------------------------------- -------
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, pack:=4)> _
Public Structure TSteamSubscriptionStats
Public NumSubscriptions As UInteger
Public MaxNameChars As UInteger
Public MaxApps As UInteger
End Structure
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi, pack:=4)> _
Public Structure TSteamSubscription
Public Name As String
Public MaxNameChars As UInteger
Public AppIDs As IntPtr
Public MaxAppIDs As UInteger
Public ID As UInteger
Public NumApps As UInteger
Public DurationInSeconds As UInteger
Public CostInCents As UInteger
Public IsAutoReNewing As UInteger
End Structure
The main thing to note about the structures above is that we need to use a pointer to reference the AppID array for the subscription in the structure TSteamSubscription. There are a number of ways of passing a structure to unmanaged code that contains a pointer to an array, the code below illustrates doing this using manual marshaling of the array memory, which is probably the simplest method to use.
The functions that we will need to use for subscriptions are:
Code:
C# -------------------------------------------------------------------------- -------
[DllImport("steam.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int SteamGetSubscriptionStats(ref TSteamSubscriptionStats SubscriptionStats, ref TSteamError SteamErr);
[DllImport("steam.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int SteamGetSubscriptionIds(uint[] SubscriptionIDs, uint MaxSubscriptionIDs, ref TSteamError SteamErr );
[DllImport("steam.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int SteamEnumerateSubscription(uint SubscriptionID, ref TSteamSubscription Subscription, ref TSteamError SteamErr);
[DllImport("steam.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int SteamIsSubscribed(uint SubscriptionId, ref uint IsSubscribed, ref uint IsSubscriptionReady, ref TSteamError SteamErr);
VB -------------------------------------------------------------------------- -------
<DllImport("steam.dll", CallingConvention := CallingConvention.Cdecl)> _
Public Function SteamGetSubscriptionStats(ByRef SubscriptionStats As TSteamSubscriptionStats, ByRef SteamErr As TSteamError) As Integer
End Function
<DllImport("steam.dll", CallingConvention := CallingConvention.Cdecl)> _
Public Function SteamGetSubscriptionIds(ByVal SubscriptionIDs As UInteger(), ByVal MaxSubscriptionIDs As UInteger, ByRef SteamErr As TSteamError) As Integer
End Function
<DllImport("steam.dll", CallingConvention := CallingConvention.Cdecl)> _
Public Function SteamEnumerateSubscription(ByVal SubscriptionID As UInteger, ByRef Subscription As TSteamSubscription, ByRef SteamErr As TSteamError) As Integer
End Function
<DllImport("steam.dll", CallingConvention := CallingConvention.Cdecl)> _
Public Function SteamIsSubscribed(ByVal SubscriptionId As UInteger, ByRef IsSubscribed As UInteger, ByRef IsSubscriptionReady As UInteger, ByRef SteamErr As TSteamError) As Integer
End Function
These functions are the "subscription" equilavent of the application functions that we have already explored.
The code to process the subscriptions is very similar to the application enumeration code in the first part of this tutorial, the only difference is in the marshaling of the TSteamSubscription structures AppID array. So, assuming we have logged in to Steam:
Code:
C# -------------------------------------------------------------------------- -------
uint iBuffSize = 512;
int iSteamReturn = 0;
TSteamAppStats AppStats = new TSteamAppStats();
iSteamReturn = SteamGetAppStats(ref AppStats, ref SErr);
TSteamSubscriptionStats SubscriptionStats = new TSteamSubscriptionStats();
iSteamReturn = SteamGetSubscriptionStats(ref SubscriptionStats, ref SErr);
if (SubscriptionStats.NumSubscriptions > 0)
{
uint[] RawSubscriptionIDs;
RawSubscriptionIDs = new uint[SubscriptionStats.NumSubscriptions];
iSteamReturn = SteamGetSubscriptionIds(RawSubscriptionIDs, SubscriptionStats.NumSubscriptions, ref SErr);
uint[] tAppIDs;
tAppIDs = new uint[AppStats.NumApps]; //set the array to the maximum amount of Steam Apps
// Here we are creating a pointer to the tAppIDs array and allocating the correct amount of memory for manual marshaling
object arraytype = tAppIDs.GetValue(0);
int arraysize = Marshal.SizeOf(arraytype);
int buffer = arraysize * tAppIDs.Length;
IntPtr intPtr = Marshal.AllocHGlobal(buffer);
uint isSubSubscribed = 0;
uint isSubReady = 0;
foreach (uint RawSubscriptionID in RawSubscriptionIDs)
{
iSteamReturn = SteamIsSubscribed(RawSubscriptionID, ref isSubSubscribed, ref isSubReady, ref SErr);
if (isSubSubscribed == 1)
{
TSteamSubscription SubNFO = new TSteamSubscription();
SubNFO.Name = new String(' ', 255);
SubNFO.MaxNameChars = 255;
SubNFO.AppIDs = intPtr;
SubNFO.MaxAppIDs = AppStats.NumApps;
iSteamReturn = SteamEnumerateSubscription(RawSubscriptionID, ref SubNFO, ref SErr);
int ofs = 0;
uint uLoop;
for (uLoop = 0; uLoop <= SubNFO.NumApps - 1; uLoop++)
{
//populate our app array from marshaled memory
tAppIDs[uLoop] = (uint)Marshal.ReadInt32(intPtr, ofs);
ofs += arraysize;
}
//Do Something!
for (uLoop = 0; uLoop < SubNFO.NumApps; uLoop++) //Clear the relevant array indexes for the next subscription
{
tAppIDs[uLoop] = 0;
}
}
}
Marshal.FreeHGlobal(intPtr); //free the marshaled memory
}
VB -------------------------------------------------------------------------- -------
Dim iBuffSize As UInteger = 512
Dim iSteamReturn As Integer = 0
Dim AppStats As New TSteamAppStats()
iSteamReturn = SteamGetAppStats(AppStats, SErr)
Dim SubscriptionStats As New TSteamSubscriptionStats()
iSteamReturn = SteamGetSubscriptionStats(SubscriptionStats, SErr)
If SubscriptionStats.NumSubscriptions > 0 Then
Dim RawSubscriptionIDs As UInteger()
RawSubscriptionIDs = New UInteger(SubscriptionStats.NumSubscriptions - 1) {}
iSteamReturn = SteamGetSubscriptionIds(RawSubscriptionIDs, SubscriptionStats.NumSubscriptions, SErr)
Dim tAppIDs = New UInteger(AppStats.NumApps) {} 'set the array to the maximum amount of Steam(Apps)
' Here we are creating a pointer to the tAppIDs array and allocating the correct amount of memory for manual marshaling
Dim arraytype As Object = tAppIDs.GetValue(0)
Dim arraysize As Integer = Marshal.SizeOf(arraytype)
Dim buffer As Integer = arraysize * tAppIDs.Length
Dim intPtr As IntPtr = Marshal.AllocHGlobal(buffer)
Dim isSubSubscribed As UInteger = 0
Dim isSubReady As UInteger = 0
For Each RawSubscriptionID As UInteger In RawSubscriptionIDs
iSteamReturn = SteamIsSubscribed(RawSubscriptionID, isSubSubscribed, isSubReady, SErr)
If isSubSubscribed = 1 Then
Dim SubNFO As New TSteamSubscription()
SubNFO.Name = New String(" "C, 255)
SubNFO.MaxNameChars = 255
SubNFO.AppIDs = intPtr
SubNFO.MaxAppIDs = AppStats.NumApps
iSteamReturn = SteamEnumerateSubscription(RawSubscriptionID, SubNFO, SErr)
Dim ofs As Integer = 0
Dim uLoop As Integer
For uLoop = 0 To SubNFO.NumApps - 1 'populate our app array from marshaled memory
tAppIDs(uLoop) = Marshal.ReadInt32(intPtr, ofs)
ofs = ofs + arraysize
Next
'Do Something!
For uLoop = 0 To SubNFO.NumApps - 1 'Clear the relevant array indexes for the next subscription
tAppIDs(uLoop) = 0
Next
End If
Next
Marshal.FreeHGlobal(intPtr) 'free the marshaled memory
End If
The code above gives us the subscription record with its relevant information, along with the subscriptions associated application IDs.
The more advanced among you may want to explore the following alternative for C# that allows the easier creation of a pinned reference to the array using an unsafe code block. This method is not supported at all in VB.NET, but is simpler to use in C# as it removes the need to manually copy the array values from unmanaged memory into our array.
Code:
C# -------------------------------------------------------------------------- -------
IntPtr intPtr;
unsafe
{
fixed (uint* pArray = tAppIDs)
{
intPtr = new IntPtr((void*)pArray);
}
}
Please feel free to play with this code, until the next update.
EDIT LOG
======================================
09/SEP/07 First publication
Last edited by Mitsukarina on Sunday 09th September 2007 04:29 pm; edited 3 times in total