Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f878f40a1 | ||
|
|
7baa9990cb | ||
|
|
5c5c391aa6 | ||
|
|
b966925212 | ||
|
|
efd7049859 | ||
|
|
3ef9446f50 | ||
|
|
68612969c9 | ||
|
|
b4712ff5c6 | ||
|
|
eab050e291 | ||
|
|
a8b090227b | ||
|
|
201e0dfa44 | ||
|
|
666f250b3e | ||
|
|
b2784574bd | ||
|
|
c0b1501f23 | ||
|
|
4de76d8023 | ||
|
|
f08684fb67 | ||
|
|
9e4f9409c5 | ||
|
|
4d3fbed957 | ||
|
|
b338888b58 | ||
|
|
4956c1d874 | ||
|
|
d4fe362d9f | ||
|
|
5d8ff870df | ||
|
|
58c9a60236 | ||
|
|
3e5b141ec7 | ||
|
|
efeb5ecda6 | ||
|
|
624e8ad4de | ||
|
|
764cfd8c4d | ||
|
|
d43ff7f83d | ||
|
|
a39999b344 | ||
|
|
8ee48e1a45 | ||
|
|
bf6affeb97 |
@@ -1,5 +1,4 @@
|
|||||||
.devres/
|
.devres/
|
||||||
.idea/
|
.idea/
|
||||||
.failed_packets/
|
.failed_packets/
|
||||||
tools/.*/
|
|
||||||
.vscode/
|
.vscode/
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/proto"
|
|
||||||
"github.com/admin-else/strom/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
|
|
||||||
}
|
|
||||||
-126
@@ -1,126 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/admin-else/strom/api"
|
"github.com/admin-else/strom/mc/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmd = flag.NewFlagSet("api", flag.ContinueOnError)
|
var cmd = flag.NewFlagSet("api", flag.ContinueOnError)
|
||||||
@@ -52,7 +53,7 @@ func Run(args []string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = PleaseProvideArgumentErr
|
slog.Info("account", "name", a.Name, "uuid", a.Uuid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = json.NewEncoder(os.Stdout).Encode(data)
|
err = json.NewEncoder(os.Stdout).Encode(data)
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package cmd_util
|
||||||
|
|
||||||
|
import "github.com/admin-else/strom/mc/api"
|
||||||
|
|
||||||
|
func Account(s string) (a *api.Account, err error) {
|
||||||
|
if len(s) > 16 {
|
||||||
|
return api.NewAccountFromYGG(s)
|
||||||
|
}
|
||||||
|
a = api.NewOfflineAccount(s)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
mcdata "github.com/admin-else/strom/mc/data"
|
||||||
"github.com/admin-else/strom/proto_generated"
|
"github.com/admin-else/strom/mc/proto_generated"
|
||||||
"github.com/admin-else/strom/util"
|
"github.com/admin-else/strom/mc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// strom data 1.21.11 blocks id 0 name => air
|
// strom data 1.21.11 blocks id 0 name => air
|
||||||
@@ -39,11 +39,11 @@ func Run(args []string) (err error) {
|
|||||||
case 0: // print available versions
|
case 0: // print available versions
|
||||||
dataValues = proto_generated.SupportedVersions
|
dataValues = proto_generated.SupportedVersions
|
||||||
case 1: // print available data for version
|
case 1: // print available data for version
|
||||||
dataValues = slices.Collect(maps.Keys(data.Paths.Data[args[0]]))
|
dataValues = slices.Collect(maps.Keys(mcdata.Paths.Data[args[0]]))
|
||||||
case 2:
|
case 2:
|
||||||
err = data.LoadVersionedJson(args[0], args[1], &dataValues)
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
case 3:
|
case 3:
|
||||||
err = data.LoadVersionedJson(args[0], args[1], &dataValues)
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ func Run(args []string) (err error) {
|
|||||||
}
|
}
|
||||||
dataValues = result
|
dataValues = result
|
||||||
case 4, 5:
|
case 4, 5:
|
||||||
err = data.LoadVersionedJson(args[0], args[1], &dataValues)
|
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/admin-else/strom/level/anvil"
|
"github.com/admin-else/strom/mc/level/anvil"
|
||||||
"github.com/admin-else/strom/nbt"
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
+29
-10
@@ -1,39 +1,52 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"debug/buildinfo"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"maps"
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/admin-else/strom/cmd/strom/api"
|
"github.com/admin-else/strom/cmd/strom/api"
|
||||||
"github.com/admin-else/strom/cmd/strom/data"
|
"github.com/admin-else/strom/cmd/strom/data"
|
||||||
"github.com/admin-else/strom/cmd/strom/extract_mca"
|
"github.com/admin-else/strom/cmd/strom/extract_mca"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/mca_to_tmcpr"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/messenger"
|
||||||
"github.com/admin-else/strom/cmd/strom/offline_uuid"
|
"github.com/admin-else/strom/cmd/strom/offline_uuid"
|
||||||
"github.com/admin-else/strom/cmd/strom/packet_info"
|
"github.com/admin-else/strom/cmd/strom/packet_info"
|
||||||
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
||||||
"github.com/admin-else/strom/cmd/strom/print_nbt"
|
"github.com/admin-else/strom/cmd/strom/print_nbt"
|
||||||
"github.com/admin-else/strom/cmd/strom/print_replay"
|
"github.com/admin-else/strom/cmd/strom/print_replay"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/registry_data_hunter"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/serve_tmcpr"
|
||||||
|
"github.com/admin-else/strom/cmd/strom/serve_world"
|
||||||
"github.com/admin-else/strom/cmd/strom/status"
|
"github.com/admin-else/strom/cmd/strom/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ExpectedASubcommandErr = errors.New("expected a subcommand")
|
var ExpectedASubcommandErr = errors.New("expected a subcommand")
|
||||||
var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands")
|
var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands")
|
||||||
|
|
||||||
func Version(args []string) (err error) {
|
func ToolVersion() string {
|
||||||
selfPath, err := os.Executable()
|
info, ok := debug.ReadBuildInfo()
|
||||||
if err != nil {
|
if !ok {
|
||||||
return
|
return "devel"
|
||||||
}
|
}
|
||||||
info, err := buildinfo.ReadFile(selfPath)
|
return info.Main.Version
|
||||||
if err != nil {
|
}
|
||||||
return
|
|
||||||
|
func ToolSum() string {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
fmt.Println("strom cli ", info.Main.Version, " (", info.Main.Sum, ")")
|
return info.Main.Sum
|
||||||
return
|
}
|
||||||
|
|
||||||
|
func Version(args []string) error {
|
||||||
|
fmt.Println("strom cli ", ToolVersion(), " (", ToolSum(), ")")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var subcommands = map[string]func(args []string) error{
|
var subcommands = map[string]func(args []string) error{
|
||||||
@@ -47,6 +60,11 @@ var subcommands = map[string]func(args []string) error{
|
|||||||
"extract-mca": extract_mca.Run,
|
"extract-mca": extract_mca.Run,
|
||||||
"data": data.Run,
|
"data": data.Run,
|
||||||
"print-replay": print_replay.Run,
|
"print-replay": print_replay.Run,
|
||||||
|
"registry-data-hunter": registry_data_hunter.Run,
|
||||||
|
"serve-world": serve_world.Run,
|
||||||
|
"mca-to-tmcpr": mca_to_tmcpr.Run,
|
||||||
|
"serve-tmcpr": serve_tmcpr.Run,
|
||||||
|
"messenger": messenger.Run,
|
||||||
}
|
}
|
||||||
|
|
||||||
func Help(args []string) (err error) {
|
func Help(args []string) (err error) {
|
||||||
@@ -59,6 +77,7 @@ func Help(args []string) (err error) {
|
|||||||
func mainE() (err error) {
|
func mainE() (err error) {
|
||||||
subcommands["help"] = Help
|
subcommands["help"] = Help
|
||||||
args := os.Args
|
args := os.Args
|
||||||
|
//args = []string{"TEST", "messenger", "-addr", "localhost"}
|
||||||
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
err = ExpectedASubcommandErr
|
err = ExpectedASubcommandErr
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
package mca_to_tmcpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/level"
|
||||||
|
"github.com/admin-else/strom/mc/level/anvil"
|
||||||
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
|
"github.com/admin-else/strom/mc/proto/replay"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||||
|
"github.com/admin-else/strom/mc/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmd = flag.NewFlagSet("mca-to-tmcpr", flag.ContinueOnError)
|
||||||
|
worldPath = cmd.String("world", "", "Path to world directory")
|
||||||
|
output = cmd.String("output", "", "Output .tmcpr file path")
|
||||||
|
radius = cmd.Int("radius", 14, "Chunk radius around spawn")
|
||||||
|
spawnX = cmd.Float64("spawn-x", 0.5, "Spawn X coordinate")
|
||||||
|
spawnY = cmd.Float64("spawn-y", 100, "Spawn Y coordinate")
|
||||||
|
spawnZ = cmd.Float64("spawn-z", 0.5, "Spawn Z coordinate")
|
||||||
|
)
|
||||||
|
|
||||||
|
var FullLight [][]uint8
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
light := make([]uint8, 2048)
|
||||||
|
for i := range light {
|
||||||
|
light[i] = 0xFF
|
||||||
|
}
|
||||||
|
sectionsPerChunk := 384 / 16
|
||||||
|
FullLight = make([][]uint8, sectionsPerChunk)
|
||||||
|
for i := range sectionsPerChunk {
|
||||||
|
FullLight[i] = light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
|
||||||
|
xmca := util.FloorDiv(x, anvil.ChunksWidthRegionFile)
|
||||||
|
zmca := util.FloorDiv(z, anvil.ChunksWidthRegionFile)
|
||||||
|
x = x % anvil.ChunksWidthRegionFile
|
||||||
|
if x < 0 {
|
||||||
|
x += anvil.ChunksWidthRegionFile
|
||||||
|
}
|
||||||
|
z = z % anvil.ChunksWidthRegionFile
|
||||||
|
if z < 0 {
|
||||||
|
z += anvil.ChunksWidthRegionFile
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(fmt.Sprintf("./level/anvil/testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
mca, err := anvil.ReadChunkHeader(f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var n *nbt.Tag
|
||||||
|
n, err = mca.ChunkAt(f, x, z)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunk anvil.Chunk
|
||||||
|
err = nbt.Format.Decode(n.Value, &chunk)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetChunk *level.Chunk
|
||||||
|
packetChunk, err = chunk.ToStorage(version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
err = packetChunk.WriteChunkData(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b = buffer.Bytes()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) error {
|
||||||
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
|
||||||
|
err := cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *worldPath == "" {
|
||||||
|
slog.Error("no world path specified")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if *output == "" {
|
||||||
|
slog.Error("no output path specified")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outFile, err := os.Create(*output)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to create output file", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
w := replay.NewWriter(outFile)
|
||||||
|
|
||||||
|
err = w.WritePacket(&v1_21_11.PlayToClientPacketLogin{
|
||||||
|
EntityId: 73,
|
||||||
|
IsHardcore: false,
|
||||||
|
WorldNames: []string{"minecraft:overworld", "minecraft:the_end", "minecraft:the_nether"},
|
||||||
|
MaxPlayers: 100,
|
||||||
|
ViewDistance: 10,
|
||||||
|
SimulationDistance: 10,
|
||||||
|
ReducedDebugInfo: false,
|
||||||
|
EnableRespawnScreen: true,
|
||||||
|
DoLimitedCrafting: false,
|
||||||
|
WorldState: v1_21_11.PlayToClientSpawnInfo{
|
||||||
|
Dimension: 0,
|
||||||
|
Name: "minecraft:overworld",
|
||||||
|
HashedSeed: -8365436745629239539,
|
||||||
|
Gamemode: "survival",
|
||||||
|
PreviousGamemode: 0xff,
|
||||||
|
IsDebug: false,
|
||||||
|
IsFlat: false,
|
||||||
|
Death: nil,
|
||||||
|
PortalCooldown: 0,
|
||||||
|
SeaLevel: 63,
|
||||||
|
},
|
||||||
|
EnforcesSecureChat: false,
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to write login packet", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.WritePacket(&v1_21_11.PlayToClientPacketPosition{
|
||||||
|
TeleportId: 0,
|
||||||
|
X: *spawnX,
|
||||||
|
Y: *spawnY,
|
||||||
|
Z: *spawnZ,
|
||||||
|
Dx: 0,
|
||||||
|
Dy: 0,
|
||||||
|
Dz: 0,
|
||||||
|
Yaw: 0,
|
||||||
|
Pitch: 0,
|
||||||
|
Flags: v1_21_11.PlayToClientPositionUpdateRelatives{},
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to write position packet", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkX := int32(*spawnX) >> 4
|
||||||
|
chunkZ := int32(*spawnZ) >> 4
|
||||||
|
|
||||||
|
err = w.WritePacket(&v1_21_11.PlayToClientPacketUpdateViewPosition{
|
||||||
|
ChunkX: chunkX,
|
||||||
|
ChunkZ: chunkZ,
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to write view position packet", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.WritePacket(&v1_21_11.PlayToClientPacketGameStateChange{
|
||||||
|
Reason: "level_chunks_load_start",
|
||||||
|
}, 0)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to write game state packet", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
radius32 := int32(*radius)
|
||||||
|
|
||||||
|
for x := -radius32; x < radius32; x++ {
|
||||||
|
for z := -radius32; z < radius32; z++ {
|
||||||
|
var chunkData []byte
|
||||||
|
chunkData, err = GetChunkFromMca(x, z, "1.21.11")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if chunkData == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
OLDpacket := &v1_21_11.PlayToClientPacketMapChunk{}
|
||||||
|
OLDpacket.X = x
|
||||||
|
OLDpacket.Z = z
|
||||||
|
OLDpacket.ChunkData = v1_21_11.ByteArray{Val: chunkData}
|
||||||
|
OLDpacket.BlockLight = FullLight
|
||||||
|
OLDpacket.BlockLightMask = []int64{(1 << 24) - 1}
|
||||||
|
err = w.WritePacket(OLDpacket, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("conversion complete", "output", *output)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package messenger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/cmd/strom/cmd_util"
|
||||||
|
"github.com/admin-else/strom/mc/bot/chat"
|
||||||
|
"github.com/admin-else/strom/mc/bot/keepalive"
|
||||||
|
"github.com/admin-else/strom/mc/bot/world"
|
||||||
|
"github.com/admin-else/strom/mc/client"
|
||||||
|
"github.com/admin-else/strom/mc/data"
|
||||||
|
"github.com/admin-else/strom/mc/event"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmd = flag.NewFlagSet("status", flag.ContinueOnError)
|
||||||
|
connectToFlag = cmd.String("addr", "", "server address to connect to")
|
||||||
|
accFlag = cmd.String("acc", "strom_messenger", "the name to use if longer will be treated as ygg token")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Messenger struct {
|
||||||
|
*proto.Conn
|
||||||
|
chat *chat.Module
|
||||||
|
world *world.Module
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) OnChat(e *v1_21_11.PlayToClientPacketPlayerChat) (err error) {
|
||||||
|
m.Log.Info("chat", "author", e.SenderUuid, "m", e.PlainMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) OnChatUnsigned(e *v1_21_11.PlayToClientPacketProfilelessChat) (err error) {
|
||||||
|
m.Log.Info("chatP", "author", e.Name, "m", e.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) OnChatSystem(e *v1_21_11.PlayToClientPacketSystemChat) (err error) {
|
||||||
|
m.Log.Info("chatS", "m", e.Content)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) OnStdin(e event.Stdin) (err error) {
|
||||||
|
input := strings.TrimSpace(e.Val)
|
||||||
|
if strings.HasPrefix(input, ">say ") {
|
||||||
|
return m.chat.SendMessage(strings.TrimSpace(strings.TrimPrefix(input, ">say")))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(input, ">getblock ") {
|
||||||
|
return m.handleGetBlock(strings.TrimSpace(strings.TrimPrefix(input, ">getblock")))
|
||||||
|
}
|
||||||
|
return m.chat.SendMessage(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) handleGetBlock(args string) (err error) {
|
||||||
|
fields := strings.Fields(args)
|
||||||
|
if len(fields) != 3 {
|
||||||
|
m.Log.Info("getblock", "error", "expected >getblock <x> <y> <z>")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
coords := make([]int64, 3)
|
||||||
|
for i, f := range fields {
|
||||||
|
coords[i], err = strconv.ParseInt(f, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
m.Log.Info("getblock", "error", fmt.Sprintf("bad coordinate %q", f))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stateId, err := m.world.GetBlock(int32(coords[0]), int32(coords[1]), int32(coords[2]))
|
||||||
|
if err != nil {
|
||||||
|
m.Log.Info("getblock", "error", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
block, ok := data.LookupBlockByStateId(m.Version, stateId)
|
||||||
|
if !ok {
|
||||||
|
m.Log.Info("getblock", "stateId", stateId, "name", "unknown")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Log.Info("getblock", "x", coords[0], "y", coords[1], "z", coords[2], "name", block.Name, "stateId", stateId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
acc, err := cmd_util.Account(*accFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c, err := client.Login(*connectToFlag, acc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
chatMod, err := chat.Start(c, acc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shareable world storage. Other bots can subscribe to the same World.
|
||||||
|
w := world.NewWorld(c.Version, -64, 384)
|
||||||
|
|
||||||
|
m := &Messenger{
|
||||||
|
Conn: c,
|
||||||
|
chat: chatMod,
|
||||||
|
world: world.Start(c, w),
|
||||||
|
}
|
||||||
|
|
||||||
|
event.StartListingStdin(m.Loop)
|
||||||
|
m.Register(m.OnStdin)
|
||||||
|
m.RegisterUntilLatest(m.OnChat, m.OnChatUnsigned, m.OnChatSystem)
|
||||||
|
keepalive.Start(m.Conn)
|
||||||
|
|
||||||
|
return m.StartConn()
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/admin-else/strom/crypto"
|
"github.com/admin-else/strom/mc/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmd = flag.NewFlagSet("offline-uuid", flag.ContinueOnError)
|
var cmd = flag.NewFlagSet("offline-uuid", flag.ContinueOnError)
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
"github.com/admin-else/strom/mc/data"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated"
|
"github.com/admin-else/strom/mc/proto_generated"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmd = flag.NewFlagSet("packet-info", flag.ContinueOnError)
|
var cmd = flag.NewFlagSet("packet-info", flag.ContinueOnError)
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
package packet_inspector
|
package packet_inspector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/api"
|
"github.com/admin-else/strom/mc/api"
|
||||||
"github.com/admin-else/strom/client"
|
"github.com/admin-else/strom/mc/client"
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/nbt"
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_11"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||||
"github.com/admin-else/strom/server"
|
"github.com/admin-else/strom/mc/server"
|
||||||
"github.com/admin-else/strom/text"
|
"github.com/admin-else/strom/mc/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError)
|
var Cmd = flag.NewFlagSet("packet-inspector", flag.ContinueOnError)
|
||||||
@@ -22,6 +25,7 @@ var Token = Cmd.String("token", "", "token to use")
|
|||||||
|
|
||||||
var TargetAddr = Cmd.String("addr", "127.0.0.1:25565", "address to connect to")
|
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")
|
var ListenAddr = Cmd.String("listen", "127.0.0.1:25566", "address to listen on")
|
||||||
|
var TrueForward = Cmd.Bool("true-forward", false, "forward packets without modification -name and custom server status wont work")
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
Client, Servee *proto.Conn
|
Client, Servee *proto.Conn
|
||||||
@@ -33,6 +37,10 @@ func (p *Proxy) Start() (err error) {
|
|||||||
p.Servee.RegisterCritical(p.OnAnything)
|
p.Servee.RegisterCritical(p.OnAnything)
|
||||||
|
|
||||||
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
||||||
|
p.Servee.RegisterCriticalUntilLatest(p.OnFinishLogin)
|
||||||
|
p.Servee.RegisterCriticalUntilLatest(p.OnHandshake)
|
||||||
|
//p.Servee.RegisterCriticalUntilLatest(p.OnCompress)
|
||||||
|
p.Client.RegisterCriticalUntilLatest(p.OnCompress)
|
||||||
|
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -45,6 +53,22 @@ func (p *Proxy) Start() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PacketLogEvent struct {
|
||||||
|
Time time.Time
|
||||||
|
Packet proto_base.EncodeDecodeAble
|
||||||
|
Info PacketInfoNoTypeJson
|
||||||
|
ClientAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketInfoNoTypeJson struct {
|
||||||
|
Type proto_base.EncodeDecodeAble `json:"-"`
|
||||||
|
Name string
|
||||||
|
Direction proto_base.Direction
|
||||||
|
State proto_base.State
|
||||||
|
PacketId int32
|
||||||
|
ProtocolVersion int32
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
||||||
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -54,6 +78,19 @@ func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
jsonBytes, err := json.Marshal(PacketLogEvent{
|
||||||
|
Time: time.Now(),
|
||||||
|
Packet: packet,
|
||||||
|
ClientAddr: p.Client.RemoteAddr().String(),
|
||||||
|
Info: PacketInfoNoTypeJson(packetInfo),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// :pray:
|
||||||
|
_, _ = os.Stdout.Write(jsonBytes)
|
||||||
|
_, _ = os.Stdout.Write([]byte("\n"))
|
||||||
|
|
||||||
switch packetInfo.Direction {
|
switch packetInfo.Direction {
|
||||||
case proto_base.ToServer:
|
case proto_base.ToServer:
|
||||||
err = p.Client.Send(packet)
|
err = p.Client.Send(packet)
|
||||||
@@ -74,6 +111,40 @@ func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPack
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnFinishLogin(packet *v1_21_11.LoginToServerPacketLoginAcknowledged) (err error) {
|
||||||
|
err = p.Client.Send(packet)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Servee.SetState(proto_base.Configuration)
|
||||||
|
p.Client.SetState(proto_base.Configuration)
|
||||||
|
err = event.DontForwardErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnHandshake(packet *v1_21_11.HandshakingToServerPacketSetProtocol) (err error) {
|
||||||
|
err = p.Client.Send(packet)
|
||||||
|
state := proto_base.State(packet.NextState)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Servee.SetState(state)
|
||||||
|
p.Client.SetState(state)
|
||||||
|
err = event.DontForwardErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) OnCompress(packet *v1_21_11.LoginToClientPacketCompress) (err error) {
|
||||||
|
err = p.Servee.Send(packet)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Servee.SetCompressionThreshold(packet.Threshold)
|
||||||
|
p.Client.SetCompressionThreshold(packet.Threshold)
|
||||||
|
err = event.DontForwardErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var StatusResponse = server.StatusResponse{
|
var StatusResponse = server.StatusResponse{
|
||||||
Version: server.StatusResponseVersion{
|
Version: server.StatusResponseVersion{
|
||||||
Name: text.Pretty("STROM"),
|
Name: text.Pretty("STROM"),
|
||||||
@@ -90,38 +161,41 @@ func Run(args []string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
|
||||||
slog.Info("Starting packet inspector", "listen", *ListenAddr)
|
slog.Info("Starting packet inspector", "listen", *ListenAddr)
|
||||||
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
||||||
serveeConn.DebugPrintPackets = []string{""}
|
|
||||||
p := &Proxy{Servee: serveeConn, Client: nil}
|
p := &Proxy{Servee: serveeConn, Client: nil}
|
||||||
var acc = api.NewOfflineAccount(*OfflineNameFlag)
|
var acc = api.NewOfflineAccount(*OfflineNameFlag)
|
||||||
|
if !*TrueForward {
|
||||||
if *Token != "" {
|
if *Token != "" {
|
||||||
acc, err = api.NewAccountFromYGG(*Token)
|
acc, err = api.NewAccountFromYGG(*Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode())
|
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode(), server.WithoutEncryption())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*TargetAddr, err = client.DoDnsSimple(*TargetAddr)
|
*TargetAddr, _, _, err = client.DoDNSChecked(nil, *TargetAddr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c, err := client.Connect(*TargetAddr)
|
c, err := client.Connect(nil, *TargetAddr, "")
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.DebugPrintPackets = []string{""}
|
|
||||||
err = client.LoginRaw(c, acc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
if !*TrueForward {
|
||||||
|
err = client.LoginRaw(c, acc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
p.Client = c
|
p.Client = c
|
||||||
|
p.Client.AlwaysDecode = true
|
||||||
|
p.Servee.AlwaysDecode = true
|
||||||
err = p.Start()
|
err = p.Start()
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/admin-else/strom/nbt"
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
"github.com/admin-else/strom/mc/data"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package registry_data_hunter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -6,16 +6,16 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/admin-else/strom/api"
|
"github.com/admin-else/strom/mc/api"
|
||||||
"github.com/admin-else/strom/client"
|
"github.com/admin-else/strom/mc/client"
|
||||||
"github.com/admin-else/strom/data"
|
"github.com/admin-else/strom/mc/data"
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/nbt"
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
"github.com/admin-else/strom/server"
|
"github.com/admin-else/strom/mc/server"
|
||||||
"github.com/admin-else/strom/text"
|
"github.com/admin-else/strom/mc/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
func wontFail[T any](v T, err error) T {
|
func wontFail[T any](v T, err error) T {
|
||||||
@@ -25,10 +25,7 @@ func wontFail[T any](v T, err error) T {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// THIS IS REDUNDANT USE LOGIN PACKET IN MINECRAFT DATA
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Edit these two
|
|
||||||
Version = "1.21.11"
|
Version = "1.21.11"
|
||||||
SaveAs = "data/registry/" + Version + ".nbt"
|
SaveAs = "data/registry/" + Version + ".nbt"
|
||||||
|
|
||||||
@@ -113,7 +110,6 @@ func (p *Proxy) OnTags(data *v1_21_8.ConfigurationToClientPacketTags) (err error
|
|||||||
func handleClient(c *proto.Conn) (err error) {
|
func handleClient(c *proto.Conn) (err error) {
|
||||||
acc := api.NewOfflineAccount("RegistryHunter")
|
acc := api.NewOfflineAccount("RegistryHunter")
|
||||||
|
|
||||||
// ignore resource leak warning we own c conn so we should not close it here
|
|
||||||
_, err = server.ServeLogin(c, server.WithOtherAccount(acc), server.WithCompatibleVersions(CompatibleProtocolVersion), server.WithRawStatus(Status))
|
_, err = server.ServeLogin(c, server.WithOtherAccount(acc), server.WithCompatibleVersions(CompatibleProtocolVersion), server.WithRawStatus(Status))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -150,10 +146,7 @@ func handleClient(c *proto.Conn) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func Run(args []string) error {
|
||||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
err := server.StartServerWithOnConn(":25566", handleClient)
|
return server.StartServerWithOnConn(":25566", handleClient)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package serve_tmcpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"math/rand/v2"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/event"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto/replay"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"github.com/admin-else/strom/mc/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*proto.Conn
|
||||||
|
*event.Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cmd = flag.NewFlagSet("serve-tmcpr", flag.ContinueOnError)
|
||||||
|
tmcprFile = cmd.String("file", "/tmp/world.tmcpr", "Path to .tmcpr replay file")
|
||||||
|
bindAddr = cmd.String("bind", ":25566", "Address to listen on")
|
||||||
|
)
|
||||||
|
|
||||||
|
func Limbo(c *proto.Conn, tmcprPath string) (err error) {
|
||||||
|
s := &Server{Conn: c}
|
||||||
|
|
||||||
|
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.ServeConfig(s.Conn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(tmcprPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
r := replay.NewReader(f)
|
||||||
|
r.Version = c.ProtocolVersion
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var packetData []byte
|
||||||
|
var timestamp uint32
|
||||||
|
packetData, timestamp, _, err = r.ReadRaw()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(timestamp)*time.Millisecond - time.Since(start))
|
||||||
|
err = s.SendRaw(packetData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Timer = &event.Timer{}
|
||||||
|
s.Timer.Every(time.Second*15, s.SendKeepAlive)
|
||||||
|
s.Timer.Start(c.Loop)
|
||||||
|
|
||||||
|
return s.StartConn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) SendKeepAlive() error {
|
||||||
|
return s.Send(&v1_21_8.PlayToClientPacketKeepAlive{KeepAliveId: rand.Int64()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) error {
|
||||||
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
|
||||||
|
err := cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *tmcprFile == "" {
|
||||||
|
slog.Error("no file specified")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.StartServerWithOnConn(*bindAddr, func(c *proto.Conn) error {
|
||||||
|
return Limbo(c, *tmcprFile)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package serve_world
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -8,14 +8,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/level"
|
"github.com/admin-else/strom/mc/level"
|
||||||
"github.com/admin-else/strom/level/anvil"
|
"github.com/admin-else/strom/mc/level/anvil"
|
||||||
"github.com/admin-else/strom/nbt"
|
"github.com/admin-else/strom/mc/nbt"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
"github.com/admin-else/strom/server"
|
"github.com/admin-else/strom/mc/server"
|
||||||
"github.com/admin-else/strom/util"
|
"github.com/admin-else/strom/mc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -28,7 +28,7 @@ type Server struct {
|
|||||||
var FullLight [][]uint8
|
var FullLight [][]uint8
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
light := make([]uint8, 2048) // 4096 values 2 per uint8
|
light := make([]uint8, 2048)
|
||||||
for i := range light {
|
for i := range light {
|
||||||
light[i] = 0xFF
|
light[i] = 0xFF
|
||||||
}
|
}
|
||||||
@@ -95,13 +95,11 @@ func Limbo(c *proto.Conn) (err error) {
|
|||||||
s := &Server{
|
s := &Server{
|
||||||
Conn: c,
|
Conn: c,
|
||||||
}
|
}
|
||||||
//s.DebugPrintPackets = []string{""}
|
|
||||||
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
|
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//goland:noinspection GoResourceLeak captures the conn object which should NOT be closed
|
|
||||||
err = server.ServeConfig(s.Conn)
|
err = server.ServeConfig(s.Conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -176,10 +174,7 @@ func (s *Server) SendKeepAlive() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func Run(args []string) error {
|
||||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
err := server.StartServerWithOnConn(":25566", Limbo)
|
return server.StartServerWithOnConn(":25566", Limbo)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/client"
|
"github.com/admin-else/strom/mc/client"
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmd = flag.NewFlagSet("status", flag.ContinueOnError)
|
var cmd = flag.NewFlagSet("status", flag.ContinueOnError)
|
||||||
@@ -46,7 +46,7 @@ func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
|
|||||||
// 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.
|
// It does not resolve SRV records.
|
||||||
func StatusRaw(addr string) (s *StatusClient, err error) {
|
func StatusRaw(addr string) (s *StatusClient, err error) {
|
||||||
c, err := client.ConnectVersionLess(addr)
|
c, err := client.ConnectVersionLess(nil, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ func Run(args []string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if *srvFlag {
|
if *srvFlag {
|
||||||
*addrFlag, err = client.DoDnsSimple(*addrFlag)
|
*addrFlag, _, _, err = client.DoDNSChecked(nil, *addrFlag, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,5 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
|
|||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import "github.com/admin-else/strom/crypto"
|
import (
|
||||||
|
"github.com/admin-else/strom/mc/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
func NewOfflineAccount(name string) *Account {
|
func NewOfflineAccount(name string) *Account {
|
||||||
return &Account{
|
return &Account{
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package chat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/api"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Module struct {
|
||||||
|
*proto.Conn
|
||||||
|
|
||||||
|
Account *api.Account
|
||||||
|
|
||||||
|
// Chat signing state
|
||||||
|
canSign bool
|
||||||
|
privKey *rsa.PrivateKey
|
||||||
|
sessionUUID uuid.UUID
|
||||||
|
msgIndex int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start creates a chat module for the connection. If acc is an online account,
|
||||||
|
// it fetches the profile key pair from Mojang and enables signed chat.
|
||||||
|
func Start(c *proto.Conn, acc *api.Account) (*Module, error) {
|
||||||
|
m := &Module{Conn: c, Account: acc}
|
||||||
|
if acc != nil && acc.Ygg != "" {
|
||||||
|
if err := m.initSigning(acc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) initSigning(acc *api.Account) (err error) {
|
||||||
|
keys, err := acc.FetchKeys()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fetch profile keys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := parsePrivateKey(keys.KeyPair.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.privKey = priv
|
||||||
|
m.sessionUUID = uuid.New()
|
||||||
|
|
||||||
|
pubDer, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sigBytes, err := base64.StdEncoding.DecodeString(keys.PublicKeySignatureV2)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decode key signature: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = m.Conn.Send(&v1_21_11.PlayToServerPacketChatSessionUpdate{
|
||||||
|
SessionUUID: m.sessionUUID,
|
||||||
|
ExpireTime: keys.ExpiresAt.UnixMilli(),
|
||||||
|
PublicKey: v1_21_11.ByteArray{Val: pubDer},
|
||||||
|
Signature: v1_21_11.ByteArray{Val: sigBytes},
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("send chat session update: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.canSign = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessage sends a chat message. If signing is enabled, the message is signed;
|
||||||
|
// otherwise it is sent unsigned.
|
||||||
|
func (m *Module) SendMessage(message string) (err error) {
|
||||||
|
if m.canSign {
|
||||||
|
return m.sendSigned(message)
|
||||||
|
}
|
||||||
|
return m.Conn.Send(&v1_21_11.PlayToServerPacketChatMessage{Message: message})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) sendSigned(message string) (err error) {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
saltBytes := make([]byte, 8)
|
||||||
|
if _, err = rand.Read(saltBytes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
salt := int64(binary.BigEndian.Uint64(saltBytes))
|
||||||
|
|
||||||
|
timestamp := now.UnixMilli()
|
||||||
|
|
||||||
|
payload, err := m.buildSignaturePayload(message, salt, timestamp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hashed := sha256.Sum256(payload)
|
||||||
|
sigBytes, err := rsa.SignPKCS1v15(rand.Reader, m.privKey, crypto.SHA256, hashed[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(sigBytes) != 256 {
|
||||||
|
return fmt.Errorf("unexpected signature length: %d", len(sigBytes))
|
||||||
|
}
|
||||||
|
var signature [256]byte
|
||||||
|
copy(signature[:], sigBytes)
|
||||||
|
|
||||||
|
m.msgIndex++
|
||||||
|
|
||||||
|
return m.Conn.Send(&v1_21_11.PlayToServerPacketChatMessage{
|
||||||
|
Message: message,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Salt: salt,
|
||||||
|
Signature: &signature,
|
||||||
|
Offset: 0,
|
||||||
|
Acknowledged: [3]byte{},
|
||||||
|
Checksum: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) buildSignaturePayload(content string, salt, timestampMillis int64) ([]byte, error) {
|
||||||
|
buf := make([]byte, 0, 64+len(content))
|
||||||
|
w := func(b []byte) { buf = append(buf, b...) }
|
||||||
|
|
||||||
|
// Header
|
||||||
|
w(int32Bytes(1))
|
||||||
|
|
||||||
|
// SignedMessageLink: sender UUID, session UUID, index
|
||||||
|
w(uuidBytes(m.Account.Uuid))
|
||||||
|
w(uuidBytes(m.sessionUUID))
|
||||||
|
w(int32Bytes(m.msgIndex))
|
||||||
|
|
||||||
|
// SignedMessageBody: salt, timestamp seconds, content
|
||||||
|
w(int64Bytes(uint64(salt)))
|
||||||
|
w(int64Bytes(uint64(timestampMillis / 1000)))
|
||||||
|
contentBytes := []byte(content)
|
||||||
|
w(int32Bytes(int32(len(contentBytes))))
|
||||||
|
w(contentBytes)
|
||||||
|
|
||||||
|
// LastSeenMessages: empty
|
||||||
|
w(int32Bytes(0))
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePrivateKey(s string) (*rsa.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode([]byte(s))
|
||||||
|
if block != nil {
|
||||||
|
k, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
k, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if key, ok := k.(*rsa.PrivateKey); ok {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not an RSA private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some implementations return raw base64 DER instead of PEM
|
||||||
|
raw, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
k, err := x509.ParsePKCS8PrivateKey(raw)
|
||||||
|
if err != nil {
|
||||||
|
k, err = x509.ParsePKCS1PrivateKey(raw)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key, ok := k.(*rsa.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("not an RSA private key")
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func int32Bytes(v int32) []byte {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(b, uint32(v))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64Bytes(v uint64) []byte {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(b, v)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func uuidBytes(u uuid.UUID) []byte {
|
||||||
|
b := make([]byte, 16)
|
||||||
|
copy(b, u[:])
|
||||||
|
return b
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package keepalive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mod struct {
|
||||||
|
*proto.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mod) onKeepAlive(p *v1_21_8.PlayToClientPacketKeepAlive) (err error) {
|
||||||
|
return m.Send(&v1_21_8.PlayToServerPacketKeepAlive{KeepAliveId: p.KeepAliveId})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(c *proto.Conn) {
|
||||||
|
m := &mod{Conn: c}
|
||||||
|
m.RegisterUntilLatest(m.onKeepAlive)
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/level"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ChunkNotLoadedErr = errors.New("chunk not loaded")
|
||||||
|
OutOfBoundsErr = errors.New("coordinates out of bounds")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChunkPos identifies a chunk column.
|
||||||
|
type ChunkPos struct {
|
||||||
|
X, Z int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// World stores a shareable in-memory view of the Minecraft world.
|
||||||
|
// Multiple bots can subscribe to the same World and update it from their
|
||||||
|
// respective connections.
|
||||||
|
type World struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
chunks map[ChunkPos]*level.Chunk
|
||||||
|
version string
|
||||||
|
minY int
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWorld creates a new World for the given protocol version and vertical
|
||||||
|
// bounds. For a 1.21 overworld use minY=-64 and height=384.
|
||||||
|
func NewWorld(version string, minY, height int) *World {
|
||||||
|
return &World{
|
||||||
|
chunks: make(map[ChunkPos]*level.Chunk),
|
||||||
|
version: version,
|
||||||
|
minY: minY,
|
||||||
|
height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlock returns the global block state ID at the given world coordinates.
|
||||||
|
func (w *World) GetBlock(x, y, z int32) (stateId int32, err error) {
|
||||||
|
w.mu.RLock()
|
||||||
|
defer w.mu.RUnlock()
|
||||||
|
return w.getBlockLocked(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *World) getBlockLocked(x, y, z int32) (stateId int32, err error) {
|
||||||
|
chunkX := floorDiv(x, level.ChunkWidth)
|
||||||
|
chunkZ := floorDiv(z, level.ChunkWidth)
|
||||||
|
chunk, ok := w.chunks[ChunkPos{chunkX, chunkZ}]
|
||||||
|
if !ok {
|
||||||
|
err = ChunkNotLoadedErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionIndex := (int(y) - w.minY) / level.ChunkWidth
|
||||||
|
if sectionIndex < 0 || sectionIndex >= len(chunk.Sections) {
|
||||||
|
err = OutOfBoundsErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lx, ly, lz := blockToLocal(x, y, z)
|
||||||
|
index := ly*level.ChunkWidth*level.ChunkWidth + lz*level.ChunkWidth + lx
|
||||||
|
return chunk.Sections[sectionIndex].Blocks.Get(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlock updates the global block state ID at the given world coordinates.
|
||||||
|
func (w *World) SetBlock(x, y, z int32, stateId int32) (err error) {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
chunkX := floorDiv(x, level.ChunkWidth)
|
||||||
|
chunkZ := floorDiv(z, level.ChunkWidth)
|
||||||
|
chunk, ok := w.chunks[ChunkPos{chunkX, chunkZ}]
|
||||||
|
if !ok {
|
||||||
|
return ChunkNotLoadedErr
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionIndex := (int(y) - w.minY) / level.ChunkWidth
|
||||||
|
if sectionIndex < 0 || sectionIndex >= len(chunk.Sections) {
|
||||||
|
return OutOfBoundsErr
|
||||||
|
}
|
||||||
|
|
||||||
|
lx, ly, lz := blockToLocal(x, y, z)
|
||||||
|
index := ly*level.ChunkWidth*level.ChunkWidth + lz*level.ChunkWidth + lx
|
||||||
|
return chunk.Sections[sectionIndex].Blocks.Set(index, stateId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module subscribes to chunk and block-change packets and updates a shared
|
||||||
|
// World. Multiple Modules can point to the same World.
|
||||||
|
type Module struct {
|
||||||
|
*proto.Conn
|
||||||
|
world *World
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlock returns the block state ID at the given world coordinates via the
|
||||||
|
// shared World.
|
||||||
|
func (m *Module) GetBlock(x, y, z int32) (int32, error) {
|
||||||
|
return m.world.GetBlock(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start registers packet handlers on c that update the shared World w.
|
||||||
|
func Start(c *proto.Conn, w *World) *Module {
|
||||||
|
m := &Module{Conn: c, world: w}
|
||||||
|
m.RegisterUntilLatest(m.onMapChunk)
|
||||||
|
m.RegisterUntilLatest(m.onBlockChange)
|
||||||
|
m.RegisterUntilLatest(m.onMultiBlockChange)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) onMapChunk(p *v1_21_11.PlayToClientPacketMapChunk) (err error) {
|
||||||
|
chunk, err := level.ReadChunkFromChunkPacketData(
|
||||||
|
bytes.NewReader(p.ChunkData.Val),
|
||||||
|
m.world.version,
|
||||||
|
m.world.height,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.world.mu.Lock()
|
||||||
|
m.world.chunks[ChunkPos{p.X, p.Z}] = chunk
|
||||||
|
m.world.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) onBlockChange(p *v1_21_11.PlayToClientPacketBlockChange) (err error) {
|
||||||
|
return m.world.SetBlock(p.Location.X, int32(p.Location.Y), p.Location.Z, p.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Module) onMultiBlockChange(p *v1_21_11.PlayToClientPacketMultiBlockChange) (err error) {
|
||||||
|
chunkX := p.ChunkCoordinates.X
|
||||||
|
chunkZ := p.ChunkCoordinates.Z
|
||||||
|
sectionY := p.ChunkCoordinates.Y
|
||||||
|
|
||||||
|
for _, record := range p.Records {
|
||||||
|
packedPos := record & 0xFFF
|
||||||
|
stateId := record >> 12
|
||||||
|
|
||||||
|
localX := (packedPos >> 8) & 0xF
|
||||||
|
localY := (packedPos >> 4) & 0xF
|
||||||
|
localZ := packedPos & 0xF
|
||||||
|
|
||||||
|
x := chunkX*level.ChunkWidth + int32(localX)
|
||||||
|
y := sectionY*level.ChunkWidth + int32(localY)
|
||||||
|
z := chunkZ*level.ChunkWidth + int32(localZ)
|
||||||
|
|
||||||
|
if err = m.world.SetBlock(x, y, z, stateId); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func floorDiv(a, b int32) int32 {
|
||||||
|
return (a - ((a%b + b) % b)) / b
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockToLocal(x, y, z int32) (lx, ly, lz int32) {
|
||||||
|
lx = x % level.ChunkWidth
|
||||||
|
if lx < 0 {
|
||||||
|
lx += level.ChunkWidth
|
||||||
|
}
|
||||||
|
ly = y % level.ChunkWidth
|
||||||
|
if ly < 0 {
|
||||||
|
ly += level.ChunkWidth
|
||||||
|
}
|
||||||
|
lz = z % level.ChunkWidth
|
||||||
|
if lz < 0 {
|
||||||
|
lz += level.ChunkWidth
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/data"
|
||||||
|
"github.com/admin-else/strom/mc/proto"
|
||||||
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
|
"github.com/admin-else/strom/mc/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// version must be a vaild minecraft version eg 1.21.11 if left empty it will be detected with a status-request.
|
||||||
|
func Connect(ctx context.Context, addr string, version string) (ret *proto.Conn, err error) {
|
||||||
|
var protocolVersion int32
|
||||||
|
if version == "" {
|
||||||
|
var s server.StatusResponse
|
||||||
|
s, err = Status(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
protocolVersion = s.Version.Protocol
|
||||||
|
} else {
|
||||||
|
var v data.VersionInfo
|
||||||
|
v, err = data.LookUpVersionByName(version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
protocolVersion = v.Version
|
||||||
|
}
|
||||||
|
ret, err = ConnectVersionLess(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = ret.SetProtocolVersion(protocolVersion)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 ConnectVersionLess(ctx context.Context, addr string) (ret *proto.Conn, err error) {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -4,10 +4,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigIgnorer struct {
|
type ConfigIgnorer struct {
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSCheckFunc func(addr string) error
|
||||||
|
|
||||||
|
// 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(ctx context.Context, in string, f DNSCheckFunc) (connectTo string, host string, portN uint16, err error) {
|
||||||
|
if f == nil {
|
||||||
|
f = func(addr string) error { return nil }
|
||||||
|
}
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34
|
||||||
|
host, port, err := net.SplitHostPort(in)
|
||||||
|
if err != nil {
|
||||||
|
if addrErr, ok := errors.AsType[*net.AddrError](err); ok && addrErr.Err == "missing port in address" {
|
||||||
|
err = nil
|
||||||
|
host = in
|
||||||
|
port = "25565"
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
porti, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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(ctx, 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
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ func TestDoDNS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Logf("Testing %s", c.Input)
|
t.Logf("Testing %s", c.Input)
|
||||||
_, host, portN, err := DoDns(c.Input)
|
_, host, portN, err := DoDNSChecked(nil, c.Input, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"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) {
|
func MakeHandshakePacket(c *proto.Conn, nextState proto_base.State) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/admin-else/strom/api"
|
"github.com/admin-else/strom/mc/api"
|
||||||
"github.com/admin-else/strom/crypto"
|
"github.com/admin-else/strom/mc/crypto"
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/text"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"github.com/admin-else/strom/mc/text"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginClient struct {
|
type LoginClient struct {
|
||||||
@@ -156,24 +156,55 @@ func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginNoSrv(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
func WithoutDns() func(ls *loginSettings) {
|
||||||
c, err = Connect(connectTo)
|
return func(ls *loginSettings) {
|
||||||
if err != nil {
|
ls.NoDns = true
|
||||||
return
|
|
||||||
}
|
}
|
||||||
err = LoginRaw(c, account)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Login(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
func WithContext(ctx context.Context) func(ls *loginSettings) {
|
||||||
connectTo, err = DoDnsSimple(connectTo)
|
return func(ls *loginSettings) {
|
||||||
|
ls.Ctx = ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithVersion(version string) func(ls *loginSettings) {
|
||||||
|
return func(ls *loginSettings) {
|
||||||
|
ls.Version = version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type loginSettings struct {
|
||||||
|
NoDns bool
|
||||||
|
Ctx context.Context
|
||||||
|
Version string
|
||||||
|
IgnoreConfig bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Login(connectTo string, account *api.Account, settings ...func(ls *loginSettings)) (c *proto.Conn, err error) {
|
||||||
|
ls := &loginSettings{}
|
||||||
|
|
||||||
|
for _, setting := range settings {
|
||||||
|
setting(ls)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ls.NoDns {
|
||||||
|
connectTo, _, _, err = DoDNSChecked(ls.Ctx, connectTo, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c, err = Connect(connectTo)
|
}
|
||||||
|
|
||||||
|
c, err = Connect(ls.Ctx, connectTo, ls.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = LoginRaw(c, account)
|
err = LoginRaw(c, account)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ls.IgnoreConfig {
|
||||||
|
err = IgnoreConfig(c)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -5,12 +5,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/server"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"github.com/admin-else/strom/mc/server"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatusClient struct {
|
type StatusClient struct {
|
||||||
@@ -41,7 +40,7 @@ func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
|
|||||||
// 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.
|
// It does not resolve SRV records.
|
||||||
func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
|
func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
|
||||||
c, err := ConnectVersionLess(addr)
|
c, err := ConnectVersionLess(ctx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -82,8 +81,8 @@ func StatusNoDns(addr string) (status server.StatusResponse, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Status resolves the status of the minecraft server at the given address.
|
// Status resolves the status of the minecraft server at the given address.
|
||||||
func Status(addr string) (status server.StatusResponse, err error) {
|
func Status(ctx context.Context, addr string) (status server.StatusResponse, err error) {
|
||||||
addr, err = DoDnsSimple(addr)
|
addr, _, _, err = DoDNSChecked(ctx, addr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UUIDv3 Variation of google.uuid.NewHash
|
// uuidV3 Variation of google.uuid.NewHash
|
||||||
// We need this because we want a 14-byte namespace and Google forces 16
|
// We need this because we want a 14-byte namespace and Google forces 16
|
||||||
func UUIDv3(space []byte, data []byte) uuid.UUID {
|
func uuidV3(space []byte, data []byte) uuid.UUID {
|
||||||
h := md5.New()
|
h := md5.New()
|
||||||
h.Reset()
|
h.Reset()
|
||||||
h.Write(space) //nolint:errcheck
|
h.Write(space) //nolint:errcheck
|
||||||
@@ -22,5 +22,5 @@ func UUIDv3(space []byte, data []byte) uuid.UUID {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FromOfflinePlayer(displayName string) uuid.UUID {
|
func FromOfflinePlayer(displayName string) uuid.UUID {
|
||||||
return UUIDv3([]byte("OfflinePlayer:"), []byte(displayName))
|
return uuidV3([]byte("OfflinePlayer:"), []byte(displayName))
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ package data_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
"github.com/admin-else/strom/mc/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLookupBiomeById(t *testing.T) {
|
func TestLookupBiomeById(t *testing.T) {
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/admin-else/strom/util"
|
"github.com/admin-else/strom/mc/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// {
|
// {
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
"github.com/admin-else/strom/mc/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLookupBlockByStateId(t *testing.T) {
|
func TestLookupBlockByStateId(t *testing.T) {
|
||||||
@@ -1630,6 +1630,7 @@
|
|||||||
"loginPacket": "pc/1.21.3",
|
"loginPacket": "pc/1.21.3",
|
||||||
"mapIcons": "pc/1.20.2",
|
"mapIcons": "pc/1.20.2",
|
||||||
"materials": "pc/1.21.8",
|
"materials": "pc/1.21.8",
|
||||||
|
"multi_noise_biome_source_parameter_list": "pc/1.21.8",
|
||||||
"particles": "pc/1.21.8",
|
"particles": "pc/1.21.8",
|
||||||
"protocol": "pc/1.21.8",
|
"protocol": "pc/1.21.8",
|
||||||
"recipes": "pc/1.21.8",
|
"recipes": "pc/1.21.8",
|
||||||
@@ -1657,6 +1658,7 @@
|
|||||||
"loginPacket": "pc/1.21.9",
|
"loginPacket": "pc/1.21.9",
|
||||||
"mapIcons": "pc/1.20.2",
|
"mapIcons": "pc/1.20.2",
|
||||||
"materials": "pc/1.21.9",
|
"materials": "pc/1.21.9",
|
||||||
|
"multi_noise_biome_source_parameter_list": "pc/1.21.9",
|
||||||
"particles": "pc/1.21.9",
|
"particles": "pc/1.21.9",
|
||||||
"protocol": "pc/1.21.9",
|
"protocol": "pc/1.21.9",
|
||||||
"recipes": "pc/1.21.9",
|
"recipes": "pc/1.21.9",
|
||||||
@@ -1711,6 +1713,7 @@
|
|||||||
"loginPacket": "pc/1.21.11",
|
"loginPacket": "pc/1.21.11",
|
||||||
"mapIcons": "pc/1.20.2",
|
"mapIcons": "pc/1.20.2",
|
||||||
"materials": "pc/1.21.11",
|
"materials": "pc/1.21.11",
|
||||||
|
"multi_noise_biome_source_parameter_list": "pc/1.21.11",
|
||||||
"particles": "pc/1.21.11",
|
"particles": "pc/1.21.11",
|
||||||
"protocol": "pc/1.21.11",
|
"protocol": "pc/1.21.11",
|
||||||
"recipes": "pc/1.21.11",
|
"recipes": "pc/1.21.11",
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user