diff --git a/level/chunk.go b/level/chunk.go index 1e891de..436ca38 100644 --- a/level/chunk.go +++ b/level/chunk.go @@ -1,54 +1,33 @@ package level -import ( - "encoding/binary" - "io" - "math" +import "io" - "github.com/admin-else/strom/data" - "github.com/admin-else/strom/util" -) - -type BlockState struct { - Id string - Properties map[string]string +type Chunk struct { + Sections []Section + version string } -const ( - ChunkWidth = 16 - ChunkColumns = ChunkWidth * ChunkWidth - BlocksPerChunkSection = ChunkWidth * ChunkWidth * ChunkWidth - ChunkBiomesWidth = 4 - BiomesPerChunkSection = ChunkBiomesWidth * ChunkBiomesWidth * ChunkBiomesWidth -) +func ReadChunkFromChunkPacketData(r io.Reader, version string, worldHeight int) (err error) { + c := &Chunk{} + c.version = version + nSections := worldHeight / ChunkWidth -type Section struct { - Blocks *Storage[*BlockState] - Biomes *Storage[string] -} - -func PossibleBpesBlocks(version string) (ret []uint8) { - maxBpe := util.BpeByNum(float64(len(data.BlocksForVersion(version)))) - ret = []uint8{0, 4, 5, 6, 7, 8, maxBpe} - return -} - -func PossibleBpesBiomes(version string) (ret []uint8) { - maxBpe := uint8(math.Ceil(math.Log2(float64(len(data.BiomesForVersion(version)))))) - ret = []uint8{0, 1, 2, 3, maxBpe} + c.Sections = make([]Section, nSections) + for i := range c.Sections { + c.Sections[i], err = SectionDecodePacket(r, version) + if err != nil { + return + } + } return } - -func SectionFromPacket(r io.Reader, version string) (s *Section, err error) { - s = &Section{} - s.Blocks = NewStorage[*BlockState](BlocksPerChunkSection, PossibleBpesBlocks(version)) - s.Biomes = NewStorage[string](BiomesPerChunkSection, PossibleBpesBiomes(version)) - var bitsPerEntryBlocks uint8 - err = binary.Read(r, binary.BigEndian, &bitsPerEntryBlocks) - if err != nil { - return +func (c *Chunk) WriteChunkData(w io.Writer) (err error) { + for _, s := range c.Sections { + err = SectionEncodePacket(w, s) + if err != nil { + return + } } - return } diff --git a/level/section.go b/level/section.go new file mode 100644 index 0000000..d3e4485 --- /dev/null +++ b/level/section.go @@ -0,0 +1,138 @@ +package level + +import ( + "encoding/binary" + "errors" + "io" + "math" + + "github.com/admin-else/strom/data" + "github.com/admin-else/strom/proto_base" + "github.com/admin-else/strom/util" +) + +type BlockState struct { + Id string + Properties map[string]string +} + +const ( + ChunkWidth = 16 + ChunkColumns = ChunkWidth * ChunkWidth + BlocksPerChunkSection = ChunkWidth * ChunkWidth * ChunkWidth + ChunkBiomesWidth = 4 + BiomesPerChunkSection = ChunkBiomesWidth * ChunkBiomesWidth * ChunkBiomesWidth +) + +type Section struct { + BlockCount int16 + Blocks, Biomes *Storage +} + +func MakeBiomeFormat(version string) StorageFormat { + directBpe := uint8(math.Ceil(math.Log2(float64(len(data.BiomesForVersion(version)))))) + return StorageFormat{ + AvailableBpes: []uint8{0, 1, 2, 3, directBpe}, + BiggestDirect: true, + Len: BiomesPerChunkSection, + } +} + +func MakeBlockFormat(version string) StorageFormat { + directBpe := uint8(math.Ceil(math.Log2(float64(len(data.BlocksForVersion(version)))))) + return StorageFormat{ + AvailableBpes: []uint8{0, 1, 2, 3, directBpe}, + BiggestDirect: true, + Len: BiomesPerChunkSection, + } +} + +var BadPaletteLenErr = errors.New("bad palette length") + +func ReadSectionStorage(r io.Reader, format StorageFormat) (s *Storage, err error) { + var bpe uint8 + err = binary.Read(r, binary.BigEndian, &bpe) + if err != nil { + return + } + var pallette []int32 + if bpe == 0 { + var v int32 + v, err = proto_base.DecodeVarInt(r) + if err != nil { + return + } + pallette = []int32{v} + } else if !util.IsLastElement(format.AvailableBpes, bpe) { + var paletteLen int32 + paletteLen, err = proto_base.DecodeVarInt(r) + if err != nil { + return + } + if paletteLen < 0 { + err = BadPaletteLenErr + return + } + for range pallette { + var v int32 + v, err = proto_base.DecodeVarInt(r) + if err != nil { + return + } + pallette = append(pallette, v) + } + } + return format.ImportFromReader(r, bpe, pallette) +} + +func WriteSectionStorage(w io.Writer, s *Storage) (err error) { + err = binary.Write(w, binary.BigEndian, s.bpe) + if err != nil { + return + } + if s.bpe == 0 { + err = proto_base.EncodeVarInt(w, s.palette[0]) + if err != nil { + return + } + } else if !util.IsLastElement(s.format.AvailableBpes, s.bpe) { + err = proto_base.EncodeVarInt(w, int32(len(s.palette))) + if err != nil { + return + } + for _, v := range s.palette { + err = proto_base.EncodeVarInt(w, v) + if err != nil { + return + } + } + } + return binary.Write(w, binary.BigEndian, s.data) +} + +func SectionDecodePacket(r io.Reader, version string) (s Section, err error) { + s = Section{} + err = binary.Read(r, binary.BigEndian, &s.BlockCount) + if err != nil { + return + } + s.Blocks, err = ReadSectionStorage(r, MakeBlockFormat(version)) + if err != nil { + return + } + s.Biomes, err = ReadSectionStorage(r, MakeBiomeFormat(version)) + return +} + +func SectionEncodePacket(w io.Writer, s Section) (err error) { + err = binary.Write(w, binary.BigEndian, s.BlockCount) + if err != nil { + return + } + err = WriteSectionStorage(w, s.Blocks) + if err != nil { + return + } + err = WriteSectionStorage(w, s.Biomes) + return +} diff --git a/level/section_test.go b/level/section_test.go new file mode 100644 index 0000000..97c3a58 --- /dev/null +++ b/level/section_test.go @@ -0,0 +1,43 @@ +package level_test + +import ( + "bytes" + "testing" + + "github.com/admin-else/strom/level" +) + +func FuzzSectionDeEnCodePacket(f *testing.F) { + f.Fuzz(func(t *testing.T, raw []byte) { + r := bytes.NewReader(raw) + s1, err := level.SectionDecodePacket(r, TestVersion) + if err != nil { + t.Skipf("decode failed: %v", err) + return + } + + buf := bytes.NewBuffer(nil) + err = level.SectionEncodePacket(buf, s1) + if err != nil { + t.Fatal(err) + return + } + + s2, err := level.SectionDecodePacket(buf, TestVersion) + if err != nil { + t.Fatal(err) + return + } + + if s1.BlockCount != s2.BlockCount { + t.Error("BlockCount mismatch") + } + if level.CompareStorage(s1.Blocks, s2.Blocks) { + t.Error("Blocks mismatch") + } + if level.CompareStorage(s1.Biomes, s2.Biomes) { + t.Error("Biomes mismatch") + } + + }) +} diff --git a/level/rrrr.go b/level/storage.go similarity index 77% rename from level/rrrr.go rename to level/storage.go index f043dd7..8f48638 100644 --- a/level/rrrr.go +++ b/level/storage.go @@ -1,7 +1,9 @@ package level import ( + "encoding/binary" "errors" + "io" "math" "slices" ) @@ -53,6 +55,28 @@ func (r StorageFormat) ImportDataDirect(data []uint64) (s *Storage, err error) { return r.Import(data, r.AvailableBpes[0], nil) } +func (r StorageFormat) ImportFromReader(reader io.Reader, bpe uint8, palette []int32) (s *Storage, err error) { + if !slices.Contains(r.AvailableBpes, bpe) { + err = BpeNotAvailableErr + return + } + var longs []uint64 + if bpe == 0 { + return r.Import(longs, bpe, palette) + } + elementsPerLong := int32(64 / int(bpe)) + numberOfLongs := int(math.Ceil(float64(r.Len) / float64(elementsPerLong))) + for range numberOfLongs { + var long uint64 + err = binary.Read(reader, binary.BigEndian, &long) // surely this works? + if err != nil { + return + } + longs = append(longs, long) + } + return r.Import(longs, bpe, palette) +} + type Storage struct { format StorageFormat data []uint64 @@ -81,14 +105,14 @@ func (r *Storage) resize(bpe uint8) { r.maxAtCurrentBpe = 1 return } - numberOfElements := int32(64 / int(bpe)) - numberOfLongs := int(math.Ceil(float64(r.format.Len) / float64(numberOfElements))) + elementsPerLong := int32(64 / int(bpe)) + numberOfLongs := int(math.Ceil(float64(r.format.Len) / float64(elementsPerLong))) newData := make([]uint64, numberOfLongs) mask := (uint64(1) << uint32(bpe)) - 1 maxAtCurrentBpe := int32(1 << bpe) if r.bpe == 0 { r.bpe = bpe - r.elementsPerLong = numberOfElements + r.elementsPerLong = elementsPerLong r.data = newData r.mask = mask r.maxAtCurrentBpe = maxAtCurrentBpe @@ -102,13 +126,13 @@ func (r *Storage) resize(bpe uint8) { } else { v = int32((r.data[i/r.elementsPerLong] >> ((i % r.elementsPerLong) * int32(r.bpe))) & r.mask) } - newData[i/int32(numberOfElements)] |= uint64(v) << ((i % int32(numberOfElements)) * int32(bpe)) + newData[i/int32(elementsPerLong)] |= uint64(v) << ((i % int32(elementsPerLong)) * int32(bpe)) } if resizeDirect { r.palette = nil } r.bpe = bpe - r.elementsPerLong = numberOfElements + r.elementsPerLong = elementsPerLong r.data = newData r.mask = mask r.maxAtCurrentBpe = maxAtCurrentBpe @@ -196,3 +220,21 @@ func (r *Storage) appendPalette(v int32) (i int, err error) { i = len(r.palette) - 1 return } + +func (r *Storage) Export() (data []uint64, bpe uint8, palette []int32, format StorageFormat) { + return r.data, r.bpe, r.palette, r.format +} + +func CompareStorage(a, b *Storage) bool { + if a.format.Len != b.format.Len { + return false + } + for i := range a.format.Len { + av, _ := a.Get(i) + bv, _ := b.Get(i) + if av == bv { + return false + } + } + return true +} diff --git a/level/rrrr_test.go b/level/storage_test.go similarity index 100% rename from level/rrrr_test.go rename to level/storage_test.go diff --git a/level/testdata/fuzz/FuzzSectionDeEnCodePacket/1423102ae2e39336 b/level/testdata/fuzz/FuzzSectionDeEnCodePacket/1423102ae2e39336 new file mode 100644 index 0000000..80d1902 --- /dev/null +++ b/level/testdata/fuzz/FuzzSectionDeEnCodePacket/1423102ae2e39336 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("000\xcf\xff\xff\xe88") diff --git a/level/testdata/fuzz/FuzzSectionDeEnCodePacket/179a5dc64d56178b b/level/testdata/fuzz/FuzzSectionDeEnCodePacket/179a5dc64d56178b new file mode 100644 index 0000000..2e9b2f9 --- /dev/null +++ b/level/testdata/fuzz/FuzzSectionDeEnCodePacket/179a5dc64d56178b @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00A\x00") diff --git a/level/testdata/fuzz/FuzzSectionDeEnCodePacket/50d71a3e97b6db2d b/level/testdata/fuzz/FuzzSectionDeEnCodePacket/50d71a3e97b6db2d new file mode 100644 index 0000000..6049e00 --- /dev/null +++ b/level/testdata/fuzz/FuzzSectionDeEnCodePacket/50d71a3e97b6db2d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00\x000") diff --git a/mapstructure/default.go b/mapstructure/default.go new file mode 100644 index 0000000..b9b7e73 --- /dev/null +++ b/mapstructure/default.go @@ -0,0 +1,7 @@ +package mapstructure + +var DefaultFormat = NewFormat("mapstructure", WithRequireAll(), WithTrySnakeCase(), WithTryLowCase()) + +func Decode(data any, v any) error { + return nil +} diff --git a/proto_generator/main.go b/proto_generator/main.go index ce1d112..dac17a7 100644 --- a/proto_generator/main.go +++ b/proto_generator/main.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/admin-else/strom/data" + "github.com/admin-else/strom/util" ) var ToDoError = errors.New("to do") @@ -126,11 +127,11 @@ func (g *Generator) VisitNameAndData(tName string, tData any) (e ast.Expr, err e } t, found := g.Protocol.Types.Types[tName] if t != "native" && found { - return Ident(CamelCase(tName)), nil + return Ident(util.CamelCase(tName)), nil } t, found = g.CurrentlyGeneratingTypes.Types[tName] if found { - return Ident(g.CurrentlyGeneratingTypesPrefix + CamelCase(tName)), nil + return Ident(g.CurrentlyGeneratingTypesPrefix + util.CamelCase(tName)), nil } err = fmt.Errorf("unknown type %s", tName) return @@ -212,7 +213,7 @@ func (g *Generator) GenerateTypes(prefix string, types Types) (err error) { continue } - tName := prefix + CamelCase(k) + tName := prefix + util.CamelCase(k) g.CurrentlyGeneratingTypeName = tName e, retExpr := FixPointerReceivers(e) @@ -293,7 +294,7 @@ func (g *Generator) GenerateProtocol(protocol Protocol, version string) (err err v := typeData.([]any)[1].([]any)[1].(map[string]any)["type"].([]any)[1].(map[string]any)["fields"].(map[string]any) for _, k2 := range OrderedKeys(v) { v2 := v[k2].(string) - typeName := prefix + CamelCase(v2) + typeName := prefix + util.CamelCase(v2) g.packetInfos = append(g.packetInfos, PacketInfo{ PacketDef: v2, TName: typeName, diff --git a/proto_generator/native_compare_to.go b/proto_generator/native_compare_to.go index 8fd5555..198dc14 100644 --- a/proto_generator/native_compare_to.go +++ b/proto_generator/native_compare_to.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/admin-else/strom/proto_generator/protodef" + "github.com/admin-else/strom/util" "github.com/go-viper/mapstructure/v2" ) @@ -52,7 +53,7 @@ func ContainerCompareTo(g *Generator, parts []string, inExpr ast.Expr, dataRaw a } for _, field := range data { if field.Name == name { - return g.VisitCompareTo(parts, SelectorExprAndStr(inExpr, CamelCase(field.Name)), field.Type) + return g.VisitCompareTo(parts, SelectorExprAndStr(inExpr, util.CamelCase(field.Name)), field.Type) } } err = errors.New("field not found") @@ -87,7 +88,7 @@ func BitfieldCompareTo(_ *Generator, parts []string, inExpr ast.Expr, dataRaw an cet = CaseExprTypeOnlyNumber } - e = SelectorExprAndStr(inExpr, CamelCase(fieldName)) + e = SelectorExprAndStr(inExpr, util.CamelCase(fieldName)) return } diff --git a/proto_generator/native_decoder.go b/proto_generator/native_decoder.go index 0b90205..c55c5e4 100644 --- a/proto_generator/native_decoder.go +++ b/proto_generator/native_decoder.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/admin-else/strom/proto_generator/protodef" + "github.com/admin-else/strom/util" "github.com/go-viper/mapstructure/v2" ) @@ -46,7 +47,7 @@ func ContainerDecoder(g *Generator, varToSet ast.Expr, dataRaw any, name string) for _, field := range data { var statements []ast.Stmt - cName := CamelCase(field.Name) + cName := util.CamelCase(field.Name) if field.Anon { cName = "Anon" } @@ -106,7 +107,7 @@ func OptionDecoder(g *Generator, varToSet ast.Expr, dataRaw any, name string) (s func ArrayDecoder(g *Generator, varToSet ast.Expr, dataRaw any, name string) (s []ast.Stmt, err error) { var data protodef.Array - lName := "l" + CamelCase(name) + lName := "l" + util.CamelCase(name) err = mapstructure.Decode(dataRaw, &data) if err != nil { return @@ -259,7 +260,7 @@ func SwitchDecoder(g *Generator, varToSet ast.Expr, dataRaw any, name string) (s if err != nil { return } - tName := name + CamelCase(fName) + "Tmp" + tName := name + util.CamelCase(fName) + "Tmp" s1 := VarStmt(tName, tType) s3 := Assign121(varToSet, Ident(tName)) var caseDecodeValueStmts []ast.Stmt @@ -309,7 +310,7 @@ func MapperDecoder(g *Generator, varToSet ast.Expr, dataRaw any, name string) (s if err != nil { return } - kName := CamelCase(name) + "Key" + kName := util.CamelCase(name) + "Key" kType, err := g.VisitType(data.Type) if err != nil { return @@ -346,7 +347,7 @@ func BufferDecoder(g *Generator, varToSet ast.Expr, dataRaw any, name string) (s // rawString, err := io.ReadAll(io.LimitReader(r, int64(l))) if data.CountType != nil { - lName := "l" + CamelCase(name) + lName := "l" + util.CamelCase(name) var countType ast.Expr countType, err = g.VisitType(data.CountType) if err != nil { @@ -411,7 +412,7 @@ func BitFieldDecoder(g *Generator, varToSet ast.Expr, dataRaw any, name string) } rShiftBy := totalSize - field.Size - fieldToSet := SelectorExprAndStr(varToSet, CamelCase(field.Name)) + fieldToSet := SelectorExprAndStr(varToSet, util.CamelCase(field.Name)) var getDataExpr ast.Expr getDataExpr = RightShift( @@ -470,7 +471,7 @@ func RegistryEntryHolderSetDecoder(g *Generator, varToSet ast.Expr, dataRaw any, if err != nil { return } - lName := "l" + CamelCase(name) + lName := "l" + util.CamelCase(name) s1 := VarStmt(lName, countType) s2, err := g.VisitDecoder(Ident(lName), "varint", name) if err != nil { diff --git a/proto_generator/native_encoder.go b/proto_generator/native_encoder.go index 01c8dbe..560521b 100644 --- a/proto_generator/native_encoder.go +++ b/proto_generator/native_encoder.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/admin-else/strom/proto_generator/protodef" + "github.com/admin-else/strom/util" "github.com/go-viper/mapstructure/v2" ) @@ -37,11 +38,11 @@ func ContainerEncoder(g *Generator, varToSet ast.Expr, dataRaw any, name string) for _, field := range data { var statements []ast.Stmt - cName := CamelCase(field.Name) + cName := util.CamelCase(field.Name) if field.Anon { cName = "Anon" } - statements, err = g.VisitEncoder(SelectorExprAndStr(varToSet, cName), field.Type, name+CamelCase(cName)) + statements, err = g.VisitEncoder(SelectorExprAndStr(varToSet, cName), field.Type, name+util.CamelCase(cName)) if err != nil { return } @@ -170,7 +171,7 @@ func BitFieldEncoder(g *Generator, varToSet ast.Expr, dataRaw any, name string) Ident(boolFix), SelectorExprAndStr( varToSet, - CamelCase(field.Name), + util.CamelCase(field.Name), ), ), NumLitHex((1< 0 && s[len(s)-1] == e +}