diff --git a/cmd/bedrocktool/main.go b/cmd/bedrocktool/main.go index 7452750..bf0d9a5 100644 --- a/cmd/bedrocktool/main.go +++ b/cmd/bedrocktool/main.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "io" + "net/http" "os" "os/signal" "runtime/pprof" @@ -68,6 +69,15 @@ func setupLogging(isDebug bool) { })) } +type logTransport struct { + rt http.RoundTripper +} + +func (t *logTransport) RoundTrip(req *http.Request) (*http.Response, error) { + logrus.Tracef("Request %s", req.URL.String()) + return t.rt.RoundTrip(req) +} + func main() { isDebug := updater.Version == "" @@ -80,6 +90,8 @@ func main() { } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() + + http.DefaultTransport = &logTransport{rt: http.DefaultTransport} } log := logrus.WithField("part", "main") diff --git a/cmd/entity-test/main.go b/cmd/entity-test/main.go deleted file mode 100644 index 3ed5cb9..0000000 --- a/cmd/entity-test/main.go +++ /dev/null @@ -1,102 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/bedrock-tool/bedrocktool/utils" - "github.com/df-mc/dragonfly/server/block/cube" - "github.com/df-mc/dragonfly/server/world" - "github.com/df-mc/dragonfly/server/world/mcdb" - "github.com/go-gl/mathgl/mgl64" - "github.com/sirupsen/logrus" -) - -type entityReg struct{} - -func (r *entityReg) Config() world.EntityRegistryConfig { - return world.EntityRegistryConfig{} -} -func (r *entityReg) Lookup(name string) (world.EntityType, bool) { - return &serverEntityType{Encoded: name}, true -} -func (r *entityReg) Types() []world.EntityType { - return []world.EntityType{nil} -} - -type serverEntityType struct { - Encoded string -} - -func (t serverEntityType) EncodeEntity() string { - return t.Encoded -} - -func (t serverEntityType) BBox(e world.Entity) cube.BBox { - return cube.Box(-0.5, 0, -0.5, 0.5, 1, 0.5) -} - -func (t serverEntityType) DecodeNBT(m map[string]any) world.Entity { - return &serverEntity{ - EntityType: t, - NBT: m, - } -} - -func (t serverEntityType) EncodeNBT(e world.Entity) map[string]any { - se := e.(*serverEntity) - return se.NBT -} - -var _ world.SaveableEntityType = &serverEntityType{} - -type serverEntity struct { - EntityType serverEntityType - NBT map[string]any -} - -func (e serverEntity) Type() world.EntityType { - return e.EntityType -} - -func (e serverEntity) Position() mgl64.Vec3 { - return mgl64.Vec3{} -} -func (e serverEntity) Rotation() cube.Rotation { - return cube.Rotation{} -} - -func (e serverEntity) World() *world.World { - return nil -} - -func (e serverEntity) Close() error { - return nil -} - -func main() { - world, err := mcdb.Config{ - Entities: &entityReg{}, - ReadOnly: true, - }.Open(os.Args[1]) - if err != nil { - logrus.Fatal(err) - } - - it := world.NewColumnIterator(nil, false) - defer it.Release() - for it.Next() { - c := it.Column() - if err = it.Error(); err != nil { - logrus.Fatal(err) - } - - for _, e := range c.Entities { - se := e.(*serverEntity) - fmt.Printf("%s\n", e.Type().EncodeEntity()) - utils.DumpStruct(os.Stdout, se.NBT) - fmt.Print("\n\n") - } - } - -} diff --git a/gophertunnel b/gophertunnel index b3db337..3c67f15 160000 --- a/gophertunnel +++ b/gophertunnel @@ -1 +1 @@ -Subproject commit b3db337515e6d1bbf4a8c2b8cbc02dad3f770e4b +Subproject commit 3c67f15e0913bc5b1873090766582b7a5900db30 diff --git a/handlers/worlds/world.go b/handlers/worlds/world.go index 01c8c13..9653fde 100644 --- a/handlers/worlds/world.go +++ b/handlers/worlds/world.go @@ -443,15 +443,16 @@ func (w *worldsHandler) saveWorldState(worldState *worldstate.World) error { } defer f.Close() zw := zip.NewWriter(f) + utils.ZipCompressPool(zw) err = zw.AddFS(os.DirFS(worldState.Folder)) if err != nil { return err } - zfs := &utils.ZipWriter{Writer: zw} - ofs := &utils.OSWriter{Base: worldState.Folder} - mfs := &utils.MultiWriterFS{FSs: []utils.WriterFS{zfs, ofs}} - err = w.AddPacks(worldState.Name, mfs) + err = w.AddPacks(worldState.Name, utils.MultiWriterFS{FSs: []utils.WriterFS{ + utils.ZipWriter{Writer: zw}, + utils.OSWriter{Base: worldState.Folder}, + }}) if err != nil { return err } diff --git a/subcommands/realms-list.go b/subcommands/realms-list.go index 4c6dfdf..a08f0de 100644 --- a/subcommands/realms-list.go +++ b/subcommands/realms-list.go @@ -22,7 +22,7 @@ func (c *RealmListCMD) Execute(ctx context.Context) error { return err } } - realms, err := utils.Auth.Realms.Realms(ctx) + realms, err := utils.Auth.Realms().Realms(ctx) if err != nil { return err } diff --git a/subcommands/resourcepack-d/resourcepack-d.go b/subcommands/resourcepack-d/resourcepack-d.go index 69d1134..2f41c47 100644 Binary files a/subcommands/resourcepack-d/resourcepack-d.go and b/subcommands/resourcepack-d/resourcepack-d.go differ diff --git a/ui/gui/popups/realmsinput.go b/ui/gui/popups/realmsinput.go index afee248..6b4fbce 100644 --- a/ui/gui/popups/realmsinput.go +++ b/ui/gui/popups/realmsinput.go @@ -51,7 +51,7 @@ func (r *RealmsList) Load() error { if !utils.Auth.LoggedIn() { return errors.New("not Logged In") } - realms, err := utils.Auth.Realms.Realms(context.Background()) + realms, err := utils.Auth.Realms().Realms(context.Background()) if err != nil { return err } diff --git a/utils/auth.go b/utils/auth.go index 4cf58ba..baaf569 100644 --- a/utils/auth.go +++ b/utils/auth.go @@ -2,28 +2,33 @@ package utils import ( "context" + "crypto/ecdsa" + "crypto/x509" + "encoding/base64" "encoding/json" "errors" "io" "os" + "time" "github.com/bedrock-tool/bedrocktool/utils/discovery" "github.com/bedrock-tool/bedrocktool/utils/gatherings" "github.com/bedrock-tool/bedrocktool/utils/playfab" + "github.com/go-jose/go-jose/v3/jwt" + "github.com/sandertv/gophertunnel/minecraft" "github.com/sandertv/gophertunnel/minecraft/auth" "github.com/sandertv/gophertunnel/minecraft/realms" "github.com/sirupsen/logrus" "golang.org/x/oauth2" ) -const TokenFile = "token.json" - type authsrv struct { - log *logrus.Entry - handler auth.MSAuthHandler - liveToken *oauth2.Token + log *logrus.Entry + handler auth.MSAuthHandler + liveToken *oauth2.Token + discovery *discovery.Discovery - Realms *realms.Client + realms *realms.Client playfab *playfab.Client gatherings *gatherings.GatheringsClient } @@ -41,25 +46,6 @@ func (a *authsrv) Startup() (err error) { if err != nil { return err } - - return a.afterLogin() -} - -func (a *authsrv) afterLogin() (err error) { - a.discovery, err = discovery.GetDiscovery(Options.Env) - if err != nil { - return err - } - - /* - realmsService, err := a.discovery.RealmsfrontendService() - if err != nil { - return err - } - */ - - a.Realms = realms.NewClient(a, "") - a.playfab = playfab.NewClient(a.discovery) return nil } @@ -83,50 +69,36 @@ func (a *authsrv) Login(ctx context.Context) (err error) { if err != nil { return err } - return a.afterLogin() + return nil } func (a *authsrv) Logout() { a.liveToken = nil - os.Remove(TokenFile) + os.Remove("token.json") + os.Remove("chain.bin") } -func (a *authsrv) refreshLiveToken() (err error) { +func (a *authsrv) refreshLiveToken() error { if a.liveToken.Valid() { return nil } a.log.Info("Refreshing Microsoft Token") - a.liveToken, err = auth.RefreshToken(a.liveToken) + liveToken, err := auth.RefreshToken(a.liveToken) if err != nil { return err } - - err = a.writeToken(a.liveToken) - if err != nil { - return err - } - return nil + a.liveToken = liveToken + return a.writeToken(liveToken) } -var Ver1token func(f io.ReadSeeker) (*oauth2.Token, error) -var Tokene = func(t *oauth2.Token, w io.Writer) error { - return json.NewEncoder(w).Encode(t) -} - -// writes the livetoken to storage -func (a *authsrv) writeToken(token *oauth2.Token) error { - f, err := os.Create(TokenFile) - if err != nil { - return err - } - defer f.Close() - return Tokene(token, f) +var Ver1token func(f io.ReadSeeker, o any) error +var Tokene = func(w io.Writer, o any) error { + return json.NewEncoder(w).Encode(o) } -// reads the live token from storage, returns os.ErrNotExist if no token is stored -func (a *authsrv) readToken() (*oauth2.Token, error) { - f, err := os.Open(TokenFile) +func readAuth[T any](name string) (*T, error) { + f, err := os.Open(name) if err != nil { return nil, err } @@ -140,20 +112,44 @@ func (a *authsrv) readToken() (*oauth2.Token, error) { switch b[0] { case '{': - var token oauth2.Token + var o T e := json.NewDecoder(f) - err = e.Decode(&token) + err = e.Decode(&o) if err != nil { return nil, err } - return &token, nil + return &o, nil case '1': if Ver1token != nil { - return Ver1token(f) + var o T + err = Ver1token(f, &o) + if err != nil { + return nil, err + } + return &o, nil } } - return nil, errors.New("unsupported token file") + return nil, errors.New("unsupported auth file") +} + +func writeAuth(name string, o any) error { + f, err := os.Create(name) + if err != nil { + return err + } + defer f.Close() + return Tokene(f, o) +} + +// writes the livetoken to storage +func (a *authsrv) writeToken(token *oauth2.Token) error { + return writeAuth("token.json", token) +} + +// reads the live token from storage, returns os.ErrNotExist if no token is stored +func (a *authsrv) readToken() (*oauth2.Token, error) { + return readAuth[oauth2.Token]("token.json") } var ErrNotLoggedIn = errors.New("not logged in") @@ -172,33 +168,148 @@ func (a *authsrv) Token() (t *oauth2.Token, err error) { return a.liveToken, nil } +func (a *authsrv) Discovery() (d *discovery.Discovery, err error) { + if a.discovery == nil { + a.discovery, err = discovery.GetDiscovery(Options.Env) + if err != nil { + return nil, err + } + } + return a.discovery, nil +} + func (a *authsrv) Playfab(ctx context.Context) (*playfab.Client, error) { - if a.playfab.LoggedIn() { - return a.playfab, nil + if a.playfab == nil { + discovery, err := a.Discovery() + if err != nil { + return nil, err + } + a.playfab = playfab.NewClient(discovery) + } + if !a.playfab.LoggedIn() { + liveToken, err := a.Token() + if err != nil { + return nil, err + } + err = a.playfab.Login(ctx, liveToken) + if err != nil { + return nil, err + } } - liveToken, err := a.Token() + return a.playfab, nil +} + +func (a *authsrv) Gatherings(ctx context.Context) (*gatherings.GatheringsClient, error) { + if a.gatherings == nil { + playfabClient, err := a.Playfab(ctx) + if err != nil { + return nil, err + } + mcToken, err := playfabClient.MCToken() + if err != nil { + return nil, err + } + discovery, err := a.Discovery() + if err != nil { + return nil, err + } + a.gatherings = gatherings.NewGatheringsClient(mcToken, discovery) + } + return a.gatherings, nil +} + +func (a *authsrv) Realms() *realms.Client { + if a.realms == nil { + a.realms = realms.NewClient(a, "") + } + return a.realms +} + +type chain struct { + ChainKey *ecdsa.PrivateKey + ChainData string +} + +func (c *chain) UnmarshalJSON(b []byte) error { + var m map[string]string + err := json.Unmarshal(b, &m) if err != nil { - return nil, err + return err + } + chainKeyBase64, err := base64.StdEncoding.DecodeString(m["ChainKey"]) + if err != nil { + return err } - err = a.playfab.Login(ctx, liveToken) + chainKey, err := x509.ParseECPrivateKey(chainKeyBase64) + if err != nil { + return err + } + c.ChainKey = chainKey + c.ChainData = m["ChainData"] + return nil +} + +func (c *chain) MarshalJSON() ([]byte, error) { + ChainKey, err := x509.MarshalECPrivateKey(c.ChainKey) if err != nil { return nil, err } - return a.playfab, nil + return json.Marshal(map[string]string{ + "ChainKey": base64.StdEncoding.EncodeToString(ChainKey), + "ChainData": c.ChainData, + }) } -func (a *authsrv) Gatherings(ctx context.Context) (*gatherings.GatheringsClient, error) { - if a.gatherings != nil { - return a.gatherings, nil +func (c *chain) Expired() bool { + var m map[string]any + err := json.Unmarshal([]byte(c.ChainData), &m) + if err != nil { + return true } - playfabClient, err := a.Playfab(ctx) + chain := m["chain"].([]any)[0].(string) + tok, err := jwt.ParseSigned(chain) if err != nil { - return nil, err + return true } - mcToken, err := playfabClient.MCToken() + var mm map[string]any + err = tok.UnsafeClaimsWithoutVerification(&mm) if err != nil { - return nil, err + return true } - a.gatherings = gatherings.NewGatheringsClient(mcToken, a.discovery) - return a.gatherings, nil + exp := mm["exp"].(float64) + t := time.Unix(int64(exp), 0) + return time.Until(t) < 30*time.Second +} + +func (a *authsrv) readChain() (*chain, error) { + return readAuth[chain]("chain.bin") +} + +func (a *authsrv) writeChain(ch *chain) error { + return writeAuth("chain.bin", ch) +} + +func (a *authsrv) Chain(ctx context.Context) (ChainKey *ecdsa.PrivateKey, ChainData string, err error) { + ch, err := a.readChain() + if errors.Is(err, os.ErrNotExist) { + err = nil + } + if err != nil { + return nil, "", err + } + if ch == nil || ch.Expired() { + ChainKey, ChainData, err := minecraft.CreateChain(ctx, a) + if err != nil { + return nil, "", err + } + ch = &chain{ + ChainKey: ChainKey, + ChainData: ChainData, + } + err = a.writeChain(ch) + if err != nil { + return nil, "", err + } + } + return ch.ChainKey, ch.ChainData, nil } diff --git a/utils/fswriter.go b/utils/fswriter.go index 3323a47..ab259b9 100644 --- a/utils/fswriter.go +++ b/utils/fswriter.go @@ -15,7 +15,7 @@ type OSWriter struct { Base string } -func (o *OSWriter) Create(filename string) (w io.WriteCloser, err error) { +func (o OSWriter) Create(filename string) (w io.WriteCloser, err error) { fullpath := o.Base + "/" + filename err = os.MkdirAll(path.Dir(fullpath), 0777) if err != nil { @@ -32,13 +32,16 @@ type ZipWriter struct { Writer *zip.Writer } -func (z *ZipWriter) Create(filename string) (w io.WriteCloser, err error) { - z.Writer.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) { +func (z ZipWriter) Create(filename string) (w io.WriteCloser, err error) { + zw, err := z.Writer.Create(filename) + return nullCloser{zw}, err +} + +func ZipCompressPool(zw *zip.Writer) { + zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) { w := deflate.GetWriter(out) return closePutback{w}, nil }) - zw, err := z.Writer.Create(filename) - return nullCloser{zw}, err } type nullCloser struct { @@ -53,7 +56,7 @@ type MultiWriterFS struct { FSs []WriterFS } -func (m *MultiWriterFS) Create(filename string) (w io.WriteCloser, err error) { +func (m MultiWriterFS) Create(filename string) (w io.WriteCloser, err error) { var files []io.Writer var closers []func() error for _, fs := range m.FSs { diff --git a/utils/input.go b/utils/input.go index e313b83..ee45467 100644 --- a/utils/input.go +++ b/utils/input.go @@ -153,7 +153,7 @@ func ParseServer(ctx context.Context, server string) (*ConnectInfo, error) { if realmRegex.MatchString(server) { p := regexGetParams(realmRegex, server) - realmsList, err := Auth.Realms.Realms(ctx) + realmsList, err := Auth.Realms().Realms(ctx) if err != nil { return nil, err } diff --git a/utils/proxy/resourcepacks.go b/utils/proxy/resourcepacks.go index d1a72de..9b066a1 100644 --- a/utils/proxy/resourcepacks.go +++ b/utils/proxy/resourcepacks.go @@ -334,8 +334,9 @@ func (r *rpHandler) downloadResourcePack(pk *packet.ResourcePackDataInfo) error return err } + chunksDone := 0 dataWritten := 0 - for i := uint32(0); i < chunkCount; i++ { + for i := uint32(0); i < min(4, chunkCount); i++ { err = r.Server.WritePacket(&packet.ResourcePackChunkRequest{ UUID: pk.UUID, ChunkIndex: i, @@ -343,30 +344,64 @@ func (r *rpHandler) downloadResourcePack(pk *packet.ResourcePackDataInfo) error if err != nil { return err } + chunksDone++ + } + for { + var frag *packet.ResourcePackChunkData + var ok bool select { case <-r.Server.OnDisconnect(): return net.ErrClosed - case frag := <-pack.newFrag: - // Write the fragment to the full buffer of the downloading resource pack. - lastData := dataWritten+int(pack.chunkSize) >= int(pack.size) - if !lastData && uint32(len(frag.Data)) != pack.chunkSize { - // The chunk data didn't have the full size and wasn't the last data to be sent for the resource pack, - // meaning we got too little data. - return fmt.Errorf("resource pack chunk data had a length of %v, but expected %v", len(frag.Data), pack.chunkSize) - } + case frag, ok = <-pack.newFrag: + } + if !ok { + break + } - if frag.DataOffset != uint64(dataWritten) { - return fmt.Errorf("resourcepack current offset %d != %d fragment offset", dataWritten, frag.DataOffset) - } + // Write the fragment to the full buffer of the downloading resource pack. + lastData := dataWritten+int(pack.chunkSize) >= int(pack.size) + if !lastData && uint32(len(frag.Data)) != pack.chunkSize { + // The chunk data didn't have the full size and wasn't the last data to be sent for the resource pack, + // meaning we got too little data. + return fmt.Errorf("resource pack chunk data had a length of %v, but expected %v", len(frag.Data), pack.chunkSize) + } + + if frag.DataOffset != uint64(dataWritten) { + return fmt.Errorf("resourcepack current offset %d != %d fragment offset", dataWritten, frag.DataOffset) + } - _, err := f.Write(frag.Data) + _, err := f.Write(frag.Data) + if err != nil { + return err + } + dataWritten += len(frag.Data) + + if lastData { + break + } + + if chunksDone < int(chunkCount) { + err = r.Server.WritePacket(&packet.ResourcePackChunkRequest{ + UUID: pk.UUID, + ChunkIndex: uint32(chunksDone), + }) if err != nil { return err } - dataWritten += len(frag.Data) + chunksDone++ } } + /* + sf, ok := r.Server.(interface { + Stats() *raknet.RakNetStatistics + }) + if ok { + stats := sf.Stats() + utils.DumpStruct(os.Stdout, stats.Total) + } + */ + if dataWritten != int(pack.size) { return fmt.Errorf("incorrect resource pack size: expected %v, but got %v", pack.size, dataWritten) } diff --git a/utils/proxy/session.go b/utils/proxy/session.go index 6b3a121..a8fd0de 100644 --- a/utils/proxy/session.go +++ b/utils/proxy/session.go @@ -347,7 +347,7 @@ func (s *Session) connectServer(ctx context.Context, connect *utils.ConnectInfo) }, } for retry := 0; retry < 3; retry++ { - d.ChainKey, d.ChainData, err = minecraft.CreateChain(ctx, utils.Auth) + d.ChainKey, d.ChainData, err = utils.Auth.Chain(ctx) if err != nil { continue } diff --git a/utils/zip.go b/utils/zip.go index 6b8f209..33f4285 100644 --- a/utils/zip.go +++ b/utils/zip.go @@ -1,17 +1,10 @@ package utils import ( - "archive/zip" "io" - "io/fs" - "os" - "path/filepath" - "strings" "sync" "github.com/klauspost/compress/flate" - - "github.com/sirupsen/logrus" ) type closePutback struct { @@ -43,46 +36,3 @@ func (pool *DeflatePool) ReturnWriter(writer *flate.Writer) { _ = writer.Close() pool.pool.Put(writer) } - -func ZipFolder(filename, folder string) error { - f, err := os.Create(filename) - if err != nil { - return err - } - defer f.Close() - zw := zip.NewWriter(f) - - // Register a custom Deflate compressor. - zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) { - w := deflate.GetWriter(out) - return closePutback{w}, nil - }) - - err = filepath.WalkDir(folder, func(fpath string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.Type().IsDir() { - return nil - } - rel, err := filepath.Rel(folder, fpath) - if err != nil { - return err - } - rel = strings.ReplaceAll(rel, "\\", "/") - - zwf, _ := zw.Create(rel) - f, err := os.Open(fpath) - if err != nil { - logrus.Error(err) - return nil - } - _, err = io.Copy(zwf, f) - return err - }) - if err != nil { - return err - } - - return zw.Close() -}