package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_19_R3; import com.destroystokyo.paper.util.maplist.EntityList; import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter; import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; import com.fastasyncworldedit.bukkit.adapter.NMSAdapter; import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.ReflectionUtils; import com.fastasyncworldedit.core.util.TaskManager; import com.mojang.datafixers.util.Either; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.Refraction; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; import io.papermc.paper.world.ChunkEntitySlices; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.IdMap; import net.minecraft.core.Registry; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.BitStorage; import net.minecraft.util.ExceptionCollector; import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.ThreadingDetector; import net.minecraft.util.ZeroBitStorage; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.GlobalPalette; import net.minecraft.world.level.chunk.HashMapPalette; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.LinearPalette; import net.minecraft.world.level.chunk.Palette; import net.minecraft.world.level.chunk.PalettedContainer; import net.minecraft.world.level.chunk.SingleValuePalette; import net.minecraft.world.level.entity.PersistentEntitySectionManager; import org.bukkit.craftbukkit.v1_19_R3.CraftChunk; import sun.misc.Unsafe; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Semaphore; import java.util.function.Function; import static net.minecraft.core.registries.Registries.BIOME; public final class PaperweightPlatformAdapter extends NMSAdapter { public static final Field fieldData; public static final Constructor dataConstructor; public static final Field fieldStorage; public static final Field fieldPalette; private static final Field fieldTickingFluidCount; private static final Field fieldTickingBlockCount; private static final Field fieldNonEmptyBlockCount; private static final MethodHandle methodGetVisibleChunk; private static final int CHUNKSECTION_BASE; private static final int CHUNKSECTION_SHIFT; private static final Field fieldThreadingDetector; private static final long fieldThreadingDetectorOffset; private static final Field fieldLock; private static final long fieldLockOffset; private static final MethodHandle methodRemoveGameEventListener; private static final MethodHandle methodremoveTickingBlockEntity; private static final Field fieldRemove; static final boolean POST_CHUNK_REWRITE; private static Method PAPER_CHUNK_GEN_ALL_ENTITIES; private static Field LEVEL_CHUNK_ENTITIES; private static Field SERVER_LEVEL_ENTITY_MANAGER; static { try { fieldData = PalettedContainer.class.getDeclaredField(Refraction.pickName("data", "d")); fieldData.setAccessible(true); Class dataClazz = fieldData.getType(); dataConstructor = dataClazz.getDeclaredConstructors()[0]; dataConstructor.setAccessible(true); fieldStorage = dataClazz.getDeclaredField(Refraction.pickName("storage", "b")); fieldStorage.setAccessible(true); fieldPalette = dataClazz.getDeclaredField(Refraction.pickName("palette", "c")); fieldPalette.setAccessible(true); fieldTickingFluidCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("tickingFluidCount", "h")); fieldTickingFluidCount.setAccessible(true); fieldTickingBlockCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("tickingBlockCount", "g")); fieldTickingBlockCount.setAccessible(true); fieldNonEmptyBlockCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("nonEmptyBlockCount", "f")); fieldNonEmptyBlockCount.setAccessible(true); Method getVisibleChunkIfPresent = ChunkMap.class.getDeclaredMethod(Refraction.pickName( "getVisibleChunkIfPresent", "b" ), long.class); getVisibleChunkIfPresent.setAccessible(true); methodGetVisibleChunk = MethodHandles.lookup().unreflect(getVisibleChunkIfPresent); Unsafe unsafe = ReflectionUtils.getUnsafe(); if (!PaperLib.isPaper()) { fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "f")); fieldThreadingDetectorOffset = unsafe.objectFieldOffset(fieldThreadingDetector); fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c")); fieldLockOffset = unsafe.objectFieldOffset(fieldLock); } else { // in paper, the used methods are synchronized properly fieldThreadingDetector = null; fieldThreadingDetectorOffset = -1; fieldLock = null; fieldLockOffset = -1; } Method removeGameEventListener = LevelChunk.class.getDeclaredMethod( Refraction.pickName("removeGameEventListener", "a"), BlockEntity.class, ServerLevel.class ); removeGameEventListener.setAccessible(true); methodRemoveGameEventListener = MethodHandles.lookup().unreflect(removeGameEventListener); Method removeBlockEntityTicker = LevelChunk.class.getDeclaredMethod( Refraction.pickName( "removeBlockEntityTicker", "l" ), BlockPos.class ); removeBlockEntityTicker.setAccessible(true); methodremoveTickingBlockEntity = MethodHandles.lookup().unreflect(removeBlockEntityTicker); fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p")); fieldRemove.setAccessible(true); CHUNKSECTION_BASE = unsafe.arrayBaseOffset(LevelChunkSection[].class); int scale = unsafe.arrayIndexScale(LevelChunkSection[].class); if ((scale & (scale - 1)) != 0) { throw new Error("data type scale not a power of two"); } CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale); boolean chunkRewrite; try { ServerLevel.class.getDeclaredMethod("getEntityLookup"); chunkRewrite = true; PAPER_CHUNK_GEN_ALL_ENTITIES = ChunkEntitySlices.class.getDeclaredMethod("getAllEntities"); PAPER_CHUNK_GEN_ALL_ENTITIES.setAccessible(true); } catch (NoSuchMethodException ignored) { chunkRewrite = false; } try { // Paper - Pre-Chunk-Update LEVEL_CHUNK_ENTITIES = LevelChunk.class.getDeclaredField("entities"); LEVEL_CHUNK_ENTITIES.setAccessible(true); } catch (NoSuchFieldException ignored) { } try { // Non-Paper SERVER_LEVEL_ENTITY_MANAGER = ServerLevel.class.getDeclaredField("entityManager"); LEVEL_CHUNK_ENTITIES.setAccessible(true); } catch (NoSuchFieldException ignored) { } POST_CHUNK_REWRITE = chunkRewrite; } catch (RuntimeException e) { throw e; } catch (Throwable rethrow) { rethrow.printStackTrace(); throw new RuntimeException(rethrow); } } static boolean setSectionAtomic( LevelChunkSection[] sections, LevelChunkSection expected, LevelChunkSection value, int layer ) { long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE; if (layer >= 0 && layer < sections.length) { return ReflectionUtils.getUnsafe().compareAndSwapObject(sections, offset, expected, value); } return false; } // There is no point in having a functional semaphore for paper servers. private static final ThreadLocal SEMAPHORE_THREAD_LOCAL = ThreadLocal.withInitial(() -> new DelegateSemaphore(1, null)); static DelegateSemaphore applyLock(LevelChunkSection section) { if (PaperLib.isPaper()) { return SEMAPHORE_THREAD_LOCAL.get(); } try { synchronized (section) { Unsafe unsafe = ReflectionUtils.getUnsafe(); PalettedContainer blocks = section.getStates(); ThreadingDetector currentThreadingDetector = (ThreadingDetector) unsafe.getObject( blocks, fieldThreadingDetectorOffset ); synchronized (currentThreadingDetector) { Semaphore currentLock = (Semaphore) unsafe.getObject(currentThreadingDetector, fieldLockOffset); if (currentLock instanceof DelegateSemaphore delegateSemaphore) { return delegateSemaphore; } DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); unsafe.putObject(currentThreadingDetector, fieldLockOffset, newLock); return newLock; } } } catch (Throwable e) { e.printStackTrace(); throw new RuntimeException(e); } } public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { if (!PaperLib.isPaper()) { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false); if (nmsChunk != null) { return nmsChunk; } if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { return nmsChunk; } nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); if (nmsChunk != null) { return nmsChunk; } // Avoid "async" methods from the main thread. if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); try { CraftChunk chunk = (CraftChunk) future.get(); return chunk.getHandle(); } catch (Throwable e) { e.printStackTrace(); } } return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; try { return (ChunkHolder) methodGetVisibleChunk.invoke(chunkMap, ChunkPos.asLong(chunkX, chunkZ)); } catch (Throwable thr) { throw new RuntimeException(thr); } } @SuppressWarnings("deprecation") public static void sendChunk(ServerLevel nmsWorld, int chunkX, int chunkZ, boolean lighting) { ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ); if (chunkHolder == null) { return; } ChunkPos coordIntPair = new ChunkPos(chunkX, chunkZ); LevelChunk levelChunk; if (PaperLib.isPaper()) { // getChunkAtIfLoadedImmediately is paper only levelChunk = nmsWorld .getChunkSource() .getChunkAtIfLoadedImmediately(chunkX, chunkZ); } else { levelChunk = ((Optional) ((Either) chunkHolder .getTickingChunkFuture() // method is not present with new paper chunk system .getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left()) .orElse(null); } if (levelChunk == null) { return; } TaskManager.taskManager().task(() -> { ClientboundLevelChunkWithLightPacket packet; if (PaperLib.isPaper()) { packet = new ClientboundLevelChunkWithLightPacket( levelChunk, nmsWorld.getChunkSource().getLightEngine(), null, null, true, false // last false is to not bother with x-ray ); } else { // deprecated on paper - deprecation suppressed packet = new ClientboundLevelChunkWithLightPacket( levelChunk, nmsWorld.getChunkSource().getLightEngine(), null, null, true ); } nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); }); } private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { return serverLevel.getChunkSource().chunkMap.getPlayers(coordIntPair, false); } /* NMS conversion */ public static LevelChunkSection newChunkSection( final int layer, final char[] blocks, CachedBukkitAdapter adapter, Registry biomeRegistry, @Nullable PalettedContainer> biomes ) { return newChunkSection(layer, null, blocks, adapter, biomeRegistry, biomes); } public static LevelChunkSection newChunkSection( final int layer, final Function get, char[] set, CachedBukkitAdapter adapter, Registry biomeRegistry, @Nullable PalettedContainer> biomes ) { if (set == null) { return newChunkSection(layer, biomeRegistry, biomes); } final int[] blockToPalette = FaweCache.INSTANCE.BLOCK_TO_PALETTE.get(); final int[] paletteToBlock = FaweCache.INSTANCE.PALETTE_TO_BLOCK.get(); final long[] blockStates = FaweCache.INSTANCE.BLOCK_STATES.get(); final int[] blocksCopy = FaweCache.INSTANCE.SECTION_BLOCKS.get(); try { int num_palette; if (get == null) { num_palette = createPalette(blockToPalette, paletteToBlock, blocksCopy, set, adapter, null); } else { num_palette = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, get, set, adapter, null); } int bitsPerEntry = MathMan.log2nlz(num_palette - 1); if (bitsPerEntry > 0 && bitsPerEntry < 5) { bitsPerEntry = 4; } else if (bitsPerEntry > 8) { bitsPerEntry = MathMan.log2nlz(Block.BLOCK_STATE_REGISTRY.size() - 1); } int bitsPerEntryNonZero = Math.max(bitsPerEntry, 1); // We do want to use zero sometimes final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntryNonZero); final int blockBitArrayEnd = MathMan.ceilZero((float) 4096 / blocksPerLong); if (num_palette == 1) { for (int i = 0; i < blockBitArrayEnd; i++) { blockStates[i] = 0; } } else { final BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntryNonZero, 4096, blockStates); bitArray.fromRaw(blocksCopy); } final long[] bits = Arrays.copyOfRange(blockStates, 0, blockBitArrayEnd); final BitStorage nmsBits; if (bitsPerEntry == 0) { nmsBits = new ZeroBitStorage(4096); } else { nmsBits = new SimpleBitStorage(bitsPerEntry, 4096, bits); } List palette; if (bitsPerEntry < 9) { palette = new ArrayList<>(); for (int i = 0; i < num_palette; i++) { int ordinal = paletteToBlock[i]; blockToPalette[ordinal] = Integer.MAX_VALUE; final BlockState state = BlockTypesCache.states[ordinal]; palette.add(((PaperweightBlockMaterial) state.getMaterial()).getState()); } } else { palette = List.of(); } // Create palette with data @SuppressWarnings("deprecation") // constructor is deprecated on paper, but needed to keep compatibility with spigot final PalettedContainer blockStatePalettedContainer = new PalettedContainer<>( Block.BLOCK_STATE_REGISTRY, PalettedContainer.Strategy.SECTION_STATES, PalettedContainer.Strategy.SECTION_STATES.getConfiguration(Block.BLOCK_STATE_REGISTRY, bitsPerEntry), nmsBits, palette ); if (biomes == null) { IdMap> biomeHolderIdMap = biomeRegistry.asHolderIdMap(); biomes = new PalettedContainer<>( biomeHolderIdMap, biomeHolderIdMap.byIdOrThrow(WorldEditPlugin .getInstance() .getBukkitImplAdapter() .getInternalBiomeId( BiomeTypes.PLAINS)), PalettedContainer.Strategy.SECTION_BIOMES, null ); } return new LevelChunkSection(layer, blockStatePalettedContainer, biomes); } catch (final Throwable e) { throw e; } finally { Arrays.fill(blockToPalette, Integer.MAX_VALUE); Arrays.fill(paletteToBlock, Integer.MAX_VALUE); Arrays.fill(blockStates, 0); Arrays.fill(blocksCopy, 0); } } @SuppressWarnings("deprecation") // Only deprecated in paper private static LevelChunkSection newChunkSection( int layer, Registry biomeRegistry, @Nullable PalettedContainer> biomes ) { if (biomes == null) { return new LevelChunkSection(layer, biomeRegistry); } PalettedContainer dataPaletteBlocks = new PalettedContainer<>( Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null ); return new LevelChunkSection(layer, dataPaletteBlocks, biomes); } /** * Create a new {@link PalettedContainer}. Should only be used if no biome container existed beforehand. */ public static PalettedContainer> getBiomePalettedContainer( BiomeType[] biomes, IdMap> biomeRegistry ) { if (biomes == null) { return null; } BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); // Don't stream this as typically will see 1-4 biomes; stream overhead is large for the small length Map> palette = new HashMap<>(); for (BiomeType biomeType : new LinkedList<>(Arrays.asList(biomes))) { Holder biome; if (biomeType == null) { biome = biomeRegistry.byId(adapter.getInternalBiomeId(BiomeTypes.PLAINS)); } else { biome = biomeRegistry.byId(adapter.getInternalBiomeId(biomeType)); } palette.put(biomeType, biome); } int biomeCount = palette.size(); int bitsPerEntry = MathMan.log2nlz(biomeCount - 1); Object configuration = PalettedContainer.Strategy.SECTION_STATES.getConfiguration( new FakeIdMapBiome(biomeCount), bitsPerEntry ); if (bitsPerEntry > 3) { bitsPerEntry = MathMan.log2nlz(biomeRegistry.size() - 1); } PalettedContainer> biomePalettedContainer = new PalettedContainer<>( biomeRegistry, biomeRegistry.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), PalettedContainer.Strategy.SECTION_BIOMES, null ); final Palette> biomePalette; if (bitsPerEntry == 0) { biomePalette = new SingleValuePalette<>( biomePalettedContainer.registry, biomePalettedContainer, new ArrayList<>(palette.values()) // Must be modifiable ); } else if (bitsPerEntry == 4) { biomePalette = LinearPalette.create( 4, biomePalettedContainer.registry, biomePalettedContainer, new ArrayList<>(palette.values()) // Must be modifiable ); } else if (bitsPerEntry < 9) { biomePalette = HashMapPalette.create( bitsPerEntry, biomePalettedContainer.registry, biomePalettedContainer, new ArrayList<>(palette.values()) // Must be modifiable ); } else { biomePalette = GlobalPalette.create( bitsPerEntry, biomePalettedContainer.registry, biomePalettedContainer, null // unused ); } int bitsPerEntryNonZero = Math.max(bitsPerEntry, 1); // We do want to use zero sometimes final int blocksPerLong = MathMan.floorZero((double) 64 / bitsPerEntryNonZero); final int arrayLength = MathMan.ceilZero(64f / blocksPerLong); BitStorage bitStorage = bitsPerEntry == 0 ? new ZeroBitStorage(64) : new SimpleBitStorage( bitsPerEntry, 64, new long[arrayLength] ); try { Object data = dataConstructor.newInstance(configuration, bitStorage, biomePalette); fieldData.set(biomePalettedContainer, data); int index = 0; for (int y = 0; y < 4; y++) { for (int z = 0; z < 4; z++) { for (int x = 0; x < 4; x++, index++) { BiomeType biomeType = biomes[index]; if (biomeType == null) { continue; } Holder biome = biomeRegistry.byId(WorldEditPlugin .getInstance() .getBukkitImplAdapter() .getInternalBiomeId(biomeType)); if (biome == null) { continue; } biomePalettedContainer.set(x, y, z, biome); } } } } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } return biomePalettedContainer; } public static void clearCounts(final LevelChunkSection section) throws IllegalAccessException { fieldTickingFluidCount.setShort(section, (short) 0); fieldTickingBlockCount.setShort(section, (short) 0); } public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) { final Registry biomeRegistry = levelAccessor.registryAccess().registryOrThrow(BIOME); if (biomeRegistry.getKey(biome.value()) == null) { return biomeRegistry.asHolderIdMap().getId(biome) == -1 ? BiomeTypes.OCEAN : null; } return BiomeTypes.get(biome.unwrapKey().orElseThrow().location().toString()); } static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { try { if (levelChunk.loaded || levelChunk.level.isClientSide()) { BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); if (blockEntity != null) { if (!levelChunk.level.isClientSide) { methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); } fieldRemove.set(beacon, true); } } methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos()); } catch (Throwable throwable) { throwable.printStackTrace(); } } static List getEntities(LevelChunk chunk) { ExceptionCollector collector = new ExceptionCollector<>(); if (PaperLib.isPaper()) { if (POST_CHUNK_REWRITE) { try { //noinspection unchecked return (List) PAPER_CHUNK_GEN_ALL_ENTITIES.invoke(chunk.level.getEntityLookup().getChunk(chunk.locX, chunk.locZ)); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Failed to lookup entities [POST_CHUNK_REWRITE=true]", e); } } try { EntityList entityList = (EntityList) LEVEL_CHUNK_ENTITIES.get(chunk); return List.of(entityList.getRawData()); } catch (IllegalAccessException e) { collector.add(new RuntimeException("Failed to lookup entities [POST_CHUNK_REWRITE=false]", e)); // fall through } } try { //noinspection unchecked return ((PersistentEntitySectionManager) (SERVER_LEVEL_ENTITY_MANAGER.get(chunk.level))).getEntities(chunk.getPos()); } catch (IllegalAccessException e) { collector.add(new RuntimeException("Failed to lookup entities [PAPER=false]", e)); } collector.throwIfPresent(); return List.of(); } record FakeIdMapBlock(int size) implements IdMap { @Override public int getId(final net.minecraft.world.level.block.state.BlockState entry) { return 0; } @Nullable @Override public net.minecraft.world.level.block.state.BlockState byId(final int index) { return null; } @Nonnull @Override public Iterator iterator() { return Collections.emptyIterator(); } } record FakeIdMapBiome(int size) implements IdMap { @Override public int getId(final Biome entry) { return 0; } @Nullable @Override public Biome byId(final int index) { return null; } @Nonnull @Override public Iterator iterator() { return Collections.emptyIterator(); } } }