├── 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 |
--------------------------------------------------------------------------------