package steam
import (
"crypto/sha1"
"sync/atomic"
"time"
. "github.com/Philipp15b/go-steam/protocol"
. "github.com/Philipp15b/go-steam/protocol/protobuf"
. "github.com/Philipp15b/go-steam/protocol/steamlang"
. "github.com/Philipp15b/go-steam/steamid"
"github.com/golang/protobuf/proto"
)
type Auth struct {
client *Client
details *LogOnDetails
}
type SentryHash []byte
type LogOnDetails struct {
Username string
// If logging into an account without a login key, the account's password.
Password string
// If you have a Steam Guard email code, you can provide it here.
AuthCode string
// If you have a Steam Guard mobile two-factor authentication code, you can provide it here.
TwoFactorCode string
SentryFileHash SentryHash
LoginKey string
// true if you want to get a login key which can be used in lieu of
// a password for subsequent logins. false or omitted otherwise.
ShouldRememberPassword bool
}
// Log on with the given details. You must always specify username and
// password OR username and loginkey. For the first login, don't set an authcode or a hash and you'll
// receive an error (EResult_AccountLogonDenied)
// and Steam will send you an authcode. Then you have to login again, this time with the authcode.
// Shortly after logging in, you'll receive a MachineAuthUpdateEvent with a hash which allows
// you to login without using an authcode in the future.
//
// If you don't use Steam Guard, username and password are enough.
//
// After the event EMsg_ClientNewLoginKey is received you can use the LoginKey
// to login instead of using the password.
func (a *Auth) LogOn(details *LogOnDetails) {
if details.Username == "" {
panic("Username must be set!")
}
if details.Password == "" && details.LoginKey == "" {
panic("Password or LoginKey must be set!")
}
logon := new(CMsgClientLogon)
logon.AccountName = &details.Username
logon.Password = &details.Password
if details.AuthCode != "" {
logon.AuthCode = proto.String(details.AuthCode)
}
if details.TwoFactorCode != "" {
logon.TwoFactorCode = proto.String(details.TwoFactorCode)
}
logon.ClientLanguage = proto.String("english")
logon.ProtocolVersion = proto.Uint32(MsgClientLogon_CurrentProtocol)
logon.ShaSentryfile = details.SentryFileHash
if details.LoginKey != "" {
logon.LoginKey = proto.String(details.LoginKey)
}
if details.ShouldRememberPassword {
logon.ShouldRememberPassword = proto.Bool(details.ShouldRememberPassword)
}
atomic.StoreUint64(&a.client.steamId, uint64(NewIdAdv(0, 1, int32(EUniverse_Public), int32(EAccountType_Individual))))
a.client.Write(NewClientMsgProtobuf(EMsg_ClientLogon, logon))
}
func (a *Auth) HandlePacket(packet *Packet) {
switch packet.EMsg {
case EMsg_ClientLogOnResponse:
a.handleLogOnResponse(packet)
case EMsg_ClientNewLoginKey:
a.handleLoginKey(packet)
case EMsg_ClientSessionToken:
case EMsg_ClientLoggedOff:
a.handleLoggedOff(packet)
case EMsg_ClientUpdateMachineAuth:
a.handleUpdateMachineAuth(packet)
case EMsg_ClientAccountInfo:
a.handleAccountInfo(packet)
}
}
func (a *Auth) handleLogOnResponse(packet *Packet) {
if !packet.IsProto {
a.client.Fatalf("Got non-proto logon response!")
return
}
body := new(CMsgClientLogonResponse)
msg := packet.ReadProtoMsg(body)
result := EResult(body.GetEresult())
if result == EResult_OK {
atomic.StoreInt32(&a.client.sessionId, msg.Header.Proto.GetClientSessionid())
atomic.StoreUint64(&a.client.steamId, msg.Header.Proto.GetSteamid())
a.client.Web.webLoginKey = *body.WebapiAuthenticateUserNonce
go a.client.heartbeatLoop(time.Duration(body.GetOutOfGameHeartbeatSeconds()))
a.client.Emit(&LoggedOnEvent{
Result: EResult(body.GetEresult()),
ExtendedResult: EResult(body.GetEresultExtended()),
OutOfGameSecsPerHeartbeat: body.GetOutOfGameHeartbeatSeconds(),
InGameSecsPerHeartbeat: body.GetInGameHeartbeatSeconds(),
PublicIp: body.GetDeprecatedPublicIp(),
ServerTime: body.GetRtime32ServerTime(),
AccountFlags: EAccountFlags(body.GetAccountFlags()),
ClientSteamId: SteamId(body.GetClientSuppliedSteamid()),
EmailDomain: body.GetEmailDomain(),
CellId: body.GetCellId(),
CellIdPingThreshold: body.GetCellIdPingThreshold(),
Steam2Ticket: body.GetSteam2Ticket(),
UsePics: body.GetDeprecatedUsePics(),
WebApiUserNonce: body.GetWebapiAuthenticateUserNonce(),
IpCountryCode: body.GetIpCountryCode(),
VanityUrl: body.GetVanityUrl(),
NumLoginFailuresToMigrate: body.GetCountLoginfailuresToMigrate(),
NumDisconnectsToMigrate: body.GetCountDisconnectsToMigrate(),
})
} else if result == EResult_Fail || result == EResult_ServiceUnavailable || result == EResult_TryAnotherCM {
// some error on Steam's side, we'll get an EOF later
} else {
a.client.Emit(&LogOnFailedEvent{
Result: EResult(body.GetEresult()),
})
a.client.Disconnect()
}
}
func (a *Auth) handleLoginKey(packet *Packet) {
body := new(CMsgClientNewLoginKey)
packet.ReadProtoMsg(body)
a.client.Write(NewClientMsgProtobuf(EMsg_ClientNewLoginKeyAccepted, &CMsgClientNewLoginKeyAccepted{
UniqueId: proto.Uint32(body.GetUniqueId()),
}))
a.client.Emit(&LoginKeyEvent{
UniqueId: body.GetUniqueId(),
LoginKey: body.GetLoginKey(),
})
}
func (a *Auth) handleLoggedOff(packet *Packet) {
result := EResult_Invalid
if packet.IsProto {
body := new(CMsgClientLoggedOff)
packet.ReadProtoMsg(body)
result = EResult(body.GetEresult())
} else {
body := new(MsgClientLoggedOff)
packet.ReadClientMsg(body)
result = body.Result
}
a.client.Emit(&LoggedOffEvent{Result: result})
}
func (a *Auth) handleUpdateMachineAuth(packet *Packet) {
body := new(CMsgClientUpdateMachineAuth)
packet.ReadProtoMsg(body)
hash := sha1.New()
hash.Write(packet.Data)
sha := hash.Sum(nil)
msg := NewClientMsgProtobuf(EMsg_ClientUpdateMachineAuthResponse, &CMsgClientUpdateMachineAuthResponse{
ShaFile: sha,
})
msg.SetTargetJobId(packet.SourceJobId)
a.client.Write(msg)
a.client.Emit(&MachineAuthUpdateEvent{sha})
}
func (a *Auth) handleAccountInfo(packet *Packet) {
body := new(CMsgClientAccountInfo)
packet.ReadProtoMsg(body)
a.client.Emit(&AccountInfoEvent{
PersonaName: body.GetPersonaName(),
Country: body.GetIpCountry(),
CountAuthedComputers: body.GetCountAuthedComputers(),
AccountFlags: EAccountFlags(body.GetAccountFlags()),
FacebookId: body.GetFacebookId(),
FacebookName: body.GetFacebookName(),
})
}