paralin / Dota2

[Deprecated] A plugin for SteamKit that interfaces with the DOTA 2 game coordinator and game servers.
https://github.com/paralin/go-dota2
115 stars 32 forks source link

NullReferenceException #35

Open codefather-labs opened 4 years ago

codefather-labs commented 4 years ago

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();
    }
}

}

codefather-labs commented 4 years ago

my connection with steam is correct, IsConnected = true, but dota object does not work. it looks like steamclient is not inited, but it was!

paralin commented 4 years ago

Haven't used or updated this repo in a while but if someone PR's a fix I will merge.

1sp34n commented 3 years ago

@paralin are you going to update it? :v