Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fd5324064 | ||
|
|
df0e5e354b | ||
|
|
23b5a5d9b7 | ||
|
|
46770ed980 | ||
|
|
6179c5f6b6 |
+64
-37
@@ -9,32 +9,40 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/crypto"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Servers struct {
|
||||||
|
ServicesHost string
|
||||||
|
SessionHost string
|
||||||
|
ProfilesHost string
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultServers = &Servers{
|
||||||
|
ServicesHost: "https://api.minecraftservices.com",
|
||||||
|
SessionHost: "https://sessionserver.mojang.com",
|
||||||
|
ProfilesHost: "https://api.mojang.com",
|
||||||
|
}
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
|
*Servers
|
||||||
Name string
|
Name string
|
||||||
Uuid uuid.UUID
|
Uuid uuid.UUID
|
||||||
Ygg string
|
Ygg string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
BaseUrlApi = "https://api.minecraftservices.com"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) {
|
func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) {
|
||||||
var b []byte
|
var b []byte
|
||||||
if method != "GET" {
|
if method != "GET" {
|
||||||
b, err = json.Marshal(from)
|
b, err = json.Marshal(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := http.NewRequest(method, url, bytes.NewReader(b))
|
r, err := http.NewRequest(method, url, bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Header.Set("Content-Type", "application/json")
|
r.Header.Set("Content-Type", "application/json")
|
||||||
@@ -44,21 +52,22 @@ func (a *Account) doMcApiRequest(method string, url string, from any, to any) (e
|
|||||||
|
|
||||||
resp, err := http.DefaultClient.Do(r)
|
resp, err := http.DefaultClient.Do(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
|
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
|
||||||
return fmt.Errorf("bad status code %v", resp.StatusCode)
|
return fmt.Errorf("bad status code %v", resp.StatusCode)
|
||||||
}
|
}
|
||||||
b, err = io.ReadAll(resp.Body)
|
b, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(b, to)
|
err = json.Unmarshal(b, to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlayerKeys struct {
|
type PlayerKeys struct {
|
||||||
@@ -73,7 +82,7 @@ type PlayerKeys struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) FetchKeys() (keys PlayerKeys, err error) {
|
func (a *Account) FetchKeys() (keys PlayerKeys, err error) {
|
||||||
err = a.doMcApiRequest("POST", BaseUrlApi+"/player/certificates", nil, &keys)
|
err = a.doMcApiRequest("POST", a.ServicesHost+"/player/certificates", nil, &keys)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,10 +99,12 @@ func (a *Account) JoinServer(serverId string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp, err := http.Post("https://sessionserver.mojang.com/session/minecraft/join", "application/json", bytes.NewReader(body))
|
resp, err := http.Post(a.SessionHost+"/session/minecraft/join", "application/json", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != 204 {
|
if resp.StatusCode != 204 {
|
||||||
body, err = io.ReadAll(resp.Body)
|
body, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -112,17 +123,10 @@ type NameChangResp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) NameChangeInfo() (r NameChangResp, err error) {
|
func (a *Account) NameChangeInfo() (r NameChangResp, err error) {
|
||||||
err = a.doMcApiRequest("GET", "https://api.minecraftservices.com/minecraft/profile/namechange", nil, &r)
|
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile/namechange", nil, &r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOfflineAccount(name string) *Account {
|
|
||||||
return &Account{
|
|
||||||
Name: name,
|
|
||||||
Uuid: crypto.FromOfflinePlayer(name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlayerProfile represents the player's profile information
|
// PlayerProfile represents the player's profile information
|
||||||
type PlayerProfile struct {
|
type PlayerProfile struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
@@ -143,7 +147,7 @@ type PlayerProfile struct {
|
|||||||
|
|
||||||
// QueryProfile queries the player's profile
|
// QueryProfile queries the player's profile
|
||||||
func (a *Account) QueryProfile() (profile PlayerProfile, err error) {
|
func (a *Account) QueryProfile() (profile PlayerProfile, err error) {
|
||||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile", nil, &profile)
|
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile", nil, &profile)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +185,7 @@ type PlayerAttributes struct {
|
|||||||
|
|
||||||
// QueryAttributes queries the player's attributes
|
// QueryAttributes queries the player's attributes
|
||||||
func (a *Account) QueryAttributes() (attrs PlayerAttributes, err error) {
|
func (a *Account) QueryAttributes() (attrs PlayerAttributes, err error) {
|
||||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/player/attributes", nil, &attrs)
|
err = a.doMcApiRequest("GET", a.ServicesHost+"/player/attributes", nil, &attrs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +198,7 @@ type ModifyAttributesRequest struct {
|
|||||||
|
|
||||||
// ModifyAttributes modifies the player's attributes
|
// ModifyAttributes modifies the player's attributes
|
||||||
func (a *Account) ModifyAttributes(req ModifyAttributesRequest) (attrs PlayerAttributes, err error) {
|
func (a *Account) ModifyAttributes(req ModifyAttributesRequest) (attrs PlayerAttributes, err error) {
|
||||||
err = a.doMcApiRequest("POST", BaseUrlApi+"/player/attributes", req, &attrs)
|
err = a.doMcApiRequest("POST", a.ServicesHost+"/player/attributes", req, &attrs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,24 +209,23 @@ type BlockedUsers struct {
|
|||||||
|
|
||||||
// GetBlocklist gets the list of blocked users
|
// GetBlocklist gets the list of blocked users
|
||||||
func (a *Account) GetBlocklist() (blocklist BlockedUsers, err error) {
|
func (a *Account) GetBlocklist() (blocklist BlockedUsers, err error) {
|
||||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/privacy/blocklist", nil, &blocklist)
|
err = a.doMcApiRequest("GET", a.ServicesHost+"/privacy/blocklist", nil, &blocklist)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckNameAvailability checks if a name is available
|
|
||||||
type NameAvailability struct {
|
type NameAvailability struct {
|
||||||
Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED
|
Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckNameAvailability checks if a name is available
|
// CheckNameAvailability checks if a name is available
|
||||||
func (a *Account) CheckNameAvailability(name string) (avail NameAvailability, err error) {
|
func (a *Account) CheckNameAvailability(name string) (avail NameAvailability, err error) {
|
||||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile/name/"+name+"/available", nil, &avail)
|
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile/name/"+name+"/available", nil, &avail)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeName changes the player's name
|
// ChangeName changes the player's name
|
||||||
func (a *Account) ChangeName(newName string) (profile PlayerProfile, err error) {
|
func (a *Account) ChangeName(newName string) (profile PlayerProfile, err error) {
|
||||||
err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/name/"+newName, nil, &profile)
|
err = a.doMcApiRequest("PUT", a.ServicesHost+"/minecraft/profile/name/"+newName, nil, &profile)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,19 +237,19 @@ type ChangeSkinRequest struct {
|
|||||||
|
|
||||||
// ChangeSkin changes the player's skin
|
// ChangeSkin changes the player's skin
|
||||||
func (a *Account) ChangeSkin(req ChangeSkinRequest) (profile PlayerProfile, err error) {
|
func (a *Account) ChangeSkin(req ChangeSkinRequest) (profile PlayerProfile, err error) {
|
||||||
err = a.doMcApiRequest("POST", BaseUrlApi+"/minecraft/profile/skins", req, &profile)
|
err = a.doMcApiRequest("POST", a.ServicesHost+"/minecraft/profile/skins", req, &profile)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetSkin resets the player's skin to default
|
// ResetSkin resets the player's skin to default
|
||||||
func (a *Account) ResetSkin() (profile PlayerProfile, err error) {
|
func (a *Account) ResetSkin() (profile PlayerProfile, err error) {
|
||||||
err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/skins/active", nil, &profile)
|
err = a.doMcApiRequest("DELETE", a.ServicesHost+"/minecraft/profile/skins/active", nil, &profile)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// HideCape hides the player's active cape
|
// HideCape hides the player's active cape
|
||||||
func (a *Account) HideCape() (profile PlayerProfile, err error) {
|
func (a *Account) HideCape() (profile PlayerProfile, err error) {
|
||||||
err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/capes/active", nil, &profile)
|
err = a.doMcApiRequest("DELETE", a.ServicesHost+"/minecraft/profile/capes/active", nil, &profile)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,29 +261,53 @@ type ShowCapeRequest struct {
|
|||||||
// ShowCape shows a specific cape
|
// ShowCape shows a specific cape
|
||||||
func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) {
|
func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) {
|
||||||
req := ShowCapeRequest{CapeID: capeID}
|
req := ShowCapeRequest{CapeID: capeID}
|
||||||
err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/capes/active", req, &profile)
|
err = a.doMcApiRequest("PUT", a.ServicesHost+"/minecraft/profile/capes/active", req, &profile)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckGiftCodeValidity checks if a gift code is valid
|
// CheckGiftCodeValidity checks if a gift code is valid
|
||||||
func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error) {
|
func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error) {
|
||||||
url := BaseUrlApi + "/productvoucher/giftcode"
|
url := a.ServicesHost + "/productvoucher/" + giftCode
|
||||||
r, err := http.NewRequest("GET", url, nil)
|
r, err := http.NewRequest("GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Header.Set("Authorization", "Bearer "+a.Ygg)
|
r.Header.Set("Authorization", "Bearer "+a.Ygg)
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(r)
|
resp, err := http.DefaultClient.Do(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Returns HTTP 200 or 204 if valid, 404 if invalid
|
// Returns HTTP 200 or 204 if valid, 404 if invalid
|
||||||
if resp.StatusCode == 200 || resp.StatusCode == 204 {
|
if resp.StatusCode == 200 || resp.StatusCode == 204 {
|
||||||
return true, nil
|
valid = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountFromYGG(ygg string) (a *Account, err error) {
|
||||||
|
a = &Account{
|
||||||
|
Ygg: ygg,
|
||||||
|
Servers: DefaultServers,
|
||||||
|
}
|
||||||
|
p, err := a.QueryProfile()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.Name = p.Name
|
||||||
|
a.Uuid, err = uuid.Parse(strings.ReplaceAll(p.ID, "-", ""))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccount(name string, uuid uuid.UUID, ygg string) (a *Account) {
|
||||||
|
return &Account{
|
||||||
|
Name: name,
|
||||||
|
Uuid: uuid,
|
||||||
|
Ygg: ygg,
|
||||||
|
Servers: DefaultServers,
|
||||||
}
|
}
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import "github.com/admin-else/strom/crypto"
|
||||||
|
|
||||||
|
func NewOfflineAccount(name string) *Account {
|
||||||
|
return &Account{
|
||||||
|
Name: name,
|
||||||
|
Uuid: crypto.FromOfflinePlayer(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,9 +20,6 @@ func (c *ConfigIgnorer) OnStart() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigIgnorer) Default(e event.Anything) (err error) {
|
func (c *ConfigIgnorer) Default(e event.Anything) (err error) {
|
||||||
if e.Val == (event.Tick{}) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
slog.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
|
slog.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ func MakeHandshakePacket(c *proto.Conn, nextState proto_base.State) (p *v1_21_8.
|
|||||||
return MakeHandshakePacketAddr(c, nextState, c.RemoteAddr().String())
|
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) {
|
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}
|
p = &v1_21_8.HandshakingToServerPacketSetProtocol{NextState: int32(nextState), ProtocolVersion: s.ProtocolVersion}
|
||||||
var portStr string
|
var portStr string
|
||||||
|
|||||||
+16
-8
@@ -46,10 +46,6 @@ func (s *LoginClient) OnDefault(event event.Unhandled) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LoginClient) OnCycle(_ event.Tick) (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LoginClient) OnClose(_ event.Close) (err error) {
|
func (s *LoginClient) OnClose(_ event.Close) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -127,13 +123,12 @@ func (s *LoginClient) OnStart() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Login(c *proto.Conn, account *api.Account) (err error) {
|
func LoginRaw(c *proto.Conn, account *api.Account, addr string) (err error) {
|
||||||
lc := &LoginClient{
|
lc := &LoginClient{
|
||||||
Conn: c,
|
Conn: c,
|
||||||
Account: account,
|
Account: account,
|
||||||
}
|
}
|
||||||
lc.RegisterCritical(lc.OnDefault)
|
lc.RegisterCritical(lc.OnDefault)
|
||||||
lc.RegisterCritical(lc.OnCycle)
|
|
||||||
lc.RegisterCritical(lc.OnClose)
|
lc.RegisterCritical(lc.OnClose)
|
||||||
lc.RegisterUntilLatest(lc.OnCompress)
|
lc.RegisterUntilLatest(lc.OnCompress)
|
||||||
lc.RegisterUntilLatest(lc.OnEncrypt)
|
lc.RegisterUntilLatest(lc.OnEncrypt)
|
||||||
@@ -159,11 +154,24 @@ func Login(c *proto.Conn, account *api.Account) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectAndLogin(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
func LoginNoSrv(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
||||||
c, err = Connect(connectTo)
|
c, err = Connect(connectTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = Login(c, account)
|
err = LoginRaw(c, account, connectTo)
|
||||||
|
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, connectTo)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -37,7 +37,8 @@ func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StatusRaw returns a StatusClient that is not connected to a server.
|
// StatusRaw returns a StatusClient that is not connected to a server.
|
||||||
// So ignore the resource leak warning. And maybe attach a warn ignore comment
|
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
|
||||||
|
// It does not resolve SRV records.
|
||||||
func StatusRaw(addr string) (s *StatusClient, err error) {
|
func StatusRaw(addr string) (s *StatusClient, err error) {
|
||||||
c, err := ConnectVersionLess(addr)
|
c, err := ConnectVersionLess(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -69,7 +70,8 @@ func StatusRaw(addr string) (s *StatusClient, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Status(addr string) (status server.StatusResponse, err error) {
|
// StatusNoDns is like Status but does not resolve SRV records.
|
||||||
|
func StatusNoDns(addr string) (status server.StatusResponse, err error) {
|
||||||
s, err := StatusRaw(addr)
|
s, err := StatusRaw(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -77,3 +79,12 @@ func Status(addr string) (status server.StatusResponse, err error) {
|
|||||||
err = json.Unmarshal([]byte(s.Status), &status)
|
err = json.Unmarshal([]byte(s.Status), &status)
|
||||||
return
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/admin-else/strom/tools/cli/offline_uuid"
|
"github.com/admin-else/strom/cmd/strom/offline_uuid"
|
||||||
"github.com/admin-else/strom/tools/cli/print_nbt"
|
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
||||||
"github.com/admin-else/strom/tools/cli/status"
|
"github.com/admin-else/strom/cmd/strom/print_nbt"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ExpectedASubcommandErr = errors.New("expected a subcommand")
|
var ExpectedASubcommandErr = errors.New("expected a subcommand")
|
||||||
@@ -20,6 +21,7 @@ var subcommands = map[string]func(args []string) error{
|
|||||||
"offline-uuid": offline_uuid.Run,
|
"offline-uuid": offline_uuid.Run,
|
||||||
"print-nbt": print_nbt.Run,
|
"print-nbt": print_nbt.Run,
|
||||||
"status": status.Run,
|
"status": status.Run,
|
||||||
|
"packet-spy": packet_inspector.Run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func Help(args []string) (err error) {
|
func Help(args []string) (err error) {
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package packet_inspector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/api"
|
||||||
|
"github.com/admin-else/strom/client"
|
||||||
|
"github.com/admin-else/strom/event"
|
||||||
|
"github.com/admin-else/strom/nbt"
|
||||||
|
"github.com/admin-else/strom/proto"
|
||||||
|
"github.com/admin-else/strom/proto_base"
|
||||||
|
"github.com/admin-else/strom/proto_generated/v1_21_11"
|
||||||
|
"github.com/admin-else/strom/server"
|
||||||
|
"github.com/admin-else/strom/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError)
|
||||||
|
|
||||||
|
var OfflineNameFlag = Cmd.String("name", "packet_inspector", "offline player name")
|
||||||
|
var Token = Cmd.String("token", "", "token to use")
|
||||||
|
|
||||||
|
var TargetAddr = Cmd.String("addr", "127.0.0.1:25565", "address to connect to")
|
||||||
|
var ListenAddr = Cmd.String("listen", "127.0.0.1:25566", "address to listen on")
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
Client, Servee *proto.Conn
|
||||||
|
Data nbt.Tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) Start() (err error) {
|
||||||
|
p.Client.RegisterCritical(p.OnAnything)
|
||||||
|
p.Servee.RegisterCritical(p.OnAnything)
|
||||||
|
|
||||||
|
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
||||||
|
|
||||||
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errChan <- p.Client.StartConn()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
errChan <- p.Servee.StartConn()
|
||||||
|
}()
|
||||||
|
err = <-errChan
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||||
|
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
packetInfo, ok := proto.LookupPacketInfoByType(packet)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch packetInfo.Direction {
|
||||||
|
case proto_base.ToServer:
|
||||||
|
err = p.Client.Send(packet)
|
||||||
|
case proto_base.ToClient:
|
||||||
|
err = p.Servee.Send(packet)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPacketFinishConfiguration) (err error) {
|
||||||
|
err = p.Client.Send(packet)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Servee.SetState(proto_base.Play)
|
||||||
|
p.Client.SetState(proto_base.Play)
|
||||||
|
err = event.DontForwardErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var StatusResponse = server.StatusResponse{
|
||||||
|
Version: server.StatusResponseVersion{
|
||||||
|
Name: text.Pretty("STROM"),
|
||||||
|
Protocol: 772,
|
||||||
|
},
|
||||||
|
Players: server.StatusResponsePlayers{},
|
||||||
|
Description: text.Pretty("Packet spy forwards to " + *TargetAddr),
|
||||||
|
Favicon: "",
|
||||||
|
EnforcesSecureChat: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = Cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
slog.Info("Starting packet inspector", "listen", *ListenAddr)
|
||||||
|
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
||||||
|
p := &Proxy{Servee: serveeConn, Client: nil}
|
||||||
|
var acc = api.NewOfflineAccount(*OfflineNameFlag)
|
||||||
|
if *Token != "" {
|
||||||
|
acc, err = api.NewAccountFromYGG(*Token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c, err := client.Login(*TargetAddr, acc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
p.Client = c
|
||||||
|
err = p.Start()
|
||||||
|
return
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
+67
-37
@@ -7,20 +7,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
LoopsPerSecond = 10000 // this is just so the cpu doesn't burn itself in an unlimited infinite loop
|
|
||||||
TimePerLoop = time.Second / LoopsPerSecond
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Loop struct {
|
type Loop struct {
|
||||||
HandlerFunctions map[reflect.Type][]reflect.Value
|
HandlerFunctions map[reflect.Type][]reflect.Value
|
||||||
|
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
ErrChan chan error
|
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
eventSources []reflect.SelectCase
|
||||||
}
|
}
|
||||||
|
|
||||||
type Unhandled struct {
|
type Unhandled struct {
|
||||||
@@ -31,10 +25,6 @@ type Anything struct {
|
|||||||
Val any
|
Val any
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
|
||||||
Tick struct{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Close is fired when the loop shuts down.
|
// Close is fired when the loop shuts down.
|
||||||
type Close struct {
|
type Close struct {
|
||||||
Reason error
|
Reason error
|
||||||
@@ -45,6 +35,14 @@ type HandlerDoneErr struct {
|
|||||||
Return error
|
Return error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CallFunc is a special value that when fired with Loop.Fire it calls a function on the main loop
|
||||||
|
// Its usefull for event event sources to call functions on the main loop
|
||||||
|
// because event sources dont run on the main loop
|
||||||
|
type CallFunc struct {
|
||||||
|
F reflect.Value
|
||||||
|
Args []reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
func (e HandlerDoneErr) Error() string {
|
func (e HandlerDoneErr) Error() string {
|
||||||
return "HandlerDoneErr"
|
return "HandlerDoneErr"
|
||||||
}
|
}
|
||||||
@@ -73,6 +71,7 @@ var WhileClosingErr = errors.New("error while closing")
|
|||||||
// CloseNonCriticalErr will close even when the event is non-critical
|
// CloseNonCriticalErr will close even when the event is non-critical
|
||||||
var CloseNonCriticalErr = errors.New("close non-critical")
|
var CloseNonCriticalErr = errors.New("close non-critical")
|
||||||
var ContextDoneErr = errors.New("context done")
|
var ContextDoneErr = errors.New("context done")
|
||||||
|
var RecvNotOkErr = errors.New("recv not ok")
|
||||||
|
|
||||||
func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) {
|
func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) {
|
||||||
hv = reflect.ValueOf(h)
|
hv = reflect.ValueOf(h)
|
||||||
@@ -89,6 +88,29 @@ func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterEventSource registers an event source an event source is a channel this channel along with all other event sources will be reflect.Select 'ed until the loop closes
|
||||||
|
// here is an example of an event source function
|
||||||
|
//
|
||||||
|
// func() (chSending <-chan any) {
|
||||||
|
// ch := make(chan any)
|
||||||
|
// go func() {
|
||||||
|
// ctx := conn.Ctx
|
||||||
|
// for {
|
||||||
|
// select {
|
||||||
|
// case <-ctx.Done():
|
||||||
|
// return
|
||||||
|
// case eventt := <-otherSource:
|
||||||
|
// err = c.DoCommand(eventt)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }()
|
||||||
|
// chSending = ch
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
func (l *Loop) RegisterEventSource(ch <-chan any) {
|
||||||
|
l.eventSources = append(l.eventSources, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)})
|
||||||
|
}
|
||||||
|
|
||||||
// Register registers a handler that will not close the loop if it returns an error.
|
// Register registers a handler that will not close the loop if it returns an error.
|
||||||
// Instead, it will log it to slog.Error
|
// Instead, it will log it to slog.Error
|
||||||
// Warning: If a bad handler is passed in the program will panic.
|
// Warning: If a bad handler is passed in the program will panic.
|
||||||
@@ -125,6 +147,10 @@ func (l *Loop) RegisterDirect(k reflect.Type, h reflect.Value) {
|
|||||||
l.HandlerFunctions[k] = append(l.HandlerFunctions[k], h)
|
l.HandlerFunctions[k] = append(l.HandlerFunctions[k], h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Loop) RegisterIgnore(t any) {
|
||||||
|
l.RegisterDirect(reflect.TypeOf(t), reflect.ValueOf(func(any) error { return nil }))
|
||||||
|
}
|
||||||
|
|
||||||
// FireFound triggers all handlers registered for the specified event type and returns if handlers were found or an error occurred.
|
// FireFound triggers all handlers registered for the specified event type and returns if handlers were found or an error occurred.
|
||||||
func (l *Loop) FireFound(event any) (found bool, err error) {
|
func (l *Loop) FireFound(event any) (found bool, err error) {
|
||||||
var handlers []reflect.Value
|
var handlers []reflect.Value
|
||||||
@@ -133,7 +159,7 @@ func (l *Loop) FireFound(event any) (found bool, err error) {
|
|||||||
errV := handler.Call([]reflect.Value{reflect.ValueOf(event)})[0]
|
errV := handler.Call([]reflect.Value{reflect.ValueOf(event)})[0]
|
||||||
if !errV.IsNil() {
|
if !errV.IsNil() {
|
||||||
err = errV.Interface().(error)
|
err = errV.Interface().(error)
|
||||||
err = errors.Join(fmt.Errorf("event handler failed at: %s(%T)", handler.Type().In(0), event), err)
|
err = errors.Join(fmt.Errorf("event handler failed at: %T", event), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,6 +169,21 @@ func (l *Loop) FireFound(event any) (found bool, err error) {
|
|||||||
|
|
||||||
// Fire dispatches the provided event to all appropriate handlers and invokes fallback handlers if the event is unhandled.
|
// Fire dispatches the provided event to all appropriate handlers and invokes fallback handlers if the event is unhandled.
|
||||||
func (l *Loop) Fire(event any) (err error) {
|
func (l *Loop) Fire(event any) (err error) {
|
||||||
|
err, isErr := event.(error)
|
||||||
|
if isErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
f, isCallFunc := event.(CallFunc)
|
||||||
|
if isCallFunc {
|
||||||
|
errV := f.F.Call(f.Args)[0].Interface()
|
||||||
|
if errV != nil {
|
||||||
|
err = errV.(error)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
found, err := l.FireFound(event)
|
found, err := l.FireFound(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, DontForwardErr) {
|
if errors.Is(err, DontForwardErr) {
|
||||||
@@ -160,24 +201,30 @@ func (l *Loop) Fire(event any) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Loop) OnTick(_ Tick) (err error) {
|
func (l *Loop) ContextDoneListener() (chSending <-chan any) {
|
||||||
|
ch := make(chan any)
|
||||||
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-l.Ctx.Done():
|
case <-l.Ctx.Done():
|
||||||
err = ContextDoneErr
|
ch <- ContextDoneErr
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
return
|
}()
|
||||||
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Loop) startLoop() (err error) {
|
func (l *Loop) StartLoop() (err error) {
|
||||||
_ = *l // exit early on nil loop
|
_ = *l // exit early on nil loop
|
||||||
|
l.RegisterEventSource(l.ContextDoneListener())
|
||||||
for {
|
for {
|
||||||
tickStartTime := time.Now()
|
_, v, ok := reflect.Select(l.eventSources)
|
||||||
err = l.Fire(Tick{})
|
if !ok {
|
||||||
|
err = RecvNotOkErr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = l.Fire(v.Interface())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
time.Sleep(TimePerLoop - time.Since(tickStartTime))
|
|
||||||
}
|
}
|
||||||
if errors.Is(err, HandlerDoneErr{}) {
|
if errors.Is(err, HandlerDoneErr{}) {
|
||||||
err = errors.Unwrap(err)
|
err = errors.Unwrap(err)
|
||||||
@@ -189,25 +236,8 @@ func (l *Loop) startLoop() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Loop) StartLoop() (err error) {
|
|
||||||
go func() {
|
|
||||||
l.ErrChan <- MainHandlerErr{l.startLoop()}
|
|
||||||
}()
|
|
||||||
err = <-l.ErrChan
|
|
||||||
l.cancel()
|
|
||||||
errMaybe := MainHandlerErr{}
|
|
||||||
if errors.As(err, &errMaybe) {
|
|
||||||
err = errors.Unwrap(err)
|
|
||||||
} else {
|
|
||||||
for !errors.Is(<-l.ErrChan, MainHandlerErr{}) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLoop() (l *Loop) {
|
func NewLoop() (l *Loop) {
|
||||||
l = &Loop{}
|
l = &Loop{}
|
||||||
l.ErrChan = make(chan error)
|
|
||||||
l.Ctx, l.cancel = context.WithCancel(context.Background())
|
l.Ctx, l.cancel = context.WithCancel(context.Background())
|
||||||
l.HandlerFunctions = make(map[reflect.Type][]reflect.Value)
|
l.HandlerFunctions = make(map[reflect.Type][]reflect.Value)
|
||||||
return
|
return
|
||||||
|
|||||||
+13
-24
@@ -2,7 +2,6 @@ package event
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,36 +9,26 @@ type Stdin struct {
|
|||||||
Val string
|
Val string
|
||||||
}
|
}
|
||||||
|
|
||||||
type StdinHandler struct {
|
func StartListingStdin(l *Loop) {
|
||||||
*Loop
|
l.RegisterEventSource(func() (chSending <-chan any) {
|
||||||
stdInChan chan Stdin
|
ch := make(chan any)
|
||||||
Ctx context.Context
|
go func() {
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StdinHandler) OnTick(_ Tick) (err error) {
|
|
||||||
select {
|
|
||||||
case stdin := <-s.stdInChan:
|
|
||||||
err = s.Fire(Stdin{stdin.Val})
|
|
||||||
default:
|
|
||||||
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StdinHandler) ScanJob() {
|
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
ctx := l.Ctx
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
select {
|
select {
|
||||||
case <-s.Ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
s.stdInChan <- Stdin{scanner.Text()}
|
ch <- Stdin{scanner.Text()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
ch <- err
|
||||||
}
|
}
|
||||||
func (s *StdinHandler) OnStart() (err error) {
|
}()
|
||||||
s.RegisterCritical(s.OnTick)
|
chSending = ch
|
||||||
s.stdInChan = make(chan Stdin)
|
return
|
||||||
go s.ScanJob()
|
}())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package event
|
package event
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -48,6 +50,44 @@ func (t *Timer) Tick() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Timer) Trigger(ch chan any) {
|
||||||
|
triggerAt := time.Time{}
|
||||||
|
found := false
|
||||||
|
iOnce, iInterval := -1, -1
|
||||||
|
var oTask OnceTask
|
||||||
|
for iOnce, oTask = range t.Once {
|
||||||
|
if oTask.Trigger.Before(triggerAt) || triggerAt.IsZero() {
|
||||||
|
found = true
|
||||||
|
triggerAt = oTask.Trigger
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var iTask IntervalTask
|
||||||
|
for iInterval, iTask = range t.Interval {
|
||||||
|
if iTask.LastTrigger.Add(iTask.Interval).Before(triggerAt) || triggerAt.IsZero() {
|
||||||
|
found = true
|
||||||
|
triggerAt = iTask.LastTrigger.Add(iTask.Interval)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
time.Sleep(triggerAt.Sub(now))
|
||||||
|
if iOnce >= 0 {
|
||||||
|
ch <- CallFunc{F: reflect.ValueOf(t.Once[iOnce].F), Args: []reflect.Value{}}
|
||||||
|
t.Once = slices.Delete(t.Once, iOnce, iOnce+1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if iInterval >= 0 {
|
||||||
|
ch <- CallFunc{F: reflect.ValueOf(t.Interval[iInterval].F), Args: []reflect.Value{}}
|
||||||
|
t.Interval[iInterval].LastTrigger = now
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Timer) Every(interval time.Duration, f func() error) {
|
func (t *Timer) Every(interval time.Duration, f func() error) {
|
||||||
t.Interval = append(t.Interval, IntervalTask{
|
t.Interval = append(t.Interval, IntervalTask{
|
||||||
F: f,
|
F: f,
|
||||||
@@ -61,3 +101,23 @@ func (t *Timer) In(interval time.Duration, f func() error) {
|
|||||||
Trigger: time.Now().Add(interval),
|
Trigger: time.Now().Add(interval),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Timer) MakeEventSource(ctx context.Context) (chSending <-chan any) {
|
||||||
|
ch := make(chan any)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.Trigger(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
chSending = ch
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Timer) Start(c *Loop) {
|
||||||
|
c.RegisterEventSource(t.MakeEventSource(c.Ctx))
|
||||||
|
}
|
||||||
|
|||||||
+38
-44
@@ -2,7 +2,6 @@ package proto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -16,6 +15,10 @@ import (
|
|||||||
"github.com/admin-else/strom/proto_generated"
|
"github.com/admin-else/strom/proto_generated"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BufferNPackets = 100
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
BadPacketTypeErr = errors.New("bad packet type")
|
BadPacketTypeErr = errors.New("bad packet type")
|
||||||
BadPacketIdErr = errors.New("bad packet id")
|
BadPacketIdErr = errors.New("bad packet id")
|
||||||
@@ -26,6 +29,9 @@ var (
|
|||||||
PacketDoesntExistInFutureVersionErr = errors.New("packet doesnt exist in future version")
|
PacketDoesntExistInFutureVersionErr = errors.New("packet doesnt exist in future version")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LoginTick is used as a subsite for the old event system because it is very useful in login contexts
|
||||||
|
type LoginTick struct{}
|
||||||
|
|
||||||
var LatestVersion = proto_generated.SupportedVersions[len(proto_generated.SupportedVersions)-1]
|
var LatestVersion = proto_generated.SupportedVersions[len(proto_generated.SupportedVersions)-1]
|
||||||
|
|
||||||
// UnCodablePacket represents a packet that could not be decoded.
|
// UnCodablePacket represents a packet that could not be decoded.
|
||||||
@@ -54,35 +60,28 @@ type Conn struct {
|
|||||||
Version string
|
Version string
|
||||||
ProtocolVersion int32
|
ProtocolVersion int32
|
||||||
|
|
||||||
receiveRoutine bool
|
sendTickAlways bool
|
||||||
packetCh chan proto_base.EncodeDecodeAble
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) StartConn() (err error) {
|
func (c *Conn) StartConn() (err error) {
|
||||||
c.RegisterCritical(c.OnTick)
|
if c.State() != proto_base.Play {
|
||||||
if c.state == proto_base.Play {
|
c.sendTickAlways = true
|
||||||
c.ActivateReceiveRoutine()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.RegisterEventSource(c.ActivateReceiveRoutine())
|
||||||
|
c.RegisterCritical(c.OnLoginTick)
|
||||||
err = c.Loop.StartLoop()
|
err = c.Loop.StartLoop()
|
||||||
c.Loop = event.NewLoop()
|
c.Loop = event.NewLoop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) ReceiveJob() {
|
func (c *Conn) OnLoginTick(_ LoginTick) (err error) {
|
||||||
ctx := c.Ctx
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
packet, err := c.Receive()
|
packet, err := c.Receive()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ErrChan <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.packetCh <- packet
|
err = c.Fire(packet)
|
||||||
}
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) SetState(state proto_base.State) {
|
func (c *Conn) SetState(state proto_base.State) {
|
||||||
@@ -119,13 +118,30 @@ func (c *Conn) SetProtocolVersion(version int32) (err error) {
|
|||||||
|
|
||||||
// ActivateReceiveRoutine activates the receive-routine.
|
// ActivateReceiveRoutine activates the receive-routine.
|
||||||
// It ensures that the receive-routine is only started once and creates a buffered channel for packet handling.
|
// It ensures that the receive-routine is only started once and creates a buffered channel for packet handling.
|
||||||
func (c *Conn) ActivateReceiveRoutine() {
|
func (c *Conn) ActivateReceiveRoutine() (chSending <-chan any) {
|
||||||
if c.receiveRoutine {
|
ch := make(chan any, BufferNPackets)
|
||||||
|
go func() {
|
||||||
|
ctx := c.Ctx // this prevents c.Ctx being reassigned
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
if c.sendTickAlways {
|
||||||
|
ch <- LoginTick{}
|
||||||
|
} else {
|
||||||
|
packet, err := c.Receive()
|
||||||
|
if err != nil {
|
||||||
|
ch <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.packetCh = make(chan proto_base.EncodeDecodeAble, 100) // i dunno how many to buffer 100 seems reasonable
|
ch <- packet
|
||||||
go c.ReceiveJob()
|
}
|
||||||
c.receiveRoutine = true
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
chSending = ch
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) translatePacketVersion(packet proto_base.EncodeDecodeAble, packetInfo proto_base.PacketInfo) (convertedPacket proto_base.EncodeDecodeAble, err error) {
|
func (c *Conn) translatePacketVersion(packet proto_base.EncodeDecodeAble, packetInfo proto_base.PacketInfo) (convertedPacket proto_base.EncodeDecodeAble, err error) {
|
||||||
@@ -216,25 +232,3 @@ func (c *Conn) Receive() (packet proto_base.EncodeDecodeAble, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) OnTick(_ event.Tick) (err error) {
|
|
||||||
var packet proto_base.EncodeDecodeAble
|
|
||||||
if c.receiveRoutine {
|
|
||||||
select {
|
|
||||||
case packet = <-c.packetCh:
|
|
||||||
if slog.Default().Enabled(context.Background(), slog.LevelDebug) {
|
|
||||||
slog.Debug("receive", "actor", c.Actor, "packet", fmt.Sprintf("%#v", packet))
|
|
||||||
}
|
|
||||||
err = c.Loop.Fire(packet)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
packet, err = c.Receive()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
slog.Debug("receive", "actor", c.Actor, "packet", fmt.Sprintf("%#v", packet))
|
|
||||||
err = c.Loop.Fire(packet)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ func (l *LoginServer) OnHandshake(packet *v1_21_8.HandshakingToServerPacketSetPr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = l.SetProtocolVersion(packet.ProtocolVersion)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +160,7 @@ func WithCompatibleVersions(versions ...int32) LoginServerSetting {
|
|||||||
func ServeLogin(c *proto.Conn, settings ...LoginServerSetting) (ret *LoginServer, err error) {
|
func ServeLogin(c *proto.Conn, settings ...LoginServerSetting) (ret *LoginServer, err error) {
|
||||||
ret = &LoginServer{Conn: c}
|
ret = &LoginServer{Conn: c}
|
||||||
ret.RegisterCritical(ret.OnDefault)
|
ret.RegisterCritical(ret.OnDefault)
|
||||||
|
ret.RegisterIgnore(event.Close{})
|
||||||
ret.RegisterUntilLatest(ret.OnHandshake)
|
ret.RegisterUntilLatest(ret.OnHandshake)
|
||||||
ret.RegisterUntilLatest(ret.OnLoginStart)
|
ret.RegisterUntilLatest(ret.OnLoginStart)
|
||||||
ret.RegisterUntilLatest(ret.OnLoginAcknowledged)
|
ret.RegisterUntilLatest(ret.OnLoginAcknowledged)
|
||||||
@@ -168,6 +170,6 @@ func ServeLogin(c *proto.Conn, settings ...LoginServerSetting) (ret *LoginServer
|
|||||||
s(ret)
|
s(ret)
|
||||||
}
|
}
|
||||||
ret.CompressionThreshold = 256
|
ret.CompressionThreshold = 256
|
||||||
err = ret.StartLoop()
|
err = ret.StartConn()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/event"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/proto_base"
|
||||||
"github.com/admin-else/strom/text"
|
"github.com/admin-else/strom/text"
|
||||||
@@ -19,6 +20,7 @@ func Servee(c net.Conn) (ret *proto.Conn) {
|
|||||||
ret.Conn = c
|
ret.Conn = c
|
||||||
ret.R = c
|
ret.R = c
|
||||||
ret.W = c
|
ret.W = c
|
||||||
|
ret.Loop = event.NewLoop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package failed_packets_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error: %v
|
||||||
|
func TestFailedPacket%10X(t *testing.T) {
|
||||||
|
p := v1_21_8.Play%vPacket{}
|
||||||
|
b := bytes.NewBuffer(%#v)
|
||||||
|
err := p.Decode(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
@@ -9,104 +10,75 @@ import (
|
|||||||
"github.com/admin-else/strom/api"
|
"github.com/admin-else/strom/api"
|
||||||
"github.com/admin-else/strom/client"
|
"github.com/admin-else/strom/client"
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/event"
|
||||||
|
"github.com/admin-else/strom/nbt"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"github.com/admin-else/strom/proto_generated/v1_21_11"
|
||||||
"github.com/admin-else/strom/server"
|
"github.com/admin-else/strom/server"
|
||||||
"github.com/admin-else/strom/text"
|
"github.com/admin-else/strom/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyClient struct {
|
//go:embed failed_packet.go.tmpl
|
||||||
*proto.Conn
|
var TestSrcF string
|
||||||
Server *Proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyClient) OnDefault(event event.Unhandled) (err error) {
|
|
||||||
packet, isPacket := event.Val.(proto_base.EncodeDecodeAble)
|
|
||||||
if !isPacket {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = p.Server.Send(packet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyClient) OnCycle(_ event.Tick) (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyClient) OnStart() (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProxyClient) OnUnCodeAble(packet *proto.UnCodablePacket) (err error) {
|
|
||||||
err = p.OnDefault(event.Unhandled{Val: packet})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
SaveUnCodeAbleAsTest(proto_base.ToServer, packet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
*proto.Conn
|
Client, Servee *proto.Conn
|
||||||
Client *ProxyClient
|
Data nbt.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) OnDefault(event event.Unhandled) (err error) {
|
func (p *Proxy) Start() (err error) {
|
||||||
packet, isPacket := event.Val.(proto_base.EncodeDecodeAble)
|
p.Client.RegisterCritical(p.OnAnything)
|
||||||
if !isPacket {
|
p.Servee.RegisterCritical(p.OnAnything)
|
||||||
|
|
||||||
|
p.Servee.Register(p.OnUnCodeAble)
|
||||||
|
p.Client.Register(p.OnUnCodeAble)
|
||||||
|
|
||||||
|
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
||||||
|
|
||||||
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errChan <- p.Client.StartConn()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
errChan <- p.Servee.StartConn()
|
||||||
|
}()
|
||||||
|
err = <-errChan
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||||
|
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
packetInfo, ok := proto.LookupPacketInfoByType(packet)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch packetInfo.Direction {
|
||||||
|
case proto_base.ToServer:
|
||||||
err = p.Client.Send(packet)
|
err = p.Client.Send(packet)
|
||||||
|
case proto_base.ToClient:
|
||||||
|
err = p.Servee.Send(packet)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) OnCycle(_ event.Tick) (err error) {
|
func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPacketFinishConfiguration) (err error) {
|
||||||
return
|
err = p.Client.Send(packet)
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proxy) OnStart() (err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proxy) OnFinishConfiguration(packet *v1_21_8.ConfigurationToServerPacketFinishConfiguration) (err error) {
|
|
||||||
err = p.OnDefault(event.Unhandled{Val: packet})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.SetState(proto_base.Play)
|
p.Servee.SetState(proto_base.Play)
|
||||||
p.Client.SetState(proto_base.Play)
|
p.Client.SetState(proto_base.Play)
|
||||||
|
err = event.DontForwardErr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) OnUnCodeAble(packet *proto.UnCodablePacket) (err error) {
|
func (p *Proxy) OnUnCodeAble(packet *proto.UnCodablePacket) (err error) {
|
||||||
err = p.OnDefault(event.Unhandled{Val: packet})
|
SaveUnCodeAbleAsTest(packet.Direction, packet)
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
SaveUnCodeAbleAsTest(proto_base.ToClient, packet)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const TestSrcF = `package failed_packets_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error: %v
|
|
||||||
func TestFailedPacket%10X(t *testing.T) {
|
|
||||||
p := v1_21_8.Play%vPacket{}
|
|
||||||
b := bytes.NewBuffer(%#v)
|
|
||||||
err := p.Decode(b)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
func SaveUnCodeAbleAsTest(d proto_base.Direction, packet *proto.UnCodablePacket) {
|
func SaveUnCodeAbleAsTest(d proto_base.Direction, packet *proto.UnCodablePacket) {
|
||||||
hUntrimmed := sha256.Sum256(packet.Data)
|
hUntrimmed := sha256.Sum256(packet.Data)
|
||||||
@@ -138,28 +110,20 @@ var StatusResponse = server.StatusResponse{
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
err := server.StartServerWithOnConn(":25565", func(serveeConn *proto.Conn) (err error) {
|
err := server.StartServerWithOnConn(":25566", func(serveeConn *proto.Conn) (err error) {
|
||||||
p := &Proxy{Conn: serveeConn, Client: nil}
|
p := &Proxy{Servee: serveeConn, Client: nil}
|
||||||
acc := api.NewOfflineAccount("sigma")
|
acc := api.NewOfflineAccount("sigma")
|
||||||
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse))
|
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c, err := client.ConnectAndLogin("127.0.0.1:25566", acc)
|
c, err := client.LoginNoSrv("127.0.0.1:25565", acc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pc := &ProxyClient{Conn: c, Server: p}
|
defer c.Close()
|
||||||
p.Client = pc
|
p.Client = c
|
||||||
errChan := make(chan error)
|
err = p.Start()
|
||||||
defer pc.Close()
|
|
||||||
go func() {
|
|
||||||
errChan <- pc.StartConn()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
errChan <- p.StartConn()
|
|
||||||
}()
|
|
||||||
err = <-errChan
|
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user