-
Notifications
You must be signed in to change notification settings - Fork 33
NullReferenceException #35
Description
When i trying use this example:
DotaGCHandler.Bootstrap(client);
DotaGCHandler dota = client.GetHandler();
I got exception:
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
at Dota2.GC.DotaGCHandler.<.ctor>b__45_0(Object stateInfo)
at System.Threading.TimerQueueTimer.<>c.<.cctor>b__23_0(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.TimerQueueTimer.CallCallback(Boolean isThreadPool)
at System.Threading.TimerQueueTimer.Fire(Boolean isThreadPool)
at System.Threading.TimerQueue.FireNextTimers()
at System.Threading.TimerQueue.AppDomainTimerCallback(Int32 id)
Process finished with exit code 6.
I initialized steam cli before starting and all looking good in this instance, but i dont know what is it
mac os
part of code:
using System;
using System.Collections.Generic;
using System.Threading;
using SteamKit2;
using SteamKit2.Internal; // brings in our protobuf client messages
using SteamKit2.GC; // brings in the GC related classes
using SteamKit2.GC.Dota.Internal; // brings in dota specific protobuf messages
using System.IO;
using System.Security.Cryptography;
using SteamKit2.Unified.Internal;
using Dota2;
using Dota2.GC;
namespace DotaAPIConsole
{
public class SteamAgent
{
public static SteamClient client;
public static SteamUser user;
SteamGameCoordinator gameCoordinator;
// public static Dota2.GC.DotaGCHandler dota;
SteamApps steamApps;
public static SteamFriends steamFriends;
CallbackManager callbackMgr;
public static bool isRunning;
bool gotMatch;
public int matchId;
static SteamUnifiedMessages steamUnifiedMessages;
static SteamUnifiedMessages.UnifiedService<IPlayer> playerService;
static JobID badgeRequest = JobID.Invalid;
public CMsgDOTAMatch Match { get; private set; }
const int APPID = 570;
public static string userName { get; private set; }
public static string password { get; private set; }
public static string authCode, twoFactorAuth;
public static byte[] sentryHash;
public SteamAgent()
{
DebugLog.AddListener(new SteamDebugListener());
DebugLog.Enabled = true;
userName =*;
password = *;
var configuration = SteamConfiguration.Create(b => b.WithProtocolTypes(ProtocolTypes.Tcp));
client = new SteamClient(configuration);
// get our handlers
callbackMgr = new CallbackManager( client );
// get the steamuser handler, which is used for logging on after successfully connecting
user = client.GetHandler<SteamUser>();
steamApps = client.GetHandler<SteamApps>();
steamFriends = client.GetHandler<SteamFriends>();
user = client.GetHandler<SteamUser>();
this.gameCoordinator = client.GetHandler<SteamGameCoordinator>();
// get the steam unified messages handler, which is used for sending and receiving responses from the unified service api
steamUnifiedMessages = client.GetHandler<SteamUnifiedMessages>();
// we also want to create our local service interface, which will help us build requests to the unified api
playerService = steamUnifiedMessages.CreateService<IPlayer>();
// setup callbacks
callbackMgr = new CallbackManager( client );
callbackMgr.Subscribe<SteamClient.ConnectedCallback>( OnConnected );
callbackMgr.Subscribe<SteamClient.DisconnectedCallback>( OnDisconnected );
callbackMgr.Subscribe<SteamUser.LoggedOnCallback>( OnLoggedOn );
callbackMgr.Subscribe<SteamUser.LoggedOffCallback>( OnLoggedOff );
callbackMgr.Subscribe<SteamGameCoordinator.MessageCallback>( OnGCMessage );
callbackMgr.Subscribe<SteamUser.UpdateMachineAuthCallback>( OnMachineAuth );
callbackMgr.Subscribe<SteamUser.AccountInfoCallback>( OnAccountInfo );
callbackMgr.Subscribe<SteamFriends.FriendsListCallback>( OnFriendsList );
callbackMgr.Subscribe<SteamFriends.PersonaStateCallback>( OnPersonaState );
callbackMgr.Subscribe<SteamFriends.FriendAddedCallback>( OnFriendAdded );
}
public void Connect()
{
Logger.Send("Connecting to Steam...", 1);
// begin the connection to steam
client.Connect();
isRunning = true;
DotaGCHandler.Bootstrap(client);
DotaGCHandler dota = client.GetHandler<DotaGCHandler>();
// Logger.Send($"Dota: {dota.GetType()}", 1);
// Logger.Send($"Dota is ready: {dota.Ready}", 1);
}
public void Wait()
{
while ( isRunning )
{
// continue running callbacks until we get match details
callbackMgr.RunWaitCallbacks( TimeSpan.FromSeconds( 1 ) );
Logger.Send("DotaClient Looking callbacks...", 1);
}
}
static void OnConnected( SteamClient.ConnectedCallback callback )
{
Logger.Send($"Connected to Steam! Logging in {userName}", 1);
if ( File.Exists( "sentry.bin" ) )
{
// if we have a saved sentry file, read and sha-1 hash it
byte[] sentryFile = File.ReadAllBytes( "sentry.bin" );
sentryHash = CryptoHelper.SHAHash( sentryFile );
}
user.LogOn( new SteamUser.LogOnDetails
{
Username = userName,
Password = password,
// in this sample, we pass in an additional authcode
// this value will be null (which is the default) for our first logon attempt
AuthCode = authCode,
// if the account is using 2-factor auth, we'll provide the two factor code instead
// this will also be null on our first logon attempt
TwoFactorCode = twoFactorAuth,
// our subsequent logons use the hash of the sentry file as proof of ownership of the file
// this will also be null for our first (no authcode) and second (authcode only) logon attempts
SentryFileHash = sentryHash,
} );
}
static void OnDisconnected( SteamClient.DisconnectedCallback callback )
{
// after recieving an AccountLogonDenied, we'll be disconnected from steam
// so after we read an authcode from the user, we need to reconnect to begin the logon flow again
Logger.Send($"Disconnected from Steam, reconnecting in 5...", 1);
Thread.Sleep( TimeSpan.FromSeconds( 5 ) );
client.Connect();
}
void OnLoggedOn( SteamUser.LoggedOnCallback callback )
{
bool isSteamGuard = callback.Result == EResult.AccountLogonDenied;
bool is2FA = callback.Result == EResult.AccountLoginDeniedNeedTwoFactor;
if ( isSteamGuard || is2FA )
{
Logger.Send($"This account is SteamGuard protected!", 1);
if ( is2FA )
{
Logger.Send("Please enter your 2 factor auth code from your authenticator app: ", 3);
twoFactorAuth = Console.ReadLine();
}
else
{
Logger.Send($"Please enter the auth code sent to the email at {callback.EmailDomain}: ", 3);
authCode = Console.ReadLine();
}
return;
}
if ( callback.Result != EResult.OK )
{
Logger.Send($"Unable to logon to Steam: {callback.Result} / {callback.ExtendedResult}", 1);
isRunning = false;
return;
}
Logger.Send("Successfully logged on! Launching DOTA...", 1);
// var playGame = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayedWithDataBlob);
// playGame.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed
// {
// game_id = (ulong) GameId,
// game_extra_info = "Dota 2",
// game_data_blob = null,
// streaming_provider_id = 0,
// game_flags = (uint) Engine,
// owner_id = Client.SteamID.AccountID
// });
// playGame.Body.client_os_type = 16;
//
// Client.Send(playGame);
// we've logged into the account
// now we need to inform the steam server that we're playing dota (in order to receive GC messages)
// steamkit doesn't expose the "play game" message through any handler, so we'll just send the message manually
// var playGame = new ClientMsgProtobuf<CMsgClientGamesPlayed>( EMsg.ClientGamesPlayed );
//
// playGame.Body.games_played.Add( new CMsgClientGamesPlayed.GamePlayed
// {
// game_id = new GameID( APPID ), // or game_id = APPID,
// } );
//
// // send it off
// // notice here we're sending this message directly using the SteamClient
// client.Send( playGame );
// delay a little to give steam some time to establish a GC connection to us
Thread.Sleep( 5000 );
// inform the dota GC that we want a session
var clientHello = new ClientGCMsgProtobuf<CMsgClientHello>( ( uint )EGCBaseClientMsg.k_EMsgGCClientHello );
clientHello.Body.engine = ESourceEngine.k_ESE_Source2;
gameCoordinator.Send( clientHello, APPID );
}
// called when a gamecoordinator (GC) message arrives
// these kinds of messages are designed to be game-specific
// in this case, we'll be handling dota's GC messages
void OnGCMessage( SteamGameCoordinator.MessageCallback callback )
{
// setup our dispatch table for messages
// this makes the code cleaner and easier to maintain
var messageMap = new Dictionary<uint, Action<IPacketGCMsg>>
{
{ ( uint )EGCBaseClientMsg.k_EMsgGCClientWelcome, OnClientWelcome },
{ ( uint )EDOTAGCMsg.k_EMsgGCMatchDetailsResponse, OnMatchDetails },
};
Action<IPacketGCMsg> func;
if ( !messageMap.TryGetValue( callback.EMsg, out func ) )
{
// this will happen when we recieve some GC messages that we're not handling
// this is okay because we're handling every essential message, and the rest can be ignored
return;
}
func( callback.Message );
}
// this message arrives when the GC welcomes a client
// this happens after telling steam that we launched dota (with the ClientGamesPlayed message)
// this can also happen after the GC has restarted (due to a crash or new version)
void OnClientWelcome( IPacketGCMsg packetMsg )
{
// in order to get at the contents of the message, we need to create a ClientGCMsgProtobuf from the packet message we recieve
// note here the difference between ClientGCMsgProtobuf and the ClientMsgProtobuf used when sending ClientGamesPlayed
// this message is used for the GC, while the other is used for general steam messages
var msg = new ClientGCMsgProtobuf<CMsgClientWelcome>( packetMsg );
Console.WriteLine( "GC is welcoming us. Version: {0}", msg.Body.version );
Console.WriteLine( "Requesting details of match {0}", matchId );
// at this point, the GC is now ready to accept messages from us
// so now we'll request the details of the match we're looking for
// var requestMatch = new ClientGCMsgProtobuf<CMsgGCMatchDetailsRequest>( ( uint )EDOTAGCMsg.k_EMsgGCMatchDetailsRequest );
// requestMatch.Body.match_id = matchId;
//
// gameCoordinator.Send( requestMatch, APPID );
}
// this message arrives after we've requested the details for a match
void OnMatchDetails( IPacketGCMsg packetMsg )
{
var msg = new ClientGCMsgProtobuf<CMsgGCMatchDetailsResponse>( packetMsg );
EResult result = ( EResult )msg.Body.result;
if ( result != EResult.OK )
{
Console.WriteLine( "Unable to request match details: {0}", result );
}
gotMatch = true;
Match = msg.Body.match;
// we've got everything we need, we can disconnect from steam now
client.Disconnect();
}
static void OnMethodResponse( SteamUnifiedMessages.ServiceMethodResponse callback )
{
if ( callback.JobID != badgeRequest )
{
// always double check the jobid of the response to ensure you're matching to your original request
return;
}
// and check for success
if ( callback.Result != EResult.OK )
{
Console.WriteLine( $"Unified service request failed with {callback.Result}" );
return;
}
// retrieve the deserialized response for the request we made
// notice the naming pattern
// for requests: CMyService_Method_Request
// for responses: CMyService_Method_Response
CPlayer_GetGameBadgeLevels_Response resp = callback.GetDeserializedResponse<CPlayer_GetGameBadgeLevels_Response>();
Console.WriteLine( $"Our player level is {resp.player_level}" );
foreach ( var badge in resp.badges )
{
Console.WriteLine( $"Badge series {badge.series} is level {badge.level}" );
}
badgeRequest = JobID.Invalid;
// now that we've completed our task, lets log off
user.LogOff();
}
static void OnLoggedOff( SteamUser.LoggedOffCallback callback )
{
Logger.Send($"Logged off of Steam: {callback.Result}", 1);
}
static void OnMachineAuth( SteamUser.UpdateMachineAuthCallback callback )
{
Logger.Send("Updating sentryfile...", 1);
// write out our sentry file
// ideally we'd want to write to the filename specified in the callback
// but then this sample would require more code to find the correct sentry file to read during logon
// for the sake of simplicity, we'll just use "sentry.bin"
int fileSize;
using ( var fs = File.Open( "sentry.bin", FileMode.OpenOrCreate, FileAccess.ReadWrite ) )
{
fs.Seek( callback.Offset, SeekOrigin.Begin );
fs.Write( callback.Data, 0, callback.BytesToWrite );
fileSize = ( int )fs.Length;
fs.Seek( 0, SeekOrigin.Begin );
using ( var sha = SHA1.Create() )
{
sentryHash = sha.ComputeHash( fs );
}
}
// inform the steam servers that we're accepting this sentry file
user.SendMachineAuthResponse( new SteamUser.MachineAuthDetails
{
JobID = callback.JobID,
FileName = callback.FileName,
BytesWritten = callback.BytesToWrite,
FileSize = fileSize,
Offset = callback.Offset,
Result = EResult.OK,
LastError = 0,
OneTimePassword = callback.OneTimePassword,
SentryFileHash = sentryHash,
} );
Logger.Send("Done!", 1);
}
static void OnAccountInfo( SteamUser.AccountInfoCallback callback )
{
// before being able to interact with friends, you must wait for the account info callback
// this callback is posted shortly after a successful logon
// at this point, we can go online on friends, so lets do that
steamFriends.SetPersonaState( EPersonaState.Online );
}
static void OnFriendsList( SteamFriends.FriendsListCallback callback )
{
// at this point, the client has received it's friends list
int friendCount = steamFriends.GetFriendCount();
Logger.Send($"We have {friendCount} friends", 1);
for ( int x = 0 ; x < friendCount ; x++ )
{
// steamids identify objects that exist on the steam network, such as friends, as an example
SteamID steamIdFriend = steamFriends.GetFriendByIndex( x );
// we'll just display the STEAM_ rendered version
Logger.Send($"Friend: {steamIdFriend.Render()}", 1);
}
// we can also iterate over our friendslist to accept or decline any pending invites
foreach ( var friend in callback.FriendList )
{
if (friend.Relationship == EFriendRelationship.RequestRecipient)
{
// this user has added us, let's add him back
steamFriends.AddFriend(friend.SteamID);
}
}
}
static void OnFriendAdded( SteamFriends.FriendAddedCallback callback)
{
// someone accepted our friend request, or we accepted one
Logger.Send($"{callback.PersonaName} is now a friend", 1);
}
static void OnPersonaState( SteamFriends.PersonaStateCallback callback )
{
// this callback is received when the persona state (friend information) of a friend changes
// for this sample we'll simply display the names of the friends
Logger.Send($"State change: {callback.Name}", 1);
}
// this is a utility function to transform a uint emsg into a string that can be used to display the name
static string GetEMsgDisplayString( uint eMsg )
{
Type[] eMsgEnums =
{
typeof( EGCBaseClientMsg ),
typeof( EDOTAGCMsg ),
typeof( EGCBaseMsg ),
typeof( EGCItemMsg ),
typeof( ESOMsg ),
};
foreach ( var enumType in eMsgEnums )
{
if ( Enum.IsDefined( enumType, ( int )eMsg ) )
return Enum.GetName( enumType, ( int )eMsg );
}
return eMsg.ToString();
}
}
}