├── generator ├── GoSteamLanguageGenerator │ ├── .gitignore │ ├── GoSteamLanguageGenerator.csproj │ ├── GoSteamLanguageGenerator.sln │ └── Program.cs ├── README.md └── generator.go ├── web_events.go ├── go.mod ├── netutil ├── url.go ├── http.go └── addr.go ├── .gitmodules ├── .gitignore ├── protocol ├── steamlang │ └── steamlang.go ├── doc.go ├── internal.go ├── gamecoordinator │ ├── packet.go │ └── msg.go ├── packet.go ├── msg.go └── protobuf │ └── app_ticket.pb.go ├── notifications_events.go ├── jsont └── jsont.go ├── tradeoffer ├── error.go ├── receipt.go ├── escrow.go └── tradeoffer.go ├── client_events.go ├── cryptoutil ├── pkcs7.go ├── cryptoutil_test.go ├── pkcs7_test.go ├── rsa.go ├── cryptoutil.go └── ecb.go ├── .github └── workflows │ └── go.yml ├── community └── community.go ├── go.sum ├── trading_events.go ├── economy └── inventory │ ├── own.go │ ├── partial.go │ ├── inventory_apps.go │ └── inventory.go ├── tf2 ├── protocol │ └── econ.go └── tf2.go ├── trade ├── types.go ├── doc.go ├── actions.go ├── tradeapi │ ├── status.go │ └── trade.go └── trade.go ├── auth_events.go ├── LICENSE.txt ├── doc.go ├── notifications.go ├── steam_directory.go ├── gsbot ├── gsbot │ └── gsbot.go └── gsbot.go ├── rwu └── rwu.go ├── gamecoordinator.go ├── totp └── totp.go ├── socialcache ├── chats.go ├── groups.go └── friends.go ├── connection.go ├── trading.go ├── keys.go ├── servers.go ├── README.md ├── steamid └── steamid.go ├── web.go ├── social_events.go ├── auth.go ├── csgo └── protocol │ └── protobuf │ ├── enginegc.pb.go │ └── uifontfile.pb.go ├── client.go └── dota └── protocol └── protobuf └── system.pb.go /generator/GoSteamLanguageGenerator/.gitignore: -------------------------------------------------------------------------------- 1 | *.userprefs 2 | bin 3 | obj 4 | *.cs~ 5 | .vs 6 | -------------------------------------------------------------------------------- /web_events.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | type WebLoggedOnEvent struct{} 4 | 5 | type WebLogOnErrorEvent error 6 | 7 | type WebSessionIdEvent struct{} 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Philipp15b/go-steam/v3 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 7 | google.golang.org/protobuf v1.27.1 8 | ) 9 | -------------------------------------------------------------------------------- /netutil/url.go: -------------------------------------------------------------------------------- 1 | package netutil 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | func ToUrlValues(m map[string]string) url.Values { 8 | r := make(url.Values) 9 | for k, v := range m { 10 | r.Add(k, v) 11 | } 12 | return r 13 | } 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "generator/SteamKit"] 2 | path = generator/SteamKit 3 | url = https://github.com/SteamRE/SteamKit.git 4 | [submodule "generator/Protobufs"] 5 | path = generator/Protobufs 6 | url = https://github.com/SteamDatabase/Protobufs.git 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | .idea 8 | .vscode 9 | _obj 10 | _test 11 | 12 | # Architecture specific extensions/prefixes 13 | *.[568vq] 14 | [568vq].out 15 | 16 | *.cgo1.go 17 | *.cgo2.c 18 | _cgo_defun.c 19 | _cgo_gotypes.go 20 | _cgo_export.* 21 | 22 | _testmain.go 23 | 24 | *.exe -------------------------------------------------------------------------------- /protocol/steamlang/steamlang.go: -------------------------------------------------------------------------------- 1 | /* 2 | Contains code generated from SteamKit's SteamLanguage data. 3 | */ 4 | package steamlang 5 | 6 | const ( 7 | ProtoMask uint32 = 0x80000000 8 | EMsgMask = ^ProtoMask 9 | ) 10 | 11 | func NewEMsg(e uint32) EMsg { 12 | return EMsg(e & EMsgMask) 13 | } 14 | 15 | func IsProto(e uint32) bool { 16 | return e&ProtoMask > 0 17 | } 18 | -------------------------------------------------------------------------------- /notifications_events.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | // This event is emitted for every CMsgClientUserNotifications message and likewise only used for 4 | // trade offers. Unlike the the above it is also emitted when the count of a type that was tracked 5 | // before by this Notifications instance reaches zero. 6 | type NotificationEvent struct { 7 | Type NotificationType 8 | Count uint 9 | } 10 | -------------------------------------------------------------------------------- /jsont/jsont.go: -------------------------------------------------------------------------------- 1 | // Includes helper types for working with JSON data 2 | package jsont 3 | 4 | import ( 5 | "encoding/json" 6 | ) 7 | 8 | // A boolean value that can be unmarshaled from a number in JSON. 9 | type UintBool bool 10 | 11 | func (u *UintBool) UnmarshalJSON(data []byte) error { 12 | var n uint 13 | err := json.Unmarshal(data, &n) 14 | if err != nil { 15 | return err 16 | } 17 | *u = n != 0 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /netutil/http.go: -------------------------------------------------------------------------------- 1 | package netutil 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "strings" 7 | ) 8 | 9 | // Version of http.Client.PostForm that returns a new request instead of executing it directly. 10 | func NewPostForm(url string, data url.Values) *http.Request { 11 | req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode())) 12 | if err != nil { 13 | panic(err) 14 | } 15 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 16 | return req 17 | } 18 | -------------------------------------------------------------------------------- /tradeoffer/error.go: -------------------------------------------------------------------------------- 1 | package tradeoffer 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // SteamError can be returned by Create, Accept, Decline and Cancel methods. 8 | // It means we got response from steam, but it was in unknown format 9 | // or request was declined. 10 | type SteamError struct { 11 | msg string 12 | } 13 | 14 | func (e *SteamError) Error() string { 15 | return e.msg 16 | } 17 | func newSteamErrorf(format string, a ...interface{}) *SteamError { 18 | return &SteamError{fmt.Sprintf(format, a...)} 19 | } 20 | -------------------------------------------------------------------------------- /generator/GoSteamLanguageGenerator/GoSteamLanguageGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | LatestMajor 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /client_events.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "github.com/Philipp15b/go-steam/v3/netutil" 5 | ) 6 | 7 | // When this event is emitted by the Client, the connection is automatically closed. 8 | // This may be caused by a network error, for example. 9 | type FatalErrorEvent error 10 | 11 | type ConnectedEvent struct{} 12 | 13 | type DisconnectedEvent struct{} 14 | 15 | // A list of connection manager addresses to connect to in the future. 16 | // You should always save them and then select one of these 17 | // instead of the builtin ones for the next connection. 18 | type ClientCMListEvent struct { 19 | Addresses []*netutil.PortAddr 20 | } 21 | -------------------------------------------------------------------------------- /cryptoutil/pkcs7.go: -------------------------------------------------------------------------------- 1 | package cryptoutil 2 | 3 | import ( 4 | "crypto/aes" 5 | ) 6 | 7 | // Returns a new byte array padded with PKCS7 and prepended 8 | // with empty space of the AES block size (16 bytes) for the IV. 9 | func padPKCS7WithIV(src []byte) []byte { 10 | missing := aes.BlockSize - (len(src) % aes.BlockSize) 11 | newSize := len(src) + aes.BlockSize + missing 12 | dest := make([]byte, newSize, newSize) 13 | copy(dest[aes.BlockSize:], src) 14 | 15 | padding := byte(missing) 16 | for i := newSize - missing; i < newSize; i++ { 17 | dest[i] = padding 18 | } 19 | return dest 20 | } 21 | 22 | func unpadPKCS7(src []byte) []byte { 23 | padLen := src[len(src)-1] 24 | return src[:len(src)-int(padLen)] 25 | } 26 | -------------------------------------------------------------------------------- /cryptoutil/cryptoutil_test.go: -------------------------------------------------------------------------------- 1 | package cryptoutil 2 | 3 | import ( 4 | "crypto/aes" 5 | "testing" 6 | ) 7 | 8 | func TestCrypt(t *testing.T) { 9 | src := []byte("Hello World!") 10 | key := []byte("hunter2 ") // key size of 16 bytes required 11 | ciph, err := aes.NewCipher(key) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | encrypted := SymmetricEncrypt(ciph, src) 16 | if len(encrypted)%aes.BlockSize != 0 { 17 | t.Fatalf("Encrypted text is not a multiple of the AES block size (got %v)", len(encrypted)) 18 | } 19 | decrypted := SymmetricDecrypt(ciph, encrypted) 20 | if len([]byte("Hello World!")) != len(decrypted) { 21 | t.Fatalf("src length (%v) does not match decrypted length (%v)!", len([]byte("Hello World!")), len(decrypted)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cryptoutil/pkcs7_test.go: -------------------------------------------------------------------------------- 1 | package cryptoutil 2 | 3 | import ( 4 | "crypto/aes" 5 | "testing" 6 | ) 7 | 8 | func TestPKCS7Pad(t *testing.T) { 9 | in := []byte("123456789012345678901234567890") 10 | out := padPKCS7WithIV(in) 11 | if len(out) != 32+aes.BlockSize { 12 | t.Fatalf("Invalid output size, expected 48 and got %v", len(out)) 13 | } 14 | if out[47] != 2 { 15 | t.Fatalf("Invalid last output byte, expected 2 and got %v", out[47]) 16 | } 17 | } 18 | 19 | func TestPKCS7Unpad(t *testing.T) { 20 | in := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 4, 4, 4, 4} 21 | out := unpadPKCS7(in) 22 | if len(out) != 12 { 23 | t.Fatalf("Invalid output size, expected 12 and got %v", len(out)) 24 | } 25 | if out[7] != 8 { 26 | t.Fatalf("Invalid last output byte, expected 8 and got %v", out[7]) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.13 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Build 34 | run: go build -v . 35 | 36 | - name: Test 37 | run: go test -v . 38 | -------------------------------------------------------------------------------- /cryptoutil/rsa.go: -------------------------------------------------------------------------------- 1 | package cryptoutil 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/sha1" 7 | "crypto/x509" 8 | "errors" 9 | ) 10 | 11 | // Parses a DER encoded RSA public key 12 | func ParseASN1RSAPublicKey(derBytes []byte) (*rsa.PublicKey, error) { 13 | key, err := x509.ParsePKIXPublicKey(derBytes) 14 | if err != nil { 15 | return nil, err 16 | } 17 | pubKey, ok := key.(*rsa.PublicKey) 18 | if !ok { 19 | return nil, errors.New("not an RSA public key") 20 | } 21 | return pubKey, nil 22 | } 23 | 24 | // Encrypts a message with the given public key using RSA-OAEP and the sha1 hash function. 25 | func RSAEncrypt(pub *rsa.PublicKey, msg []byte) []byte { 26 | b, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, pub, msg, nil) 27 | if err != nil { 28 | panic(err) 29 | } 30 | return b 31 | } 32 | -------------------------------------------------------------------------------- /generator/README.md: -------------------------------------------------------------------------------- 1 | # Generator for steamlang and protobuf 2 | 3 | We generate Go code from SteamKit protocol descriptors, namely `steamlang` files and protocol buffer files. 4 | 5 | ## Dependencies 6 | 1. Get SteamKit submodule: `git submodule update --init --recursive`. 7 | 2. Install [`protoc`](https://developers.google.com/protocol-buffers/docs/downloads), the protocol buffer compiler. 8 | 9 | ``` 10 | ✗ protoc --version 11 | libprotoc 3.17.1 12 | ``` 13 | 14 | 3. Install `protoc-gen-go`: `go get google.golang.org/protobuf/cmd/protoc-gen-go` 15 | 16 | ``` 17 | ✗ protoc-gen-go --version 18 | protoc-gen-go v1.27.1 19 | ``` 20 | 21 | 4. Install the .NET Core SDK (3.1 or later). 22 | 23 | ## Execute generator 24 | 25 | Execute `go run generator.go clean proto steamlang` to clean build files, then build protocol buffer files and then build steamlang files. 26 | -------------------------------------------------------------------------------- /community/community.go: -------------------------------------------------------------------------------- 1 | package community 2 | 3 | import ( 4 | "net/http" 5 | "net/http/cookiejar" 6 | "net/url" 7 | ) 8 | 9 | const cookiePath = "https://steamcommunity.com/" 10 | 11 | func SetCookies(client *http.Client, sessionId, steamLogin, steamLoginSecure string) { 12 | if client.Jar == nil { 13 | client.Jar, _ = cookiejar.New(new(cookiejar.Options)) 14 | } 15 | base, err := url.Parse(cookiePath) 16 | if err != nil { 17 | panic(err) 18 | } 19 | client.Jar.SetCookies(base, []*http.Cookie{ 20 | // It seems that, for some reason, Steam tries to URL-decode the cookie. 21 | &http.Cookie{ 22 | Name: "sessionid", 23 | Value: url.QueryEscape(sessionId), 24 | }, 25 | // steamLogin is already URL-encoded. 26 | &http.Cookie{ 27 | Name: "steamLogin", 28 | Value: steamLogin, 29 | }, 30 | &http.Cookie{ 31 | Name: "steamLoginSecure", 32 | Value: steamLoginSecure, 33 | }, 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 4 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 5 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 6 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 7 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 8 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 9 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 10 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 11 | -------------------------------------------------------------------------------- /trading_events.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 5 | "github.com/Philipp15b/go-steam/v3/steamid" 6 | ) 7 | 8 | type TradeProposedEvent struct { 9 | RequestId TradeRequestId 10 | Other steamid.SteamId `json:",string"` 11 | } 12 | 13 | type TradeResultEvent struct { 14 | RequestId TradeRequestId 15 | Response steamlang.EEconTradeResponse 16 | Other steamid.SteamId `json:",string"` 17 | // Number of days Steam Guard is required to have been active 18 | NumDaysSteamGuardRequired uint32 19 | // Number of days a new device cannot trade for. 20 | NumDaysNewDeviceCooldown uint32 21 | // Default number of days one cannot trade after a password reset. 22 | DefaultNumDaysPasswordResetProbation uint32 23 | // See above. 24 | NumDaysPasswordResetProbation uint32 25 | } 26 | 27 | type TradeSessionStartEvent struct { 28 | Other steamid.SteamId `json:",string"` 29 | } 30 | -------------------------------------------------------------------------------- /tradeoffer/receipt.go: -------------------------------------------------------------------------------- 1 | package tradeoffer 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | 8 | "github.com/Philipp15b/go-steam/v3/economy/inventory" 9 | ) 10 | 11 | type TradeReceiptItem struct { 12 | AssetId uint64 `json:"id,string"` 13 | AppId uint32 14 | ContextId uint64 15 | Owner uint64 `json:",string"` 16 | Pos uint32 17 | inventory.Description 18 | } 19 | 20 | func parseTradeReceipt(data []byte) ([]*TradeReceiptItem, error) { 21 | reg := regexp.MustCompile("oItem =\\s+(.+?});") 22 | itemMatches := reg.FindAllSubmatch(data, -1) 23 | if itemMatches == nil { 24 | return nil, fmt.Errorf("items not found\n") 25 | } 26 | items := make([]*TradeReceiptItem, 0, len(itemMatches)) 27 | for _, m := range itemMatches { 28 | item := new(TradeReceiptItem) 29 | err := json.Unmarshal(m[1], &item) 30 | if err != nil { 31 | return nil, err 32 | } 33 | items = append(items, item) 34 | } 35 | return items, nil 36 | } 37 | -------------------------------------------------------------------------------- /economy/inventory/own.go: -------------------------------------------------------------------------------- 1 | package inventory 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | ) 8 | 9 | func GetPartialOwnInventory(client *http.Client, contextId uint64, appId uint32, start *uint) (*PartialInventory, error) { 10 | // TODO: the "trading" parameter can be left off to return non-tradable items too 11 | url := fmt.Sprintf("http://steamcommunity.com/my/inventory/json/%d/%d?trading=1", appId, contextId) 12 | if start != nil { 13 | url += "&start=" + strconv.FormatUint(uint64(*start), 10) 14 | } 15 | req, err := http.NewRequest("GET", url, nil) 16 | if err != nil { 17 | panic(err) 18 | } 19 | return DoInventoryRequest(client, req) 20 | } 21 | 22 | func GetOwnInventory(client *http.Client, contextId uint64, appId uint32) (*Inventory, error) { 23 | return GetFullInventory(func() (*PartialInventory, error) { 24 | return GetPartialOwnInventory(client, contextId, appId, nil) 25 | }, func(start uint) (*PartialInventory, error) { 26 | return GetPartialOwnInventory(client, contextId, appId, &start) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /netutil/addr.go: -------------------------------------------------------------------------------- 1 | package netutil 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // An addr that is neither restricted to TCP nor UDP, but has an IP and a port. 10 | type PortAddr struct { 11 | IP net.IP 12 | Port uint16 13 | } 14 | 15 | // Parses an IP address with a port, for example "209.197.29.196:27017". 16 | // If the given string is not valid, this function returns nil. 17 | func ParsePortAddr(addr string) *PortAddr { 18 | parts := strings.Split(addr, ":") 19 | if len(parts) != 2 { 20 | return nil 21 | } 22 | ip := net.ParseIP(parts[0]) 23 | if ip == nil { 24 | return nil 25 | } 26 | port, err := strconv.ParseUint(parts[1], 10, 16) 27 | if err != nil { 28 | return nil 29 | } 30 | return &PortAddr{ip, uint16(port)} 31 | } 32 | 33 | func (p *PortAddr) ToTCPAddr() *net.TCPAddr { 34 | return &net.TCPAddr{p.IP, int(p.Port), ""} 35 | } 36 | 37 | func (p *PortAddr) ToUDPAddr() *net.UDPAddr { 38 | return &net.UDPAddr{p.IP, int(p.Port), ""} 39 | } 40 | 41 | func (p *PortAddr) String() string { 42 | return p.IP.String() + ":" + strconv.FormatUint(uint64(p.Port), 10) 43 | } 44 | -------------------------------------------------------------------------------- /tf2/protocol/econ.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | type MsgGCSetItemPosition struct { 9 | AssetId, Position uint64 10 | } 11 | 12 | func (m *MsgGCSetItemPosition) Serialize(w io.Writer) error { 13 | return binary.Write(w, binary.LittleEndian, m) 14 | } 15 | 16 | type MsgGCCraft struct { 17 | Recipe int16 // -2 = wildcard 18 | numItems int16 19 | Items []uint64 20 | } 21 | 22 | func (m *MsgGCCraft) Serialize(w io.Writer) error { 23 | m.numItems = int16(len(m.Items)) 24 | return binary.Write(w, binary.LittleEndian, m) 25 | } 26 | 27 | type MsgGCDeleteItem struct { 28 | ItemId uint64 29 | } 30 | 31 | func (m *MsgGCDeleteItem) Serialize(w io.Writer) error { 32 | return binary.Write(w, binary.LittleEndian, m.ItemId) 33 | } 34 | 35 | type MsgGCNameItem struct { 36 | Tool, Target uint64 37 | Name string 38 | } 39 | 40 | func (m *MsgGCNameItem) Serialize(w io.Writer) error { 41 | err := binary.Write(w, binary.LittleEndian, m.Tool) 42 | if err != nil { 43 | return err 44 | } 45 | err = binary.Write(w, binary.LittleEndian, m.Target) 46 | if err != nil { 47 | return err 48 | } 49 | _, err = w.Write([]byte(m.Name)) 50 | return err 51 | } 52 | -------------------------------------------------------------------------------- /protocol/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package includes some basics for the Steam protocol. It defines basic interfaces that are used throughout go-steam: 3 | There is IMsg, which is extended by IClientMsg (sent after logging in) and abstracts over 4 | the outgoing message types. Both interfaces are implemented by ClientMsgProtobuf and ClientMsg. 5 | Msg is like ClientMsg, but it is used for sending messages before logging in. 6 | 7 | There is also the concept of a Packet: This is a type for incoming messages where only 8 | the header is deserialized. It therefore only contains EMsg data, job information and the remaining data. 9 | Its contents can then be read via the Read* methods which read data into a MessageBody - a type which is Serializable and 10 | has an EMsg. 11 | 12 | In addition, there are extra types for communication with the Game Coordinator (GC) included in the gamecoordinator sub-package. 13 | For outgoing messages the IGCMsg interface is used which is implemented by GCMsgProtobuf and GCMsg. 14 | Incoming messages are of the GCPacket type and are read like regular Packets. 15 | 16 | The actual messages and enums are in the sub-packages steamlang and protobuf, generated from the SteamKit data. 17 | */ 18 | package protocol 19 | -------------------------------------------------------------------------------- /protocol/internal.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "encoding/hex" 5 | "io" 6 | "math" 7 | "strconv" 8 | 9 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 10 | ) 11 | 12 | type JobId uint64 13 | 14 | func (j JobId) String() string { 15 | if j == math.MaxUint64 { 16 | return "(none)" 17 | } 18 | return strconv.FormatUint(uint64(j), 10) 19 | } 20 | 21 | type Serializer interface { 22 | Serialize(w io.Writer) error 23 | } 24 | 25 | type Deserializer interface { 26 | Deserialize(r io.Reader) error 27 | } 28 | 29 | type Serializable interface { 30 | Serializer 31 | Deserializer 32 | } 33 | 34 | type MessageBody interface { 35 | Serializable 36 | GetEMsg() steamlang.EMsg 37 | } 38 | 39 | // the default details to request in most situations 40 | const EClientPersonaStateFlag_DefaultInfoRequest = steamlang.EClientPersonaStateFlag_PlayerName | 41 | steamlang.EClientPersonaStateFlag_Presence | steamlang.EClientPersonaStateFlag_SourceID | 42 | steamlang.EClientPersonaStateFlag_GameExtraInfo 43 | 44 | const DefaultAvatar = "fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb" 45 | 46 | func ValidAvatar(avatar []byte) bool { 47 | str := hex.EncodeToString(avatar) 48 | return !(str == "0000000000000000000000000000000000000000" || len(str) != 40) 49 | } 50 | -------------------------------------------------------------------------------- /trade/types.go: -------------------------------------------------------------------------------- 1 | package trade 2 | 3 | import ( 4 | "github.com/Philipp15b/go-steam/v3/trade/tradeapi" 5 | ) 6 | 7 | type TradeEndedEvent struct { 8 | Reason TradeEndReason 9 | } 10 | 11 | type TradeEndReason uint 12 | 13 | const ( 14 | TradeEndReason_Complete TradeEndReason = 1 15 | TradeEndReason_Cancelled = 2 16 | TradeEndReason_Timeout = 3 17 | TradeEndReason_Failed = 4 18 | ) 19 | 20 | func newItem(event *tradeapi.Event) *Item { 21 | return &Item{ 22 | event.AppId, 23 | event.ContextId, 24 | event.AssetId, 25 | } 26 | } 27 | 28 | type Item struct { 29 | AppId uint32 30 | ContextId uint64 31 | AssetId uint64 32 | } 33 | 34 | type ItemAddedEvent struct { 35 | Item *Item 36 | } 37 | 38 | type ItemRemovedEvent struct { 39 | Item *Item 40 | } 41 | 42 | type ReadyEvent struct{} 43 | type UnreadyEvent struct{} 44 | 45 | func newCurrency(event *tradeapi.Event) *Currency { 46 | return &Currency{ 47 | event.AppId, 48 | event.ContextId, 49 | event.CurrencyId, 50 | } 51 | } 52 | 53 | type Currency struct { 54 | AppId uint32 55 | ContextId uint64 56 | CurrencyId uint64 57 | } 58 | 59 | type SetCurrencyEvent struct { 60 | Currency *Currency 61 | OldAmount uint64 62 | NewAmount uint64 63 | } 64 | 65 | type ChatEvent struct { 66 | Message string 67 | } 68 | -------------------------------------------------------------------------------- /cryptoutil/cryptoutil.go: -------------------------------------------------------------------------------- 1 | package cryptoutil 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | ) 8 | 9 | // Performs an encryption using AES/CBC/PKCS7 10 | // with a random IV prepended using AES/ECB/None. 11 | func SymmetricEncrypt(ciph cipher.Block, src []byte) []byte { 12 | // get a random IV and ECB encrypt it 13 | iv := make([]byte, aes.BlockSize, aes.BlockSize) 14 | _, err := rand.Read(iv) 15 | if err != nil { 16 | panic(err) 17 | } 18 | encryptedIv := make([]byte, aes.BlockSize, aes.BlockSize) 19 | newECBEncrypter(ciph).CryptBlocks(encryptedIv, iv) 20 | 21 | // pad it, copy the IV to the first 16 bytes and encrypt the rest with CBC 22 | encrypted := padPKCS7WithIV(src) 23 | copy(encrypted, encryptedIv) 24 | cipher.NewCBCEncrypter(ciph, iv).CryptBlocks(encrypted[aes.BlockSize:], encrypted[aes.BlockSize:]) 25 | return encrypted 26 | } 27 | 28 | // Decrypts data from the reader using AES/CBC/PKCS7 with an IV 29 | // prepended using AES/ECB/None. The src slice may not be used anymore. 30 | func SymmetricDecrypt(ciph cipher.Block, src []byte) []byte { 31 | iv := src[:aes.BlockSize] 32 | newECBDecrypter(ciph).CryptBlocks(iv, iv) 33 | 34 | data := src[aes.BlockSize:] 35 | cipher.NewCBCDecrypter(ciph, iv).CryptBlocks(data, data) 36 | 37 | return unpadPKCS7(data) 38 | } 39 | -------------------------------------------------------------------------------- /tradeoffer/escrow.go: -------------------------------------------------------------------------------- 1 | package tradeoffer 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | ) 9 | 10 | type EscrowDuration struct { 11 | DaysMyEscrow uint32 12 | DaysTheirEscrow uint32 13 | } 14 | 15 | func parseEscrowDuration(data []byte) (*EscrowDuration, error) { 16 | // TODO: why we are using case insensitive matching? 17 | myRegex := regexp.MustCompile("(?i)g_daysMyEscrow[\\s=]+(\\d+);") 18 | theirRegex := regexp.MustCompile("(?i)g_daysTheirEscrow[\\s=]+(\\d+);") 19 | 20 | myM := myRegex.FindSubmatch(data) 21 | theirM := theirRegex.FindSubmatch(data) 22 | 23 | if myM == nil || theirM == nil { 24 | // check if access token is valid 25 | notFriendsRegex := regexp.MustCompile(">You are not friends with this user<") 26 | notFriendsM := notFriendsRegex.FindSubmatch(data) 27 | if notFriendsM == nil { 28 | return nil, errors.New("regexp does not match") 29 | } else { 30 | return nil, errors.New("you are not friends with this user") 31 | } 32 | } 33 | 34 | myEscrow, err := strconv.ParseUint(string(myM[1]), 10, 32) 35 | if err != nil { 36 | return nil, fmt.Errorf("failed to parse my duration into uint: %v", err) 37 | } 38 | theirEscrow, err := strconv.ParseUint(string(theirM[1]), 10, 32) 39 | if err != nil { 40 | return nil, fmt.Errorf("failed to parse their duration into uint: %v", err) 41 | } 42 | 43 | return &EscrowDuration{ 44 | DaysMyEscrow: uint32(myEscrow), 45 | DaysTheirEscrow: uint32(theirEscrow), 46 | }, nil 47 | } 48 | -------------------------------------------------------------------------------- /auth_events.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 5 | "github.com/Philipp15b/go-steam/v3/steamid" 6 | ) 7 | 8 | type LoggedOnEvent struct { 9 | Result steamlang.EResult 10 | ExtendedResult steamlang.EResult 11 | OutOfGameSecsPerHeartbeat int32 12 | InGameSecsPerHeartbeat int32 13 | PublicIp uint32 14 | ServerTime uint32 15 | AccountFlags steamlang.EAccountFlags 16 | ClientSteamId steamid.SteamId `json:",string"` 17 | EmailDomain string 18 | CellId uint32 19 | CellIdPingThreshold uint32 20 | Steam2Ticket []byte 21 | UsePics bool 22 | WebApiUserNonce string 23 | IpCountryCode string 24 | VanityUrl string 25 | NumLoginFailuresToMigrate int32 26 | NumDisconnectsToMigrate int32 27 | } 28 | 29 | type LogOnFailedEvent struct { 30 | Result steamlang.EResult 31 | } 32 | 33 | type LoginKeyEvent struct { 34 | UniqueId uint32 35 | LoginKey string 36 | } 37 | 38 | type LoggedOffEvent struct { 39 | Result steamlang.EResult 40 | } 41 | 42 | type MachineAuthUpdateEvent struct { 43 | Hash []byte 44 | } 45 | 46 | type AccountInfoEvent struct { 47 | PersonaName string 48 | Country string 49 | CountAuthedComputers int32 50 | AccountFlags steamlang.EAccountFlags 51 | FacebookId uint64 `json:",string"` 52 | FacebookName string 53 | } 54 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 The go-steam Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * The names of its contributors may not be used to endorse or promote 14 | products derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /trade/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Allows automation of Steam Trading. 3 | 4 | Usage 5 | 6 | Like go-steam, this package is event-based. Call Poll() until the trade has ended, that is until the TradeEndedEvent is emitted. 7 | 8 | // After receiving the steam.TradeSessionStartEvent 9 | t := trade.New(sessionIdCookie, steamLoginCookie, steamLoginSecure, event.Other) 10 | for { 11 | eventList, err := t.Poll() 12 | if err != nil { 13 | // error handling here 14 | continue 15 | } 16 | for _, event := range eventList { 17 | switch e := event.(type) { 18 | case *trade.ChatEvent: 19 | // respond to any chat message 20 | t.Chat("Trading is awesome!") 21 | case *trade.TradeEndedEvent: 22 | return 23 | // other event handlers here 24 | } 25 | } 26 | } 27 | 28 | You can either log into steamcommunity.com and use the values of the `sessionId` and `steamLogin` cookies, 29 | or use go-steam and after logging in with client.Web.LogOn() and receiving the WebLoggedOnEvent use the `SessionId` 30 | and `SteamLogin` fields of steam.Web for the respective cookies. 31 | 32 | It is important that there is no delay between the Poll() calls greater than the timeout of the Steam client 33 | (currently five seconds before the trade partner sees a warning) or the trade will be closed automatically by Steam. 34 | 35 | Notes 36 | 37 | All method calls to Steam APIs are blocking. This packages' and its subpackages' types are not thread-safe and no calls to any method of the same 38 | trade instance may be done concurrently except when otherwise noted. 39 | */ 40 | package trade 41 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package allows you to automate actions on Valve's Steam network. It is a Go port of SteamKit. 3 | 4 | To login, you'll have to create a new Client first. Then connect to the Steam network 5 | and wait for a ConnectedCallback. Then you may call the Login method in the Auth module 6 | with your login information. This is covered in more detail in the method's documentation. After you've 7 | received the LoggedOnEvent, you should set your persona state to online to receive friend lists etc. 8 | 9 | Example code 10 | 11 | You can also find a running example in the `gsbot` package. 12 | 13 | package main 14 | 15 | import ( 16 | "io/ioutil" 17 | "log" 18 | 19 | "github.com/Philipp15b/go-steam/v3" 20 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 21 | ) 22 | 23 | func main() { 24 | myLoginInfo := new(steam.LogOnDetails) 25 | myLoginInfo.Username = "Your username" 26 | myLoginInfo.Password = "Your password" 27 | 28 | client := steam.NewClient() 29 | client.Connect() 30 | for event := range client.Events() { 31 | switch e := event.(type) { 32 | case *steam.ConnectedEvent: 33 | client.Auth.LogOn(myLoginInfo) 34 | case *steam.MachineAuthUpdateEvent: 35 | ioutil.WriteFile("sentry", e.Hash, 0666) 36 | case *steam.LoggedOnEvent: 37 | client.Social.SetPersonaState(steamlang.EPersonaState_Online) 38 | case steam.FatalErrorEvent: 39 | log.Print(e) 40 | case error: 41 | log.Print(e) 42 | } 43 | } 44 | } 45 | 46 | 47 | Events 48 | 49 | go-steam emits events that can be read via Client.Events(). Although the channel has the type interface{}, 50 | only types from this package ending with "Event" and errors will be emitted. 51 | 52 | */ 53 | package steam 54 | -------------------------------------------------------------------------------- /protocol/gamecoordinator/packet.go: -------------------------------------------------------------------------------- 1 | package gamecoordinator 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/Philipp15b/go-steam/v3/protocol" 7 | "github.com/Philipp15b/go-steam/v3/protocol/protobuf" 8 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 9 | "google.golang.org/protobuf/proto" 10 | ) 11 | 12 | // An incoming, partially unread message from the Game Coordinator. 13 | type GCPacket struct { 14 | AppId uint32 15 | MsgType uint32 16 | IsProto bool 17 | GCName string 18 | Body []byte 19 | TargetJobId protocol.JobId 20 | } 21 | 22 | func NewGCPacket(wrapper *protobuf.CMsgGCClient) (*GCPacket, error) { 23 | packet := &GCPacket{ 24 | AppId: wrapper.GetAppid(), 25 | MsgType: wrapper.GetMsgtype(), 26 | GCName: wrapper.GetGcname(), 27 | } 28 | 29 | r := bytes.NewReader(wrapper.GetPayload()) 30 | if steamlang.IsProto(wrapper.GetMsgtype()) { 31 | packet.MsgType = packet.MsgType & steamlang.EMsgMask 32 | packet.IsProto = true 33 | 34 | header := steamlang.NewMsgGCHdrProtoBuf() 35 | err := header.Deserialize(r) 36 | if err != nil { 37 | return nil, err 38 | } 39 | packet.TargetJobId = protocol.JobId(header.Proto.GetJobidTarget()) 40 | } else { 41 | header := steamlang.NewMsgGCHdr() 42 | err := header.Deserialize(r) 43 | if err != nil { 44 | return nil, err 45 | } 46 | packet.TargetJobId = protocol.JobId(header.TargetJobID) 47 | } 48 | 49 | body := make([]byte, r.Len()) 50 | r.Read(body) 51 | packet.Body = body 52 | 53 | return packet, nil 54 | } 55 | 56 | func (g *GCPacket) ReadProtoMsg(body proto.Message) { 57 | proto.Unmarshal(g.Body, body) 58 | } 59 | 60 | func (g *GCPacket) ReadMsg(body protocol.MessageBody) { 61 | body.Deserialize(bytes.NewReader(g.Body)) 62 | } 63 | -------------------------------------------------------------------------------- /notifications.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "github.com/Philipp15b/go-steam/v3/protocol" 5 | "github.com/Philipp15b/go-steam/v3/protocol/protobuf" 6 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 7 | ) 8 | 9 | type Notifications struct { 10 | // Maps notification types to their count. If a type is not present in the map, 11 | // its count is zero. 12 | notifications map[NotificationType]uint 13 | client *Client 14 | } 15 | 16 | func newNotifications(client *Client) *Notifications { 17 | return &Notifications{ 18 | make(map[NotificationType]uint), 19 | client, 20 | } 21 | } 22 | 23 | func (n *Notifications) HandlePacket(packet *protocol.Packet) { 24 | switch packet.EMsg { 25 | case steamlang.EMsg_ClientUserNotifications: 26 | n.handleClientUserNotifications(packet) 27 | } 28 | } 29 | 30 | type NotificationType uint 31 | 32 | const ( 33 | TradeOffer NotificationType = 1 34 | ) 35 | 36 | func (n *Notifications) handleClientUserNotifications(packet *protocol.Packet) { 37 | msg := new(protobuf.CMsgClientUserNotifications) 38 | packet.ReadProtoMsg(msg) 39 | 40 | for _, notification := range msg.GetNotifications() { 41 | typ := NotificationType(*notification.UserNotificationType) 42 | count := uint(*notification.Count) 43 | n.notifications[typ] = count 44 | n.client.Emit(&NotificationEvent{typ, count}) 45 | } 46 | 47 | // check if there is a notification in our map that isn't in the current packet 48 | for typ, _ := range n.notifications { 49 | exists := false 50 | for _, t := range msg.GetNotifications() { 51 | if NotificationType(*t.UserNotificationType) == typ { 52 | exists = true 53 | break 54 | } 55 | } 56 | 57 | if !exists { 58 | delete(n.notifications, typ) 59 | n.client.Emit(&NotificationEvent{typ, 0}) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /generator/GoSteamLanguageGenerator/GoSteamLanguageGenerator.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29209.62 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GoSteamLanguageGenerator", "GoSteamLanguageGenerator.csproj", "{A985D945-65B0-47B4-AA36-65E304FF2AF5}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamLanguageParser", "..\SteamKit\Resources\SteamLanguageParser\SteamLanguageParser.csproj", "{CCAB4961-4C59-4400-978B-7D9552AE2296}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {A985D945-65B0-47B4-AA36-65E304FF2AF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {A985D945-65B0-47B4-AA36-65E304FF2AF5}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {A985D945-65B0-47B4-AA36-65E304FF2AF5}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {A985D945-65B0-47B4-AA36-65E304FF2AF5}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {CCAB4961-4C59-4400-978B-7D9552AE2296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {CCAB4961-4C59-4400-978B-7D9552AE2296}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {CCAB4961-4C59-4400-978B-7D9552AE2296}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {CCAB4961-4C59-4400-978B-7D9552AE2296}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {F4DFD840-7263-49A4-AF7C-1C5E5CE84732} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /cryptoutil/ecb.go: -------------------------------------------------------------------------------- 1 | package cryptoutil 2 | 3 | import ( 4 | "crypto/cipher" 5 | ) 6 | 7 | // From this code review: https://codereview.appspot.com/7860047/ 8 | // by fasmat for the Go crypto/cipher package 9 | 10 | type ecb struct { 11 | b cipher.Block 12 | blockSize int 13 | } 14 | 15 | func newECB(b cipher.Block) *ecb { 16 | return &ecb{ 17 | b: b, 18 | blockSize: b.BlockSize(), 19 | } 20 | } 21 | 22 | type ecbEncrypter ecb 23 | 24 | // NewECBEncrypter returns a BlockMode which encrypts in electronic code book 25 | // mode, using the given Block. 26 | func newECBEncrypter(b cipher.Block) cipher.BlockMode { 27 | return (*ecbEncrypter)(newECB(b)) 28 | } 29 | 30 | func (x *ecbEncrypter) BlockSize() int { return x.blockSize } 31 | 32 | func (x *ecbEncrypter) CryptBlocks(dst, src []byte) { 33 | if len(src)%x.blockSize != 0 { 34 | panic("cryptoutil/ecb: input not full blocks") 35 | } 36 | if len(dst) < len(src) { 37 | panic("cryptoutil/ecb: output smaller than input") 38 | } 39 | for len(src) > 0 { 40 | x.b.Encrypt(dst, src[:x.blockSize]) 41 | src = src[x.blockSize:] 42 | dst = dst[x.blockSize:] 43 | } 44 | } 45 | 46 | type ecbDecrypter ecb 47 | 48 | // newECBDecrypter returns a BlockMode which decrypts in electronic code book 49 | // mode, using the given Block. 50 | func newECBDecrypter(b cipher.Block) cipher.BlockMode { 51 | return (*ecbDecrypter)(newECB(b)) 52 | } 53 | 54 | func (x *ecbDecrypter) BlockSize() int { return x.blockSize } 55 | 56 | func (x *ecbDecrypter) CryptBlocks(dst, src []byte) { 57 | if len(src)%x.blockSize != 0 { 58 | panic("cryptoutil/ecb: input not full blocks") 59 | } 60 | if len(dst) < len(src) { 61 | panic("cryptoutil/ecb: output smaller than input") 62 | } 63 | for len(src) > 0 { 64 | x.b.Decrypt(dst, src[:x.blockSize]) 65 | src = src[x.blockSize:] 66 | dst = dst[x.blockSize:] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /generator/GoSteamLanguageGenerator/Program.cs: -------------------------------------------------------------------------------- 1 | using SteamLanguageParser; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace GoSteamLanguageGenerator 9 | { 10 | class MainClass 11 | { 12 | public static void Main(string[] args) 13 | { 14 | if (args.Length < 2) 15 | { 16 | Console.WriteLine("Must have at least two parameters: SteamKit root path and output path!"); 17 | return; 18 | } 19 | 20 | string steamKitPath = Path.GetFullPath(args[0]); 21 | string languagePath = Path.Combine(steamKitPath, "Resources", "SteamLanguage"); 22 | string outputPath = Path.GetFullPath(args[1]); 23 | 24 | Environment.CurrentDirectory = languagePath; 25 | 26 | var codeGen = new GoGen(); 27 | 28 | Queue tokenList = LanguageParser.TokenizeString(File.ReadAllText("steammsg.steamd")); 29 | 30 | Node root = TokenAnalyzer.Analyze(tokenList); 31 | 32 | Node rootEnumNode = new Node(); 33 | Node rootMessageNode = new Node(); 34 | 35 | rootEnumNode.childNodes.AddRange(root.childNodes.Where(n => n is EnumNode)); 36 | rootMessageNode.childNodes.AddRange(root.childNodes.Where(n => n is ClassNode)); 37 | 38 | StringBuilder enumBuilder = new StringBuilder(); 39 | StringBuilder messageBuilder = new StringBuilder(); 40 | 41 | codeGen.EmitEnums(rootEnumNode, enumBuilder); 42 | codeGen.EmitClasses(rootMessageNode, messageBuilder); 43 | 44 | string outputEnumFile = Path.Combine(outputPath, "enums.go"); 45 | string outputMessageFile = Path.Combine(outputPath, "messages.go"); 46 | 47 | Directory.CreateDirectory(Path.GetDirectoryName(outputEnumFile)); 48 | 49 | File.WriteAllText(Path.Combine(steamKitPath, outputEnumFile), enumBuilder.ToString()); 50 | File.WriteAllText(Path.Combine(steamKitPath, outputMessageFile), messageBuilder.ToString()); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /steam_directory.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/rand" 7 | "net/http" 8 | "sync" 9 | "time" 10 | 11 | "github.com/Philipp15b/go-steam/v3/netutil" 12 | ) 13 | 14 | // Load initial server list from Steam Directory Web API. 15 | // Call InitializeSteamDirectory() before Connect() to use 16 | // steam directory server list instead of static one. 17 | func InitializeSteamDirectory() error { 18 | return steamDirectoryCache.Initialize() 19 | } 20 | 21 | var steamDirectoryCache *steamDirectory = &steamDirectory{} 22 | 23 | type steamDirectory struct { 24 | sync.RWMutex 25 | servers []string 26 | isInitialized bool 27 | } 28 | 29 | // Get server list from steam directory and save it for later 30 | func (sd *steamDirectory) Initialize() error { 31 | sd.Lock() 32 | defer sd.Unlock() 33 | client := new(http.Client) 34 | resp, err := client.Get(fmt.Sprintf("https://api.steampowered.com/ISteamDirectory/GetCMList/v1/?cellId=0")) 35 | if err != nil { 36 | return err 37 | } 38 | defer resp.Body.Close() 39 | r := struct { 40 | Response struct { 41 | ServerList []string 42 | Result uint32 43 | Message string 44 | } 45 | }{} 46 | if err = json.NewDecoder(resp.Body).Decode(&r); err != nil { 47 | return err 48 | } 49 | if r.Response.Result != 1 { 50 | return fmt.Errorf("Failed to get steam directory, result: %v, message: %v\n", r.Response.Result, r.Response.Message) 51 | } 52 | if len(r.Response.ServerList) == 0 { 53 | return fmt.Errorf("Steam returned zero servers for steam directory request\n") 54 | } 55 | sd.servers = r.Response.ServerList 56 | sd.isInitialized = true 57 | return nil 58 | } 59 | 60 | func (sd *steamDirectory) GetRandomCM() *netutil.PortAddr { 61 | sd.RLock() 62 | defer sd.RUnlock() 63 | if !sd.isInitialized { 64 | panic("steam directory is not initialized") 65 | } 66 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) 67 | addr := netutil.ParsePortAddr(sd.servers[rng.Int31n(int32(len(sd.servers)))]) 68 | return addr 69 | } 70 | 71 | func (sd *steamDirectory) IsInitialized() bool { 72 | sd.RLock() 73 | defer sd.RUnlock() 74 | isInitialized := sd.isInitialized 75 | return isInitialized 76 | } 77 | -------------------------------------------------------------------------------- /gsbot/gsbot/gsbot.go: -------------------------------------------------------------------------------- 1 | // A simple example that uses the modules from the gsbot package and go-steam to log on 2 | // to the Steam network. 3 | // 4 | // Use the right options for your account settings: 5 | // Normal login: username + password 6 | // 7 | // Email code: username + password 8 | // username + password + authcode 9 | // 10 | // Mobile code: username + password + twofactorcode 11 | // username + loginkey 12 | // 13 | // gsbot [username] [-p password] [-a authcode] [-t twofactorcode] [-l loginkey] 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | "github.com/Philipp15b/go-steam/v3" 22 | "github.com/Philipp15b/go-steam/v3/gsbot" 23 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 24 | ) 25 | 26 | const usage string = "usage: gsbot [username] [-p password] [-a authcode] [-t twofactorcode] [-l loginkey]" 27 | 28 | func main() { 29 | if len(os.Args) < 3 || len(os.Args)%2 != 0 { 30 | fmt.Println(usage) 31 | return 32 | } 33 | 34 | details := &steam.LogOnDetails{ 35 | Username: os.Args[1], 36 | ShouldRememberPassword: true, 37 | } 38 | 39 | for i := 2; i < len(os.Args)-1; i += 2 { 40 | switch os.Args[i] { 41 | case "-p": 42 | details.Password = os.Args[i+1] 43 | case "-a": 44 | details.AuthCode = os.Args[i+1] 45 | case "-t": 46 | details.TwoFactorCode = os.Args[i+1] 47 | case "-l": 48 | details.LoginKey = os.Args[i+1] 49 | default: 50 | fmt.Println(usage) 51 | return 52 | } 53 | } 54 | 55 | bot := gsbot.Default() 56 | client := bot.Client 57 | auth := gsbot.NewAuth(bot, details, "sentry.bin") 58 | debug, err := gsbot.NewDebug(bot, "debug") 59 | if err != nil { 60 | panic(err) 61 | } 62 | client.RegisterPacketHandler(debug) 63 | serverList := gsbot.NewServerList(bot, "serverlist.json") 64 | serverList.Connect() 65 | 66 | for event := range client.Events() { 67 | auth.HandleEvent(event) 68 | debug.HandleEvent(event) 69 | serverList.HandleEvent(event) 70 | 71 | switch e := event.(type) { 72 | case error: 73 | fmt.Printf("Error: %v", e) 74 | case *steam.LoggedOnEvent: 75 | client.Social.SetPersonaState(steamlang.EPersonaState_Online) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rwu/rwu.go: -------------------------------------------------------------------------------- 1 | // Utilities for reading and writing of binary data 2 | package rwu 3 | 4 | import ( 5 | "encoding/binary" 6 | "io" 7 | ) 8 | 9 | func ReadBool(r io.Reader) (bool, error) { 10 | var c uint8 11 | err := binary.Read(r, binary.LittleEndian, &c) 12 | return c != 0, err 13 | } 14 | 15 | func ReadUint8(r io.Reader) (uint8, error) { 16 | var c uint8 17 | err := binary.Read(r, binary.LittleEndian, &c) 18 | return c, err 19 | } 20 | 21 | func ReadUint16(r io.Reader) (uint16, error) { 22 | var c uint16 23 | err := binary.Read(r, binary.LittleEndian, &c) 24 | return c, err 25 | } 26 | 27 | func ReadUint32(r io.Reader) (uint32, error) { 28 | var c uint32 29 | err := binary.Read(r, binary.LittleEndian, &c) 30 | return c, err 31 | } 32 | 33 | func ReadUint64(r io.Reader) (uint64, error) { 34 | var c uint64 35 | err := binary.Read(r, binary.LittleEndian, &c) 36 | return c, err 37 | } 38 | 39 | func ReadInt8(r io.Reader) (int8, error) { 40 | var c int8 41 | err := binary.Read(r, binary.LittleEndian, &c) 42 | return c, err 43 | } 44 | 45 | func ReadInt16(r io.Reader) (int16, error) { 46 | var c int16 47 | err := binary.Read(r, binary.LittleEndian, &c) 48 | return c, err 49 | } 50 | 51 | func ReadInt32(r io.Reader) (int32, error) { 52 | var c int32 53 | err := binary.Read(r, binary.LittleEndian, &c) 54 | return c, err 55 | } 56 | 57 | func ReadInt64(r io.Reader) (int64, error) { 58 | var c int64 59 | err := binary.Read(r, binary.LittleEndian, &c) 60 | return c, err 61 | } 62 | 63 | func ReadString(r io.Reader) (string, error) { 64 | c := make([]byte, 0) 65 | var err error 66 | for { 67 | var b byte 68 | err = binary.Read(r, binary.LittleEndian, &b) 69 | if b == byte(0x0) || err != nil { 70 | break 71 | } 72 | c = append(c, b) 73 | } 74 | return string(c), err 75 | } 76 | 77 | func ReadByte(r io.Reader) (byte, error) { 78 | var c byte 79 | err := binary.Read(r, binary.LittleEndian, &c) 80 | return c, err 81 | } 82 | 83 | func ReadBytes(r io.Reader, num int32) ([]byte, error) { 84 | c := make([]byte, num) 85 | err := binary.Read(r, binary.LittleEndian, &c) 86 | return c, err 87 | } 88 | 89 | func WriteBool(w io.Writer, b bool) error { 90 | var err error 91 | if b { 92 | _, err = w.Write([]byte{1}) 93 | } else { 94 | _, err = w.Write([]byte{0}) 95 | } 96 | return err 97 | } 98 | -------------------------------------------------------------------------------- /gamecoordinator.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/Philipp15b/go-steam/v3/protocol" 7 | "github.com/Philipp15b/go-steam/v3/protocol/gamecoordinator" 8 | "github.com/Philipp15b/go-steam/v3/protocol/protobuf" 9 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 10 | "google.golang.org/protobuf/proto" 11 | ) 12 | 13 | type GameCoordinator struct { 14 | client *Client 15 | handlers []GCPacketHandler 16 | } 17 | 18 | func newGC(client *Client) *GameCoordinator { 19 | return &GameCoordinator{ 20 | client: client, 21 | handlers: make([]GCPacketHandler, 0), 22 | } 23 | } 24 | 25 | type GCPacketHandler interface { 26 | HandleGCPacket(*gamecoordinator.GCPacket) 27 | } 28 | 29 | func (g *GameCoordinator) RegisterPacketHandler(handler GCPacketHandler) { 30 | g.handlers = append(g.handlers, handler) 31 | } 32 | 33 | func (g *GameCoordinator) HandlePacket(packet *protocol.Packet) { 34 | if packet.EMsg != steamlang.EMsg_ClientFromGC { 35 | return 36 | } 37 | 38 | msg := new(protobuf.CMsgGCClient) 39 | packet.ReadProtoMsg(msg) 40 | 41 | p, err := gamecoordinator.NewGCPacket(msg) 42 | if err != nil { 43 | g.client.Errorf("Error reading GC message: %v", err) 44 | return 45 | } 46 | 47 | for _, handler := range g.handlers { 48 | handler.HandleGCPacket(p) 49 | } 50 | } 51 | 52 | func (g *GameCoordinator) Write(msg gamecoordinator.IGCMsg) { 53 | buf := new(bytes.Buffer) 54 | msg.Serialize(buf) 55 | 56 | msgType := msg.GetMsgType() 57 | if msg.IsProto() { 58 | msgType = msgType | 0x80000000 // mask with protoMask 59 | } 60 | 61 | g.client.Write(protocol.NewClientMsgProtobuf(steamlang.EMsg_ClientToGC, &protobuf.CMsgGCClient{ 62 | Msgtype: proto.Uint32(msgType), 63 | Appid: proto.Uint32(msg.GetAppId()), 64 | Payload: buf.Bytes(), 65 | })) 66 | } 67 | 68 | // Sets you in the given games. Specify none to quit all games. 69 | func (g *GameCoordinator) SetGamesPlayed(appIds ...uint64) { 70 | games := make([]*protobuf.CMsgClientGamesPlayed_GamePlayed, 0) 71 | for _, appId := range appIds { 72 | games = append(games, &protobuf.CMsgClientGamesPlayed_GamePlayed{ 73 | GameId: proto.Uint64(appId), 74 | }) 75 | } 76 | 77 | g.client.Write(protocol.NewClientMsgProtobuf(steamlang.EMsg_ClientGamesPlayed, &protobuf.CMsgClientGamesPlayed{ 78 | GamesPlayed: games, 79 | })) 80 | } 81 | -------------------------------------------------------------------------------- /tf2/tf2.go: -------------------------------------------------------------------------------- 1 | /* 2 | Provides access to TF2 Game Coordinator functionality. 3 | */ 4 | package tf2 5 | 6 | import ( 7 | "github.com/Philipp15b/go-steam/v3" 8 | "github.com/Philipp15b/go-steam/v3/protocol/gamecoordinator" 9 | "github.com/Philipp15b/go-steam/v3/tf2/protocol" 10 | "github.com/Philipp15b/go-steam/v3/tf2/protocol/protobuf" 11 | ) 12 | 13 | const AppId = 440 14 | 15 | // To use any methods of this, you'll need to SetPlaying(true) and wait for 16 | // the GCReadyEvent. 17 | type TF2 struct { 18 | client *steam.Client 19 | } 20 | 21 | // Creates a new TF2 instance and registers it as a packet handler 22 | func New(client *steam.Client) *TF2 { 23 | t := &TF2{client} 24 | client.GC.RegisterPacketHandler(t) 25 | return t 26 | } 27 | 28 | func (t *TF2) SetPlaying(playing bool) { 29 | if playing { 30 | t.client.GC.SetGamesPlayed(AppId) 31 | } else { 32 | t.client.GC.SetGamesPlayed() 33 | } 34 | } 35 | 36 | func (t *TF2) SetItemPosition(itemId, position uint64) { 37 | t.client.GC.Write(gamecoordinator.NewGCMsg(AppId, uint32(protobuf.EGCItemMsg_k_EMsgGCSetSingleItemPosition), &protocol.MsgGCSetItemPosition{ 38 | itemId, position, 39 | })) 40 | } 41 | 42 | // recipe -2 = wildcard 43 | func (t *TF2) CraftItems(items []uint64, recipe int16) { 44 | t.client.GC.Write(gamecoordinator.NewGCMsg(AppId, uint32(protobuf.EGCItemMsg_k_EMsgGCCraft), &protocol.MsgGCCraft{ 45 | Recipe: recipe, 46 | Items: items, 47 | })) 48 | } 49 | 50 | func (t *TF2) DeleteItem(itemId uint64) { 51 | t.client.GC.Write(gamecoordinator.NewGCMsg(AppId, uint32(protobuf.EGCItemMsg_k_EMsgGCDelete), &protocol.MsgGCDeleteItem{itemId})) 52 | } 53 | 54 | func (t *TF2) NameItem(toolId, target uint64, name string) { 55 | t.client.GC.Write(gamecoordinator.NewGCMsg(AppId, uint32(protobuf.EGCItemMsg_k_EMsgGCNameItem), &protocol.MsgGCNameItem{ 56 | toolId, target, name, 57 | })) 58 | } 59 | 60 | type GCReadyEvent struct{} 61 | 62 | func (t *TF2) HandleGCPacket(packet *gamecoordinator.GCPacket) { 63 | if packet.AppId != AppId { 64 | return 65 | } 66 | switch protobuf.EGCBaseClientMsg(packet.MsgType) { 67 | case protobuf.EGCBaseClientMsg_k_EMsgGCClientWelcome: 68 | t.handleWelcome(packet) 69 | } 70 | } 71 | 72 | func (t *TF2) handleWelcome(packet *gamecoordinator.GCPacket) { 73 | // the packet's body is pretty useless 74 | t.client.Emit(&GCReadyEvent{}) 75 | } 76 | -------------------------------------------------------------------------------- /economy/inventory/partial.go: -------------------------------------------------------------------------------- 1 | package inventory 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "net/http" 8 | ) 9 | 10 | // A partial inventory as sent by the Steam API. 11 | type PartialInventory struct { 12 | Success bool 13 | Error string 14 | Inventory 15 | More bool 16 | MoreStart MoreStart `json:"more_start"` 17 | } 18 | 19 | type MoreStart uint 20 | 21 | func (m *MoreStart) UnmarshalJSON(data []byte) error { 22 | if bytes.Equal(data, []byte("false")) { 23 | return nil 24 | } 25 | return json.Unmarshal(data, (*uint)(m)) 26 | } 27 | 28 | func DoInventoryRequest(client *http.Client, req *http.Request) (*PartialInventory, error) { 29 | resp, err := client.Do(req) 30 | if err != nil { 31 | return nil, err 32 | } 33 | defer resp.Body.Close() 34 | 35 | inv := new(PartialInventory) 36 | err = json.NewDecoder(resp.Body).Decode(inv) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return inv, nil 41 | } 42 | 43 | func GetFullInventory(getFirst func() (*PartialInventory, error), getNext func(start uint) (*PartialInventory, error)) (*Inventory, error) { 44 | first, err := getFirst() 45 | if err != nil { 46 | return nil, err 47 | } 48 | if !first.Success { 49 | return nil, errors.New("GetFullInventory API call failed: " + first.Error) 50 | } 51 | 52 | result := &first.Inventory 53 | var next *PartialInventory 54 | for latest := first; latest.More; latest = next { 55 | next, err := getNext(uint(latest.MoreStart)) 56 | if err != nil { 57 | return nil, err 58 | } 59 | if !next.Success { 60 | return nil, errors.New("GetFullInventory API call failed: " + next.Error) 61 | } 62 | 63 | result = Merge(result, &next.Inventory) 64 | } 65 | 66 | return result, nil 67 | } 68 | 69 | // Merges the given Inventory into a single Inventory. 70 | // The given slice must have at least one element. The first element of the slice is used 71 | // and modified. 72 | func Merge(p ...*Inventory) *Inventory { 73 | inv := p[0] 74 | for idx, i := range p { 75 | if idx == 0 { 76 | continue 77 | } 78 | 79 | for key, value := range i.Items { 80 | inv.Items[key] = value 81 | } 82 | for key, value := range i.Descriptions { 83 | inv.Descriptions[key] = value 84 | } 85 | for key, value := range i.Currencies { 86 | inv.Currencies[key] = value 87 | } 88 | } 89 | 90 | return inv 91 | } 92 | -------------------------------------------------------------------------------- /economy/inventory/inventory_apps.go: -------------------------------------------------------------------------------- 1 | package inventory 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "regexp" 9 | "strconv" 10 | 11 | "github.com/Philipp15b/go-steam/v3/steamid" 12 | ) 13 | 14 | type InventoryApps map[string]*InventoryApp 15 | 16 | func (i *InventoryApps) Get(appId uint32) (*InventoryApp, error) { 17 | iMap := (map[string]*InventoryApp)(*i) 18 | if inventoryApp, ok := iMap[strconv.FormatUint(uint64(appId), 10)]; ok { 19 | return inventoryApp, nil 20 | } 21 | return nil, fmt.Errorf("inventory app not found") 22 | } 23 | 24 | func (i *InventoryApps) ToMap() map[string]*InventoryApp { 25 | return (map[string]*InventoryApp)(*i) 26 | } 27 | 28 | type InventoryApp struct { 29 | AppId uint32 30 | Name string 31 | Icon string 32 | Link string 33 | AssetCount uint32 `json:"asset_count"` 34 | InventoryLogo string `json:"inventory_logo"` 35 | TradePermissions string `json:"trade_permissions"` 36 | Contexts Contexts `json:"rgContexts"` 37 | } 38 | 39 | type Contexts map[string]*Context 40 | 41 | func (c *Contexts) Get(contextId uint64) (*Context, error) { 42 | cMap := (map[string]*Context)(*c) 43 | if context, ok := cMap[strconv.FormatUint(contextId, 10)]; ok { 44 | return context, nil 45 | } 46 | return nil, fmt.Errorf("context not found") 47 | } 48 | 49 | func (c *Contexts) ToMap() map[string]*Context { 50 | return (map[string]*Context)(*c) 51 | } 52 | 53 | type Context struct { 54 | ContextId uint64 `json:"id,string"` 55 | AssetCount uint32 `json:"asset_count"` 56 | Name string 57 | } 58 | 59 | func GetInventoryApps(client *http.Client, steamId steamid.SteamId) (InventoryApps, error) { 60 | resp, err := http.Get("http://steamcommunity.com/profiles/" + steamId.ToString() + "/inventory/") 61 | if err != nil { 62 | return nil, err 63 | } 64 | defer resp.Body.Close() 65 | respBody, err := ioutil.ReadAll(resp.Body) 66 | if err != nil { 67 | return nil, err 68 | } 69 | reg := regexp.MustCompile("var g_rgAppContextData = (.*?);") 70 | inventoryAppsMatches := reg.FindSubmatch(respBody) 71 | if inventoryAppsMatches == nil { 72 | return nil, fmt.Errorf("profile inventory not found in steam response") 73 | } 74 | var inventoryApps InventoryApps 75 | if err = json.Unmarshal(inventoryAppsMatches[1], &inventoryApps); err != nil { 76 | return nil, err 77 | } 78 | 79 | return inventoryApps, nil 80 | } 81 | -------------------------------------------------------------------------------- /totp/totp.go: -------------------------------------------------------------------------------- 1 | package totp 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "encoding/base64" 7 | "encoding/binary" 8 | "errors" 9 | "time" 10 | ) 11 | 12 | // ErrInvalidSharedSecret is returned when shared secret isn't in base64 form 13 | var ErrInvalidSharedSecret error = errors.New("invalid base64 shared secret") 14 | 15 | const ( 16 | // Range of possible chars for auth code. 17 | chars string = "23456789BCDFGHJKMNPQRTVWXY" 18 | charsLen int = len(chars) 19 | ) 20 | 21 | // Totp is structure holding data needed for generating TOTP token. 22 | type Totp struct { 23 | sharedSecret string 24 | Time time.Time 25 | } 26 | 27 | // NewTotp creates new Totp structure with current time. 28 | func NewTotp(sharedSecret string) *Totp { 29 | return &Totp{sharedSecret, time.Now()} 30 | } 31 | 32 | // NewTimedTotp creates new Totp structure with custom time. 33 | func NewTimedTotp(sharedSecret string, time time.Time) *Totp { 34 | return &Totp{sharedSecret, time} 35 | } 36 | 37 | // GenerateCode generates Steam TOTP code which is always 5 symbols. 38 | func (totp *Totp) GenerateCode() (string, error) { 39 | return GenerateTotpCode(totp.sharedSecret, totp.Time) 40 | } 41 | 42 | // SharedSecret returns shared secret of Totp structure. 43 | func (totp *Totp) SharedSecret() string { 44 | return totp.sharedSecret 45 | } 46 | 47 | // Function below is originally made by https://github.com/fortis/ and used in https://github.com/fortis/go-steam-totp/. 48 | 49 | // GenerateTotpCode generates steam TOTP code which is always 5 symbols. 50 | func GenerateTotpCode(sharedSecret string, time time.Time) (string, error) { 51 | key, err := base64.StdEncoding.DecodeString(sharedSecret) 52 | if err != nil { 53 | return "", ErrInvalidSharedSecret 54 | } 55 | 56 | // Converting time for any reason 57 | // 00 00 00 00 00 00 00 00 58 | // 00 00 00 00 xx xx xx xx 59 | ut := uint64(time.Unix()) / 30 60 | tb := make([]byte, 8) 61 | binary.BigEndian.PutUint64(tb, ut) 62 | 63 | // Evaluate hash code for `tb` by key 64 | mac := hmac.New(sha1.New, key) 65 | mac.Write(tb) 66 | hashcode := mac.Sum(nil) 67 | 68 | // Last 4 bits provide initial position 69 | // len(hashcode) = 20 bytes 70 | start := hashcode[19] & 0xf 71 | 72 | // Extract 4 bytes at `start` and drop first bit 73 | fc32 := binary.BigEndian.Uint32(hashcode[start : start+4]) 74 | fc32 &= 1<<31 - 1 75 | fullcode := int(fc32) 76 | 77 | // Generate auth code 78 | code := make([]byte, 5) 79 | for i := range code { 80 | code[i] = chars[fullcode%charsLen] 81 | fullcode /= charsLen 82 | } 83 | 84 | return string(code[:]), nil 85 | } 86 | -------------------------------------------------------------------------------- /trade/actions.go: -------------------------------------------------------------------------------- 1 | package trade 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/Philipp15b/go-steam/v3/economy/inventory" 7 | "github.com/Philipp15b/go-steam/v3/trade/tradeapi" 8 | ) 9 | 10 | type Slot uint 11 | 12 | func (t *Trade) action(status *tradeapi.Status, err error) error { 13 | if err != nil { 14 | return err 15 | } 16 | t.onStatus(status) 17 | return nil 18 | } 19 | 20 | // Returns the next batch of events to process. These can be queued from calls to methods 21 | // like `AddItem` or, if there are no queued events, from a new HTTP request to Steam's API (blocking!). 22 | // If the latter is the case, this method may also sleep before the request 23 | // to conform to the polling interval of the official Steam client. 24 | func (t *Trade) Poll() ([]interface{}, error) { 25 | if t.queuedEvents != nil { 26 | return t.Events(), nil 27 | } 28 | 29 | if d := time.Since(t.lastPoll); d < pollTimeout { 30 | time.Sleep(pollTimeout - d) 31 | } 32 | t.lastPoll = time.Now() 33 | 34 | err := t.action(t.api.GetStatus()) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return t.Events(), nil 40 | } 41 | 42 | func (t *Trade) GetTheirInventory(contextId uint64, appId uint32) (*inventory.Inventory, error) { 43 | return inventory.GetFullInventory(func() (*inventory.PartialInventory, error) { 44 | return t.api.GetForeignInventory(contextId, appId, nil) 45 | }, func(start uint) (*inventory.PartialInventory, error) { 46 | return t.api.GetForeignInventory(contextId, appId, &start) 47 | }) 48 | } 49 | 50 | func (t *Trade) GetOwnInventory(contextId uint64, appId uint32) (*inventory.Inventory, error) { 51 | return t.api.GetOwnInventory(contextId, appId) 52 | } 53 | 54 | func (t *Trade) GetMain() (*tradeapi.Main, error) { 55 | return t.api.GetMain() 56 | } 57 | 58 | func (t *Trade) AddItem(slot Slot, item *Item) error { 59 | return t.action(t.api.AddItem(uint(slot), item.AssetId, item.ContextId, item.AppId)) 60 | } 61 | 62 | func (t *Trade) RemoveItem(slot Slot, item *Item) error { 63 | return t.action(t.api.RemoveItem(uint(slot), item.AssetId, item.ContextId, item.AppId)) 64 | } 65 | 66 | func (t *Trade) Chat(message string) error { 67 | return t.action(t.api.Chat(message)) 68 | } 69 | 70 | func (t *Trade) SetCurrency(amount uint, currency *Currency) error { 71 | return t.action(t.api.SetCurrency(amount, currency.CurrencyId, currency.ContextId, currency.AppId)) 72 | } 73 | 74 | func (t *Trade) SetReady(ready bool) error { 75 | return t.action(t.api.SetReady(ready)) 76 | } 77 | 78 | // This may only be called after a successful `SetReady(true)`. 79 | func (t *Trade) Confirm() error { 80 | return t.action(t.api.Confirm()) 81 | } 82 | 83 | func (t *Trade) Cancel() error { 84 | return t.action(t.api.Cancel()) 85 | } 86 | -------------------------------------------------------------------------------- /trade/tradeapi/status.go: -------------------------------------------------------------------------------- 1 | package tradeapi 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | 7 | "github.com/Philipp15b/go-steam/v3/jsont" 8 | "github.com/Philipp15b/go-steam/v3/steamid" 9 | ) 10 | 11 | type Status struct { 12 | Success bool 13 | Error string 14 | NewVersion bool `json:"newversion"` 15 | TradeStatus TradeStatus `json:"trade_status"` 16 | Version uint 17 | LogPos int 18 | Me User 19 | Them User 20 | Events EventList 21 | } 22 | 23 | type TradeStatus uint 24 | 25 | const ( 26 | TradeStatus_Open TradeStatus = 0 27 | TradeStatus_Complete = 1 28 | TradeStatus_Empty = 2 // when both parties trade no items 29 | TradeStatus_Cancelled = 3 30 | TradeStatus_Timeout = 4 // the partner timed out 31 | TradeStatus_Failed = 5 32 | ) 33 | 34 | type EventList map[uint]*Event 35 | 36 | // The EventList can either be an array or an object of id -> event 37 | func (e *EventList) UnmarshalJSON(data []byte) error { 38 | // initialize the map if it's nil 39 | if *e == nil { 40 | *e = make(EventList) 41 | } 42 | 43 | o := make(map[string]*Event) 44 | err := json.Unmarshal(data, &o) 45 | // it's an object 46 | if err == nil { 47 | for is, event := range o { 48 | i, err := strconv.ParseUint(is, 10, 32) 49 | if err != nil { 50 | panic(err) 51 | } 52 | (*e)[uint(i)] = event 53 | } 54 | return nil 55 | } 56 | 57 | // it's an array 58 | var a []*Event 59 | err = json.Unmarshal(data, &a) 60 | if err != nil { 61 | return err 62 | } 63 | for i, event := range a { 64 | (*e)[uint(i)] = event 65 | } 66 | return nil 67 | } 68 | 69 | type Event struct { 70 | SteamId steamid.SteamId `json:",string"` 71 | Action Action `json:",string"` 72 | Timestamp uint64 73 | 74 | AppId uint32 75 | ContextId uint64 `json:",string"` 76 | AssetId uint64 `json:",string"` 77 | 78 | Text string // only used for chat messages 79 | 80 | // The following is used for SetCurrency 81 | CurrencyId uint64 `json:",string"` 82 | OldAmount uint64 `json:"old_amount,string"` 83 | NewAmount uint64 `json:"amount,string"` 84 | } 85 | 86 | type Action uint 87 | 88 | const ( 89 | Action_AddItem Action = 0 90 | Action_RemoveItem = 1 91 | Action_Ready = 2 92 | Action_Unready = 3 93 | Action_Accept = 4 94 | Action_SetCurrency = 6 95 | Action_ChatMessage = 7 96 | ) 97 | 98 | type User struct { 99 | Ready jsont.UintBool 100 | Confirmed jsont.UintBool 101 | SecSinceTouch int `json:"sec_since_touch"` 102 | ConnectionPending bool `json:"connection_pending"` 103 | Assets interface{} 104 | Currency interface{} // either []*Currency or empty string 105 | } 106 | 107 | type Currency struct { 108 | AppId uint64 `json:",string"` 109 | ContextId uint64 `json:",string"` 110 | CurrencyId uint64 `json:",string"` 111 | Amount uint64 `json:",string"` 112 | } 113 | -------------------------------------------------------------------------------- /socialcache/chats.go: -------------------------------------------------------------------------------- 1 | package socialcache 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | 7 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 8 | "github.com/Philipp15b/go-steam/v3/steamid" 9 | ) 10 | 11 | // Chats list is a thread safe map 12 | // They can be iterated over like so: 13 | // for id, chat := range client.Social.Chats.GetCopy() { 14 | // log.Println(id, chat.Name) 15 | // } 16 | type ChatsList struct { 17 | mutex sync.RWMutex 18 | byId map[steamid.SteamId]*Chat 19 | } 20 | 21 | // Returns a new chats list 22 | func NewChatsList() *ChatsList { 23 | return &ChatsList{byId: make(map[steamid.SteamId]*Chat)} 24 | } 25 | 26 | // Adds a chat to the chat list 27 | func (list *ChatsList) Add(chat Chat) { 28 | list.mutex.Lock() 29 | defer list.mutex.Unlock() 30 | _, exists := list.byId[chat.SteamId] 31 | if !exists { // make sure this doesnt already exist 32 | list.byId[chat.SteamId] = &chat 33 | } 34 | } 35 | 36 | // Removes a chat from the chat list 37 | func (list *ChatsList) Remove(id steamid.SteamId) { 38 | list.mutex.Lock() 39 | defer list.mutex.Unlock() 40 | delete(list.byId, id) 41 | } 42 | 43 | // Adds a chat member to a given chat 44 | func (list *ChatsList) AddChatMember(id steamid.SteamId, member ChatMember) { 45 | list.mutex.Lock() 46 | defer list.mutex.Unlock() 47 | chat := list.byId[id] 48 | if chat == nil { // Chat doesn't exist 49 | chat = &Chat{SteamId: id} 50 | list.byId[id] = chat 51 | } 52 | if chat.ChatMembers == nil { // New chat 53 | chat.ChatMembers = make(map[steamid.SteamId]ChatMember) 54 | } 55 | chat.ChatMembers[member.SteamId] = member 56 | } 57 | 58 | // Removes a chat member from a given chat 59 | func (list *ChatsList) RemoveChatMember(id steamid.SteamId, member steamid.SteamId) { 60 | list.mutex.Lock() 61 | defer list.mutex.Unlock() 62 | chat := list.byId[id] 63 | if chat == nil { // Chat doesn't exist 64 | return 65 | } 66 | if chat.ChatMembers == nil { // New chat 67 | return 68 | } 69 | delete(chat.ChatMembers, member) 70 | } 71 | 72 | // Returns a copy of the chats map 73 | func (list *ChatsList) GetCopy() map[steamid.SteamId]Chat { 74 | list.mutex.RLock() 75 | defer list.mutex.RUnlock() 76 | glist := make(map[steamid.SteamId]Chat) 77 | for key, chat := range list.byId { 78 | glist[key] = *chat 79 | } 80 | return glist 81 | } 82 | 83 | // Returns a copy of the chat of a given SteamId 84 | func (list *ChatsList) ById(id steamid.SteamId) (Chat, error) { 85 | list.mutex.RLock() 86 | defer list.mutex.RUnlock() 87 | if val, ok := list.byId[id]; ok { 88 | return *val, nil 89 | } 90 | return Chat{}, errors.New("Chat not found") 91 | } 92 | 93 | // Returns the number of chats 94 | func (list *ChatsList) Count() int { 95 | list.mutex.RLock() 96 | defer list.mutex.RUnlock() 97 | return len(list.byId) 98 | } 99 | 100 | // A Chat 101 | type Chat struct { 102 | SteamId steamid.SteamId `json:",string"` 103 | GroupId steamid.SteamId `json:",string"` 104 | ChatMembers map[steamid.SteamId]ChatMember 105 | } 106 | 107 | // A Chat Member 108 | type ChatMember struct { 109 | SteamId steamid.SteamId `json:",string"` 110 | ChatPermissions steamlang.EChatPermission 111 | ClanPermissions steamlang.EClanPermission 112 | } 113 | -------------------------------------------------------------------------------- /connection.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | "net" 10 | "sync" 11 | 12 | "github.com/Philipp15b/go-steam/v3/cryptoutil" 13 | "github.com/Philipp15b/go-steam/v3/protocol" 14 | ) 15 | 16 | type connection interface { 17 | Read() (*protocol.Packet, error) 18 | Write([]byte) error 19 | Close() error 20 | SetEncryptionKey([]byte) 21 | IsEncrypted() bool 22 | } 23 | 24 | const tcpConnectionMagic uint32 = 0x31305456 // "VT01" 25 | 26 | type tcpConnection struct { 27 | conn *net.TCPConn 28 | ciph cipher.Block 29 | cipherMutex sync.RWMutex 30 | } 31 | 32 | func dialTCP(laddr, raddr *net.TCPAddr) (*tcpConnection, error) { 33 | conn, err := net.DialTCP("tcp", laddr, raddr) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return &tcpConnection{ 39 | conn: conn, 40 | }, nil 41 | } 42 | 43 | func (c *tcpConnection) Read() (*protocol.Packet, error) { 44 | // All packets begin with a packet length 45 | var packetLen uint32 46 | err := binary.Read(c.conn, binary.LittleEndian, &packetLen) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | // A magic value follows for validation 52 | var packetMagic uint32 53 | err = binary.Read(c.conn, binary.LittleEndian, &packetMagic) 54 | if err != nil { 55 | return nil, err 56 | } 57 | if packetMagic != tcpConnectionMagic { 58 | return nil, fmt.Errorf("Invalid connection magic! Expected %d, got %d!", tcpConnectionMagic, packetMagic) 59 | } 60 | 61 | buf := make([]byte, packetLen, packetLen) 62 | _, err = io.ReadFull(c.conn, buf) 63 | if err == io.ErrUnexpectedEOF { 64 | return nil, io.EOF 65 | } 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | // Packets after ChannelEncryptResult are encrypted 71 | c.cipherMutex.RLock() 72 | if c.ciph != nil { 73 | buf = cryptoutil.SymmetricDecrypt(c.ciph, buf) 74 | } 75 | c.cipherMutex.RUnlock() 76 | 77 | return protocol.NewPacket(buf) 78 | } 79 | 80 | // Writes a message. This may only be used by one goroutine at a time. 81 | func (c *tcpConnection) Write(message []byte) error { 82 | c.cipherMutex.RLock() 83 | if c.ciph != nil { 84 | message = cryptoutil.SymmetricEncrypt(c.ciph, message) 85 | } 86 | c.cipherMutex.RUnlock() 87 | 88 | err := binary.Write(c.conn, binary.LittleEndian, uint32(len(message))) 89 | if err != nil { 90 | return err 91 | } 92 | err = binary.Write(c.conn, binary.LittleEndian, tcpConnectionMagic) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | _, err = c.conn.Write(message) 98 | return err 99 | } 100 | 101 | func (c *tcpConnection) Close() error { 102 | return c.conn.Close() 103 | } 104 | 105 | func (c *tcpConnection) SetEncryptionKey(key []byte) { 106 | c.cipherMutex.Lock() 107 | defer c.cipherMutex.Unlock() 108 | if key == nil { 109 | c.ciph = nil 110 | return 111 | } 112 | if len(key) != 32 { 113 | panic("Connection AES key is not 32 bytes long!") 114 | } 115 | 116 | var err error 117 | c.ciph, err = aes.NewCipher(key) 118 | if err != nil { 119 | panic(err) 120 | } 121 | } 122 | 123 | func (c *tcpConnection) IsEncrypted() bool { 124 | c.cipherMutex.RLock() 125 | defer c.cipherMutex.RUnlock() 126 | return c.ciph != nil 127 | } 128 | -------------------------------------------------------------------------------- /trade/trade.go: -------------------------------------------------------------------------------- 1 | package trade 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/Philipp15b/go-steam/v3/steamid" 8 | "github.com/Philipp15b/go-steam/v3/trade/tradeapi" 9 | ) 10 | 11 | const pollTimeout = time.Second 12 | 13 | type Trade struct { 14 | ThemId steamid.SteamId 15 | 16 | MeReady, ThemReady bool 17 | 18 | lastPoll time.Time 19 | queuedEvents []interface{} 20 | api *tradeapi.Trade 21 | } 22 | 23 | func New(sessionId, steamLogin, steamLoginSecure string, other steamid.SteamId) *Trade { 24 | return &Trade{ 25 | other, 26 | false, false, 27 | time.Unix(0, 0), 28 | nil, 29 | tradeapi.New(sessionId, steamLogin, steamLoginSecure, other), 30 | } 31 | } 32 | 33 | func (t *Trade) Version() uint { 34 | return t.api.Version 35 | } 36 | 37 | // Returns all queued events and removes them from the queue without performing a HTTP request, like Poll() would. 38 | func (t *Trade) Events() []interface{} { 39 | qe := t.queuedEvents 40 | t.queuedEvents = nil 41 | return qe 42 | } 43 | 44 | func (t *Trade) onStatus(status *tradeapi.Status) error { 45 | if !status.Success { 46 | return errors.New("trade: returned status not successful! error message: " + status.Error) 47 | } 48 | 49 | if status.NewVersion { 50 | t.api.Version = status.Version 51 | 52 | t.MeReady = status.Me.Ready == true 53 | t.ThemReady = status.Them.Ready == true 54 | } 55 | 56 | switch status.TradeStatus { 57 | case tradeapi.TradeStatus_Complete: 58 | t.addEvent(&TradeEndedEvent{TradeEndReason_Complete}) 59 | case tradeapi.TradeStatus_Cancelled: 60 | t.addEvent(&TradeEndedEvent{TradeEndReason_Cancelled}) 61 | case tradeapi.TradeStatus_Timeout: 62 | t.addEvent(&TradeEndedEvent{TradeEndReason_Timeout}) 63 | case tradeapi.TradeStatus_Failed: 64 | t.addEvent(&TradeEndedEvent{TradeEndReason_Failed}) 65 | case tradeapi.TradeStatus_Open: 66 | // nothing 67 | default: 68 | // ignore too 69 | } 70 | 71 | t.updateEvents(status.Events) 72 | return nil 73 | } 74 | 75 | func (t *Trade) updateEvents(events tradeapi.EventList) { 76 | if len(events) == 0 { 77 | return 78 | } 79 | 80 | var lastLogPos uint 81 | for i, event := range events { 82 | if i < t.api.LogPos { 83 | continue 84 | } 85 | if event.SteamId != t.ThemId { 86 | continue 87 | } 88 | 89 | if lastLogPos < i { 90 | lastLogPos = i 91 | } 92 | 93 | switch event.Action { 94 | case tradeapi.Action_AddItem: 95 | t.addEvent(&ItemAddedEvent{newItem(event)}) 96 | case tradeapi.Action_RemoveItem: 97 | t.addEvent(&ItemRemovedEvent{newItem(event)}) 98 | case tradeapi.Action_Ready: 99 | t.ThemReady = true 100 | t.addEvent(new(ReadyEvent)) 101 | case tradeapi.Action_Unready: 102 | t.ThemReady = false 103 | t.addEvent(new(UnreadyEvent)) 104 | case tradeapi.Action_SetCurrency: 105 | t.addEvent(&SetCurrencyEvent{ 106 | newCurrency(event), 107 | event.OldAmount, 108 | event.NewAmount, 109 | }) 110 | case tradeapi.Action_ChatMessage: 111 | t.addEvent(&ChatEvent{ 112 | event.Text, 113 | }) 114 | } 115 | } 116 | 117 | t.api.LogPos = uint(lastLogPos) + 1 118 | } 119 | 120 | func (t *Trade) addEvent(event interface{}) { 121 | t.queuedEvents = append(t.queuedEvents, event) 122 | } 123 | -------------------------------------------------------------------------------- /trading.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "github.com/Philipp15b/go-steam/v3/protocol" 5 | "github.com/Philipp15b/go-steam/v3/protocol/protobuf" 6 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 7 | "github.com/Philipp15b/go-steam/v3/steamid" 8 | "google.golang.org/protobuf/proto" 9 | ) 10 | 11 | // Provides access to the Steam client's part of Steam Trading, that is bootstrapping 12 | // the trade. 13 | // The trade itself is not handled by the Steam client itself, but it's a part of 14 | // the Steam website. 15 | // 16 | // You'll receive a TradeProposedEvent when a friend proposes a trade. You can accept it with 17 | // the RespondRequest method. You can request a trade yourself with RequestTrade. 18 | type Trading struct { 19 | client *Client 20 | } 21 | 22 | type TradeRequestId uint32 23 | 24 | func (t *Trading) HandlePacket(packet *protocol.Packet) { 25 | switch packet.EMsg { 26 | case steamlang.EMsg_EconTrading_InitiateTradeProposed: 27 | msg := new(protobuf.CMsgTrading_InitiateTradeRequest) 28 | packet.ReadProtoMsg(msg) 29 | t.client.Emit(&TradeProposedEvent{ 30 | RequestId: TradeRequestId(msg.GetTradeRequestId()), 31 | Other: steamid.SteamId(msg.GetOtherSteamid()), 32 | }) 33 | case steamlang.EMsg_EconTrading_InitiateTradeResult: 34 | msg := new(protobuf.CMsgTrading_InitiateTradeResponse) 35 | packet.ReadProtoMsg(msg) 36 | t.client.Emit(&TradeResultEvent{ 37 | RequestId: TradeRequestId(msg.GetTradeRequestId()), 38 | Response: steamlang.EEconTradeResponse(msg.GetResponse()), 39 | Other: steamid.SteamId(msg.GetOtherSteamid()), 40 | 41 | NumDaysSteamGuardRequired: msg.GetSteamguardRequiredDays(), 42 | NumDaysNewDeviceCooldown: msg.GetNewDeviceCooldownDays(), 43 | DefaultNumDaysPasswordResetProbation: msg.GetDefaultPasswordResetProbationDays(), 44 | NumDaysPasswordResetProbation: msg.GetPasswordResetProbationDays(), 45 | }) 46 | case steamlang.EMsg_EconTrading_StartSession: 47 | msg := new(protobuf.CMsgTrading_StartSession) 48 | packet.ReadProtoMsg(msg) 49 | t.client.Emit(&TradeSessionStartEvent{ 50 | Other: steamid.SteamId(msg.GetOtherSteamid()), 51 | }) 52 | } 53 | } 54 | 55 | // Requests a trade. You'll receive a TradeResultEvent if the request fails or 56 | // if the friend accepted the trade. 57 | func (t *Trading) RequestTrade(other steamid.SteamId) { 58 | t.client.Write(protocol.NewClientMsgProtobuf(steamlang.EMsg_EconTrading_InitiateTradeRequest, &protobuf.CMsgTrading_InitiateTradeRequest{ 59 | OtherSteamid: proto.Uint64(uint64(other)), 60 | })) 61 | } 62 | 63 | // Responds to a TradeProposedEvent. 64 | func (t *Trading) RespondRequest(requestId TradeRequestId, accept bool) { 65 | var resp uint32 66 | if accept { 67 | resp = 0 68 | } else { 69 | resp = 1 70 | } 71 | 72 | t.client.Write(protocol.NewClientMsgProtobuf(steamlang.EMsg_EconTrading_InitiateTradeResponse, &protobuf.CMsgTrading_InitiateTradeResponse{ 73 | TradeRequestId: proto.Uint32(uint32(requestId)), 74 | Response: proto.Uint32(resp), 75 | })) 76 | } 77 | 78 | // This cancels a request made with RequestTrade. 79 | func (t *Trading) CancelRequest(other steamid.SteamId) { 80 | t.client.Write(protocol.NewClientMsgProtobuf(steamlang.EMsg_EconTrading_CancelTradeRequest, &protobuf.CMsgTrading_CancelTradeRequest{ 81 | OtherSteamid: proto.Uint64(uint64(other)), 82 | })) 83 | } 84 | -------------------------------------------------------------------------------- /protocol/gamecoordinator/msg.go: -------------------------------------------------------------------------------- 1 | package gamecoordinator 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/Philipp15b/go-steam/v3/protocol" 7 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 8 | "google.golang.org/protobuf/proto" 9 | ) 10 | 11 | // An outgoing message to the Game Coordinator. 12 | type IGCMsg interface { 13 | protocol.Serializer 14 | IsProto() bool 15 | GetAppId() uint32 16 | GetMsgType() uint32 17 | 18 | GetTargetJobId() protocol.JobId 19 | SetTargetJobId(protocol.JobId) 20 | GetSourceJobId() protocol.JobId 21 | SetSourceJobId(protocol.JobId) 22 | } 23 | 24 | type GCMsgProtobuf struct { 25 | AppId uint32 26 | Header *steamlang.MsgGCHdrProtoBuf 27 | Body proto.Message 28 | } 29 | 30 | func NewGCMsgProtobuf(appId, msgType uint32, body proto.Message) *GCMsgProtobuf { 31 | hdr := steamlang.NewMsgGCHdrProtoBuf() 32 | hdr.Msg = msgType 33 | return &GCMsgProtobuf{ 34 | AppId: appId, 35 | Header: hdr, 36 | Body: body, 37 | } 38 | } 39 | 40 | func (g *GCMsgProtobuf) IsProto() bool { 41 | return true 42 | } 43 | 44 | func (g *GCMsgProtobuf) GetAppId() uint32 { 45 | return g.AppId 46 | } 47 | 48 | func (g *GCMsgProtobuf) GetMsgType() uint32 { 49 | return g.Header.Msg 50 | } 51 | 52 | func (g *GCMsgProtobuf) GetTargetJobId() protocol.JobId { 53 | return protocol.JobId(g.Header.Proto.GetJobidTarget()) 54 | } 55 | 56 | func (g *GCMsgProtobuf) SetTargetJobId(job protocol.JobId) { 57 | g.Header.Proto.JobidTarget = proto.Uint64(uint64(job)) 58 | } 59 | 60 | func (g *GCMsgProtobuf) GetSourceJobId() protocol.JobId { 61 | return protocol.JobId(g.Header.Proto.GetJobidSource()) 62 | } 63 | 64 | func (g *GCMsgProtobuf) SetSourceJobId(job protocol.JobId) { 65 | g.Header.Proto.JobidSource = proto.Uint64(uint64(job)) 66 | } 67 | 68 | func (g *GCMsgProtobuf) Serialize(w io.Writer) error { 69 | err := g.Header.Serialize(w) 70 | if err != nil { 71 | return err 72 | } 73 | body, err := proto.Marshal(g.Body) 74 | if err != nil { 75 | return err 76 | } 77 | _, err = w.Write(body) 78 | return err 79 | } 80 | 81 | type GCMsg struct { 82 | AppId uint32 83 | MsgType uint32 84 | Header *steamlang.MsgGCHdr 85 | Body protocol.Serializer 86 | } 87 | 88 | func NewGCMsg(appId, msgType uint32, body protocol.Serializer) *GCMsg { 89 | return &GCMsg{ 90 | AppId: appId, 91 | MsgType: msgType, 92 | Header: steamlang.NewMsgGCHdr(), 93 | Body: body, 94 | } 95 | } 96 | 97 | func (g *GCMsg) GetMsgType() uint32 { 98 | return g.MsgType 99 | } 100 | 101 | func (g *GCMsg) GetAppId() uint32 { 102 | return g.AppId 103 | } 104 | 105 | func (g *GCMsg) IsProto() bool { 106 | return false 107 | } 108 | 109 | func (g *GCMsg) GetTargetJobId() protocol.JobId { 110 | return protocol.JobId(g.Header.TargetJobID) 111 | } 112 | 113 | func (g *GCMsg) SetTargetJobId(job protocol.JobId) { 114 | g.Header.TargetJobID = uint64(job) 115 | } 116 | 117 | func (g *GCMsg) GetSourceJobId() protocol.JobId { 118 | return protocol.JobId(g.Header.SourceJobID) 119 | } 120 | 121 | func (g *GCMsg) SetSourceJobId(job protocol.JobId) { 122 | g.Header.SourceJobID = uint64(job) 123 | } 124 | 125 | func (g *GCMsg) Serialize(w io.Writer) error { 126 | err := g.Header.Serialize(w) 127 | if err != nil { 128 | return err 129 | } 130 | err = g.Body.Serialize(w) 131 | return err 132 | } 133 | -------------------------------------------------------------------------------- /keys.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "crypto/rsa" 5 | 6 | "github.com/Philipp15b/go-steam/v3/cryptoutil" 7 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 8 | ) 9 | 10 | var publicKeys = map[steamlang.EUniverse][]byte{ 11 | steamlang.EUniverse_Public: { 12 | 0x30, 0x81, 0x9D, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 13 | 0x05, 0x00, 0x03, 0x81, 0x8B, 0x00, 0x30, 0x81, 0x87, 0x02, 0x81, 0x81, 0x00, 0xDF, 0xEC, 0x1A, 14 | 0xD6, 0x2C, 0x10, 0x66, 0x2C, 0x17, 0x35, 0x3A, 0x14, 0xB0, 0x7C, 0x59, 0x11, 0x7F, 0x9D, 0xD3, 15 | 0xD8, 0x2B, 0x7A, 0xE3, 0xE0, 0x15, 0xCD, 0x19, 0x1E, 0x46, 0xE8, 0x7B, 0x87, 0x74, 0xA2, 0x18, 16 | 0x46, 0x31, 0xA9, 0x03, 0x14, 0x79, 0x82, 0x8E, 0xE9, 0x45, 0xA2, 0x49, 0x12, 0xA9, 0x23, 0x68, 17 | 0x73, 0x89, 0xCF, 0x69, 0xA1, 0xB1, 0x61, 0x46, 0xBD, 0xC1, 0xBE, 0xBF, 0xD6, 0x01, 0x1B, 0xD8, 18 | 0x81, 0xD4, 0xDC, 0x90, 0xFB, 0xFE, 0x4F, 0x52, 0x73, 0x66, 0xCB, 0x95, 0x70, 0xD7, 0xC5, 0x8E, 19 | 0xBA, 0x1C, 0x7A, 0x33, 0x75, 0xA1, 0x62, 0x34, 0x46, 0xBB, 0x60, 0xB7, 0x80, 0x68, 0xFA, 0x13, 20 | 0xA7, 0x7A, 0x8A, 0x37, 0x4B, 0x9E, 0xC6, 0xF4, 0x5D, 0x5F, 0x3A, 0x99, 0xF9, 0x9E, 0xC4, 0x3A, 21 | 0xE9, 0x63, 0xA2, 0xBB, 0x88, 0x19, 0x28, 0xE0, 0xE7, 0x14, 0xC0, 0x42, 0x89, 0x02, 0x01, 0x11, 22 | }, 23 | 24 | steamlang.EUniverse_Beta: { 25 | 0x30, 0x81, 0x9D, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 26 | 0x05, 0x00, 0x03, 0x81, 0x8B, 0x00, 0x30, 0x81, 0x87, 0x02, 0x81, 0x81, 0x00, 0xAE, 0xD1, 0x4B, 27 | 0xC0, 0xA3, 0x36, 0x8B, 0xA0, 0x39, 0x0B, 0x43, 0xDC, 0xED, 0x6A, 0xC8, 0xF2, 0xA3, 0xE4, 0x7E, 28 | 0x09, 0x8C, 0x55, 0x2E, 0xE7, 0xE9, 0x3C, 0xBB, 0xE5, 0x5E, 0x0F, 0x18, 0x74, 0x54, 0x8F, 0xF3, 29 | 0xBD, 0x56, 0x69, 0x5B, 0x13, 0x09, 0xAF, 0xC8, 0xBE, 0xB3, 0xA1, 0x48, 0x69, 0xE9, 0x83, 0x49, 30 | 0x65, 0x8D, 0xD2, 0x93, 0x21, 0x2F, 0xB9, 0x1E, 0xFA, 0x74, 0x3B, 0x55, 0x22, 0x79, 0xBF, 0x85, 31 | 0x18, 0xCB, 0x6D, 0x52, 0x44, 0x4E, 0x05, 0x92, 0x89, 0x6A, 0xA8, 0x99, 0xED, 0x44, 0xAE, 0xE2, 32 | 0x66, 0x46, 0x42, 0x0C, 0xFB, 0x6E, 0x4C, 0x30, 0xC6, 0x6C, 0x5C, 0x16, 0xFF, 0xBA, 0x9C, 0xB9, 33 | 0x78, 0x3F, 0x17, 0x4B, 0xCB, 0xC9, 0x01, 0x5D, 0x3E, 0x37, 0x70, 0xEC, 0x67, 0x5A, 0x33, 0x48, 34 | }, 35 | 36 | steamlang.EUniverse_Internal: { 37 | 0x30, 0x81, 0x9D, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 38 | 0x05, 0x00, 0x03, 0x81, 0x8B, 0x00, 0x30, 0x81, 0x87, 0x02, 0x81, 0x81, 0x00, 0xA8, 0xFE, 0x01, 39 | 0x3B, 0xB6, 0xD7, 0x21, 0x4B, 0x53, 0x23, 0x6F, 0xA1, 0xAB, 0x4E, 0xF1, 0x07, 0x30, 0xA7, 0xC6, 40 | 0x7E, 0x6A, 0x2C, 0xC2, 0x5D, 0x3A, 0xB8, 0x40, 0xCA, 0x59, 0x4D, 0x16, 0x2D, 0x74, 0xEB, 0x0E, 41 | 0x72, 0x46, 0x29, 0xF9, 0xDE, 0x9B, 0xCE, 0x4B, 0x8C, 0xD0, 0xCA, 0xF4, 0x08, 0x94, 0x46, 0xA5, 42 | 0x11, 0xAF, 0x3A, 0xCB, 0xB8, 0x4E, 0xDE, 0xC6, 0xD8, 0x85, 0x0A, 0x7D, 0xAA, 0x96, 0x0A, 0xEA, 43 | 0x7B, 0x51, 0xD6, 0x22, 0x62, 0x5C, 0x1E, 0x58, 0xD7, 0x46, 0x1E, 0x09, 0xAE, 0x43, 0xA7, 0xC4, 44 | 0x34, 0x69, 0xA2, 0xA5, 0xE8, 0x44, 0x76, 0x18, 0xE2, 0x3D, 0xB7, 0xC5, 0xA8, 0x96, 0xFD, 0xE5, 45 | 0xB4, 0x4B, 0xF8, 0x40, 0x12, 0xA6, 0x17, 0x4E, 0xC4, 0xC1, 0x60, 0x0E, 0xB0, 0xC2, 0xB8, 0x40, 46 | }, 47 | } 48 | 49 | func GetPublicKey(universe steamlang.EUniverse) *rsa.PublicKey { 50 | bytes, ok := publicKeys[universe] 51 | if !ok { 52 | return nil 53 | } 54 | key, err := cryptoutil.ParseASN1RSAPublicKey(bytes) 55 | if err != nil { 56 | panic(err) 57 | } 58 | return key 59 | } 60 | -------------------------------------------------------------------------------- /protocol/packet.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | 8 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 9 | "google.golang.org/protobuf/proto" 10 | ) 11 | 12 | // TODO: Headers are always deserialized twice. 13 | 14 | // Represents an incoming, partially unread message. 15 | type Packet struct { 16 | EMsg steamlang.EMsg 17 | IsProto bool 18 | TargetJobId JobId 19 | SourceJobId JobId 20 | Data []byte 21 | } 22 | 23 | func NewPacket(data []byte) (*Packet, error) { 24 | var rawEMsg uint32 25 | err := binary.Read(bytes.NewReader(data), binary.LittleEndian, &rawEMsg) 26 | if err != nil { 27 | return nil, err 28 | } 29 | eMsg := steamlang.NewEMsg(rawEMsg) 30 | buf := bytes.NewReader(data) 31 | if eMsg == steamlang.EMsg_ChannelEncryptRequest || eMsg == steamlang.EMsg_ChannelEncryptResult { 32 | header := steamlang.NewMsgHdr() 33 | header.Msg = eMsg 34 | err = header.Deserialize(buf) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return &Packet{ 39 | EMsg: eMsg, 40 | IsProto: false, 41 | TargetJobId: JobId(header.TargetJobID), 42 | SourceJobId: JobId(header.SourceJobID), 43 | Data: data, 44 | }, nil 45 | } else if steamlang.IsProto(rawEMsg) { 46 | header := steamlang.NewMsgHdrProtoBuf() 47 | header.Msg = eMsg 48 | err = header.Deserialize(buf) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return &Packet{ 53 | EMsg: eMsg, 54 | IsProto: true, 55 | TargetJobId: JobId(header.Proto.GetJobidTarget()), 56 | SourceJobId: JobId(header.Proto.GetJobidSource()), 57 | Data: data, 58 | }, nil 59 | } else { 60 | header := steamlang.NewExtendedClientMsgHdr() 61 | header.Msg = eMsg 62 | err = header.Deserialize(buf) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return &Packet{ 67 | EMsg: eMsg, 68 | IsProto: false, 69 | TargetJobId: JobId(header.TargetJobID), 70 | SourceJobId: JobId(header.SourceJobID), 71 | Data: data, 72 | }, nil 73 | } 74 | } 75 | 76 | func (p *Packet) String() string { 77 | return fmt.Sprintf("Packet{EMsg = %v, Proto = %v, Len = %v, TargetJobId = %v, SourceJobId = %v}", p.EMsg, p.IsProto, len(p.Data), p.TargetJobId, p.SourceJobId) 78 | } 79 | 80 | func (p *Packet) ReadProtoMsg(body proto.Message) *ClientMsgProtobuf { 81 | header := steamlang.NewMsgHdrProtoBuf() 82 | buf := bytes.NewBuffer(p.Data) 83 | header.Deserialize(buf) 84 | proto.Unmarshal(buf.Bytes(), body) 85 | return &ClientMsgProtobuf{ // protobuf messages have no payload 86 | Header: header, 87 | Body: body, 88 | } 89 | } 90 | 91 | func (p *Packet) ReadClientMsg(body MessageBody) *ClientMsg { 92 | header := steamlang.NewExtendedClientMsgHdr() 93 | buf := bytes.NewReader(p.Data) 94 | header.Deserialize(buf) 95 | body.Deserialize(buf) 96 | payload := make([]byte, buf.Len()) 97 | buf.Read(payload) 98 | return &ClientMsg{ 99 | Header: header, 100 | Body: body, 101 | Payload: payload, 102 | } 103 | } 104 | 105 | func (p *Packet) ReadMsg(body MessageBody) *Msg { 106 | header := steamlang.NewMsgHdr() 107 | buf := bytes.NewReader(p.Data) 108 | header.Deserialize(buf) 109 | body.Deserialize(buf) 110 | payload := make([]byte, buf.Len()) 111 | buf.Read(payload) 112 | return &Msg{ 113 | Header: header, 114 | Body: body, 115 | Payload: payload, 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /servers.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/Philipp15b/go-steam/v3/netutil" 8 | ) 9 | 10 | // CMServers contains a list of worlwide servers 11 | var CMServers = []string{ 12 | "155.133.248.52:27018", 13 | "162.254.197.40:27018", 14 | "162.254.197.180:27019", 15 | "155.133.248.50:27019", 16 | "162.254.197.181:27017", 17 | "162.254.197.42:27019", 18 | "162.254.197.180:27017", 19 | "162.254.197.181:27018", 20 | "162.254.197.42:27018", 21 | "155.133.248.50:27017", 22 | "155.133.248.52:27019", 23 | "155.133.248.51:27019", 24 | "155.133.248.53:27019", 25 | "155.133.248.51:27017", 26 | "155.133.248.53:27017", 27 | "155.133.248.52:27017", 28 | "155.133.248.50:27018", 29 | "162.254.197.180:27018", 30 | "162.254.197.40:27017", 31 | "162.254.197.40:27019", 32 | "162.254.197.42:27017", 33 | "162.254.197.181:27019", 34 | "155.133.248.53:27018", 35 | "155.133.248.51:27018", 36 | "146.66.152.11:27017", 37 | "146.66.152.10:27019", 38 | "146.66.152.10:27017", 39 | "146.66.152.10:27018", 40 | "146.66.152.11:27019", 41 | "162.254.198.133:27017", 42 | "162.254.198.133:27018", 43 | "162.254.198.130:27019", 44 | "162.254.198.130:27017", 45 | "162.254.198.132:27018", 46 | "162.254.198.130:27018", 47 | "162.254.198.132:27017", 48 | "162.254.198.132:27019", 49 | "162.254.198.131:27019", 50 | "162.254.198.131:27017", 51 | "146.66.152.11:27018", 52 | "162.254.198.131:27018", 53 | "162.254.198.133:27019", 54 | "185.25.182.77:27017", 55 | "185.25.182.76:27018", 56 | "185.25.182.76:27019", 57 | "185.25.182.77:27018", 58 | "185.25.182.76:27017", 59 | "185.25.182.77:27019", 60 | "162.254.196.67:27019", 61 | "162.254.196.67:27018", 62 | "162.254.196.83:27018", 63 | "162.254.196.84:27018", 64 | "162.254.196.83:27017", 65 | "162.254.196.84:27017", 66 | "162.254.196.68:27019", 67 | "162.254.196.68:27017", 68 | "162.254.196.84:27019", 69 | "162.254.196.67:27017", 70 | "162.254.196.83:27019", 71 | "162.254.196.68:27018", 72 | "146.66.155.101:27017", 73 | "146.66.155.101:27018", 74 | "146.66.155.100:27017", 75 | "146.66.155.100:27018", 76 | "146.66.155.101:27019", 77 | "146.66.155.100:27019", 78 | "155.133.230.50:27017", 79 | "155.133.230.34:27018", 80 | "155.133.230.34:27017", 81 | "155.133.230.50:27019", 82 | "155.133.230.34:27019", 83 | "155.133.230.50:27018", 84 | "162.254.192.100:27017", 85 | "162.254.192.108:27017", 86 | "155.133.246.68:27017", 87 | "155.133.246.68:27018", 88 | "155.133.246.68:27019", 89 | "155.133.246.69:27019", 90 | "155.133.246.69:27017", 91 | "155.133.246.69:27018", 92 | "162.254.192.108:27018", 93 | "162.254.192.101:27018", 94 | "162.254.192.101:27019", 95 | "162.254.192.109:27018", 96 | "162.254.192.100:27018", 97 | "162.254.192.109:27017", 98 | "162.254.192.109:27019", 99 | "162.254.192.108:27019", 100 | "162.254.192.101:27017", 101 | "162.254.192.100:27019", 102 | "162.254.193.46:27019", 103 | "162.254.193.6:27018", 104 | "162.254.193.47:27018", 105 | "162.254.193.6:27019", 106 | "162.254.193.7:27018", 107 | "162.254.193.7:27017", 108 | "162.254.193.7:27019", 109 | "162.254.193.47:27017", 110 | "162.254.193.47:27019", 111 | "162.254.193.46:27018", 112 | } 113 | 114 | // GetRandomCM returns a random server from a built-in IP list. 115 | // 116 | // Prefer Client.Connect(), which uses IPs from the Steam Directory, 117 | // which is always more up-to-date. 118 | func GetRandomCM() *netutil.PortAddr { 119 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) 120 | addr := netutil.ParsePortAddr(CMServers[rng.Int31n(int32(len(CMServers)))]) 121 | if addr == nil { 122 | panic("invalid address in CMServers slice") 123 | } 124 | return addr 125 | } 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Steam for Go 2 | 3 | This library implements Steam's protocol to allow automation of different actions on Steam without running an actual Steam client. It is based on [SteamKit2](https://github.com/SteamRE/SteamKit), a .NET library. 4 | 5 | In addition, it contains APIs to Steam Community features, like trade offers and inventories. 6 | 7 | Some of the currently implemented features: 8 | 9 | * Trading and trade offers, including inventories and notifications 10 | * Friend and group management 11 | * Chatting with friends 12 | * Persona states (online, offline, looking to trade, etc.) 13 | * SteamGuard with two-factor authentication 14 | * Team Fortress 2: Crafting, moving, naming and deleting items 15 | 16 | If this is useful to you, there's also the [go-steamapi](https://github.com/Philipp15b/go-steamapi) package that wraps some of the official Steam Web API's types. 17 | 18 | ## Installation 19 | 20 | go get github.com/Philipp15b/go-steam 21 | 22 | ## Usage 23 | 24 | You can view the documentation with the [`godoc`](http://golang.org/cmd/godoc) tool or 25 | [online on godoc.org](http://godoc.org/github.com/Philipp15b/go-steam). 26 | 27 | You should also take a look at the following sub-packages: 28 | 29 | * [`gsbot`](http://godoc.org/github.com/Philipp15b/go-steam/gsbot) utilites that make writing bots easier 30 | * [example bot](http://godoc.org/github.com/Philipp15b/go-steam/gsbot/gsbot) and [its source code](https://github.com/Philipp15b/go-steam/blob/master/gsbot/gsbot/gsbot.go) 31 | * [`trade`](http://godoc.org/github.com/Philipp15b/go-steam/trade) for trading 32 | * [`tradeoffer`](http://godoc.org/github.com/Philipp15b/go-steam/tradeoffer) for trade offers 33 | * [`economy/inventory`](http://godoc.org/github.com/Philipp15b/go-steam/economy/inventory) for inventories 34 | * [`tf2`](http://godoc.org/github.com/Philipp15b/go-steam/tf2) for Team Fortress 2 related things 35 | 36 | ## Working with go-steam 37 | 38 | Whether you want to develop your own Steam bot or directly work on go-steam itself, there are are few things to know. 39 | 40 | * If something is not working, check first if the same operation works (under the same conditions!) in the Steam client on that account. Maybe there's something go-steam doesn't handle correctly or you're missing a warning that's not obviously shown in go-steam. This is particularly important when working with trading since there are [restrictions](https://support.steampowered.com/kb_article.php?ref=1047-edfm-2932), for example newly authorized devices will not be able to trade for seven days. 41 | * Since Steam does not maintain a public API for most of the things go-steam implements, you can expect that sometimes things break randomly. Especially the `trade` and `tradeoffer` packages have been affected in the past. 42 | * Always gather as much information as possible. When you file an issue, be as precise and complete as you can. This makes debugging way easier. 43 | * If you haven't noticed yet, expect to find lots of things out yourself. Debugging can be complicated and Steam's internals are too. 44 | * Sometimes things break and other [SteamKit ports](https://github.com/SteamRE/SteamKit/wiki/Ports) are fixed already. Maybe take a look what people are saying over there? There's also the [SteamKit IRC channel](https://github.com/SteamRE/SteamKit/wiki#contact). 45 | 46 | ## Updating go-steam to a new SteamKit version 47 | 48 | Go source code is generated with code in the `generator` directory. 49 | Look at `generator/README.md` for more information on how to use the generator. 50 | 51 | Then, after generating new Go source files, update `go-steam` as necessary. 52 | 53 | ## License 54 | 55 | Steam for Go is licensed under the New BSD License. More information can be found in LICENSE.txt. 56 | -------------------------------------------------------------------------------- /steamid/steamid.go: -------------------------------------------------------------------------------- 1 | package steamid 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type ChatInstanceFlag uint64 12 | 13 | const ( 14 | Clan ChatInstanceFlag = 0x100000 >> 1 15 | Lobby ChatInstanceFlag = 0x100000 >> 2 16 | MMSLobby ChatInstanceFlag = 0x100000 >> 3 17 | ) 18 | 19 | type SteamId uint64 20 | 21 | func NewId(id string) (SteamId, error) { 22 | valid, err := regexp.MatchString(`STEAM_[0-5]:[01]:\d+`, id) 23 | if err != nil { 24 | return SteamId(0), err 25 | } 26 | if valid { 27 | id = strings.Replace(id, "STEAM_", "", -1) // remove STEAM_ 28 | splitid := strings.Split(id, ":") // split 0:1:00000000 into 0 1 00000000 29 | universe, _ := strconv.ParseInt(splitid[0], 10, 32) 30 | if universe == 0 { //EUniverse_Invalid 31 | universe = 1 //EUniverse_Public 32 | } 33 | authServer, _ := strconv.ParseUint(splitid[1], 10, 32) 34 | accId, _ := strconv.ParseUint(splitid[2], 10, 32) 35 | accountType := int32(1) //EAccountType_Individual 36 | accountId := (uint32(accId) << 1) | uint32(authServer) 37 | return NewIdAdv(uint32(accountId), 1, int32(universe), accountType), nil 38 | } else { 39 | newid, err := strconv.ParseUint(id, 10, 64) 40 | if err != nil { 41 | return SteamId(0), err 42 | } 43 | return SteamId(newid), nil 44 | } 45 | return SteamId(0), errors.New(fmt.Sprintf("Invalid SteamId: %s\n", id)) 46 | } 47 | 48 | func NewIdAdv(accountId, instance uint32, universe int32, accountType int32) SteamId { 49 | s := SteamId(0) 50 | s = s.SetAccountId(accountId) 51 | s = s.SetAccountInstance(instance) 52 | s = s.SetAccountUniverse(universe) 53 | s = s.SetAccountType(accountType) 54 | return s 55 | } 56 | 57 | func (s SteamId) ToUint64() uint64 { 58 | return uint64(s) 59 | } 60 | 61 | func (s SteamId) ToString() string { 62 | return strconv.FormatUint(uint64(s), 10) 63 | } 64 | 65 | func (s SteamId) String() string { 66 | switch s.GetAccountType() { 67 | case 0: // EAccountType_Invalid 68 | fallthrough 69 | case 1: // EAccountType_Individual 70 | if s.GetAccountUniverse() <= 1 { // EUniverse_Public 71 | return fmt.Sprintf("STEAM_0:%d:%d", s.GetAccountId()&1, s.GetAccountId()>>1) 72 | } else { 73 | return fmt.Sprintf("STEAM_%d:%d:%d", s.GetAccountUniverse(), s.GetAccountId()&1, s.GetAccountId()>>1) 74 | } 75 | default: 76 | return strconv.FormatUint(uint64(s), 10) 77 | } 78 | } 79 | 80 | func (s SteamId) get(offset uint, mask uint64) uint64 { 81 | return (uint64(s) >> offset) & mask 82 | } 83 | 84 | func (s SteamId) set(offset uint, mask, value uint64) SteamId { 85 | return SteamId((uint64(s) & ^(mask << offset)) | (value&mask)< string -> bytes -> base64 128 | w.SessionId = base64.StdEncoding.EncodeToString([]byte(strconv.FormatUint(uint64(msg.GetUniqueId()), 10))) 129 | 130 | w.client.Emit(new(WebSessionIdEvent)) 131 | } 132 | 133 | func (w *Web) handleAuthNonceResponse(packet *protocol.Packet) { 134 | // this has to be the best name for a message yet. 135 | msg := new(protobuf.CMsgClientRequestWebAPIAuthenticateUserNonceResponse) 136 | packet.ReadProtoMsg(msg) 137 | w.webLoginKey = msg.GetWebapiAuthenticateUserNonce() 138 | 139 | // if the nonce was specifically requested in apiLogOn(), 140 | // don't emit an event. 141 | if atomic.CompareAndSwapUint32(&w.relogOnNonce, 1, 0) { 142 | w.LogOn() 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /social_events.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/Philipp15b/go-steam/v3/protocol/protobuf" 7 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 8 | "github.com/Philipp15b/go-steam/v3/steamid" 9 | ) 10 | 11 | type FriendsListEvent struct{} 12 | 13 | type FriendStateEvent struct { 14 | SteamId steamid.SteamId `json:",string"` 15 | Relationship steamlang.EFriendRelationship 16 | } 17 | 18 | func (f *FriendStateEvent) IsFriend() bool { 19 | return f.Relationship == steamlang.EFriendRelationship_Friend 20 | } 21 | 22 | type GroupStateEvent struct { 23 | SteamId steamid.SteamId `json:",string"` 24 | Relationship steamlang.EClanRelationship 25 | } 26 | 27 | func (g *GroupStateEvent) IsMember() bool { 28 | return g.Relationship == steamlang.EClanRelationship_Member 29 | } 30 | 31 | // Fired when someone changing their friend details 32 | type PersonaStateEvent struct { 33 | StatusFlags steamlang.EClientPersonaStateFlag 34 | FriendId steamid.SteamId `json:",string"` 35 | State steamlang.EPersonaState 36 | StateFlags steamlang.EPersonaStateFlag 37 | GameAppId uint32 38 | GameId uint64 `json:",string"` 39 | GameName string 40 | GameServerIp uint32 41 | GameServerPort uint32 42 | QueryPort uint32 43 | SourceSteamId steamid.SteamId `json:",string"` 44 | GameDataBlob []byte 45 | Name string 46 | Avatar []byte 47 | LastLogOff uint32 48 | LastLogOn uint32 49 | ClanRank uint32 50 | ClanTag string 51 | OnlineSessionInstances uint32 52 | PersonaSetByUser bool 53 | RichPresence []*protobuf.CMsgClientPersonaState_Friend_KV 54 | } 55 | 56 | // Fired when a clan's state has been changed 57 | type ClanStateEvent struct { 58 | ClanId steamid.SteamId `json:",string"` 59 | AccountFlags steamlang.EAccountFlags 60 | ClanName string 61 | Avatar []byte 62 | MemberTotalCount uint32 63 | MemberOnlineCount uint32 64 | MemberChattingCount uint32 65 | MemberInGameCount uint32 66 | Events []ClanEventDetails 67 | Announcements []ClanEventDetails 68 | } 69 | 70 | type ClanEventDetails struct { 71 | Id uint64 `json:",string"` 72 | EventTime uint32 73 | Headline string 74 | GameId uint64 `json:",string"` 75 | JustPosted bool 76 | } 77 | 78 | // Fired in response to adding a friend to your friends list 79 | type FriendAddedEvent struct { 80 | Result steamlang.EResult 81 | SteamId steamid.SteamId `json:",string"` 82 | PersonaName string 83 | } 84 | 85 | // Fired when the client receives a message from either a friend or a chat room 86 | type ChatMsgEvent struct { 87 | ChatRoomId steamid.SteamId `json:",string"` // not set for friend messages 88 | ChatterId steamid.SteamId `json:",string"` 89 | Message string 90 | EntryType steamlang.EChatEntryType 91 | Timestamp time.Time 92 | Offline bool 93 | } 94 | 95 | // Whether the type is ChatMsg 96 | func (c *ChatMsgEvent) IsMessage() bool { 97 | return c.EntryType == steamlang.EChatEntryType_ChatMsg 98 | } 99 | 100 | // Fired in response to joining a chat 101 | type ChatEnterEvent struct { 102 | ChatRoomId steamid.SteamId `json:",string"` 103 | FriendId steamid.SteamId `json:",string"` 104 | ChatRoomType steamlang.EChatRoomType 105 | OwnerId steamid.SteamId `json:",string"` 106 | ClanId steamid.SteamId `json:",string"` 107 | ChatFlags byte 108 | EnterResponse steamlang.EChatRoomEnterResponse 109 | Name string 110 | } 111 | 112 | // Fired in response to a chat member's info being received 113 | type ChatMemberInfoEvent struct { 114 | ChatRoomId steamid.SteamId `json:",string"` 115 | Type steamlang.EChatInfoType 116 | StateChangeInfo StateChangeDetails 117 | } 118 | 119 | type StateChangeDetails struct { 120 | ChatterActedOn steamid.SteamId `json:",string"` 121 | StateChange steamlang.EChatMemberStateChange 122 | ChatterActedBy steamid.SteamId `json:",string"` 123 | } 124 | 125 | // Fired when a chat action has completed 126 | type ChatActionResultEvent struct { 127 | ChatRoomId steamid.SteamId `json:",string"` 128 | ChatterId steamid.SteamId `json:",string"` 129 | Action steamlang.EChatAction 130 | Result steamlang.EChatActionResult 131 | } 132 | 133 | // Fired when a chat invite is received 134 | type ChatInviteEvent struct { 135 | InvitedId steamid.SteamId `json:",string"` 136 | ChatRoomId steamid.SteamId `json:",string"` 137 | PatronId steamid.SteamId `json:",string"` 138 | ChatRoomType steamlang.EChatRoomType 139 | FriendChatId steamid.SteamId `json:",string"` 140 | ChatRoomName string 141 | GameId uint64 `json:",string"` 142 | } 143 | 144 | // Fired in response to ignoring a friend 145 | type IgnoreFriendEvent struct { 146 | Result steamlang.EResult 147 | } 148 | 149 | // Fired in response to requesting profile info for a user 150 | type ProfileInfoEvent struct { 151 | Result steamlang.EResult 152 | SteamId steamid.SteamId `json:",string"` 153 | TimeCreated uint32 154 | RealName string 155 | CityName string 156 | StateName string 157 | CountryName string 158 | Headline string 159 | Summary string 160 | } 161 | -------------------------------------------------------------------------------- /tradeoffer/tradeoffer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Implements methods to interact with the official Trade Offer API. 3 | 4 | See: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService 5 | */ 6 | package tradeoffer 7 | 8 | import ( 9 | "encoding/json" 10 | 11 | "github.com/Philipp15b/go-steam/v3/economy/inventory" 12 | "github.com/Philipp15b/go-steam/v3/steamid" 13 | ) 14 | 15 | type TradeOfferState uint 16 | 17 | const ( 18 | TradeOfferState_Invalid TradeOfferState = 1 // Invalid 19 | TradeOfferState_Active = 2 // This trade offer has been sent, neither party has acted on it yet. 20 | TradeOfferState_Accepted = 3 // The trade offer was accepted by the recipient and items were exchanged. 21 | TradeOfferState_Countered = 4 // The recipient made a counter offer 22 | TradeOfferState_Expired = 5 // The trade offer was not accepted before the expiration date 23 | TradeOfferState_Canceled = 6 // The sender cancelled the offer 24 | TradeOfferState_Declined = 7 // The recipient declined the offer 25 | TradeOfferState_InvalidItems = 8 // Some of the items in the offer are no longer available (indicated by the missing flag in the output) 26 | TradeOfferState_CreatedNeedsConfirmation = 9 // The offer hasn't been sent yet and is awaiting email/mobile confirmation. The offer is only visible to the sender. 27 | TradeOfferState_CanceledBySecondFactor = 10 // Either party canceled the offer via email/mobile. The offer is visible to both parties, even if the sender canceled it before it was sent. 28 | TradeOfferState_InEscrow = 11 // The trade has been placed on hold. The items involved in the trade have all been removed from both parties' inventories and will be automatically delivered in the future. 29 | ) 30 | 31 | type TradeOfferConfirmationMethod uint 32 | 33 | const ( 34 | TradeOfferConfirmationMethod_Invalid TradeOfferConfirmationMethod = 0 35 | TradeOfferConfirmationMethod_Email = 1 36 | TradeOfferConfirmationMethod_MobileApp = 2 37 | ) 38 | 39 | type Asset struct { 40 | AppId uint32 `json:"-"` 41 | ContextId uint64 `json:",string"` 42 | AssetId uint64 `json:",string"` 43 | CurrencyId uint64 `json:",string"` 44 | ClassId uint64 `json:",string"` 45 | InstanceId uint64 `json:",string"` 46 | Amount uint64 `json:",string"` 47 | Missing bool 48 | } 49 | 50 | type TradeOffer struct { 51 | TradeOfferId uint64 `json:",string"` 52 | TradeId uint64 `json:",string"` 53 | OtherAccountId uint32 `json:"accountid_other"` 54 | OtherSteamId steamid.SteamId `json:"-"` 55 | Message string `json:"message"` 56 | ExpirationTime uint32 `json:"expiraton_time"` 57 | State TradeOfferState `json:"trade_offer_state"` 58 | ToGive []*Asset `json:"items_to_give"` 59 | ToReceive []*Asset `json:"items_to_receive"` 60 | IsOurOffer bool `json:"is_our_offer"` 61 | TimeCreated uint32 `json:"time_created"` 62 | TimeUpdated uint32 `json:"time_updated"` 63 | EscrowEndDate uint32 `json:"escrow_end_date"` 64 | ConfirmationMethod TradeOfferConfirmationMethod `json:"confirmation_method"` 65 | } 66 | 67 | func (t *TradeOffer) UnmarshalJSON(data []byte) error { 68 | type Alias TradeOffer 69 | aux := struct { 70 | *Alias 71 | }{ 72 | Alias: (*Alias)(t), 73 | } 74 | if err := json.Unmarshal(data, &aux); err != nil { 75 | return err 76 | } 77 | if t.OtherAccountId == 0 { 78 | t.OtherSteamId = steamid.SteamId(0) 79 | return nil 80 | } 81 | t.OtherSteamId = steamid.SteamId(uint64(t.OtherAccountId) + 76561197960265728) 82 | return nil 83 | } 84 | 85 | type TradeOffersResult struct { 86 | Sent []*TradeOffer `json:"trade_offers_sent"` 87 | Received []*TradeOffer `json:"trade_offers_received"` 88 | Descriptions []*Description 89 | } 90 | 91 | type TradeOfferResult struct { 92 | Offer *TradeOffer 93 | Descriptions []*Description 94 | } 95 | type Description struct { 96 | AppId uint32 `json:"appid"` 97 | ClassId uint64 `json:"classid,string"` 98 | InstanceId uint64 `json:"instanceid,string"` 99 | 100 | IconUrl string `json:"icon_url"` 101 | IconUrlLarge string `json:"icon_url_large"` 102 | 103 | Name string 104 | MarketName string `json:"market_name"` 105 | MarketHashName string `json:"market_hash_name"` 106 | 107 | // Colors in hex, for example `B2B2B2` 108 | NameColor string `json:"name_color"` 109 | BackgroundColor string `json:"background_color"` 110 | 111 | Type string 112 | 113 | Tradable bool `json:"tradable"` 114 | Commodity bool `json:"commodity"` 115 | MarketTradableRestriction uint32 `json:"market_tradable_restriction"` 116 | 117 | Descriptions inventory.DescriptionLines `json:"descriptions"` 118 | Actions []*inventory.Action `json:"actions"` 119 | } 120 | -------------------------------------------------------------------------------- /economy/inventory/inventory.go: -------------------------------------------------------------------------------- 1 | /* 2 | Includes inventory types as used in the trade package 3 | */ 4 | package inventory 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "fmt" 10 | "strconv" 11 | 12 | "github.com/Philipp15b/go-steam/v3/jsont" 13 | ) 14 | 15 | type GenericInventory map[uint32]map[uint64]*Inventory 16 | 17 | func NewGenericInventory() GenericInventory { 18 | iMap := make(map[uint32]map[uint64]*Inventory) 19 | return GenericInventory(iMap) 20 | } 21 | 22 | // Get inventory for specified AppId and ContextId 23 | func (i *GenericInventory) Get(appId uint32, contextId uint64) (*Inventory, error) { 24 | iMap := (map[uint32]map[uint64]*Inventory)(*i) 25 | iMap2, ok := iMap[appId] 26 | if !ok { 27 | return nil, fmt.Errorf("inventory for specified appId not found") 28 | } 29 | inv, ok := iMap2[contextId] 30 | if !ok { 31 | return nil, fmt.Errorf("inventory for specified contextId not found") 32 | } 33 | return inv, nil 34 | } 35 | 36 | func (i *GenericInventory) Add(appId uint32, contextId uint64, inv *Inventory) { 37 | iMap := (map[uint32]map[uint64]*Inventory)(*i) 38 | iMap2, ok := iMap[appId] 39 | if !ok { 40 | iMap2 = make(map[uint64]*Inventory) 41 | iMap[appId] = iMap2 42 | } 43 | iMap2[contextId] = inv 44 | } 45 | 46 | type Inventory struct { 47 | Items Items `json:"rgInventory"` 48 | Currencies Currencies `json:"rgCurrency"` 49 | Descriptions Descriptions `json:"rgDescriptions"` 50 | AppInfo *AppInfo `json:"rgAppInfo"` 51 | } 52 | 53 | // Items key is an AssetId 54 | type Items map[string]*Item 55 | 56 | func (i *Items) ToMap() map[string]*Item { 57 | return (map[string]*Item)(*i) 58 | } 59 | 60 | func (i *Items) Get(assetId uint64) (*Item, error) { 61 | iMap := (map[string]*Item)(*i) 62 | if item, ok := iMap[strconv.FormatUint(assetId, 10)]; ok { 63 | return item, nil 64 | } 65 | return nil, fmt.Errorf("item not found") 66 | } 67 | 68 | func (i *Items) UnmarshalJSON(data []byte) error { 69 | if bytes.Equal(data, []byte("[]")) { 70 | return nil 71 | } 72 | return json.Unmarshal(data, (*map[string]*Item)(i)) 73 | } 74 | 75 | type Currencies map[string]*Currency 76 | 77 | func (c *Currencies) ToMap() map[string]*Currency { 78 | return (map[string]*Currency)(*c) 79 | } 80 | 81 | func (c *Currencies) UnmarshalJSON(data []byte) error { 82 | if bytes.Equal(data, []byte("[]")) { 83 | return nil 84 | } 85 | return json.Unmarshal(data, (*map[string]*Currency)(c)) 86 | } 87 | 88 | // Descriptions key format is %d_%d, first %d is ClassId, second is InstanceId 89 | type Descriptions map[string]*Description 90 | 91 | func (d *Descriptions) ToMap() map[string]*Description { 92 | return (map[string]*Description)(*d) 93 | } 94 | 95 | func (d *Descriptions) Get(classId uint64, instanceId uint64) (*Description, error) { 96 | dMap := (map[string]*Description)(*d) 97 | descId := fmt.Sprintf("%v_%v", classId, instanceId) 98 | if desc, ok := dMap[descId]; ok { 99 | return desc, nil 100 | } 101 | return nil, fmt.Errorf("description not found") 102 | } 103 | 104 | func (d *Descriptions) UnmarshalJSON(data []byte) error { 105 | if bytes.Equal(data, []byte("[]")) { 106 | return nil 107 | } 108 | return json.Unmarshal(data, (*map[string]*Description)(d)) 109 | } 110 | 111 | type Item struct { 112 | Id uint64 `json:",string"` 113 | ClassId uint64 `json:",string"` 114 | InstanceId uint64 `json:",string"` 115 | Amount uint64 `json:",string"` 116 | Pos uint32 117 | } 118 | 119 | type Currency struct { 120 | Id uint64 `json:",string"` 121 | ClassId uint64 `json:",string"` 122 | IsCurrency bool `json:"is_currency"` 123 | Pos uint32 124 | } 125 | 126 | type Description struct { 127 | AppId uint32 `json:",string"` 128 | ClassId uint64 `json:",string"` 129 | InstanceId uint64 `json:",string"` 130 | 131 | IconUrl string `json:"icon_url"` 132 | IconUrlLarge string `json:"icon_url_large"` 133 | IconDragUrl string `json:"icon_drag_url"` 134 | 135 | Name string 136 | MarketName string `json:"market_name"` 137 | MarketHashName string `json:"market_hash_name"` 138 | 139 | // Colors in hex, for example `B2B2B2` 140 | NameColor string `json:"name_color"` 141 | BackgroundColor string `json:"background_color"` 142 | 143 | Type string 144 | 145 | Tradable jsont.UintBool 146 | Marketable jsont.UintBool 147 | Commodity jsont.UintBool 148 | MarketTradableRestriction uint32 `json:"market_tradable_restriction,string"` 149 | 150 | Descriptions DescriptionLines 151 | Actions []*Action 152 | // Application-specific data, like "def_index" and "quality" for TF2 153 | AppData map[string]string 154 | Tags []*Tag 155 | } 156 | 157 | type DescriptionLines []*DescriptionLine 158 | 159 | func (d *DescriptionLines) UnmarshalJSON(data []byte) error { 160 | if bytes.Equal(data, []byte(`""`)) { 161 | return nil 162 | } 163 | return json.Unmarshal(data, (*[]*DescriptionLine)(d)) 164 | } 165 | 166 | type DescriptionLine struct { 167 | Value string 168 | Type *string // Is `html` for HTML descriptions 169 | Color *string 170 | } 171 | 172 | type Action struct { 173 | Name string 174 | Link string 175 | } 176 | 177 | type AppInfo struct { 178 | AppId uint32 179 | Name string 180 | Icon string 181 | Link string 182 | } 183 | 184 | type Tag struct { 185 | InternalName string `json:internal_name` 186 | Name string 187 | Category string 188 | CategoryName string `json:category_name` 189 | } 190 | -------------------------------------------------------------------------------- /protocol/msg.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 7 | "github.com/Philipp15b/go-steam/v3/steamid" 8 | "google.golang.org/protobuf/proto" 9 | ) 10 | 11 | // Interface for all messages, typically outgoing. They can also be created by 12 | // using the Read* methods in a PacketMsg. 13 | type IMsg interface { 14 | Serializer 15 | IsProto() bool 16 | GetMsgType() steamlang.EMsg 17 | GetTargetJobId() JobId 18 | SetTargetJobId(JobId) 19 | GetSourceJobId() JobId 20 | SetSourceJobId(JobId) 21 | } 22 | 23 | // Interface for client messages, i.e. messages that are sent after logging in. 24 | // ClientMsgProtobuf and ClientMsg implement this. 25 | type IClientMsg interface { 26 | IMsg 27 | GetSessionId() int32 28 | SetSessionId(int32) 29 | GetSteamId() steamid.SteamId 30 | SetSteamId(steamid.SteamId) 31 | } 32 | 33 | // Represents a protobuf backed client message with session data. 34 | type ClientMsgProtobuf struct { 35 | Header *steamlang.MsgHdrProtoBuf 36 | Body proto.Message 37 | } 38 | 39 | func NewClientMsgProtobuf(eMsg steamlang.EMsg, body proto.Message) *ClientMsgProtobuf { 40 | hdr := steamlang.NewMsgHdrProtoBuf() 41 | hdr.Msg = eMsg 42 | return &ClientMsgProtobuf{ 43 | Header: hdr, 44 | Body: body, 45 | } 46 | } 47 | 48 | func (c *ClientMsgProtobuf) IsProto() bool { 49 | return true 50 | } 51 | 52 | func (c *ClientMsgProtobuf) GetMsgType() steamlang.EMsg { 53 | return steamlang.NewEMsg(uint32(c.Header.Msg)) 54 | } 55 | 56 | func (c *ClientMsgProtobuf) GetSessionId() int32 { 57 | return c.Header.Proto.GetClientSessionid() 58 | } 59 | 60 | func (c *ClientMsgProtobuf) SetSessionId(session int32) { 61 | c.Header.Proto.ClientSessionid = &session 62 | } 63 | 64 | func (c *ClientMsgProtobuf) GetSteamId() steamid.SteamId { 65 | return steamid.SteamId(c.Header.Proto.GetSteamid()) 66 | } 67 | 68 | func (c *ClientMsgProtobuf) SetSteamId(s steamid.SteamId) { 69 | c.Header.Proto.Steamid = proto.Uint64(uint64(s)) 70 | } 71 | 72 | func (c *ClientMsgProtobuf) GetTargetJobId() JobId { 73 | return JobId(c.Header.Proto.GetJobidTarget()) 74 | } 75 | 76 | func (c *ClientMsgProtobuf) SetTargetJobId(job JobId) { 77 | c.Header.Proto.JobidTarget = proto.Uint64(uint64(job)) 78 | } 79 | 80 | func (c *ClientMsgProtobuf) GetSourceJobId() JobId { 81 | return JobId(c.Header.Proto.GetJobidSource()) 82 | } 83 | 84 | func (c *ClientMsgProtobuf) SetSourceJobId(job JobId) { 85 | c.Header.Proto.JobidSource = proto.Uint64(uint64(job)) 86 | } 87 | 88 | func (c *ClientMsgProtobuf) Serialize(w io.Writer) error { 89 | err := c.Header.Serialize(w) 90 | if err != nil { 91 | return err 92 | } 93 | body, err := proto.Marshal(c.Body) 94 | if err != nil { 95 | return err 96 | } 97 | _, err = w.Write(body) 98 | return err 99 | } 100 | 101 | // Represents a struct backed client message. 102 | type ClientMsg struct { 103 | Header *steamlang.ExtendedClientMsgHdr 104 | Body MessageBody 105 | Payload []byte 106 | } 107 | 108 | func NewClientMsg(body MessageBody, payload []byte) *ClientMsg { 109 | hdr := steamlang.NewExtendedClientMsgHdr() 110 | hdr.Msg = body.GetEMsg() 111 | return &ClientMsg{ 112 | Header: hdr, 113 | Body: body, 114 | Payload: payload, 115 | } 116 | } 117 | 118 | func (c *ClientMsg) IsProto() bool { 119 | return true 120 | } 121 | 122 | func (c *ClientMsg) GetMsgType() steamlang.EMsg { 123 | return c.Header.Msg 124 | } 125 | 126 | func (c *ClientMsg) GetSessionId() int32 { 127 | return c.Header.SessionID 128 | } 129 | 130 | func (c *ClientMsg) SetSessionId(session int32) { 131 | c.Header.SessionID = session 132 | } 133 | 134 | func (c *ClientMsg) GetSteamId() steamid.SteamId { 135 | return c.Header.SteamID 136 | } 137 | 138 | func (c *ClientMsg) SetSteamId(s steamid.SteamId) { 139 | c.Header.SteamID = s 140 | } 141 | 142 | func (c *ClientMsg) GetTargetJobId() JobId { 143 | return JobId(c.Header.TargetJobID) 144 | } 145 | 146 | func (c *ClientMsg) SetTargetJobId(job JobId) { 147 | c.Header.TargetJobID = uint64(job) 148 | } 149 | 150 | func (c *ClientMsg) GetSourceJobId() JobId { 151 | return JobId(c.Header.SourceJobID) 152 | } 153 | 154 | func (c *ClientMsg) SetSourceJobId(job JobId) { 155 | c.Header.SourceJobID = uint64(job) 156 | } 157 | 158 | func (c *ClientMsg) Serialize(w io.Writer) error { 159 | err := c.Header.Serialize(w) 160 | if err != nil { 161 | return err 162 | } 163 | err = c.Body.Serialize(w) 164 | if err != nil { 165 | return err 166 | } 167 | _, err = w.Write(c.Payload) 168 | return err 169 | } 170 | 171 | type Msg struct { 172 | Header *steamlang.MsgHdr 173 | Body MessageBody 174 | Payload []byte 175 | } 176 | 177 | func NewMsg(body MessageBody, payload []byte) *Msg { 178 | hdr := steamlang.NewMsgHdr() 179 | hdr.Msg = body.GetEMsg() 180 | return &Msg{ 181 | Header: hdr, 182 | Body: body, 183 | Payload: payload, 184 | } 185 | } 186 | 187 | func (m *Msg) GetMsgType() steamlang.EMsg { 188 | return m.Header.Msg 189 | } 190 | 191 | func (m *Msg) IsProto() bool { 192 | return false 193 | } 194 | 195 | func (m *Msg) GetTargetJobId() JobId { 196 | return JobId(m.Header.TargetJobID) 197 | } 198 | 199 | func (m *Msg) SetTargetJobId(job JobId) { 200 | m.Header.TargetJobID = uint64(job) 201 | } 202 | 203 | func (m *Msg) GetSourceJobId() JobId { 204 | return JobId(m.Header.SourceJobID) 205 | } 206 | 207 | func (m *Msg) SetSourceJobId(job JobId) { 208 | m.Header.SourceJobID = uint64(job) 209 | } 210 | 211 | func (m *Msg) Serialize(w io.Writer) error { 212 | err := m.Header.Serialize(w) 213 | if err != nil { 214 | return err 215 | } 216 | err = m.Body.Serialize(w) 217 | if err != nil { 218 | return err 219 | } 220 | _, err = w.Write(m.Payload) 221 | return err 222 | } 223 | -------------------------------------------------------------------------------- /gsbot/gsbot.go: -------------------------------------------------------------------------------- 1 | // The GsBot package contains some useful utilites for working with the 2 | // steam package. It implements authentication with sentries, server lists and 3 | // logging messages and events. 4 | // 5 | // Every module is optional and requires an instance of the GsBot struct. 6 | // Should a module have a `HandlePacket` method, you must register it with the 7 | // steam.Client with `RegisterPacketHandler`. Any module with a `HandleEvent` 8 | // method must be integrated into your event loop and should be called for each 9 | // event you receive. 10 | package gsbot 11 | 12 | import ( 13 | "encoding/hex" 14 | "encoding/json" 15 | "fmt" 16 | "io/ioutil" 17 | "log" 18 | "math/rand" 19 | "net" 20 | "os" 21 | "path" 22 | "reflect" 23 | "time" 24 | 25 | "github.com/Philipp15b/go-steam/v3" 26 | "github.com/Philipp15b/go-steam/v3/netutil" 27 | "github.com/Philipp15b/go-steam/v3/protocol" 28 | "github.com/davecgh/go-spew/spew" 29 | ) 30 | 31 | // Base structure holding common data among GsBot modules. 32 | type GsBot struct { 33 | Client *steam.Client 34 | Log *log.Logger 35 | } 36 | 37 | // Creates a new GsBot with a new steam.Client where logs are written to stdout. 38 | func Default() *GsBot { 39 | return &GsBot{ 40 | steam.NewClient(), 41 | log.New(os.Stdout, "", 0), 42 | } 43 | } 44 | 45 | // This module handles authentication. It logs on automatically after a ConnectedEvent 46 | // and saves the sentry data to a file which is also used for logon if available. 47 | // If you're logging on for the first time Steam may require an authcode. You can then 48 | // connect again with the new logon details. 49 | type Auth struct { 50 | bot *GsBot 51 | details *steam.LogOnDetails 52 | sentryPath string 53 | machineAuthHash []byte 54 | } 55 | 56 | func NewAuth(bot *GsBot, details *steam.LogOnDetails, sentryPath string) *Auth { 57 | return &Auth{ 58 | bot: bot, 59 | details: details, 60 | sentryPath: sentryPath, 61 | } 62 | } 63 | 64 | // This is called automatically after every ConnectedEvent, but must be called once again manually 65 | // with an authcode if Steam requires it when logging on for the first time. 66 | func (a *Auth) LogOn() { 67 | sentry, err := ioutil.ReadFile(a.sentryPath) 68 | if err != nil { 69 | a.bot.Log.Printf("Error loading sentry file from path %v - This is normal if you're logging in for the first time.\n", a.sentryPath) 70 | } 71 | a.details.SentryFileHash = sentry 72 | a.bot.Client.Auth.LogOn(a.details) 73 | } 74 | 75 | func (a *Auth) HandleEvent(event interface{}) { 76 | switch e := event.(type) { 77 | case *steam.ConnectedEvent: 78 | a.LogOn() 79 | case *steam.LoggedOnEvent: 80 | a.bot.Log.Printf("Logged on (%v) with SteamId %v and account flags %v", e.Result, e.ClientSteamId, e.AccountFlags) 81 | case *steam.MachineAuthUpdateEvent: 82 | a.machineAuthHash = e.Hash 83 | err := ioutil.WriteFile(a.sentryPath, e.Hash, 0666) 84 | if err != nil { 85 | panic(err) 86 | } 87 | case *steam.LoginKeyEvent: 88 | a.bot.Log.Printf("New LoginKey: %v\n", e.LoginKey) 89 | case *steam.LogOnFailedEvent: 90 | a.bot.Log.Printf("Failed to log on (%v)", e.Result) 91 | } 92 | } 93 | 94 | // This module saves the server list from ClientCMListEvent and uses 95 | // it when you call `Connect()`. 96 | type ServerList struct { 97 | bot *GsBot 98 | listPath string 99 | } 100 | 101 | func NewServerList(bot *GsBot, listPath string) *ServerList { 102 | return &ServerList{ 103 | bot, 104 | listPath, 105 | } 106 | } 107 | 108 | func (s *ServerList) HandleEvent(event interface{}) { 109 | switch e := event.(type) { 110 | case *steam.ClientCMListEvent: 111 | d, err := json.Marshal(e.Addresses) 112 | if err != nil { 113 | panic(err) 114 | } 115 | err = ioutil.WriteFile(s.listPath, d, 0666) 116 | if err != nil { 117 | panic(err) 118 | } 119 | } 120 | } 121 | 122 | func (s *ServerList) Connect() (bool, error) { 123 | return s.ConnectBind(nil) 124 | } 125 | 126 | func (s *ServerList) ConnectBind(laddr *net.TCPAddr) (bool, error) { 127 | d, err := ioutil.ReadFile(s.listPath) 128 | if err != nil { 129 | s.bot.Log.Println("Connecting to random server.") 130 | _, err := s.bot.Client.Connect() 131 | return err == nil, err 132 | } 133 | var addrs []*netutil.PortAddr 134 | err = json.Unmarshal(d, &addrs) 135 | if err != nil { 136 | return false, err 137 | } 138 | raddr := addrs[rand.Intn(len(addrs))] 139 | s.bot.Log.Printf("Connecting to %v from server list\n", raddr) 140 | err = s.bot.Client.ConnectToBind(raddr, laddr) 141 | return err == nil, err 142 | } 143 | 144 | // This module logs incoming packets and events to a directory. 145 | type Debug struct { 146 | packetId, eventId uint64 147 | bot *GsBot 148 | base string 149 | } 150 | 151 | func NewDebug(bot *GsBot, base string) (*Debug, error) { 152 | base = path.Join(base, fmt.Sprint(time.Now().Unix())) 153 | err := os.MkdirAll(path.Join(base, "events"), 0700) 154 | if err != nil { 155 | return nil, err 156 | } 157 | err = os.MkdirAll(path.Join(base, "packets"), 0700) 158 | if err != nil { 159 | return nil, err 160 | } 161 | return &Debug{ 162 | 0, 0, 163 | bot, 164 | base, 165 | }, nil 166 | } 167 | 168 | func (d *Debug) HandlePacket(packet *protocol.Packet) { 169 | d.packetId++ 170 | name := path.Join(d.base, "packets", fmt.Sprintf("%d_%d_%s", time.Now().Unix(), d.packetId, packet.EMsg)) 171 | 172 | text := packet.String() + "\n\n" + hex.Dump(packet.Data) 173 | err := ioutil.WriteFile(name+".txt", []byte(text), 0666) 174 | if err != nil { 175 | panic(err) 176 | } 177 | 178 | err = ioutil.WriteFile(name+".bin", packet.Data, 0666) 179 | if err != nil { 180 | panic(err) 181 | } 182 | } 183 | 184 | func (d *Debug) HandleEvent(event interface{}) { 185 | d.eventId++ 186 | name := fmt.Sprintf("%d_%d_%s.txt", time.Now().Unix(), d.eventId, name(event)) 187 | err := ioutil.WriteFile(path.Join(d.base, "events", name), []byte(spew.Sdump(event)), 0666) 188 | if err != nil { 189 | panic(err) 190 | } 191 | } 192 | 193 | func name(obj interface{}) string { 194 | val := reflect.ValueOf(obj) 195 | ind := reflect.Indirect(val) 196 | if ind.IsValid() { 197 | return ind.Type().Name() 198 | } else { 199 | return val.Type().Name() 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /trade/tradeapi/trade.go: -------------------------------------------------------------------------------- 1 | /* 2 | Wrapper around the HTTP trading API for type safety 'n' stuff. 3 | */ 4 | package tradeapi 5 | 6 | import ( 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "io/ioutil" 11 | "net/http" 12 | "regexp" 13 | "strconv" 14 | "time" 15 | 16 | "github.com/Philipp15b/go-steam/v3/community" 17 | "github.com/Philipp15b/go-steam/v3/economy/inventory" 18 | "github.com/Philipp15b/go-steam/v3/netutil" 19 | "github.com/Philipp15b/go-steam/v3/steamid" 20 | ) 21 | 22 | const tradeUrl = "https://steamcommunity.com/trade/%d/" 23 | 24 | type Trade struct { 25 | client *http.Client 26 | other steamid.SteamId 27 | 28 | LogPos uint // not automatically updated 29 | Version uint // Incremented for each item change by Steam; not automatically updated. 30 | 31 | // the `sessionid` cookie is sent as a parameter/POST data for CSRF protection. 32 | sessionId string 33 | baseUrl string 34 | } 35 | 36 | // Creates a new Trade based on the given cookies `sessionid`, `steamLogin`, `steamLoginSecure` and the trade partner's Steam ID. 37 | func New(sessionId, steamLogin, steamLoginSecure string, other steamid.SteamId) *Trade { 38 | client := new(http.Client) 39 | client.Timeout = 10 * time.Second 40 | 41 | t := &Trade{ 42 | client: client, 43 | other: other, 44 | sessionId: sessionId, 45 | baseUrl: fmt.Sprintf(tradeUrl, other), 46 | Version: 1, 47 | } 48 | community.SetCookies(t.client, sessionId, steamLogin, steamLoginSecure) 49 | return t 50 | } 51 | 52 | type Main struct { 53 | PartnerOnProbation bool 54 | } 55 | 56 | var onProbationRegex = regexp.MustCompile(`var g_bTradePartnerProbation = (\w+);`) 57 | 58 | // Fetches the main HTML page and parses it. Thread-safe. 59 | func (t *Trade) GetMain() (*Main, error) { 60 | resp, err := t.client.Get(t.baseUrl) 61 | if err != nil { 62 | return nil, err 63 | } 64 | defer resp.Body.Close() 65 | body, err := ioutil.ReadAll(resp.Body) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | match := onProbationRegex.FindSubmatch(body) 71 | if len(match) == 0 { 72 | return nil, errors.New("tradeapi.GetMain: Could not find probation info") 73 | } 74 | 75 | return &Main{ 76 | string(match[1]) == "true", 77 | }, nil 78 | } 79 | 80 | // Ajax POSTs to an API endpoint that should return a status 81 | func (t *Trade) postWithStatus(url string, data map[string]string) (*Status, error) { 82 | status := new(Status) 83 | 84 | req := netutil.NewPostForm(url, netutil.ToUrlValues(data)) 85 | // Tales of Madness and Pain, Episode 1: If you forget this, Steam will return an error 86 | // saying "missing required parameter", even though they are all there. IT WAS JUST THE HEADER, ARGH! 87 | req.Header.Add("Referer", t.baseUrl) 88 | 89 | resp, err := t.client.Do(req) 90 | if err != nil { 91 | return nil, err 92 | } 93 | defer resp.Body.Close() 94 | 95 | err = json.NewDecoder(resp.Body).Decode(status) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return status, nil 100 | } 101 | 102 | func (t *Trade) GetStatus() (*Status, error) { 103 | return t.postWithStatus(t.baseUrl+"tradestatus/", map[string]string{ 104 | "sessionid": t.sessionId, 105 | "logpos": strconv.FormatUint(uint64(t.LogPos), 10), 106 | "version": strconv.FormatUint(uint64(t.Version), 10), 107 | }) 108 | } 109 | 110 | // Thread-safe. 111 | func (t *Trade) GetForeignInventory(contextId uint64, appId uint32, start *uint) (*inventory.PartialInventory, error) { 112 | data := map[string]string{ 113 | "sessionid": t.sessionId, 114 | "steamid": fmt.Sprintf("%d", t.other), 115 | "contextid": strconv.FormatUint(contextId, 10), 116 | "appid": strconv.FormatUint(uint64(appId), 10), 117 | } 118 | if start != nil { 119 | data["start"] = strconv.FormatUint(uint64(*start), 10) 120 | } 121 | 122 | req, err := http.NewRequest("GET", t.baseUrl+"foreigninventory?"+netutil.ToUrlValues(data).Encode(), nil) 123 | if err != nil { 124 | panic(err) 125 | } 126 | req.Header.Add("Referer", t.baseUrl) 127 | 128 | return inventory.DoInventoryRequest(t.client, req) 129 | } 130 | 131 | // Thread-safe. 132 | func (t *Trade) GetOwnInventory(contextId uint64, appId uint32) (*inventory.Inventory, error) { 133 | return inventory.GetOwnInventory(t.client, contextId, appId) 134 | } 135 | 136 | func (t *Trade) Chat(message string) (*Status, error) { 137 | return t.postWithStatus(t.baseUrl+"chat", map[string]string{ 138 | "sessionid": t.sessionId, 139 | "logpos": strconv.FormatUint(uint64(t.LogPos), 10), 140 | "version": strconv.FormatUint(uint64(t.Version), 10), 141 | "message": message, 142 | }) 143 | } 144 | 145 | func (t *Trade) AddItem(slot uint, itemId, contextId uint64, appId uint32) (*Status, error) { 146 | return t.postWithStatus(t.baseUrl+"additem", map[string]string{ 147 | "sessionid": t.sessionId, 148 | "slot": strconv.FormatUint(uint64(slot), 10), 149 | "itemid": strconv.FormatUint(itemId, 10), 150 | "contextid": strconv.FormatUint(contextId, 10), 151 | "appid": strconv.FormatUint(uint64(appId), 10), 152 | }) 153 | } 154 | 155 | func (t *Trade) RemoveItem(slot uint, itemId, contextId uint64, appId uint32) (*Status, error) { 156 | return t.postWithStatus(t.baseUrl+"removeitem", map[string]string{ 157 | "sessionid": t.sessionId, 158 | "slot": strconv.FormatUint(uint64(slot), 10), 159 | "itemid": strconv.FormatUint(itemId, 10), 160 | "contextid": strconv.FormatUint(contextId, 10), 161 | "appid": strconv.FormatUint(uint64(appId), 10), 162 | }) 163 | } 164 | 165 | func (t *Trade) SetCurrency(amount uint, currencyId, contextId uint64, appId uint32) (*Status, error) { 166 | return t.postWithStatus(t.baseUrl+"setcurrency", map[string]string{ 167 | "sessionid": t.sessionId, 168 | "amount": strconv.FormatUint(uint64(amount), 10), 169 | "currencyid": strconv.FormatUint(uint64(currencyId), 10), 170 | "contextid": strconv.FormatUint(contextId, 10), 171 | "appid": strconv.FormatUint(uint64(appId), 10), 172 | }) 173 | } 174 | 175 | func (t *Trade) SetReady(ready bool) (*Status, error) { 176 | return t.postWithStatus(t.baseUrl+"toggleready", map[string]string{ 177 | "sessionid": t.sessionId, 178 | "version": strconv.FormatUint(uint64(t.Version), 10), 179 | "ready": fmt.Sprint(ready), 180 | }) 181 | } 182 | 183 | func (t *Trade) Confirm() (*Status, error) { 184 | return t.postWithStatus(t.baseUrl+"confirm", map[string]string{ 185 | "sessionid": t.sessionId, 186 | "version": strconv.FormatUint(uint64(t.Version), 10), 187 | }) 188 | } 189 | 190 | func (t *Trade) Cancel() (*Status, error) { 191 | return t.postWithStatus(t.baseUrl+"cancel", map[string]string{ 192 | "sessionid": t.sessionId, 193 | }) 194 | } 195 | 196 | func isSuccess(v interface{}) bool { 197 | if m, ok := v.(map[string]interface{}); ok { 198 | return m["success"] == true 199 | } 200 | return false 201 | } 202 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "crypto/sha1" 5 | "sync/atomic" 6 | "time" 7 | 8 | "github.com/Philipp15b/go-steam/v3/protocol" 9 | "github.com/Philipp15b/go-steam/v3/protocol/protobuf" 10 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 11 | "github.com/Philipp15b/go-steam/v3/steamid" 12 | "google.golang.org/protobuf/proto" 13 | ) 14 | 15 | type Auth struct { 16 | client *Client 17 | details *LogOnDetails 18 | } 19 | 20 | type SentryHash []byte 21 | 22 | type LogOnDetails struct { 23 | Username string 24 | 25 | // If logging into an account without a login key, the account's password. 26 | Password string 27 | 28 | // If you have a Steam Guard email code, you can provide it here. 29 | AuthCode string 30 | 31 | // If you have a Steam Guard mobile two-factor authentication code, you can provide it here. 32 | TwoFactorCode string 33 | SentryFileHash SentryHash 34 | LoginKey string 35 | 36 | // true if you want to get a login key which can be used in lieu of 37 | // a password for subsequent logins. false or omitted otherwise. 38 | ShouldRememberPassword bool 39 | } 40 | 41 | // Log on with the given details. You must always specify username and 42 | // password OR username and loginkey. For the first login, don't set an authcode or a hash and you'll 43 | // receive an error (EResult_AccountLogonDenied) 44 | // and Steam will send you an authcode. Then you have to login again, this time with the authcode. 45 | // Shortly after logging in, you'll receive a MachineAuthUpdateEvent with a hash which allows 46 | // you to login without using an authcode in the future. 47 | // 48 | // If you don't use Steam Guard, username and password are enough. 49 | // 50 | // After the event EMsg_ClientNewLoginKey is received you can use the LoginKey 51 | // to login instead of using the password. 52 | func (a *Auth) LogOn(details *LogOnDetails) { 53 | if details.Username == "" { 54 | panic("Username must be set!") 55 | } 56 | if details.Password == "" && details.LoginKey == "" { 57 | panic("Password or LoginKey must be set!") 58 | } 59 | 60 | logon := new(protobuf.CMsgClientLogon) 61 | logon.AccountName = &details.Username 62 | logon.Password = &details.Password 63 | if details.AuthCode != "" { 64 | logon.AuthCode = proto.String(details.AuthCode) 65 | } 66 | if details.TwoFactorCode != "" { 67 | logon.TwoFactorCode = proto.String(details.TwoFactorCode) 68 | } 69 | logon.ClientLanguage = proto.String("english") 70 | logon.ProtocolVersion = proto.Uint32(steamlang.MsgClientLogon_CurrentProtocol) 71 | logon.ShaSentryfile = details.SentryFileHash 72 | if details.LoginKey != "" { 73 | logon.LoginKey = proto.String(details.LoginKey) 74 | } 75 | if details.ShouldRememberPassword { 76 | logon.ShouldRememberPassword = proto.Bool(details.ShouldRememberPassword) 77 | } 78 | 79 | atomic.StoreUint64(&a.client.steamId, uint64(steamid.NewIdAdv(0, 1, int32(steamlang.EUniverse_Public), int32(steamlang.EAccountType_Individual)))) 80 | 81 | a.client.Write(protocol.NewClientMsgProtobuf(steamlang.EMsg_ClientLogon, logon)) 82 | } 83 | 84 | func (a *Auth) HandlePacket(packet *protocol.Packet) { 85 | switch packet.EMsg { 86 | case steamlang.EMsg_ClientLogOnResponse: 87 | a.handleLogOnResponse(packet) 88 | case steamlang.EMsg_ClientNewLoginKey: 89 | a.handleLoginKey(packet) 90 | case steamlang.EMsg_ClientSessionToken: 91 | case steamlang.EMsg_ClientLoggedOff: 92 | a.handleLoggedOff(packet) 93 | case steamlang.EMsg_ClientUpdateMachineAuth: 94 | a.handleUpdateMachineAuth(packet) 95 | case steamlang.EMsg_ClientAccountInfo: 96 | a.handleAccountInfo(packet) 97 | } 98 | } 99 | 100 | func (a *Auth) handleLogOnResponse(packet *protocol.Packet) { 101 | if !packet.IsProto { 102 | a.client.Fatalf("Got non-proto logon response!") 103 | return 104 | } 105 | 106 | body := new(protobuf.CMsgClientLogonResponse) 107 | msg := packet.ReadProtoMsg(body) 108 | 109 | result := steamlang.EResult(body.GetEresult()) 110 | if result == steamlang.EResult_OK { 111 | atomic.StoreInt32(&a.client.sessionId, msg.Header.Proto.GetClientSessionid()) 112 | atomic.StoreUint64(&a.client.steamId, msg.Header.Proto.GetSteamid()) 113 | a.client.Web.webLoginKey = *body.WebapiAuthenticateUserNonce 114 | 115 | go a.client.heartbeatLoop(time.Duration(body.GetOutOfGameHeartbeatSeconds())) 116 | 117 | a.client.Emit(&LoggedOnEvent{ 118 | Result: steamlang.EResult(body.GetEresult()), 119 | ExtendedResult: steamlang.EResult(body.GetEresultExtended()), 120 | OutOfGameSecsPerHeartbeat: body.GetOutOfGameHeartbeatSeconds(), 121 | InGameSecsPerHeartbeat: body.GetInGameHeartbeatSeconds(), 122 | PublicIp: body.GetDeprecatedPublicIp(), 123 | ServerTime: body.GetRtime32ServerTime(), 124 | AccountFlags: steamlang.EAccountFlags(body.GetAccountFlags()), 125 | ClientSteamId: steamid.SteamId(body.GetClientSuppliedSteamid()), 126 | EmailDomain: body.GetEmailDomain(), 127 | CellId: body.GetCellId(), 128 | CellIdPingThreshold: body.GetCellIdPingThreshold(), 129 | Steam2Ticket: body.GetSteam2Ticket(), 130 | UsePics: body.GetDeprecatedUsePics(), 131 | WebApiUserNonce: body.GetWebapiAuthenticateUserNonce(), 132 | IpCountryCode: body.GetIpCountryCode(), 133 | VanityUrl: body.GetVanityUrl(), 134 | NumLoginFailuresToMigrate: body.GetCountLoginfailuresToMigrate(), 135 | NumDisconnectsToMigrate: body.GetCountDisconnectsToMigrate(), 136 | }) 137 | } else if result == steamlang.EResult_Fail || result == steamlang.EResult_ServiceUnavailable || result == steamlang.EResult_TryAnotherCM { 138 | // some error on Steam's side, we'll get an EOF later 139 | } else { 140 | a.client.Emit(&LogOnFailedEvent{ 141 | Result: steamlang.EResult(body.GetEresult()), 142 | }) 143 | a.client.Disconnect() 144 | } 145 | } 146 | 147 | func (a *Auth) handleLoginKey(packet *protocol.Packet) { 148 | body := new(protobuf.CMsgClientNewLoginKey) 149 | packet.ReadProtoMsg(body) 150 | a.client.Write(protocol.NewClientMsgProtobuf(steamlang.EMsg_ClientNewLoginKeyAccepted, &protobuf.CMsgClientNewLoginKeyAccepted{ 151 | UniqueId: proto.Uint32(body.GetUniqueId()), 152 | })) 153 | a.client.Emit(&LoginKeyEvent{ 154 | UniqueId: body.GetUniqueId(), 155 | LoginKey: body.GetLoginKey(), 156 | }) 157 | } 158 | 159 | func (a *Auth) handleLoggedOff(packet *protocol.Packet) { 160 | result := steamlang.EResult_Invalid 161 | if packet.IsProto { 162 | body := new(protobuf.CMsgClientLoggedOff) 163 | packet.ReadProtoMsg(body) 164 | result = steamlang.EResult(body.GetEresult()) 165 | } else { 166 | body := new(steamlang.MsgClientLoggedOff) 167 | packet.ReadClientMsg(body) 168 | result = body.Result 169 | } 170 | a.client.Emit(&LoggedOffEvent{Result: result}) 171 | } 172 | 173 | func (a *Auth) handleUpdateMachineAuth(packet *protocol.Packet) { 174 | body := new(protobuf.CMsgClientUpdateMachineAuth) 175 | packet.ReadProtoMsg(body) 176 | hash := sha1.New() 177 | hash.Write(packet.Data) 178 | sha := hash.Sum(nil) 179 | 180 | msg := protocol.NewClientMsgProtobuf(steamlang.EMsg_ClientUpdateMachineAuthResponse, &protobuf.CMsgClientUpdateMachineAuthResponse{ 181 | ShaFile: sha, 182 | }) 183 | msg.SetTargetJobId(packet.SourceJobId) 184 | a.client.Write(msg) 185 | 186 | a.client.Emit(&MachineAuthUpdateEvent{sha}) 187 | } 188 | 189 | func (a *Auth) handleAccountInfo(packet *protocol.Packet) { 190 | body := new(protobuf.CMsgClientAccountInfo) 191 | packet.ReadProtoMsg(body) 192 | a.client.Emit(&AccountInfoEvent{ 193 | PersonaName: body.GetPersonaName(), 194 | Country: body.GetIpCountry(), 195 | CountAuthedComputers: body.GetCountAuthedComputers(), 196 | AccountFlags: steamlang.EAccountFlags(body.GetAccountFlags()), 197 | FacebookId: body.GetFacebookId(), 198 | FacebookName: body.GetFacebookName(), 199 | }) 200 | } 201 | -------------------------------------------------------------------------------- /protocol/protobuf/app_ticket.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.17.1 5 | // source: encrypted_app_ticket.proto 6 | 7 | package protobuf 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type EncryptedAppTicket struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | TicketVersionNo *uint32 `protobuf:"varint,1,opt,name=ticket_version_no,json=ticketVersionNo" json:"ticket_version_no,omitempty"` 29 | CrcEncryptedticket *uint32 `protobuf:"varint,2,opt,name=crc_encryptedticket,json=crcEncryptedticket" json:"crc_encryptedticket,omitempty"` 30 | CbEncrypteduserdata *uint32 `protobuf:"varint,3,opt,name=cb_encrypteduserdata,json=cbEncrypteduserdata" json:"cb_encrypteduserdata,omitempty"` 31 | CbEncryptedAppownershipticket *uint32 `protobuf:"varint,4,opt,name=cb_encrypted_appownershipticket,json=cbEncryptedAppownershipticket" json:"cb_encrypted_appownershipticket,omitempty"` 32 | EncryptedTicket []byte `protobuf:"bytes,5,opt,name=encrypted_ticket,json=encryptedTicket" json:"encrypted_ticket,omitempty"` 33 | } 34 | 35 | func (x *EncryptedAppTicket) Reset() { 36 | *x = EncryptedAppTicket{} 37 | if protoimpl.UnsafeEnabled { 38 | mi := &file_encrypted_app_ticket_proto_msgTypes[0] 39 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 40 | ms.StoreMessageInfo(mi) 41 | } 42 | } 43 | 44 | func (x *EncryptedAppTicket) String() string { 45 | return protoimpl.X.MessageStringOf(x) 46 | } 47 | 48 | func (*EncryptedAppTicket) ProtoMessage() {} 49 | 50 | func (x *EncryptedAppTicket) ProtoReflect() protoreflect.Message { 51 | mi := &file_encrypted_app_ticket_proto_msgTypes[0] 52 | if protoimpl.UnsafeEnabled && x != nil { 53 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 54 | if ms.LoadMessageInfo() == nil { 55 | ms.StoreMessageInfo(mi) 56 | } 57 | return ms 58 | } 59 | return mi.MessageOf(x) 60 | } 61 | 62 | // Deprecated: Use EncryptedAppTicket.ProtoReflect.Descriptor instead. 63 | func (*EncryptedAppTicket) Descriptor() ([]byte, []int) { 64 | return file_encrypted_app_ticket_proto_rawDescGZIP(), []int{0} 65 | } 66 | 67 | func (x *EncryptedAppTicket) GetTicketVersionNo() uint32 { 68 | if x != nil && x.TicketVersionNo != nil { 69 | return *x.TicketVersionNo 70 | } 71 | return 0 72 | } 73 | 74 | func (x *EncryptedAppTicket) GetCrcEncryptedticket() uint32 { 75 | if x != nil && x.CrcEncryptedticket != nil { 76 | return *x.CrcEncryptedticket 77 | } 78 | return 0 79 | } 80 | 81 | func (x *EncryptedAppTicket) GetCbEncrypteduserdata() uint32 { 82 | if x != nil && x.CbEncrypteduserdata != nil { 83 | return *x.CbEncrypteduserdata 84 | } 85 | return 0 86 | } 87 | 88 | func (x *EncryptedAppTicket) GetCbEncryptedAppownershipticket() uint32 { 89 | if x != nil && x.CbEncryptedAppownershipticket != nil { 90 | return *x.CbEncryptedAppownershipticket 91 | } 92 | return 0 93 | } 94 | 95 | func (x *EncryptedAppTicket) GetEncryptedTicket() []byte { 96 | if x != nil { 97 | return x.EncryptedTicket 98 | } 99 | return nil 100 | } 101 | 102 | var File_encrypted_app_ticket_proto protoreflect.FileDescriptor 103 | 104 | var file_encrypted_app_ticket_proto_rawDesc = []byte{ 105 | 0x0a, 0x1a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x5f, 106 | 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x97, 0x02, 0x0a, 107 | 0x12, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x41, 0x70, 0x70, 0x54, 0x69, 0x63, 108 | 0x6b, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x76, 0x65, 109 | 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 110 | 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x12, 111 | 0x2f, 0x0a, 0x13, 0x63, 0x72, 0x63, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 112 | 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x63, 0x72, 113 | 0x63, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 114 | 0x12, 0x31, 0x0a, 0x14, 0x63, 0x62, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 115 | 0x75, 0x73, 0x65, 0x72, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 116 | 0x63, 0x62, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x75, 0x73, 0x65, 0x72, 0x64, 117 | 0x61, 0x74, 0x61, 0x12, 0x46, 0x0a, 0x1f, 0x63, 0x62, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 118 | 0x74, 0x65, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 119 | 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1d, 0x63, 0x62, 120 | 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x41, 0x70, 0x70, 0x6f, 0x77, 0x6e, 0x65, 121 | 0x72, 0x73, 0x68, 0x69, 0x70, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x65, 122 | 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 123 | 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 124 | 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x42, 0x05, 0x48, 0x01, 0x80, 0x01, 0x00, 125 | } 126 | 127 | var ( 128 | file_encrypted_app_ticket_proto_rawDescOnce sync.Once 129 | file_encrypted_app_ticket_proto_rawDescData = file_encrypted_app_ticket_proto_rawDesc 130 | ) 131 | 132 | func file_encrypted_app_ticket_proto_rawDescGZIP() []byte { 133 | file_encrypted_app_ticket_proto_rawDescOnce.Do(func() { 134 | file_encrypted_app_ticket_proto_rawDescData = protoimpl.X.CompressGZIP(file_encrypted_app_ticket_proto_rawDescData) 135 | }) 136 | return file_encrypted_app_ticket_proto_rawDescData 137 | } 138 | 139 | var file_encrypted_app_ticket_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 140 | var file_encrypted_app_ticket_proto_goTypes = []interface{}{ 141 | (*EncryptedAppTicket)(nil), // 0: EncryptedAppTicket 142 | } 143 | var file_encrypted_app_ticket_proto_depIdxs = []int32{ 144 | 0, // [0:0] is the sub-list for method output_type 145 | 0, // [0:0] is the sub-list for method input_type 146 | 0, // [0:0] is the sub-list for extension type_name 147 | 0, // [0:0] is the sub-list for extension extendee 148 | 0, // [0:0] is the sub-list for field type_name 149 | } 150 | 151 | func init() { file_encrypted_app_ticket_proto_init() } 152 | func file_encrypted_app_ticket_proto_init() { 153 | if File_encrypted_app_ticket_proto != nil { 154 | return 155 | } 156 | if !protoimpl.UnsafeEnabled { 157 | file_encrypted_app_ticket_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 158 | switch v := v.(*EncryptedAppTicket); i { 159 | case 0: 160 | return &v.state 161 | case 1: 162 | return &v.sizeCache 163 | case 2: 164 | return &v.unknownFields 165 | default: 166 | return nil 167 | } 168 | } 169 | } 170 | type x struct{} 171 | out := protoimpl.TypeBuilder{ 172 | File: protoimpl.DescBuilder{ 173 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 174 | RawDescriptor: file_encrypted_app_ticket_proto_rawDesc, 175 | NumEnums: 0, 176 | NumMessages: 1, 177 | NumExtensions: 0, 178 | NumServices: 0, 179 | }, 180 | GoTypes: file_encrypted_app_ticket_proto_goTypes, 181 | DependencyIndexes: file_encrypted_app_ticket_proto_depIdxs, 182 | MessageInfos: file_encrypted_app_ticket_proto_msgTypes, 183 | }.Build() 184 | File_encrypted_app_ticket_proto = out.File 185 | file_encrypted_app_ticket_proto_rawDesc = nil 186 | file_encrypted_app_ticket_proto_goTypes = nil 187 | file_encrypted_app_ticket_proto_depIdxs = nil 188 | } 189 | -------------------------------------------------------------------------------- /csgo/protocol/protobuf/enginegc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.17.1 5 | // source: engine_gcmessages.proto 6 | 7 | package protobuf 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | _ "google.golang.org/protobuf/types/descriptorpb" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | type CEngineGotvSyncPacket struct { 25 | state protoimpl.MessageState 26 | sizeCache protoimpl.SizeCache 27 | unknownFields protoimpl.UnknownFields 28 | 29 | MatchId *uint64 `protobuf:"varint,1,opt,name=match_id,json=matchId" json:"match_id,omitempty"` 30 | InstanceId *uint32 `protobuf:"varint,2,opt,name=instance_id,json=instanceId" json:"instance_id,omitempty"` 31 | Signupfragment *uint32 `protobuf:"varint,3,opt,name=signupfragment" json:"signupfragment,omitempty"` 32 | Currentfragment *uint32 `protobuf:"varint,4,opt,name=currentfragment" json:"currentfragment,omitempty"` 33 | Tickrate *float32 `protobuf:"fixed32,5,opt,name=tickrate" json:"tickrate,omitempty"` 34 | Tick *uint32 `protobuf:"varint,6,opt,name=tick" json:"tick,omitempty"` 35 | Rtdelay *float32 `protobuf:"fixed32,8,opt,name=rtdelay" json:"rtdelay,omitempty"` 36 | Rcvage *float32 `protobuf:"fixed32,9,opt,name=rcvage" json:"rcvage,omitempty"` 37 | KeyframeInterval *float32 `protobuf:"fixed32,10,opt,name=keyframe_interval,json=keyframeInterval" json:"keyframe_interval,omitempty"` 38 | Cdndelay *uint32 `protobuf:"varint,11,opt,name=cdndelay" json:"cdndelay,omitempty"` 39 | } 40 | 41 | func (x *CEngineGotvSyncPacket) Reset() { 42 | *x = CEngineGotvSyncPacket{} 43 | if protoimpl.UnsafeEnabled { 44 | mi := &file_engine_gcmessages_proto_msgTypes[0] 45 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 46 | ms.StoreMessageInfo(mi) 47 | } 48 | } 49 | 50 | func (x *CEngineGotvSyncPacket) String() string { 51 | return protoimpl.X.MessageStringOf(x) 52 | } 53 | 54 | func (*CEngineGotvSyncPacket) ProtoMessage() {} 55 | 56 | func (x *CEngineGotvSyncPacket) ProtoReflect() protoreflect.Message { 57 | mi := &file_engine_gcmessages_proto_msgTypes[0] 58 | if protoimpl.UnsafeEnabled && x != nil { 59 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 60 | if ms.LoadMessageInfo() == nil { 61 | ms.StoreMessageInfo(mi) 62 | } 63 | return ms 64 | } 65 | return mi.MessageOf(x) 66 | } 67 | 68 | // Deprecated: Use CEngineGotvSyncPacket.ProtoReflect.Descriptor instead. 69 | func (*CEngineGotvSyncPacket) Descriptor() ([]byte, []int) { 70 | return file_engine_gcmessages_proto_rawDescGZIP(), []int{0} 71 | } 72 | 73 | func (x *CEngineGotvSyncPacket) GetMatchId() uint64 { 74 | if x != nil && x.MatchId != nil { 75 | return *x.MatchId 76 | } 77 | return 0 78 | } 79 | 80 | func (x *CEngineGotvSyncPacket) GetInstanceId() uint32 { 81 | if x != nil && x.InstanceId != nil { 82 | return *x.InstanceId 83 | } 84 | return 0 85 | } 86 | 87 | func (x *CEngineGotvSyncPacket) GetSignupfragment() uint32 { 88 | if x != nil && x.Signupfragment != nil { 89 | return *x.Signupfragment 90 | } 91 | return 0 92 | } 93 | 94 | func (x *CEngineGotvSyncPacket) GetCurrentfragment() uint32 { 95 | if x != nil && x.Currentfragment != nil { 96 | return *x.Currentfragment 97 | } 98 | return 0 99 | } 100 | 101 | func (x *CEngineGotvSyncPacket) GetTickrate() float32 { 102 | if x != nil && x.Tickrate != nil { 103 | return *x.Tickrate 104 | } 105 | return 0 106 | } 107 | 108 | func (x *CEngineGotvSyncPacket) GetTick() uint32 { 109 | if x != nil && x.Tick != nil { 110 | return *x.Tick 111 | } 112 | return 0 113 | } 114 | 115 | func (x *CEngineGotvSyncPacket) GetRtdelay() float32 { 116 | if x != nil && x.Rtdelay != nil { 117 | return *x.Rtdelay 118 | } 119 | return 0 120 | } 121 | 122 | func (x *CEngineGotvSyncPacket) GetRcvage() float32 { 123 | if x != nil && x.Rcvage != nil { 124 | return *x.Rcvage 125 | } 126 | return 0 127 | } 128 | 129 | func (x *CEngineGotvSyncPacket) GetKeyframeInterval() float32 { 130 | if x != nil && x.KeyframeInterval != nil { 131 | return *x.KeyframeInterval 132 | } 133 | return 0 134 | } 135 | 136 | func (x *CEngineGotvSyncPacket) GetCdndelay() uint32 { 137 | if x != nil && x.Cdndelay != nil { 138 | return *x.Cdndelay 139 | } 140 | return 0 141 | } 142 | 143 | var File_engine_gcmessages_proto protoreflect.FileDescriptor 144 | 145 | var file_engine_gcmessages_proto_rawDesc = []byte{ 146 | 0x0a, 0x17, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x67, 0x63, 0x6d, 0x65, 0x73, 0x73, 0x61, 147 | 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 148 | 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 149 | 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd0, 0x02, 0x0a, 0x15, 150 | 0x43, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x47, 0x6f, 0x74, 0x76, 0x53, 0x79, 0x6e, 0x63, 0x50, 151 | 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x69, 152 | 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x49, 0x64, 153 | 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 154 | 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 155 | 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x75, 0x70, 0x66, 0x72, 0x61, 0x67, 0x6d, 156 | 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x75, 157 | 0x70, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x75, 0x72, 158 | 0x72, 0x65, 0x6e, 0x74, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 159 | 0x28, 0x0d, 0x52, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x66, 0x72, 0x61, 0x67, 0x6d, 160 | 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x69, 0x63, 0x6b, 0x72, 0x61, 0x74, 0x65, 0x18, 161 | 0x05, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x74, 0x69, 0x63, 0x6b, 0x72, 0x61, 0x74, 0x65, 0x12, 162 | 0x12, 0x0a, 0x04, 0x74, 0x69, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 163 | 0x69, 0x63, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x08, 164 | 0x20, 0x01, 0x28, 0x02, 0x52, 0x07, 0x72, 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x16, 0x0a, 165 | 0x06, 0x72, 0x63, 0x76, 0x61, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x02, 0x52, 0x06, 0x72, 166 | 0x63, 0x76, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x6b, 0x65, 0x79, 0x66, 0x72, 0x61, 0x6d, 167 | 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x02, 168 | 0x52, 0x10, 0x6b, 0x65, 0x79, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 169 | 0x61, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x64, 0x6e, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x0b, 170 | 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x63, 0x64, 0x6e, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x42, 0x03, 171 | 0x80, 0x01, 0x00, 172 | } 173 | 174 | var ( 175 | file_engine_gcmessages_proto_rawDescOnce sync.Once 176 | file_engine_gcmessages_proto_rawDescData = file_engine_gcmessages_proto_rawDesc 177 | ) 178 | 179 | func file_engine_gcmessages_proto_rawDescGZIP() []byte { 180 | file_engine_gcmessages_proto_rawDescOnce.Do(func() { 181 | file_engine_gcmessages_proto_rawDescData = protoimpl.X.CompressGZIP(file_engine_gcmessages_proto_rawDescData) 182 | }) 183 | return file_engine_gcmessages_proto_rawDescData 184 | } 185 | 186 | var file_engine_gcmessages_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 187 | var file_engine_gcmessages_proto_goTypes = []interface{}{ 188 | (*CEngineGotvSyncPacket)(nil), // 0: CEngineGotvSyncPacket 189 | } 190 | var file_engine_gcmessages_proto_depIdxs = []int32{ 191 | 0, // [0:0] is the sub-list for method output_type 192 | 0, // [0:0] is the sub-list for method input_type 193 | 0, // [0:0] is the sub-list for extension type_name 194 | 0, // [0:0] is the sub-list for extension extendee 195 | 0, // [0:0] is the sub-list for field type_name 196 | } 197 | 198 | func init() { file_engine_gcmessages_proto_init() } 199 | func file_engine_gcmessages_proto_init() { 200 | if File_engine_gcmessages_proto != nil { 201 | return 202 | } 203 | if !protoimpl.UnsafeEnabled { 204 | file_engine_gcmessages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 205 | switch v := v.(*CEngineGotvSyncPacket); i { 206 | case 0: 207 | return &v.state 208 | case 1: 209 | return &v.sizeCache 210 | case 2: 211 | return &v.unknownFields 212 | default: 213 | return nil 214 | } 215 | } 216 | } 217 | type x struct{} 218 | out := protoimpl.TypeBuilder{ 219 | File: protoimpl.DescBuilder{ 220 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 221 | RawDescriptor: file_engine_gcmessages_proto_rawDesc, 222 | NumEnums: 0, 223 | NumMessages: 1, 224 | NumExtensions: 0, 225 | NumServices: 0, 226 | }, 227 | GoTypes: file_engine_gcmessages_proto_goTypes, 228 | DependencyIndexes: file_engine_gcmessages_proto_depIdxs, 229 | MessageInfos: file_engine_gcmessages_proto_msgTypes, 230 | }.Build() 231 | File_engine_gcmessages_proto = out.File 232 | file_engine_gcmessages_proto_rawDesc = nil 233 | file_engine_gcmessages_proto_goTypes = nil 234 | file_engine_gcmessages_proto_depIdxs = nil 235 | } 236 | -------------------------------------------------------------------------------- /generator/generator.go: -------------------------------------------------------------------------------- 1 | /* 2 | This program generates the protobuf and SteamLanguage files from the SteamKit data. 3 | */ 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "go/ast" 10 | "go/parser" 11 | "go/token" 12 | "io" 13 | "io/ioutil" 14 | "os" 15 | "os/exec" 16 | "path/filepath" 17 | "regexp" 18 | "strings" 19 | ) 20 | 21 | var printCommands = false 22 | 23 | func main() { 24 | args := strings.Join(os.Args[1:], " ") 25 | 26 | found := false 27 | if strings.Contains(args, "clean") { 28 | clean() 29 | found = true 30 | } 31 | if strings.Contains(args, "steamlang") { 32 | buildSteamLanguage() 33 | found = true 34 | } 35 | if strings.Contains(args, "proto") { 36 | buildProto() 37 | found = true 38 | } 39 | 40 | if !found { 41 | os.Stderr.WriteString("Invalid target!\nAvailable targets: clean, proto, steamlang\n") 42 | os.Exit(1) 43 | } 44 | } 45 | 46 | func clean() { 47 | print("# Cleaning") 48 | cleanGlob("../protocol/**/*.pb.go") 49 | cleanGlob("../tf2/protocol/**/*.pb.go") 50 | cleanGlob("../dota/protocol/**/*.pb.go") 51 | cleanGlob("../csgo/protocol/**/*.pb.go") 52 | 53 | os.Remove("../protocol/steamlang/enums.go") 54 | os.Remove("../protocol/steamlang/messages.go") 55 | } 56 | 57 | func cleanGlob(pattern string) { 58 | protos, _ := filepath.Glob(pattern) 59 | for _, proto := range protos { 60 | err := os.Remove(proto) 61 | if err != nil { 62 | panic(err) 63 | } 64 | } 65 | } 66 | 67 | func buildSteamLanguage() { 68 | print("# Building Steam Language") 69 | execute("dotnet", []string{"run", "-c", "release", "-p", "./GoSteamLanguageGenerator", "./SteamKit", "../protocol/steamlang"}) 70 | execute("gofmt", []string{"-w", "../protocol/steamlang/enums.go", "../protocol/steamlang/messages.go"}) 71 | } 72 | 73 | func buildProto() { 74 | print("# Building Protobufs") 75 | 76 | buildProtoMap("steam", clientProtoFiles, "../protocol/protobuf") 77 | buildProtoMap("tf2", tf2ProtoFiles, "../tf2/protocol/protobuf") 78 | buildProtoMap("dota2", dotaProtoFiles, "../dota/protocol/protobuf") 79 | buildProtoMap("csgo", csgoProtoFiles, "../csgo/protocol/protobuf") 80 | } 81 | 82 | func buildProtoMap(srcSubdir string, files map[string]string, outDir string) { 83 | _ = os.MkdirAll(outDir, os.ModePerm) 84 | 85 | var opt []string 86 | 87 | opt = append(opt, "--go_opt=Mgoogle/protobuf/descriptor.proto=google/protobuf/descriptor.proto") 88 | 89 | for proto := range files { 90 | opt = append(opt, "--go_opt=Msteammessages.proto=Protobufs/"+srcSubdir+"/steammessages.proto") 91 | opt = append(opt, "--go_opt=M"+proto+"=Protobufs/"+srcSubdir+"/"+proto) 92 | } 93 | 94 | if srcSubdir == "dota2" { 95 | opt = append(opt, "--go_opt=Mecon_shared_enums.proto=Protobufs/"+srcSubdir+"/econ_shared_enums.proto") 96 | } 97 | 98 | for proto, out := range files { 99 | full := filepath.Join(outDir, out) 100 | print("# Building: " + full) 101 | compileProto("Protobufs", srcSubdir, proto, full, opt) 102 | fixProto(outDir, full) 103 | } 104 | } 105 | 106 | // Maps the proto files to their target files. 107 | // See `SteamKit/Resources/Protobufs/steamclient/generate-base.bat` for reference. 108 | var clientProtoFiles = map[string]string{ 109 | "steammessages_base.proto": "base.pb.go", 110 | "encrypted_app_ticket.proto": "app_ticket.pb.go", 111 | 112 | "steammessages_clientserver.proto": "client_server.pb.go", 113 | "steammessages_clientserver_2.proto": "client_server_2.pb.go", 114 | "steammessages_clientserver_friends.proto": "client_server_friends.pb.go", 115 | "steammessages_clientserver_login.proto": "client_server_login.pb.go", 116 | "steammessages_sitelicenseclient.proto": "client_site_license.pb.go", 117 | 118 | "content_manifest.proto": "content_manifest.pb.go", 119 | 120 | "steammessages_unified_base.steamclient.proto": "unified/base.pb.go", 121 | "steammessages_cloud.steamclient.proto": "unified/cloud.pb.go", 122 | "steammessages_credentials.steamclient.proto": "unified/credentials.pb.go", 123 | "steammessages_deviceauth.steamclient.proto": "unified/deviceauth.pb.go", 124 | "steammessages_gamenotifications.steamclient.proto": "unified/gamenotifications.pb.go", 125 | "steammessages_offline.steamclient.proto": "unified/offline.pb.go", 126 | "steammessages_parental.steamclient.proto": "unified/parental.pb.go", 127 | "steammessages_partnerapps.steamclient.proto": "unified/partnerapps.pb.go", 128 | "steammessages_player.steamclient.proto": "unified/player.pb.go", 129 | "steammessages_publishedfile.steamclient.proto": "unified/publishedfile.pb.go", 130 | } 131 | 132 | var tf2ProtoFiles = map[string]string{ 133 | "base_gcmessages.proto": "base.pb.go", 134 | "econ_gcmessages.proto": "econ.pb.go", 135 | "gcsdk_gcmessages.proto": "gcsdk.pb.go", 136 | "tf_gcmessages.proto": "tf.pb.go", 137 | "gcsystemmsgs.proto": "system.pb.go", 138 | } 139 | 140 | var dotaProtoFiles = map[string]string{ 141 | "base_gcmessages.proto": "base.pb.go", 142 | "econ_shared_enums.proto": "econ_shared_enum.pb.go", 143 | "econ_gcmessages.proto": "econ.pb.go", 144 | "gcsdk_gcmessages.proto": "gcsdk.pb.go", 145 | "gcsystemmsgs.proto": "system.pb.go", 146 | "steammessages.proto": "steam.pb.go", 147 | } 148 | 149 | var csgoProtoFiles = map[string]string{ 150 | "base_gcmessages.proto": "base.pb.go", 151 | "cstrike15_gcmessages.proto": "cstrike15gc.pb.go", 152 | "cstrike15_usermessages.proto": "cstrike15user.pb.go", 153 | "econ_gcmessages.proto": "econ.pb.go", 154 | "engine_gcmessages.proto": "enginegc.pb.go", 155 | "fatdemo.proto": "fatdemo.pb.go", 156 | "gcsdk_gcmessages.proto": "gcsdk.pb.go", 157 | "gcsystemmsgs.proto": "system.pb.go", 158 | "netmessages.proto": "net.pb.go", 159 | "network_connection.proto": "networkconnection.pb.go", 160 | "steammessages.proto": "steam.pb.go", 161 | "uifontfile_format.proto": "uifontfile.pb.go", 162 | } 163 | 164 | func compileProto(srcBase, srcSubdir, proto, target string, opt []string) { 165 | outDir, _ := filepath.Split(target) 166 | err := os.MkdirAll(outDir, os.ModePerm) 167 | if err != nil { 168 | panic(err) 169 | } 170 | 171 | args := []string{ 172 | "-I=" + srcBase, 173 | "-I=" + srcBase + "/google", 174 | "-I=" + srcBase + "/" + srcSubdir, 175 | "--go_out=" + outDir, 176 | } 177 | args = append(args, opt...) 178 | args = append(args, proto) 179 | 180 | execute("protoc", args) 181 | 182 | dir := outDir + "Protobufs/" + srcSubdir + "/" + proto 183 | file := filepath.Join(dir, strings.Replace(proto, ".proto", ".pb.go", 1)) 184 | 185 | err = forceRename(file, target) 186 | if err != nil { 187 | panic(err) 188 | } 189 | 190 | // clean stuff 191 | if err := os.RemoveAll(outDir + "/Protobufs"); err != nil { 192 | panic(err) 193 | } 194 | } 195 | 196 | func forceRename(from, to string) error { 197 | if from != to { 198 | os.Remove(to) 199 | } 200 | return os.Rename(from, to) 201 | } 202 | 203 | var pkgRegex = regexp.MustCompile(`(package \w+)`) 204 | var unusedImportCommentRegex = regexp.MustCompile("// discarding unused import .*\n") 205 | var fileDescriptorVarRegex = regexp.MustCompile(`fileDescriptor\d+`) 206 | 207 | func fixProto(outDir, path string) { 208 | // goprotobuf is really bad at dependencies, so we must fix them manually... 209 | // It tries to load each dependency of a file as a seperate package (but in a very, very wrong way). 210 | // Because we want some files in the same package, we'll remove those imports to local files. 211 | 212 | file, err := ioutil.ReadFile(path) 213 | if err != nil { 214 | panic(err) 215 | } 216 | 217 | fset := token.NewFileSet() 218 | f, err := parser.ParseFile(fset, path, file, parser.ImportsOnly) 219 | if err != nil { 220 | panic("Error parsing " + path + ": " + err.Error()) 221 | } 222 | 223 | importsToRemove := make([]*ast.ImportSpec, 0) 224 | for _, i := range f.Imports { 225 | if strings.Contains(i.Path.Value, "google/protobuf/descriptor.proto") { 226 | continue 227 | } 228 | // We remove all local imports 229 | if strings.Contains(i.Path.Value, ".proto") { 230 | importsToRemove = append(importsToRemove, i) 231 | } 232 | } 233 | 234 | for _, itr := range importsToRemove { 235 | // remove the package name from all types 236 | file = bytes.Replace(file, []byte(itr.Name.Name+"."), []byte{}, -1) 237 | // and remove the import itself 238 | file = bytes.Replace(file, []byte(fmt.Sprintf("%v %v", itr.Name.Name, itr.Path.Value)), []byte{}, -1) 239 | } 240 | 241 | // remove warnings 242 | file = unusedImportCommentRegex.ReplaceAllLiteral(file, []byte{}) 243 | 244 | // fix the package name 245 | file = pkgRegex.ReplaceAll(file, []byte("package "+inferPackageName(path))) 246 | 247 | // fix the google dependency; 248 | // we just reuse the one from protoc-gen-go 249 | file = bytes.Replace(file, []byte("google/protobuf/descriptor.proto"), []byte("google.golang.org/protobuf/types/descriptorpb"), -1) 250 | 251 | // we need to prefix local variables created by protoc-gen-go so that they don't clash with others in the same package 252 | filename := strings.Split(filepath.Base(path), ".")[0] 253 | file = fileDescriptorVarRegex.ReplaceAllFunc(file, func(match []byte) []byte { 254 | return []byte(filename + "_" + string(match)) 255 | }) 256 | 257 | err = ioutil.WriteFile(path, file, os.ModePerm) 258 | if err != nil { 259 | panic(err) 260 | } 261 | } 262 | 263 | func inferPackageName(path string) string { 264 | pieces := strings.Split(path, string(filepath.Separator)) 265 | return pieces[len(pieces)-2] 266 | } 267 | 268 | func print(text string) { os.Stdout.WriteString(text + "\n") } 269 | 270 | func printerr(text string) { os.Stderr.WriteString(text + "\n") } 271 | 272 | // This writer appends a "> " after every newline so that the outpout appears quoted. 273 | type quotedWriter struct { 274 | w io.Writer 275 | started bool 276 | } 277 | 278 | func newQuotedWriter(w io.Writer) *quotedWriter { 279 | return "edWriter{w, false} 280 | } 281 | 282 | func (w *quotedWriter) Write(p []byte) (n int, err error) { 283 | if !w.started { 284 | _, err = w.w.Write([]byte("> ")) 285 | if err != nil { 286 | return n, err 287 | } 288 | w.started = true 289 | } 290 | 291 | for i, c := range p { 292 | if c == '\n' { 293 | nw, err := w.w.Write(p[n : i+1]) 294 | n += nw 295 | if err != nil { 296 | return n, err 297 | } 298 | 299 | _, err = w.w.Write([]byte("> ")) 300 | if err != nil { 301 | return n, err 302 | } 303 | } 304 | } 305 | if n != len(p) { 306 | nw, err := w.w.Write(p[n:len(p)]) 307 | n += nw 308 | return n, err 309 | } 310 | return 311 | } 312 | 313 | func execute(command string, args []string) { 314 | if printCommands { 315 | print(command + " " + strings.Join(args, " ")) 316 | } 317 | 318 | cmd := exec.Command(command, args...) 319 | cmd.Stdout = newQuotedWriter(os.Stdout) 320 | cmd.Stderr = newQuotedWriter(os.Stderr) 321 | 322 | err := cmd.Run() 323 | if err != nil { 324 | printerr(err.Error()) 325 | os.Exit(1) 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "crypto/rand" 7 | "encoding/binary" 8 | "fmt" 9 | "hash/crc32" 10 | "io/ioutil" 11 | "net" 12 | "sync" 13 | "sync/atomic" 14 | "time" 15 | 16 | "github.com/Philipp15b/go-steam/v3/cryptoutil" 17 | "github.com/Philipp15b/go-steam/v3/netutil" 18 | "github.com/Philipp15b/go-steam/v3/protocol" 19 | "github.com/Philipp15b/go-steam/v3/protocol/protobuf" 20 | "github.com/Philipp15b/go-steam/v3/protocol/steamlang" 21 | "github.com/Philipp15b/go-steam/v3/steamid" 22 | ) 23 | 24 | // Represents a client to the Steam network. 25 | // Always poll events from the channel returned by Events() or receiving messages will stop. 26 | // All access, unless otherwise noted, should be threadsafe. 27 | // 28 | // When a FatalErrorEvent is emitted, the connection is automatically closed. The same client can be used to reconnect. 29 | // Other errors don't have any effect. 30 | type Client struct { 31 | // these need to be 64 bit aligned for sync/atomic on 32bit 32 | sessionId int32 33 | _ uint32 34 | steamId uint64 35 | currentJobId uint64 36 | 37 | Auth *Auth 38 | Social *Social 39 | Web *Web 40 | Notifications *Notifications 41 | Trading *Trading 42 | GC *GameCoordinator 43 | 44 | events chan interface{} 45 | handlers []PacketHandler 46 | handlersMutex sync.RWMutex 47 | 48 | tempSessionKey []byte 49 | 50 | ConnectionTimeout time.Duration 51 | 52 | mutex sync.RWMutex // guarding conn and writeChan 53 | conn connection 54 | writeChan chan protocol.IMsg 55 | writeBuf *bytes.Buffer 56 | heartbeat *time.Ticker 57 | } 58 | 59 | type PacketHandler interface { 60 | HandlePacket(*protocol.Packet) 61 | } 62 | 63 | func NewClient() *Client { 64 | client := &Client{ 65 | events: make(chan interface{}, 3), 66 | writeBuf: new(bytes.Buffer), 67 | } 68 | 69 | client.Auth = &Auth{client: client} 70 | client.RegisterPacketHandler(client.Auth) 71 | 72 | client.Social = newSocial(client) 73 | client.RegisterPacketHandler(client.Social) 74 | 75 | client.Web = &Web{client: client} 76 | client.RegisterPacketHandler(client.Web) 77 | 78 | client.Notifications = newNotifications(client) 79 | client.RegisterPacketHandler(client.Notifications) 80 | 81 | client.Trading = &Trading{client: client} 82 | client.RegisterPacketHandler(client.Trading) 83 | 84 | client.GC = newGC(client) 85 | client.RegisterPacketHandler(client.GC) 86 | 87 | return client 88 | } 89 | 90 | // Get the event channel. By convention all events are pointers, except for errors. 91 | // It is never closed. 92 | func (c *Client) Events() <-chan interface{} { 93 | return c.events 94 | } 95 | 96 | func (c *Client) Emit(event interface{}) { 97 | c.events <- event 98 | } 99 | 100 | // Emits a FatalErrorEvent formatted with fmt.Errorf and disconnects. 101 | func (c *Client) Fatalf(format string, a ...interface{}) { 102 | c.Emit(FatalErrorEvent(fmt.Errorf(format, a...))) 103 | c.Disconnect() 104 | } 105 | 106 | // Emits an error formatted with fmt.Errorf. 107 | func (c *Client) Errorf(format string, a ...interface{}) { 108 | c.Emit(fmt.Errorf(format, a...)) 109 | } 110 | 111 | // Registers a PacketHandler that receives all incoming packets. 112 | func (c *Client) RegisterPacketHandler(handler PacketHandler) { 113 | c.handlersMutex.Lock() 114 | defer c.handlersMutex.Unlock() 115 | c.handlers = append(c.handlers, handler) 116 | } 117 | 118 | func (c *Client) GetNextJobId() protocol.JobId { 119 | return protocol.JobId(atomic.AddUint64(&c.currentJobId, 1)) 120 | } 121 | 122 | func (c *Client) SteamId() steamid.SteamId { 123 | return steamid.SteamId(atomic.LoadUint64(&c.steamId)) 124 | } 125 | 126 | func (c *Client) SessionId() int32 { 127 | return atomic.LoadInt32(&c.sessionId) 128 | } 129 | 130 | func (c *Client) Connected() bool { 131 | c.mutex.RLock() 132 | defer c.mutex.RUnlock() 133 | return c.conn != nil 134 | } 135 | 136 | // Connects to a random Steam server and returns its address. 137 | // If this client is already connected, it is disconnected first. 138 | // This method tries to use an address from the Steam Directory and falls 139 | // back to the built-in server list if the Steam Directory can't be reached. 140 | // If you want to connect to a specific server, use `ConnectTo`. 141 | func (c *Client) Connect() (*netutil.PortAddr, error) { 142 | var server *netutil.PortAddr 143 | 144 | // try to initialize the directory cache 145 | if !steamDirectoryCache.IsInitialized() { 146 | _ = steamDirectoryCache.Initialize() 147 | } 148 | if steamDirectoryCache.IsInitialized() { 149 | server = steamDirectoryCache.GetRandomCM() 150 | } else { 151 | server = GetRandomCM() 152 | } 153 | 154 | err := c.ConnectTo(server) 155 | return server, err 156 | } 157 | 158 | // Connects to a specific server. 159 | // You may want to use one of the `GetRandom*CM()` functions in this package. 160 | // If this client is already connected, it is disconnected first. 161 | func (c *Client) ConnectTo(addr *netutil.PortAddr) error { 162 | return c.ConnectToBind(addr, nil) 163 | } 164 | 165 | // Connects to a specific server, and binds to a specified local IP 166 | // If this client is already connected, it is disconnected first. 167 | func (c *Client) ConnectToBind(addr *netutil.PortAddr, local *net.TCPAddr) error { 168 | c.Disconnect() 169 | 170 | conn, err := dialTCP(local, addr.ToTCPAddr()) 171 | if err != nil { 172 | c.Fatalf("Connect failed: %v", err) 173 | return err 174 | } 175 | c.conn = conn 176 | c.writeChan = make(chan protocol.IMsg, 5) 177 | 178 | go c.readLoop() 179 | go c.writeLoop() 180 | 181 | return nil 182 | } 183 | 184 | func (c *Client) Disconnect() { 185 | c.mutex.Lock() 186 | defer c.mutex.Unlock() 187 | 188 | if c.conn == nil { 189 | return 190 | } 191 | 192 | c.conn.Close() 193 | c.conn = nil 194 | if c.heartbeat != nil { 195 | c.heartbeat.Stop() 196 | } 197 | close(c.writeChan) 198 | c.Emit(&DisconnectedEvent{}) 199 | 200 | } 201 | 202 | // Adds a message to the send queue. Modifications to the given message after 203 | // writing are not allowed (possible race conditions). 204 | // 205 | // Writes to this client when not connected are ignored. 206 | func (c *Client) Write(msg protocol.IMsg) { 207 | if cm, ok := msg.(protocol.IClientMsg); ok { 208 | cm.SetSessionId(c.SessionId()) 209 | cm.SetSteamId(c.SteamId()) 210 | } 211 | c.mutex.RLock() 212 | defer c.mutex.RUnlock() 213 | if c.conn == nil { 214 | return 215 | } 216 | c.writeChan <- msg 217 | } 218 | 219 | func (c *Client) readLoop() { 220 | for { 221 | // This *should* be atomic on most platforms, but the Go spec doesn't guarantee it 222 | c.mutex.RLock() 223 | conn := c.conn 224 | c.mutex.RUnlock() 225 | if conn == nil { 226 | return 227 | } 228 | packet, err := conn.Read() 229 | 230 | if err != nil { 231 | c.Fatalf("Error reading from the connection: %v", err) 232 | return 233 | } 234 | c.handlePacket(packet) 235 | } 236 | } 237 | 238 | func (c *Client) writeLoop() { 239 | for { 240 | c.mutex.RLock() 241 | conn := c.conn 242 | c.mutex.RUnlock() 243 | if conn == nil { 244 | return 245 | } 246 | 247 | msg, ok := <-c.writeChan 248 | if !ok { 249 | return 250 | } 251 | 252 | err := msg.Serialize(c.writeBuf) 253 | if err != nil { 254 | c.writeBuf.Reset() 255 | c.Fatalf("Error serializing message %v: %v", msg, err) 256 | return 257 | } 258 | 259 | err = conn.Write(c.writeBuf.Bytes()) 260 | 261 | c.writeBuf.Reset() 262 | 263 | if err != nil { 264 | c.Fatalf("Error writing message %v: %v", msg, err) 265 | return 266 | } 267 | } 268 | } 269 | 270 | func (c *Client) heartbeatLoop(seconds time.Duration) { 271 | if c.heartbeat != nil { 272 | c.heartbeat.Stop() 273 | } 274 | c.heartbeat = time.NewTicker(seconds * time.Second) 275 | for { 276 | _, ok := <-c.heartbeat.C 277 | if !ok { 278 | break 279 | } 280 | c.Write(protocol.NewClientMsgProtobuf(steamlang.EMsg_ClientHeartBeat, new(protobuf.CMsgClientHeartBeat))) 281 | } 282 | c.heartbeat = nil 283 | } 284 | 285 | func (c *Client) handlePacket(packet *protocol.Packet) { 286 | switch packet.EMsg { 287 | case steamlang.EMsg_ChannelEncryptRequest: 288 | c.handleChannelEncryptRequest(packet) 289 | case steamlang.EMsg_ChannelEncryptResult: 290 | c.handleChannelEncryptResult(packet) 291 | case steamlang.EMsg_Multi: 292 | c.handleMulti(packet) 293 | case steamlang.EMsg_ClientCMList: 294 | c.handleClientCMList(packet) 295 | } 296 | 297 | c.handlersMutex.RLock() 298 | defer c.handlersMutex.RUnlock() 299 | for _, handler := range c.handlers { 300 | handler.HandlePacket(packet) 301 | } 302 | } 303 | 304 | func (c *Client) handleChannelEncryptRequest(packet *protocol.Packet) { 305 | body := steamlang.NewMsgChannelEncryptRequest() 306 | packet.ReadMsg(body) 307 | 308 | if body.Universe != steamlang.EUniverse_Public { 309 | c.Fatalf("Invalid univserse %v!", body.Universe) 310 | } 311 | 312 | c.tempSessionKey = make([]byte, 32) 313 | rand.Read(c.tempSessionKey) 314 | encryptedKey := cryptoutil.RSAEncrypt(GetPublicKey(steamlang.EUniverse_Public), c.tempSessionKey) 315 | 316 | payload := new(bytes.Buffer) 317 | payload.Write(encryptedKey) 318 | binary.Write(payload, binary.LittleEndian, crc32.ChecksumIEEE(encryptedKey)) 319 | payload.WriteByte(0) 320 | payload.WriteByte(0) 321 | payload.WriteByte(0) 322 | payload.WriteByte(0) 323 | 324 | c.Write(protocol.NewMsg(steamlang.NewMsgChannelEncryptResponse(), payload.Bytes())) 325 | } 326 | 327 | func (c *Client) handleChannelEncryptResult(packet *protocol.Packet) { 328 | body := steamlang.NewMsgChannelEncryptResult() 329 | packet.ReadMsg(body) 330 | 331 | if body.Result != steamlang.EResult_OK { 332 | c.Fatalf("Encryption failed: %v", body.Result) 333 | return 334 | } 335 | c.conn.SetEncryptionKey(c.tempSessionKey) 336 | c.tempSessionKey = nil 337 | 338 | c.Emit(&ConnectedEvent{}) 339 | } 340 | 341 | func (c *Client) handleMulti(packet *protocol.Packet) { 342 | body := new(protobuf.CMsgMulti) 343 | packet.ReadProtoMsg(body) 344 | 345 | payload := body.GetMessageBody() 346 | 347 | if body.GetSizeUnzipped() > 0 { 348 | r, err := gzip.NewReader(bytes.NewReader(payload)) 349 | if err != nil { 350 | c.Errorf("handleMulti: Error while decompressing: %v", err) 351 | return 352 | } 353 | 354 | payload, err = ioutil.ReadAll(r) 355 | if err != nil { 356 | c.Errorf("handleMulti: Error while decompressing: %v", err) 357 | return 358 | } 359 | } 360 | 361 | pr := bytes.NewReader(payload) 362 | for pr.Len() > 0 { 363 | var length uint32 364 | binary.Read(pr, binary.LittleEndian, &length) 365 | packetData := make([]byte, length) 366 | pr.Read(packetData) 367 | p, err := protocol.NewPacket(packetData) 368 | if err != nil { 369 | c.Errorf("Error reading packet in Multi msg %v: %v", packet, err) 370 | continue 371 | } 372 | c.handlePacket(p) 373 | } 374 | } 375 | 376 | func (c *Client) handleClientCMList(packet *protocol.Packet) { 377 | body := new(protobuf.CMsgClientCMList) 378 | packet.ReadProtoMsg(body) 379 | 380 | l := make([]*netutil.PortAddr, 0) 381 | for i, ip := range body.GetCmAddresses() { 382 | l = append(l, &netutil.PortAddr{ 383 | readIp(ip), 384 | uint16(body.GetCmPorts()[i]), 385 | }) 386 | } 387 | 388 | c.Emit(&ClientCMListEvent{l}) 389 | } 390 | 391 | func readIp(ip uint32) net.IP { 392 | r := make(net.IP, 4) 393 | r[3] = byte(ip) 394 | r[2] = byte(ip >> 8) 395 | r[1] = byte(ip >> 16) 396 | r[0] = byte(ip >> 24) 397 | return r 398 | } 399 | -------------------------------------------------------------------------------- /csgo/protocol/protobuf/uifontfile.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.17.1 5 | // source: uifontfile_format.proto 6 | 7 | package protobuf 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type CUIFontFilePB struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | FontFileName *string `protobuf:"bytes,1,opt,name=font_file_name,json=fontFileName" json:"font_file_name,omitempty"` 29 | OpentypeFontData []byte `protobuf:"bytes,2,opt,name=opentype_font_data,json=opentypeFontData" json:"opentype_font_data,omitempty"` 30 | } 31 | 32 | func (x *CUIFontFilePB) Reset() { 33 | *x = CUIFontFilePB{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_uifontfile_format_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *CUIFontFilePB) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*CUIFontFilePB) ProtoMessage() {} 46 | 47 | func (x *CUIFontFilePB) ProtoReflect() protoreflect.Message { 48 | mi := &file_uifontfile_format_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use CUIFontFilePB.ProtoReflect.Descriptor instead. 60 | func (*CUIFontFilePB) Descriptor() ([]byte, []int) { 61 | return file_uifontfile_format_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *CUIFontFilePB) GetFontFileName() string { 65 | if x != nil && x.FontFileName != nil { 66 | return *x.FontFileName 67 | } 68 | return "" 69 | } 70 | 71 | func (x *CUIFontFilePB) GetOpentypeFontData() []byte { 72 | if x != nil { 73 | return x.OpentypeFontData 74 | } 75 | return nil 76 | } 77 | 78 | type CUIFontFilePackagePB struct { 79 | state protoimpl.MessageState 80 | sizeCache protoimpl.SizeCache 81 | unknownFields protoimpl.UnknownFields 82 | 83 | PackageVersion *uint32 `protobuf:"varint,1,req,name=package_version,json=packageVersion" json:"package_version,omitempty"` 84 | EncryptedFontFiles []*CUIFontFilePackagePB_CUIEncryptedFontFilePB `protobuf:"bytes,2,rep,name=encrypted_font_files,json=encryptedFontFiles" json:"encrypted_font_files,omitempty"` 85 | } 86 | 87 | func (x *CUIFontFilePackagePB) Reset() { 88 | *x = CUIFontFilePackagePB{} 89 | if protoimpl.UnsafeEnabled { 90 | mi := &file_uifontfile_format_proto_msgTypes[1] 91 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 92 | ms.StoreMessageInfo(mi) 93 | } 94 | } 95 | 96 | func (x *CUIFontFilePackagePB) String() string { 97 | return protoimpl.X.MessageStringOf(x) 98 | } 99 | 100 | func (*CUIFontFilePackagePB) ProtoMessage() {} 101 | 102 | func (x *CUIFontFilePackagePB) ProtoReflect() protoreflect.Message { 103 | mi := &file_uifontfile_format_proto_msgTypes[1] 104 | if protoimpl.UnsafeEnabled && x != nil { 105 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 106 | if ms.LoadMessageInfo() == nil { 107 | ms.StoreMessageInfo(mi) 108 | } 109 | return ms 110 | } 111 | return mi.MessageOf(x) 112 | } 113 | 114 | // Deprecated: Use CUIFontFilePackagePB.ProtoReflect.Descriptor instead. 115 | func (*CUIFontFilePackagePB) Descriptor() ([]byte, []int) { 116 | return file_uifontfile_format_proto_rawDescGZIP(), []int{1} 117 | } 118 | 119 | func (x *CUIFontFilePackagePB) GetPackageVersion() uint32 { 120 | if x != nil && x.PackageVersion != nil { 121 | return *x.PackageVersion 122 | } 123 | return 0 124 | } 125 | 126 | func (x *CUIFontFilePackagePB) GetEncryptedFontFiles() []*CUIFontFilePackagePB_CUIEncryptedFontFilePB { 127 | if x != nil { 128 | return x.EncryptedFontFiles 129 | } 130 | return nil 131 | } 132 | 133 | type CUIFontFilePackagePB_CUIEncryptedFontFilePB struct { 134 | state protoimpl.MessageState 135 | sizeCache protoimpl.SizeCache 136 | unknownFields protoimpl.UnknownFields 137 | 138 | EncryptedContents []byte `protobuf:"bytes,1,opt,name=encrypted_contents,json=encryptedContents" json:"encrypted_contents,omitempty"` 139 | } 140 | 141 | func (x *CUIFontFilePackagePB_CUIEncryptedFontFilePB) Reset() { 142 | *x = CUIFontFilePackagePB_CUIEncryptedFontFilePB{} 143 | if protoimpl.UnsafeEnabled { 144 | mi := &file_uifontfile_format_proto_msgTypes[2] 145 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 146 | ms.StoreMessageInfo(mi) 147 | } 148 | } 149 | 150 | func (x *CUIFontFilePackagePB_CUIEncryptedFontFilePB) String() string { 151 | return protoimpl.X.MessageStringOf(x) 152 | } 153 | 154 | func (*CUIFontFilePackagePB_CUIEncryptedFontFilePB) ProtoMessage() {} 155 | 156 | func (x *CUIFontFilePackagePB_CUIEncryptedFontFilePB) ProtoReflect() protoreflect.Message { 157 | mi := &file_uifontfile_format_proto_msgTypes[2] 158 | if protoimpl.UnsafeEnabled && x != nil { 159 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 160 | if ms.LoadMessageInfo() == nil { 161 | ms.StoreMessageInfo(mi) 162 | } 163 | return ms 164 | } 165 | return mi.MessageOf(x) 166 | } 167 | 168 | // Deprecated: Use CUIFontFilePackagePB_CUIEncryptedFontFilePB.ProtoReflect.Descriptor instead. 169 | func (*CUIFontFilePackagePB_CUIEncryptedFontFilePB) Descriptor() ([]byte, []int) { 170 | return file_uifontfile_format_proto_rawDescGZIP(), []int{1, 0} 171 | } 172 | 173 | func (x *CUIFontFilePackagePB_CUIEncryptedFontFilePB) GetEncryptedContents() []byte { 174 | if x != nil { 175 | return x.EncryptedContents 176 | } 177 | return nil 178 | } 179 | 180 | var File_uifontfile_format_proto protoreflect.FileDescriptor 181 | 182 | var file_uifontfile_format_proto_rawDesc = []byte{ 183 | 0x0a, 0x17, 0x75, 0x69, 0x66, 0x6f, 0x6e, 0x74, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x66, 0x6f, 0x72, 184 | 0x6d, 0x61, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x63, 0x0a, 0x0d, 0x43, 0x55, 0x49, 185 | 0x46, 0x6f, 0x6e, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x42, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x6f, 186 | 0x6e, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 187 | 0x28, 0x09, 0x52, 0x0c, 0x66, 0x6f, 0x6e, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 188 | 0x12, 0x2c, 0x0a, 0x12, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x66, 0x6f, 0x6e, 189 | 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x6f, 0x70, 190 | 0x65, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x46, 0x6f, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0xe8, 191 | 0x01, 0x0a, 0x14, 0x43, 0x55, 0x49, 0x46, 0x6f, 0x6e, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x61, 192 | 0x63, 0x6b, 0x61, 0x67, 0x65, 0x50, 0x42, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x63, 0x6b, 0x61, 193 | 0x67, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x02, 0x28, 0x0d, 194 | 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 195 | 0x12, 0x5e, 0x0a, 0x14, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x6f, 196 | 0x6e, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 197 | 0x2e, 0x43, 0x55, 0x49, 0x46, 0x6f, 0x6e, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x63, 0x6b, 198 | 0x61, 0x67, 0x65, 0x50, 0x42, 0x2e, 0x43, 0x55, 0x49, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 199 | 0x65, 0x64, 0x46, 0x6f, 0x6e, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x42, 0x52, 0x12, 0x65, 0x6e, 200 | 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x46, 0x6f, 0x6e, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 201 | 0x1a, 0x47, 0x0a, 0x16, 0x43, 0x55, 0x49, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 202 | 0x46, 0x6f, 0x6e, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x42, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x6e, 203 | 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 204 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 205 | 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x05, 0x48, 0x01, 0x80, 0x01, 0x00, 206 | } 207 | 208 | var ( 209 | file_uifontfile_format_proto_rawDescOnce sync.Once 210 | file_uifontfile_format_proto_rawDescData = file_uifontfile_format_proto_rawDesc 211 | ) 212 | 213 | func file_uifontfile_format_proto_rawDescGZIP() []byte { 214 | file_uifontfile_format_proto_rawDescOnce.Do(func() { 215 | file_uifontfile_format_proto_rawDescData = protoimpl.X.CompressGZIP(file_uifontfile_format_proto_rawDescData) 216 | }) 217 | return file_uifontfile_format_proto_rawDescData 218 | } 219 | 220 | var file_uifontfile_format_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 221 | var file_uifontfile_format_proto_goTypes = []interface{}{ 222 | (*CUIFontFilePB)(nil), // 0: CUIFontFilePB 223 | (*CUIFontFilePackagePB)(nil), // 1: CUIFontFilePackagePB 224 | (*CUIFontFilePackagePB_CUIEncryptedFontFilePB)(nil), // 2: CUIFontFilePackagePB.CUIEncryptedFontFilePB 225 | } 226 | var file_uifontfile_format_proto_depIdxs = []int32{ 227 | 2, // 0: CUIFontFilePackagePB.encrypted_font_files:type_name -> CUIFontFilePackagePB.CUIEncryptedFontFilePB 228 | 1, // [1:1] is the sub-list for method output_type 229 | 1, // [1:1] is the sub-list for method input_type 230 | 1, // [1:1] is the sub-list for extension type_name 231 | 1, // [1:1] is the sub-list for extension extendee 232 | 0, // [0:1] is the sub-list for field type_name 233 | } 234 | 235 | func init() { file_uifontfile_format_proto_init() } 236 | func file_uifontfile_format_proto_init() { 237 | if File_uifontfile_format_proto != nil { 238 | return 239 | } 240 | if !protoimpl.UnsafeEnabled { 241 | file_uifontfile_format_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 242 | switch v := v.(*CUIFontFilePB); i { 243 | case 0: 244 | return &v.state 245 | case 1: 246 | return &v.sizeCache 247 | case 2: 248 | return &v.unknownFields 249 | default: 250 | return nil 251 | } 252 | } 253 | file_uifontfile_format_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 254 | switch v := v.(*CUIFontFilePackagePB); i { 255 | case 0: 256 | return &v.state 257 | case 1: 258 | return &v.sizeCache 259 | case 2: 260 | return &v.unknownFields 261 | default: 262 | return nil 263 | } 264 | } 265 | file_uifontfile_format_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 266 | switch v := v.(*CUIFontFilePackagePB_CUIEncryptedFontFilePB); i { 267 | case 0: 268 | return &v.state 269 | case 1: 270 | return &v.sizeCache 271 | case 2: 272 | return &v.unknownFields 273 | default: 274 | return nil 275 | } 276 | } 277 | } 278 | type x struct{} 279 | out := protoimpl.TypeBuilder{ 280 | File: protoimpl.DescBuilder{ 281 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 282 | RawDescriptor: file_uifontfile_format_proto_rawDesc, 283 | NumEnums: 0, 284 | NumMessages: 3, 285 | NumExtensions: 0, 286 | NumServices: 0, 287 | }, 288 | GoTypes: file_uifontfile_format_proto_goTypes, 289 | DependencyIndexes: file_uifontfile_format_proto_depIdxs, 290 | MessageInfos: file_uifontfile_format_proto_msgTypes, 291 | }.Build() 292 | File_uifontfile_format_proto = out.File 293 | file_uifontfile_format_proto_rawDesc = nil 294 | file_uifontfile_format_proto_goTypes = nil 295 | file_uifontfile_format_proto_depIdxs = nil 296 | } 297 | -------------------------------------------------------------------------------- /dota/protocol/protobuf/system.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.17.1 5 | // source: gcsystemmsgs.proto 6 | 7 | package protobuf 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type ESOMsg int32 24 | 25 | const ( 26 | ESOMsg_k_ESOMsg_Create ESOMsg = 21 27 | ESOMsg_k_ESOMsg_Update ESOMsg = 22 28 | ESOMsg_k_ESOMsg_Destroy ESOMsg = 23 29 | ESOMsg_k_ESOMsg_CacheSubscribed ESOMsg = 24 30 | ESOMsg_k_ESOMsg_CacheUnsubscribed ESOMsg = 25 31 | ESOMsg_k_ESOMsg_UpdateMultiple ESOMsg = 26 32 | ESOMsg_k_ESOMsg_CacheSubscriptionRefresh ESOMsg = 28 33 | ESOMsg_k_ESOMsg_CacheSubscribedUpToDate ESOMsg = 29 34 | ) 35 | 36 | // Enum value maps for ESOMsg. 37 | var ( 38 | ESOMsg_name = map[int32]string{ 39 | 21: "k_ESOMsg_Create", 40 | 22: "k_ESOMsg_Update", 41 | 23: "k_ESOMsg_Destroy", 42 | 24: "k_ESOMsg_CacheSubscribed", 43 | 25: "k_ESOMsg_CacheUnsubscribed", 44 | 26: "k_ESOMsg_UpdateMultiple", 45 | 28: "k_ESOMsg_CacheSubscriptionRefresh", 46 | 29: "k_ESOMsg_CacheSubscribedUpToDate", 47 | } 48 | ESOMsg_value = map[string]int32{ 49 | "k_ESOMsg_Create": 21, 50 | "k_ESOMsg_Update": 22, 51 | "k_ESOMsg_Destroy": 23, 52 | "k_ESOMsg_CacheSubscribed": 24, 53 | "k_ESOMsg_CacheUnsubscribed": 25, 54 | "k_ESOMsg_UpdateMultiple": 26, 55 | "k_ESOMsg_CacheSubscriptionRefresh": 28, 56 | "k_ESOMsg_CacheSubscribedUpToDate": 29, 57 | } 58 | ) 59 | 60 | func (x ESOMsg) Enum() *ESOMsg { 61 | p := new(ESOMsg) 62 | *p = x 63 | return p 64 | } 65 | 66 | func (x ESOMsg) String() string { 67 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 68 | } 69 | 70 | func (ESOMsg) Descriptor() protoreflect.EnumDescriptor { 71 | return file_gcsystemmsgs_proto_enumTypes[0].Descriptor() 72 | } 73 | 74 | func (ESOMsg) Type() protoreflect.EnumType { 75 | return &file_gcsystemmsgs_proto_enumTypes[0] 76 | } 77 | 78 | func (x ESOMsg) Number() protoreflect.EnumNumber { 79 | return protoreflect.EnumNumber(x) 80 | } 81 | 82 | // Deprecated: Do not use. 83 | func (x *ESOMsg) UnmarshalJSON(b []byte) error { 84 | num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) 85 | if err != nil { 86 | return err 87 | } 88 | *x = ESOMsg(num) 89 | return nil 90 | } 91 | 92 | // Deprecated: Use ESOMsg.Descriptor instead. 93 | func (ESOMsg) EnumDescriptor() ([]byte, []int) { 94 | return file_gcsystemmsgs_proto_rawDescGZIP(), []int{0} 95 | } 96 | 97 | type EGCBaseClientMsg int32 98 | 99 | const ( 100 | EGCBaseClientMsg_k_EMsgGCPingRequest EGCBaseClientMsg = 3001 101 | EGCBaseClientMsg_k_EMsgGCPingResponse EGCBaseClientMsg = 3002 102 | EGCBaseClientMsg_k_EMsgGCToClientPollConvarRequest EGCBaseClientMsg = 3003 103 | EGCBaseClientMsg_k_EMsgGCToClientPollConvarResponse EGCBaseClientMsg = 3004 104 | EGCBaseClientMsg_k_EMsgGCCompressedMsgToClient EGCBaseClientMsg = 3005 105 | EGCBaseClientMsg_k_EMsgGCCompressedMsgToClient_Legacy EGCBaseClientMsg = 523 106 | EGCBaseClientMsg_k_EMsgGCToClientRequestDropped EGCBaseClientMsg = 3006 107 | EGCBaseClientMsg_k_EMsgGCClientWelcome EGCBaseClientMsg = 4004 108 | EGCBaseClientMsg_k_EMsgGCServerWelcome EGCBaseClientMsg = 4005 109 | EGCBaseClientMsg_k_EMsgGCClientHello EGCBaseClientMsg = 4006 110 | EGCBaseClientMsg_k_EMsgGCServerHello EGCBaseClientMsg = 4007 111 | EGCBaseClientMsg_k_EMsgGCClientConnectionStatus EGCBaseClientMsg = 4009 112 | EGCBaseClientMsg_k_EMsgGCServerConnectionStatus EGCBaseClientMsg = 4010 113 | ) 114 | 115 | // Enum value maps for EGCBaseClientMsg. 116 | var ( 117 | EGCBaseClientMsg_name = map[int32]string{ 118 | 3001: "k_EMsgGCPingRequest", 119 | 3002: "k_EMsgGCPingResponse", 120 | 3003: "k_EMsgGCToClientPollConvarRequest", 121 | 3004: "k_EMsgGCToClientPollConvarResponse", 122 | 3005: "k_EMsgGCCompressedMsgToClient", 123 | 523: "k_EMsgGCCompressedMsgToClient_Legacy", 124 | 3006: "k_EMsgGCToClientRequestDropped", 125 | 4004: "k_EMsgGCClientWelcome", 126 | 4005: "k_EMsgGCServerWelcome", 127 | 4006: "k_EMsgGCClientHello", 128 | 4007: "k_EMsgGCServerHello", 129 | 4009: "k_EMsgGCClientConnectionStatus", 130 | 4010: "k_EMsgGCServerConnectionStatus", 131 | } 132 | EGCBaseClientMsg_value = map[string]int32{ 133 | "k_EMsgGCPingRequest": 3001, 134 | "k_EMsgGCPingResponse": 3002, 135 | "k_EMsgGCToClientPollConvarRequest": 3003, 136 | "k_EMsgGCToClientPollConvarResponse": 3004, 137 | "k_EMsgGCCompressedMsgToClient": 3005, 138 | "k_EMsgGCCompressedMsgToClient_Legacy": 523, 139 | "k_EMsgGCToClientRequestDropped": 3006, 140 | "k_EMsgGCClientWelcome": 4004, 141 | "k_EMsgGCServerWelcome": 4005, 142 | "k_EMsgGCClientHello": 4006, 143 | "k_EMsgGCServerHello": 4007, 144 | "k_EMsgGCClientConnectionStatus": 4009, 145 | "k_EMsgGCServerConnectionStatus": 4010, 146 | } 147 | ) 148 | 149 | func (x EGCBaseClientMsg) Enum() *EGCBaseClientMsg { 150 | p := new(EGCBaseClientMsg) 151 | *p = x 152 | return p 153 | } 154 | 155 | func (x EGCBaseClientMsg) String() string { 156 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 157 | } 158 | 159 | func (EGCBaseClientMsg) Descriptor() protoreflect.EnumDescriptor { 160 | return file_gcsystemmsgs_proto_enumTypes[1].Descriptor() 161 | } 162 | 163 | func (EGCBaseClientMsg) Type() protoreflect.EnumType { 164 | return &file_gcsystemmsgs_proto_enumTypes[1] 165 | } 166 | 167 | func (x EGCBaseClientMsg) Number() protoreflect.EnumNumber { 168 | return protoreflect.EnumNumber(x) 169 | } 170 | 171 | // Deprecated: Do not use. 172 | func (x *EGCBaseClientMsg) UnmarshalJSON(b []byte) error { 173 | num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) 174 | if err != nil { 175 | return err 176 | } 177 | *x = EGCBaseClientMsg(num) 178 | return nil 179 | } 180 | 181 | // Deprecated: Use EGCBaseClientMsg.Descriptor instead. 182 | func (EGCBaseClientMsg) EnumDescriptor() ([]byte, []int) { 183 | return file_gcsystemmsgs_proto_rawDescGZIP(), []int{1} 184 | } 185 | 186 | var File_gcsystemmsgs_proto protoreflect.FileDescriptor 187 | 188 | var file_gcsystemmsgs_proto_rawDesc = []byte{ 189 | 0x0a, 0x12, 0x67, 0x63, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x6d, 0x73, 0x67, 0x73, 0x2e, 0x70, 190 | 0x72, 0x6f, 0x74, 0x6f, 0x2a, 0xf0, 0x01, 0x0a, 0x06, 0x45, 0x53, 0x4f, 0x4d, 0x73, 0x67, 0x12, 191 | 0x13, 0x0a, 0x0f, 0x6b, 0x5f, 0x45, 0x53, 0x4f, 0x4d, 0x73, 0x67, 0x5f, 0x43, 0x72, 0x65, 0x61, 192 | 0x74, 0x65, 0x10, 0x15, 0x12, 0x13, 0x0a, 0x0f, 0x6b, 0x5f, 0x45, 0x53, 0x4f, 0x4d, 0x73, 0x67, 193 | 0x5f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x10, 0x16, 0x12, 0x14, 0x0a, 0x10, 0x6b, 0x5f, 0x45, 194 | 0x53, 0x4f, 0x4d, 0x73, 0x67, 0x5f, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x10, 0x17, 0x12, 195 | 0x1c, 0x0a, 0x18, 0x6b, 0x5f, 0x45, 0x53, 0x4f, 0x4d, 0x73, 0x67, 0x5f, 0x43, 0x61, 0x63, 0x68, 196 | 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x10, 0x18, 0x12, 0x1e, 0x0a, 197 | 0x1a, 0x6b, 0x5f, 0x45, 0x53, 0x4f, 0x4d, 0x73, 0x67, 0x5f, 0x43, 0x61, 0x63, 0x68, 0x65, 0x55, 198 | 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x10, 0x19, 0x12, 0x1b, 0x0a, 199 | 0x17, 0x6b, 0x5f, 0x45, 0x53, 0x4f, 0x4d, 0x73, 0x67, 0x5f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 200 | 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x10, 0x1a, 0x12, 0x25, 0x0a, 0x21, 0x6b, 0x5f, 201 | 0x45, 0x53, 0x4f, 0x4d, 0x73, 0x67, 0x5f, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x75, 0x62, 0x73, 202 | 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x10, 203 | 0x1c, 0x12, 0x24, 0x0a, 0x20, 0x6b, 0x5f, 0x45, 0x53, 0x4f, 0x4d, 0x73, 0x67, 0x5f, 0x43, 0x61, 204 | 0x63, 0x68, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x55, 0x70, 0x54, 205 | 0x6f, 0x44, 0x61, 0x74, 0x65, 0x10, 0x1d, 0x2a, 0xc2, 0x03, 0x0a, 0x10, 0x45, 0x47, 0x43, 0x42, 206 | 0x61, 0x73, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x73, 0x67, 0x12, 0x18, 0x0a, 0x13, 207 | 0x6b, 0x5f, 0x45, 0x4d, 0x73, 0x67, 0x47, 0x43, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 208 | 0x65, 0x73, 0x74, 0x10, 0xb9, 0x17, 0x12, 0x19, 0x0a, 0x14, 0x6b, 0x5f, 0x45, 0x4d, 0x73, 0x67, 209 | 0x47, 0x43, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x10, 0xba, 210 | 0x17, 0x12, 0x26, 0x0a, 0x21, 0x6b, 0x5f, 0x45, 0x4d, 0x73, 0x67, 0x47, 0x43, 0x54, 0x6f, 0x43, 211 | 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x76, 0x61, 0x72, 0x52, 212 | 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xbb, 0x17, 0x12, 0x27, 0x0a, 0x22, 0x6b, 0x5f, 0x45, 213 | 0x4d, 0x73, 0x67, 0x47, 0x43, 0x54, 0x6f, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x6c, 214 | 0x6c, 0x43, 0x6f, 0x6e, 0x76, 0x61, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x10, 215 | 0xbc, 0x17, 0x12, 0x22, 0x0a, 0x1d, 0x6b, 0x5f, 0x45, 0x4d, 0x73, 0x67, 0x47, 0x43, 0x43, 0x6f, 216 | 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x54, 0x6f, 0x43, 0x6c, 0x69, 217 | 0x65, 0x6e, 0x74, 0x10, 0xbd, 0x17, 0x12, 0x29, 0x0a, 0x24, 0x6b, 0x5f, 0x45, 0x4d, 0x73, 0x67, 218 | 0x47, 0x43, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x54, 219 | 0x6f, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x10, 0x8b, 220 | 0x04, 0x12, 0x23, 0x0a, 0x1e, 0x6b, 0x5f, 0x45, 0x4d, 0x73, 0x67, 0x47, 0x43, 0x54, 0x6f, 0x43, 221 | 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x72, 0x6f, 0x70, 222 | 0x70, 0x65, 0x64, 0x10, 0xbe, 0x17, 0x12, 0x1a, 0x0a, 0x15, 0x6b, 0x5f, 0x45, 0x4d, 0x73, 0x67, 223 | 0x47, 0x43, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x57, 0x65, 0x6c, 0x63, 0x6f, 0x6d, 0x65, 0x10, 224 | 0xa4, 0x1f, 0x12, 0x1a, 0x0a, 0x15, 0x6b, 0x5f, 0x45, 0x4d, 0x73, 0x67, 0x47, 0x43, 0x53, 0x65, 225 | 0x72, 0x76, 0x65, 0x72, 0x57, 0x65, 0x6c, 0x63, 0x6f, 0x6d, 0x65, 0x10, 0xa5, 0x1f, 0x12, 0x18, 226 | 0x0a, 0x13, 0x6b, 0x5f, 0x45, 0x4d, 0x73, 0x67, 0x47, 0x43, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 227 | 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x10, 0xa6, 0x1f, 0x12, 0x18, 0x0a, 0x13, 0x6b, 0x5f, 0x45, 0x4d, 228 | 0x73, 0x67, 0x47, 0x43, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x10, 229 | 0xa7, 0x1f, 0x12, 0x23, 0x0a, 0x1e, 0x6b, 0x5f, 0x45, 0x4d, 0x73, 0x67, 0x47, 0x43, 0x43, 0x6c, 230 | 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 231 | 0x61, 0x74, 0x75, 0x73, 0x10, 0xa9, 0x1f, 0x12, 0x23, 0x0a, 0x1e, 0x6b, 0x5f, 0x45, 0x4d, 0x73, 232 | 0x67, 0x47, 0x43, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 233 | 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x10, 0xaa, 0x1f, 0x42, 0x05, 0x48, 0x01, 234 | 0x80, 0x01, 0x00, 235 | } 236 | 237 | var ( 238 | file_gcsystemmsgs_proto_rawDescOnce sync.Once 239 | file_gcsystemmsgs_proto_rawDescData = file_gcsystemmsgs_proto_rawDesc 240 | ) 241 | 242 | func file_gcsystemmsgs_proto_rawDescGZIP() []byte { 243 | file_gcsystemmsgs_proto_rawDescOnce.Do(func() { 244 | file_gcsystemmsgs_proto_rawDescData = protoimpl.X.CompressGZIP(file_gcsystemmsgs_proto_rawDescData) 245 | }) 246 | return file_gcsystemmsgs_proto_rawDescData 247 | } 248 | 249 | var file_gcsystemmsgs_proto_enumTypes = make([]protoimpl.EnumInfo, 2) 250 | var file_gcsystemmsgs_proto_goTypes = []interface{}{ 251 | (ESOMsg)(0), // 0: ESOMsg 252 | (EGCBaseClientMsg)(0), // 1: EGCBaseClientMsg 253 | } 254 | var file_gcsystemmsgs_proto_depIdxs = []int32{ 255 | 0, // [0:0] is the sub-list for method output_type 256 | 0, // [0:0] is the sub-list for method input_type 257 | 0, // [0:0] is the sub-list for extension type_name 258 | 0, // [0:0] is the sub-list for extension extendee 259 | 0, // [0:0] is the sub-list for field type_name 260 | } 261 | 262 | func init() { file_gcsystemmsgs_proto_init() } 263 | func file_gcsystemmsgs_proto_init() { 264 | if File_gcsystemmsgs_proto != nil { 265 | return 266 | } 267 | type x struct{} 268 | out := protoimpl.TypeBuilder{ 269 | File: protoimpl.DescBuilder{ 270 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 271 | RawDescriptor: file_gcsystemmsgs_proto_rawDesc, 272 | NumEnums: 2, 273 | NumMessages: 0, 274 | NumExtensions: 0, 275 | NumServices: 0, 276 | }, 277 | GoTypes: file_gcsystemmsgs_proto_goTypes, 278 | DependencyIndexes: file_gcsystemmsgs_proto_depIdxs, 279 | EnumInfos: file_gcsystemmsgs_proto_enumTypes, 280 | }.Build() 281 | File_gcsystemmsgs_proto = out.File 282 | file_gcsystemmsgs_proto_rawDesc = nil 283 | file_gcsystemmsgs_proto_goTypes = nil 284 | file_gcsystemmsgs_proto_depIdxs = nil 285 | } 286 | --------------------------------------------------------------------------------