diff --git a/client/client.go b/client/client.go index b0b7e19..16de82b 100644 --- a/client/client.go +++ b/client/client.go @@ -3,6 +3,7 @@ package client import ( "net" + "github.com/admin-else/strom/event" "github.com/admin-else/strom/proto" "github.com/admin-else/strom/proto_base" ) @@ -30,6 +31,7 @@ func ConnectVersionLess(addr string) (ret *proto.Conn, err error) { ret.SetState(proto_base.Handshaking) ret.SetCompressionThreshold(-1) ret.Actor = proto_base.Client + ret.Loop = event.NewLoop() ret.Conn, err = net.Dial("tcp", addr) if err != nil { return diff --git a/client/config_ignorer.go b/client/config_ignorer.go index cc320af..8beafbb 100644 --- a/client/config_ignorer.go +++ b/client/config_ignorer.go @@ -19,8 +19,11 @@ func (c *ConfigIgnorer) OnStart() (err error) { return } -func (c *ConfigIgnorer) Default(event event.Anything) (err error) { - slog.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", event)) +func (c *ConfigIgnorer) Default(e event.Anything) (err error) { + if e.Val == (event.Tick{}) { + return + } + slog.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e)) return } @@ -56,7 +59,7 @@ func IgnoreConfig(c *proto.Conn) (err error) { ci.RegisterUntilLatest(ci.OnPing) ci.RegisterUntilLatest(ci.OnKeepAlive) - err = ci.Start() + err = ci.StartConn() if err != nil { err = errors.Join(err, errors.New("failed to ignore config")) } diff --git a/client/dns.go b/client/dns.go index b8e8b8d..214e6d3 100644 --- a/client/dns.go +++ b/client/dns.go @@ -106,7 +106,7 @@ func DoDnsSimple(in string) (out string, err error) { if err != nil { return } - out = in + out = host + ":" + strconv.Itoa(int(portN)) if portN != 25565 { return } diff --git a/client/login.go b/client/login.go index f04e629..b4f489a 100644 --- a/client/login.go +++ b/client/login.go @@ -152,7 +152,7 @@ func Login(c *proto.Conn, account *api.Account) (err error) { lc.SetState(proto_base.Login) err = lc.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: lc.Account.Name, PlayerUUID: lc.Account.Uuid}) - err = lc.Start() + err = lc.StartConn() if err != nil { err = errors.Join(FailedToLoginErr, err) } @@ -165,8 +165,5 @@ func ConnectAndLogin(connectTo string, account *api.Account) (c *proto.Conn, err return } err = Login(c, account) - if err != nil { - err = errors.Join(FailedToLoginErr, err) - } return } diff --git a/client/status.go b/client/status.go index 7456fa2..f1ef7bd 100644 --- a/client/status.go +++ b/client/status.go @@ -19,10 +19,6 @@ type StatusClient struct { DoPingRoundTripTime bool } -func (s *StatusClient) OnStart() (err error) { - return -} - func (s *StatusClient) OnStatus(p *v1_21_8.StatusToClientPacketServerInfo) (err error) { s.Status = p.Response s.PingSendTime = time.Now() @@ -55,9 +51,8 @@ func StatusRaw(addr string) (s *StatusClient, err error) { s = &StatusClient{ Conn: c, } - s.Loop = event.NewLoop() - s.RegisterUntilLatest(s.OnStatus) - s.RegisterUntilLatest(s.OnPong) + s.RegisterCriticalUntilLatest(s.OnStatus) + s.RegisterCriticalUntilLatest(s.OnPong) p, err := MakeHandshakePacketAddr(s.Conn, proto_base.Status, addr) if err != nil { diff --git a/data/blocks.go b/data/blocks.go index d4e247f..a139df4 100644 --- a/data/blocks.go +++ b/data/blocks.go @@ -2,6 +2,7 @@ package data import ( "errors" + "strconv" ) // { @@ -75,9 +76,9 @@ func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool) return } -func LookupBlockByName(version string, stateId int32) (block *Block, ok bool) { +func LookupBlockByName(version string, name string) (block *Block, ok bool) { for _, b := range BlocksForVersion(version) { - if b.MinStateId <= stateId && stateId <= b.MaxStateId { + if b.Name == name { block = b ok = true } @@ -85,6 +86,16 @@ func LookupBlockByName(version string, stateId int32) (block *Block, ok bool) { return } +func StateIdFromBlocKAndStateMap(version string, name string, stateMap map[string]string) (stateId int32, err error) { + b, ok := LookupBlockByName(version, name) + if !ok { + err = BlockNotFoundErr + return + } + stateId, err = b.IdFromStateMap(stateMap) + return +} + var BlockStateOutOfRangeErr = errors.New("stateId out of range") var BlockNotFoundErr = errors.New("block not found") var InvalidBoolValueErr = errors.New("invalid bool value") @@ -92,7 +103,7 @@ var BadBlockStateTypeErr = errors.New("bad block state type") var BadEnumErr = errors.New("bad enum") var SupplyAllStatesErr = errors.New("supply all states") -func FromBlockState(version string, stateId int32) (b *Block, stateData map[string]any, err error) { +func FromBlockState(version string, stateId int32) (b *Block, stateData map[string]string, err error) { b, ok := LookupBlockByStateId(version, stateId) if !ok { err = BlockNotFoundErr @@ -101,19 +112,17 @@ func FromBlockState(version string, stateId int32) (b *Block, stateData map[stri return } -// TODO: mc stores these as strings so we will do the same - -func mcDataBlockStateTypeParse(n int32, s *BlockState) (v any, err error) { +func mcDataBlockStateTypeParse(n int32, s *BlockState) (v string, err error) { switch s.Type { case "int": - v = n + v = strconv.Itoa(int(n)) case "enum": v = s.Values[n] case "bool": if n == 0 { // MOJANK: 0 is true, 1 is false - v = true + v = "true" } else if n == 1 { - v = false + v = "false" } else { err = InvalidBoolValueErr } @@ -123,35 +132,32 @@ func mcDataBlockStateTypeParse(n int32, s *BlockState) (v any, err error) { return } -func mcDataBlockStateTypeSerialize(v any, s *BlockState) (n int32, err error) { - switch t := v.(type) { - case int32: - n = t - case bool: - if t { - n = 0 - } else { - n = 1 - } - case string: - for i, enumValue := range s.Values { - if enumValue == t { - return int32(i), nil - } - } - err = BadEnumErr - default: - err = BadBlockStateTypeErr +func mcDataBlockStateTypeSerialize(v string, s *BlockState) (n int32, err error) { + if v == "true" { + return 0, nil } + if v == "false" { + return 1, nil + } + n1, errMabye := strconv.Atoi(v) + if errMabye == nil { + return int32(n1), nil + } + for i, enumValue := range s.Values { + if enumValue == v { + return int32(i), nil + } + } + err = BadEnumErr return } -func (b *Block) StateMapFromId(stateId int32) (ret map[string]any, err error) { +func (b *Block) StateMapFromId(stateId int32) (ret map[string]string, err error) { if stateId < b.MinStateId || stateId > b.MaxStateId { err = BlockStateOutOfRangeErr return } - ret = make(map[string]any) + ret = make(map[string]string) stateId -= b.MinStateId a := int32(1) @@ -166,7 +172,7 @@ func (b *Block) StateMapFromId(stateId int32) (ret map[string]any, err error) { return } -func (b *Block) IdFromStateMap(m map[string]any) (ret int32, err error) { +func (b *Block) IdFromStateMap(m map[string]string) (ret int32, err error) { a := int32(1) for i := range len(b.States) { s := b.States[len(b.States)-1-i] diff --git a/data/protocol_version.go b/data/protocol_version.go index c79489f..e3dceeb 100644 --- a/data/protocol_version.go +++ b/data/protocol_version.go @@ -12,7 +12,7 @@ package data type VersionInfo struct { MinecraftVersion string Version int32 - DataVersion int + DataVersion int32 UsesNetty bool MajorVersion string ReleaseType string @@ -42,6 +42,16 @@ func LookUpVersionByProtocolVersion(version int32) (ret VersionInfo, err error) return } +func LookUpVersionByDataVersion(version int32) (ret VersionInfo, err error) { + for _, v := range ProtocolVersions { + if v.DataVersion == version { + ret = v + } + } + err = UnknownMinecraftVersionError + return +} + func init() { must(LoadJson("minecraft-data/data/pc/common/protocolVersions.json", &ProtocolVersions)) } diff --git a/event/event_bus.go b/event/event_bus.go index 158ad59..8091261 100644 --- a/event/event_bus.go +++ b/event/event_bus.go @@ -53,10 +53,13 @@ func (e HandlerDoneErr) Unwrap() error { return e.Return } -// ErrDontForward will stop the event from being forwarded to other handlers, including anything handlers. -var ErrDontForward = errors.New("dont forward") +// DontForwardErr will stop the event from being forwarded to other handlers, including anything handlers. +var DontForwardErr = errors.New("dont forward") var WhileClosingErr = errors.New("error while closing") +// CloseNonCriticalErr will close even when the event is non-critical +var CloseNonCriticalErr = errors.New("close non-critical") + func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) { hv = reflect.ValueOf(h) if hv.Kind() != reflect.Func { @@ -86,7 +89,11 @@ func (l *Loop) RegisterCustomType(eventType reflect.Type, hv reflect.Value) { handleFunc := func(packet any) error { v := hv.Call([]reflect.Value{reflect.ValueOf(packet)})[0] if !v.IsNil() { - slog.Error("handler failed", "err", v.Interface().(error)) + err := v.Interface().(error) + if errors.Is(err, CloseNonCriticalErr) { + return err + } + slog.Error("handler failed", "err", err) } return nil } @@ -124,7 +131,7 @@ func (l *Loop) FireFound(event any) (found bool, err error) { func (l *Loop) Fire(event any) (err error) { found, err := l.FireFound(event) if err != nil { - if errors.Is(err, ErrDontForward) { + if errors.Is(err, DontForwardErr) { err = nil } return @@ -159,7 +166,7 @@ func (l *Loop) startLoop() (err error) { return } -func (l *Loop) Start() (err error) { +func (l *Loop) StartLoop() (err error) { go func() { l.ErrChan <- l.startLoop() }() diff --git a/level/litematic/main.go b/level/litematic/main.go new file mode 100644 index 0000000..350bff7 --- /dev/null +++ b/level/litematic/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "github.com/admin-else/strom/data" + "github.com/go-viper/mapstructure/v2" +) + +type Position struct { + X, Y, Z int32 +} + +type MetaData struct { + Author string + Description string + Name string + TimeCreated int64 + TimeModified int64 + TotalBlocks int32 + TotalVolume int32 + RegionCount int32 + EnclosingSize Position +} + +type BlockState struct { + Name string + Properties map[string]string +} + +type Region struct { + Position Position + Size Position + BlockStatePalette []BlockState + BlockStates []int64 +} + +type File struct { + Version int32 + SubVersion int32 + MinecraftDataVersion int32 + Metadata MetaData + Regions map[string]Region +} + +type Structure struct { + File + BlockStatePalletRealized map[string][]int32 +} + +func Load(v any) (s *Structure, err error) { + s = &Structure{} + err = mapstructure.Decode(v, &s.File) + if err != nil { + return + } + ret, err := data.LookUpVersionByDataVersion(s.MinecraftDataVersion) + if err != nil { + return + } + for k, r := range s.Regions { + s.BlockStatePalletRealized[k] = make([]int32, len(r.BlockStatePalette)) + for i, b := range r.BlockStatePalette { + var stateId int32 + stateId, err = data.StateIdFromBlocKAndStateMap(ret.MinecraftVersion, b.Name, b.Properties) + if err != nil { + return + } + s.BlockStatePalletRealized[k][i] = stateId + } + } + return +} diff --git a/level/structure.go b/level/structure/structure.go similarity index 90% rename from level/structure.go rename to level/structure/structure.go index 17cffdd..645aafe 100644 --- a/level/structure.go +++ b/level/structure/structure.go @@ -1,11 +1,11 @@ -package level +package structure import ( "github.com/admin-else/strom/nbt" "github.com/go-viper/mapstructure/v2" ) -func LoadStructure(fileName string) (s *Structure, err error) { +func Load(fileName string) (s *Structure, err error) { n, err := nbt.ReadFilePath(fileName) if err != nil { return diff --git a/nbt/nbt.go b/nbt/nbt.go index 909a0e2..2da7e42 100644 --- a/nbt/nbt.go +++ b/nbt/nbt.go @@ -291,6 +291,8 @@ func readPayload(id int8, r io.Reader) (ret any, err error) { return } +var UnknownTagTypeErr = errors.New("unknown nbt type") + func writePayload(v any, w io.Writer) (err error) { switch v := v.(type) { case struct{}: @@ -361,7 +363,7 @@ func writePayload(v any, w io.Writer) (err error) { } err = binary.Write(w, Order, End) default: - err = errors.New("unknown nbt type") + err = UnknownTagTypeErr } return } diff --git a/nbt/snbt.go b/nbt/snbt.go new file mode 100644 index 0000000..ca335f5 --- /dev/null +++ b/nbt/snbt.go @@ -0,0 +1,91 @@ +package nbt + +import ( + "fmt" + "io" +) + +// PrintSNBTAny documented at https://minecraft.wiki/w/NBT_format#SNBT_format +func PrintSNBTAny(t any, w io.Writer) (err error) { + switch t := t.(type) { + case int8: + _, err = fmt.Fprintf(w, "%vB", t) + case bool: + _, err = fmt.Fprintf(w, "%v", t) + case int16: + _, err = fmt.Fprintf(w, "%vS", t) + case int32: + _, err = fmt.Fprintf(w, "%vI", t) + case int64: + _, err = fmt.Fprintf(w, "%vL", t) + case float32: + _, err = fmt.Fprintf(w, "%vF", t) + case float64: + _, err = fmt.Fprintf(w, "%vD", t) + case string: + _, err = fmt.Fprintf(w, "%q", t) + case []any: + _, err = fmt.Fprintf(w, "[") + for _, v := range t { + err = PrintSNBTAny(v, w) + if err != nil { + return + } + _, err = fmt.Fprintf(w, ",") + } + _, err = fmt.Fprintf(w, "]") + case map[string]any: + _, err = fmt.Fprintf(w, "{") + for k, v := range t { + _, err = fmt.Fprintf(w, "%q:,", k) + err = PrintSNBTAny(v, w) + if err != nil { + return + } + _, err = fmt.Fprintf(w, ",") + } + _, err = fmt.Fprintf(w, "}") + case []int8: + _, err = fmt.Fprintf(w, "[B;") + if err != nil { + return + } + for _, v := range t { + err = PrintSNBTAny(v, w) + if err != nil { + return + } + _, err = fmt.Fprintf(w, ",") + } + _, err = fmt.Fprintf(w, "]") + case []int32: + _, err = fmt.Fprintf(w, "[I;") + if err != nil { + return + } + for _, v := range t { + err = PrintSNBTAny(v, w) + if err != nil { + return + } + _, err = fmt.Fprintf(w, ",") + } + _, err = fmt.Fprintf(w, "]") + case []int64: + _, err = fmt.Fprintf(w, "[L;") + if err != nil { + return + } + for _, v := range t { + err = PrintSNBTAny(v, w) + if err != nil { + return + } + _, err = fmt.Fprintf(w, ",") + } + _, err = fmt.Fprintf(w, "]") + default: + err = UnknownTagTypeErr + } + return +} diff --git a/nbt/snbt_test.go b/nbt/snbt_test.go new file mode 100644 index 0000000..7365930 --- /dev/null +++ b/nbt/snbt_test.go @@ -0,0 +1,23 @@ +package nbt + +import ( + "bytes" + _ "embed" + "testing" +) + +//go:embed testdata/top_0_20.nbt +var topSNBT []byte + +func TestPrintSNBTAny(t *testing.T) { + n, err := ReadFile(bytes.NewReader(topSNBT)) + if err != nil { + panic(err) + } + bb := bytes.NewBuffer(nil) + err = PrintSNBTAny(n.Value, bb) + if err != nil { + panic(err) + } + t.Log(bb.String()) +} diff --git a/nbt/testdata/top_0_20.nbt b/nbt/testdata/top_0_20.nbt new file mode 100644 index 0000000..a7f899c Binary files /dev/null and b/nbt/testdata/top_0_20.nbt differ diff --git a/proto/connection.go b/proto/connection.go index af42aa8..cbccc45 100644 --- a/proto/connection.go +++ b/proto/connection.go @@ -60,13 +60,19 @@ type Conn struct { func (c *Conn) StartConn() (err error) { c.RegisterCritical(c.OnTick) - return c.Loop.Start() + if c.state == proto_base.Play { + c.ActivateReceiveRoutine() + } + err = c.Loop.StartLoop() + c.Loop = event.NewLoop() + return } func (c *Conn) ReceiveJob() { + ctx := c.Ctx for { select { - case <-c.Ctx.Done(): + case <-ctx.Done(): return default: packet, err := c.Receive() @@ -233,10 +239,3 @@ func (c *Conn) OnTick(_ event.Tick) (err error) { } return } - -func (c *Conn) Start() (err error) { - if c.Loop == nil { - c.Loop = &event.Loop{} - } - return c.Loop.Start() -} diff --git a/server/config_server.go b/server/config_server.go index a3cbbd8..8c3f797 100644 --- a/server/config_server.go +++ b/server/config_server.go @@ -65,7 +65,7 @@ func ServeConfig(c *proto.Conn) (err error) { } err = c.Send(&v1_21_8.ConfigurationToClientPacketFinishConfiguration{}) cs := &ConfigServer{c} - return cs.Start() + return cs.StartLoop() } func (c *ConfigServer) OnFinishConfiguration(_ *v1_21_8.ConfigurationToServerPacketFinishConfiguration) (err error) { diff --git a/server/login_server.go b/server/login_server.go index edc06f6..236de20 100644 --- a/server/login_server.go +++ b/server/login_server.go @@ -168,6 +168,6 @@ func ServeLogin(c *proto.Conn, settings ...LoginServerSetting) (ret *LoginServer s(ret) } ret.CompressionThreshold = 256 - err = ret.Start() + err = ret.StartLoop() return } diff --git a/server/status.go b/server/status.go index 438b976..f7e8364 100644 --- a/server/status.go +++ b/server/status.go @@ -67,6 +67,6 @@ func ServeStatus(c *proto.Conn, s StatusResponse) (err error) { } server.RegisterUntilLatest(server.OnStatusRequest) server.RegisterUntilLatest(server.OnStatusPing) - err = server.Start() + err = server.StartLoop() return } diff --git a/tools/cli/print_nbt/main.go b/tools/cli/print_nbt/main.go index 039bd41..da9df0c 100644 --- a/tools/cli/print_nbt/main.go +++ b/tools/cli/print_nbt/main.go @@ -1,6 +1,7 @@ package print_nbt import ( + "bytes" "encoding/json" "flag" "os" @@ -9,8 +10,9 @@ import ( ) var ( - cmd = flag.NewFlagSet("print-nbt", flag.ContinueOnError) - FileFlag = cmd.String("file", "", "The NBT file to print") + cmd = flag.NewFlagSet("print-nbt", flag.ContinueOnError) + FileFlag = cmd.String("file", "", "The NBT file to print") + HashFormatFlag = cmd.Bool("snbt", false, "will print with go %#v") ) func Run(args []string) (err error) { @@ -27,7 +29,18 @@ func Run(args []string) (err error) { if err != nil { return } - b, err := json.MarshalIndent(n, "", " ") + var b []byte + if *HashFormatFlag { + bb := bytes.NewBuffer(nil) + err = nbt.PrintSNBTAny(n.Value, bb) + if err != nil { + return + } + b = bb.Bytes() + //b = []byte(strings.ReplaceAll(string(b), ",", ",\n")) + } else { + b, err = json.MarshalIndent(n, "", " ") + } if err != nil { return } diff --git a/tools/registry_data_hunter/main.go b/tools/registry_data_hunter/main.go index 50cd467..5fea5dd 100644 --- a/tools/registry_data_hunter/main.go +++ b/tools/registry_data_hunter/main.go @@ -116,10 +116,7 @@ func handleClient(c *proto.Conn) (err error) { return } defer sockPuppet.Close() - err = sockPuppet.Start(&client.LoginClient{ - Conn: sockPuppet, - Account: acc, - }) + err = sockPuppet.StartLoop() if err != nil { return } @@ -127,10 +124,10 @@ func handleClient(c *proto.Conn) (err error) { p := &Proxy{Client: sockPuppet, Servee: c} p.Data.Value = map[string]any{"Registries": []any{}} go func() { - errChan <- p.Client.Start(p) + errChan <- p.Client.StartLoop() }() go func() { - errChan <- p.Servee.Start(p) + errChan <- p.Servee.StartLoop() }() err = <-errChan f, err := os.Create("data/registry/" + SaveAs + ".nbt") diff --git a/tools/uncodeable_packet_hunter_proxy/main.go b/tools/uncodeable_packet_hunter_proxy/main.go index 2e495c3..fbd613c 100644 --- a/tools/uncodeable_packet_hunter_proxy/main.go +++ b/tools/uncodeable_packet_hunter_proxy/main.go @@ -154,10 +154,10 @@ func main() { errChan := make(chan error) defer pc.Close() go func() { - errChan <- pc.Start(pc) + errChan <- pc.StartConn() }() go func() { - errChan <- p.Start(p) + errChan <- p.StartConn() }() err = <-errChan return