From d3e645daf50f9cc369b0b69d2105204d1e95c609 Mon Sep 17 00:00:00 2001 From: Daniar Mannanov Date: Wed, 26 Aug 2026 02:05:00 +0300 Subject: [PATCH] vanillacapture: capture featureless base terrain -featureless derives a world datapack from the server jar (inner server or bundler layout): every biome keeps its carvers but loses its feature stages and every structure set loses its structures, so the capture is pure noise terrain with surface rules, carvers, aquifers, and veins. -blocks-only writes a blocks-only RIOBASE1 fixture for it. -port avoids colliding with something else already listening on 25565. --- cmd/vanillacapture/main.go | 224 ++++++++++++++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 6 deletions(-) diff --git a/cmd/vanillacapture/main.go b/cmd/vanillacapture/main.go index aee4ada..8bc2b2d 100644 --- a/cmd/vanillacapture/main.go +++ b/cmd/vanillacapture/main.go @@ -3,9 +3,12 @@ package main import ( + "archive/zip" "bufio" + "bytes" "context" "encoding/binary" + "encoding/json" "errors" "flag" "fmt" @@ -20,7 +23,10 @@ import ( "regionio/internal/world" ) -const fixtureMagic = "RIOPAR02" +const ( + fixtureMagic = "RIOPAR02" + baseFixtureMagic = "RIOBASE1" +) type chunkPos struct{ x, z int32 } @@ -31,6 +37,9 @@ func main() { chunksFlag := flag.String("chunks", "0,0;1,0;0,1;-1,-1", "semicolon-separated chunk coordinates") output := flag.String("output", "internal/world/testdata/vanilla_overworld_12345.bin", "output fixture") keep := flag.Bool("keep", false, "keep the temporary vanilla world") + featureless := flag.Bool("featureless", false, "install a derived datapack that empties biome features and structure sets, capturing base terrain only") + blocksOnly := flag.Bool("blocks-only", false, "write a blocks-only fixture (RIOBASE1) without biome cells or heightmaps") + port := flag.Int("port", 25565, "server listen port; change it when 25565 is taken on this host") flag.Parse() chunks, err := parseChunks(*chunksFlag) @@ -57,15 +66,27 @@ func main() { } else { fmt.Fprintf(os.Stderr, "vanilla workspace: %s\n", work) } - if err := prepareServer(work, *seed); err != nil { + if *featureless { + packs := filepath.Join(work, "world", "datapacks") + if err := installFeaturelessDatapack(jar, packs); err != nil { + fatal(err) + } + } + if err := prepareServer(work, *seed, *port); err != nil { fatal(err) } if err := runServer(*java, jar, work, chunks); err != nil { fatal(err) } overworld := filepath.Join(work, "world", "dimensions", "minecraft", "overworld") - if err := writeFixture(overworld, *output, *seed, chunks); err != nil { - fatal(err) + if *blocksOnly { + if err := writeBaseFixture(overworld, *output, *seed, chunks); err != nil { + fatal(err) + } + } else { + if err := writeFixture(overworld, *output, *seed, chunks); err != nil { + fatal(err) + } } fmt.Printf("wrote %s: seed %d, %d chunks\n", *output, *seed, len(chunks)) } @@ -83,11 +104,11 @@ func requireJava25(java string) error { return nil } -func prepareServer(dir string, seed int64) error { +func prepareServer(dir string, seed int64, port int) error { if err := os.WriteFile(filepath.Join(dir, "eula.txt"), []byte("eula=true\n"), 0o644); err != nil { return err } - properties := fmt.Sprintf("level-seed=%d\nonline-mode=false\nspawn-protection=0\nview-distance=2\nsimulation-distance=2\nmax-tick-time=-1\n", seed) + properties := fmt.Sprintf("level-seed=%d\nonline-mode=false\nspawn-protection=0\nview-distance=2\nsimulation-distance=2\nmax-tick-time=-1\nserver-port=%d\n", seed, port) return os.WriteFile(filepath.Join(dir, "server.properties"), []byte(properties), 0o644) } @@ -260,6 +281,197 @@ func writeFixture(worldDir, output string, seed int64, chunks []chunkPos) error return nil } +func writeBaseFixture(worldDir, output string, seed int64, chunks []chunkPos) error { + store, err := world.NewStore(worldDir) + if err != nil { + return err + } + defer store.Close() + if err := os.MkdirAll(filepath.Dir(output), 0o755); err != nil { + return err + } + tmp := output + ".tmp" + f, err := os.Create(tmp) + if err != nil { + return err + } + ok := false + defer func() { + _ = f.Close() + if !ok { + _ = os.Remove(tmp) + } + }() + var header [16]byte + copy(header[:8], baseFixtureMagic) + binary.BigEndian.PutUint64(header[8:16], uint64(seed)) + if _, err := f.Write(header[:]); err != nil { + return err + } + var value [8]byte + for _, pos := range chunks { + chunk, err := store.LoadVanillaChunk(pos.x, pos.z) + if err != nil { + return fmt.Errorf("load vanilla chunk (%d,%d): %w", pos.x, pos.z, err) + } + binary.BigEndian.PutUint32(value[:4], uint32(pos.x)) + binary.BigEndian.PutUint32(value[4:], uint32(pos.z)) + if _, err := f.Write(value[:]); err != nil { + return err + } + for y := world.MinY; y < world.MinY+world.WorldHeight; y++ { + for z := 0; z < 16; z++ { + for x := 0; x < 16; x++ { + binary.BigEndian.PutUint16(value[:2], chunk.GetBlock(x, y, z)) + if _, err := f.Write(value[:2]); err != nil { + return err + } + } + } + } + } + if err := f.Sync(); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + if err := os.Rename(tmp, output); err != nil { + return err + } + ok = true + return nil +} + +// installFeaturelessDatapack derives a world datapack from the server jar's own +// worldgen data: every biome keeps its carvers but loses its feature stages, +// and every structure set loses its structures. A server started with the pack +// installed therefore generates noise terrain with surface rules, carvers, +// aquifers, and noise-router veins — and nothing else — which is exactly the +// ground truth the undecorated side of our pipeline should reproduce. +// +// Both jar shapes work: the deobfuscated inner server, whose data/ entries are +// read directly, and the official bundler, which stores that same inner server +// under META-INF/versions/. +func installFeaturelessDatapack(jar, packsDir string) error { + r, err := zip.OpenReader(jar) + if err != nil { + return err + } + defer r.Close() + + type source struct { + biomes map[string][]byte + sets map[string][]byte + } + read := func(entries []*zip.File) (map[string][]byte, map[string][]byte, error) { + biomes := make(map[string][]byte) + sets := make(map[string][]byte) + for _, entry := range entries { + dir, name := filepath.Split(filepath.ToSlash(entry.Name)) + if !strings.HasSuffix(name, ".json") { + continue + } + raw, err := readZipEntry(entry) + if err != nil { + return nil, nil, err + } + switch dir { + case "data/minecraft/worldgen/biome/": + biomes[name] = raw + case "data/minecraft/worldgen/structure_set/": + sets[name] = raw + } + } + return biomes, sets, nil + } + + topBiomes, topSets, err := read(r.File) + if err != nil { + return err + } + biomes, sets := topBiomes, topSets + if len(biomes) == 0 { + for _, entry := range r.File { + if !strings.HasPrefix(entry.Name, "META-INF/versions/") || !strings.HasSuffix(entry.Name, ".jar") { + continue + } + nestedRaw, err := readZipEntry(entry) + if err != nil { + return err + } + nested, err := zip.NewReader(bytes.NewReader(nestedRaw), int64(len(nestedRaw))) + if err != nil { + return fmt.Errorf("%s: %w", entry.Name, err) + } + nestedBiomes, nestedSets, err := read(nested.File) + if err != nil { + return err + } + if len(nestedBiomes) > 0 { + biomes, sets = nestedBiomes, nestedSets + break + } + } + } + if len(biomes) == 0 { + return errors.New("no biome JSONs found in server jar; is this a Mojang server jar?") + } + + root := filepath.Join(packsDir, "regionio_featureless") + biomeDir := filepath.Join(root, "data", "minecraft", "worldgen", "biome") + setDir := filepath.Join(root, "data", "minecraft", "worldgen", "structure_set") + if err := os.MkdirAll(biomeDir, 0o755); err != nil { + return err + } + if err := os.MkdirAll(setDir, 0o755); err != nil { + return err + } + mcmeta := `{"pack":{"description":"RegionIO base-terrain capture: features and structure sets removed","min_format":[101,1],"max_format":101}}` + if err := os.WriteFile(filepath.Join(root, "pack.mcmeta"), []byte(mcmeta), 0o644); err != nil { + return err + } + for name, raw := range biomes { + var doc map[string]any + if err := json.Unmarshal(raw, &doc); err != nil { + return fmt.Errorf("biome %s: %w", name, err) + } + doc["features"] = []any{} + out, err := json.Marshal(doc) + if err != nil { + return err + } + if err := os.WriteFile(filepath.Join(biomeDir, name), out, 0o644); err != nil { + return err + } + } + for name, raw := range sets { + var doc map[string]any + if err := json.Unmarshal(raw, &doc); err != nil { + return fmt.Errorf("structure set %s: %w", name, err) + } + doc["structures"] = []any{} + out, err := json.Marshal(doc) + if err != nil { + return err + } + if err := os.WriteFile(filepath.Join(setDir, name), out, 0o644); err != nil { + return err + } + } + fmt.Fprintf(os.Stderr, "featureless datapack: %d biomes, %d structure sets\n", len(biomes), len(sets)) + return nil +} + +func readZipEntry(entry *zip.File) ([]byte, error) { + rc, err := entry.Open() + if err != nil { + return nil, err + } + defer rc.Close() + return io.ReadAll(rc) +} + func parseChunks(raw string) ([]chunkPos, error) { parts := strings.Split(raw, ";") chunks := make([]chunkPos, 0, len(parts))