Compare commits

...

7 Commits

Author SHA1 Message Date
olebeck 5149487c18 rewrite replay to support resourcepacks 2023-04-17 21:48:12 +02:00
olebeck 87cd32e280 small cleanup 2023-04-12 14:10:45 +02:00
olebeck aa73c86d11 code cleaning, remove dns 2023-04-10 19:55:08 +02:00
olebeck c20e017f0d fix packs with same names causing issues 2023-04-07 17:40:29 +02:00
olebeck 2c87715966 fix bug with extra debug 2023-04-07 16:35:36 +02:00
olebeck 7726c707c6 small fixes 2023-04-07 16:24:38 +02:00
olebeck 2e920a7bf0 fix shadows on entities 2023-04-05 16:07:49 +02:00
34 changed files with 1050 additions and 748 deletions

View File

@ -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.

View File

@ -35,7 +35,6 @@ func (c *CLI) Init() bool {
func (c *CLI) Start(ctx context.Context, cancel context.CancelFunc) error {
flag.Parse()
utils.InitDNS()
subcommands.Execute(ctx)
time.Sleep(50 * time.Millisecond)
cancel()

5
go.mod
View File

@ -3,10 +3,10 @@ module github.com/bedrock-tool/bedrocktool
go 1.20
//replace github.com/sandertv/gophertunnel => ./gophertunnel
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.28.1-1
replace github.com/sandertv/gophertunnel => github.com/olebeck/gophertunnel v1.28.1-2
//replace github.com/df-mc/dragonfly => ./dragonfly
replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.9.4-7
replace github.com/df-mc/dragonfly => github.com/olebeck/dragonfly v0.9.4-9
//replace gioui.org => ./gio
replace gioui.org => github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9
@ -51,6 +51,7 @@ require (
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 // indirect
github.com/df-mc/atomic v1.10.0 // indirect
github.com/df-mc/worldupgrader v1.0.3 // indirect
github.com/dlclark/regexp2 v1.9.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 // indirect
github.com/golang/protobuf v1.5.2 // indirect

6
go.sum
View File

@ -37,6 +37,8 @@ github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU
github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk=
github.com/df-mc/worldupgrader v1.0.3 h1:3nbthy6vfSNQZdqHBR+E5Fh3mCeWmCwLtqrYDiPUG5I=
github.com/df-mc/worldupgrader v1.0.3/go.mod h1:6ybkJ/BV9b0XkcPzcLmvgT9Nv/xgBXdDQTmRhu7b8zQ=
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/flytam/filenamify v1.1.2 h1:dGlfWU4zrhDlsmvob4IFcfgjG5vIjfo4UwLyec6Wx94=
@ -94,10 +96,14 @@ github.com/olebeck/dragonfly v0.9.4-6 h1:Pom7oMbUA/kFu6PCwr3mWtTOSPvgzD2/71+mUsq
github.com/olebeck/dragonfly v0.9.4-6/go.mod h1:k8OQvjmfj+JbrwQf1qHfMETlFHOp0WJLjILN+QVqh+c=
github.com/olebeck/dragonfly v0.9.4-7 h1:xzSc9U9upx+mxayAHN1MxkD+PStVgqksJ4uls0o3g4w=
github.com/olebeck/dragonfly v0.9.4-7/go.mod h1:k8OQvjmfj+JbrwQf1qHfMETlFHOp0WJLjILN+QVqh+c=
github.com/olebeck/dragonfly v0.9.4-9 h1:VgcYFAyEZMo2VSm68fkUZ68iye7xmOWttt6gvjAG6Uw=
github.com/olebeck/dragonfly v0.9.4-9/go.mod h1:k8OQvjmfj+JbrwQf1qHfMETlFHOp0WJLjILN+QVqh+c=
github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9 h1:TqDsMHwjW5ZYfh+RE8ussT62m0qXqo+QjzSXb7BCVA4=
github.com/olebeck/gio v0.0.0-20230321105529-d424f1a59af9/go.mod h1:+W1Kpf96YcfissZocFqIp6O42FDTuphkObbEybp+Ffc=
github.com/olebeck/gophertunnel v1.28.1-1 h1:bw2jeMz94YHF5qQYhq1Yq/6fALkklGu7k26YbPI4DSs=
github.com/olebeck/gophertunnel v1.28.1-1/go.mod h1:ekREo7U9TPHh86kbuPMaWA93NMyWsfVvP/iNT3XhAb8=
github.com/olebeck/gophertunnel v1.28.1-2 h1:EoGyQNzmsXtg+ObOshCKyNffTqAfk9yRFNdJcHGjxmY=
github.com/olebeck/gophertunnel v1.28.1-2/go.mod h1:HxQfl/8mZzvjzhekEH8RO6xLAgan9i/wIyrQzw0tIPY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
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=

View File

@ -4,7 +4,6 @@ import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"os"
"sync"
@ -14,23 +13,22 @@ import (
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
var dumpLock sync.Mutex
func dumpPacket(f io.WriteCloser, toServer bool, payload []byte) {
dumpLock.Lock()
defer dumpLock.Unlock()
f.Write([]byte{0xAA, 0xAA, 0xAA, 0xAA})
func (p *packetCapturer) dumpPacket(toServer bool, payload []byte) {
p.dumpLock.Lock()
defer p.dumpLock.Unlock()
p.fio.Write([]byte{0xAA, 0xAA, 0xAA, 0xAA})
packetSize := uint32(len(payload))
binary.Write(f, binary.LittleEndian, packetSize)
binary.Write(f, binary.LittleEndian, toServer)
binary.Write(f, binary.LittleEndian, time.Now().UnixMilli())
f.Write(payload)
f.Write([]byte{0xBB, 0xBB, 0xBB, 0xBB})
binary.Write(p.fio, binary.LittleEndian, packetSize)
binary.Write(p.fio, binary.LittleEndian, toServer)
binary.Write(p.fio, binary.LittleEndian, time.Now().UnixMilli())
p.fio.Write(payload)
p.fio.Write([]byte{0xBB, 0xBB, 0xBB, 0xBB})
}
type packetCapturer struct {
proxy *utils.ProxyContext
fio *os.File
proxy *utils.ProxyContext
fio *os.File
dumpLock sync.Mutex
}
func (p *packetCapturer) AddressAndName(address, hostname string) error {
@ -45,12 +43,10 @@ func (p *packetCapturer) AddressAndName(address, hostname string) error {
}
func (p *packetCapturer) PacketFunc(header packet.Header, payload []byte, src, dst net.Addr) {
IsfromClient := p.proxy.IsClient(src)
buf := bytes.NewBuffer(nil)
header.Write(buf)
buf.Write(payload)
dumpPacket(p.fio, IsfromClient, buf.Bytes())
p.dumpPacket(p.proxy.IsClient(src), buf.Bytes())
}
func NewPacketCapturer() *utils.ProxyHandler {
@ -63,8 +59,8 @@ func NewPacketCapturer() *utils.ProxyHandler {
AddressAndName: p.AddressAndName,
PacketFunc: p.PacketFunc,
OnEnd: func() {
dumpLock.Lock()
defer dumpLock.Unlock()
p.dumpLock.Lock()
defer p.dumpLock.Unlock()
p.fio.Close()
},
}

View File

@ -48,11 +48,6 @@ var MutedPackets = []string{
"packet.PlaySound",
}
var (
FLog io.Writer
dmpLock sync.Mutex
)
func dmpStruct(level int, inputStruct any, withType bool, isInList bool) (s string) {
tBase := strings.Repeat("\t", level)
@ -173,15 +168,6 @@ func dmpStruct(level int, inputStruct any, withType bool, isInList bool) (s stri
return s
}
func DumpStruct(data interface{}) {
if FLog == nil {
return
}
FLog.Write([]byte(dmpStruct(0, data, true, false)))
FLog.Write([]byte("\n\n\n"))
}
var dirS2C = color.GreenString("S") + "->" + color.CyanString("C")
var dirC2S = color.CyanString("C") + "->" + color.GreenString("S")
var pool = packet.NewPool()
@ -189,6 +175,7 @@ var pool = packet.NewPool()
func NewDebugLogger(extraVerbose bool) *utils.ProxyHandler {
var logPlain, logCrypt, logCryptEnc io.WriteCloser
var packetsLogF *bufio.Writer
var dmpLock sync.Mutex
if extraVerbose {
// open plain text log
@ -196,19 +183,14 @@ func NewDebugLogger(extraVerbose bool) *utils.ProxyHandler {
if err != nil {
logrus.Error(err)
}
// open gpg log
logCrypt, err := os.Create("packets.log.gpg")
logCryptEnc, err = crypt.Encer("packets.log.gpg")
if err != nil {
logrus.Error(err)
} else {
// encrypter for the log
logCryptEnc, err = crypt.Encer("packets.log", logCrypt)
if err != nil {
logrus.Error(err)
}
}
packetsLogF = bufio.NewWriter(io.MultiWriter(logPlain, logCryptEnc))
if logPlain != nil || logCryptEnc != nil {
packetsLogF = bufio.NewWriter(io.MultiWriter(logPlain, logCryptEnc))
}
}
var proxy *utils.ProxyContext
@ -233,7 +215,7 @@ func NewDebugLogger(extraVerbose bool) *utils.ProxyHandler {
}()
pk.Marshal(protocol.NewReader(bytes.NewBuffer(payload), 0))
if extraVerbose {
if packetsLogF != nil {
dmpLock.Lock()
packetsLogF.Write([]byte(dmpStruct(0, pk, true, false) + "\n\n\n"))
dmpLock.Unlock()
@ -249,6 +231,7 @@ func NewDebugLogger(extraVerbose bool) *utils.ProxyHandler {
}
},
OnEnd: func() {
dmpLock.Lock()
if packetsLogF != nil {
packetsLogF.Flush()
}
@ -261,6 +244,7 @@ func NewDebugLogger(extraVerbose bool) *utils.ProxyHandler {
if logCrypt != nil {
logCrypt.Close()
}
dmpLock.Unlock()
},
}
}

View File

@ -80,9 +80,8 @@ func (s *secondaryUser) processLevelChunk(pk *packet.LevelChunk) {
}
func (s *secondaryUser) processSubChunk(pk *packet.SubChunk) {
offsets := make([]protocol.SubChunkOffset, 0, len(pk.SubChunkEntries))
offsets := make(map[world.ChunkPos]bool, len(pk.SubChunkEntries))
for _, sub := range pk.SubChunkEntries {
offsets = append(offsets, sub.Offset)
var (
absX = pk.Position[0] + int32(sub.Offset[0])
absY = pk.Position[1] + int32(sub.Offset[1])
@ -90,6 +89,7 @@ func (s *secondaryUser) processSubChunk(pk *packet.SubChunk) {
subPos = protocol.SubChunkPos{absX, absY, absZ}
pos = world.ChunkPos{absX, absZ}
)
offsets[pos] = true
ch, ok := s.chunks[pos]
if !ok {
logrus.Error(locale.Loc("subchunk_before_chunk", nil))
@ -107,6 +107,13 @@ func (s *secondaryUser) processSubChunk(pk *packet.SubChunk) {
}
for _, p := range s.server.Players() {
p.Session().ViewSubChunks(world.SubChunkPos(pk.Position), offsets)
for pos := range offsets {
ch, ok := s.chunks[pos]
if !ok {
continue
}
p.Session().ViewChunk(pos, ch, nil)
}
}
}

View File

@ -66,7 +66,7 @@ func NewSecondUser() *utils.ProxyHandler {
s.proxy = pc
},
SecondaryClientCB: s.SecondaryClientCB,
OnClientConnect: func(conn *minecraft.Conn) {
OnClientConnect: func(conn minecraft.IConn) {
id := conn.IdentityData()
s.mainPlayer = player.New(id.DisplayName, skin.New(64, 64), mgl64.Vec3{0, 00})
s.server.World().AddEntity(s.mainPlayer)
@ -99,7 +99,7 @@ func NewSecondUser() *utils.ProxyHandler {
}
}
func (s *secondaryUser) SecondaryClientCB(conn *minecraft.Conn) {
func (s *secondaryUser) SecondaryClientCB(conn minecraft.IConn) {
s.listener.Conn <- conn
}

View File

@ -56,13 +56,13 @@ func (s *SkinSaver) AddSkin(playerName string, playerID uuid.UUID, playerSkin *p
}
}
if !strings.HasPrefix(playerName, s.PlayerNameFilter) {
return "", nil, false
return playerName, nil, false
}
s.playerNames[playerID] = playerName
skin := &utils.Skin{Skin: playerSkin}
if s.OnlyIfHasGeometry && !skin.HaveGeometry() {
return "", nil, false
return playerName, nil, false
}
wasAdded := s.AddPlayerSkin(playerID, playerName, skin)

View File

@ -11,18 +11,12 @@ 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
}
d, _ := world.DimensionByID(int(dimensionID))
w.worldState.dimension = d
w.worldState.dimension, _ = world.DimensionByID(int(dimensionID))
}
func (w *worldsHandler) processLevelChunk(pk *packet.LevelChunk) {
@ -158,7 +152,7 @@ func (w *worldsHandler) ProcessChunkPackets(pk packet.Packet) packet.Packet {
if ok {
x, y, z := blockPosInChunk(pk.Position)
c.SetBlock(x, y, z, uint8(pk.Layer), pk.NewBlockRuntimeID)
w.mapUI.SetChunk(cp, w.worldState.chunks[cp], true)
w.mapUI.SetChunk(cp, c, true)
}
}
case *packet.UpdateSubChunkBlocks:
@ -174,7 +168,7 @@ func (w *worldsHandler) ProcessChunkPackets(pk packet.Packet) packet.Packet {
c.SetBlock(x, y, z, 0, bce.BlockRuntimeID)
}
}
w.mapUI.SetChunk(cp, w.worldState.chunks[cp], true)
w.mapUI.SetChunk(cp, c, true)
}
}
}

View File

@ -14,7 +14,7 @@ import (
func Test(t *testing.T) {
data, _ := os.ReadFile("chunk.bin")
ch, _, _ := chunk.NetworkDecode(33, data, 6, cube.Range{0, 255}, true, false)
i := utils.Chunk2Img(ch, true)
i := utils.Chunk2Img(ch)
f, _ := os.Create("chunk.png")
png.Encode(f, i)
f.Close()
@ -47,7 +47,7 @@ func Benchmark_render_chunk(b *testing.B) {
b.Error(err)
}
for i := 0; i < b.N; i++ {
utils.Chunk2Img(ch, true)
utils.Chunk2Img(ch)
}
pprof.StopCPUProfile()
}

View File

@ -62,7 +62,7 @@ func (e serverEntity) Type() world.EntityType {
}
func (w *worldsHandler) processAddActor(pk *packet.AddActor) {
e, ok := w.worldState.entities[pk.EntityRuntimeID]
e, ok := w.getEntity(pk.EntityRuntimeID)
if !ok {
e = &entityState{
RuntimeID: pk.EntityRuntimeID,
@ -197,7 +197,7 @@ func (s *entityState) ToServerEntity() serverEntity {
Encoded: s.EntityType,
NBT: map[string]any{
"Pos": vec3float32(s.Position),
"Rotation": []float32{s.Yaw, s.Pitch},
"Rotation": []float32{s.HeadYaw, s.Pitch},
"Motion": vec3float32(s.Velocity),
"UniqueID": int64(s.UniqueID),
},
@ -216,6 +216,11 @@ func (s *entityState) ToServerEntity() serverEntity {
return e
}
func (w *worldsHandler) getEntity(id uint64) (*entityState, bool) {
e, ok := w.worldState.entities[id]
return e, ok
}
func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
if !w.settings.SaveEntities {
return pk
@ -226,7 +231,7 @@ func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
w.processAddActor(pk)
case *packet.RemoveActor:
case *packet.SetActorData:
e, ok := w.worldState.entities[pk.EntityRuntimeID]
e, ok := w.getEntity(pk.EntityRuntimeID)
if ok {
e.Metadata = pk.EntityMetadata
w.bp.AddEntity(behaviourpack.EntityIn{
@ -236,12 +241,12 @@ func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
})
}
case *packet.SetActorMotion:
e, ok := w.worldState.entities[pk.EntityRuntimeID]
e, ok := w.getEntity(pk.EntityRuntimeID)
if ok {
e.Velocity = pk.Velocity
}
case *packet.MoveActorDelta:
e, ok := w.worldState.entities[pk.EntityRuntimeID]
e, ok := w.getEntity(pk.EntityRuntimeID)
if ok {
if pk.Flags&packet.MoveActorDeltaFlagHasX != 0 {
e.Position[0] = pk.Position[0]
@ -263,14 +268,14 @@ func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
//}
}
case *packet.MoveActorAbsolute:
e, ok := w.worldState.entities[pk.EntityRuntimeID]
e, ok := w.getEntity(pk.EntityRuntimeID)
if ok {
e.Position = pk.Position
e.Pitch = pk.Rotation.X()
e.Yaw = pk.Rotation.Y()
}
case *packet.MobEquipment:
e, ok := w.worldState.entities[pk.EntityRuntimeID]
e, ok := w.getEntity(pk.EntityRuntimeID)
if ok {
w, ok := e.Inventory[pk.WindowID]
if !ok {
@ -280,7 +285,7 @@ func (w *worldsHandler) ProcessEntityPackets(pk packet.Packet) packet.Packet {
w[pk.HotBarSlot] = pk.NewItem
}
case *packet.MobArmourEquipment:
e, ok := w.worldState.entities[pk.EntityRuntimeID]
e, ok := w.getEntity(pk.EntityRuntimeID)
if ok {
e.Helmet = &pk.Helmet
e.Chestplate = &pk.Chestplate

View File

@ -112,10 +112,6 @@ func (w *worldsHandler) processItemPacketsServer(pk packet.Packet) packet.Packet
case *packet.ItemComponent:
w.bp.ApplyComponentEntries(pk.Items)
case *packet.MobArmourEquipment:
if pk.EntityRuntimeID == w.proxy.Server.GameData().EntityRuntimeID {
}
}
return pk
}
@ -190,199 +186,3 @@ func stackToItem(it protocol.ItemStack) item.Stack {
s := item.NewStack(t, int(it.Count))
return nbtconv.Item(it.NBTData, &s)
}
func (w *worldsHandler) playerData() (ret map[string]any) {
ret = map[string]any{
"format_version": "1.12.0",
"identifier": "minecraft:player",
}
if len(w.serverState.playerInventory) > 0 {
inv := inventory.New(len(w.serverState.playerInventory), nil)
for i, ii := range w.serverState.playerInventory {
inv.SetItem(i, stackToItem(ii.Stack))
}
ret["Inventory"] = nbtconv.InvToNBT(inv)
}
ret["abilities"] = map[string]any{
"doorsandswitches": true,
"op": true,
"opencontainers": true,
"teleport": true,
"attackmobs": true,
"instabuild": true,
"permissionsLevel": int32(3),
"flying": false,
"lightning": false,
"playerPermissionsLevel": int32(2),
"attackplayers": true,
"build": true,
"flySpeed": float32(0.05),
"invulnerable": true,
"mayfly": true,
"mine": true,
"walkSpeed": float32(0.1),
}
type attribute struct {
Name string
Base float32
Current float32
DefaultMax float32
DefaultMin float32
Max float32
Min float32
}
ret["Attributes"] = []attribute{
{
Base: 0,
Current: 0,
DefaultMax: 1024,
DefaultMin: -1024,
Max: 1024,
Min: -1024,
Name: "minecraft:luck",
},
{
Base: 20,
Current: 20,
DefaultMax: 20,
DefaultMin: 0,
Max: 20,
Min: 0,
Name: "minecraft:health",
},
{
Base: 0,
Current: 0,
DefaultMax: 16,
DefaultMin: 0,
Max: 16,
Min: 0,
Name: "minecraft:absorption",
},
{
Base: 0,
Current: 0,
DefaultMax: 1,
DefaultMin: 0,
Max: 1,
Min: 0,
Name: "minecraft:knockback_resistance",
},
{
Base: 0.1,
Current: 0.1,
DefaultMax: 3.4028235e+38,
DefaultMin: 0,
Max: 3.4028235e+38,
Min: 0,
Name: "minecraft:movement",
},
{
Base: 0.02,
Current: 0.02,
DefaultMax: 3.4028235e+38,
DefaultMin: 0,
Max: 3.4028235e+38,
Min: 0,
Name: "minecraft:underwater_movement",
},
{
Base: 0.02,
Current: 0.02,
DefaultMax: 3.4028235e+38,
DefaultMin: 0,
Max: 3.4028235e+38,
Min: 0,
Name: "minecraft:lava_movement",
},
{
Base: 16,
Current: 16,
DefaultMax: 2048,
DefaultMin: 0,
Max: 2048,
Min: 0,
Name: "minecraft:follow_range",
},
{
Base: 1,
Current: 1,
DefaultMax: 1,
DefaultMin: 1,
Max: 1,
Min: 1,
Name: "minecraft:attack_damage",
},
{
Base: 20,
Current: 20,
DefaultMax: 20,
DefaultMin: 0,
Max: 20,
Min: 0,
Name: "minecraft:player.hunger",
},
{
Base: 0,
Current: 0,
DefaultMax: 20,
DefaultMin: 0,
Max: 20,
Min: 0,
Name: "minecraft:player.exhaustion",
},
{
Base: 5,
Current: 5,
DefaultMax: 20,
DefaultMin: 0,
Max: 20,
Min: 0,
Name: "minecraft:player.saturation",
},
{
Base: 0,
Current: 0,
DefaultMax: 24791,
DefaultMin: 0,
Max: 24791,
Min: 0,
Name: "minecraft:player.level",
},
{
Base: 0,
Current: 0,
DefaultMax: 1,
DefaultMin: 0,
Max: 1,
Min: 0,
Name: "minecraft:player.experience",
},
}
ret["Tags"] = []string{}
ret["OnGround"] = true
spawn := w.proxy.Server.GameData().PlayerPosition
ret["SpawnX"] = int32(spawn.X())
ret["SpawnY"] = int32(spawn.Y())
ret["SpawnZ"] = int32(spawn.Z())
ret["Pos"] = []float32{
float32(spawn.X()),
float32(spawn.Y()),
float32(spawn.Z()),
}
ret["Rotation"] = []float32{
w.serverState.PlayerPos.Pitch,
w.serverState.PlayerPos.Yaw,
}
return
}

View File

@ -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{
{
@ -43,31 +43,37 @@ var MapItemPacket packet.InventoryContent = packet.InventoryContent{
},
}
func imin(a, b int32) int32 {
if a < b {
return a
}
return b
}
func imax(a, b int32) int32 {
if a > b {
return a
}
return b
}
func (m *MapUI) GetBounds() (min, max protocol.ChunkPos) {
// get the chunk coord bounds
i := 0
for _ch := range m.renderedChunks {
if _ch.X() < min.X() || i == 0 {
min[0] = _ch.X()
}
if _ch.Z() < min.Z() || i == 0 {
min[1] = _ch.Z()
}
if _ch.X() > max.X() || i == 0 {
max[0] = _ch.X()
}
if _ch.Z() > max.Z() || i == 0 {
max[1] = _ch.Z()
}
i++
if len(m.renderedChunks) == 0 {
return
}
min = protocol.ChunkPos{math.MaxInt32, math.MaxInt32}
for chunk := range m.renderedChunks {
min[0] = imin(min[0], chunk[0])
min[1] = imin(min[1], chunk[1])
max[0] = imax(max[0], chunk[0])
max[1] = imax(max[1], chunk[1])
}
return
}
type RenderElem struct {
pos protocol.ChunkPos
ch *chunk.Chunk
complete bool
pos protocol.ChunkPos
ch *chunk.Chunk
}
type MapUI struct {
@ -106,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 {
@ -190,9 +193,7 @@ func (m *MapUI) Redraw() {
break
}
if r.ch != nil {
m.renderedChunks[r.pos] = utils.Chunk2Img(r.ch, !r.complete)
} else {
m.renderedChunks[r.pos] = black16x16
m.renderedChunks[r.pos] = utils.Chunk2Img(r.ch)
}
updatedChunks = append(updatedChunks, r.pos)
}
@ -244,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 {
@ -252,17 +253,17 @@ 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) {
m.renderQueue.Enqueue(&RenderElem{pos, ch, complete})
m.renderQueue.Enqueue(&RenderElem{pos, ch})
m.SchedRedraw()
}

202
handlers/worlds/player.go Normal file
View File

@ -0,0 +1,202 @@
package worlds
import (
"github.com/bedrock-tool/bedrocktool/utils/nbtconv"
"github.com/df-mc/dragonfly/server/item/inventory"
)
func (w *worldsHandler) playerData() (ret map[string]any) {
ret = map[string]any{
"format_version": "1.12.0",
"identifier": "minecraft:player",
}
if len(w.serverState.playerInventory) > 0 {
inv := inventory.New(len(w.serverState.playerInventory), nil)
for i, ii := range w.serverState.playerInventory {
inv.SetItem(i, stackToItem(ii.Stack))
}
ret["Inventory"] = nbtconv.InvToNBT(inv)
}
ret["abilities"] = map[string]any{
"doorsandswitches": true,
"op": true,
"opencontainers": true,
"teleport": true,
"attackmobs": true,
"instabuild": true,
"permissionsLevel": int32(3),
"flying": false,
"lightning": false,
"playerPermissionsLevel": int32(2),
"attackplayers": true,
"build": true,
"flySpeed": float32(0.05),
"invulnerable": true,
"mayfly": true,
"mine": true,
"walkSpeed": float32(0.1),
}
type attribute struct {
Name string
Base float32
Current float32
DefaultMax float32
DefaultMin float32
Max float32
Min float32
}
ret["Attributes"] = []attribute{
{
Base: 0,
Current: 0,
DefaultMax: 1024,
DefaultMin: -1024,
Max: 1024,
Min: -1024,
Name: "minecraft:luck",
},
{
Base: 20,
Current: 20,
DefaultMax: 20,
DefaultMin: 0,
Max: 20,
Min: 0,
Name: "minecraft:health",
},
{
Base: 0,
Current: 0,
DefaultMax: 16,
DefaultMin: 0,
Max: 16,
Min: 0,
Name: "minecraft:absorption",
},
{
Base: 0,
Current: 0,
DefaultMax: 1,
DefaultMin: 0,
Max: 1,
Min: 0,
Name: "minecraft:knockback_resistance",
},
{
Base: 0.1,
Current: 0.1,
DefaultMax: 3.4028235e+38,
DefaultMin: 0,
Max: 3.4028235e+38,
Min: 0,
Name: "minecraft:movement",
},
{
Base: 0.02,
Current: 0.02,
DefaultMax: 3.4028235e+38,
DefaultMin: 0,
Max: 3.4028235e+38,
Min: 0,
Name: "minecraft:underwater_movement",
},
{
Base: 0.02,
Current: 0.02,
DefaultMax: 3.4028235e+38,
DefaultMin: 0,
Max: 3.4028235e+38,
Min: 0,
Name: "minecraft:lava_movement",
},
{
Base: 16,
Current: 16,
DefaultMax: 2048,
DefaultMin: 0,
Max: 2048,
Min: 0,
Name: "minecraft:follow_range",
},
{
Base: 1,
Current: 1,
DefaultMax: 1,
DefaultMin: 1,
Max: 1,
Min: 1,
Name: "minecraft:attack_damage",
},
{
Base: 20,
Current: 20,
DefaultMax: 20,
DefaultMin: 0,
Max: 20,
Min: 0,
Name: "minecraft:player.hunger",
},
{
Base: 0,
Current: 0,
DefaultMax: 20,
DefaultMin: 0,
Max: 20,
Min: 0,
Name: "minecraft:player.exhaustion",
},
{
Base: 5,
Current: 5,
DefaultMax: 20,
DefaultMin: 0,
Max: 20,
Min: 0,
Name: "minecraft:player.saturation",
},
{
Base: 0,
Current: 0,
DefaultMax: 24791,
DefaultMin: 0,
Max: 24791,
Min: 0,
Name: "minecraft:player.level",
},
{
Base: 0,
Current: 0,
DefaultMax: 1,
DefaultMin: 0,
Max: 1,
Min: 0,
Name: "minecraft:player.experience",
},
}
ret["Tags"] = []string{}
ret["OnGround"] = true
spawn := w.serverState.PlayerPos.Position
ret["SpawnX"] = int32(spawn.X())
ret["SpawnY"] = int32(spawn.Y())
ret["SpawnZ"] = int32(spawn.Z())
ret["Pos"] = []float32{
float32(spawn.X()),
float32(spawn.Y()),
float32(spawn.Z()),
}
ret["Rotation"] = []float32{
w.serverState.PlayerPos.Pitch,
w.serverState.PlayerPos.Yaw,
}
return
}

View File

@ -11,12 +11,14 @@ import (
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/ui/messages"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/bedrock-tool/bedrocktool/utils/behaviourpack"
"github.com/flytam/filenamify"
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
@ -72,6 +74,7 @@ type serverState struct {
type worldsHandler struct {
ctx context.Context
wg sync.WaitGroup
proxy *utils.ProxyContext
mapUI *MapUI
gui utils.UI
@ -82,34 +85,22 @@ type worldsHandler struct {
settings WorldSettings
}
var black16x16 = image.NewRGBA(image.Rect(0, 0, 16, 16))
func init() {
for i := 3; i < len(black16x16.Pix); i += 4 {
black16x16.Pix[i] = 255
}
}
func NewWorldsHandler(ctx context.Context, ui utils.UI, settings WorldSettings) *utils.ProxyHandler {
w := &worldsHandler{
ctx: ctx,
mapUI: nil,
gui: ui,
bp: nil,
ctx: ctx,
gui: ui,
serverState: serverState{
ispre118: false,
worldCounter: 0,
ChunkRadius: 0,
playerInventory: nil,
PlayerPos: TPlayerPos{},
PlayerPos: TPlayerPos{},
},
settings: settings,
}
w.mapUI = NewMapUI(w)
w.Reset(w.CurrentName())
w.Reset()
return &utils.ProxyHandler{
Name: "Worlds",
@ -148,7 +139,7 @@ func NewWorldsHandler(ctx context.Context, ui utils.UI, settings WorldSettings)
w.serverState.Name = hostname
return nil
},
OnClientConnect: func(conn *minecraft.Conn) {
OnClientConnect: func(conn minecraft.IConn) {
w.gui.Message(messages.SetUIState(messages.UIStateConnecting))
},
GameDataModifier: func(gd *minecraft.GameData) {
@ -181,6 +172,7 @@ func NewWorldsHandler(ctx context.Context, ui utils.UI, settings WorldSettings)
},
OnEnd: func() {
w.SaveAndReset()
w.wg.Wait()
},
}
}
@ -215,22 +207,22 @@ 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 > 1 {
if w.serverState.worldCounter > 0 {
worldName = fmt.Sprintf("world-%d", w.serverState.worldCounter)
}
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()
}
@ -249,8 +241,11 @@ func (w *worldState) cullChunks() {
}
}
func (w *worldState) Save(folder string) (*mcdb.Provider, error) {
provider, err := mcdb.New(logrus.StandardLogger(), folder, opt.DefaultCompression)
func (w *worldState) Save(folder string) (*mcdb.DB, error) {
provider, err := mcdb.Config{
Log: logrus.StandardLogger(),
Compression: opt.DefaultCompression,
}.New(folder)
if err != nil {
return nil, err
}
@ -289,138 +284,174 @@ func (w *worldState) Save(folder string) (*mcdb.Provider, 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)
}
playerPos := w.proxy.Server.GameData().PlayerPosition
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),
})
// 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
}
err = provider.SaveLocalPlayerData(playerData)
if err != nil {
logrus.Error(err)
return
}
// 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) {
type dep struct {
PackID string `json:"pack_id"`
Version [3]int `json:"version"`
@ -467,22 +498,31 @@ func (w *worldsHandler) SaveAndReset() {
if err != nil {
logrus.Error(err)
} else {
packNames := make(map[string]int)
for _, pack := range packs {
packNames[pack.Name()] += 1
}
var rdeps []dep
for k, p := range packs {
if p.Encrypted() && !p.CanDecrypt() {
logrus.Warnf("Cant add %s, it is encrypted", p.Name())
for _, pack := range packs {
if pack.Encrypted() && !pack.CanDecrypt() {
logrus.Warnf("Cant add %s, it is encrypted", pack.Name())
continue
}
logrus.Infof(locale.Loc("adding_pack", locale.Strmap{"Name": k}))
name := p.Name()
name = strings.ReplaceAll(name, ":", "_")
packFolder := path.Join(folder, "resource_packs", name)
logrus.Infof(locale.Loc("adding_pack", locale.Strmap{"Name": pack.Name()}))
packName := pack.Name()
if packNames[packName] > 1 {
packName += "_" + pack.UUID()
}
packName, _ = filenamify.FilenamifyV2(packName)
packFolder := path.Join(folder, "resource_packs", packName)
os.MkdirAll(packFolder, 0o755)
utils.UnpackZip(p, int64(p.Len()), packFolder)
utils.UnpackZip(pack, int64(pack.Len()), packFolder)
rdeps = append(rdeps, dep{
PackID: p.Manifest().Header.UUID,
Version: p.Manifest().Header.Version,
PackID: pack.Manifest().Header.UUID,
Version: pack.Manifest().Header.Version,
})
}
if len(rdeps) > 0 {
@ -490,22 +530,6 @@ func (w *worldsHandler) SaveAndReset() {
}
}
}
if w.settings.SaveImage {
f, _ := os.Create(folder + ".png")
png.Encode(f, w.mapUI.ToImage())
f.Close()
}
// 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))
}
func (w *worldsHandler) OnConnect(err error) bool {

View File

@ -33,6 +33,7 @@ func (c *CaptureCMD) Execute(ctx context.Context, ui utils.UI) error {
if err != nil {
return err
}
proxy.AlwaysGetPacks = true
proxy.AddHandler(handlers.NewPacketCapturer())
return proxy.Run(ctx, address, hostname)
}

View File

@ -8,7 +8,6 @@ import (
"github.com/bedrock-tool/bedrocktool/locale"
"github.com/bedrock-tool/bedrocktool/ui/messages"
"github.com/bedrock-tool/bedrocktool/utils"
"github.com/sandertv/gophertunnel/minecraft"
)
@ -43,7 +42,7 @@ func (c *SkinCMD) Execute(ctx context.Context, ui utils.UI) error {
}))
proxy.AddHandler(&utils.ProxyHandler{
Name: "Skin CMD",
OnClientConnect: func(conn *minecraft.Conn) {
OnClientConnect: func(conn minecraft.IConn) {
ui.Message(messages.SetUIState(messages.UIStateConnecting))
},
})

View File

@ -22,7 +22,7 @@ type Page interface {
NavItem() component.NavItem
// handle events from program
Handler() HandlerFunc
Handler(data any) messages.MessageResponse
}
type Router struct {
@ -136,7 +136,7 @@ func (r *Router) Layout(gtx layout.Context, th *material.Theme) layout.Dimension
func (r *Router) Handler(data interface{}) messages.MessageResponse {
page, ok := r.pages[r.current]
if ok {
return page.Handler()(data)
return page.Handler(data)
}
return messages.MessageResponse{}
}

View File

@ -101,7 +101,6 @@ func (p *Page) Layout(gtx C, th *material.Theme) D {
p.Router.Wg.Add(1)
go func() {
defer p.Router.Wg.Done()
utils.InitDNS()
err := cmd.Execute(p.Router.Ctx, utils.CurrentUI)
if err != nil {
@ -174,11 +173,9 @@ func (p *Page) Layout(gtx C, th *material.Theme) D {
})
}
func (p *Page) Handler() pages.HandlerFunc {
return func(data interface{}) messages.MessageResponse {
return messages.MessageResponse{
Ok: false,
Data: nil,
}
func (p *Page) Handler(any) messages.MessageResponse {
return messages.MessageResponse{
Ok: false,
Data: nil,
}
}

View File

@ -8,7 +8,6 @@ import (
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
"github.com/bedrock-tool/bedrocktool/ui/gui"
"github.com/bedrock-tool/bedrocktool/ui/gui/pages"
"github.com/bedrock-tool/bedrocktool/ui/messages"
)
@ -96,7 +95,7 @@ func (p *Page) Layout(gtx C, th *material.Theme) D {
return layout.Flex{}.Layout(gtx)
}
func (p *Page) handler(data interface{}) messages.MessageResponse {
func (p *Page) Handler(data interface{}) messages.MessageResponse {
r := messages.MessageResponse{
Ok: false,
Data: nil,
@ -116,7 +115,3 @@ func (p *Page) handler(data interface{}) messages.MessageResponse {
}
return r
}
func (p *Page) Handler() gui.HandlerFunc {
return p.handler
}

View File

@ -10,7 +10,6 @@ import (
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
"github.com/bedrock-tool/bedrocktool/ui/gui"
"github.com/bedrock-tool/bedrocktool/ui/gui/pages"
"github.com/bedrock-tool/bedrocktool/ui/messages"
)
@ -115,7 +114,7 @@ func (p *Page) Layout(gtx C, th *material.Theme) D {
return layout.Dimensions{}
}
func (u *Page) handler(data interface{}) messages.MessageResponse {
func (u *Page) Handler(data any) messages.MessageResponse {
r := messages.MessageResponse{
Ok: false,
Data: nil,
@ -148,7 +147,3 @@ func (u *Page) handler(data interface{}) messages.MessageResponse {
}
return r
}
func (p *Page) Handler() gui.HandlerFunc {
return p.handler
}

View File

@ -1,11 +0,0 @@
package gui
import (
"gioui.org/layout"
"github.com/bedrock-tool/bedrocktool/ui/messages"
)
type C = layout.Context
type D = layout.Dimensions
type HandlerFunc = func(data interface{}) messages.MessageResponse

View File

@ -20,7 +20,7 @@ const (
UIStateFinished
)
type HandlerFunc = func(name string, data interface{}) MessageResponse
type HandlerFunc = func(data interface{}) MessageResponse
//

View File

@ -72,11 +72,23 @@ func (bp *BehaviourPack) AddEntity(entity EntityIn) {
"value": scale,
}
}
AlwaysShowName := entity.Meta.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName)
if AlwaysShowName {
entry.MinecraftEntity.Components["minecraft:nameable"] = map[string]any{
"always_show": true,
"allow_name_tag_renaming": false,
width, widthOk := entity.Meta[protocol.EntityDataKeyWidth].(float32)
height, heightOk := entity.Meta[protocol.EntityDataKeyHeight].(float32)
if widthOk || heightOk {
entry.MinecraftEntity.Components["minecraft:collision_box"] = map[string]any{
"width": width,
"height": height,
}
}
if _, ok := entity.Meta[protocol.EntityDataKeyFlags]; ok {
AlwaysShowName := entity.Meta.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName)
if AlwaysShowName {
entry.MinecraftEntity.Components["minecraft:nameable"] = map[string]any{
"always_show": true,
"allow_name_tag_renaming": false,
}
}
}

View File

@ -97,7 +97,7 @@ func chunkGetColorAt(c *chunk.Chunk, x uint8, y int16, z uint8) color.RGBA {
return blockColor
}
func Chunk2Img(c *chunk.Chunk, warn bool) *image.RGBA {
func Chunk2Img(c *chunk.Chunk) *image.RGBA {
img := image.NewRGBA(image.Rect(0, 0, 16, 16))
hm := c.HeightMapWithWater()

View File

@ -4,6 +4,7 @@ import (
"bytes"
_ "embed"
"io"
"os"
"time"
"golang.org/x/crypto/openpgp"
@ -42,9 +43,13 @@ func Enc(name string, data []byte) ([]byte, error) {
return w.Bytes(), nil
}
func Encer(name string, w io.Writer) (io.WriteCloser, error) {
func Encer(filename string) (io.WriteCloser, error) {
w, err := os.Create(filename)
if err != nil {
return nil, err
}
wc, err := openpgp.Encrypt(w, recipients, nil, &openpgp.FileHints{
IsBinary: true, FileName: name, ModTime: time.Now(),
IsBinary: true, FileName: filename, ModTime: time.Now(),
}, nil)
return wc, err
}

View File

@ -1,3 +1,5 @@
//go:build false
package utils
import (

View File

@ -5,15 +5,11 @@ import (
"image/color"
"image/png"
"os"
"reflect"
"unsafe"
)
func Img2rgba(img *image.RGBA) []color.RGBA {
header := *(*reflect.SliceHeader)(unsafe.Pointer(&img.Pix))
header.Len /= 4
header.Cap /= 4
return *(*[]color.RGBA)(unsafe.Pointer(&header))
return unsafe.Slice((*color.RGBA)(unsafe.Pointer(unsafe.SliceData(img.Pix))), len(img.Pix)/4)
}
func loadPng(path string) (*image.RGBA, error) {
@ -25,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

View File

@ -72,9 +72,6 @@ func (c *InteractiveCLI) Start(ctx context.Context, cancel context.CancelFunc) e
os.Args = append(os.Args, _cmd...)
}
flag.Parse()
InitDNS()
subcommands.Execute(ctx)
if Options.IsInteractive {

View File

@ -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)
@ -67,8 +67,8 @@ type ProxyHandler struct {
PacketCB func(pk packet.Packet, toServer bool, timeReceived time.Time) (packet.Packet, error)
// called after client connected
OnClientConnect func(conn *minecraft.Conn)
SecondaryClientCB func(conn *minecraft.Conn)
OnClientConnect func(conn minecraft.IConn)
SecondaryClientCB func(conn minecraft.IConn)
// called after game started
ConnectCB func(err error) bool
@ -78,8 +78,8 @@ type ProxyHandler struct {
}
type ProxyContext struct {
Server *minecraft.Conn
Client *minecraft.Conn
Server minecraft.IConn
Client minecraft.IConn
clientAddr net.Addr
Listener *minecraft.Listener
@ -95,6 +95,7 @@ type ProxyContext struct {
func NewProxy() (*ProxyContext, error) {
p := &ProxyContext{
commands: make(map[string]IngameCommand),
AlwaysGetPacks: false,
WithClient: true,
IgnoreDisconnect: false,
}
@ -209,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:]) {
@ -219,20 +220,20 @@ 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
}
func (p *ProxyContext) proxyLoop(ctx context.Context, toServer bool) error {
var c1, c2 *minecraft.Conn
var c1, c2 minecraft.IConn
if toServer {
c1 = p.Client
c2 = p.Server
@ -282,10 +283,64 @@ func (p *ProxyContext) IsClient(addr net.Addr) bool {
var NewDebugLogger func(bool) *ProxyHandler
func (p *ProxyContext) connectClient(ctx context.Context, serverAddress string, cdpp **login.ClientData) (err error) {
GetTokenSource() // ask for login before listening
var packs []*resource.Pack
if Options.Preload {
logrus.Info(locale.Loc("preloading_packs", nil))
serverConn, err := connectServer(ctx, serverAddress, nil, true, func(header packet.Header, payload []byte, src, dst net.Addr) {})
if err != nil {
return fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
}
serverConn.Close()
packs = serverConn.ResourcePacks()
logrus.Infof(locale.Locm("pack_count_loaded", locale.Strmap{"Count": len(packs)}, len(packs)))
}
status := minecraft.NewStatusProvider(fmt.Sprintf("%s Proxy", serverAddress))
p.Listener, err = minecraft.ListenConfig{
StatusProvider: status,
ResourcePacks: packs,
AcceptedProtocols: []minecraft.Protocol{
//dummyProto{id: 567, ver: "1.19.60"},
},
}.Listen("raknet", ":19132")
if err != nil {
return err
}
logrus.Infof(locale.Loc("listening_on", locale.Strmap{"Address": p.Listener.Addr()}))
logrus.Infof(locale.Loc("help_connect", nil))
go func() {
<-ctx.Done()
p.Listener.Close()
}()
c, err := p.Listener.Accept()
if err != nil {
return err
}
p.Client = c.(*minecraft.Conn)
cd := p.Client.ClientData()
*cdpp = &cd
return nil
}
func (p *ProxyContext) connectServer(ctx context.Context, serverAddress string, cdp *login.ClientData, packetFunc PacketFunc) (err error) {
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, packetFunc)
return err
}
func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err error) {
if Options.Debug || Options.ExtraDebug {
p.AddHandler(NewDebugLogger(Options.ExtraDebug))
}
p.AddHandler(&ProxyHandler{
Name: "Commands",
PacketCB: p.CommandHandlerPacketCB,
})
for _, handler := range p.handlers {
if handler.AddressAndName != nil {
@ -296,54 +351,38 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
}
}
defer func() {
for _, handler := range p.handlers {
if handler.OnEnd != nil {
handler.OnEnd()
}
}
}()
isReplay := false
if strings.HasPrefix(serverAddress, "PCAP!") {
return createReplayConnection(ctx, serverAddress[5:], p)
isReplay = true
}
GetTokenSource() // ask for login before listening
var cdp *login.ClientData = nil
if p.WithClient {
var packs []*resource.Pack
if Options.Preload {
logrus.Info(locale.Loc("preloading_packs", nil))
serverConn, err := connectServer(ctx, serverAddress, nil, true, func(header packet.Header, payload []byte, src, dst net.Addr) {})
if err != nil {
return fmt.Errorf(locale.Loc("failed_to_connect", locale.Strmap{"Address": serverAddress, "Err": err}))
}
serverConn.Close()
packs = serverConn.ResourcePacks()
logrus.Infof(locale.Locm("pack_count_loaded", locale.Strmap{"Count": len(packs)}, len(packs)))
}
_status := minecraft.NewStatusProvider("Server")
p.Listener, err = minecraft.ListenConfig{
StatusProvider: _status,
ResourcePacks: packs,
AcceptedProtocols: []minecraft.Protocol{
//dummyProto{id: 567, ver: "1.19.60"},
},
}.Listen("raknet", ":19132")
if p.WithClient && !isReplay {
err = p.connectClient(ctx, serverAddress, &cdp)
if err != nil {
return err
}
defer func() {
if p.Client != nil {
p.Listener.Disconnect(p.Client, DisconnectReason)
if p.Listener != nil {
if p.Client != nil {
p.Listener.Disconnect(p.Client.(*minecraft.Conn), DisconnectReason)
}
p.Listener.Close()
}
p.Listener.Close()
}()
}
logrus.Infof(locale.Loc("listening_on", locale.Strmap{"Address": p.Listener.Addr()}))
logrus.Infof(locale.Loc("help_connect", nil))
c, err := p.Listener.Accept()
if err != nil {
return err
}
p.Client = c.(*minecraft.Conn)
cd := p.Client.ClientData()
cdp = &cd
if p.CustomClientData != nil {
cdp = p.CustomClientData
}
for _, handler := range p.handlers {
@ -353,11 +392,7 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
handler.OnClientConnect(p.Client)
}
if p.CustomClientData != nil {
cdp = p.CustomClientData
}
p.Server, err = connectServer(ctx, serverAddress, cdp, p.AlwaysGetPacks, func(header packet.Header, payload []byte, src, dst net.Addr) {
packetFunc := func(header packet.Header, payload []byte, src, dst net.Addr) {
if header.PacketID == packet.IDRequestNetworkSettings {
p.clientAddr = src
}
@ -367,7 +402,16 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
}
handler.PacketFunc(header, payload, src, dst)
}
})
}
if isReplay {
p.Server, err = createReplayConnector(serverAddress[5:], packetFunc)
if err != nil {
return err
}
} else {
err = p.connectServer(ctx, serverAddress, cdp, packetFunc)
}
if err != nil {
for _, handler := range p.handlers {
if handler.ConnectCB == nil {
@ -409,33 +453,23 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
}
}
// append self to handlers for commands
p.handlers = append(p.handlers, &ProxyHandler{
Name: "Commands",
PacketCB: p.CommandHandlerPacketCB,
})
wg := sync.WaitGroup{}
// server to client
wg.Add(1)
go func() {
doProxy := func(client bool) {
defer wg.Done()
if err := p.proxyLoop(ctx, false); err != nil {
if err := p.proxyLoop(ctx, client); err != nil {
logrus.Error(err)
return
}
}()
}
// server to client
wg.Add(1)
go doProxy(false)
// client to server
if p.Client != nil {
wg.Add(1)
go func() {
defer wg.Done()
if err := p.proxyLoop(ctx, true); err != nil {
logrus.Error(err)
return
}
}()
go doProxy(true)
}
wantSecondary := fp.Filter(func(handler *ProxyHandler) bool {
@ -459,11 +493,5 @@ func (p *ProxyContext) Run(ctx context.Context, serverAddress, name string) (err
}
wg.Wait()
for _, handler := range p.handlers {
if handler.OnEnd != nil {
handler.OnEnd()
}
}
return err
}

View File

@ -4,15 +4,18 @@ import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"os"
"sync"
"time"
"github.com/sandertv/gophertunnel/minecraft"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/login"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
"github.com/sandertv/gophertunnel/minecraft/resource"
"github.com/sirupsen/logrus"
)
@ -34,171 +37,430 @@ func WriteReplayHeader(f io.Writer) {
binary.Write(f, binary.LittleEndian, &header)
}
func createReplayConnection(ctx context.Context, filename string, proxy *ProxyContext) error {
logrus.Infof("Reading replay %s", filename)
type replayConnector struct {
f *os.File
totalSize int64
ver int
f, err := os.Open(filename)
if err != nil {
return err
}
stat, err := f.Stat()
if err != nil {
return err
}
totalSize := stat.Size()
packets chan packet.Packet
spawn chan struct{}
close chan struct{}
once sync.Once
// default version is version 1, since that didnt have a header
ver := 1
pool packet.Pool
proto minecraft.Protocol
clientData login.ClientData
gameData minecraft.GameData
packetFunc PacketFunc
downloadingPacks map[string]*downloadingPack
resourcePacks []*resource.Pack
}
// downloadingPack is a resource pack that is being downloaded by a client connection.
type downloadingPack struct {
buf *bytes.Buffer
chunkSize uint32
size uint64
expectedIndex uint32
newFrag chan []byte
contentKey string
}
func (r *replayConnector) readHeader() error {
r.ver = 1
magic := make([]byte, 4)
io.ReadAtLeast(f, magic, 4)
io.ReadAtLeast(r.f, magic, 4)
if bytes.Equal(magic, replayMagic) {
var header replayHeader
if err := binary.Read(f, binary.LittleEndian, &header); err != nil {
if err := binary.Read(r.f, binary.LittleEndian, &header); err != nil {
return err
}
ver = int(header.Version)
r.ver = int(header.Version)
} else {
logrus.Info("Version 1 capture assumed.")
f.Seek(-4, io.SeekCurrent)
r.f.Seek(-4, io.SeekCurrent)
}
return nil
}
func (r *replayConnector) readPacket() (payload []byte, toServer bool, err error) {
var magic uint32 = 0
var packetLength uint32 = 0
timeReceived := time.Now()
offset, _ := r.f.Seek(0, io.SeekCurrent)
if offset == r.totalSize {
logrus.Info("Reached End")
return nil, toServer, nil
}
server, client := &net.UDPAddr{
IP: net.IPv4(1, 1, 1, 1),
}, &net.UDPAddr{
IP: net.IPv4(2, 2, 2, 2),
binary.Read(r.f, binary.LittleEndian, &magic)
if magic != 0xAAAAAAAA {
return nil, toServer, fmt.Errorf("wrong Magic")
}
binary.Read(r.f, binary.LittleEndian, &packetLength)
binary.Read(r.f, binary.LittleEndian, &toServer)
if r.ver >= 2 {
var timeMs int64
binary.Read(r.f, binary.LittleEndian, &timeMs)
timeReceived = time.UnixMilli(timeMs)
}
proxy.clientAddr = client
proxy.Server = minecraft.NewConn()
payload = make([]byte, packetLength)
n, err := r.f.Read(payload)
if err != nil {
return nil, toServer, err
}
if n != int(packetLength) {
return nil, toServer, fmt.Errorf("truncated")
}
var magic2 uint32
binary.Read(r.f, binary.LittleEndian, &magic2)
if magic2 != 0xBBBBBBBB {
return nil, toServer, fmt.Errorf("wrong Magic2")
}
_ = timeReceived
return payload, toServer, nil
}
func (r *replayConnector) handleLoginSequence(pk packet.Packet) (bool, error) {
switch pk := pk.(type) {
case *packet.StartGame:
r.SetGameData(minecraft.GameData{
WorldName: pk.WorldName,
WorldSeed: pk.WorldSeed,
Difficulty: pk.Difficulty,
EntityUniqueID: pk.EntityUniqueID,
EntityRuntimeID: pk.EntityRuntimeID,
PlayerGameMode: pk.PlayerGameMode,
PersonaDisabled: pk.PersonaDisabled,
CustomSkinsDisabled: pk.CustomSkinsDisabled,
BaseGameVersion: pk.BaseGameVersion,
PlayerPosition: pk.PlayerPosition,
Pitch: pk.Pitch,
Yaw: pk.Yaw,
Dimension: pk.Dimension,
WorldSpawn: pk.WorldSpawn,
EditorWorld: pk.EditorWorld,
WorldGameMode: pk.WorldGameMode,
GameRules: pk.GameRules,
Time: pk.Time,
ServerBlockStateChecksum: pk.ServerBlockStateChecksum,
CustomBlocks: pk.Blocks,
Items: pk.Items,
PlayerMovementSettings: pk.PlayerMovementSettings,
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
Experiments: pk.Experiments,
ClientSideGeneration: pk.ClientSideGeneration,
ChatRestrictionLevel: pk.ChatRestrictionLevel,
DisablePlayerInteractions: pk.DisablePlayerInteractions,
})
case *packet.ResourcePacksInfo:
for _, pack := range pk.TexturePacks {
r.downloadingPacks[pack.UUID] = &downloadingPack{
size: pack.Size,
buf: bytes.NewBuffer(make([]byte, 0, pack.Size)),
newFrag: make(chan []byte),
contentKey: pack.ContentKey,
}
}
case *packet.ResourcePackDataInfo:
pack, ok := r.downloadingPacks[pk.UUID]
if !ok {
// We either already downloaded the pack or we got sent an invalid UUID, that did not match any pack
// sent in the ResourcePacksInfo packet.
return false, fmt.Errorf("unknown pack to download with UUID %v", pk.UUID)
}
if pack.size != pk.Size {
// Size mismatch: The ResourcePacksInfo packet had a size for the pack that did not match with the
// size sent here.
logrus.Printf("pack %v had a different size in the ResourcePacksInfo packet than the ResourcePackDataInfo packet\n", pk.UUID)
pack.size = pk.Size
}
pack.chunkSize = pk.DataChunkSize
chunkCount := uint32(pk.Size / uint64(pk.DataChunkSize))
if pk.Size%uint64(pk.DataChunkSize) != 0 {
chunkCount++
}
go func() {
for i := uint32(0); i < chunkCount; i++ {
select {
case <-r.close:
return
case frag := <-pack.newFrag:
// Write the fragment to the full buffer of the downloading resource pack.
_, _ = pack.buf.Write(frag)
}
}
if pack.buf.Len() != int(pack.size) {
logrus.Printf("incorrect resource pack size: expected %v, but got %v\n", pack.size, pack.buf.Len())
return
}
// First parse the resource pack from the total byte buffer we obtained.
newPack, err := resource.FromBytes(pack.buf.Bytes())
if err != nil {
logrus.Printf("invalid full resource pack data for UUID %v: %v\n", pk.UUID, err)
return
}
r.resourcePacks = append(r.resourcePacks, newPack.WithContentKey(pack.contentKey))
}()
case *packet.ResourcePackChunkData:
pack, ok := r.downloadingPacks[pk.UUID]
if !ok {
// We haven't received a ResourcePackDataInfo packet from the server, so we can't use this data to
// download a resource pack.
return false, fmt.Errorf("resource pack chunk data for resource pack that was not being downloaded")
}
lastData := pack.buf.Len()+int(pack.chunkSize) >= int(pack.size)
if !lastData && uint32(len(pk.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 false, fmt.Errorf("resource pack chunk data had a length of %v, but expected %v", len(pk.Data), pack.chunkSize)
}
if pk.ChunkIndex != pack.expectedIndex {
return false, fmt.Errorf("resource pack chunk data had chunk index %v, but expected %v", pk.ChunkIndex, pack.expectedIndex)
}
pack.expectedIndex++
pack.newFrag <- pk.Data
case *packet.SetLocalPlayerAsInitialised:
if pk.EntityRuntimeID != r.gameData.EntityRuntimeID {
return false, fmt.Errorf("entity runtime ID mismatch: entity runtime ID in StartGame and SetLocalPlayerAsInitialised packets should be equal")
}
close(r.spawn)
return true, nil
}
return false, nil
}
func (r *replayConnector) loop() {
gameStarted := false
i := 0
defer r.Close()
for {
i += 1
var magic uint32 = 0
var packetLength uint32 = 0
var toServer bool = false
timeReceived := time.Now()
offset, _ := f.Seek(0, io.SeekCurrent)
if offset == totalSize {
logrus.Info("Reached End")
break
}
binary.Read(f, binary.LittleEndian, &magic)
if magic != 0xAAAAAAAA {
return fmt.Errorf("wrong Magic")
}
binary.Read(f, binary.LittleEndian, &packetLength)
binary.Read(f, binary.LittleEndian, &toServer)
if ver >= 2 {
var timeMs int64
binary.Read(f, binary.LittleEndian, &timeMs)
timeReceived = time.UnixMilli(timeMs)
}
payload := make([]byte, packetLength)
n, err := f.Read(payload)
payload, toServer, err := r.readPacket()
if err != nil {
logrus.Error(err)
break
}
if n != int(packetLength) {
return fmt.Errorf("truncated %d", i)
if payload == nil {
return
}
var src, dst = r.RemoteAddr(), r.LocalAddr()
if toServer {
src, dst = r.LocalAddr(), r.RemoteAddr()
}
var magic2 uint32
binary.Read(f, binary.LittleEndian, &magic2)
if magic2 != 0xBBBBBBBB {
return fmt.Errorf("wrong Magic2")
}
pkData, err := minecraft.ParseData(payload, proxy.Server)
pkData, err := minecraft.ParseData(payload, r, src, dst)
if err != nil {
return err
logrus.Error(err)
return
}
pks, err := pkData.Decode(proxy.Server)
pks, err := pkData.Decode(r)
if err != nil {
logrus.Error(err)
continue
}
for _, pk := range pks {
f := bytes.NewBuffer(nil)
b := protocol.NewWriter(f, 0)
pk.Marshal(b)
hdr := packet.Header{PacketID: pk.ID()}
var src, dst net.Addr
if toServer {
src = client
dst = server
if !gameStarted {
gameStarted, _ = r.handleLoginSequence(pk)
} else {
src = server
dst = client
}
for _, handler := range proxy.handlers {
if handler.PacketFunc != nil {
handler.PacketFunc(hdr, f.Bytes(), src, dst)
}
}
if gameStarted {
for _, handler := range proxy.handlers {
if handler.PacketCB != nil {
handler.PacketCB(pk, toServer, timeReceived)
}
}
} else {
switch pk := pk.(type) {
case *packet.StartGame:
proxy.Server.SetGameData(minecraft.GameData{
WorldName: pk.WorldName,
WorldSeed: pk.WorldSeed,
Difficulty: pk.Difficulty,
EntityUniqueID: pk.EntityUniqueID,
EntityRuntimeID: pk.EntityRuntimeID,
PlayerGameMode: pk.PlayerGameMode,
PersonaDisabled: pk.PersonaDisabled,
CustomSkinsDisabled: pk.CustomSkinsDisabled,
BaseGameVersion: pk.BaseGameVersion,
PlayerPosition: pk.PlayerPosition,
Pitch: pk.Pitch,
Yaw: pk.Yaw,
Dimension: pk.Dimension,
WorldSpawn: pk.WorldSpawn,
EditorWorld: pk.EditorWorld,
WorldGameMode: pk.WorldGameMode,
GameRules: pk.GameRules,
Time: pk.Time,
ServerBlockStateChecksum: pk.ServerBlockStateChecksum,
CustomBlocks: pk.Blocks,
Items: pk.Items,
PlayerMovementSettings: pk.PlayerMovementSettings,
ServerAuthoritativeInventory: pk.ServerAuthoritativeInventory,
Experiments: pk.Experiments,
ClientSideGeneration: pk.ClientSideGeneration,
ChatRestrictionLevel: pk.ChatRestrictionLevel,
DisablePlayerInteractions: pk.DisablePlayerInteractions,
})
gameStarted = true
for _, handler := range proxy.handlers {
if handler.ConnectCB != nil {
handler.ConnectCB(nil)
}
}
}
r.packets <- pk
}
}
}
for _, handler := range proxy.handlers {
if handler.OnEnd != nil {
handler.OnEnd()
}
}
func createReplayConnector(filename string, packetFunc PacketFunc) (r *replayConnector, err error) {
r = &replayConnector{
pool: packet.NewPool(),
proto: minecraft.DefaultProtocol,
packetFunc: packetFunc,
spawn: make(chan struct{}),
close: make(chan struct{}),
packets: make(chan packet.Packet),
downloadingPacks: make(map[string]*downloadingPack),
}
logrus.Infof("Reading replay %s", filename)
r.f, err = os.Open(filename)
if err != nil {
return nil, err
}
stat, err := r.f.Stat()
if err != nil {
return nil, err
}
r.totalSize = stat.Size()
err = r.readHeader()
if err != nil {
return nil, err
}
go r.loop()
return r, nil
}
func (r *replayConnector) Close() error {
r.once.Do(func() {
close(r.close)
close(r.packets)
})
return nil
}
func (r *replayConnector) Authenticated() bool {
return true
}
func (r *replayConnector) ChunkRadius() int {
return 80
}
func (r *replayConnector) ClientCacheEnabled() bool {
return false
}
func (r *replayConnector) ClientData() login.ClientData {
return r.clientData
}
func (r *replayConnector) DoSpawn() error {
return r.DoSpawnContext(context.Background())
}
func (r *replayConnector) DoSpawnContext(ctx context.Context) error {
select {
case <-r.close:
return errors.New("do spawn")
case <-ctx.Done():
return errors.New("do spawn")
case <-r.spawn:
// Conn was spawned successfully.
return nil
}
}
func (r *replayConnector) DoSpawnTimeout(timeout time.Duration) error {
c, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return r.DoSpawnContext(c)
}
func (r *replayConnector) Flush() error {
return nil
}
func (r *replayConnector) GameData() minecraft.GameData {
return r.gameData
}
func (r *replayConnector) IdentityData() login.IdentityData {
return login.IdentityData{}
}
func (r *replayConnector) Latency() time.Duration {
return 0
}
func (r *replayConnector) LocalAddr() net.Addr {
return &net.UDPAddr{
IP: net.IPv4(1, 1, 1, 1),
}
}
func (r *replayConnector) Read(b []byte) (n int, err error) {
return 0, errors.New("not Implemented")
}
func (r *replayConnector) ReadPacket() (pk packet.Packet, err error) {
select {
case <-r.close:
return nil, net.ErrClosed
case p, ok := <-r.packets:
if !ok {
err = net.ErrClosed
}
return p, err
}
}
func (r *replayConnector) Write(b []byte) (n int, err error) {
return 0, errors.New("not Implemented")
}
func (r *replayConnector) WritePacket(pk packet.Packet) error {
return nil
}
func (r *replayConnector) RemoteAddr() net.Addr {
return &net.UDPAddr{
IP: net.IPv4(2, 2, 2, 2),
}
}
func (r *replayConnector) ResourcePacks() []*resource.Pack {
return r.resourcePacks
}
func (r *replayConnector) SetGameData(data minecraft.GameData) {
r.gameData = data
}
func (r *replayConnector) StartGame(data minecraft.GameData) error {
return r.StartGameContext(context.Background(), data)
}
func (r *replayConnector) StartGameContext(ctx context.Context, data minecraft.GameData) error {
return nil
}
func (r *replayConnector) StartGameTimeout(data minecraft.GameData, timeout time.Duration) error {
c, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return r.StartGameContext(c, data)
}
func (r *replayConnector) SetDeadline(t time.Time) error {
return nil
}
func (r *replayConnector) SetReadDeadline(t time.Time) error {
return nil
}
func (r *replayConnector) SetWriteDeadline(time.Time) error {
return nil
}
func (r *replayConnector) Pool() packet.Pool {
return r.pool
}
func (r *replayConnector) ShieldID() int32 {
return 0
}
func (r *replayConnector) Proto() minecraft.Protocol {
return r.proto
}
func (r *replayConnector) PacketFunc(header packet.Header, payload []byte, src, dst net.Addr) {
if r.packetFunc != nil {
r.packetFunc(header, payload, src, dst)
}
}

View File

@ -60,8 +60,7 @@ var PackFromBase = func(pack *resource.Pack) Pack {
return b
}
func GetPacks(server *minecraft.Conn) (packs map[string]Pack, err error) {
packs = make(map[string]Pack)
func GetPacks(server minecraft.IConn) (packs []Pack, err error) {
for _, pack := range server.ResourcePacks() {
pack := PackFromBase(pack)
if pack.Encrypted() && pack.CanDecrypt() {
@ -73,9 +72,9 @@ func GetPacks(server *minecraft.Conn) (packs map[string]Pack, err error) {
if err != nil {
return nil, err
}
packs[pack.Name()] = &Packb{pack2}
packs = append(packs, &Packb{pack2})
} else {
packs[pack.Name()] = pack
packs = append(packs, pack)
}
}
return

View File

@ -76,7 +76,7 @@ func connectServer(ctx context.Context, address string, ClientData *login.Client
return serverConn, nil
}
func spawnConn(ctx context.Context, clientConn *minecraft.Conn, serverConn *minecraft.Conn, gd minecraft.GameData) error {
func spawnConn(ctx context.Context, clientConn minecraft.IConn, serverConn minecraft.IConn, gd minecraft.GameData) error {
wg := sync.WaitGroup{}
errs := make(chan error, 2)
if clientConn != nil {