From 57968255160c136c13e27a09473125c682434f9d Mon Sep 17 00:00:00 2001 From: paulevsGitch Date: Thu, 27 May 2021 11:11:02 +0300 Subject: [PATCH] DataFixerAPI, WorldDataAPI, ServerLevelMixin --- src/main/java/ru/bclib/api/DataFixerAPI.java | 146 ++++++++++++++++++ src/main/java/ru/bclib/api/WorldDataAPI.java | 88 +++++++++++ .../bclib/mixin/common/ServerLevelMixin.java | 52 +++++++ src/main/resources/bclib.mixins.common.json | 1 + 4 files changed, 287 insertions(+) create mode 100644 src/main/java/ru/bclib/api/DataFixerAPI.java create mode 100644 src/main/java/ru/bclib/api/WorldDataAPI.java create mode 100644 src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java diff --git a/src/main/java/ru/bclib/api/DataFixerAPI.java b/src/main/java/ru/bclib/api/DataFixerAPI.java new file mode 100644 index 00000000..503abe26 --- /dev/null +++ b/src/main/java/ru/bclib/api/DataFixerAPI.java @@ -0,0 +1,146 @@ +package ru.bclib.api; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.storage.RegionFile; + +public class DataFixerAPI { + private static final Map REPLACEMENT = Maps.newHashMap(); + private static final Map FIX_VERSIONS = Maps.newHashMap(); + + public static void fixData(File dir) { + if (REPLACEMENT.isEmpty()) { + return; + } + + boolean shoudFix = false; + Collection mods = FabricLoader.getInstance().getAllMods(); + for (ModContainer mod: mods) { + String name = mod.getMetadata().getId(); + int version = getModVersion(mod.getMetadata().getVersion().toString()); + if (version > 0) { + shoudFix |= FIX_VERSIONS.getOrDefault(name, version) < version; + } + }; + if (!shoudFix) { + return; + } + + List regions = getAllRegions(dir, null); + regions.parallelStream().forEach((file) -> { + try { + System.out.println("Fixing " + file); + boolean[] changed = new boolean[1]; + RegionFile region = new RegionFile(file, file.getParentFile(), true); + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + ChunkPos pos = new ChunkPos(x, z); + changed[0] = false; + if (region.hasChunk(pos)) { + DataInputStream input = region.getChunkDataInputStream(pos); + CompoundTag root = NbtIo.read(input); + input.close(); + ListTag sections = root.getCompound("Level").getList("Sections", 10); + sections.forEach((tag) -> { + ListTag palette = ((CompoundTag) tag).getList("Palette", 10); + palette.forEach((blockTag) -> { + CompoundTag blockTagCompound = ((CompoundTag) blockTag); + String name = blockTagCompound.getString("Name"); + String replace = REPLACEMENT.get(name); + if (replace != null) { + blockTagCompound.putString("Name", replace); + changed[0] = true; + } + }); + }); + if (changed[0]) { + System.out.println("Write!"); + DataOutputStream output = region.getChunkDataOutputStream(pos); + NbtIo.write(root, output); + output.close(); + } + } + } + } + region.close(); + } + catch (Exception e) { + e.printStackTrace(); + } + }); + } + + /** + * Register block data fix. Fix will be applied on world load if current mod version will be newer than specified one. + * @param modID - {@link String} mod id; + * @param modVersion - {@link String} mod version, should be in format: %d.%d.%d + * @param result - {@link String} new block name; + * @param names - array of {@link String}, old block names to convert. + */ + protected static void addFix(String modID, String modVersion, String result, String... names) { + FIX_VERSIONS.put(modID, getModVersion(modVersion)); + for (String name: names) { + REPLACEMENT.put(name, result); + } + } + + private static List getAllRegions(File dir, List list) { + if (list == null) { + list = Lists.newArrayList(); + } + for (File file: dir.listFiles()) { + if (file.isDirectory()) { + getAllRegions(file, list); + } + else if (file.isFile() && file.getName().endsWith(".mca")) { + list.add(file); + } + } + return list; + } + + /** + * Get mod version from string. String should be in format: %d.%d.%d + * @param version - {@link String} mod version. + * @return + */ + public static int getModVersion(String version) { + if (version.isEmpty()) { + return 0; + } + try { + String[] values = version.split("\\."); + return Integer.parseInt(values[0]) << 12 | Integer.parseInt(values[1]) << 6 | Integer.parseInt(values[2]); + } + catch (Exception e) { + return 0; + } + } + + /** + * Get mod version from integer. String will be in format %d.%d.%d + * @param version + * @return + */ + public static String getModVersion(int version) { + int a = (version >> 12) & 63; + int b = (version >> 6) & 63; + int c = version & 63; + return String.format(Locale.ROOT, "%d.%d.%d", a, b, c); + } +} diff --git a/src/main/java/ru/bclib/api/WorldDataAPI.java b/src/main/java/ru/bclib/api/WorldDataAPI.java new file mode 100644 index 00000000..af8a6afa --- /dev/null +++ b/src/main/java/ru/bclib/api/WorldDataAPI.java @@ -0,0 +1,88 @@ +package ru.bclib.api; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import ru.bclib.BCLib; + +public class WorldDataAPI { + private static final Map TAGS = Maps.newHashMap(); + private static final List MODS = Lists.newArrayList(); + private static File dataDir; + + public static void load(File dataDir) { + WorldDataAPI.dataDir = dataDir; + MODS.stream().parallel().forEach(modID -> { + File file = new File(dataDir, modID); + CompoundTag root = new CompoundTag(); + if (file.exists()) { + try { + root = NbtIo.readCompressed(file); + } + catch (IOException e) { + BCLib.LOGGER.error("World data loading failed", e); + } + } + TAGS.put(modID, root); + }); + } + + /** + * Register mod cache, world cache is located in world data folder. + * @param modID - {@link String} modID. + */ + public static void registerModCache(String modID) { + MODS.add(modID); + } + + /** + * Get root {@link CompoundTag} for mod cache in world data folder. + * @param modID - {@link String} modID. + * @return {@link CompoundTag} + */ + public static CompoundTag getRootTag(String modID) { + CompoundTag root = TAGS.get(modID); + if (root == null) { + root = new CompoundTag(); + TAGS.put(modID, root); + } + return root; + } + + /** + * Get {@link CompoundTag} with specified path from mod cache in world data folder. + * @param modID - {@link String} path to tag, dot-separated. + * @return {@link CompoundTag} + */ + public static CompoundTag getCompoundTag(String modID, String path) { + String[] parts = path.split("\\."); + CompoundTag tag = getRootTag(modID); + for (String part: parts) { + if (tag.contains(part)) { + tag = tag.getCompound(part); + } + else { + CompoundTag t = new CompoundTag(); + tag.put(part, t); + tag = t; + } + } + return tag; + } + + public static void saveFile(String modID) { + try { + NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt")); + } + catch (IOException e) { + BCLib.LOGGER.error("World data saving failed", e); + } + } +} diff --git a/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java b/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java new file mode 100644 index 00000000..1b36adc8 --- /dev/null +++ b/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java @@ -0,0 +1,52 @@ +package ru.bclib.mixin.common; + +import java.io.File; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.CustomSpawner; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.ServerLevelData; +import net.minecraft.world.level.storage.WritableLevelData; +import ru.bclib.api.DataFixerAPI; +import ru.bclib.api.WorldDataAPI; + +@Mixin(ServerLevel.class) +public abstract class ServerLevelMixin extends Level { + private static String be_lastWorld = null; + + protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey resourceKey, DimensionType dimensionType, Supplier supplier, boolean bl, boolean bl2, long l) { + super(writableLevelData, resourceKey, dimensionType, supplier, bl, bl2, l); + } + + @Inject(method = "*", at = @At("TAIL")) + private void be_onServerWorldInit(MinecraftServer server, Executor workerExecutor, LevelStorageSource.LevelStorageAccess session, ServerLevelData properties, ResourceKey registryKey, DimensionType dimensionType, ChunkProgressListener worldGenerationProgressListener, ChunkGenerator chunkGenerator, boolean debugWorld, long l, List list, boolean bl, CallbackInfo info) { + if (be_lastWorld != null && be_lastWorld.equals(session.getLevelId())) { + return; + } + + be_lastWorld = session.getLevelId(); + + ServerLevel world = ServerLevel.class.cast(this); + File dir = session.getDimensionPath(world.dimension()); + if (!new File(dir, "level.dat").exists()) { + dir = dir.getParentFile(); + } + + DataFixerAPI.fixData(dir); + WorldDataAPI.load(dir); + } +} diff --git a/src/main/resources/bclib.mixins.common.json b/src/main/resources/bclib.mixins.common.json index e90d188a..6dd95e3b 100644 --- a/src/main/resources/bclib.mixins.common.json +++ b/src/main/resources/bclib.mixins.common.json @@ -10,6 +10,7 @@ "EnchantmentMenuMixin", "RecipeManagerMixin", "BoneMealItemMixin", + "ServerLevelMixin", "TagLoaderMixin" ], "injectors": {