diff --git a/.gitignore b/.gitignore index f85114e..09e4fae 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ keys.db /updates/ /fyne-cross/ /tmp/ +/cmd/test packets.log.gpg customdata.json diff --git a/subcommands/world/items.go b/subcommands/world/items.go index 27ed1d7..82e6e0d 100644 --- a/subcommands/world/items.go +++ b/subcommands/world/items.go @@ -18,77 +18,84 @@ type itemContainer struct { } func (w *WorldState) processItemPacketsServer(pk packet.Packet) packet.Packet { + if !w.experimentInventory { + return pk + } + switch pk := pk.(type) { case *packet.ContainerOpen: - if w.experimentInventory { - // add to open containers - existing, ok := w.openItemContainers[pk.WindowID] - if !ok { - existing = &itemContainer{} - } - w.openItemContainers[pk.WindowID] = &itemContainer{ - OpenPacket: pk, - Content: existing.Content, - } + // add to open containers + existing, ok := w.openItemContainers[pk.WindowID] + if !ok { + existing = &itemContainer{} } + w.openItemContainers[pk.WindowID] = &itemContainer{ + OpenPacket: pk, + Content: existing.Content, + } + case *packet.InventoryContent: - if w.experimentInventory { - // save content - existing, ok := w.openItemContainers[byte(pk.WindowID)] - if !ok { - if pk.WindowID == 0x0 { // inventory - w.openItemContainers[byte(pk.WindowID)] = &itemContainer{ - Content: pk, - } + // save content + existing, ok := w.openItemContainers[byte(pk.WindowID)] + if !ok { + if pk.WindowID == 0x0 { // inventory + w.openItemContainers[byte(pk.WindowID)] = &itemContainer{ + Content: pk, } + } + break + } + existing.Content = pk + + case *packet.ContainerClose: + // find container info + existing, ok := w.openItemContainers[byte(pk.WindowID)] + + switch pk.WindowID { + case protocol.WindowIDArmour: // todo handle + case protocol.WindowIDOffHand: // todo handle + case protocol.WindowIDUI: + case protocol.WindowIDInventory: // todo handle + if !ok { break } - existing.Content = pk - } - case *packet.ContainerClose: - if w.experimentInventory { - switch pk.WindowID { - case protocol.WindowIDArmour: // todo handle - case protocol.WindowIDOffHand: // todo handle - case protocol.WindowIDUI: - case protocol.WindowIDInventory: // todo handle - default: - // find container info - existing, ok := w.openItemContainers[byte(pk.WindowID)] - if !ok { - logrus.Warn(locale.Loc("warn_window_closed_not_open", nil)) - break - } - if existing.Content == nil { - break - } - - pos := existing.OpenPacket.ContainerPosition - cp := protocol.SubChunkPos{pos.X() << 4, pos.Z() << 4} - - // create inventory - inv := inventory.New(len(existing.Content.Content), nil) - for i, c := range existing.Content.Content { - item := stackToItem(c.Stack) - inv.SetItem(i, item) - } - - // put into subchunk - nbts := w.blockNBT[cp] - for i, v := range nbts { - NBTPos := protocol.BlockPos{v["x"].(int32), v["y"].(int32), v["z"].(int32)} - if NBTPos == pos { - w.blockNBT[cp][i]["Items"] = nbtconv.InvToNBT(inv) - } - } - - w.proxy.SendMessage(locale.Loc("saved_block_inv", nil)) - - // remove it again - delete(w.openItemContainers, byte(pk.WindowID)) + default: + if !ok { + logrus.Warn(locale.Loc("warn_window_closed_not_open", nil)) + break } + + if existing.Content == nil { + break + } + + pos := existing.OpenPacket.ContainerPosition + cp := protocol.SubChunkPos{pos.X() << 4, pos.Z() << 4} + + // create inventory + inv := inventory.New(len(existing.Content.Content), nil) + for i, c := range existing.Content.Content { + item := stackToItem(c.Stack) + inv.SetItem(i, item) + } + + // put into subchunk + nbts := w.blockNBT[cp] + for i, v := range nbts { + NBTPos := protocol.BlockPos{v["x"].(int32), v["y"].(int32), v["z"].(int32)} + if NBTPos == pos { + w.blockNBT[cp][i]["Items"] = nbtconv.InvToNBT(inv) + break + } + } + + w.proxy.SendMessage(locale.Loc("saved_block_inv", nil)) + + // remove it again + delete(w.openItemContainers, byte(pk.WindowID)) } + case *packet.ItemComponent: w.bp.ApplyComponentEntries(pk.Items) } diff --git a/ui/gui/pages/worlds/map.go b/ui/gui/pages/worlds/map.go index d3f462b..fa6c12a 100644 --- a/ui/gui/pages/worlds/map.go +++ b/ui/gui/pages/worlds/map.go @@ -3,13 +3,13 @@ package worlds import ( "image" "image/draw" - "time" + "math" "gioui.org/f32" - "gioui.org/gesture" "gioui.org/io/pointer" "gioui.org/layout" "gioui.org/op" + "gioui.org/op/clip" "gioui.org/op/paint" "github.com/bedrock-tool/bedrocktool/ui/messages" "github.com/sandertv/gophertunnel/minecraft/protocol" @@ -17,68 +17,83 @@ import ( type Map struct { click f32.Point - mapPos f32.Point - pos f32.Point imageOp paint.ImageOp - zoom float32 - drag gesture.Drag - scroll gesture.Scroll + scaleFactor float32 + center f32.Point + transform f32.Affine2D + grabbed bool MapImage *image.RGBA BoundsMin protocol.ChunkPos BoundsMax protocol.ChunkPos - Rotation float32 +} + +func (m *Map) HandlePointerEvent(e pointer.Event) { + switch e.Type { + case pointer.Press: + m.click = e.Position + m.grabbed = true + case pointer.Drag: + m.transform = m.transform.Offset(e.Position.Sub(m.click)) + m.click = e.Position + case pointer.Release: + m.grabbed = false + case pointer.Scroll: + m.HandleScrollEvent(e) + } +} + +func (m *Map) HandleScrollEvent(e pointer.Event) { + scaleFactor := float32(math.Pow(1.01, float64(e.Scroll.Y))) + m.transform = m.transform.Scale(e.Position.Sub(m.center), f32.Pt(scaleFactor, scaleFactor)) + m.scaleFactor *= scaleFactor } func (m *Map) Layout(gtx layout.Context) layout.Dimensions { // here we loop through all the events associated with this button. - for _, e := range m.drag.Events(gtx.Metric, gtx.Queue, gesture.Both) { - switch e.Type { - case pointer.Press: - m.click = e.Position - case pointer.Drag: - m.pos = m.mapPos.Sub(m.click).Add(e.Position) - case pointer.Release: - m.mapPos = m.pos + for _, e := range gtx.Events(m) { + if e, ok := e.(pointer.Event); ok { + m.HandlePointerEvent(e) } } - scrollDist := m.scroll.Scroll(gtx.Metric, gtx.Queue, time.Now(), gesture.Vertical) - - m.zoom -= float32(scrollDist) / 20 - if m.zoom < 0.2 { - m.zoom = 0.2 - } - - size := gtx.Constraints.Max - if m.MapImage != nil { - m.imageOp.Add(gtx.Ops) - b := m.MapImage.Bounds() - sx := float32(b.Dx() / 2) - sy := float32(b.Dy() / 2) + // Calculate the size of the widget based on the size of the image and the current scale factor. + dx := float32(m.MapImage.Bounds().Dx()) + dy := float32(m.MapImage.Bounds().Dy()) + size := f32.Pt(dx*m.scaleFactor, dy*m.scaleFactor) - op.Affine( - f32.Affine2D{}. - Scale(f32.Pt(sx, sy), f32.Pt(m.zoom, m.zoom)). - Offset(m.pos), - ).Add(gtx.Ops) + m.center = f32.Pt( + float32(gtx.Constraints.Max.X), + float32(gtx.Constraints.Max.Y), + ).Div(2) + + // Calculate the offset required to center the image within the widget. + offset := m.center.Sub(size.Div(2)) + + // Draw the image at the correct position and scale. + defer clip.Rect{Max: gtx.Constraints.Max}.Push(gtx.Ops).Pop() + op.Affine(m.transform.Offset(offset)).Add(gtx.Ops) + m.imageOp.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) } - m.drag.Add(gtx.Ops) - m.scroll.Add(gtx.Ops, image.Rect(-size.X, -size.Y, size.X, size.Y)) + size := gtx.Constraints.Max + pointer.InputOp{ + Tag: m, + Grab: m.grabbed, + Types: pointer.Scroll | pointer.Drag | pointer.Press | pointer.Release, + ScrollBounds: image.Rect(-size.X, -size.Y, size.X, size.Y), + }.Add(gtx.Ops) - return layout.Dimensions{ - Size: size, - } + return layout.Dimensions{Size: size} } func drawTile(img *image.RGBA, min, pos protocol.ChunkPos, tile *image.RGBA) { px := image.Pt( int((pos.X()-min[0])*16), - int((pos.Z()-min[0])*16), + int((pos.Z()-min[1])*16), ) draw.Draw(img, image.Rect( px.X, px.Y, @@ -88,7 +103,7 @@ func drawTile(img *image.RGBA, min, pos protocol.ChunkPos, tile *image.RGBA) { func (m *Map) Update(u *messages.UpdateMapPayload) { if m.MapImage == nil { - m.zoom = 1 + m.scaleFactor = 1 } needNewImage := false diff --git a/utils/encryptor/enc.go b/utils/encryptor/enc.go new file mode 100644 index 0000000..1d15de8 --- /dev/null +++ b/utils/encryptor/enc.go @@ -0,0 +1,200 @@ +package encryptor + +import ( + "archive/zip" + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/binary" + "encoding/hex" + "encoding/json" + "errors" + "io" + "io/fs" + "math/rand" + "path/filepath" + "testing/fstest" + "time" + + "github.com/google/uuid" +) + +type contentItem struct { + Path string `json:"path"` + Key string `json:"key"` +} + +type Content struct { + Content []contentItem `json:"content"` +} + +var StaticKey = []byte("s5s5ejuDru4uchuF2drUFuthaspAbepE") + +func generateKey() (out []byte) { + out = make([]byte, 32) + var vocab = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + for i := 0; i < 32; i++ { + out[i] = vocab[rand.Intn(len(vocab))] + } + return +} + +func encryptCfb(data []byte, key []byte) { + b, _ := aes.NewCipher(key) + s := cipher.NewCFBEncrypter(b, key[0:16]) + s.XORKeyStream(data, data) +} + +func canEncrypt(path string) bool { + if path == "manifest.json" { + return false + } + s := filepath.SplitList(path) + if s[0] == "texts" { + return false + } + if s[len(s)-1] == "contents.json" { + return false + } + return true +} + +func enc(fsys fs.FS, fsyso fstest.MapFS, contentsJson *Content, dir string) error { + matches, err := fs.Glob(fsys, dir+"**") + if err != nil { + return err + } + + for _, path := range matches { + ifo, err := fs.Stat(fsys, path) + if err != nil { + return err + } + fo := &fstest.MapFile{ + ModTime: ifo.ModTime(), + Mode: ifo.Mode(), + } + fsyso[path] = fo + + if ifo.IsDir() { + return enc(fsys, fsyso, contentsJson, path+"/") + } + + var data []byte + data, err = fs.ReadFile(fsys, path) + if err != nil { + return err + } + + if canEncrypt(path) && !ifo.IsDir() { + key := generateKey() + it := contentItem{ + Path: path, + Key: hex.EncodeToString(key), + } + contentsJson.Content = append(contentsJson.Content, it) + encryptCfb(data, key) + } + fo.Data = data + } + return nil +} + +func Enc(fsys fs.FS, id *uuid.UUID, ContentKey []byte) (fs.FS, error) { + if id == nil { + f, err := fsys.Open("manifest.json") + if err != nil { + return nil, err + } + + var manifest map[string]any + dec := json.NewDecoder(f) + err = dec.Decode(&manifest) + if err != nil { + return nil, err + } + + header, ok := manifest["header"].(map[string]any) + if !ok { + return nil, errors.New("no header") + } + idstr, ok := header["uuid"].(string) + if !ok { + return nil, errors.New("no id") + } + + _id, err := uuid.Parse(idstr) + if err != nil { + return nil, err + } + id = &_id + } + + if ContentKey == nil { + ContentKey = StaticKey + } + + var contentsJson Content + + fsyso := fstest.MapFS{} + err := enc(fsys, fsyso, &contentsJson, "") + if err != nil { + return nil, err + } + + contentsBuf := bytes.NewBuffer(nil) + binary.Write(contentsBuf, binary.LittleEndian, uint32(0)) + binary.Write(contentsBuf, binary.LittleEndian, uint32(0x9bcfb9fc)) + binary.Write(contentsBuf, binary.LittleEndian, uint64(0)) + contentsBuf.WriteByte(byte(len(id.String()))) + contentsBuf.Write([]byte(id.String())) + contentsBuf.Write(make([]byte, 0xff-contentsBuf.Len())) + + contentsData, _ := json.Marshal(&contentsJson) + encryptCfb(contentsData, ContentKey) + contentsBuf.Write(contentsData) + + fsyso["contents.json"] = &fstest.MapFile{ + Data: contentsBuf.Bytes(), + Mode: 0775, + ModTime: time.Now(), + } + + return fsyso, nil +} + +func fstozip(fsys fs.FS, zw *zip.Writer, dir string) error { + matches, err := fs.Glob(fsys, dir+"**") + if err != nil { + return err + } + for _, path := range matches { + ifo, _ := fs.Stat(fsys, path) + if ifo.IsDir() { + return fstozip(fsys, zw, path+"/") + } + w, err := zw.CreateHeader(&zip.FileHeader{ + Name: ifo.Name(), + Modified: ifo.ModTime(), + }) + if err != nil { + return err + } + data, err := fs.ReadFile(fsys, path) + if err != nil { + return err + } + w.Write(data) + } + return nil +} + +func FSToZip(fsys fs.FS, w io.Writer) error { + zw := zip.NewWriter(w) + err := fstozip(fsys, zw, "") + if err != nil { + return err + } + zw.Close() + return nil +}