diff --git a/go.mod b/go.mod index 13b47b9..df6b863 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/google/subcommands v1.2.0 github.com/google/uuid v1.3.0 github.com/miekg/dns v1.1.50 + github.com/pidurentry/buttplug-go v0.0.0-20190410200554-0b2e068d769a github.com/sandertv/gophertunnel v1.24.0 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 @@ -22,13 +23,15 @@ require ( replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.24.5 -replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.8.2-3 +replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.8.2-4 require ( github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect + github.com/cespare/xxhash v1.1.0 // indirect github.com/df-mc/atomic v1.10.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/gorilla/websocket v1.4.0 // indirect github.com/klauspost/compress v1.15.9 // indirect github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect github.com/sandertv/go-raknet v1.11.1 // indirect diff --git a/go.sum b/go.sum index 9ec3bcb..fdcc5fc 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,8 @@ +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 h1:/G0ghZwrhou0Wq21qc1vXXMm/t/aKWkALWwITptKbE0= github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9/go.mod h1:TOk10ahXejq9wkEaym3KPRNeuR/h5Jx+s8QRWIa2oTM= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -26,6 +29,8 @@ github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= @@ -41,6 +46,8 @@ github.com/olebeck/dragonfly v0.8.2-2 h1:rIUAEJx2ZeQhsRFNkHXrfYR3Zl85WB5spS/SZEH github.com/olebeck/dragonfly v0.8.2-2/go.mod h1:xgpCDhHoP03RygPaTrzzDwsSTcEZhxNPMV3CAxETj+I= github.com/olebeck/dragonfly v0.8.2-3 h1:qzcNc76U1E8RXpg98Fv02kxFulhsR/TWcKArqRY+xdY= github.com/olebeck/dragonfly v0.8.2-3/go.mod h1:xgpCDhHoP03RygPaTrzzDwsSTcEZhxNPMV3CAxETj+I= +github.com/olebeck/dragonfly v0.8.2-4 h1:K//DtE5R4DjcVB791y6Nx1Vg0/KzQwYoBb72ukUbdBA= +github.com/olebeck/dragonfly v0.8.2-4/go.mod h1:xgpCDhHoP03RygPaTrzzDwsSTcEZhxNPMV3CAxETj+I= github.com/olebeck/gophertunnel v1.24.4 h1:nX7Std61XpXW4VP7KKd2RvinRwx1nGB5l8QnbwrArLE= github.com/olebeck/gophertunnel v1.24.4/go.mod h1:dMOw79FHxr2azEqiGH20AwdljisAN1kqwu5SjPBnZ5k= github.com/olebeck/gophertunnel v1.24.5 h1:FtpHzfp833qQGBSJtKysSmRzFzsSheq5l61d5gTp03s= @@ -50,12 +57,15 @@ github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pidurentry/buttplug-go v0.0.0-20190410200554-0b2e068d769a h1:DIcuKv1jCe8vf10lqdD/5hZ2nnb3qYn0JycyXSnvE4s= +github.com/pidurentry/buttplug-go v0.0.0-20190410200554-0b2e068d769a/go.mod h1:oJXeBWse2BXoaZYRQFJ5p1Ut69KYf6SfiUJ/oaYnOhQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sandertv/go-raknet v1.11.1 h1:0auvhHoZNyC/Z1l5xqniE3JE+w3MGO3n3JXEGHzdlRE= github.com/sandertv/go-raknet v1.11.1/go.mod h1:Gx+WgZBMQ0V2UoouGoJ8Wj6CDrMBQ4SB2F/ggpl5/+Y= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/merge.go b/merge.go new file mode 100644 index 0000000..065fca4 --- /dev/null +++ b/merge.go @@ -0,0 +1,112 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + + "github.com/df-mc/dragonfly/server/world/mcdb" + "github.com/df-mc/goleveldb/leveldb/opt" + "github.com/google/subcommands" +) + +type MergeCMD struct { + worlds []string + legacy bool +} + +func (*MergeCMD) Name() string { return "merge" } +func (*MergeCMD) Synopsis() string { return "merge 2 or more worlds" } + +func (c *MergeCMD) SetFlags(f *flag.FlagSet) { + f.BoolVar(&c.legacy, "legacy", false, "if the worlds are before 1.18") +} +func (c *MergeCMD) Usage() string { + return c.Name() + ": " + c.Synopsis() + "\n" +} + +func (c *MergeCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + if f.NArg() == 0 { + fmt.Println("you need to specify 1 or more worlds") + return 1 + } + c.worlds = f.Args() + out_name := c.worlds[0] + "-merged" + + prov_out, err := mcdb.New(out_name, opt.DefaultCompression) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open output %s\n", err) + } + + for i, world_name := range c.worlds { + fmt.Printf("Adding %s\n", world_name) + s, err := os.Stat(world_name) + if errors.Is(err, os.ErrNotExist) { + fmt.Fprintf(os.Stderr, "%s not found\n", world_name) + } + if !s.IsDir() { + f, _ := os.Open(world_name) + world_name += "_unpack" + unpack_zip(f, s.Size(), world_name) + } + err = c.merge_worlds(prov_out, world_name, i == 0) + if err != nil { + fmt.Fprintf(os.Stderr, "%s %s\n", world_name, err) + } + } + return 0 +} + +func (c *MergeCMD) merge_worlds(prov_out *mcdb.Provider, folder string, first bool) error { + prov_in, err := mcdb.New(folder, opt.DefaultCompression) + if err != nil { + return err + } + count := 0 + existing := prov_out.Chunks(c.legacy) + new := prov_in.Chunks(c.legacy) + for i := range new { + if _, ok := existing[i]; !ok { + d := i.D + // chunks + ch, _, err := prov_in.LoadChunk(i.P, d) + if err != nil { + return err + } + if err := prov_out.SaveChunk(i.P, ch, i.D); err != nil { + return err + } + + // blockNBT + n, err := prov_in.LoadBlockNBT(i.P, i.D) + if err != nil { + return err + } + if err := prov_out.SaveBlockNBT(i.P, n, i.D); err != nil { + return err + } + + // entities + entities, err := prov_in.LoadEntities(i.P, i.D) + if err != nil { + return err + } + if err := prov_out.SaveEntities(i.P, entities, i.D); err != nil { + return err + } + + if first { + prov_out.SaveSettings(prov_in.Settings()) + } + count += 1 + } + } + fmt.Printf("Added: %d\n", count) + return nil +} + +func init() { + register_command(&MergeCMD{}) +} diff --git a/nop.notgo b/nop.notgo index 85f0393..9ef75ad 100644 --- a/nop.notgo +++ b/nop.notgo @@ -1 +1,7 @@ -package main \ No newline at end of file +package main + +func (w *WorldState) getPacks() (packs map[string]*resource.Pack, err error) { + for _, pack := range w.ServerConn.ResourcePacks() { + packs[pack.Name()] = pack + } +} diff --git a/resourcepack-ace.go b/resourcepack-ace.go index 7aad3ef..92f9769 100644 Binary files a/resourcepack-ace.go and b/resourcepack-ace.go differ diff --git a/utils.go b/utils.go index 062e3ed..76bb406 100644 --- a/utils.go +++ b/utils.go @@ -1,6 +1,7 @@ package main import ( + "archive/zip" "bufio" "bytes" "compress/gzip" @@ -17,6 +18,8 @@ import ( "strings" "time" + "github.com/go-gl/mathgl/mgl32" + "github.com/go-gl/mathgl/mgl64" "github.com/sandertv/gophertunnel/minecraft" "github.com/sandertv/gophertunnel/minecraft/auth" "github.com/sandertv/gophertunnel/minecraft/protocol/login" @@ -40,6 +43,14 @@ func send_popup(conn *minecraft.Conn, text string) { }) } +func v32tov64(in mgl32.Vec3) mgl64.Vec3 { + return mgl64.Vec3{ + float64(in[0]), + float64(in[1]), + float64(in[2]), + } +} + func write_token(token *oauth2.Token) { buf, err := json.Marshal(token) if err != nil { @@ -307,3 +318,18 @@ func splitext(filename string) (name, ext string) { } return } + +func unpack_zip(r io.ReaderAt, size int64, unpack_folder string) { + zr, _ := zip.NewReader(r, size) + for _, src_file := range zr.File { + out_path := path.Join(unpack_folder, src_file.Name) + if src_file.Mode().IsDir() { + os.Mkdir(out_path, 0755) + } else { + os.MkdirAll(path.Dir(out_path), 0755) + fr, _ := src_file.Open() + f, _ := os.Create(path.Join(unpack_folder, src_file.Name)) + io.Copy(f, fr) + } + } +} diff --git a/world.go b/world.go index 1edc393..b4f22cc 100644 --- a/world.go +++ b/world.go @@ -2,6 +2,7 @@ package main import ( "archive/zip" + "bytes" "context" "errors" "flag" @@ -19,18 +20,16 @@ import ( "time" "github.com/df-mc/dragonfly/server/block/cube" - "github.com/df-mc/dragonfly/server/entity" - "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/chunk" "github.com/df-mc/dragonfly/server/world/mcdb" "github.com/df-mc/goleveldb/leveldb/opt" "github.com/go-gl/mathgl/mgl32" - "github.com/go-gl/mathgl/mgl64" "github.com/google/subcommands" "github.com/sandertv/gophertunnel/minecraft" "github.com/sandertv/gophertunnel/minecraft/protocol" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" + "github.com/sandertv/gophertunnel/minecraft/resource" _ "github.com/df-mc/dragonfly/server/block" // to load blocks ) @@ -53,6 +52,7 @@ type WorldState struct { WorldName string ServerName string worldCounter int + packs map[string]*resource.Pack PlayerPos TPlayerPos ClientConn *minecraft.Conn @@ -103,6 +103,7 @@ func init() { type WorldCMD struct { server_address string + packs bool } func (*WorldCMD) Name() string { return "worlds" } @@ -110,6 +111,7 @@ func (*WorldCMD) Synopsis() string { return "download a world from a server" } func (p *WorldCMD) SetFlags(f *flag.FlagSet) { f.StringVar(&p.server_address, "address", "", "remote server address") + f.BoolVar(&p.packs, "packs", false, "save resourcepacks to the worlds") } func (c *WorldCMD) Usage() string { return c.Name() + ": " + c.Synopsis() + "\n" + SERVER_ADDRESS_HELP @@ -127,30 +129,18 @@ func (c *WorldCMD) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{ fmt.Fprintln(os.Stderr, err) return 1 } - handleConn(ctx, listener, clientConn, serverConn, hostname) + + c.handleConn(ctx, listener, clientConn, serverConn, hostname) return 0 } -// overworld with 0 - 255 height -type overworld_legacy struct{} - -func (overworld_legacy) Range() cube.Range { return cube.Range{0, 255} } -func (overworld_legacy) EncodeDimension() int { return 0 } -func (overworld_legacy) WaterEvaporates() bool { return false } -func (overworld_legacy) LavaSpreadDuration() time.Duration { return time.Second * 3 / 2 } -func (overworld_legacy) WeatherCycle() bool { return true } -func (overworld_legacy) TimeCycle() bool { return true } -func (overworld_legacy) String() string { return "Overworld" } - -var Overworld_legacy overworld_legacy - -var dimension_ids = map[int32]world.Dimension{ +var dimension_ids = map[uint8]world.Dimension{ 0: world.Overworld, 1: world.Nether, 2: world.End, // < 1.18 - 10: Overworld_legacy, + 10: world.Overworld_legacy, 11: world.Nether, 12: world.End, } @@ -245,19 +235,6 @@ func (w *WorldState) ProcessAnimate(pk *packet.Animate) { } } -func (w *WorldState) ProcessAddItemActor(pk *packet.AddItemActor) { - it, ok := world.ItemByRuntimeID(pk.Item.StackNetworkID, int16(pk.Item.Stack.MetadataValue)) - if !ok { - return - } - stack := item.NewStack(it, int(pk.Item.Stack.Count)) - w.entities[pk.EntityUniqueID] = entity.NewItem(stack, mgl64.Vec3{ - float64(pk.Position[0]), - float64(pk.Position[1]), - float64(pk.Position[2]), - }) -} - func (w *WorldState) ProcessChangeDimension(pk *packet.ChangeDimension) { fmt.Printf("ChangeDimension %d\n", pk.Dimension) if len(w.chunks) > 0 { @@ -270,7 +247,7 @@ func (w *WorldState) ProcessChangeDimension(pk *packet.ChangeDimension) { if w.ispre118 { dim_id += 10 } - w.Dim = dimension_ids[dim_id] + w.Dim = dimension_ids[uint8(dim_id)] } func (w *WorldState) SendMessage(text string) { @@ -307,13 +284,15 @@ func (w *WorldState) Reset() { // writes the world to a folder, resets all the chunks func (w *WorldState) SaveAndReset() { - fmt.Println("Saving world") var world_name string if w.WorldName == "world" { world_name = fmt.Sprintf("%s-%d", w.WorldName, w.worldCounter) } else { world_name = w.WorldName } + fmt.Printf("Saving world %s\n", world_name) + + // open world folder := path.Join("worlds", fmt.Sprintf("%s/%s", w.ServerName, world_name)) os.MkdirAll(folder, 0777) provider, err := mcdb.New(folder, opt.DefaultCompression) @@ -321,17 +300,17 @@ func (w *WorldState) SaveAndReset() { log.Fatal(err) } + // save chunk data for cp, c := range w.chunks { provider.SaveChunk((world.ChunkPos)(cp), c, w.Dim) } + // save block nbt data var blockNBT = make(map[protocol.ChunkPos][]map[string]any) - for scp, v := range w.blockNBT { // 3d to 2d cp := protocol.ChunkPos{scp.X(), scp.Z()} blockNBT[cp] = append(blockNBT[cp], v...) } - for cp, v := range blockNBT { err = provider.SaveBlockNBT((world.ChunkPos)(cp), v, w.Dim) if err != nil { @@ -339,8 +318,7 @@ func (w *WorldState) SaveAndReset() { } } - //provider.SaveEntities(w.entities) - + // write metadata s := provider.Settings() s.Spawn = cube.Pos{ int(w.PlayerPos.Position[0]), @@ -349,6 +327,7 @@ func (w *WorldState) SaveAndReset() { } s.Name = w.WorldName + // set gamerules ld := provider.LevelDat() for _, gr := range w.ServerConn.GameData().GameRules { switch gr.Name { @@ -418,7 +397,7 @@ func (w *WorldState) SaveAndReset() { } } - // dont generate + // void world ld.FlatWorldLayers = `{"biome_id":1,"block_layers":[{"block_data":0,"block_id":0,"count":1},{"block_data":0,"block_id":0,"count":2},{"block_data":0,"block_id":0,"count":1}],"encoding_version":3,"structure_options":null}` ld.Generator = 2 @@ -426,6 +405,16 @@ func (w *WorldState) SaveAndReset() { provider.Close() w.worldCounter += 1 + for k, p := range w.packs { + fmt.Printf("Adding resource pack: %s\n", k) + pack_folder := path.Join(folder, "resource_packs", k) + os.MkdirAll(pack_folder, 0755) + data := make([]byte, p.Len()) + p.ReadAt(data, 0) + unpack_zip(bytes.NewReader(data), int64(len(data)), pack_folder) + } + + // zip it filename := folder + ".mcworld" f, err := os.Create(filename) if err != nil { @@ -454,14 +443,21 @@ func (w *WorldState) SaveAndReset() { w.Reset() } -func handleConn(ctx context.Context, l *minecraft.Listener, cc, sc *minecraft.Conn, server_name string) { +func (c *WorldCMD) handleConn(ctx context.Context, l *minecraft.Listener, cc, sc *minecraft.Conn, server_name string) { + var err error w := NewWorldState() w.ServerName = server_name w.ClientConn = cc w.ServerConn = sc + if c.packs { + fmt.Println("reformatting packs") + go func() { + w.packs, err = w.getPacks() + }() + } + gv := strings.Split(w.ServerConn.GameData().BaseGameVersion, ".") - var err error if len(gv) > 1 { var ver int ver, err = strconv.Atoi(gv[1]) @@ -551,7 +547,7 @@ func handleConn(ctx context.Context, l *minecraft.Listener, cc, sc *minecraft.Co if w.ispre118 { dim_id += 10 } - w.Dim = dimension_ids[dim_id] + w.Dim = dimension_ids[uint8(dim_id)] for { pk, err := w.ServerConn.ReadPacket() @@ -571,8 +567,6 @@ func handleConn(ctx context.Context, l *minecraft.Listener, cc, sc *minecraft.Co send_popup(w.ClientConn, fmt.Sprintf("%d chunks loaded\nname: %s", len(w.chunks), w.WorldName)) case *packet.SubChunk: w.ProcessSubChunk(pk) - case *packet.AddItemActor: - w.ProcessAddItemActor(pk) case *packet.AvailableCommands: pk.Commands = append(pk.Commands, setname_command) }