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.
This commit is contained in:
Daniar Mannanov 2026-08-26 02:05:00 +03:00
parent 4a9d4a7407
commit d3e645daf5

View file

@ -3,9 +3,12 @@
package main package main
import ( import (
"archive/zip"
"bufio" "bufio"
"bytes"
"context" "context"
"encoding/binary" "encoding/binary"
"encoding/json"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
@ -20,7 +23,10 @@ import (
"regionio/internal/world" "regionio/internal/world"
) )
const fixtureMagic = "RIOPAR02" const (
fixtureMagic = "RIOPAR02"
baseFixtureMagic = "RIOBASE1"
)
type chunkPos struct{ x, z int32 } 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") 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") output := flag.String("output", "internal/world/testdata/vanilla_overworld_12345.bin", "output fixture")
keep := flag.Bool("keep", false, "keep the temporary vanilla world") 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() flag.Parse()
chunks, err := parseChunks(*chunksFlag) chunks, err := parseChunks(*chunksFlag)
@ -57,15 +66,27 @@ func main() {
} else { } else {
fmt.Fprintf(os.Stderr, "vanilla workspace: %s\n", work) 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) fatal(err)
} }
if err := runServer(*java, jar, work, chunks); err != nil { if err := runServer(*java, jar, work, chunks); err != nil {
fatal(err) fatal(err)
} }
overworld := filepath.Join(work, "world", "dimensions", "minecraft", "overworld") overworld := filepath.Join(work, "world", "dimensions", "minecraft", "overworld")
if err := writeFixture(overworld, *output, *seed, chunks); err != nil { if *blocksOnly {
fatal(err) 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)) fmt.Printf("wrote %s: seed %d, %d chunks\n", *output, *seed, len(chunks))
} }
@ -83,11 +104,11 @@ func requireJava25(java string) error {
return nil 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 { if err := os.WriteFile(filepath.Join(dir, "eula.txt"), []byte("eula=true\n"), 0o644); err != nil {
return err 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) 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 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) { func parseChunks(raw string) ([]chunkPos, error) {
parts := strings.Split(raw, ";") parts := strings.Split(raw, ";")
chunks := make([]chunkPos, 0, len(parts)) chunks := make([]chunkPos, 0, len(parts))