From 978555bf106e8e23426092e49e2c4d13a0b8886d Mon Sep 17 00:00:00 2001 From: adman Date: Wed, 26 Nov 2025 22:24:47 +0100 Subject: [PATCH] Refactor: Simplify `LoginClient`, replace `Account` struct with `strom.Account`, add `Ticker` module, and enhance configuration handling --- account.go | 107 ++++++++++++++++++++++++++++++++++++++ connection.go | 9 ++-- event_bus.go | 8 +++ examples/client/main.go | 101 ++++++++++++++++++++++++++++------- examples/proxy/main.go | 11 ++-- modules/config_ignorer.go | 31 ++++++++++- modules/login.go | 56 ++++---------------- modules/ticker.go | 49 +++++++++++++++++ secure_chat.go | 8 +++ 9 files changed, 306 insertions(+), 74 deletions(-) create mode 100644 account.go create mode 100644 modules/ticker.go create mode 100644 secure_chat.go diff --git a/account.go b/account.go new file mode 100644 index 0000000..048aa6e --- /dev/null +++ b/account.go @@ -0,0 +1,107 @@ +package strom + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/google/uuid" +) + +type Account struct { + Name string + Uuid uuid.UUID + Ygg string +} + +const ( + ApiBase = "https://api.minecraftservices.com" +) + +func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) { + var b []byte + if method != "GET" { + b, err = json.Marshal(from) + if err != nil { + return err + } + } + + r, err := http.NewRequest(method, url, bytes.NewReader(b)) + if err != nil { + return err + } + + r.Header.Set("Content-Type", "application/json") + r.Header.Set("Accept", "application/json") + r.Header.Set("x-xbl-contract-version", "1") + r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", a.Ygg)) + + resp, err := http.DefaultClient.Do(r) + if err != nil { + return err + } + + if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) { + return fmt.Errorf("bad status code %v", resp.StatusCode) + } + b, err = io.ReadAll(resp.Body) + if err != nil { + return err + } + err = json.Unmarshal(b, to) + if err != nil { + return err + } + return nil +} + +type PlayerKeys struct { + ExpiresAt time.Time `json:"expiresAt"` + KeyPair struct { + PublicKey string `json:"publicKey"` + PrivateKey string `json:"privateKey"` + } `json:"keyPair"` + PublicKeySignature string `json:"publicKeySignature"` + PublicKeySignatureV2 string `json:"publicKeySignatureV2"` + RefreshedAfter time.Time `json:"refreshedAfter"` +} + +func (a *Account) FetchKeys() (keys PlayerKeys, err error) { + err = a.doMcApiRequest("POST", ApiBase+"/player/certificates", nil, &keys) + return +} + +func (a *Account) JoinServer(serverId string) (err error) { + var body []byte + body, err = json.Marshal(struct { + AccessToken string `json:"accessToken"` + SelectedProfile string `json:"selectedProfile"` + ServerId string `json:"serverId"` + }{ + AccessToken: a.Ygg, + SelectedProfile: strings.ReplaceAll(a.Uuid.String(), "-", ""), + ServerId: serverId, + }) + if err != nil { + return + } + var resp *http.Response + resp, err = http.Post("https://sessionserver.mojang.com/session/minecraft/join", "application/json", bytes.NewReader(body)) + if err != nil { + return + } + if resp.StatusCode != 204 { + body, err = io.ReadAll(resp.Body) + if err != nil { + return + } + err = fmt.Errorf("bad client code %v body %v", resp.StatusCode, string(body)) + return + } + return +} diff --git a/connection.go b/connection.go index 57ca08e..c300453 100644 --- a/connection.go +++ b/connection.go @@ -15,8 +15,9 @@ var BadPacketTypeError = errors.New("bad packet type") // UnCodablePacket represents a packet that could not be decoded due to querser not supporting all packets. type UnCodablePacket struct { - Err error - Data []byte + Err error + Partial any + Data []byte } type Actor int @@ -148,8 +149,8 @@ func (c *Conn) Receive() (packet any, err error) { packetBuff := bytes.NewBuffer(packetBytes) packet, err = generated.DecodePacket(c.Version, c.Actor.ReceiveDirection(), c.State, packetBuff) if err != nil { - packet = UnCodablePacket{Err: err, Data: packetBytes} - return + packet = UnCodablePacket{Err: err, Data: packetBytes, Partial: packet} + err = nil } return } diff --git a/event_bus.go b/event_bus.go index c3b1f23..3f4111f 100644 --- a/event_bus.go +++ b/event_bus.go @@ -10,6 +10,7 @@ type HandlerInst interface { } type OnStart struct{} +type OnLoopCycle struct{} var HandlerDone = errors.New("handler done") @@ -53,6 +54,13 @@ func (c *Conn) Start(inst HandlerInst) (err error) { return } err = FireEvent(inst, event, handlers) + if err != nil { + break + } + err = FireEvent(inst, OnLoopCycle{}, handlers) + } + if errors.Is(err, HandlerDone) { + err = nil } return } diff --git a/examples/client/main.go b/examples/client/main.go index 2c0f2dd..679d8c3 100644 --- a/examples/client/main.go +++ b/examples/client/main.go @@ -1,45 +1,110 @@ package main import ( - "errors" - "fmt" + "bytes" + "encoding/json" + "math/rand/v2" + "net/http" "strom" "strom/modules" + "sync" + "time" - "github.com/google/uuid" + "github.com/admin-else/queser/generated/v1_21_8" ) -var altAccount = modules.Account{ - Username: "laibergraceful70", - Uuid: uuid.MustParse("3a863af533e5452591e913e021bfa724"), - Token: "eyJraWQiOiIwNDkxODEiLCJhbGciOiJSUzI1NiJ9.eyJ4dWlkIjoiMjc3NzIxOTc3MDIzMjM1OCIsImFnZyI6IkFkdWx0Iiwic3ViIjoiMzczMjI0OTktZTAwYS0…JRnCbAsBJl5zg-GAu6NkX1JCvpWdQkgFb0H5RYi2dIUKhNpP57WG2gh562T3BoPELVXDQZQVUBzDFeDuVDGGSkKmSmhzO7l-fEPBt2CjnaLCyr1FmYFSRzDBy1w", -} - type Client struct { *strom.Conn + Ticker *modules.Ticker + Message string + AltAccount strom.Account } func (c *Client) Default(event any) (err error) { - fmt.Printf("%T%v\n", event, event) return } +func (c *Client) SendUnReportableChat(message string) (err error) { + return c.Send(v1_21_8.PlayToServerPacketChatMessage{Message: message, Timestamp: time.Now().Unix(), Salt: rand.Int64(), Signature: nil, Offset: 0, Acknowledged: [3]uint8{0x0, 0x0, 0x0}, Checksum: 0x1}) +} + func (c *Client) OnStart(_ strom.OnStart) (err error) { - err = modules.Login(c.Conn, altAccount) - if errors.Is(err, strom.HandlerDone) { - return nil + err = modules.Login(c.Conn, c.AltAccount) + if err != nil { + return } + err = modules.IgnoreConfig(c.Conn) + if err != nil { + return + } + c.Ticker.Active = true + return +} + +func (c *Client) OnLoopCycle(strom.OnLoopCycle) (err error) { + err = c.Ticker.Tick() + return +} + +func (c *Client) KeepAlive(packet v1_21_8.PlayToClientPacketKeepAlive) (err error) { + return c.Send(v1_21_8.PlayToServerPacketKeepAlive(packet)) +} + +func (c *Client) Spam() (err error) { + err = c.SendUnReportableChat(c.Message) + return +} + +func spawnSpammer(message, connectTo string, account strom.Account, wg *sync.WaitGroup) { + conn, err := strom.Connect(connectTo) + if err != nil { + panic(err) + } + defer conn.Close() + c := &Client{Conn: conn, Message: message} + t := modules.Ticker{} + t.Interval = append(t.Interval, modules.IntervalTask{ + F: c.Spam, + Interval: time.Second, + }) + c.Ticker = &t + c.AltAccount = account + err = conn.Start(c) + wg.Done() +} + +func DownloadAccounts(token string) (accounts []strom.Account, err error) { + r, err := http.NewRequest("GET", "https://griefing.homes/api/list/active", nil) + if err != nil { + return + } + r.Header.Set("Accept", "application/json") + r.Header.Set("Authorization", token) + resp, err := http.DefaultClient.Do(r) + if err != nil { + return + } + defer resp.Body.Close() + b := bytes.NewBuffer(nil) + _, err = b.ReadFrom(resp.Body) + if err != nil { + return + } + err = json.Unmarshal(b.Bytes(), &accounts) return } func main() { - c, err := strom.Connect("127.0.0.1:25566") + accounts, err := DownloadAccounts("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzNDBjNWIzMS02MmEzLTRjODUtYTE5ZC1lZjcxMWVkYzY3ZWQifQ.M_gwEK2HyyY6d4RFY7JzyeuqGVvmqmPK-13wNP0AxGc") if err != nil { panic(err) } - defer c.Close() - err = c.Start(&Client{c}) - if err != nil { - panic(err) + + wg := &sync.WaitGroup{} + wg.Add(len(accounts)) + for _, account := range accounts { + go spawnSpammer("LOBA ONTOP XD", "127.0.0.1:25566", account, wg) + time.Sleep(time.Second * 6) } + wg.Wait() } diff --git a/examples/proxy/main.go b/examples/proxy/main.go index f8f6995..362eec7 100644 --- a/examples/proxy/main.go +++ b/examples/proxy/main.go @@ -16,8 +16,11 @@ type ProxyClient struct { } func (p *ProxyClient) Default(event any) (err error) { - fmt.Printf("1 %T%v\n", event, event) + fmt.Printf("S2C %#v\n", event) err = p.Proxy.Send(event) + if err != nil { + return + } return } @@ -41,7 +44,7 @@ type Proxy struct { } func (p *Proxy) Default(event any) (err error) { - fmt.Printf("2 %T%v\n", event, event) + fmt.Printf("C2S LastTrigger%#v\n", event) err = p.ProxyClient.Send(event) return } @@ -91,10 +94,10 @@ func handleConn(conn net.Conn) { p.ProxyClient = &ProxyClient{Conn: c, Proxy: p} errChan := make(chan error) go func() { - errChan <- c.Start(p.ProxyClient) + errChan <- fmt.Errorf("client error: %v", c.Start(p.ProxyClient)) }() go func() { - errChan <- s.Start(p) + errChan <- fmt.Errorf("server error: %v", s.Start(p)) }() err = <-errChan if err != nil { diff --git a/modules/config_ignorer.go b/modules/config_ignorer.go index b5d8eef..32efd49 100644 --- a/modules/config_ignorer.go +++ b/modules/config_ignorer.go @@ -1,7 +1,34 @@ package modules -import "strom" +import ( + "strom" -func IgnoreConfig(c *strom.Conn) (err error) { + "github.com/admin-else/queser" + "github.com/admin-else/queser/generated/v1_21_8" +) + +type ConfigIgnorer struct { + *strom.Conn +} + +func (c *ConfigIgnorer) Default(event any) (err error) { return } + +func (c *ConfigIgnorer) OnKnownPacks(packet v1_21_8.PacketCommonSelectKnownPacks) (err error) { + return c.Send(packet) +} + +func (c *ConfigIgnorer) OnFinish(packet v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) { + err = c.Send(v1_21_8.ConfigurationToServerPacketFinishConfiguration{}) + if err != nil { + return + } + c.State = queser.Play + err = strom.HandlerDone + return +} + +func IgnoreConfig(c *strom.Conn) (err error) { + return c.Start(&ConfigIgnorer{c}) +} diff --git a/modules/login.go b/modules/login.go index 28afd53..9283f3d 100644 --- a/modules/login.go +++ b/modules/login.go @@ -1,7 +1,6 @@ package modules import ( - "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" @@ -9,10 +8,7 @@ import ( "crypto/sha1" "crypto/x509" "encoding/hex" - "encoding/json" "fmt" - "io" - "net/http" "strconv" "strings" "strom" @@ -20,23 +16,21 @@ import ( "github.com/admin-else/queser" "github.com/admin-else/queser/data" "github.com/admin-else/queser/generated/v1_21_8" - "github.com/google/uuid" ) -type Account struct { - Username string - Uuid uuid.UUID - Token string -} type LoginClient struct { *strom.Conn - Account Account + Account strom.Account GivenAccount v1_21_8.LoginToClientPacketSuccess } func (s *LoginClient) Default(event any) (err error) { - err = fmt.Errorf("unexpected event: %T%v", event, event) + err = fmt.Errorf("unexpected event: %#v", event) + return +} + +func (s *LoginClient) OnCycle(_ strom.OnLoopCycle) (err error) { return } @@ -60,7 +54,7 @@ func (s *LoginClient) OnStart(_ strom.OnStart) (err error) { return } s.State = queser.Login - err = s.Send(v1_21_8.LoginToServerPacketLoginStart{Username: s.Account.Username, PlayerUUID: s.Account.Uuid}) + err = s.Send(v1_21_8.LoginToServerPacketLoginStart{Username: s.Account.Name, PlayerUUID: s.Account.Uuid}) return } @@ -102,47 +96,17 @@ func AuthDigest(elems ...[]byte) string { return res } -func JoinServerAPI(a Account, serverId string) (err error) { - var body []byte - body, err = json.Marshal(struct { - AccessToken string `json:"accessToken"` - SelectedProfile string `json:"selectedProfile"` - ServerId string `json:"serverId"` - }{ - AccessToken: a.Token, - SelectedProfile: strings.ReplaceAll(a.Uuid.String(), "-", ""), - ServerId: serverId, - }) - if err != nil { - return - } - var resp *http.Response - resp, err = http.Post("https://sessionserver.mojang.com/session/minecraft/join", "application/json", bytes.NewReader(body)) - if err != nil { - return - } - if resp.StatusCode != 204 { - body, err = io.ReadAll(resp.Body) - if err != nil { - return - } - err = fmt.Errorf("bad client code %v body %v", resp.StatusCode, string(body)) - return - } - return -} - func (s *LoginClient) OnEncrypt(packet v1_21_8.LoginToClientPacketEncryptionBegin) (err error) { sharedSecret := make([]byte, 16) _, _ = rand.Read(sharedSecret) //never fails if packet.ShouldAuthenticate { - if s.Account.Token == "" { + if s.Account.Ygg == "" { err = fmt.Errorf("the account has no token so we cant join online servers") return } serverId := AuthDigest([]byte(packet.ServerId), sharedSecret, packet.PublicKey) - err = JoinServerAPI(s.Account, serverId) + err = s.Account.JoinServer(serverId) if err != nil { return } @@ -199,7 +163,7 @@ func (s *LoginClient) OnSuccess(success v1_21_8.LoginToClientPacketSuccess) (err return } -func Login(c *strom.Conn, account Account) (err error) { +func Login(c *strom.Conn, account strom.Account) (err error) { return c.Start(&LoginClient{ Conn: c, Account: account, diff --git a/modules/ticker.go b/modules/ticker.go new file mode 100644 index 0000000..40ca813 --- /dev/null +++ b/modules/ticker.go @@ -0,0 +1,49 @@ +package modules + +import "time" + +type IntervalTask struct { + F func() error + LastTrigger time.Time + Interval time.Duration +} + +type OnceTask struct { + F func() error + Trigger time.Time +} + +type Ticker struct { + Once []OnceTask + Interval []IntervalTask + Active bool +} + +func (t *Ticker) Tick() (err error) { + if !t.Active { + return + } + for i, task := range t.Interval { + if time.Since(task.LastTrigger) >= task.Interval { + task.LastTrigger = time.Now() + err = task.F() + if err != nil { + return + } + } + t.Interval[i] = task + } + var triggerLater []OnceTask + for _, task := range t.Once { + if task.Trigger.After(time.Now()) { + err = task.F() + if err != nil { + return + } + } else { + triggerLater = append(triggerLater, task) + } + } + t.Once = triggerLater + return +} diff --git a/secure_chat.go b/secure_chat.go new file mode 100644 index 0000000..d96a70f --- /dev/null +++ b/secure_chat.go @@ -0,0 +1,8 @@ +package strom + +type SecureChat struct { +} + +func FetchSecureChat(account Account) { + +}