137 lines
9.9 KiB
Java
137 lines
9.9 KiB
Java
// Copyright Shacraft contributors. MIT OR Apache-2.0.
|
|
// Generate our own tiny world metadata and validate files using public Java26.2
|
|
// APIs. Does not start a server, open sockets, or read/accept an EULA.
|
|
import java.nio.file.*;
|
|
import java.io.*;
|
|
import java.util.*;
|
|
import com.mojang.serialization.*;
|
|
import net.minecraft.SharedConstants;
|
|
import net.minecraft.server.Bootstrap;
|
|
import net.minecraft.nbt.*;
|
|
import net.minecraft.core.*;
|
|
import net.minecraft.core.registries.*;
|
|
import net.minecraft.data.registries.VanillaRegistries;
|
|
import net.minecraft.world.level.*;
|
|
import net.minecraft.world.level.block.*;
|
|
import net.minecraft.world.level.chunk.*;
|
|
import net.minecraft.world.level.chunk.storage.*;
|
|
import net.minecraft.world.level.levelgen.*;
|
|
import net.minecraft.world.level.levelgen.flat.*;
|
|
import net.minecraft.world.level.levelgen.presets.*;
|
|
import net.minecraft.world.level.storage.*;
|
|
import net.minecraft.world.level.gamerules.*;
|
|
import net.minecraft.world.level.biome.*;
|
|
|
|
class compat_java {
|
|
static final NbtAccounter BUDGET=NbtAccounter.create(16*1024*1024);
|
|
static void generate(Path out, HolderLookup.Provider lookup) throws Exception {
|
|
Files.createDirectories(out);
|
|
var settings=new LevelSettings("Shacraft template",GameType.CREATIVE,LevelSettings.DifficultySettings.DEFAULT,true,WorldDataConfiguration.DEFAULT);
|
|
var level=new PrimaryLevelData(settings,PrimaryLevelData.SpecialWorldProperty.FLAT,Lifecycle.stable());
|
|
level.setInitialized(true);
|
|
level.setSpawn(LevelData.RespawnData.of(Level.OVERWORLD,new BlockPos(0,80,0),0,0));
|
|
var root=new CompoundTag();root.put("Data",level.createTag(null));
|
|
NbtIo.writeCompressed(root,out.resolve("level.dat"));
|
|
var flat=new FlatLevelGeneratorSettings(Optional.of(HolderSet.direct()),lookup.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.PLAINS),List.of());
|
|
flat.getLayersInfo().add(new FlatLayerInfo(1,Blocks.AIR));flat.updateLayers();
|
|
var dims=lookup.lookupOrThrow(Registries.WORLD_PRESET).getOrThrow(WorldPresets.FLAT).value().createWorldDimensions().replaceOverworldGenerator(lookup,new FlatLevelSource(flat));
|
|
var worldgen=new WorldGenSettings(new WorldOptions(0,false,false),dims);
|
|
var encoded=WorldGenSettings.CODEC.encodeStart(lookup.createSerializationContext(NbtOps.INSTANCE),worldgen).getOrThrow();
|
|
var data=new CompoundTag();data.put("data",encoded);NbtUtils.addCurrentDataVersion(data);
|
|
Path gen=WorldGenSettings.TYPE.id().withSuffix(".dat").resolveAgainst(out.resolve("data"));
|
|
Files.createDirectories(gen.getParent());NbtIo.writeCompressed(data,gen);
|
|
var rules=new GameRules(level.enabledFeatures());rules.set(GameRules.SPAWN_MOBS,false,null);rules.set(GameRules.ADVANCE_TIME,false,null);
|
|
LevelStorageSource.writeGameRules(level,out,rules);
|
|
System.out.println("Generated metadata with public Java26.2 APIs: "+out);
|
|
}
|
|
static PalettedContainerFactory factory(HolderLookup.Provider lookup) {
|
|
var biomeRegistry=new MappedRegistry<Biome>(Registries.BIOME,Lifecycle.stable());
|
|
lookup.lookupOrThrow(Registries.BIOME).listElements().forEach(h->biomeRegistry.register(h.key(),h.value(),RegistrationInfo.BUILT_IN));
|
|
biomeRegistry.freeze();
|
|
return PalettedContainerFactory.create(new RegistryAccess.ImmutableRegistryAccess(List.of(biomeRegistry)));
|
|
}
|
|
static void fixture(Path out,HolderLookup.Provider lookup) throws Exception {
|
|
generate(out,lookup);
|
|
var factory=factory(lookup);
|
|
var section=new LevelChunkSection(factory);
|
|
section.setBlockState(15,15,15,Blocks.CHEST.defaultBlockState());
|
|
section.setBlockState(14,15,15,Blocks.OAK_STAIRS.defaultBlockState());
|
|
section.setBlockState(13,15,15,Blocks.STONE.defaultBlockState());
|
|
var chest=new CompoundTag();chest.putString("id","minecraft:chest");chest.putInt("x",-1);chest.putInt("y",-1);chest.putInt("z",31);
|
|
var item=new CompoundTag();item.putByte("Slot",(byte)0);item.putString("id","minecraft:diamond");item.putInt("count",3);
|
|
var inventory=new ListTag();inventory.add(item);chest.put("Items",inventory);chest.putLong("unknown_inventory_tag",12345678901234567L);
|
|
var chunk=new SerializableChunkData(factory,new ChunkPos(-1,1),-4,0,0,net.minecraft.world.level.chunk.status.ChunkStatus.FULL,
|
|
null,null,UpgradeData.EMPTY,null,Map.of(),new ChunkAccess.PackedTicks(List.of(),List.of()),
|
|
new it.unimi.dsi.fastutil.shorts.ShortList[24],false,
|
|
List.of(new SerializableChunkData.SectionData(-1,section,null,null)),List.of(),List.of(chest),new CompoundTag());
|
|
var tag=chunk.write();var unknown=new CompoundTag();unknown.putByte("Byte",(byte)-128);unknown.putShort("Short",(short)-32768);
|
|
unknown.putLong("Long",Long.MIN_VALUE);unknown.putString("Unicode","Привет🌍\u0000");unknown.putIntArray("UUID",new int[]{1,2,3,4});tag.put("UnknownRoot",unknown);
|
|
Files.createDirectories(out.resolve("region"));
|
|
try(var region=new RegionFile(new RegionStorageInfo("compat-fixture",Level.OVERWORLD,"chunk"),out.resolve("region/r.-1.0.mca"),out.resolve("region"),false)) {
|
|
try(var output=region.getChunkDataOutputStream(new ChunkPos(-1,1))){NbtIo.write(tag,output);}
|
|
}
|
|
var entity=new CompoundTag();entity.putString("id","minecraft:armor_stand");entity.putIntArray("UUID",new int[]{1,2,3,4});
|
|
var pos=new ListTag();pos.add(DoubleTag.valueOf(-0.5));pos.add(DoubleTag.valueOf(0));pos.add(DoubleTag.valueOf(31.5));entity.put("Pos",pos);entity.putBoolean("NoGravity",true);
|
|
var entities=new ListTag();entities.add(entity);var root=new CompoundTag();root.putInt("DataVersion",4903);root.putIntArray("Position",new int[]{-1,1});root.put("Entities",entities);
|
|
Files.createDirectories(out.resolve("entities"));
|
|
try(var region=new RegionFile(new RegionStorageInfo("compat-fixture",Level.OVERWORLD,"entities"),out.resolve("entities/r.-1.0.mca"),out.resolve("entities"),false)) {
|
|
try(var output=region.getChunkDataOutputStream(new ChunkPos(-1,1))){NbtIo.write(root,output);}
|
|
}
|
|
Files.writeString(out.resolve("custom-metadata.txt"),"own fixture; retain unknown file\n");
|
|
}
|
|
static void validate(Path world,HolderLookup.Provider lookup) throws Exception {
|
|
var levelRoot=NbtIo.readCompressed(world.resolve("level.dat"),NbtAccounter.create(16*1024*1024));
|
|
var level=levelRoot.getCompoundOrEmpty("Data");
|
|
if(level.getIntOr("DataVersion",-1)!=4903)throw new AssertionError("DataVersion");
|
|
var dynamic=new Dynamic<>(NbtOps.INSTANCE,level);
|
|
var config=LevelStorageSource.readDataConfig(dynamic);
|
|
var settings=LevelSettings.parse(dynamic,config);
|
|
var parsed=PrimaryLevelData.parse(dynamic,settings,PrimaryLevelData.SpecialWorldProperty.FLAT,Lifecycle.stable());
|
|
Path gen=WorldGenSettings.TYPE.id().withSuffix(".dat").resolveAgainst(world.resolve("data"));
|
|
var encoded=NbtIo.readCompressed(gen,NbtAccounter.create(16*1024*1024)).getCompoundOrEmpty("data");
|
|
var worldgen=WorldGenSettings.CODEC.parse(lookup.createSerializationContext(NbtOps.INSTANCE),encoded).getOrThrow();
|
|
if(worldgen.dimensions().dimensions().size()!=3)throw new AssertionError("dimensions");
|
|
var factory=factory(lookup);int chunks=0,sections=0,entities=0;long nonair=0;
|
|
try(var files=Files.walk(world)) {
|
|
for(Path path:files.filter(p->p.toString().endsWith(".mca")&&Set.of("region","entities").contains(p.getParent().getFileName().toString())&&!p.toString().contains("source-sidecar")).toList()) {
|
|
boolean entityRegion=path.getParent().getFileName().toString().equals("entities");
|
|
String[] parts=path.getFileName().toString().split("\\.");int rx=Integer.parseInt(parts[1]),rz=Integer.parseInt(parts[2]);
|
|
try(var region=new RegionFile(new RegionStorageInfo("compat-check",Level.OVERWORLD,"chunk"),path,path.getParent(),false)) {
|
|
for(int i=0;i<1024;i++) {
|
|
var pos=new ChunkPos(rx*32+i%32,rz*32+i/32);
|
|
if(!region.hasChunk(pos))continue;
|
|
try(var input=region.getChunkDataInputStream(pos)) {
|
|
var tag=NbtIo.read(input,NbtAccounter.create(16*1024*1024));
|
|
if(entityRegion) {
|
|
int[] coordinates=tag.getIntArray("Position").orElseThrow();
|
|
if(coordinates.length!=2||coordinates[0]!=pos.x()||coordinates[1]!=pos.z())throw new AssertionError("entity chunk Position");
|
|
for(var value:tag.getListOrEmpty("Entities")) {
|
|
var entity=(CompoundTag)value;
|
|
if(entity.getIntArray("UUID").orElseThrow().length!=4)throw new AssertionError("entity UUID");
|
|
if(entity.getListOrEmpty("Pos").size()!=3)throw new AssertionError("entity Pos");
|
|
if(!BuiltInRegistries.ENTITY_TYPE.containsKey(net.minecraft.resources.Identifier.parse(entity.getString("id").orElseThrow())))throw new AssertionError("unknown vanilla entity type");
|
|
entities++;
|
|
}
|
|
continue;
|
|
}
|
|
var chunk=SerializableChunkData.parse(LevelHeightAccessor.create(-64,384),factory,tag);
|
|
if(chunk==null||!chunk.chunkPos().equals(pos))throw new AssertionError("missing/misplaced chunk "+pos);
|
|
for(var section:chunk.sectionData()) {
|
|
if(section.chunkSection()!=null){sections++;for(int y=0;y<16;y++)for(int z=0;z<16;z++)for(int x=0;x<16;x++)if(!section.chunkSection().getBlockState(x,y,z).isAir())nonair++;}
|
|
}
|
|
chunks++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
System.out.println("{\"java26_2_verified\":true,\"chunks\":"+chunks+",\"sections\":"+sections+",\"entities\":"+entities+",\"nonair_blocks\":"+nonair+",\"level_name\":\""+parsed.getLevelName()+"\",\"scope\":\"NbtIo,RegionFile,SerializableChunkData,PrimaryLevelData,WorldGenSettings codecs; no server simulation\"}");
|
|
}
|
|
public static void main(String[] args) throws Exception {
|
|
SharedConstants.tryDetectVersion();Bootstrap.bootStrap();var lookup=VanillaRegistries.createLookup();
|
|
if(args[0].equals("generate"))generate(Path.of(args[1]),lookup);
|
|
else if(args[0].equals("fixture"))fixture(Path.of(args[1]),lookup);
|
|
else validate(Path.of(args[1]),lookup);
|
|
}
|
|
}
|