All pastes #734236 Raw Edit

Something

public text v1 · immutable
·published 2007-10-12 14:53 UTC
rendered paste body
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