Files

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);
}
}