mc root source folder cleanup
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/admin-else/strom/mc/data"
|
||||
"github.com/admin-else/strom/mc/event"
|
||||
"github.com/admin-else/strom/mc/nbt"
|
||||
"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 ConfigServer struct {
|
||||
*proto.Conn
|
||||
registryData map[string]any
|
||||
}
|
||||
|
||||
func ServeConfig(c *proto.Conn) (err error) {
|
||||
cs := &ConfigServer{Conn: c}
|
||||
n, err := data.LoadRegistry(c.Version)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Send(&v1_21_8.ConfigurationToClientPacketFeatureFlags{Features: []string{"minecraft:vanilla"}})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cs.registryData = n.Value.(map[string]any)
|
||||
packet := &v1_21_8.ConfigurationToClientPacketCommonSelectKnownPacks{}
|
||||
for _, pAny := range cs.registryData["KnownPacks"].([]any) {
|
||||
p := pAny.(map[string]any)
|
||||
packet.Packs = append(packet.Packs, struct {
|
||||
Namespace string
|
||||
Id string
|
||||
Version string
|
||||
}{p["Namespace"].(string), p["Id"].(string), p["Version"].(string)})
|
||||
}
|
||||
err = c.Send(packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cs.RegisterCriticalUntilLatest(cs.OnSelectKnownPacksResponse)
|
||||
cs.RegisterCriticalUntilLatest(cs.OnFinishConfiguration)
|
||||
return cs.StartConn()
|
||||
}
|
||||
|
||||
func (c *ConfigServer) OnSelectKnownPacksResponse(_ *v1_21_8.ConfigurationToServerPacketCommonSelectKnownPacks) (err error) {
|
||||
for _, p := range c.registryData["Registries"].([]any) {
|
||||
packet := &v1_21_8.ConfigurationToClientPacketRegistryData{Id: p.(map[string]any)["Id"].(string)}
|
||||
for _, entry := range p.(map[string]any)["Entries"].([]any) {
|
||||
entryTyped := entry.(map[string]any)
|
||||
valAny, ok := entryTyped["Value"]
|
||||
var val *nbt.Anon = nil
|
||||
if ok {
|
||||
val = &nbt.Anon{Value: valAny}
|
||||
}
|
||||
packet.Entries = append(packet.Entries, struct {
|
||||
Key string
|
||||
Value *nbt.Anon
|
||||
}{entryTyped["TagType"].(string), val})
|
||||
}
|
||||
err = c.Send(packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
tagsPacket := &v1_21_8.ConfigurationToClientPacketTags{}
|
||||
for _, p := range c.registryData["Tags"].([]any) {
|
||||
p := p.(map[string]any)
|
||||
tags := v1_21_8.Tags{}
|
||||
for _, entry := range p["Entries"].([]any) {
|
||||
entry := entry.(map[string]any)
|
||||
tags.Val = append(tags.Val, struct {
|
||||
TagName string
|
||||
Entries []int32
|
||||
}{
|
||||
TagName: entry["TagName"].(string),
|
||||
Entries: entry["Entries"].([]int32),
|
||||
})
|
||||
}
|
||||
tagsPacket.Tags = append(tagsPacket.Tags, struct {
|
||||
TagType string
|
||||
Tags v1_21_8.Tags
|
||||
}{TagType: p["TagType"].(string), Tags: tags})
|
||||
}
|
||||
err = c.Send(tagsPacket)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Send(&v1_21_8.ConfigurationToClientPacketFinishConfiguration{})
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ConfigServer) OnFinishConfiguration(_ *v1_21_8.ConfigurationToServerPacketFinishConfiguration) (err error) {
|
||||
c.SetState(proto_base.Play)
|
||||
err = event.HandlerDoneErr{}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/admin-else/strom/mc/nbt"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
NotClientErr = errors.New("not a servee")
|
||||
CantKickInStateErr = errors.New("cant kick in this state")
|
||||
)
|
||||
|
||||
func Kick(c *proto.Conn, reason *text.Component, status string) (err error) {
|
||||
if c.Actor != proto_base.Servee {
|
||||
err = NotClientErr
|
||||
return
|
||||
}
|
||||
switch c.State() {
|
||||
case proto_base.Handshaking:
|
||||
err = CantKickInStateErr
|
||||
case proto_base.Status:
|
||||
if status == "" {
|
||||
var b []byte
|
||||
b, err = reason.MarshalJSON()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
status = fmt.Sprintf("{\"description\":%s}", string(b))
|
||||
}
|
||||
err = c.Send(&v1_21_8.StatusToClientPacketServerInfo{Response: status})
|
||||
case proto_base.Login:
|
||||
var b []byte
|
||||
b, err = reason.MarshalJSON()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Send(&v1_21_8.LoginToClientPacketDisconnect{Reason: string(b)})
|
||||
case proto_base.Configuration:
|
||||
err = c.Send(&v1_21_8.ConfigurationToClientPacketDisconnect{Reason: nbt.Anon{Value: reason.ToNBT()}})
|
||||
case proto_base.Play:
|
||||
err = c.Send(&v1_21_8.PlayToClientPacketKickDisconnect{Reason: nbt.Anon{Value: reason.ToNBT()}})
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
api2 "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/google/uuid"
|
||||
)
|
||||
|
||||
var StatusServedErr = errors.New("status served")
|
||||
|
||||
var UnexpectedStatusRequest = UnexpectedNextStateError{proto_base.Status}
|
||||
|
||||
type NameAndUUID struct {
|
||||
Name string
|
||||
UUID uuid.UUID
|
||||
}
|
||||
|
||||
type UnexpectedNextStateError struct {
|
||||
NextState proto_base.State
|
||||
}
|
||||
|
||||
func (u UnexpectedNextStateError) Error() string {
|
||||
return fmt.Sprintf("unexpected next state: %v", u.NextState)
|
||||
}
|
||||
|
||||
type LoginServer struct {
|
||||
*proto.Conn
|
||||
Encryption, OnlineMode bool
|
||||
ServerHost string
|
||||
ServerPort uint16
|
||||
Requested NameAndUUID
|
||||
Given *NameAndUUID
|
||||
CompressionThreshold int32
|
||||
Status []byte
|
||||
CompatibleVersions []int32
|
||||
|
||||
ServerId string // always empty for now
|
||||
VerifyToken []byte
|
||||
PrivateKey *rsa.PrivateKey
|
||||
PublicKeyBytes []byte
|
||||
}
|
||||
|
||||
func (l *LoginServer) OnHandshake(packet *v1_21_8.HandshakingToServerPacketSetProtocol) (err error) {
|
||||
l.SetState(proto_base.State(packet.NextState))
|
||||
l.ServerHost = packet.ServerHost
|
||||
l.ServerPort = packet.ServerPort
|
||||
|
||||
if packet.NextState == int32(proto_base.Status) && l.Status != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if packet.NextState != int32(proto_base.Login) {
|
||||
err = UnexpectedNextStateError{proto_base.State(packet.NextState)}
|
||||
return
|
||||
}
|
||||
|
||||
if len(l.CompatibleVersions) != 0 && !slices.Contains(l.CompatibleVersions, packet.ProtocolVersion) {
|
||||
err = fmt.Errorf("incompatible protocol version please use one of %v you can look up the real corresponding version at https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol_version_numbers", l.CompatibleVersions)
|
||||
return
|
||||
}
|
||||
|
||||
err = l.SetProtocolVersion(packet.ProtocolVersion)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *LoginServer) OnStatusRequest(_ *v1_21_8.StatusToServerPacketPingStart) (err error) {
|
||||
err = l.Send(&v1_21_8.StatusToClientPacketServerInfo{Response: string(l.Status)})
|
||||
return
|
||||
}
|
||||
|
||||
func (l *LoginServer) OnStatusPing(packet *v1_21_8.StatusToServerPacketPing) (err error) {
|
||||
err = l.Send(&v1_21_8.StatusToClientPacketPing{Time: packet.Time})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return event.HandlerDoneErr{Return: StatusServedErr}
|
||||
}
|
||||
|
||||
func (l *LoginServer) SetCompressionThreshold(threshold int32) (err error) {
|
||||
err = l.Send(&v1_21_8.LoginToClientPacketCompress{Threshold: threshold})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
l.RawConn.SetCompressionThreshold(threshold)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *LoginServer) OnLoginStart(packet *v1_21_8.LoginToServerPacketLoginStart) (err error) {
|
||||
if l.CompressionThreshold < 0 {
|
||||
err = l.SetCompressionThreshold(l.CompressionThreshold)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
l.Requested = NameAndUUID{packet.Username, packet.PlayerUUID}
|
||||
if l.Encryption {
|
||||
return l.Encrypt()
|
||||
}
|
||||
return l.FinishLogin()
|
||||
}
|
||||
|
||||
func (l *LoginServer) OnLoginAcknowledged(_ *v1_21_8.LoginToServerPacketLoginAcknowledged) (err error) {
|
||||
l.SetState(proto_base.Configuration)
|
||||
err = event.HandlerDoneErr{}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *LoginServer) OnDefault(event event.Unhandled) (err error) {
|
||||
if _, ok := event.Val.(proto_base.EncodeDecodeAble); !ok {
|
||||
return
|
||||
}
|
||||
l.Log.Warn("unexpected packet during login serving", "packet", event.Val)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *LoginServer) Encrypt() (err error) {
|
||||
l.PrivateKey, err = rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
l.VerifyToken = make([]byte, 4)
|
||||
_, _ = rand.Read(l.VerifyToken)
|
||||
l.PublicKeyBytes, err = x509.MarshalPKIXPublicKey(l.PrivateKey.Public())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return l.Send(&v1_21_8.LoginToClientPacketEncryptionBegin{
|
||||
ServerId: "",
|
||||
PublicKey: l.PublicKeyBytes,
|
||||
VerifyToken: l.VerifyToken,
|
||||
ShouldAuthenticate: l.OnlineMode,
|
||||
})
|
||||
}
|
||||
|
||||
var ErrEncryptionFailedVerifyToken = errors.New("clients verify token does not match server verify token")
|
||||
|
||||
func (l *LoginServer) OnEncryptionResponse(packet *v1_21_8.LoginToServerPacketEncryptionBegin) (err error) {
|
||||
plain, err := l.PrivateKey.Decrypt(rand.Reader, packet.VerifyToken, &rsa.PKCS1v15DecryptOptions{}) // Maybe supply 1024????
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !slices.Equal(plain, l.VerifyToken) {
|
||||
err = ErrEncryptionFailedVerifyToken
|
||||
return
|
||||
}
|
||||
sharedSecret, err := l.PrivateKey.Decrypt(rand.Reader, packet.SharedSecret, &rsa.PKCS1v15DecryptOptions{}) // Maybe supply 1024????
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = l.SetSecret(sharedSecret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
serverId := crypto.AuthDigest([]byte(l.ServerId), sharedSecret, l.PublicKeyBytes)
|
||||
if l.OnlineMode {
|
||||
_, err = api2.HasJoined(l.Requested.Name, serverId, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = l.FinishLogin()
|
||||
return
|
||||
}
|
||||
|
||||
func (l *LoginServer) FinishLogin() (err error) {
|
||||
if l.Given == nil {
|
||||
l.Given = &l.Requested
|
||||
}
|
||||
err = l.Send(&v1_21_8.LoginToClientPacketSuccess{
|
||||
Uuid: l.Given.UUID,
|
||||
Username: l.Given.Name,
|
||||
Properties: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
type LoginServerSetting func(*LoginServer)
|
||||
|
||||
func WithOtherAccount(a *api2.Account) LoginServerSetting {
|
||||
return func(s *LoginServer) {
|
||||
if s.Given == nil {
|
||||
return
|
||||
}
|
||||
s.Given = &NameAndUUID{
|
||||
Name: a.Name,
|
||||
UUID: a.Uuid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithRawStatus(status []byte) LoginServerSetting {
|
||||
return func(s *LoginServer) {
|
||||
s.Status = status
|
||||
}
|
||||
}
|
||||
|
||||
func WithStatus(response StatusResponse) LoginServerSetting {
|
||||
var status []byte
|
||||
status, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
slog.Debug("With status", "status", status)
|
||||
return WithRawStatus(status)
|
||||
}
|
||||
|
||||
func WithCompatibleVersionsRange(start, end int32) LoginServerSetting {
|
||||
var versions []int32
|
||||
for i := start; i <= end; i++ {
|
||||
versions = append(versions, i)
|
||||
}
|
||||
return WithCompatibleVersions(versions...)
|
||||
}
|
||||
|
||||
func WithCompatibleVersions(versions ...int32) LoginServerSetting {
|
||||
return func(s *LoginServer) {
|
||||
s.CompatibleVersions = versions
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutOnlineMode() LoginServerSetting {
|
||||
return func(s *LoginServer) {
|
||||
s.OnlineMode = false
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutEncryption() LoginServerSetting {
|
||||
return func(s *LoginServer) {
|
||||
s.Encryption = false
|
||||
}
|
||||
}
|
||||
|
||||
func ServeLogin(c *proto.Conn, settings ...LoginServerSetting) (ret *LoginServer, err error) {
|
||||
ret = &LoginServer{Conn: c}
|
||||
ret.OnlineMode = true
|
||||
ret.Encryption = true
|
||||
|
||||
ret.RegisterCritical(ret.OnDefault)
|
||||
ret.RegisterCriticalUntilLatest(ret.OnHandshake)
|
||||
ret.RegisterCriticalUntilLatest(ret.OnLoginStart)
|
||||
ret.RegisterCriticalUntilLatest(ret.OnLoginAcknowledged)
|
||||
ret.RegisterCriticalUntilLatest(ret.OnStatusRequest)
|
||||
ret.RegisterCriticalUntilLatest(ret.OnStatusPing)
|
||||
ret.RegisterCriticalUntilLatest(ret.OnEncryptionResponse)
|
||||
for _, s := range settings {
|
||||
s(ret)
|
||||
}
|
||||
if ret.OnlineMode && !ret.Encryption {
|
||||
err = fmt.Errorf("online mode requires encryption")
|
||||
return
|
||||
}
|
||||
|
||||
ret.CompressionThreshold = 256
|
||||
err = ret.StartConn()
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package server
|
||||
|
||||
type SchematicServer interface {
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"github.com/admin-else/strom/mc/proto_base"
|
||||
"github.com/admin-else/strom/mc/text"
|
||||
)
|
||||
|
||||
func Servee(c net.Conn) (ret *proto.Conn) {
|
||||
ret = proto.NewConn()
|
||||
ret.Actor = proto_base.Servee
|
||||
ret.Conn = c
|
||||
ret.R = c
|
||||
ret.W = c
|
||||
|
||||
ret.Log = ret.Log.With("conn", c.RemoteAddr())
|
||||
return
|
||||
}
|
||||
|
||||
type ConnAcceptor func(c net.Conn) (err error)
|
||||
|
||||
type Factory func(c *proto.Conn) (h ConnAcceptor, err error)
|
||||
|
||||
func ServeClient(cNet net.Conn, factory Factory) {
|
||||
c := Servee(cNet)
|
||||
defer c.Close()
|
||||
h, err := factory(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = h(c)
|
||||
if errors.Is(err, StatusServedErr) {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
err = Kick(c, text.Pretty(err.Error()), "")
|
||||
}
|
||||
}
|
||||
|
||||
func StartServerWithFactory(listenAddr string, factory Factory) (err error) {
|
||||
l, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var cNet net.Conn
|
||||
for {
|
||||
cNet, err = l.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go ServeClient(cNet, factory)
|
||||
}
|
||||
}
|
||||
|
||||
var ShutDownServerErr = errors.New("server shutting down")
|
||||
|
||||
func StartServerWithOnConn(listenAddr string, onConn func(c *proto.Conn) (err error)) (err error) {
|
||||
l, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
running := true
|
||||
for running {
|
||||
var cNet net.Conn
|
||||
cNet, err = l.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
c := Servee(cNet)
|
||||
defer c.Close()
|
||||
connErr := onConn(c)
|
||||
if errors.Is(connErr, ShutDownServerErr) {
|
||||
running = false
|
||||
}
|
||||
c.Log.Error("closing connection", "err", connErr)
|
||||
if connErr != nil {
|
||||
connErr = Kick(c, text.Pretty(connErr.Error()), "")
|
||||
}
|
||||
}()
|
||||
}
|
||||
err = ShutDownServerErr
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||
"github.com/admin-else/strom/mc/text"
|
||||
)
|
||||
|
||||
// These are separate types so a status definition looks less ugly
|
||||
|
||||
type StatusResponseVersion struct {
|
||||
Name *text.Component `json:"name"`
|
||||
Protocol int32 `json:"protocol"`
|
||||
}
|
||||
|
||||
type StatusResponseSample struct {
|
||||
Name *text.Component `json:"name"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
type StatusResponsePlayers struct {
|
||||
Max int `json:"max"`
|
||||
Online int `json:"online"`
|
||||
Sample []StatusResponseSample `json:"sample"`
|
||||
}
|
||||
|
||||
// StatusResponse here is the structure I used: https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping#Status_Response
|
||||
type StatusResponse struct {
|
||||
Version StatusResponseVersion `json:"version"`
|
||||
Players StatusResponsePlayers `json:"players"`
|
||||
Description *text.Component `json:"description"`
|
||||
Favicon string `json:"favicon"`
|
||||
EnforcesSecureChat bool `json:"enforcesSecureChat"`
|
||||
}
|
||||
|
||||
// MustMarshal is a utility for marshalling StatusResponse to JSON
|
||||
func (s StatusResponse) MustMarshal() []byte {
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type StatusServer struct {
|
||||
*proto.Conn
|
||||
Status string
|
||||
}
|
||||
|
||||
func (p *StatusServer) OnStatusRequest(_ *v1_21_8.StatusToServerPacketPingStart) (err error) {
|
||||
return p.Send(&v1_21_8.StatusToClientPacketServerInfo{Response: p.Status})
|
||||
}
|
||||
|
||||
func (p *StatusServer) OnStatusPing(packet *v1_21_8.StatusToServerPacketPing) (err error) {
|
||||
return p.Send(&v1_21_8.StatusToClientPacketPing{Time: packet.Time})
|
||||
}
|
||||
|
||||
func ServeStatus(c *proto.Conn, s StatusResponse) (err error) {
|
||||
status, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
server := &StatusServer{
|
||||
Conn: c,
|
||||
Status: string(status),
|
||||
}
|
||||
server.RegisterUntilLatest(server.OnStatusRequest)
|
||||
server.RegisterUntilLatest(server.OnStatusPing)
|
||||
err = server.StartLoop()
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user