diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea7..a08d58c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,7 +8,8 @@ assignees: '' --- **Describe the bug** -A clear and concise description of what the bug is. +A clear and concise description of what the bug is, +and what server it occurs on. **To Reproduce** Steps to reproduce the behavior: @@ -24,15 +25,20 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + - OS: [e.g. windows] + - Version [e.g. 1.28.0-36] + - Minecraft Version [e.g. 1.19.73] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + - OS: [e.g. iOS12] + - Version [e.g. 1.28.0-36] **Additional context** Add any other context about the problem here. + + +**attach packets.log.gpg (not always necessary)** +this file can be helpful for debugging without having to connect to the server. +it can be created by running with -extra-debug [e.g. bedrocktool.exe -extra-debug worlds -address play.mojang.com ] +be sure to only attach the .gpg file which is not publicly readable. \ No newline at end of file diff --git a/handlers/packet_logger.go b/handlers/packet_logger.go index 6fb5ed6..e347a14 100644 --- a/handlers/packet_logger.go +++ b/handlers/packet_logger.go @@ -231,6 +231,7 @@ func NewDebugLogger(extraVerbose bool) *utils.ProxyHandler { } }, OnEnd: func() { + dmpLock.Lock() if packetsLogF != nil { packetsLogF.Flush() } @@ -243,6 +244,7 @@ func NewDebugLogger(extraVerbose bool) *utils.ProxyHandler { if logCrypt != nil { logCrypt.Close() } + dmpLock.Unlock() }, } } diff --git a/handlers/worlds/chunk.go b/handlers/worlds/chunk.go index 4498b64..6f6fa86 100644 --- a/handlers/worlds/chunk.go +++ b/handlers/worlds/chunk.go @@ -11,12 +11,7 @@ import ( ) func (w *worldsHandler) processChangeDimension(pk *packet.ChangeDimension) { - if len(w.worldState.chunks) > 0 { - w.SaveAndReset() - } else { - logrus.Info(locale.Loc("not_saving_empty", nil)) - w.Reset(w.CurrentName()) - } + w.SaveAndReset() dimensionID := pk.Dimension if w.serverState.ispre118 && dimensionID == 0 { dimensionID += 10 diff --git a/handlers/worlds/map_item.go b/handlers/worlds/map_item.go index ad8f4dd..4ae6a56 100644 --- a/handlers/worlds/map_item.go +++ b/handlers/worlds/map_item.go @@ -22,7 +22,7 @@ import ( const ViewMapID = 0x424242 // MapItemPacket tells the client that it has a map with id 0x424242 in the offhand -var MapItemPacket packet.InventoryContent = packet.InventoryContent{ +var MapItemPacket = packet.InventoryContent{ WindowID: 119, Content: []protocol.ItemInstance{ { @@ -112,9 +112,6 @@ func (m *MapUI) Start() { MapID: ViewMapID, Scale: 4, MapsIncludedIn: []int64{ViewMapID}, - Width: 0, - Height: 0, - Pixels: nil, UpdateFlags: packet.MapUpdateFlagInitialisation, }) if err != nil { @@ -248,7 +245,7 @@ func (m *MapUI) ToImage() *image.RGBA { chunksX := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate chunksY := int(max[1] - min[1] + 1) - img2 := image.NewRGBA(image.Rect(0, 0, chunksX*16, chunksY*16)) + img := image.NewRGBA(image.Rect(0, 0, chunksX*16, chunksY*16)) m.l.RLock() for pos, tile := range m.renderedChunks { @@ -256,13 +253,13 @@ func (m *MapUI) ToImage() *image.RGBA { int((pos.X()-min.X())*16), int((pos.Z()-min.Z())*16), ) - draw.Draw(img2, image.Rect( + draw.Draw(img, image.Rect( px.X, px.Y, px.X+16, px.Y+16, ), tile, image.Point{}, draw.Src) } m.l.RUnlock() - return img2 + return img } func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk, complete bool) { diff --git a/handlers/worlds/world.go b/handlers/worlds/world.go index cb0f845..1d969bd 100644 --- a/handlers/worlds/world.go +++ b/handlers/worlds/world.go @@ -4,12 +4,14 @@ import ( "context" "encoding/json" "fmt" + "image" "image/png" "math/rand" "os" "path" "strconv" "strings" + "sync" "time" "github.com/bedrock-tool/bedrocktool/locale" @@ -72,6 +74,7 @@ type serverState struct { type worldsHandler struct { ctx context.Context + wg sync.WaitGroup proxy *utils.ProxyContext mapUI *MapUI gui utils.UI @@ -97,7 +100,7 @@ func NewWorldsHandler(ctx context.Context, ui utils.UI, settings WorldSettings) settings: settings, } w.mapUI = NewMapUI(w) - w.Reset(w.CurrentName()) + w.Reset() return &utils.ProxyHandler{ Name: "Worlds", @@ -169,6 +172,7 @@ func NewWorldsHandler(ctx context.Context, ui utils.UI, settings WorldSettings) }, OnEnd: func() { w.SaveAndReset() + w.wg.Wait() }, } } @@ -203,7 +207,7 @@ func (w *worldsHandler) setWorldName(val string, fromUI bool) bool { return true } -func (w *worldsHandler) CurrentName() string { +func (w *worldsHandler) currentName() string { worldName := "world" if w.serverState.worldCounter > 0 { worldName = fmt.Sprintf("world-%d", w.serverState.worldCounter) @@ -211,14 +215,14 @@ func (w *worldsHandler) CurrentName() string { return worldName } -func (w *worldsHandler) Reset(newName string) { +func (w *worldsHandler) Reset() { w.worldState = worldState{ dimension: w.worldState.dimension, chunks: make(map[protocol.ChunkPos]*chunk.Chunk), blockNBT: make(map[protocol.SubChunkPos][]map[string]any), entities: make(map[uint64]*entityState), openItemContainers: make(map[byte]*itemContainer), - Name: newName, + Name: w.currentName(), } w.mapUI.Reset() } @@ -280,155 +284,171 @@ func (w *worldState) Save(folder string) (*mcdb.DB, error) { return provider, err } -// SaveAndReset writes the world to a folder, resets all the chunks func (w *worldsHandler) SaveAndReset() { w.worldState.cullChunks() if len(w.worldState.chunks) == 0 { - w.Reset(w.CurrentName()) + w.Reset() return } - logrus.Infof(locale.Loc("saving_world", locale.Strmap{"Name": w.worldState.Name, "Count": len(w.worldState.chunks)})) - w.gui.Message(messages.SavingWorld{ - Name: w.worldState.Name, - Chunks: len(w.worldState.chunks), - }) - - // open world - folder := path.Join("worlds", fmt.Sprintf("%s/%s", w.serverState.Name, w.worldState.Name)) - os.RemoveAll(folder) - os.MkdirAll(folder, 0o777) - provider, err := w.worldState.Save(folder) - if err != nil { - logrus.Error(err) - return - } - - err = provider.SaveLocalPlayerData(w.playerData()) - if err != nil { - logrus.Error(err) - } - + worldStateCopy := w.worldState + playerData := w.playerData() playerPos := w.serverState.PlayerPos.Position spawnPos := cube.Pos{int(playerPos.X()), int(playerPos.Y()), int(playerPos.Z())} - // write metadata - s := provider.Settings() - s.Spawn = spawnPos - s.Name = w.worldState.Name - - // set gamerules - ld := provider.LevelDat() - gd := w.proxy.Server.GameData() - ld.RandomSeed = int64(gd.WorldSeed) - for _, gr := range gd.GameRules { - switch gr.Name { - case "commandblockoutput": - ld.CommandBlockOutput = gr.Value.(bool) - case "maxcommandchainlength": - ld.MaxCommandChainLength = int32(gr.Value.(uint32)) - case "commandblocksenabled": - ld.CommandsEnabled = gr.Value.(bool) - case "dodaylightcycle": - ld.DoDayLightCycle = gr.Value.(bool) - case "doentitydrops": - ld.DoEntityDrops = gr.Value.(bool) - case "dofiretick": - ld.DoFireTick = gr.Value.(bool) - case "domobloot": - ld.DoMobLoot = gr.Value.(bool) - case "domobspawning": - ld.DoMobSpawning = gr.Value.(bool) - case "dotiledrops": - ld.DoTileDrops = gr.Value.(bool) - case "doweathercycle": - ld.DoWeatherCycle = gr.Value.(bool) - case "drowningdamage": - ld.DrowningDamage = gr.Value.(bool) - case "doinsomnia": - ld.DoInsomnia = gr.Value.(bool) - case "falldamage": - ld.FallDamage = gr.Value.(bool) - case "firedamage": - ld.FireDamage = gr.Value.(bool) - case "keepinventory": - ld.KeepInventory = gr.Value.(bool) - case "mobgriefing": - ld.MobGriefing = gr.Value.(bool) - case "pvp": - ld.PVP = gr.Value.(bool) - case "showcoordinates": - ld.ShowCoordinates = gr.Value.(bool) - case "naturalregeneration": - ld.NaturalRegeneration = gr.Value.(bool) - case "tntexplodes": - ld.TNTExplodes = gr.Value.(bool) - case "sendcommandfeedback": - ld.SendCommandFeedback = gr.Value.(bool) - case "randomtickspeed": - ld.RandomTickSpeed = int32(gr.Value.(uint32)) - case "doimmediaterespawn": - ld.DoImmediateRespawn = gr.Value.(bool) - case "showdeathmessages": - ld.ShowDeathMessages = gr.Value.(bool) - case "functioncommandlimit": - ld.FunctionCommandLimit = int32(gr.Value.(uint32)) - case "spawnradius": - ld.SpawnRadius = int32(gr.Value.(uint32)) - case "showtags": - ld.ShowTags = gr.Value.(bool) - case "freezedamage": - ld.FreezeDamage = gr.Value.(bool) - case "respawnblocksexplode": - ld.RespawnBlocksExplode = gr.Value.(bool) - case "showbordereffect": - ld.ShowBorderEffect = gr.Value.(bool) - // todo - default: - logrus.Warnf(locale.Loc("unknown_gamerule", locale.Strmap{"Name": gr.Name})) - } - } - - // void world - if w.settings.VoidGen { - 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 - } - - if w.bp.HasContent() { - if ld.Experiments == nil { - ld.Experiments = map[string]any{} - } - ld.Experiments["data_driven_items"] = true - ld.Experiments["experiments_ever_used"] = true - ld.Experiments["saved_with_toggled_experiments"] = true - } - ld.RandomTickSpeed = 0 - s.CurrentTick = 0 - provider.SaveSettings(s) - if err = provider.Close(); err != nil { - logrus.Error(err) + var img image.Image + if w.settings.SaveImage { + img = w.mapUI.ToImage() } w.serverState.worldCounter += 1 + w.Reset() + w.wg.Add(1) + go func() { + defer w.wg.Done() + logrus.Infof(locale.Loc("saving_world", locale.Strmap{"Name": worldStateCopy.Name, "Count": len(worldStateCopy.chunks)})) + w.gui.Message(messages.SavingWorld{ + Name: w.worldState.Name, + Chunks: len(w.worldState.chunks), + }) - if w.settings.SaveImage { - f, _ := os.Create(folder + ".png") - png.Encode(f, w.mapUI.ToImage()) - f.Close() - } + // open world + folder := fmt.Sprintf("worlds/%s/%s", w.serverState.Name, worldStateCopy.Name) + os.RemoveAll(folder) + os.MkdirAll(folder, 0o777) + provider, err := worldStateCopy.Save(folder) + if err != nil { + logrus.Error(err) + return + } - w.AddPacks(folder) + err = provider.SaveLocalPlayerData(playerData) + if err != nil { + logrus.Error(err) + return + } - // zip it - filename := folder + ".mcworld" - if err := utils.ZipFolder(filename, folder); err != nil { - logrus.Error(err) - } - logrus.Info(locale.Loc("saved", locale.Strmap{"Name": filename})) - //os.RemoveAll(folder) - w.Reset(w.CurrentName()) - w.gui.Message(messages.SetUIState(messages.UIStateMain)) + // write metadata + s := provider.Settings() + s.Spawn = spawnPos + s.Name = worldStateCopy.Name + + // set gamerules + ld := provider.LevelDat() + gd := w.proxy.Server.GameData() + ld.RandomSeed = int64(gd.WorldSeed) + for _, gr := range gd.GameRules { + switch gr.Name { + case "commandblockoutput": + ld.CommandBlockOutput = gr.Value.(bool) + case "maxcommandchainlength": + ld.MaxCommandChainLength = int32(gr.Value.(uint32)) + case "commandblocksenabled": + ld.CommandsEnabled = gr.Value.(bool) + case "dodaylightcycle": + ld.DoDayLightCycle = gr.Value.(bool) + case "doentitydrops": + ld.DoEntityDrops = gr.Value.(bool) + case "dofiretick": + ld.DoFireTick = gr.Value.(bool) + case "domobloot": + ld.DoMobLoot = gr.Value.(bool) + case "domobspawning": + ld.DoMobSpawning = gr.Value.(bool) + case "dotiledrops": + ld.DoTileDrops = gr.Value.(bool) + case "doweathercycle": + ld.DoWeatherCycle = gr.Value.(bool) + case "drowningdamage": + ld.DrowningDamage = gr.Value.(bool) + case "doinsomnia": + ld.DoInsomnia = gr.Value.(bool) + case "falldamage": + ld.FallDamage = gr.Value.(bool) + case "firedamage": + ld.FireDamage = gr.Value.(bool) + case "keepinventory": + ld.KeepInventory = gr.Value.(bool) + case "mobgriefing": + ld.MobGriefing = gr.Value.(bool) + case "pvp": + ld.PVP = gr.Value.(bool) + case "showcoordinates": + ld.ShowCoordinates = gr.Value.(bool) + case "naturalregeneration": + ld.NaturalRegeneration = gr.Value.(bool) + case "tntexplodes": + ld.TNTExplodes = gr.Value.(bool) + case "sendcommandfeedback": + ld.SendCommandFeedback = gr.Value.(bool) + case "randomtickspeed": + ld.RandomTickSpeed = int32(gr.Value.(uint32)) + case "doimmediaterespawn": + ld.DoImmediateRespawn = gr.Value.(bool) + case "showdeathmessages": + ld.ShowDeathMessages = gr.Value.(bool) + case "functioncommandlimit": + ld.FunctionCommandLimit = int32(gr.Value.(uint32)) + case "spawnradius": + ld.SpawnRadius = int32(gr.Value.(uint32)) + case "showtags": + ld.ShowTags = gr.Value.(bool) + case "freezedamage": + ld.FreezeDamage = gr.Value.(bool) + case "respawnblocksexplode": + ld.RespawnBlocksExplode = gr.Value.(bool) + case "showbordereffect": + ld.ShowBorderEffect = gr.Value.(bool) + // todo + default: + logrus.Warnf(locale.Loc("unknown_gamerule", locale.Strmap{"Name": gr.Name})) + } + } + + // void world + if w.settings.VoidGen { + 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 + } + + ld.RandomTickSpeed = 0 + s.CurrentTick = 0 + + if w.bp.HasContent() { + if ld.Experiments == nil { + ld.Experiments = map[string]any{} + } + ld.Experiments["data_driven_items"] = true + ld.Experiments["experiments_ever_used"] = true + ld.Experiments["saved_with_toggled_experiments"] = true + } + + provider.SaveSettings(s) + err = provider.Close() + if err != nil { + logrus.Error(err) + return + } + + if w.settings.SaveImage { + f, _ := os.Create(folder + ".png") + png.Encode(f, img) + f.Close() + } + + w.AddPacks(folder) + + // zip it + filename := folder + ".mcworld" + err = utils.ZipFolder(filename, folder) + if err != nil { + logrus.Error(err) + return + } + logrus.Info(locale.Loc("saved", locale.Strmap{"Name": filename})) + //os.RemoveAll(folder) + w.gui.Message(messages.SetUIState(messages.UIStateMain)) + }() } func (w *worldsHandler) AddPacks(folder string) { diff --git a/utils/images.go b/utils/images.go index fe654b8..799e796 100644 --- a/utils/images.go +++ b/utils/images.go @@ -21,7 +21,7 @@ func loadPng(path string) (*image.RGBA, error) { if err != nil { return nil, err } - return (*image.RGBA)(img.(*image.NRGBA)), err + return (*image.RGBA)(img.(*image.NRGBA)), nil } // LERP is a linear interpolation function diff --git a/utils/proxy.go b/utils/proxy.go index 23ed361..ca4c18e 100644 --- a/utils/proxy.go +++ b/utils/proxy.go @@ -53,12 +53,12 @@ type ( type ProxyHandler struct { Name string - ProxyRef func(*ProxyContext) + ProxyRef func(pc *ProxyContext) // AddressAndName func(address, hostname string) error // called to change game data - GameDataModifier func(*minecraft.GameData) + GameDataModifier func(gd *minecraft.GameData) // called for every packet PacketFunc func(header packet.Header, payload []byte, src, dst net.Addr) @@ -210,9 +210,9 @@ func (p *ProxyContext) AddHandler(handler *ProxyHandler) { } func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, toServer bool, _ time.Time) (packet.Packet, error) { - switch _pk := pk.(type) { + switch pk := pk.(type) { case *packet.CommandRequest: - cmd := strings.Split(_pk.CommandLine, " ") + cmd := strings.Split(pk.CommandLine, " ") name := cmd[0][1:] if h, ok := p.commands[name]; ok { if h.Exec(cmd[1:]) { @@ -220,13 +220,13 @@ func (p *ProxyContext) CommandHandlerPacketCB(pk packet.Packet, toServer bool, _ } } case *packet.AvailableCommands: - cmds := make([]protocol.Command, len(p.commands)) + cmds := make([]protocol.Command, 0, len(p.commands)) for _, ic := range p.commands { cmds = append(cmds, ic.Cmd) } pk = &packet.AvailableCommands{ - Constraints: _pk.Constraints, - Commands: append(_pk.Commands, cmds...), + Constraints: pk.Constraints, + Commands: append(pk.Commands, cmds...), } } return pk, nil @@ -321,9 +321,9 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err logrus.Infof(locale.Locm("pack_count_loaded", locale.Strmap{"Count": len(packs)}, len(packs))) } - _status := minecraft.NewStatusProvider("Server") + status := minecraft.NewStatusProvider(fmt.Sprintf("%s Proxy", name)) p.Listener, err = minecraft.ListenConfig{ - StatusProvider: _status, + StatusProvider: status, ResourcePacks: packs, AcceptedProtocols: []minecraft.Protocol{ //dummyProto{id: 567, ver: "1.19.60"},