monorepo fixes

This commit is contained in:
adman
2025-12-15 22:46:34 +01:00
parent 6108534a12
commit c58dbbe5b0
83 changed files with 2273 additions and 2042 deletions
-107
View File
@@ -1,107 +0,0 @@
package bot
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
}
-139
View File
@@ -1,139 +0,0 @@
package bot
// https://github.com/Tnze/go-mc/blob/master/net/CFB8/cfb8.go
// Package CFB8 implements CFB8 block cipher mode of operation used by Minecraft protocol.
import (
"crypto/cipher"
"crypto/subtle"
"unsafe"
)
type CFB8 struct {
c cipher.Block
blockSize int
ivPos int
iv []byte
de bool
}
func NewCFB8Decrypt(c cipher.Block, iv []byte) *CFB8 {
return newCFB8(c, iv, true)
}
func NewCFB8Encrypt(c cipher.Block, iv []byte) *CFB8 {
return newCFB8(c, iv, false)
}
func newCFB8(c cipher.Block, iv []byte, de bool) *CFB8 {
cp := make([]byte, len(iv)*3)
copy(cp, iv)
return &CFB8{
c: c,
blockSize: c.BlockSize(),
iv: cp,
de: de,
}
}
func (cf *CFB8) XORKeyStream(dst, src []byte) {
if len(src) == 0 {
return
}
if len(dst) < len(src) {
panic("cfb8: output smaller than input")
}
// If dst and src does not overlap in first block size,
// and the length of src is greater than 2*blockSize,
// we can use an optimized implementation.
if len(src) > cf.blockSize<<1 &&
(uintptr(unsafe.Pointer(&dst[0]))+uintptr(cf.blockSize) <= uintptr(unsafe.Pointer(&src[0])) ||
uintptr(unsafe.Pointer(&src[0]))+uintptr(len(src)) <= uintptr(unsafe.Pointer(&dst[0]))) {
// encrypt/decrypt first blockSize bytes
// After this, the IV will come to the same as
// the last blockSize of ciphertext, so
// we can reuse them without copy.
cf.xorKeyStream(dst, src[:cf.blockSize])
var ciphertext []byte
if cf.de {
ciphertext = src
} else {
ciphertext = dst
}
dst = dst[cf.blockSize:]
src = src[cf.blockSize:]
iv := cf.iv
_ = iv[0] // bounds check hint to compiler; see golang.org/issue/14808
var (
i int
val byte
)
dst = dst[:len(src)]
if cf.de && // and requires to be non-overlapping at all
uintptr(unsafe.Pointer(&dst[0])) <= uintptr(unsafe.Pointer(&src[len(src)-1])) &&
uintptr(unsafe.Pointer(&src[0])) <= uintptr(unsafe.Pointer(&dst[len(dst)-1])) {
for i = 0; i < len(src)-cf.blockSize; i += 1 {
cf.c.Encrypt(dst[i:], ciphertext[i:])
}
subtle.XORBytes(dst, src[:i], dst)
for ; i < len(src); i += 1 {
cf.c.Encrypt(iv, ciphertext[i:])
dst[i] = src[i] ^ iv[0]
}
} else {
_ = ciphertext[len(src)]
for i, val = range src {
cf.c.Encrypt(iv, ciphertext[i:])
dst[i] = val ^ iv[0]
}
// for-range does not increase i in the last loop,
// compared to the classic for clause
i += 1
}
// copy the current IV for next operation
copy(iv, ciphertext[i:i+cf.blockSize])
cf.ivPos = 0
return
}
cf.xorKeyStream(dst, src)
}
func (cf *CFB8) xorKeyStream(dst, src []byte) {
dst = dst[:len(src)] // remove bounds check in loop
for i, val := range src {
posPlusBlockSize := cf.ivPos + cf.blockSize
// fast mod; 2*blockSize must be a non-negative integer power of 2
tempPos := posPlusBlockSize & (cf.blockSize<<1 - 1)
// reuse space to store encrypted block
cf.c.Encrypt(cf.iv[tempPos:], cf.iv[cf.ivPos:])
// Only the first byte of the encrypted block is used
// for encryption/decryption, other bytes are ignored.
val ^= cf.iv[tempPos]
if cf.ivPos == cf.blockSize<<1 {
// bound reached; move to next round for next operation
// copy next block to the start of the ring buffer
copy(cf.iv, cf.iv[cf.ivPos+1:])
// insert the encrypted byte to the end of IV
if cf.de {
cf.iv[cf.blockSize-1] = src[i]
} else {
cf.iv[cf.blockSize-1] = val
}
cf.ivPos = 0
} else {
// insert the encrypted byte to the end of IV
if cf.de {
cf.iv[posPlusBlockSize] = src[i]
} else {
cf.iv[posPlusBlockSize] = val
}
// move to next block
cf.ivPos += 1
}
dst[i] = val
}
}
+19 -19
View File
@@ -7,8 +7,8 @@ import (
"io"
"net"
"github.com/admin-else/queser"
"github.com/admin-else/queser/generated"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/proto_generated"
)
var BadPacketTypeError = errors.New("bad packet type")
@@ -27,23 +27,23 @@ const (
Client
)
func (a Actor) SendDirection() queser.Direction {
func (a Actor) SendDirection() proto_base.Direction {
switch a {
case Server:
return queser.ToClient
return proto_base.ToClient
case Client:
return queser.ToServer
return proto_base.ToServer
default:
panic("invalid actor")
}
}
func (a Actor) ReceiveDirection() queser.Direction {
func (a Actor) ReceiveDirection() proto_base.Direction {
switch a {
case Server:
return queser.ToServer
return proto_base.ToServer
case Client:
return queser.ToClient
return proto_base.ToClient
default:
panic("invalid actor")
}
@@ -54,7 +54,7 @@ type Conn struct {
R io.Reader
W io.Writer
CompressionThreshold int32
State queser.State
State proto_base.State
Actor Actor
Version string
}
@@ -65,13 +65,13 @@ func (c *Conn) Send(packet any) (err error) {
case UnCodablePacket:
rawPacketBytes = packet.Data
default:
packetIdentifier := generated.TypeToPacketIdentifier(c.Version, c.Actor.SendDirection(), c.State, packet)
packetIdentifier := proto_generated.TypeToPacketIdentifier(c.Version, c.Actor.SendDirection(), c.State, packet)
if packetIdentifier == "" {
err = BadPacketTypeError
return
}
rawPacketBuffer := bytes.NewBuffer(nil)
err = generated.EncodePacket(c.Version, c.Actor.SendDirection(), c.State, packetIdentifier, packet, rawPacketBuffer)
err = proto_generated.EncodePacket(c.Version, c.Actor.SendDirection(), c.State, packetIdentifier, packet, rawPacketBuffer)
if err != nil {
return
}
@@ -81,7 +81,7 @@ func (c *Conn) Send(packet any) (err error) {
if c.CompressionThreshold > 0 {
packetBuffer := bytes.NewBuffer(nil)
if int32(len(packetBytes)) >= c.CompressionThreshold {
err = queser.VarInt(len(packetBytes)).Encode(packetBuffer)
err = proto_base.VarInt(len(packetBytes)).Encode(packetBuffer)
if err != nil {
return
}
@@ -90,7 +90,7 @@ func (c *Conn) Send(packet any) (err error) {
return
}
} else {
err = queser.VarInt(0).Encode(packetBuffer)
err = proto_base.VarInt(0).Encode(packetBuffer)
if err != nil {
return
}
@@ -103,7 +103,7 @@ func (c *Conn) Send(packet any) (err error) {
} else {
packetBytes = rawPacketBytes
}
err = queser.VarInt(len(packetBytes)).Encode(c.W)
err = proto_base.VarInt(len(packetBytes)).Encode(c.W)
if err != nil {
return
}
@@ -112,7 +112,7 @@ func (c *Conn) Send(packet any) (err error) {
}
func (c *Conn) Receive() (packet any, err error) {
var rawPacketLen queser.VarInt
var rawPacketLen proto_base.VarInt
rawPacketLen, err = rawPacketLen.Decode(c.R)
if err != nil {
return
@@ -124,7 +124,7 @@ func (c *Conn) Receive() (packet any, err error) {
rawPacketBuffer := bytes.NewBuffer(rawPacketBytes)
var packetBytes []byte
if c.CompressionThreshold > 0 {
var packetLen queser.VarInt
var packetLen proto_base.VarInt
packetLen, err = packetLen.Decode(rawPacketBuffer)
if err != nil {
return
@@ -147,7 +147,7 @@ func (c *Conn) Receive() (packet any, err error) {
packetBytes = rawPacketBytes
}
packetBuff := bytes.NewBuffer(packetBytes)
packet, err = generated.DecodePacket(c.Version, c.Actor.ReceiveDirection(), c.State, packetBuff)
packet, err = proto_generated.DecodePacket(c.Version, c.Actor.ReceiveDirection(), c.State, packetBuff)
if err != nil {
packet = UnCodablePacket{Err: err, Data: packetBytes, Partial: packet}
err = nil
@@ -158,7 +158,7 @@ func (c *Conn) Receive() (packet any, err error) {
func Connect(addr string) (ret *Conn, err error) {
ret = &Conn{}
ret.Version = "1.21.8"
ret.State = queser.Handshaking
ret.State = proto_base.Handshaking
ret.CompressionThreshold = -1
ret.Actor = Client
ret.Conn, err = net.Dial("tcp", addr)
@@ -173,7 +173,7 @@ func Connect(addr string) (ret *Conn, err error) {
func Servee(c net.Conn) (ret *Conn) {
ret = &Conn{}
ret.Version = "1.21.8"
ret.State = queser.Handshaking
ret.State = proto_base.Handshaking
ret.CompressionThreshold = -1
ret.Actor = Server
ret.Conn = c
+51
View File
@@ -0,0 +1,51 @@
package modules
import (
"errors"
"fmt"
"github.com/admin-else/strom/bot"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
type ConfigIgnorer struct {
*bot.Conn
}
func (c *ConfigIgnorer) Default(event any) (err error) {
fmt.Printf("%#v\n", event)
return
}
func (c *ConfigIgnorer) OnKnownPacks(packet v1_21_8.PacketCommonSelectKnownPacks) (err error) {
return c.Send(packet)
}
func (c *ConfigIgnorer) OnFinish(_ v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) {
err = c.Send(v1_21_8.ConfigurationToServerPacketFinishConfiguration{})
if err != nil {
return
}
c.State = proto_base.Play
err = bot.HandlerDone
return
}
func (c *ConfigIgnorer) OnPing(packet v1_21_8.ConfigurationToClientPacketPing) (err error) {
err = c.Send(v1_21_8.ConfigurationToServerPacketPong(packet))
return
}
func (c *ConfigIgnorer) OnKeepAlive(packet v1_21_8.ConfigurationToClientPacketKeepAlive) (err error) {
err = c.Send(v1_21_8.ConfigurationToServerPacketKeepAlive(packet))
return
}
func IgnoreConfig(c *bot.Conn) (err error) {
err = c.Start(&ConfigIgnorer{c})
if err != nil {
err = errors.Join(err, errors.New("failed to ignore config"))
}
return
}
+147
View File
@@ -0,0 +1,147 @@
package modules
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"errors"
"fmt"
"net"
"strconv"
"github.com/admin-else/strom/api"
"github.com/admin-else/strom/bot"
"github.com/admin-else/strom/crypto"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/data"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
type LoginClient struct {
*bot.Conn
Account api.Account
GivenAccount v1_21_8.LoginToClientPacketSuccess
}
func (s *LoginClient) Default(event any) (err error) {
err = fmt.Errorf("unexpected event: %#v", event)
return
}
func (s *LoginClient) OnCycle(_ bot.OnLoopCycle) (err error) {
return
}
func (s *LoginClient) OnStart(_ bot.OnStart) (err error) {
host, portStr, err := net.SplitHostPort(s.RemoteAddr().String())
if err != nil {
return
}
versionData, err := data.LookUpProtocolVersionByName(s.Version)
if err != nil {
return
}
port, err := strconv.Atoi(portStr)
if err != nil {
return
}
err = s.Send(v1_21_8.HandshakingToServerPacketSetProtocol{
ProtocolVersion: proto_base.VarInt(versionData.Version),
ServerHost: host,
ServerPort: uint16(port),
NextState: proto_base.VarInt(proto_base.Login),
})
if err != nil {
return
}
s.State = proto_base.Login
err = s.Send(v1_21_8.LoginToServerPacketLoginStart{Username: s.Account.Name, PlayerUUID: s.Account.Uuid})
return
}
func (s *LoginClient) OnCompress(compress v1_21_8.LoginToClientPacketCompress) (err error) {
s.CompressionThreshold = int32(compress.Threshold)
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.Ygg == "" {
err = fmt.Errorf("the account has no token so we cant join online servers")
return
}
serverId := crypto.AuthDigest([]byte(packet.ServerId), sharedSecret, packet.PublicKey)
err = s.Account.JoinServer(serverId)
if err != nil {
return
}
}
pubAny, err := x509.ParsePKIXPublicKey(packet.PublicKey)
if err != nil {
return
}
pub, ok := pubAny.(*rsa.PublicKey)
if !ok {
err = fmt.Errorf("public key is not rsa")
return
}
verifyTokenEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, packet.VerifyToken)
if err != nil {
return
}
sharedSecretEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, sharedSecret)
if err != nil {
return
}
err = s.Send(v1_21_8.LoginToServerPacketEncryptionBegin{SharedSecret: sharedSecretEnc, VerifyToken: verifyTokenEnc})
if err != nil {
return
}
var b cipher.Block
b, err = aes.NewCipher(sharedSecret)
if err != nil {
return
}
s.R = cipher.StreamReader{
S: crypto.NewCFB8Decrypt(b, sharedSecret),
R: s.Conn,
}
s.W = cipher.StreamWriter{
S: crypto.NewCFB8Encrypt(b, sharedSecret),
W: s.Conn,
}
return
}
func (s *LoginClient) OnSuccess(success v1_21_8.LoginToClientPacketSuccess) (err error) {
s.GivenAccount = success
err = s.Send(v1_21_8.LoginToServerPacketLoginAcknowledged{})
if err != nil {
return
}
s.State = proto_base.Configuration
err = bot.HandlerDone
return
}
func Login(c *bot.Conn, account api.Account) (err error) {
err = c.Start(&LoginClient{
Conn: c,
Account: account,
})
if err != nil {
err = errors.Join(err, errors.New("failed to login"))
}
return
}
+53
View File
@@ -0,0 +1,53 @@
package modules
import (
"fmt"
"strconv"
"strings"
"github.com/admin-else/strom/bot"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/data"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
type StatusClient struct {
*bot.Conn
Status string
}
func (s *StatusClient) Default(event any) (err error) {
err = fmt.Errorf("unexpected event: %v", event)
return
}
func (s *StatusClient) OnStart(_ bot.OnStart) (err error) {
parts := strings.Split(s.RemoteAddr().String(), ":")
versionData, err := data.LookUpProtocolVersionByName(s.Version)
if err != nil {
return
}
port, err := strconv.Atoi(parts[1])
if err != nil {
return
}
err = s.Send(v1_21_8.HandshakingToServerPacketSetProtocol{
ProtocolVersion: proto_base.VarInt(versionData.Version),
ServerHost: parts[0],
ServerPort: uint16(port),
NextState: proto_base.VarInt(proto_base.Status),
})
if err != nil {
return
}
s.State = proto_base.Status
err = s.Send(v1_21_8.StatusToServerPacketPingStart{})
return
}
func (s *StatusClient) OnStatus(p v1_21_8.StatusToClientPacketServerInfo) (err error) {
s.Status = p.Response
err = bot.HandlerDone
return
}
+63
View File
@@ -0,0 +1,63 @@
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 Timer struct {
Once []OnceTask
Interval []IntervalTask
Active bool
}
func (t *Timer) 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
}
func (t *Timer) Every(interval time.Duration, f func() error) {
t.Interval = append(t.Interval, IntervalTask{
F: f,
Interval: interval,
})
}
func (t *Timer) In(interval time.Duration, f func() error) {
t.Once = append(t.Once, OnceTask{
F: f,
Trigger: time.Now().Add(interval),
})
}
+4 -3
View File
@@ -5,17 +5,18 @@ import (
"crypto/x509"
"encoding/pem"
"github.com/admin-else/strom/api"
"github.com/google/uuid"
)
type SecureChatSession struct {
Account *Account
PlayerKeys PlayerKeys
Account *api.Account
PlayerKeys api.PlayerKeys
Private *rsa.PrivateKey
SessionId uuid.UUID
}
func NewSecureChat(a *Account) (s *SecureChatSession, err error) {
func NewSecureChat(a *api.Account) (s *SecureChatSession, err error) {
s = &SecureChatSession{Account: a}
s.PlayerKeys, err = a.FetchKeys()