From 895ec536982f30aded6759e0f6d09ac59277ef10 Mon Sep 17 00:00:00 2001 From: adman Date: Tue, 21 Apr 2026 22:24:12 +0200 Subject: [PATCH] parse the world --- level/anvil/level_test.go | 22 ++++++++ level/anvil/mca.go | 103 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 level/anvil/mca.go diff --git a/level/anvil/level_test.go b/level/anvil/level_test.go index bc3ca7a..71e1174 100644 --- a/level/anvil/level_test.go +++ b/level/anvil/level_test.go @@ -1,10 +1,13 @@ package anvil_test import ( + "encoding/json" + "fmt" "os" "testing" "github.com/admin-else/strom/level/anvil" + "github.com/admin-else/strom/util" ) func TestLevel(t *testing.T) { @@ -20,3 +23,22 @@ func TestLevel(t *testing.T) { } t.Log(l) } + +func TestRegionFile(t *testing.T) { + f, err := os.Open("./testdata/testworld/region/r.0.0.mca") + if err != nil { + t.Fatal(err) + } + defer f.Close() + ch, err := anvil.ReadChunkHeader(f) + if err != nil { + t.Fatal(err) + } + + n, err := ch.ChunkAt(f, 0, 0) + if err != nil { + return + } + + fmt.Println(string(util.MustT(json.MarshalIndent(n, "", " ")))) +} diff --git a/level/anvil/mca.go b/level/anvil/mca.go new file mode 100644 index 0000000..c2515e1 --- /dev/null +++ b/level/anvil/mca.go @@ -0,0 +1,103 @@ +package anvil + +import ( + "compress/gzip" + "compress/zlib" + "encoding/binary" + "fmt" + "io" + "os" + + "github.com/admin-else/strom/nbt" +) + +type CompressionStrategy uint8 + +const ( + ChunksWidthRegionFile = 32 + ChunksRegionFile = ChunksWidthRegionFile * ChunksWidthRegionFile +) + +const ( + _ CompressionStrategy = iota + CompressionStrategyGZIP + CompressionStrategyZLIB + CompressionStrategyNONE + CompressionStrategyLZ4 +) + +func (c CompressionStrategy) Wrap(r io.ReadCloser) (rNew io.ReadCloser, shouldClose bool, err error) { + switch c { + case CompressionStrategyGZIP: + shouldClose = true + rNew, err = gzip.NewReader(r) + return + case CompressionStrategyZLIB: + shouldClose = true + rNew, err = zlib.NewReader(r) + return + case CompressionStrategyNONE: + rNew = r + return + case CompressionStrategyLZ4: + err = fmt.Errorf("lz4 compression not supported") + return + default: + err = fmt.Errorf("unknown compression strategy %d", c) + return + } +} + +type ChunkHeader struct { + Chunk, Time [ChunksRegionFile]int32 +} + +func ReadChunkHeader(r *os.File) (ch *ChunkHeader, err error) { + var h ChunkHeader + err = binary.Read(r, binary.BigEndian, &h) + if err != nil { + return + } + ch = &h + return +} + +func (c *ChunkHeader) ChunkAt(f *os.File, x, z int32) (n *nbt.Tag, err error) { + off, size := chunkLocation(c.Chunk[chunkXZtoIndex(x, z)]) + _, err = f.Seek(int64(off), 0) + if err != nil { + return + } + _ = size // i dont really know why mc has this maybe to point out how much space is wasted? + var realSize int32 + err = binary.Read(f, binary.BigEndian, &realSize) + if err != nil { + return + } + var compressionStrategy CompressionStrategy + err = binary.Read(f, binary.BigEndian, &compressionStrategy) + if err != nil { + return + } + r, shouldClose, err := compressionStrategy.Wrap(f) + if err != nil { + return + } + if shouldClose { + defer r.Close() + } + n = &nbt.Tag{} + err = n.Decode(r) + if err != nil { + return + } + return +} + +func chunkXZtoIndex(x, z int32) int32 { + return (x % ChunksWidthRegionFile) + (z%ChunksWidthRegionFile)*ChunksWidthRegionFile +} + +func chunkLocation(v int32) (offset, l int32) { + return ((v >> 8) & 0xFFFFFF) * 4096, (v & 0xFF) * 4096 +}