mc root source folder cleanup

This commit is contained in:
adman
2026-06-27 13:54:54 +02:00
parent b2784574bd
commit 666f250b3e
480 changed files with 325 additions and 325 deletions
+43
View File
@@ -0,0 +1,43 @@
package client
import (
"context"
"net"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
)
// Connect connects to the given address note that it does not resolve the SRV records it's just a raw net.Dial
// You must close the connection when you're done with it
func Connect(addr string) (ret *proto.Conn, err error) {
s, err := Status(addr)
if err != nil {
return
}
ret, err = ConnectVersionLess(addr)
if err != nil {
return
}
err = ret.SetProtocolVersion(s.Version.Protocol)
return
}
func ConnectVersionLess(addr string) (ret *proto.Conn, err error) {
return ConnectVersionLessCtx(context.Background(), addr)
}
// ConnectVersionLess connects to the given address without setting the version you should set it
// when using this function.
// You must close the connection when you're done with it
func ConnectVersionLessCtx(ctx context.Context, addr string) (ret *proto.Conn, err error) {
ret = proto.NewConnCtx(ctx)
ret.Actor = proto_base.Client
ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr)
if err != nil {
return
}
ret.R = ret.Conn
ret.W = ret.Conn
return
}
+63
View File
@@ -0,0 +1,63 @@
package client
import (
"errors"
"fmt"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
)
type ConfigIgnorer struct {
*proto.Conn
}
func (c *ConfigIgnorer) OnStart() (err error) {
return
}
func (c *ConfigIgnorer) Default(e event.Anything) (err error) {
c.Log.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
return
}
func (c *ConfigIgnorer) OnKnownPacks(packet *v1_21_8.ConfigurationToClientPacketCommonSelectKnownPacks) (err error) {
return c.Send(&v1_21_8.ConfigurationToServerPacketCommonSelectKnownPacks{Packs: packet.Packs})
}
func (c *ConfigIgnorer) OnFinish(_ *v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) {
err = c.Send(&v1_21_8.ConfigurationToServerPacketFinishConfiguration{})
if err != nil {
return
}
c.SetState(proto_base.Play)
err = event.HandlerDoneErr{}
return
}
func (c *ConfigIgnorer) OnPing(packet *v1_21_8.ConfigurationToClientPacketPing) (err error) {
err = c.Send(&v1_21_8.ConfigurationToServerPacketPong{Id: packet.Id})
return
}
func (c *ConfigIgnorer) OnKeepAlive(packet *v1_21_8.ConfigurationToClientPacketKeepAlive) (err error) {
err = c.Send(&v1_21_8.ConfigurationToServerPacketKeepAlive{KeepAliveId: packet.KeepAliveId})
return
}
func IgnoreConfig(c *proto.Conn) (err error) {
ci := &ConfigIgnorer{c}
ci.RegisterCritical(ci.Default)
ci.RegisterUntilLatest(ci.OnKnownPacks)
ci.RegisterUntilLatest(ci.OnFinish)
ci.RegisterUntilLatest(ci.OnPing)
ci.RegisterUntilLatest(ci.OnKeepAlive)
err = ci.StartConn()
if err != nil {
err = errors.Join(err, errors.New("failed to ignore config"))
}
return
}
+126
View File
@@ -0,0 +1,126 @@
package client
import (
"context"
"errors"
"net"
"strconv"
"strings"
"time"
)
type DNSCheckFunc func(addr string) error
// DoDns resolves a DNS name to an IP address
// It should resolve a minecraft compatible address into something net.Dial
func DoDns(in string) (out string, host string, portN uint16, err error) {
return DoDNSChecked(in, nil)
}
// DoDNSChecked resolves a DNS name to an IP address
// It should resolve a minecraft compatible address into something net.Dial
// It also optionally accepts a function to "check" the address
func DoDNSChecked(in string, f DNSCheckFunc) (connectTo string, host string, portN uint16, err error) {
// https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34
host, port, err := net.SplitHostPort(in)
if err != nil {
var addrErr *net.AddrError
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
err = nil
host = in
port = "25565"
} else {
return
}
}
porti, err := strconv.Atoi(port)
if err != nil {
return
}
if f == nil {
f = func(addr string) error { return nil }
}
err = f(host)
if err != nil {
return
}
portN = uint16(porti)
outIP, err := net.ResolveIPAddr("ip", host)
if err != nil {
return
}
err = f(outIP.String())
if err != nil {
return
}
connectTo = outIP.String() + ":" + port
if portN != 25565 {
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second) // otherwise this takes 10+ seconds
defer cancel()
r := &net.Resolver{}
_, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host)
if len(srvs) == 0 {
return
}
host = strings.TrimSuffix(srvs[0].Target, ".")
portN = srvs[0].Port
outIP, err = net.ResolveIPAddr("ip", host)
if err != nil {
return
}
err = f(outIP.String())
if err != nil {
return
}
connectTo = outIP.String() + ":" + port
return
}
// DoDnsSimple resolves a DNS name to an IP address
// It should resolve a minecraft compatible address into something net.Dial
// It does not check the address
func DoDnsSimple(in string) (out string, err error) {
host, port, err := net.SplitHostPort(in)
if err != nil {
var addrErr *net.AddrError
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
err = nil
host = in
port = "25565"
} else {
return
}
}
porti, err := strconv.Atoi(port)
if err != nil {
return
}
portN := uint16(porti)
_, err = net.ResolveIPAddr("ip", host)
if err != nil {
return
}
out = host + ":" + strconv.Itoa(int(portN))
if portN != 25565 {
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second) // otherwise this takes 10+ seconds
defer cancel()
r := &net.Resolver{}
_, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host)
if len(srvs) == 0 {
return
}
host = strings.TrimSuffix(srvs[0].Target, ".")
portN = srvs[0].Port
out = host + ":" + strconv.Itoa(int(portN))
return
}
+27
View File
@@ -0,0 +1,27 @@
package client
import "testing"
func TestDoDNS(t *testing.T) {
type Case struct {
Input string
Host string
portN uint16
}
cases := []Case{
{"localhost", "localhost", 25565},
{"play.hypixel.net", "play.hypixel.net", 25565},
{"1f2d.net", "tcpshield.1f2d.net", 25565},
}
for _, c := range cases {
t.Logf("Testing %s", c.Input)
_, host, portN, err := DoDns(c.Input)
if err != nil {
t.Error(err)
continue
}
if host != c.Host || portN != c.portN {
t.Errorf("expected %s:%d, got %s:%d", c.Host, c.portN, host, portN)
}
}
}
+31
View File
@@ -0,0 +1,31 @@
package client
import (
"net"
"strconv"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
)
func MakeHandshakePacket(c *proto.Conn, nextState proto_base.State) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
return MakeHandshakePacketAddr(c, nextState, c.RemoteAddr().String())
}
// MakeHandshakePacketAddr gets a custom addr
func MakeHandshakePacketAddr(s *proto.Conn, nextState proto_base.State, addr string) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
p = &v1_21_8.HandshakingToServerPacketSetProtocol{NextState: int32(nextState), ProtocolVersion: s.ProtocolVersion}
var portStr string
p.ServerHost, portStr, err = net.SplitHostPort(addr)
if err != nil {
return
}
var portUint uint64
portUint, err = strconv.ParseUint(portStr, 10, 16)
if err != nil {
return
}
p.ServerPort = uint16(portUint)
return
}
+178
View File
@@ -0,0 +1,178 @@
package client
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"errors"
"github.com/admin-else/strom/mc/api"
"github.com/admin-else/strom/mc/crypto"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
"github.com/admin-else/strom/mc/text"
)
type LoginClient struct {
*proto.Conn
Account *api.Account
ServerHost string
ServerPort uint16
GivenAccount *v1_21_8.LoginToClientPacketSuccess
}
var (
NoTokenErr = errors.New("the account has no token so we cant join online servers")
BadPublicKeyTypeErr = errors.New("the public key is not rsa")
FailedToLoginErr = errors.New("failed to login")
)
type KickedDuringLoginErr struct {
text.Component
}
func (k KickedDuringLoginErr) Error() string {
return k.String()
}
func (s *LoginClient) OnDefault(event event.Unhandled) (err error) {
if _, ok := event.Val.(proto_base.EncodeDecodeAble); !ok {
return
}
s.Log.Warn("unexpected packet during login client", "packet", event.Val)
return
}
func (s *LoginClient) OnClose(_ event.Close) (err error) {
return
}
func (s *LoginClient) OnCompress(compress *v1_21_8.LoginToClientPacketCompress) (err error) {
s.SetCompressionThreshold(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 = NoTokenErr
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 = BadPublicKeyTypeErr
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
}
err = s.SetSecret(sharedSecret)
return
}
func (s *LoginClient) OnDisconnect(packet *v1_21_8.LoginToClientPacketDisconnect) (err error) {
var reason text.Component
err = json.Unmarshal([]byte(packet.Reason), &reason)
if err != nil {
return
}
err = KickedDuringLoginErr{reason}
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.SetState(proto_base.Configuration)
err = event.HandlerDoneErr{}
return
}
func (s *LoginClient) OnStart() (err error) {
return
}
func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
lc := &LoginClient{
Conn: c,
Account: account,
}
lc.RegisterCritical(lc.OnDefault)
lc.RegisterCritical(lc.OnClose)
lc.RegisterCriticalUntilLatest(lc.OnCompress)
lc.RegisterCriticalUntilLatest(lc.OnEncrypt)
lc.RegisterCriticalUntilLatest(lc.OnDisconnect)
lc.RegisterCriticalUntilLatest(lc.OnSuccess)
p, err := MakeHandshakePacket(lc.Conn, proto_base.Login)
if err != nil {
return
}
err = lc.Send(p)
if err != nil {
return
}
lc.SetState(proto_base.Login)
err = lc.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: lc.Account.Name, PlayerUUID: lc.Account.Uuid})
err = lc.StartConn()
if err != nil {
err = errors.Join(FailedToLoginErr, err)
}
return
}
func LoginNoSrv(connectTo string, account *api.Account) (c *proto.Conn, err error) {
c, err = Connect(connectTo)
if err != nil {
return
}
err = LoginRaw(c, account)
return
}
func Login(connectTo string, account *api.Account) (c *proto.Conn, err error) {
connectTo, err = DoDnsSimple(connectTo)
if err != nil {
return
}
c, err = Connect(connectTo)
if err != nil {
return
}
err = LoginRaw(c, account)
return
}
+90
View File
@@ -0,0 +1,90 @@
package client
import (
"context"
"encoding/json"
"time"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
"github.com/admin-else/strom/mc/server"
)
type StatusClient struct {
*proto.Conn
Status string
PingSendTime, PingReceiveTime time.Time
DoPingRoundTripTime bool
}
func (s *StatusClient) OnStatus(p *v1_21_8.StatusToClientPacketServerInfo) (err error) {
s.Status = p.Response
s.PingSendTime = time.Now()
if s.DoPingRoundTripTime {
err = s.Send(&v1_21_8.StatusToServerPacketPing{Time: s.PingSendTime.UnixMilli()})
} else {
err = event.HandlerDoneErr{}
}
return
}
func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
s.PingReceiveTime = time.Now()
err = event.HandlerDoneErr{}
return
}
// StatusRaw returns a StatusClient that is not connected to a server.
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
// It does not resolve SRV records.
func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
c, err := ConnectVersionLess(addr)
if err != nil {
return
}
defer c.Close()
err = c.SetVersion("1.21.8")
if err != nil {
return
}
s = &StatusClient{
Conn: c,
}
s.RegisterCriticalUntilLatest(s.OnStatus)
s.RegisterCriticalUntilLatest(s.OnPong)
p, err := MakeHandshakePacketAddr(s.Conn, proto_base.Status, addr)
if err != nil {
return
}
err = s.Send(p)
if err != nil {
return
}
s.SetState(proto_base.Status)
err = s.Send(&v1_21_8.StatusToServerPacketPingStart{})
err = s.StartConn()
return
}
// StatusNoDns is like Status but does not resolve SRV records.
func StatusNoDns(addr string) (status server.StatusResponse, err error) {
s, err := StatusRaw(context.Background(), addr)
if err != nil {
return
}
err = json.Unmarshal([]byte(s.Status), &status)
return
}
// Status resolves the status of the minecraft server at the given address.
func Status(addr string) (status server.StatusResponse, err error) {
addr, err = DoDnsSimple(addr)
if err != nil {
return
}
return StatusNoDns(addr)
}