- ) tag;
- }
-
/**
* Adds {@link Block} to NETHER_GROUND and GEN_TERRAIN tags to process it properly in terrain generators and block logic.
- *
* @param block - {@link Block}.
*/
public static void addNetherGround(Block block) {
- addTag(BLOCK_NETHER_GROUND, block);
- addTag(BLOCK_GEN_TERRAIN, block);
+ TagHelper.addTag(NETHER_GROUND, block);
+ TagHelper.addTag(GEN_TERRAIN, block);
}
/**
* Adds {@link Block} to END_GROUND and GEN_TERRAIN tags to process it properly in terrain generators and block logic.
- *
* @param block - {@link Block}.
*/
public static void addEndGround(Block block) {
- addTag(BLOCK_GEN_TERRAIN, block);
- addTag(BLOCK_END_GROUND, block);
+ TagHelper.addTag(GEN_TERRAIN, block);
+ TagHelper.addTag(END_GROUND, block);
}
/**
* Initializes basic tags. Should be called only in BCLib main class.
*/
public static void init() {
- addTag(BLOCK_BOOKSHELVES, Blocks.BOOKSHELF);
- addTag(BLOCK_GEN_TERRAIN, Blocks.END_STONE, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL);
- addTag(BLOCK_NETHER_GROUND, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL);
- addTag(BLOCK_END_GROUND, Blocks.END_STONE);
- addTag(BLOCK_CHEST, Blocks.CHEST);
- addTag(ITEM_CHEST, Items.CHEST);
- addTag(ITEM_IRON_INGOTS, Items.IRON_INGOT);
- addTag(ITEM_FURNACES, Blocks.FURNACE);
- }
-
- /**
- * Adds one Tag to multiple Blocks.
- *
- * Example:
- *
{@code Tag.Named DIMENSION_STONE = makeBlockTag("mymod", "dim_stone");
- * addTag(DIMENSION_STONE, Blocks.END_STONE, Blocks.NETHERRACK);}
- *
- * The call will reserve the Tag. The Tag is added to the blocks once
- * {@link #apply(String, Map)} was executed.
- *
- * @param tag The new Tag
- * @param blocks One or more blocks that should receive the Tag.
- */
- public static void addTag(Tag.Named tag, Block... blocks) {
- ResourceLocation tagID = tag.getName();
- Set set = TAGS_BLOCK.computeIfAbsent(tagID, k -> Sets.newHashSet());
- for (Block block : blocks) {
- ResourceLocation id = Registry.BLOCK.getKey(block);
- if (id != Registry.BLOCK.getDefaultKey()) {
- set.add(id);
- }
- }
- }
-
- /**
- * Adds one Tag to multiple Items.
- *
- * Example:
- *
{@code Tag.Named- METALS = makeBlockTag("mymod", "metals");
- * addTag(METALS, Items.IRON_INGOT, Items.GOLD_INGOT, Items.COPPER_INGOT);}
- *
- * The call will reserve the Tag. The Tag is added to the items once
- * {@link #apply(String, Map)} was executed.
- *
- * @param tag The new Tag
- * @param items One or more item that should receive the Tag.
- */
- public static void addTag(Tag.Named- tag, ItemLike... items) {
- ResourceLocation tagID = tag.getName();
- Set set = TAGS_ITEM.computeIfAbsent(tagID, k -> Sets.newHashSet());
- for (ItemLike item : items) {
- ResourceLocation id = Registry.ITEM.getKey(item.asItem());
- if (id != Registry.ITEM.getDefaultKey()) {
- set.add(id);
- }
- }
- }
-
- /**
- * Adds multiple Tags to one Item.
- *
- * The call will reserve the Tags. The Tags are added to the Item once
- * * {@link #apply(String, Map)} was executed.
- *
- * @param item The Item that will receive all Tags
- * @param tags One or more Tags
- */
- @SafeVarargs
- public static void addTags(ItemLike item, Tag.Named- ... tags) {
- for (Tag.Named
- tag : tags) {
- addTag(tag, item);
- }
- }
-
- /**
- * Adds multiple Tags to one Block.
- *
- * The call will reserve the Tags. The Tags are added to the Block once
- * * {@link #apply(String, Map)} was executed.
- *
- * @param block The Block that will receive all Tags
- * @param tags One or more Tags
- */
- @SafeVarargs
- public static void addTags(Block block, Tag.Named... tags) {
- for (Tag.Named tag : tags) {
- addTag(tag, block);
- }
- }
-
- /**
- * Adds all {@code ids} to the {@code builder}.
- *
- * @param builder
- * @param ids
- * @return The Builder passed as {@code builder}.
- */
- public static Tag.Builder apply(Tag.Builder builder, Set ids) {
- ids.forEach(value -> builder.addElement(value, "Better End Code"));
- return builder;
- }
-
- /**
- * Automatically called in {@link net.minecraft.tags.TagLoader#loadAndBuild(ResourceManager)}.
- *
- * In most cases there is no need to call this Method manually.
- *
- * @param directory The name of the Tag-directory. Should be either "tags/blocks" or
- * "tags/items".
- * @param tagsMap The map that will hold the registered Tags
- * @return The {@code tagsMap} Parameter.
- */
- public static Map apply(String directory, Map tagsMap) {
- Map> endTags = null;
- if ("tags/blocks".equals(directory)) {
- endTags = TAGS_BLOCK;
- }
- else if ("tags/items".equals(directory)) {
- endTags = TAGS_ITEM;
- }
- if (endTags != null) {
- endTags.forEach((id, ids) -> apply(tagsMap.computeIfAbsent(id, key -> Tag.Builder.tag()), ids));
- }
- return tagsMap;
+ TagHelper.addTag(BOOKSHELVES, Blocks.BOOKSHELF);
+ TagHelper.addTag(GEN_TERRAIN, Blocks.END_STONE, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL);
+ TagHelper.addTag(NETHER_GROUND, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL);
+ TagHelper.addTag(END_GROUND, Blocks.END_STONE);
+ TagHelper.addTag(BLOCK_CHEST, Blocks.CHEST);
+ TagHelper.addTag(ITEM_CHEST, Items.CHEST);
+ TagHelper.addTag(IRON_INGOTS, Items.IRON_INGOT);
+ TagHelper.addTag(FURNACES, Blocks.FURNACE);
}
}
diff --git a/src/main/java/ru/bclib/api/WorldDataAPI.java b/src/main/java/ru/bclib/api/WorldDataAPI.java
index b1794df7..6927c9ed 100644
--- a/src/main/java/ru/bclib/api/WorldDataAPI.java
+++ b/src/main/java/ru/bclib/api/WorldDataAPI.java
@@ -1,30 +1,20 @@
package ru.bclib.api;
-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.NbtIo;
-import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
-import ru.bclib.BCLib;
-import ru.bclib.api.datafixer.DataFixerAPI;
-import ru.bclib.util.ModUtil;
-
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.function.Consumer;
-/**
- * Mod-specifix data-storage for a world.
- *
- * This class provides the ability for mod to store persistent data inside a world. The Storage for the world is
- * currently initialized as part of the {@link DataFixerAPI} in {@link DataFixerAPI#fixData(LevelStorageAccess, boolean, Consumer)}
- * or {@link DataFixerAPI#initializeWorldData(File, boolean)}
- */
+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.NbtIo;
+import ru.bclib.BCLib;
+
public class WorldDataAPI {
private static final Map TAGS = Maps.newHashMap();
private static final List MODS = Lists.newArrayList();
@@ -32,43 +22,36 @@ public class WorldDataAPI {
public static void load(File dataDir) {
WorldDataAPI.dataDir = dataDir;
- MODS.stream()
- .parallel()
- .forEach(modID -> {
- File file = new File(dataDir, modID + ".nbt");
- CompoundTag root = new CompoundTag();
- if (file.exists()) {
- try {
- root = NbtIo.readCompressed(file);
- }
- catch (IOException e) {
- BCLib.LOGGER.error("World data loading failed", e);
- }
+ MODS.stream().parallel().forEach(modID -> {
+ File file = new File(dataDir, modID + ".nbt");
+ CompoundTag root = new CompoundTag();
+ TAGS.put(modID, root);
+ if (file.exists()) {
+ try {
+ root = NbtIo.readCompressed(file);
}
- else {
- Optional optional = FabricLoader.getInstance()
- .getModContainer(modID);
- if (optional.isPresent()) {
- ModContainer modContainer = optional.get();
- if (BCLib.isDevEnvironment()) {
- root.putString("version", "255.255.9999");
- }
- else {
- root.putString("version", modContainer.getMetadata()
- .getVersion()
- .toString());
- }
- saveFile(modID);
- }
+ catch (IOException e) {
+ BCLib.LOGGER.error("World data loading failed", e);
}
-
- TAGS.put(modID, root);
- });
+ }
+ else {
+ Optional optional = FabricLoader.getInstance().getModContainer(modID);
+ if (optional.isPresent()) {
+ ModContainer modContainer = optional.get();
+ if (BCLib.isDevEnvironment()) {
+ root.putString("version", "63.63.63");
+ }
+ else {
+ root.putString("version", modContainer.getMetadata().getVersion().toString());
+ }
+ saveFile(modID);
+ }
+ }
+ });
}
/**
* Register mod cache, world cache is located in world data folder.
- *
* @param modID - {@link String} modID.
*/
public static void registerModCache(String modID) {
@@ -77,7 +60,6 @@ public class WorldDataAPI {
/**
* Get root {@link CompoundTag} for mod cache in world data folder.
- *
* @param modID - {@link String} modID.
* @return {@link CompoundTag}
*/
@@ -92,14 +74,13 @@ public class WorldDataAPI {
/**
* 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) {
+ for (String part: parts) {
if (tag.contains(part)) {
tag = tag.getCompound(part);
}
@@ -114,14 +95,10 @@ public class WorldDataAPI {
/**
* Forces mod cache file to be saved.
- *
* @param modID {@link String} mod ID.
*/
public static void saveFile(String modID) {
try {
- if (!dataDir.exists()) {
- dataDir.mkdirs();
- }
NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt"));
}
catch (IOException e) {
@@ -131,7 +108,6 @@ public class WorldDataAPI {
/**
* Get stored mod version (only for mods with registered cache).
- *
* @return {@link String} mod version.
*/
public static String getModVersion(String modID) {
@@ -140,10 +116,9 @@ public class WorldDataAPI {
/**
* Get stored mod version as integer (only for mods with registered cache).
- *
* @return {@code int} mod version.
*/
public static int getIntModVersion(String modID) {
- return ModUtil.convertModVersion(getModVersion(modID));
+ return DataFixerAPI.getModVersion(getModVersion(modID));
}
}
diff --git a/src/main/java/ru/bclib/api/dataexchange/BaseDataHandler.java b/src/main/java/ru/bclib/api/dataexchange/BaseDataHandler.java
deleted file mode 100644
index 47ca87af..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/BaseDataHandler.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package ru.bclib.api.dataexchange;
-
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.fabricmc.fabric.api.networking.v1.PacketSender;
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.multiplayer.ClientPacketListener;
-import net.minecraft.network.FriendlyByteBuf;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.server.MinecraftServer;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.server.network.ServerGamePacketListenerImpl;
-import org.jetbrains.annotations.NotNull;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Objects;
-
-public abstract class BaseDataHandler {
- private final boolean originatesOnServer;
- @NotNull
- private final ResourceLocation identifier;
-
- protected BaseDataHandler(ResourceLocation identifier, boolean originatesOnServer) {
- this.originatesOnServer = originatesOnServer;
- this.identifier = identifier;
- }
-
- final public boolean getOriginatesOnServer() {
- return originatesOnServer;
- }
-
- final public ResourceLocation getIdentifier() {
- return identifier;
- }
-
- @Environment(EnvType.CLIENT)
- abstract void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender);
-
- private ServerPlayer lastMessageSender;
-
- void receiveFromClient(MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender) {
- lastMessageSender = player;
- }
-
- final protected boolean reply(BaseDataHandler message, MinecraftServer server) {
- if (lastMessageSender == null) return false;
- message.sendToClient(server, lastMessageSender);
- return true;
- }
-
- abstract void sendToClient(MinecraftServer server);
-
- abstract void sendToClient(MinecraftServer server, ServerPlayer player);
-
- @Environment(EnvType.CLIENT)
- abstract void sendToServer(Minecraft client);
-
- protected boolean isBlocking() { return false; }
-
- @Override
- public String toString() {
- return "BasDataHandler{" + "originatesOnServer=" + originatesOnServer + ", identifier=" + identifier + '}';
- }
-
- /**
- * Write a String to a buffer (Convenience Method)
- *
- * @param buf The buffer to write to
- * @param s The String you want to write
- */
- public static void writeString(FriendlyByteBuf buf, String s) {
- buf.writeByteArray(s.getBytes(StandardCharsets.UTF_8));
- }
-
- /**
- * Read a string from a buffer (Convenience Method)
- *
- * @param buf Thea buffer to read from
- * @return The received String
- */
- public static String readString(FriendlyByteBuf buf) {
- byte[] data = buf.readByteArray();
- return new String(data, StandardCharsets.UTF_8);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof BaseDataHandler)) return false;
- BaseDataHandler that = (BaseDataHandler) o;
- return originatesOnServer == that.originatesOnServer && identifier.equals(that.identifier);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(originatesOnServer, identifier);
- }
-}
-
diff --git a/src/main/java/ru/bclib/api/dataexchange/Connector.java b/src/main/java/ru/bclib/api/dataexchange/Connector.java
deleted file mode 100644
index 6f933489..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/Connector.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package ru.bclib.api.dataexchange;
-
-import ru.bclib.api.dataexchange.handler.DataExchange;
-
-import java.util.Set;
-
-abstract class Connector {
- protected final DataExchange api;
-
- Connector(DataExchange api) {
- this.api = api;
- }
- public abstract boolean onClient();
-
- protected Set getDescriptors(){
- return api.getDescriptors();
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/ConnectorClientside.java b/src/main/java/ru/bclib/api/dataexchange/ConnectorClientside.java
deleted file mode 100644
index 6e192b91..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/ConnectorClientside.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package ru.bclib.api.dataexchange;
-
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
-import net.fabricmc.fabric.api.networking.v1.PacketSender;
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.multiplayer.ClientPacketListener;
-import net.minecraft.network.FriendlyByteBuf;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.handler.DataExchange;
-
-/**
- * This is an internal class that handles a Clienetside players Connection to a Server
- */
-@Environment(EnvType.CLIENT)
-public class ConnectorClientside extends Connector {
- private Minecraft client;
- ConnectorClientside(DataExchange api) {
- super(api);
- this.client = null;
- }
-
-
- @Override
- public boolean onClient() {
- return true;
- }
-
- public void onPlayInit(ClientPacketListener handler, Minecraft client){
- if (this.client!=null && this.client != client){
- BCLib.LOGGER.warning("Client changed!");
- }
- this.client = client;
- for(DataHandlerDescriptor desc : getDescriptors()){
- ClientPlayNetworking.registerReceiver(desc.IDENTIFIER, (_client, _handler, _buf, _responseSender)->{
- receiveFromServer(desc, _client, _handler, _buf, _responseSender);
- });
- }
- }
-
- public void onPlayReady(ClientPacketListener handler, PacketSender sender, Minecraft client){
- for(DataHandlerDescriptor desc : getDescriptors()){
- if (desc.sendOnJoin){
- BaseDataHandler h = desc.JOIN_INSTANCE.get();
- if (!h.getOriginatesOnServer()) {
- h.sendToServer(client);
- }
- }
- }
- }
-
- public void onPlayDisconnect(ClientPacketListener handler, Minecraft client){
- for(DataHandlerDescriptor desc : getDescriptors()) {
- ClientPlayNetworking.unregisterReceiver(desc.IDENTIFIER);
- }
- }
-
- void receiveFromServer(DataHandlerDescriptor desc, Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender){
- BaseDataHandler h = desc.INSTANCE.get();
- h.receiveFromServer(client, handler, buf, responseSender);
- }
-
- public void sendToServer(BaseDataHandler h){
- if (client==null){
- throw new RuntimeException("[internal error] Client not initialized yet!");
- }
- h.sendToServer(this.client);
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/ConnectorServerside.java b/src/main/java/ru/bclib/api/dataexchange/ConnectorServerside.java
deleted file mode 100644
index f8debc99..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/ConnectorServerside.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package ru.bclib.api.dataexchange;
-
-import net.fabricmc.fabric.api.networking.v1.PacketSender;
-import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
-import net.minecraft.network.FriendlyByteBuf;
-import net.minecraft.server.MinecraftServer;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.server.network.ServerGamePacketListenerImpl;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.handler.DataExchange;
-
-/**
- * This is an internal class that handles a Serverside Connection to a Client-Player
- */
-public class ConnectorServerside extends Connector {
- private MinecraftServer server;
- ConnectorServerside(DataExchange api) {
- super(api);
- server = null;
- }
-
- @Override
- public boolean onClient() {
- return false;
- }
-
- public void onPlayInit(ServerGamePacketListenerImpl handler, MinecraftServer server){
- if (this.server!=null && this.server != server){
- BCLib.LOGGER.warning("Server changed!");
- }
- this.server = server;
- for(DataHandlerDescriptor desc : getDescriptors()){
- ServerPlayNetworking.registerReceiver(handler, desc.IDENTIFIER, (_server, _player, _handler, _buf, _responseSender) -> {
- receiveFromClient(desc, _server, _player, _handler, _buf, _responseSender);
- });
- }
- }
-
- public void onPlayReady(ServerGamePacketListenerImpl handler, PacketSender sender, MinecraftServer server){
- for(DataHandlerDescriptor desc : getDescriptors()){
- if (desc.sendOnJoin){
- BaseDataHandler h = desc.JOIN_INSTANCE.get();
- if (h.getOriginatesOnServer()) {
- h.sendToClient(server, handler.player);
- }
- }
- }
- }
-
- public void onPlayDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server){
- for(DataHandlerDescriptor desc : getDescriptors()){
- ServerPlayNetworking.unregisterReceiver(handler, desc.IDENTIFIER);
- }
- }
-
- void receiveFromClient(DataHandlerDescriptor desc, MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender){
- BaseDataHandler h = desc.INSTANCE.get();
- h.receiveFromClient(server, player, handler, buf, responseSender);
- }
-
- public void sendToClient(BaseDataHandler h){
- if (server==null){
- throw new RuntimeException("[internal error] Server not initialized yet!");
- }
- h.sendToClient(this.server);
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java b/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java
deleted file mode 100644
index 4a22c6db..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/DataExchangeAPI.java
+++ /dev/null
@@ -1,211 +0,0 @@
-package ru.bclib.api.dataexchange;
-
-import com.google.common.collect.Lists;
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.minecraft.network.FriendlyByteBuf;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.handler.DataExchange;
-import ru.bclib.api.dataexchange.handler.autosync.AutoSync;
-import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate;
-import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID;
-import ru.bclib.config.Config;
-import ru.bclib.util.ModUtil;
-
-import java.io.File;
-import java.util.List;
-import java.util.function.BiConsumer;
-
-public class DataExchangeAPI extends DataExchange {
- private final static List MODS = Lists.newArrayList();
-
- /**
- * You should never need to create a custom instance of this Object.
- */
- public DataExchangeAPI() {
- super();
- }
-
- @Environment(EnvType.CLIENT)
- protected ConnectorClientside clientSupplier(DataExchange api) {
- return new ConnectorClientside(api);
- }
-
- protected ConnectorServerside serverSupplier(DataExchange api) {
- return new ConnectorServerside(api);
- }
-
- /**
- * Register a mod to participate in the DataExchange.
- *
- * @param modID - {@link String} modID.
- */
- public static void registerMod(String modID) {
- if (!MODS.contains(modID)) MODS.add(modID);
- }
-
- /**
- * Register a mod dependency to participate in the DataExchange.
- *
- * @param modID - {@link String} modID.
- */
- public static void registerModDependency(String modID) {
- if (ModUtil.getModInfo(modID, false) != null && !"0.0.0".equals(ModUtil.getModVersion(modID))) {
- registerMod(modID);
- } else {
- BCLib.LOGGER.info("Mod Dependency '" + modID + "' not found. This is probably OK.");
- }
- }
-
- /**
- * Returns the IDs of all registered Mods.
- *
- * @return List of modIDs
- */
- public static List registeredMods() {
- return MODS;
- }
-
- /**
- * Add a new Descriptor for a {@link DataHandler}.
- *
- * @param desc The Descriptor you want to add.
- */
- public static void registerDescriptor(DataHandlerDescriptor desc) {
- DataExchange api = DataExchange.getInstance();
- api.getDescriptors()
- .add(desc);
- }
-
- /**
- * Bulk-Add a Descriptors for your {@link DataHandler}-Objects.
- *
- * @param desc The Descriptors you want to add.
- */
- public static void registerDescriptors(List desc) {
- DataExchange api = DataExchange.getInstance();
- api.getDescriptors()
- .addAll(desc);
- }
-
- /**
- * Sends the Handler.
- *
- * Depending on what the result of {@link DataHandler#getOriginatesOnServer()}, the Data is sent from the server
- * to the client (if {@code true}) or the other way around.
- *
- * The method {@link DataHandler#serializeData(FriendlyByteBuf, boolean)} is called just before the data is sent. You should
- * use this method to add the Data you need to the communication.
- *
- * @param h The Data that you want to send
- */
- public static void send(BaseDataHandler h) {
- if (h.getOriginatesOnServer()) {
- DataExchangeAPI.getInstance().server.sendToClient(h);
- }
- else {
- DataExchangeAPI.getInstance().client.sendToServer(h);
- }
- }
-
- /**
- * Registers a File for automatic client syncing.
- *
- * @param modID The ID of the calling Mod
- * @param fileName The name of the File
- */
- public static void addAutoSyncFile(String modID, File fileName) {
- AutoSync.addAutoSyncFileData(modID, fileName, false, SyncFileHash.NEED_TRANSFER);
- }
-
- /**
- * Registers a File for automatic client syncing.
- *
- * The file is synced of the {@link SyncFileHash} on client and server are not equal. This method will not copy the
- * configs content from the client to the server.
- *
- * @param modID The ID of the calling Mod
- * @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
- * Details
- * @param fileName The name of the File
- */
- public static void addAutoSyncFile(String modID, String uniqueID, File fileName) {
- AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, false, SyncFileHash.NEED_TRANSFER);
- }
-
- /**
- * Registers a File for automatic client syncing.
- *
- * The content of the file is requested for comparison. This will copy the
- * entire file from the client to the server.
- *
- * You should only use this option, if you need to compare parts of the file in order to decide
- * if the File needs to be copied. Normally using the {@link SyncFileHash}
- * for comparison is sufficient.
- *
- * @param modID The ID of the calling Mod
- * @param fileName The name of the File
- * @param needTransfer If the predicate returns true, the file needs to get copied to the server.
- */
- public static void addAutoSyncFile(String modID, File fileName, NeedTransferPredicate needTransfer) {
- AutoSync.addAutoSyncFileData(modID, fileName, true, needTransfer);
- }
-
- /**
- * Registers a File for automatic client syncing.
- *
- * The content of the file is requested for comparison. This will copy the
- * entire file from the client to the server.
- *
- * You should only use this option, if you need to compare parts of the file in order to decide
- * if the File needs to be copied. Normally using the {@link SyncFileHash}
- * for comparison is sufficient.
- *
- * @param modID The ID of the calling Mod
- * @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
- * Details
- * @param fileName The name of the File
- * @param needTransfer If the predicate returns true, the file needs to get copied to the server.
- */
- public static void addAutoSyncFile(String modID, String uniqueID, File fileName, NeedTransferPredicate needTransfer) {
- AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, true, needTransfer);
- }
-
- /**
- * Register a function that is called whenever the client receives a file from the server and replaced toe local
- * file with the new content.
- *
- * This callback is usefull if you need to reload the new content before the game is quit.
- *
- * @param callback A Function that receives the AutoSyncID as well as the Filename.
- */
- public static void addOnWriteCallback(BiConsumer callback) {
- AutoSync.addOnWriteCallback(callback);
- }
-
- /**
- * Returns the sync-folder for a given Mod.
- *
- * BCLib will ensure that the contents of sync-folder on the client is the same as the one on the server.
- *
- * @param modID ID of the Mod
- * @return The path to the sync-folder
- */
- public static File getModSyncFolder(String modID) {
- File fl = AutoSync.SYNC_FOLDER.localFolder.resolve(modID.replace(".", "-")
- .replace(":", "-")
- .replace("\\", "-")
- .replace("/", "-"))
- .normalize()
- .toFile();
-
- if (!fl.exists()) {
- fl.mkdirs();
- }
- return fl;
- }
-
- static {
- addOnWriteCallback(Config::reloadSyncedConfig);
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/DataHandler.java b/src/main/java/ru/bclib/api/dataexchange/DataHandler.java
deleted file mode 100644
index b1043fc3..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/DataHandler.java
+++ /dev/null
@@ -1,274 +0,0 @@
-package ru.bclib.api.dataexchange;
-
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
-import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
-import net.fabricmc.fabric.api.networking.v1.PacketSender;
-import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
-import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.multiplayer.ClientPacketListener;
-import net.minecraft.network.FriendlyByteBuf;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.server.MinecraftServer;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.server.network.ServerGamePacketListenerImpl;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.handler.autosync.Chunker;
-import ru.bclib.api.dataexchange.handler.autosync.Chunker.PacketChunkSender;
-
-import java.util.Collection;
-import java.util.List;
-
-public abstract class DataHandler extends BaseDataHandler {
- public abstract static class WithoutPayload extends DataHandler {
- protected WithoutPayload(ResourceLocation identifier, boolean originatesOnServer) {
- super(identifier, originatesOnServer);
- }
-
- @Override
- protected boolean prepareData(boolean isClient) { return true; }
-
- @Override
- protected void serializeData(FriendlyByteBuf buf, boolean isClient) {
- }
-
- @Override
- protected void deserializeIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient) {
- }
- }
-
- protected DataHandler(ResourceLocation identifier, boolean originatesOnServer) {
- super(identifier, originatesOnServer);
- }
-
- protected boolean prepareData(boolean isClient) { return true; }
-
- abstract protected void serializeData(FriendlyByteBuf buf, boolean isClient);
-
- abstract protected void deserializeIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient);
-
- abstract protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient);
-
-
- @Environment(EnvType.CLIENT)
- @Override
- void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender) {
- deserializeIncomingData(buf, responseSender, true);
- final Runnable runner = () -> runOnGameThread(client, null, true);
-
- if (isBlocking()) client.executeBlocking(runner);
- else client.execute(runner);
- }
-
- @Override
- void receiveFromClient(MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender) {
- super.receiveFromClient(server, player, handler, buf, responseSender);
-
- deserializeIncomingData(buf, responseSender, false);
- final Runnable runner = () -> runOnGameThread(null, server, false);
-
- if (isBlocking()) server.executeBlocking(runner);
- else server.execute(runner);
- }
-
- @Override
- void sendToClient(MinecraftServer server) {
- if (prepareData(false)) {
- FriendlyByteBuf buf = PacketByteBufs.create();
- serializeData(buf, false);
-
- _sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
- }
- }
-
- @Override
- void sendToClient(MinecraftServer server, ServerPlayer player) {
- if (prepareData(false)) {
- FriendlyByteBuf buf = PacketByteBufs.create();
- serializeData(buf, false);
-
- _sendToClient(getIdentifier(), server, List.of(player), buf);
- }
- }
-
-
- public static void _sendToClient(ResourceLocation identifier, MinecraftServer server, Collection players, FriendlyByteBuf buf) {
- if (buf.readableBytes()> Chunker.MAX_PACKET_SIZE) {
- final PacketChunkSender sender = new PacketChunkSender(buf, identifier);
- sender.sendChunks(players);
- } else {
- for (ServerPlayer player : players) {
- ServerPlayNetworking.send(player, identifier, buf);
- }
- }
- }
-
- @Environment(EnvType.CLIENT)
- @Override
- void sendToServer(Minecraft client) {
- if (prepareData(true)) {
- FriendlyByteBuf buf = PacketByteBufs.create();
- serializeData(buf, true);
- ClientPlayNetworking.send(getIdentifier(), buf);
- }
- }
-
- /**
- * A Message that always originates on the Client
- */
- public abstract static class FromClient extends BaseDataHandler {
- public abstract static class WithoutPayload extends FromClient {
- protected WithoutPayload(ResourceLocation identifier) {
- super(identifier);
- }
-
- @Override
- protected boolean prepareDataOnClient() { return true; }
-
- @Override
- protected void serializeDataOnClient(FriendlyByteBuf buf) {
- }
-
- @Override
- protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, PacketSender responseSender) {
- }
- }
-
- protected FromClient(ResourceLocation identifier) {
- super(identifier, false);
- }
-
- @Environment(EnvType.CLIENT)
- protected boolean prepareDataOnClient() { return true; }
-
- @Environment(EnvType.CLIENT)
- abstract protected void serializeDataOnClient(FriendlyByteBuf buf);
-
- abstract protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, PacketSender responseSender);
-
- abstract protected void runOnServerGameThread(MinecraftServer server);
-
-
- @Environment(EnvType.CLIENT)
- @Override
- void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender) {
- BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
- }
-
- @Override
- void receiveFromClient(MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender) {
- super.receiveFromClient(server, player, handler, buf, responseSender);
-
- deserializeIncomingDataOnServer(buf, responseSender);
- final Runnable runner = () -> runOnServerGameThread(server);
-
- if (isBlocking()) server.executeBlocking(runner);
- else server.execute(runner);
- }
-
- @Override
- void sendToClient(MinecraftServer server) {
- BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
- }
-
- @Override
- void sendToClient(MinecraftServer server, ServerPlayer player) {
- BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
- }
-
- @Environment(EnvType.CLIENT)
- @Override
- void sendToServer(Minecraft client) {
- if (prepareDataOnClient()) {
- FriendlyByteBuf buf = PacketByteBufs.create();
- serializeDataOnClient(buf);
- ClientPlayNetworking.send(getIdentifier(), buf);
- }
- }
- }
-
- /**
- * A Message that always originates on the Server
- */
- public abstract static class FromServer extends BaseDataHandler {
- public abstract static class WithoutPayload extends FromServer {
- protected WithoutPayload(ResourceLocation identifier) {
- super(identifier);
- }
-
- @Override
- protected boolean prepareDataOnServer() { return true; }
-
- @Override
- protected void serializeDataOnServer(FriendlyByteBuf buf) {
- }
-
- @Override
- protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
- }
- }
-
- protected FromServer(ResourceLocation identifier) {
- super(identifier, true);
- }
-
- protected boolean prepareDataOnServer() { return true; }
-
- abstract protected void serializeDataOnServer(FriendlyByteBuf buf);
-
- @Environment(EnvType.CLIENT)
- abstract protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender);
-
- @Environment(EnvType.CLIENT)
- abstract protected void runOnClientGameThread(Minecraft client);
-
-
- @Environment(EnvType.CLIENT)
- @Override
- final void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender) {
- deserializeIncomingDataOnClient(buf, responseSender);
- final Runnable runner = () -> runOnClientGameThread(client);
-
- if (isBlocking()) client.executeBlocking(runner);
- else client.execute(runner);
- }
-
- @Override
- final void receiveFromClient(MinecraftServer server, ServerPlayer player, ServerGamePacketListenerImpl handler, FriendlyByteBuf buf, PacketSender responseSender) {
- super.receiveFromClient(server, player, handler, buf, responseSender);
- BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
- }
-
- public void receiveFromMemory(FriendlyByteBuf buf){
- receiveFromServer(Minecraft.getInstance(), null, buf, null);
- }
-
- @Override
- final void sendToClient(MinecraftServer server) {
- if (prepareDataOnServer()) {
- FriendlyByteBuf buf = PacketByteBufs.create();
- serializeDataOnServer(buf);
-
- _sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
- }
- }
-
- @Override
- final void sendToClient(MinecraftServer server, ServerPlayer player) {
- if (prepareDataOnServer()) {
- FriendlyByteBuf buf = PacketByteBufs.create();
- serializeDataOnServer(buf);
-
- _sendToClient(getIdentifier(), server, List.of(player), buf);
- }
- }
-
- @Environment(EnvType.CLIENT)
- @Override
- final void sendToServer(Minecraft client) {
- BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
- }
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/DataHandlerDescriptor.java b/src/main/java/ru/bclib/api/dataexchange/DataHandlerDescriptor.java
deleted file mode 100644
index 2f9f80e5..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/DataHandlerDescriptor.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package ru.bclib.api.dataexchange;
-
-import net.minecraft.resources.ResourceLocation;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-import java.util.function.Supplier;
-
-public class DataHandlerDescriptor {
- public DataHandlerDescriptor(@NotNull ResourceLocation identifier, @NotNull Supplier instancer){
- this(identifier, instancer, instancer, false, false);
- }
-
- public DataHandlerDescriptor(@NotNull ResourceLocation identifier,@NotNull Supplier instancer, boolean sendOnJoin, boolean sendBeforeEnter){
- this(identifier, instancer, instancer, sendOnJoin, sendBeforeEnter);
- }
- public DataHandlerDescriptor(@NotNull ResourceLocation identifier, @NotNull Supplier receiv_instancer, @NotNull Supplier join_instancer, boolean sendOnJoin, boolean sendBeforeEnter){
- this.INSTANCE = receiv_instancer;
- this.JOIN_INSTANCE = join_instancer;
- this.IDENTIFIER = identifier;
-
- this.sendOnJoin = sendOnJoin;
- this.sendBeforeEnter = sendBeforeEnter;
- }
-
- public final boolean sendOnJoin;
- public final boolean sendBeforeEnter;
- @NotNull
- public final ResourceLocation IDENTIFIER;
- @NotNull
- public final Supplier INSTANCE;
- @NotNull
- public final Supplier JOIN_INSTANCE;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o instanceof ResourceLocation){
- return o.equals(IDENTIFIER);
- }
- if (!(o instanceof DataHandlerDescriptor that)) return false;
- return IDENTIFIER.equals(that.IDENTIFIER);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(IDENTIFIER);
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/FileHash.java b/src/main/java/ru/bclib/api/dataexchange/FileHash.java
deleted file mode 100644
index de87f686..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/FileHash.java
+++ /dev/null
@@ -1,161 +0,0 @@
-package ru.bclib.api.dataexchange;
-
-import net.minecraft.network.FriendlyByteBuf;
-import org.jetbrains.annotations.NotNull;
-import ru.bclib.BCLib;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import java.util.Objects;
-
-public class FileHash {
- private static int ERR_DOES_NOT_EXIST = -10;
- private static int ERR_IO_ERROR = -20;
-
- /**
- * The md5-hash of the file
- */
- @NotNull
- public final byte[] md5;
-
- /**
- * The size (in bytes) of the input.
- */
- public final int size;
-
- /**
- * a value that is directly calculated from defined byte positions.
- */
- public final int value;
-
- FileHash(byte[] md5, int size, int value) {
- Objects.nonNull(md5);
-
- this.md5 = md5;
- this.size = size;
- this.value = value;
- }
-
- static FileHash createForEmpty(int errCode) {
- return new FileHash(new byte[0], 0, errCode);
- }
-
- public boolean noFile() {
- return md5.length == 0;
- }
-
- /**
- * Serializes the Object to a buffer
- *
- * @param buf The buffer to write to
- */
- public void serialize(FriendlyByteBuf buf) {
- buf.writeInt(size);
- buf.writeInt(value);
- buf.writeByteArray(md5);
- }
-
- /**
- * Deserialize a Buffer to a new {@link SyncFileHash}-Object
- *
- * @param buf Thea buffer to read from
- * @return The received String
- */
- public static FileHash deserialize(FriendlyByteBuf buf) {
- final int size = buf.readInt();
- final int value = buf.readInt();
- final byte[] md5 = buf.readByteArray();
-
- return new FileHash(md5, size, value);
- }
-
- /**
- * Convert the md5-hash to a human readable string
- *
- * @return The converted String
- */
- public String getMd5String() {
- return toHexString(md5);
- }
-
- /**
- * Converts a byte-array to a hex-string representation
- *
- * @param bytes The source array
- * @return The resulting string, or an empty String if the input was {@code null}
- */
- public static String toHexString(byte[] bytes) {
- if (bytes == null) return "";
-
- StringBuilder sb = new StringBuilder();
- for (byte b : bytes) {
- sb.append(String.format("%02x", b));
- }
- return sb.toString();
- }
-
- /**
- * Create a new {@link FileHash}.
- *
- * @param file The input file
- * @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
- * identical. Will return {@code null} when an error occurs or the File does not exist
- */
- public static FileHash create(File file) {
- if (!file.exists()) return createForEmpty(ERR_DOES_NOT_EXIST);
- final Path path = file.toPath();
-
- int size = 0;
- byte[] md5 = new byte[0];
- int value = 0;
-
- try {
- byte[] data = Files.readAllBytes(path);
-
- size = data.length;
-
- value = size > 0 ? (data[size / 3] | (data[size / 2] << 8) | (data[size / 5] << 16)) : -1;
- if (size > 20) value |= data[20] << 24;
-
- MessageDigest md = MessageDigest.getInstance("MD5");
- md.update(data);
- md5 = md.digest();
-
- return new FileHash(md5, size, value);
- }
- catch (IOException e) {
- BCLib.LOGGER.error("Failed to read file: " + file);
- return null;
- }
- catch (NoSuchAlgorithmException e) {
- BCLib.LOGGER.error("Unable to build hash for file: " + file);
- }
-
- return createForEmpty(ERR_IO_ERROR);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof FileHash)) return false;
- FileHash fileHash = (FileHash) o;
- return size == fileHash.size && value == fileHash.value && Arrays.equals(md5, fileHash.md5);
- }
-
- @Override
- public int hashCode() {
- int result = Objects.hash(size, value);
- result = 31 * result + Arrays.hashCode(md5);
- return result;
- }
-
- @Override
- public String toString() {
- return String.format("%08x", size) + "-" + String.format("%08x", value) + "-" + getMd5String();
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/SyncFileHash.java b/src/main/java/ru/bclib/api/dataexchange/SyncFileHash.java
deleted file mode 100644
index 81a3c853..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/SyncFileHash.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package ru.bclib.api.dataexchange;
-
-import net.minecraft.network.FriendlyByteBuf;
-import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate;
-import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID;
-
-import java.io.File;
-import java.util.Objects;
-
-/**
- * Calculates a hash based on the contents of a File.
- *
- * A File-Hash contains the md5-sum of the File, as well as its size and byte-values from defined positions
- *
- * You can compare instances using {@link #equals(Object)} to determine if two files are
- * identical.
- */
-public class SyncFileHash extends AutoSyncID {
- public final FileHash hash;
-
- SyncFileHash(String modID, File file, byte[] md5, int size, int value) {
- this(modID, file.getName(), md5, size, value);
- }
-
- SyncFileHash(String modID, String uniqueID, byte[] md5, int size, int value) {
- this(modID, uniqueID, new FileHash(md5, size, value));
- }
-
- SyncFileHash(String modID, File file, FileHash hash) {
- this(modID, file.getName(), hash);
- }
-
- SyncFileHash(String modID, String uniqueID, FileHash hash) {
- super(modID, uniqueID);
- this.hash = hash;
- }
-
-
- final static NeedTransferPredicate NEED_TRANSFER = (clientHash, serverHash, content)-> !clientHash.equals(serverHash);
-
- @Override
- public String toString() {
- return super.toString()+": "+hash.toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof SyncFileHash)) return false;
- if (!super.equals(o)) return false;
- SyncFileHash that = (SyncFileHash) o;
- return hash.equals(that.hash);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(super.hashCode(), hash);
- }
-
- /**
- * Serializes the Object to a buffer
- * @param buf The buffer to write to
- */
- public void serialize(FriendlyByteBuf buf) {
- hash.serialize(buf);
- DataHandler.writeString(buf, modID);
- DataHandler.writeString(buf, uniqueID);
- }
-
- /**
- *Deserialize a Buffer to a new {@link SyncFileHash}-Object
- * @param buf Thea buffer to read from
- * @return The received String
- */
- public static SyncFileHash deserialize(FriendlyByteBuf buf){
- final FileHash hash = FileHash.deserialize(buf);
- final String modID = DataHandler.readString(buf);
- final String uniqueID = DataHandler.readString(buf);
-
- return new SyncFileHash(modID, uniqueID, hash);
- }
-
- /**
- * Create a new {@link SyncFileHash}.
- *
- * Will call {@link #create(String, File, String)} using the name of the File as {@code uniqueID}.
- * @param modID ID of the calling Mod
- * @param file The input file
- *
- * @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
- * identical. Will return {@code null} when an error occurs or the File does not exist
- */
- public static SyncFileHash create(String modID, File file){
- return create(modID, file, file.getName());
- }
-
- /**
- * Create a new {@link SyncFileHash}.
- * @param modID ID of the calling Mod
- * @param file The input file
- * @param uniqueID The unique ID that is used for this File (see {@link SyncFileHash#uniqueID} for Details.
- * @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
- * identical. Will return {@code null} when an error occurs or the File does not exist
- */
- public static SyncFileHash create(String modID, File file, String uniqueID){
- return new SyncFileHash(modID, uniqueID, FileHash.create(file));
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java b/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java
deleted file mode 100644
index a11ddb6d..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/handler/DataExchange.java
+++ /dev/null
@@ -1,113 +0,0 @@
-package ru.bclib.api.dataexchange.handler;
-
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
-import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
-import net.minecraft.resources.ResourceLocation;
-import ru.bclib.api.dataexchange.BaseDataHandler;
-import ru.bclib.api.dataexchange.ConnectorClientside;
-import ru.bclib.api.dataexchange.ConnectorServerside;
-import ru.bclib.api.dataexchange.DataExchangeAPI;
-import ru.bclib.api.dataexchange.DataHandler;
-import ru.bclib.api.dataexchange.DataHandlerDescriptor;
-
-import java.util.HashSet;
-import java.util.Set;
-
-abstract public class DataExchange {
-
-
- private static DataExchangeAPI instance;
-
- protected static DataExchangeAPI getInstance() {
- if (instance == null) {
- instance = new DataExchangeAPI();
- }
- return instance;
- }
-
- protected ConnectorServerside server;
- protected ConnectorClientside client;
- protected final Set descriptors;
-
-
- private boolean didLoadSyncFolder = false;
-
- abstract protected ConnectorClientside clientSupplier(DataExchange api);
-
- abstract protected ConnectorServerside serverSupplier(DataExchange api);
-
- protected DataExchange() {
- descriptors = new HashSet<>();
- }
-
- public Set getDescriptors() { return descriptors; }
-
- public static DataHandlerDescriptor getDescriptor(ResourceLocation identifier){
- return getInstance().descriptors.stream().filter(d -> d.equals(identifier)).findFirst().orElse(null);
- }
-
- @Environment(EnvType.CLIENT)
- protected void initClientside() {
- if (client != null) return;
- client = clientSupplier(this);
-
- ClientPlayConnectionEvents.INIT.register(client::onPlayInit);
- ClientPlayConnectionEvents.JOIN.register(client::onPlayReady);
- ClientPlayConnectionEvents.DISCONNECT.register(client::onPlayDisconnect);
- }
-
- protected void initServerSide() {
- if (server != null) return;
- server = serverSupplier(this);
-
- ServerPlayConnectionEvents.INIT.register(server::onPlayInit);
- ServerPlayConnectionEvents.JOIN.register(server::onPlayReady);
- ServerPlayConnectionEvents.DISCONNECT.register(server::onPlayDisconnect);
- }
-
- /**
- * Initializes all datastructures that need to exist in the client component.
- *
- * This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
- */
- @Environment(EnvType.CLIENT)
- public static void prepareClientside() {
- DataExchange api = DataExchange.getInstance();
- api.initClientside();
-
- }
-
- /**
- * Initializes all datastructures that need to exist in the server component.
- *
- * This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
- */
- public static void prepareServerside() {
- DataExchange api = DataExchange.getInstance();
- api.initServerSide();
- }
-
-
- /**
- * Automatically called before the player enters the world.
- *
- * This is automatically called by BCLib. It will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to*
- * {@code true},
- */
- @Environment(EnvType.CLIENT)
- public static void sendOnEnter() {
- getInstance().descriptors.forEach((desc) -> {
- if (desc.sendBeforeEnter) {
- BaseDataHandler h = desc.JOIN_INSTANCE.get();
- if (!h.getOriginatesOnServer()) {
- getInstance().client.sendToServer(h);
- }
- }
- });
- }
-
-
-
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoFileSyncEntry.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoFileSyncEntry.java
deleted file mode 100644
index 83e4ff0b..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoFileSyncEntry.java
+++ /dev/null
@@ -1,237 +0,0 @@
-package ru.bclib.api.dataexchange.handler.autosync;
-
-import net.minecraft.network.FriendlyByteBuf;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.DataHandler;
-import ru.bclib.api.dataexchange.SyncFileHash;
-import ru.bclib.api.dataexchange.handler.autosync.AutoSync.NeedTransferPredicate;
-import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile;
-import ru.bclib.util.ModUtil;
-import ru.bclib.util.ModUtil.ModInfo;
-import ru.bclib.util.Pair;
-import ru.bclib.util.PathUtil;
-import ru.bclib.util.Triple;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-class AutoFileSyncEntry extends AutoSyncID {
- static class ForDirectFileRequest extends AutoFileSyncEntry {
- final File relFile;
-
- ForDirectFileRequest(String syncID, File relFile, File absFile) {
- super(AutoSyncID.ForDirectFileRequest.MOD_ID, syncID, absFile, false, (a, b, c) -> false);
- this.relFile = relFile;
- }
-
- @Override
- public int serializeContent(FriendlyByteBuf buf) {
- int res = super.serializeContent(buf);
- DataHandler.writeString(buf, relFile.toString());
-
- return res;
- }
-
- static AutoFileSyncEntry.ForDirectFileRequest finishDeserializeContent(String syncID, FriendlyByteBuf buf) {
- final String relFile = DataHandler.readString(buf);
- SyncFolderDescriptor desc = AutoSync.getSyncFolderDescriptor(syncID);
- if (desc != null) {
- //ensures that the file is not above the base-folder
- if (desc.acceptChildElements(desc.mapAbsolute(relFile))) {
- return new AutoFileSyncEntry.ForDirectFileRequest(syncID, new File(relFile), desc.localFolder.resolve(relFile)
- .normalize()
- .toFile());
- }
- }
- return null;
- }
-
- @Override
- public String toString() {
- return uniqueID + " - " + relFile;
- }
- }
-
- static class ForModFileRequest extends AutoFileSyncEntry {
- public static File getLocalPathForID(String modID, boolean matchLocalVersion){
- ModInfo mi = ModUtil.getModInfo(modID, matchLocalVersion);
- if (mi!=null){
- return mi.jarPath.toFile();
- }
- return null;
- }
-
- public final String version;
- ForModFileRequest(String modID, boolean matchLocalVersion, String version) {
- super(modID, AutoSyncID.ForModFileRequest.UNIQUE_ID, getLocalPathForID(modID, matchLocalVersion), false, (a, b, c) -> false);
- if (this.fileName == null && matchLocalVersion){
- BCLib.LOGGER.error("Unknown mod '"+modID+"'.");
- }
- if (version==null)
- this.version = ModUtil.getModVersion(modID);
- else
- this.version = version;
- }
-
- @Override
- public int serializeContent(FriendlyByteBuf buf) {
- final int res = super.serializeContent(buf);
- buf.writeInt(ModUtil.convertModVersion(version));
- return res;
- }
-
- static AutoFileSyncEntry.ForModFileRequest finishDeserializeContent(String modID, FriendlyByteBuf buf) {
- final String version = ModUtil.convertModVersion(buf.readInt());
- return new AutoFileSyncEntry.ForModFileRequest(modID, false, version);
- }
-
- @Override
- public String toString() {
- return "Mod " + modID + " (v" + version + ")";
- }
- }
-
- public final NeedTransferPredicate needTransfer;
- public final File fileName;
- public final boolean requestContent;
- private SyncFileHash hash;
-
- AutoFileSyncEntry(String modID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) {
- this(modID, fileName.getName(), fileName, requestContent, needTransfer);
- }
-
- AutoFileSyncEntry(String modID, String uniqueID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) {
- super(modID, uniqueID);
- this.needTransfer = needTransfer;
- this.fileName = fileName;
- this.requestContent = requestContent;
- }
-
-
- public SyncFileHash getFileHash() {
- if (hash == null) {
- hash = SyncFileHash.create(modID, fileName, uniqueID);
- }
- return hash;
- }
-
- public byte[] getContent() {
- if (!fileName.exists()) return new byte[0];
- final Path path = fileName.toPath();
-
- try {
- return Files.readAllBytes(path);
- }
- catch (IOException e) {
-
- }
- return new byte[0];
- }
-
- public int serializeContent(FriendlyByteBuf buf) {
- DataHandler.writeString(buf, modID);
- DataHandler.writeString(buf, uniqueID);
- return serializeFileContent(buf);
- }
-
- public static Triple deserializeContent(FriendlyByteBuf buf) {
- final String modID = DataHandler.readString(buf);
- final String uniqueID = DataHandler.readString(buf);
- byte[] data = deserializeFileContent(buf);
-
- AutoFileSyncEntry entry;
- if (AutoSyncID.ForDirectFileRequest.MOD_ID.equals(modID)) {
- entry = AutoFileSyncEntry.ForDirectFileRequest.finishDeserializeContent(uniqueID, buf);
- }
- else if (AutoSyncID.ForModFileRequest.UNIQUE_ID.equals(uniqueID)) {
- entry = AutoFileSyncEntry.ForModFileRequest.finishDeserializeContent(modID, buf);
- }
- else {
- entry = AutoFileSyncEntry.findMatching(modID, uniqueID);
- }
- return new Triple<>(entry, data, new AutoSyncID(modID, uniqueID));
- }
-
-
- public void serialize(FriendlyByteBuf buf) {
- getFileHash().serialize(buf);
- buf.writeBoolean(requestContent);
-
- if (requestContent) {
- serializeFileContent(buf);
- }
- }
-
- public static AutoSync.AutoSyncTriple deserializeAndMatch(FriendlyByteBuf buf) {
- Pair e = deserialize(buf);
- AutoFileSyncEntry match = findMatching(e.first);
- return new AutoSync.AutoSyncTriple(e.first, e.second, match);
- }
-
- public static Pair deserialize(FriendlyByteBuf buf) {
- SyncFileHash hash = SyncFileHash.deserialize(buf);
- boolean withContent = buf.readBoolean();
- byte[] data = null;
- if (withContent) {
- data = deserializeFileContent(buf);
- }
-
- return new Pair(hash, data);
- }
-
- private int serializeFileContent(FriendlyByteBuf buf) {
- if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())){
- BCLib.LOGGER.error(fileName + " is not within game folder " + PathUtil.GAME_FOLDER + ". Pretending it does not exist.");
- buf.writeInt(0);
- return 0;
- }
-
- byte[] content = getContent();
- buf.writeInt(content.length);
- buf.writeByteArray(content);
- return content.length;
- }
-
- private static byte[] deserializeFileContent(FriendlyByteBuf buf) {
- byte[] data;
- int size = buf.readInt();
- data = buf.readByteArray(size);
- return data;
- }
-
-
- public static AutoFileSyncEntry findMatching(SyncFileHash hash) {
- return findMatching(hash.modID, hash.uniqueID);
- }
-
- public static AutoFileSyncEntry findMatching(AutoSyncID aid) {
- if (aid instanceof AutoSyncID.ForDirectFileRequest) {
- AutoSyncID.ForDirectFileRequest freq = (AutoSyncID.ForDirectFileRequest) aid;
- SyncFolderDescriptor desc = AutoSync.getSyncFolderDescriptor(freq.uniqueID);
- if (desc != null) {
- SubFile subFile = desc.getLocalSubFile(freq.relFile.toString());
- if (subFile != null) {
- final File absPath = desc.localFolder.resolve(subFile.relPath)
- .normalize()
- .toFile();
- return new AutoFileSyncEntry.ForDirectFileRequest(freq.uniqueID, new File(subFile.relPath), absPath);
- }
- }
- return null;
- } else if (aid instanceof AutoSyncID.ForModFileRequest) {
- AutoSyncID.ForModFileRequest mreq = (AutoSyncID.ForModFileRequest) aid;
- return new AutoFileSyncEntry.ForModFileRequest(mreq.modID, true, null);
- }
- return findMatching(aid.modID, aid.uniqueID);
- }
-
- public static AutoFileSyncEntry findMatching(String modID, String uniqueID) {
- return AutoSync.getAutoSyncFiles()
- .stream()
- .filter(asf -> asf.modID.equals(modID) && asf.uniqueID.equals(uniqueID))
- .findFirst()
- .orElse(null);
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSync.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSync.java
deleted file mode 100644
index 89a33463..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSync.java
+++ /dev/null
@@ -1,187 +0,0 @@
-package ru.bclib.api.dataexchange.handler.autosync;
-
-import net.fabricmc.loader.api.FabricLoader;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.DataExchangeAPI;
-import ru.bclib.api.dataexchange.SyncFileHash;
-import ru.bclib.config.Configs;
-import ru.bclib.config.ServerConfig;
-import ru.bclib.util.PathUtil;
-
-import java.io.File;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.BiConsumer;
-
-public class AutoSync {
- public static final String SYNC_CATEGORY = "auto_sync";
- public final static SyncFolderDescriptor SYNC_FOLDER = new SyncFolderDescriptor("BCLIB-SYNC", FabricLoader.getInstance()
- .getGameDir()
- .resolve("bclib-sync")
- .normalize()
- .toAbsolutePath(), true);
-
- @FunctionalInterface
- public interface NeedTransferPredicate {
- public boolean test(SyncFileHash clientHash, SyncFileHash serverHash, FileContentWrapper content);
- }
-
- final static class AutoSyncTriple {
- public final SyncFileHash serverHash;
- public final byte[] serverContent;
- public final AutoFileSyncEntry localMatch;
-
- public AutoSyncTriple(SyncFileHash serverHash, byte[] serverContent, AutoFileSyncEntry localMatch) {
- this.serverHash = serverHash;
- this.serverContent = serverContent;
- this.localMatch = localMatch;
- }
-
- @Override
- public String toString() {
- return serverHash.modID + "." + serverHash.uniqueID;
- }
- }
-
-
- // ##### File Syncing
- protected final static List> onWriteCallbacks = new ArrayList<>(2);
- /**
- * Register a function that is called whenever the client receives a file from the server and replaced toe local
- * file with the new content.
- *
- * This callback is usefull if you need to reload the new content before the game is quit.
- *
- * @param callback A Function that receives the AutoSyncID as well as the Filename.
- */
- public static void addOnWriteCallback(BiConsumer callback) {
- onWriteCallbacks.add(callback);
- }
- private static final List autoSyncFiles = new ArrayList<>(4);
-
- public static List getAutoSyncFiles() {
- return autoSyncFiles;
- }
-
- /**
- * Registers a File for automatic client syncing.
- *
- * @param modID The ID of the calling Mod
- * @param needTransfer If the predicate returns true, the file needs to get copied to the server.
- * @param fileName The name of the File
- * @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
- * entire file from the client to the server.
- *
- * You should only use this option, if you need to compare parts of the file in order to decide
- * If the File needs to be copied. Normally using the {@link SyncFileHash}
- * for comparison is sufficient.
- */
- public static void addAutoSyncFileData(String modID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) {
- if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())){
- BCLib.LOGGER.error(fileName + " is outside of Game Folder " + PathUtil.GAME_FOLDER);
- } else {
- autoSyncFiles.add(new AutoFileSyncEntry(modID, fileName, requestContent, needTransfer));
- }
- }
-
- /**
- * Registers a File for automatic client syncing.
- *
- * @param modID The ID of the calling Mod
- * @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
- * Details
- * @param needTransfer If the predicate returns true, the file needs to get copied to the server.
- * @param fileName The name of the File
- * @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
- * entire file from the client to the server.
- *
- * You should only use this option, if you need to compare parts of the file in order to decide
- * If the File needs to be copied. Normally using the {@link SyncFileHash}
- * for comparison is sufficient.
- */
- public static void addAutoSyncFileData(String modID, String uniqueID, File fileName, boolean requestContent, NeedTransferPredicate needTransfer) {
- if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())){
- BCLib.LOGGER.error(fileName + " is outside of Game Folder " + PathUtil.GAME_FOLDER);
- } else {
- autoSyncFiles.add(new AutoFileSyncEntry(modID, uniqueID, fileName, requestContent, needTransfer));
- }
- }
-
- /**
- * Called when {@code SendFiles} received a File on the Client and wrote it to the FileSystem.
- *
- * This is the place where reload Code should go.
- *
- * @param aid The ID of the received File
- * @param file The location of the FIle on the client
- */
- static void didReceiveFile(AutoSyncID aid, File file) {
- onWriteCallbacks.forEach(fkt -> fkt.accept(aid, file));
- }
-
-
- // ##### Folder Syncing
- static final List syncFolderDescriptions = Arrays.asList(SYNC_FOLDER);
-
- private List syncFolderContent;
-
- protected List getSyncFolderContent() {
- if (syncFolderContent == null) {
- return new ArrayList<>(0);
- }
- return syncFolderContent;
- }
-
- private static boolean didRegisterAdditionalMods = false;
- //we call this from HelloClient on the Server to prepare transfer
- protected static void loadSyncFolder() {
- if (Configs.SERVER_CONFIG.isOfferingFiles()) {
- syncFolderDescriptions.forEach(desc -> desc.loadCache());
- }
-
- if (!didRegisterAdditionalMods && Configs.SERVER_CONFIG.isOfferingMods()){
- didRegisterAdditionalMods = true;
- List modIDs = Configs.SERVER_CONFIG.get(ServerConfig.ADDITIONAL_MODS);
- if (modIDs != null){
- modIDs.stream().forEach(modID -> DataExchangeAPI.registerModDependency(modID));
- }
- }
-
- }
-
- protected static SyncFolderDescriptor getSyncFolderDescriptor(String folderID) {
- return syncFolderDescriptions.stream()
- .filter(d -> d.equals(folderID))
- .findFirst()
- .orElse(null);
- }
-
- protected static Path localBasePathForFolderID(String folderID) {
- final SyncFolderDescriptor desc = getSyncFolderDescriptor(folderID);
- if (desc != null) {
- return desc.localFolder;
- }
- else {
- BCLib.LOGGER.warning("Unknown Sync-Folder ID '" + folderID + "'");
- return null;
- }
- }
-
- public static void registerSyncFolder(String folderID, Path localBaseFolder, boolean removeAdditionalFiles) {
- localBaseFolder = localBaseFolder.normalize();
- if (PathUtil.isChildOf(PathUtil.GAME_FOLDER, localBaseFolder)) {
- final SyncFolderDescriptor desc = new SyncFolderDescriptor(folderID, localBaseFolder, removeAdditionalFiles);
- if (syncFolderDescriptions.contains(desc)) {
- BCLib.LOGGER.warning("Tried to override Folder Sync '" + folderID + "' again.");
- }
- else {
- syncFolderDescriptions.add(desc);
- }
- }
- else {
- BCLib.LOGGER.error(localBaseFolder + " (from " + folderID + ") is outside the game directory " + PathUtil.GAME_FOLDER + ". Sync is not allowed.");
- }
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSyncID.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSyncID.java
deleted file mode 100644
index 9ffbc64f..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/AutoSyncID.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package ru.bclib.api.dataexchange.handler.autosync;
-
-import net.minecraft.network.FriendlyByteBuf;
-import org.jetbrains.annotations.NotNull;
-import ru.bclib.api.dataexchange.DataHandler;
-import ru.bclib.config.Config;
-import ru.bclib.util.ModUtil;
-
-import java.io.File;
-import java.util.Objects;
-
-public class AutoSyncID {
- static class WithContentOverride extends AutoSyncID {
- final FileContentWrapper contentWrapper;
- final File localFile;
-
- WithContentOverride(String modID, String uniqueID, FileContentWrapper contentWrapper, File localFile) {
- super(modID, uniqueID);
- this.contentWrapper = contentWrapper;
- this.localFile = localFile;
- }
-
- @Override
- public String toString() {
- return super.toString() + " (Content override)";
- }
- }
-
- static class ForDirectFileRequest extends AutoSyncID {
- public final static String MOD_ID = "bclib::FILE";
- final File relFile;
-
- ForDirectFileRequest(String syncID, File relFile) {
- super(ForDirectFileRequest.MOD_ID, syncID);
- this.relFile = relFile;
- }
-
- @Override
- void serializeData(FriendlyByteBuf buf) {
- super.serializeData(buf);
- DataHandler.writeString(buf, relFile.toString());
- }
-
- static ForDirectFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf){
- final File fl = new File(DataHandler.readString(buf));
- return new ForDirectFileRequest(uniqueID, fl);
- }
-
- @Override
- public String toString() {
- return super.uniqueID + " (" + this.relFile + ")";
- }
- }
-
- static class ForModFileRequest extends AutoSyncID {
- public final static String UNIQUE_ID = "bclib::MOD";
- private final String version;
- ForModFileRequest(String modID, String version) {
- super(modID, ForModFileRequest.UNIQUE_ID);
- this.version = version;
- }
-
- @Override
- void serializeData(FriendlyByteBuf buf) {
- super.serializeData(buf);
- buf.writeInt(ModUtil.convertModVersion(version));
- }
-
- static ForModFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf){
- final String version = ModUtil.convertModVersion(buf.readInt());
- return new ForModFileRequest(modID, version);
- }
-
- @Override
- public String toString() {
- return super.modID + " (v" + this.version + ")";
- }
- }
-
- /**
- * A Unique ID for the referenced File.
- *
- * Files with the same {@link #modID} need to have a unique IDs. Normally the filename from FileHash(String, File, byte[], int, int)
- * is used to generated that ID, but you can directly specify one using FileHash(String, String, byte[], int, int).
- */
- @NotNull
- public final String uniqueID;
-
- /**
- * The ID of the Mod that is registering the File
- */
- @NotNull
- public final String modID;
-
- public AutoSyncID(String modID, String uniqueID) {
- Objects.nonNull(modID);
- Objects.nonNull(uniqueID);
-
- this.modID = modID;
- this.uniqueID = uniqueID;
- }
-
- @Override
- public String toString() {
- return modID + "." + uniqueID;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof AutoSyncID)) return false;
- AutoSyncID that = (AutoSyncID) o;
- return uniqueID.equals(that.uniqueID) && modID.equals(that.modID);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(uniqueID, modID);
- }
-
- void serializeData(FriendlyByteBuf buf) {
- DataHandler.writeString(buf, modID);
- DataHandler.writeString(buf, uniqueID);
- }
-
- static AutoSyncID deserializeData(FriendlyByteBuf buf){
- String modID = DataHandler.readString(buf);
- String uID = DataHandler.readString(buf);
-
- if (ForDirectFileRequest.MOD_ID.equals(modID)){
- return ForDirectFileRequest.finishDeserialize(modID, uID, buf);
- } else if (ForModFileRequest.UNIQUE_ID.equals(uID)){
- return ForModFileRequest.finishDeserialize(modID, uID, buf);
- } else{
- return new AutoSyncID(modID, uID);
- }
- }
-
- public boolean isConfigFile(){
- return this.uniqueID.startsWith(Config.CONFIG_SYNC_PREFIX);
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/Chunker.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/Chunker.java
deleted file mode 100644
index 33a16719..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/Chunker.java
+++ /dev/null
@@ -1,270 +0,0 @@
-package ru.bclib.api.dataexchange.handler.autosync;
-
-import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
-import net.fabricmc.fabric.api.networking.v1.PacketSender;
-import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
-import net.minecraft.client.Minecraft;
-import net.minecraft.network.FriendlyByteBuf;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.util.ProgressListener;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.BaseDataHandler;
-import ru.bclib.api.dataexchange.DataHandler;
-import ru.bclib.api.dataexchange.DataHandlerDescriptor;
-import ru.bclib.api.dataexchange.handler.DataExchange;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.UUID;
-
-/**
- * Used to seperate large data transfers into multiple smaller messages.
- *
- * {@link DataHandler} will automatically convert larger messages into Chunks on the Server
- * and assemble the original message from those chunks on the client.
- */
-public class Chunker extends DataHandler.FromServer {
-
- /**
- * Responsible for assembling the original ByteBuffer created by {@link PacketChunkSender} on the
- * receiving end. Automatically created from the header {@link Chunker}-Message (where the serialNo==-1)
- */
- static class PacketChunkReceiver {
- @NotNull
- public final UUID uuid;
- public final int chunkCount;
- @NotNull
- private final FriendlyByteBuf networkedBuf;
- @Nullable
- private final DataHandlerDescriptor descriptor;
-
- private static List active = new ArrayList<>(1);
- private static PacketChunkReceiver newReceiver(@NotNull UUID uuid, int chunkCount, ResourceLocation origin){
- DataHandlerDescriptor desc = DataExchange.getDescriptor(origin);
- final PacketChunkReceiver r = new PacketChunkReceiver(uuid, chunkCount, desc);
- active.add(r);
- return r;
- }
-
- private static PacketChunkReceiver getOrCreate(@NotNull UUID uuid, int chunkCount, ResourceLocation origin){
- return active.stream().filter(r -> r.uuid.equals(uuid)).findFirst().orElse(newReceiver(uuid, chunkCount, origin));
- }
-
- public static PacketChunkReceiver get(@NotNull UUID uuid){
- return active.stream().filter(r -> r.uuid.equals(uuid)).findFirst().orElse(null);
- }
-
- private PacketChunkReceiver(@NotNull UUID uuid, int chunkCount, @Nullable DataHandlerDescriptor descriptor){
- this.uuid = uuid;
- this.chunkCount = chunkCount;
- networkedBuf = PacketByteBufs.create();
- this.descriptor = descriptor;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof PacketChunkReceiver)) return false;
- PacketChunkReceiver that = (PacketChunkReceiver) o;
- return uuid.equals(that.uuid);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(uuid);
- }
-
- public boolean testFinished(){
- ProgressListener listener = ChunkerProgress.getProgressListener();
- if (listener!=null){
- listener.progressStagePercentage((100*receivedCount)/chunkCount);
- }
- if (incomingBuffer == null){
- return true;
- } if (lastReadSerial>=chunkCount-1){
- onFinish();
- return true;
- }
- return false;
- }
-
- private void addBuffer(FriendlyByteBuf input){
- final int size = input.readableBytes();
- final int cap = networkedBuf.capacity()-networkedBuf.writerIndex();
-
- if (cap < size){
- networkedBuf.capacity(networkedBuf.writerIndex() + size);
- }
- input.readBytes(networkedBuf, size);
- input.clear();
- }
-
- protected void onFinish(){
- incomingBuffer.clear();
- incomingBuffer = null;
-
- final BaseDataHandler baseHandler = descriptor.INSTANCE.get();
- if (baseHandler instanceof DataHandler.FromServer handler){
- handler.receiveFromMemory(networkedBuf);
- }
- }
-
- Map incomingBuffer = new HashMap<>();
- int lastReadSerial = -1;
- int receivedCount = 0;
- public void processReceived(FriendlyByteBuf buf, int serialNo, int size){
- receivedCount++;
-
- if (lastReadSerial == serialNo-1){
- addBuffer(buf);
- lastReadSerial = serialNo;
- } else {
- //not sure if order is guaranteed by the underlying system!
- boolean haveAll = true;
- for (int nr = lastReadSerial+1; nr < serialNo-1; nr++){
- if (incomingBuffer.get(nr) == null){
- haveAll = false;
- break;
- }
- }
-
- if (haveAll){
- for (int nr = lastReadSerial+1; nr < serialNo-1; nr++){
- addBuffer(incomingBuffer.get(nr));
- incomingBuffer.put(nr, null);
- }
- addBuffer(buf);
- lastReadSerial = serialNo;
- } else {
- incomingBuffer.put(serialNo, buf);
- }
- }
- }
- }
-
- /**
- * Responsible for splitting an outgoing ByteBuffer into several smaller Chunks and
- * send them as seperate messages to the {@link Chunker}-Channel
- */
- public static class PacketChunkSender {
- private final FriendlyByteBuf networkedBuf;
- public final UUID uuid;
- public final int chunkCount;
- public final int size;
- public final ResourceLocation origin;
-
- public PacketChunkSender(FriendlyByteBuf buf, ResourceLocation origin){
- networkedBuf = buf;
-
- size = buf.readableBytes();
- chunkCount = (int)Math.ceil((double)size / MAX_PAYLOAD_SIZE);
- uuid = UUID.randomUUID();
- this.origin = origin;
- }
-
- public void sendChunks(Collection players){
- BCLib.LOGGER.info("Sending Request in " + chunkCount + " Packet-Chunks");
- for (int i=-1; i
- * For Details refer to {@link HelloServer}
- */
-public class HelloClient extends DataHandler.FromServer {
- public record OfferedModInfo(String version, int size, boolean canDownload) {
- }
- public interface IServerModMap extends Map {}
- public static class ServerModMap extends HashMap implements IServerModMap {}
-
- public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_client"), HelloClient::new, false, false);
-
- public HelloClient() {
- super(DESCRIPTOR.IDENTIFIER);
- }
-
- static String getBCLibVersion() {
- return ModUtil.getModVersion(BCLib.MOD_ID);
- }
-
- @Override
- protected boolean prepareDataOnServer() {
- if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
- BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
- return false;
- }
-
- AutoSync.loadSyncFolder();
- return true;
- }
-
- @Override
- protected void serializeDataOnServer(FriendlyByteBuf buf) {
- final String vbclib = getBCLibVersion();
- BCLib.LOGGER.info("Sending Hello to Client. (server=" + vbclib + ")");
-
- //write BCLibVersion (=protocol version)
- buf.writeInt(ModUtil.convertModVersion(vbclib));
-
- if (Configs.SERVER_CONFIG.isOfferingMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()) {
- List mods = DataExchangeAPI.registeredMods();
- final List inmods = mods;
- if (Configs.SERVER_CONFIG.isOfferingAllMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()){
- mods = new ArrayList<>(inmods.size());
- mods.addAll(inmods);
- mods.addAll(ModUtil
- .getMods()
- .entrySet()
- .stream()
- .filter(entry -> entry.getValue().metadata.getEnvironment()!= ModEnvironment.SERVER && !inmods.contains(entry.getKey()))
- .map(entry -> entry.getKey())
- .collect(Collectors.toList())
- );
- }
-
- mods = mods
- .stream()
- .filter(entry -> !Configs.SERVER_CONFIG.get(ServerConfig.EXCLUDED_MODS).contains(entry))
- .collect(Collectors.toList());
-
- //write Plugin Versions
- buf.writeInt(mods.size());
- for (String modID : mods) {
- final String ver = ModUtil.getModVersion(modID);
- int size = 0;
-
- final ModInfo mi = ModUtil.getModInfo(modID);
- if (mi != null) {
- try {
- size = (int) Files.size(mi.jarPath);
- } catch (IOException e) {
- BCLib.LOGGER.error("Unable to get File Size: " + e.getMessage());
- }
- }
-
-
- writeString(buf, modID);
- buf.writeInt(ModUtil.convertModVersion(ver));
- buf.writeInt(size);
- final boolean canDownload = size>0 && Configs.SERVER_CONFIG.isOfferingMods() && (Configs.SERVER_CONFIG.isOfferingAllMods() || inmods.contains(modID));
- buf.writeBoolean(canDownload);
-
- BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver + " (size: " + PathUtil.humanReadableFileSize(size) + ", download="+canDownload+")");
- }
- }
- else {
- BCLib.LOGGER.info("Server will not list Mods.");
- buf.writeInt(0);
- }
-
- if (Configs.SERVER_CONFIG.isOfferingFiles() || Configs.SERVER_CONFIG.isOfferingConfigs()) {
- //do only include files that exist on the server
- final List existingAutoSyncFiles = AutoSync.getAutoSyncFiles()
- .stream()
- .filter(e -> e.fileName.exists())
- .filter(e -> (e.isConfigFile() && Configs.SERVER_CONFIG.isOfferingConfigs()) || (e instanceof AutoFileSyncEntry.ForDirectFileRequest && Configs.SERVER_CONFIG.isOfferingFiles()))
- .collect(Collectors.toList());
-
- //send config Data
- buf.writeInt(existingAutoSyncFiles.size());
- for (AutoFileSyncEntry entry : existingAutoSyncFiles) {
- entry.serialize(buf);
- BCLib.LOGGER.info(" - Offering " + (entry.isConfigFile() ? "Config " : "File ") + entry);
- }
- }
- else {
- BCLib.LOGGER.info("Server will neither offer Files nor Configs.");
- buf.writeInt(0);
- }
-
- if (Configs.SERVER_CONFIG.isOfferingFiles()) {
- buf.writeInt(AutoSync.syncFolderDescriptions.size());
- AutoSync.syncFolderDescriptions.forEach(desc -> {
- BCLib.LOGGER.info(" - Offering Folder " + desc.localFolder + " (allowDelete=" + desc.removeAdditionalFiles + ")");
- desc.serialize(buf);
- });
- }
- else {
- BCLib.LOGGER.info("Server will not offer Sync Folders.");
- buf.writeInt(0);
- }
-
- buf.writeBoolean(Configs.SERVER_CONFIG.isOfferingInfosForMods());
- }
-
- String bclibVersion = "0.0.0";
-
-
-
- IServerModMap modVersion = new ServerModMap();
- List autoSyncedFiles = null;
- List autoSynFolders = null;
- boolean serverPublishedModInfo = false;
-
- @Environment(EnvType.CLIENT)
- @Override
- protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
- //read BCLibVersion (=protocol version)
- bclibVersion = ModUtil.convertModVersion(buf.readInt());
- final boolean protocolVersion_0_4_1 = ModUtil.isLargerOrEqualVersion(bclibVersion, "0.4.1");
-
-
- //read Plugin Versions
- modVersion = new ServerModMap();
- int count = buf.readInt();
- for (int i = 0; i < count; i++) {
- final String id = readString(buf);
- final String version = ModUtil.convertModVersion(buf.readInt());
- final int size;
- final boolean canDownload;
- //since v0.4.1 we also send the size of the mod-File
- if (protocolVersion_0_4_1) {
- size = buf.readInt();
- canDownload = buf.readBoolean();
- }
- else {
- size = 0;
- canDownload = true;
- }
- modVersion.put(id, new OfferedModInfo(version, size, canDownload));
- }
-
- //read config Data
- count = buf.readInt();
- autoSyncedFiles = new ArrayList<>(count);
- for (int i = 0; i < count; i++) {
- //System.out.println("Deserializing ");
- AutoSync.AutoSyncTriple t = AutoFileSyncEntry.deserializeAndMatch(buf);
- autoSyncedFiles.add(t);
- //System.out.println(t.first);
- }
-
-
- autoSynFolders = new ArrayList<>(1);
- //since v0.4.1 we also send the sync folders
- if (protocolVersion_0_4_1) {
- final int folderCount = buf.readInt();
- for (int i = 0; i < folderCount; i++) {
- SyncFolderDescriptor desc = SyncFolderDescriptor.deserialize(buf);
- autoSynFolders.add(desc);
- }
-
- serverPublishedModInfo = buf.readBoolean();
- }
- }
-
- @Environment(EnvType.CLIENT)
- private void processAutoSyncFolder(final List filesToRequest, final List filesToRemove) {
- if (!Configs.CLIENT_CONFIG.isAcceptingFiles()) {
- return;
- }
-
- if (autoSynFolders.size() > 0) {
- BCLib.LOGGER.info("Folders offered by Server:");
- }
-
- autoSynFolders.forEach(desc -> {
- //desc contains the fileCache sent from the server, load the local version to get hold of the actual file cache on the client
- SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(desc.folderID);
- if (localDescriptor != null) {
- BCLib.LOGGER.info(" - " + desc.folderID + " (" + desc.localFolder + ", allowRemove=" + desc.removeAdditionalFiles + ")");
- localDescriptor.invalidateCache();
-
- desc.relativeFilesStream()
- .filter(desc::discardChildElements)
- .forEach(subFile -> {
- BCLib.LOGGER.warning(" * " + subFile.relPath + " (REJECTED)");
- });
-
-
- if (desc.removeAdditionalFiles) {
- List additionalFiles = localDescriptor.relativeFilesStream()
- .filter(subFile -> !desc.hasRelativeFile(subFile))
- .map(desc::mapAbsolute)
- .filter(desc::acceptChildElements)
- .map(absPath -> new AutoSyncID.ForDirectFileRequest(desc.folderID, absPath.toFile()))
- .collect(Collectors.toList());
-
- additionalFiles.forEach(aid -> BCLib.LOGGER.info(" * " + desc.localFolder.relativize(aid.relFile.toPath()) + " (missing on server)"));
- filesToRemove.addAll(additionalFiles);
- }
-
- desc.relativeFilesStream()
- .filter(desc::acceptChildElements)
- .forEach(subFile -> {
- SubFile localSubFile = localDescriptor.getLocalSubFile(subFile.relPath);
- if (localSubFile != null) {
- //the file exists locally, check if the hashes match
- if (!localSubFile.hash.equals(subFile.hash)) {
- BCLib.LOGGER.info(" * " + subFile.relPath + " (changed)");
- filesToRequest.add(new AutoSyncID.ForDirectFileRequest(desc.folderID, new File(subFile.relPath)));
- }
- else {
- BCLib.LOGGER.info(" * " + subFile.relPath);
- }
- }
- else {
- //the file is missing locally
- BCLib.LOGGER.info(" * " + subFile.relPath + " (missing on client)");
- filesToRequest.add(new AutoSyncID.ForDirectFileRequest(desc.folderID, new File(subFile.relPath)));
- }
- });
-
- //free some memory
- localDescriptor.invalidateCache();
- }
- else {
- BCLib.LOGGER.info(" - " + desc.folderID + " (Failed to find)");
- }
- });
- }
-
- @Environment(EnvType.CLIENT)
- private void processSingleFileSync(final List filesToRequest) {
- final boolean debugHashes = Configs.CLIENT_CONFIG.shouldPrintDebugHashes();
-
- if (autoSyncedFiles.size() > 0) {
- BCLib.LOGGER.info("Files offered by Server:");
- }
-
- //Handle single sync files
- //Single files need to be registered for sync on both client and server
- //There are no restrictions to the target folder, but the client decides the final
- //location.
- for (AutoSync.AutoSyncTriple e : autoSyncedFiles) {
- String actionString = "";
- FileContentWrapper contentWrapper = new FileContentWrapper(e.serverContent);
- if (e.localMatch == null) {
- actionString = "(unknown source -> omitting)";
- //filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
- }
- else if (e.localMatch.needTransfer.test(e.localMatch.getFileHash(), e.serverHash, contentWrapper)) {
- actionString = "(prepare update)";
- //we did not yet receive the new content
- if (contentWrapper.getRawContent() == null) {
- filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
- }
- else {
- filesToRequest.add(new AutoSyncID.WithContentOverride(e.serverHash.modID, e.serverHash.uniqueID, contentWrapper, e.localMatch.fileName));
- }
- }
-
- BCLib.LOGGER.info(" - " + e + ": " + actionString);
- if (debugHashes) {
- BCLib.LOGGER.info(" * " + e.serverHash + " (Server)");
- BCLib.LOGGER.info(" * " + e.localMatch.getFileHash() + " (Client)");
- BCLib.LOGGER.info(" * local Content " + (contentWrapper.getRawContent() == null));
- }
- }
- }
-
-
- @Environment(EnvType.CLIENT)
- private void processModFileSync(final List filesToRequest, final Set mismatchingMods) {
- for (Entry e : modVersion.entrySet()) {
- final String localVersion = ModUtil.getModVersion(e.getKey());
- final OfferedModInfo serverInfo = e.getValue();
- final boolean requestMod = !serverInfo.version.equals(localVersion) && serverInfo.size > 0 && serverInfo.canDownload;
-
- BCLib.LOGGER.info(" - " + e.getKey() + " (client=" + localVersion + ", server=" + serverInfo.version + ", size=" + PathUtil.humanReadableFileSize(serverInfo.size) + (requestMod ? ", requesting" : "") + (serverInfo.canDownload ? "" :", not offered")+ ")");
- if (requestMod) {
- filesToRequest.add(new AutoSyncID.ForModFileRequest(e.getKey(), serverInfo.version));
- }
- if (!serverInfo.version.equals(localVersion)) {
- mismatchingMods.add(e.getKey());
- }
- }
-
- mismatchingMods.addAll(ModListScreen.localMissing(modVersion));
- mismatchingMods.addAll(ModListScreen.serverMissing(modVersion));
- }
-
- @Override
- protected boolean isBlocking() {
- return true;
- }
-
- @Environment(EnvType.CLIENT)
- @Override
- protected void runOnClientGameThread(Minecraft client) {
- if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
- BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
- return;
- }
- final String localBclibVersion = getBCLibVersion();
- BCLib.LOGGER.info("Received Hello from Server. (client=" + localBclibVersion + ", server=" + bclibVersion + ")");
-
- if (ModUtil.convertModVersion(localBclibVersion) != ModUtil.convertModVersion(bclibVersion)){
- showBCLibError(client);
- return;
- }
-
- final List filesToRequest = new ArrayList<>(2);
- final List filesToRemove = new ArrayList<>(2);
- final Set mismatchingMods = new HashSet<>(2);
-
-
- processModFileSync(filesToRequest, mismatchingMods);
- processSingleFileSync(filesToRequest);
- processAutoSyncFolder(filesToRequest, filesToRemove);
-
- //Handle folder sync
- //Both client and server need to know about the folder you want to sync
- //Files can only get placed within that folder
-
- if ((filesToRequest.size() > 0 || filesToRemove.size() > 0) && ( Configs.CLIENT_CONFIG.isAcceptingMods() || Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles())) {
- showSyncFilesScreen(client, filesToRequest, filesToRemove);
- return;
- } else if (serverPublishedModInfo && mismatchingMods.size()>0 && Configs.CLIENT_CONFIG.isShowingModInfo()) {
- client.setScreen(new ModListScreen(client.screen, new TranslatableComponent("title.bclib.modmissmatch"), new TranslatableComponent("message.bclib.modmissmatch"), CommonComponents.GUI_PROCEED, ModUtil.getMods(), modVersion));
- return;
- }
- }
-
- @Environment(EnvType.CLIENT)
- protected void showBCLibError(Minecraft client) {
- BCLib.LOGGER.error("BCLib differs on client and server.");
- client.setScreen(new WarnBCLibVersionMismatch((download) -> {
- if (download) {
- requestBCLibDownload();
-
- this.onCloseSyncFilesScreen();
- } else {
- Minecraft.getInstance()
- .setScreen(null);
- }
- }));
- }
-
- @Environment(EnvType.CLIENT)
- protected void showSyncFilesScreen(Minecraft client, List files, final List filesToRemove) {
- int configFiles = 0;
- int singleFiles = 0;
- int folderFiles = 0;
- int modFiles = 0;
-
- for (AutoSyncID aid : files) {
- if (aid.isConfigFile()) {
- configFiles++;
- }
- else if (aid instanceof AutoSyncID.ForModFileRequest) {
- modFiles++;
- }
- else if (aid instanceof AutoSyncID.ForDirectFileRequest) {
- folderFiles++;
- }
- else {
- singleFiles++;
- }
- }
-
- client.setScreen(new SyncFilesScreen(modFiles, configFiles, singleFiles, folderFiles, filesToRemove.size(), modVersion, (downloadMods, downloadConfigs, downloadFiles, removeFiles) -> {
- if (downloadMods || downloadConfigs || downloadFiles) {
- BCLib.LOGGER.info("Updating local Files:");
- List localChanges = new ArrayList<>(files.toArray().length);
- List requestFiles = new ArrayList<>(files.toArray().length);
-
- files.forEach(aid -> {
- if (aid.isConfigFile() && downloadConfigs) {
- processOfferedFile(requestFiles, aid);
- }
- else if (aid instanceof AutoSyncID.ForModFileRequest && downloadMods) {
- processOfferedFile(requestFiles, aid);
- }
- else if (downloadFiles) {
- processOfferedFile(requestFiles, aid);
- }
- });
-
- requestFileDownloads(requestFiles);
- }
- if (removeFiles) {
- filesToRemove.forEach(aid -> {
- BCLib.LOGGER.info(" - " + aid.relFile + " (removing)");
- aid.relFile.delete();
- });
- }
-
- this.onCloseSyncFilesScreen();
- }));
- }
-
- @Environment(EnvType.CLIENT)
- private void onCloseSyncFilesScreen(){
- Minecraft.getInstance()
- .setScreen(ChunkerProgress.getProgressScreen());
- }
-
- private void processOfferedFile(List requestFiles, AutoSyncID aid) {
- if (aid instanceof WithContentOverride) {
- final WithContentOverride aidc = (WithContentOverride) aid;
- BCLib.LOGGER.info(" - " + aid + " (updating Content)");
-
- SendFiles.writeSyncedFile(aid, aidc.contentWrapper.getRawContent(), aidc.localFile);
- }
- else {
- requestFiles.add(aid);
- BCLib.LOGGER.info(" - " + aid + " (requesting)");
- }
- }
-
- private void requestBCLibDownload() {
- BCLib.LOGGER.warning("Starting download of BCLib");
- requestFileDownloads(List.of(new AutoSyncID.ForModFileRequest(BCLib.MOD_ID, bclibVersion)));
- }
-
- @Environment(EnvType.CLIENT)
- private void requestFileDownloads(List files) {
- BCLib.LOGGER.info("Starting download of Files:" + files.size());
-
- final ProgressScreen progress = new ProgressScreen(null, new TranslatableComponent("title.bclib.filesync.progress"), new TranslatableComponent("message.bclib.filesync.progress"));
- progress.progressStart(new TranslatableComponent("message.bclib.filesync.progress.stage.empty"));
- ChunkerProgress.setProgressScreen(progress);
-
- DataExchangeAPI.send(new RequestFiles(files));
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloServer.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloServer.java
deleted file mode 100644
index a3abd668..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloServer.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package ru.bclib.api.dataexchange.handler.autosync;
-
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.fabricmc.fabric.api.networking.v1.PacketSender;
-import net.minecraft.network.FriendlyByteBuf;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.server.MinecraftServer;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.DataExchangeAPI;
-import ru.bclib.api.dataexchange.DataHandler;
-import ru.bclib.api.dataexchange.DataHandlerDescriptor;
-import ru.bclib.config.Configs;
-import ru.bclib.util.ModUtil;
-
-import java.io.File;
-
-/**
- * This message is sent once a player enters the world. It initiates a sequence of Messages that will sync files between both
- * client and server.
- *
- * Description
- *
- * Server |
- * |
- * Client |
- * |
- *
- *
- * Player enters World |
- *
- *
- * |
- * <-- |
- * {@link HelloServer} |
- * Sends the current BLib-Version installed on the Client |
- *
- *
- * {@link HelloClient} |
- * --> |
- * |
- * Sends the current BClIb-Version, the Version of all Plugins and data for all AutpoSync-Files
- * ({@link DataExchangeAPI#addAutoSyncFile(String, File)} on the Server |
- *
- *
- * |
- * <-- |
- * {@link RequestFiles} |
- * Request missing or out of sync Files from the Server |
- *
- *
- * {@link SendFiles} |
- * --> |
- * |
- * Send Files from the Server to the Client |
- *
- *
- */
-public class HelloServer extends DataHandler.FromClient {
- public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "hello_server"), HelloServer::new, true, false);
-
- protected String bclibVersion = "0.0.0";
-
- public HelloServer() {
- super(DESCRIPTOR.IDENTIFIER);
- }
-
- @Environment(EnvType.CLIENT)
- @Override
- protected boolean prepareDataOnClient() {
- if (! Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
- BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
- return false;
- }
-
- return true;
- }
-
- @Environment(EnvType.CLIENT)
- @Override
- protected void serializeDataOnClient(FriendlyByteBuf buf) {
- BCLib.LOGGER.info("Sending hello to server.");
- buf.writeInt(ModUtil.convertModVersion(HelloClient.getBCLibVersion()));
- }
-
- @Override
- protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, PacketSender responseSender) {
- bclibVersion = ModUtil.convertModVersion(buf.readInt());
- }
-
- @Override
- protected void runOnServerGameThread(MinecraftServer server) {
- if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
- BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
- return;
- }
-
- String localBclibVersion = HelloClient.getBCLibVersion();
- BCLib.LOGGER.info("Received Hello from Client. (server=" + localBclibVersion + ", client=" + bclibVersion + ")");
-
- if (!server.isPublished()) {
- BCLib.LOGGER.info("Auto-Sync is disabled for Singleplayer worlds.");
- return;
- }
-
- reply(new HelloClient(), server);
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/RequestFiles.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/RequestFiles.java
deleted file mode 100644
index 45579906..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/RequestFiles.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package ru.bclib.api.dataexchange.handler.autosync;
-
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.fabricmc.fabric.api.networking.v1.PacketSender;
-import net.minecraft.network.FriendlyByteBuf;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.server.MinecraftServer;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.DataHandler;
-import ru.bclib.api.dataexchange.DataHandlerDescriptor;
-import ru.bclib.config.Configs;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-public class RequestFiles extends DataHandler.FromClient {
- public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "request_files"), RequestFiles::new, false, false);
- static String currentToken = "";
-
- protected List files;
-
- private RequestFiles() {
- this(null);
- }
-
- public RequestFiles(List files) {
- super(DESCRIPTOR.IDENTIFIER);
- this.files = files;
- }
-
- @Environment(EnvType.CLIENT)
- @Override
- protected boolean prepareDataOnClient() {
- if (! Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
- BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
- return false;
- }
- return true;
- }
-
- @Environment(EnvType.CLIENT)
- @Override
- protected void serializeDataOnClient(FriendlyByteBuf buf) {
- newToken();
- writeString(buf, currentToken);
-
- buf.writeInt(files.size());
-
- for (AutoSyncID a : files) {
- a.serializeData(buf);
- }
- }
-
- String receivedToken = "";
-
- @Override
- protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, PacketSender responseSender) {
- receivedToken = readString(buf);
- int size = buf.readInt();
- files = new ArrayList<>(size);
-
- BCLib.LOGGER.info("Client requested " + size + " Files:");
- for (int i = 0; i < size; i++) {
- AutoSyncID asid = AutoSyncID.deserializeData(buf);
- files.add(asid);
- BCLib.LOGGER.info(" - " + asid);
- }
-
-
- }
-
- @Override
- protected void runOnServerGameThread(MinecraftServer server) {
- if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
- BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
- return;
- }
-
- List syncEntries = files.stream()
- .map(asid -> AutoFileSyncEntry.findMatching(asid))
- .filter(e -> e != null)
- .collect(Collectors.toList());
-
- reply(new SendFiles(syncEntries, receivedToken), server);
- }
-
- public static void newToken() {
- currentToken = UUID.randomUUID()
- .toString();
- }
-
- static {
- newToken();
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SendFiles.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SendFiles.java
deleted file mode 100644
index 000be5c0..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SendFiles.java
+++ /dev/null
@@ -1,216 +0,0 @@
-package ru.bclib.api.dataexchange.handler.autosync;
-
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.fabricmc.fabric.api.networking.v1.PacketSender;
-import net.minecraft.client.Minecraft;
-import net.minecraft.network.FriendlyByteBuf;
-import net.minecraft.resources.ResourceLocation;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.DataHandler;
-import ru.bclib.api.dataexchange.DataHandlerDescriptor;
-import ru.bclib.config.Configs;
-import ru.bclib.gui.screens.ConfirmRestartScreen;
-import ru.bclib.util.Pair;
-import ru.bclib.util.PathUtil;
-import ru.bclib.util.Triple;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class SendFiles extends DataHandler.FromServer {
- public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "send_files"), SendFiles::new, false, false);
-
- protected List files;
- private String token;
-
- public SendFiles() {
- this(null, "");
- }
-
- public SendFiles(List files, String token) {
- super(DESCRIPTOR.IDENTIFIER);
- this.files = files;
- this.token = token;
- }
-
- @Override
- protected boolean prepareDataOnServer() {
- if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
- BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
- return false;
- }
-
- return true;
- }
-
- @Override
- protected void serializeDataOnServer(FriendlyByteBuf buf) {
- List existingFiles = files.stream()
- .filter(e -> e!=null &&e.fileName!=null && e.fileName.exists())
- .collect(Collectors.toList());
- /*
- //this will try to send a file that was not registered or requested by the client
- existingFiles.add(new AutoFileSyncEntry("none", new File("D:\\MinecraftPlugins\\BetterNether\\run\\server.properties"),true,(a, b, content) -> {
- System.out.println("Got Content:" + content.length);
- return true;
- }));*/
-
- /*//this will try to send a folder-file that was not registered or requested by the client
- existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("test.json"), DataExchange.SYNC_FOLDER.mapAbsolute("test.json").toFile()));*/
-
- /*//this will try to send a folder-file that was not registered or requested by the client and is outside the base-folder
- existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("../breakout.json"), DataExchange.SYNC_FOLDER.mapAbsolute("../breakout.json").toFile()));*/
-
-
- writeString(buf, token);
- buf.writeInt(existingFiles.size());
-
- BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:");
- for (AutoFileSyncEntry entry : existingFiles) {
- int length = entry.serializeContent(buf);
- BCLib.LOGGER.info(" - " + entry + " (" + PathUtil.humanReadableFileSize(length) + ")");
- }
- }
-
- private List> receivedFiles;
-
- @Environment(EnvType.CLIENT)
- @Override
- protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
- if ( Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) {
- token = readString(buf);
- if (!token.equals(RequestFiles.currentToken)) {
- RequestFiles.newToken();
- BCLib.LOGGER.error("Unrequested File Transfer!");
- receivedFiles = new ArrayList<>(0);
- return;
- }
- RequestFiles.newToken();
-
- int size = buf.readInt();
- receivedFiles = new ArrayList<>(size);
- BCLib.LOGGER.info("Server sent " + size + " Files:");
- for (int i = 0; i < size; i++) {
- Triple p = AutoFileSyncEntry.deserializeContent(buf);
- if (p.first != null) {
- final String type;
- if (p.first.isConfigFile() && Configs.CLIENT_CONFIG.isAcceptingConfigs()) {
- receivedFiles.add(p);
- type = "Accepted Config ";
- } else if (p.first instanceof AutoFileSyncEntry.ForModFileRequest && Configs.CLIENT_CONFIG.isAcceptingMods()){
- receivedFiles.add(p);
- type = "Accepted Mod ";
- } else if ( Configs.CLIENT_CONFIG.isAcceptingFiles()){
- receivedFiles.add(p);
- type = "Accepted File ";
- } else {
- type = "Ignoring ";
- }
- BCLib.LOGGER.info(" - " + type + p.first + " (" + PathUtil.humanReadableFileSize(p.second.length) + ")");
- }
- else {
- BCLib.LOGGER.error(" - Failed to receive File " + p.third + ", possibly sent from a Mod that is not installed on the client.");
- }
- }
- }
- }
-
- @Environment(EnvType.CLIENT)
- @Override
- protected void runOnClientGameThread(Minecraft client) {
- if ( Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) {
- BCLib.LOGGER.info("Writing Files:");
-
- //TODO: Reject files that were not in the last RequestFiles.
- for (Pair entry : receivedFiles) {
- final AutoFileSyncEntry e = entry.first;
- final byte[] data = entry.second;
-
- writeSyncedFile(e, data, e.fileName);
- }
-
- showConfirmRestart(client);
- }
- }
-
-
- @Environment(EnvType.CLIENT)
- static void writeSyncedFile(AutoSyncID e, byte[] data, File fileName) {
- if (fileName!=null && !PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())){
- BCLib.LOGGER.error(fileName + " is not within game folder " + PathUtil.GAME_FOLDER);
- return;
- }
-
- if (!PathUtil.MOD_BAK_FOLDER.toFile().exists()){
- PathUtil.MOD_BAK_FOLDER.toFile().mkdirs();
- }
-
- Path path = fileName!=null?fileName.toPath():null;
- Path removeAfter = null;
- if (e instanceof AutoFileSyncEntry.ForModFileRequest mase){
- removeAfter = path;
- int count = 0;
- final String prefix = "_bclib_synced_";
- String name = prefix + mase.modID + "_" + mase.version.replace(".", "_") + ".jar";
- do {
- if (path != null) {
- //move to the same directory as the existing Mod
- path = path.getParent()
- .resolve(name);
- }
- else {
- //move to the default mode location
- path = PathUtil.MOD_FOLDER.resolve(name);
- }
- count++;
- name = prefix + mase.modID + "_" + mase.version.replace(".", "_") + "__" + String.format("%03d", count) + ".jar";
- } while (path.toFile().exists());
- }
-
- BCLib.LOGGER.info(" - Writing " + path + " (" + PathUtil.humanReadableFileSize(data.length) + ")");
- try {
- final File parentFile = path.getParent()
- .toFile();
- if (!parentFile.exists()) {
- parentFile.mkdirs();
- }
- Files.write(path, data);
- if (removeAfter != null){
- final String bakFileName = removeAfter.toFile().getName();
- String collisionFreeName = bakFileName;
- Path targetPath;
- int count = 0;
- do {
- targetPath = PathUtil.MOD_BAK_FOLDER.resolve(collisionFreeName);
- count++;
- collisionFreeName = String.format("%03d", count) + "_" + bakFileName;
- } while (targetPath.toFile().exists());
-
- BCLib.LOGGER.info(" - Moving " + removeAfter + " to " +targetPath);
- removeAfter.toFile().renameTo(targetPath.toFile());
- }
- AutoSync.didReceiveFile(e, fileName);
-
-
- }
- catch (IOException ioException) {
- BCLib.LOGGER.error(" --> Writing " + fileName + " failed: " + ioException);
- }
- }
-
- @Environment(EnvType.CLIENT)
- protected void showConfirmRestart(Minecraft client) {
- client.setScreen(new ConfirmRestartScreen(() -> {
- Minecraft.getInstance()
- .setScreen(null);
- client.stop();
- }));
-
- }
-}
diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SyncFolderDescriptor.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SyncFolderDescriptor.java
deleted file mode 100644
index bba41d5e..00000000
--- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SyncFolderDescriptor.java
+++ /dev/null
@@ -1,206 +0,0 @@
-package ru.bclib.api.dataexchange.handler.autosync;
-
-import net.minecraft.network.FriendlyByteBuf;
-import org.jetbrains.annotations.NotNull;
-import ru.bclib.BCLib;
-import ru.bclib.api.dataexchange.DataHandler;
-import ru.bclib.api.dataexchange.FileHash;
-import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID.ForDirectFileRequest;
-import ru.bclib.config.Configs;
-import ru.bclib.util.PathUtil;
-
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Stream;
-
-public class SyncFolderDescriptor {
- static class SubFile {
- public final String relPath;
- public final FileHash hash;
-
-
- SubFile(String relPath, FileHash hash) {
- this.relPath = relPath;
- this.hash = hash;
- }
-
- @Override
- public String toString() {
- return relPath;
- }
-
- public void serialize(FriendlyByteBuf buf) {
- DataHandler.writeString(buf, relPath);
- hash.serialize(buf);
- }
-
- public static SubFile deserialize(FriendlyByteBuf buf) {
- final String relPath = DataHandler.readString(buf);
- FileHash hash = FileHash.deserialize(buf);
- return new SubFile(relPath, hash);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o instanceof String) return relPath.equals(o);
- if (!(o instanceof SubFile)) return false;
- SubFile subFile = (SubFile) o;
- return relPath.equals(subFile.relPath);
- }
-
- @Override
- public int hashCode() {
- return relPath.hashCode();
- }
- }
-
- @NotNull
- public final String folderID;
- public final boolean removeAdditionalFiles;
- @NotNull
- public final Path localFolder;
-
- private List fileCache;
-
- public SyncFolderDescriptor(String folderID, Path localFolder, boolean removeAdditionalFiles) {
- this.removeAdditionalFiles = removeAdditionalFiles;
- this.folderID = folderID;
- this.localFolder = localFolder;
- fileCache = null;
- }
-
- @Override
- public String toString() {
- return "SyncFolderDescriptor{" + "folderID='" + folderID + '\'' + ", removeAdditionalFiles=" + removeAdditionalFiles + ", localFolder=" + localFolder + ", files=" + (fileCache == null ? "?" : fileCache.size()) + "}";
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o instanceof String) {
- return folderID.equals(o);
- }
- if (o instanceof ForDirectFileRequest) {
- return folderID.equals(((ForDirectFileRequest) o).uniqueID);
- }
- if (!(o instanceof SyncFolderDescriptor)) return false;
- SyncFolderDescriptor that = (SyncFolderDescriptor) o;
- return folderID.equals(that.folderID);
- }
-
- @Override
- public int hashCode() {
- return folderID.hashCode();
- }
-
- public int fileCount() {
- return fileCache == null ? 0 : fileCache.size();
- }
-
- public void invalidateCache() {
- fileCache = null;
- }
-
- public void loadCache() {
- if (fileCache == null) {
- fileCache = new ArrayList<>(8);
- PathUtil.fileWalker(localFolder.toFile(), p -> fileCache.add(new SubFile(localFolder.relativize(p)
- .toString(), FileHash.create(p.toFile()))));
-
- /*//this tests if we can trick the system to load files that are not beneath the base-folder
- if (!BCLib.isClient()) {
- fileCache.add(new SubFile("../breakout.json", FileHash.create(mapAbsolute("../breakout.json").toFile())));
- }*/
- }
- }
-
- public void serialize(FriendlyByteBuf buf) {
- final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(AutoSync.SYNC_CATEGORY, "debugHashes", false);
- loadCache();
-
- DataHandler.writeString(buf, folderID);
- buf.writeBoolean(removeAdditionalFiles);
- buf.writeInt(fileCache.size());
- fileCache.forEach(fl -> {
- BCLib.LOGGER.info(" - " + fl.relPath);
- if (debugHashes) {
- BCLib.LOGGER.info(" " + fl.hash);
- }
- fl.serialize(buf);
- });
- }
-
- public static SyncFolderDescriptor deserialize(FriendlyByteBuf buf) {
- final String folderID = DataHandler.readString(buf);
- final boolean remAddFiles = buf.readBoolean();
- final int count = buf.readInt();
- SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(folderID);
-
- final SyncFolderDescriptor desc;
- if (localDescriptor != null) {
- desc = new SyncFolderDescriptor(folderID, localDescriptor.localFolder, localDescriptor.removeAdditionalFiles && remAddFiles);
- desc.fileCache = new ArrayList<>(count);
- }
- else {
- BCLib.LOGGER.warning(BCLib.isClient() ? "Client" : "Server" + " does not know Sync-Folder ID '" + folderID + "'");
- desc = null;
- }
-
- for (int i = 0; i < count; i++) {
- SubFile relPath = SubFile.deserialize(buf);
- if (desc != null) desc.fileCache.add(relPath);
- }
-
- return desc;
- }
-
- //Note: make sure loadCache was called before using this
- boolean hasRelativeFile(String relFile) {
- return fileCache.stream()
- .filter(sf -> sf.equals(relFile))
- .findFirst()
- .isPresent();
- }
-
- //Note: make sure loadCache was called before using this
- boolean hasRelativeFile(SubFile subFile) {
- return hasRelativeFile(subFile.relPath);
- }
-
- //Note: make sure loadCache was called before using this
- SubFile getLocalSubFile(String relPath) {
- return fileCache.stream()
- .filter(sf -> sf.relPath.equals(relPath))
- .findFirst()
- .orElse(null);
- }
-
- Stream relativeFilesStream() {
- loadCache();
- return fileCache.stream();
- }
-
- public Path mapAbsolute(String relPath) {
- return this.localFolder.resolve(relPath)
- .normalize();
- }
-
- public Path mapAbsolute(SubFile subFile) {
- return this.localFolder.resolve(subFile.relPath)
- .normalize();
- }
-
- public boolean acceptChildElements(Path absPath) {
- return PathUtil.isChildOf(this.localFolder, absPath);
- }
-
- public boolean acceptChildElements(SubFile subFile) {
- return acceptChildElements(mapAbsolute(subFile));
- }
-
- public boolean discardChildElements(SubFile subFile) {
- return !acceptChildElements(subFile);
- }
-}
diff --git a/src/main/java/ru/bclib/api/datafixer/DataFixerAPI.java b/src/main/java/ru/bclib/api/datafixer/DataFixerAPI.java
deleted file mode 100644
index 1a832f6f..00000000
--- a/src/main/java/ru/bclib/api/datafixer/DataFixerAPI.java
+++ /dev/null
@@ -1,616 +0,0 @@
-package ru.bclib.api.datafixer;
-
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.minecraft.Util;
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.components.toasts.SystemToast;
-import net.minecraft.client.gui.screens.Screen;
-import net.minecraft.client.gui.screens.worldselection.EditWorldScreen;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.ListTag;
-import net.minecraft.nbt.NbtIo;
-import net.minecraft.nbt.StringTag;
-import net.minecraft.nbt.Tag;
-import net.minecraft.network.chat.Component;
-import net.minecraft.network.chat.TranslatableComponent;
-import net.minecraft.world.level.ChunkPos;
-import net.minecraft.world.level.chunk.storage.RegionFile;
-import net.minecraft.world.level.storage.LevelResource;
-import net.minecraft.world.level.storage.LevelStorageSource;
-import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
-import org.jetbrains.annotations.NotNull;
-import ru.bclib.BCLib;
-import ru.bclib.api.WorldDataAPI;
-import ru.bclib.config.Configs;
-import ru.bclib.gui.screens.AtomicProgressListener;
-import ru.bclib.gui.screens.ConfirmFixScreen;
-import ru.bclib.gui.screens.LevelFixErrorScreen;
-import ru.bclib.gui.screens.LevelFixErrorScreen.Listener;
-import ru.bclib.gui.screens.ProgressScreen;
-import ru.bclib.util.Logger;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.EOFException;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.zip.ZipException;
-
-/**
- * API to manage Patches that need to get applied to a world
- */
-public class DataFixerAPI {
- static final Logger LOGGER = new Logger("DataFixerAPI");
- static class State {
- public boolean didFail = false;
- protected ArrayList errors = new ArrayList<>();
-
- public void addError(String s){
- errors.add(s);
- }
-
- public boolean hasError(){
- return errors.size()>0;
- }
-
- public String getErrorMessage(){
- return errors.stream().reduce("", (a, b) -> a + " - " + b + "\n");
- }
-
- public String[] getErrorMessages(){
- String[] res = new String[errors.size()];
- return errors.toArray(res);
- }
- }
-
- @FunctionalInterface
- public static interface Callback {
- public void call();
- }
-
- private static boolean wrapCall(LevelStorageSource levelSource, String levelID, Function runWithLevel) {
- LevelStorageSource.LevelStorageAccess levelStorageAccess;
- try {
- levelStorageAccess = levelSource.createAccess(levelID);
- }
- catch (IOException e) {
- BCLib.LOGGER.warning("Failed to read level {} data", levelID, e);
- SystemToast.onWorldAccessFailure(Minecraft.getInstance(), levelID);
- Minecraft.getInstance().setScreen(null);
- return true;
- }
-
- boolean returnValue = runWithLevel.apply(levelStorageAccess);
-
- try {
- levelStorageAccess.close();
- }
- catch (IOException e) {
- BCLib.LOGGER.warning("Failed to unlock access to level {}", levelID, e);
- }
-
- return returnValue;
- }
-
- /**
- * Will apply necessary Patches to the world.
- *
- * @param levelSource The SourceStorage for this Minecraft instance, You can get this using
- * {@code Minecraft.getInstance().getLevelSource()}
- * @param levelID The ID of the Level you want to patch
- * @param showUI {@code true}, if you want to present the user with a Screen that offers to backup the world
- * before applying the patches
- * @param onResume When this method retursn {@code true}, this function will be called when the world is ready
- * @return {@code true} if the UI was displayed. The UI is only displayed if {@code showUI} was {@code true} and
- * patches were enabled in the config and the Guardian did find any patches that need to be applied to the world.
- *
- */
- public static boolean fixData(LevelStorageSource levelSource, String levelID, boolean showUI, Consumer onResume) {
- return wrapCall(levelSource, levelID, (levelStorageAccess) -> fixData(levelStorageAccess, showUI, onResume));
- }
-
- /**
- * Will apply necessary Patches to the world.
- *
- * @param levelStorageAccess The access class of the level you want to patch
- * @param showUI {@code true}, if you want to present the user with a Screen that offers to backup the world
- * before applying the patches
- * @param onResume When this method retursn {@code true}, this function will be called when the world is ready
- * @return {@code true} if the UI was displayed. The UI is only displayed if {@code showUI} was {@code true} and
- * patches were enabled in the config and the Guardian did find any patches that need to be applied to the world.
- *
- */
- public static boolean fixData(LevelStorageSource.LevelStorageAccess levelStorageAccess, boolean showUI, Consumer onResume){
- File levelPath = levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile();
- File levelDat = levelStorageAccess.getLevelPath(LevelResource.LEVEL_DATA_FILE).toFile();
- boolean newWorld = false;
- if (!levelDat.exists()) {
- BCLib.LOGGER.info("Creating a new World, no fixes needed");
- newWorld = true;
- }
-
- initializeWorldData(levelPath, newWorld);
- if (newWorld) return false;
-
- return fixData(levelPath, levelStorageAccess.getLevelId(), showUI, onResume);
- }
- /**
- * Initializes the DataStorage for this world. If the world is new, the patch registry is initialized to the
- * current versions of the plugins.
- *
- * This implementation will create a new {@link LevelStorageAccess} and call {@link #initializeWorldData(File, boolean)}
- * using the provided root path.
- * @param levelSource The SourceStorage for this Minecraft instance, You can get this using
- * {@code Minecraft.getInstance().getLevelSource()}
- * @param levelID The ID of the Level you want to patch
- * @param newWorld {@code true} if this is a fresh world
- */
- public static void initializeWorldData(LevelStorageSource levelSource, String levelID, boolean newWorld) {
- wrapCall(levelSource, levelID, (levelStorageAccess) -> {
- initializeWorldData(levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile(), newWorld);
- return true;
- });
- }
-
- /**
- * Initializes the DataStorage for this world. If the world is new, the patch registry is initialized to the
- * current versions of the plugins.
- * @param levelBaseDir Folder of the world
- * @param newWorld {@code true} if this is a fresh world
- *
- */
- public static void initializeWorldData(File levelBaseDir, boolean newWorld){
- WorldDataAPI.load(new File(levelBaseDir, "data"));
-
- if (newWorld){
- getMigrationProfile().markApplied();
- WorldDataAPI.saveFile(BCLib.MOD_ID);
- }
- }
-
- @Environment(EnvType.CLIENT)
- private static AtomicProgressListener showProgressScreen(){
- ProgressScreen ps = new ProgressScreen(Minecraft.getInstance().screen, new TranslatableComponent("title.bclib.datafixer.progress"), new TranslatableComponent("message.bclib.datafixer.progress"));
- Minecraft.getInstance().setScreen(ps);
- return ps;
- }
-
- private static boolean fixData(File dir, String levelID, boolean showUI, Consumer onResume) {
- MigrationProfile profile = loadProfileIfNeeded(dir);
-
- BiConsumer runFixes = (createBackup, applyFixes) -> {
- final AtomicProgressListener progress;
- if (applyFixes) {
- if (showUI) {
- progress = showProgressScreen();
- } else {
- progress = new AtomicProgressListener() {
- private long timeStamp = Util.getMillis();
- private AtomicInteger counter = new AtomicInteger(0);
-
- @Override
- public void incAtomic(int maxProgress) {
- int percentage = (100 * counter.incrementAndGet()) / maxProgress;
- if (Util.getMillis() - this.timeStamp >= 1000L) {
- this.timeStamp = Util.getMillis();
- BCLib.LOGGER.info((String) "Patching... {}%", percentage);
- }
- }
-
- @Override
- public void resetAtomic() {
- counter = new AtomicInteger(0);
- }
-
- public void stop() {
- }
-
- public void progressStage(Component component) {
- BCLib.LOGGER.info((String) "Patcher Stage... {}%", component.getString());
- }
- };
- }
- } else {
- progress = null;
- }
-
- Supplier runner = () -> {
- if (createBackup) {
- progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.waitbackup"));
- EditWorldScreen.makeBackupAndShowToast(Minecraft.getInstance().getLevelSource(), levelID);
- }
-
- if (applyFixes) {
- return runDataFixes(dir, profile, progress);
- }
-
- return new State();
- };
-
- if (showUI) {
- Thread fixerThread = new Thread(() -> {
- final State state = runner.get();
-
- Minecraft.getInstance()
- .execute(() -> {
- if (profile != null && showUI) {
- //something went wrong, show the user our error
- if (state.didFail || state.hasError()){
- showLevelFixErrorScreen(state, (markFixed)->{
- if (markFixed) {
- profile.markApplied();
- }
- onResume.accept(applyFixes);
- });
- } else {
- onResume.accept(applyFixes);
- }
- }
- });
-
- });
- fixerThread.start();
- } else {
- State state = runner.get();
- if (state.hasError()){
- LOGGER.error("There were Errors while fixing the Level:");
- LOGGER.error(state.getErrorMessage());
- }
- }
- };
-
- //we have some migrations
- if (profile != null) {
- //display the confirm UI.
- if (showUI){
- showBackupWarning(levelID, runFixes);
- return true;
- } else {
- BCLib.LOGGER.warning("Applying Fixes on Level");
- runFixes.accept(false, true);
- }
- }
- return false;
- }
- @Environment(EnvType.CLIENT)
- private static void showLevelFixErrorScreen(State state, Listener onContinue){
- Minecraft.getInstance()
- .setScreen(new LevelFixErrorScreen(Minecraft.getInstance().screen, state.getErrorMessages(), onContinue));
- }
-
- private static MigrationProfile loadProfileIfNeeded(File levelBaseDir){
- if (!Configs.MAIN_CONFIG.getBoolean(Configs.MAIN_PATCH_CATEGORY, "applyPatches", true)) {
- LOGGER.info("World Patches are disabled");
- return null;
- }
-
- MigrationProfile profile = getMigrationProfile();
- profile.runPrePatches(levelBaseDir);
-
- if (!profile.hasAnyFixes()) {
- LOGGER.info("Everything up to date");
- return null;
- }
-
- return profile;
- }
-
- @NotNull
- private static MigrationProfile getMigrationProfile() {
- final CompoundTag patchConfig = WorldDataAPI.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
- MigrationProfile profile = Patch.createMigrationData(patchConfig);
- return profile;
- }
-
- @Environment(EnvType.CLIENT)
- static void showBackupWarning(String levelID, BiConsumer whenFinished){
- Minecraft.getInstance().setScreen(new ConfirmFixScreen((Screen) null, whenFinished::accept));
- }
-
- private static State runDataFixes(File dir, MigrationProfile profile, AtomicProgressListener progress) {
- State state = new State();
- progress.resetAtomic();
-
- progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.reading"));
- List players = getAllPlayers(dir);
- List regions = getAllRegions(dir, null);
- final int maxProgress = players.size()+regions.size()+4;
- progress.incAtomic(maxProgress);
-
- progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.players"));
- players.parallelStream().forEach((file) -> {
- fixPlayer(profile, state, file);
- progress.incAtomic(maxProgress);
- });
-
- progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.level"));
- fixLevel(profile, state, dir);
- progress.incAtomic(maxProgress);
-
- progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.worlddata"));
- try {
- profile.patchWorldData();
- } catch (PatchDidiFailException e){
- state.didFail = true;
- state.addError("Failed fixing worldconfig (" + e.getMessage() + ")");
- BCLib.LOGGER.error(e.getMessage());
- }
- progress.incAtomic(maxProgress);
-
- progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.regions"));
- regions.parallelStream().forEach((file) -> {
- fixRegion(profile, state, file);
- progress.incAtomic(maxProgress);
- });
-
- if (!state.didFail) {
- progress.progressStage(new TranslatableComponent("message.bclib.datafixer.progress.saving"));
- profile.markApplied();
- WorldDataAPI.saveFile(BCLib.MOD_ID);
- }
- progress.incAtomic(maxProgress);
-
- progress.stop();
-
- return state;
- }
-
- private static void fixLevel(MigrationProfile profile, State state, File levelBaseDir) {
- try {
- LOGGER.info("Inspecting level.dat in " + levelBaseDir);
-
- //load the level (could already contain patches applied by patchLevelDat)
- CompoundTag level = profile.getLevelDat(levelBaseDir);
- boolean[] changed = { profile.isLevelDatChanged() };
-
- if (profile.getPrePatchException()!=null){
- throw profile.getPrePatchException();
- }
-
- if (level.contains("Data")) {
- CompoundTag dataTag = (CompoundTag)level.get("Data");
- if (dataTag.contains("Player")) {
- CompoundTag player = (CompoundTag)dataTag.get("Player");
- fixPlayerNbt(player, changed, profile);
- }
- }
-
- if (changed[0]) {
- LOGGER.warning("Writing '{}'", profile.getLevelDatFile());
- NbtIo.writeCompressed(level, profile.getLevelDatFile());
- }
- }
- catch (Exception e) {
- BCLib.LOGGER.error("Failed fixing Level-Data.");
- state.addError("Failed fixing Level-Data in level.dat (" + e.getMessage() + ")");
- state.didFail = true;
- e.printStackTrace();
- }
- }
-
- private static void fixPlayer(MigrationProfile data, State state, File file) {
- try {
- LOGGER.info("Inspecting " + file);
-
- CompoundTag player = readNbt(file);
- boolean[] changed = { false };
- fixPlayerNbt(player, changed, data);
-
- if (changed[0]) {
- LOGGER.warning("Writing '{}'", file);
- NbtIo.writeCompressed(player, file);
- }
- }
- catch (Exception e) {
- BCLib.LOGGER.error("Failed fixing Player-Data.");
- state.addError("Failed fixing Player-Data in " + file.getName() + " (" + e.getMessage() + ")");
- state.didFail = true;
- e.printStackTrace();
- }
- }
-
- private static void fixPlayerNbt(CompoundTag player, boolean[] changed, MigrationProfile data) {
- //Checking Inventory
- ListTag inventory = player.getList("Inventory", Tag.TAG_COMPOUND);
- fixItemArrayWithID(inventory, changed, data, true);
-
- //Checking EnderChest
- ListTag enderitems = player.getList("EnderItems", Tag.TAG_COMPOUND);
- fixItemArrayWithID(enderitems, changed, data, true);
-
- //Checking ReceipBook
- if (player.contains("recipeBook")) {
- CompoundTag recipeBook = player.getCompound("recipeBook");
- changed[0] |= fixStringIDList(recipeBook, "recipes", data);
- changed[0] |= fixStringIDList(recipeBook, "toBeDisplayed", data);
- }
- }
-
- static boolean fixStringIDList(CompoundTag root, String name, MigrationProfile data) {
- boolean _changed = false;
- if (root.contains(name)) {
- ListTag items = root.getList(name, Tag.TAG_STRING);
- ListTag newItems = new ListTag();
-
- for (Tag tag : items) {
- final StringTag str = (StringTag)tag;
- final String replace = data.replaceStringFromIDs(str.getAsString());
- if (replace!=null) {
- _changed = true;
- newItems.add(StringTag.valueOf(replace));
- } else {
- newItems.add(tag);
- }
- }
- if (_changed) {
- root.put(name, newItems);
- }
- }
- return _changed;
- }
-
- private static void fixRegion(MigrationProfile data, State state, File file) {
- try {
- LOGGER.info("Inspecting " + 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) && !state.didFail) {
- DataInputStream input = region.getChunkDataInputStream(pos);
- CompoundTag root = NbtIo.read(input);
- // if ((root.toString().contains("betternether:chest") || root.toString().contains("bclib:chest"))) {
- // NbtIo.write(root, new File(file.toString() + "-" + x + "-" + z + ".nbt"));
- // }
- input.close();
-
- //Checking TileEntities
- ListTag tileEntities = root.getCompound("Level")
- .getList("TileEntities", Tag.TAG_COMPOUND);
- fixItemArrayWithID(tileEntities, changed, data, true);
-
- //Checking Entities
- ListTag entities = root.getList("Entities", Tag.TAG_COMPOUND);
- fixItemArrayWithID(entities, changed, data, true);
-
- //Checking Block Palette
- ListTag sections = root.getCompound("Level")
- .getList("Sections", Tag.TAG_COMPOUND);
- sections.forEach((tag) -> {
- ListTag palette = ((CompoundTag) tag).getList("Palette", Tag.TAG_COMPOUND);
- palette.forEach((blockTag) -> {
- CompoundTag blockTagCompound = ((CompoundTag) blockTag);
- changed[0] |= data.replaceStringFromIDs(blockTagCompound, "Name");
- });
-
- try {
- changed[0] |= data.patchBlockState(palette, ((CompoundTag) tag).getList("BlockStates", Tag.TAG_LONG));
- }
- catch (PatchDidiFailException e) {
- BCLib.LOGGER.error("Failed fixing BlockState in " + pos);
- state.addError("Failed fixing BlockState in " + pos + " (" + e.getMessage() + ")");
- state.didFail = true;
- changed[0] = false;
- e.printStackTrace();
- }
- });
-
- if (changed[0]) {
- LOGGER.warning("Writing '{}': {}/{}", file, x, z);
- // NbtIo.write(root, new File(file.toString() + "-" + x + "-" + z + "-changed.nbt"));
- DataOutputStream output = region.getChunkDataOutputStream(pos);
- NbtIo.write(root, output);
- output.close();
- }
- }
- }
- }
- region.close();
- }
- catch (Exception e) {
- BCLib.LOGGER.error("Failed fixing Region.");
- state.addError("Failed fixing Region in " + file.getName() + " (" + e.getMessage() + ")");
- state.didFail = true;
- e.printStackTrace();
- }
- }
-
- static CompoundTag patchConfTag = null;
- static CompoundTag getPatchData(){
- if (patchConfTag==null) {
- patchConfTag = WorldDataAPI.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
- }
- return patchConfTag;
- }
-
- static void fixItemArrayWithID(ListTag items, boolean[] changed, MigrationProfile data, boolean recursive) {
- items.forEach(inTag -> {
- fixID((CompoundTag) inTag, changed, data, recursive);
- });
- }
-
-
- static void fixID(CompoundTag inTag, boolean[] changed, MigrationProfile data, boolean recursive) {
- final CompoundTag tag = inTag;
-
- changed[0] |= data.replaceStringFromIDs(tag, "id");
- if (tag.contains("Item")) {
- CompoundTag item = (CompoundTag)tag.get("Item");
- fixID(item, changed, data, recursive);
- }
-
- if (recursive && tag.contains("Items")) {
- fixItemArrayWithID(tag.getList("Items", Tag.TAG_COMPOUND), changed, data, true);
- }
- if (recursive && tag.contains("Inventory")) {
- ListTag inventory = tag.getList("Inventory", Tag.TAG_COMPOUND);
- fixItemArrayWithID(inventory, changed, data, true);
- }
- if (tag.contains("tag")) {
- CompoundTag entityTag = (CompoundTag)tag.get("tag");
- if (entityTag.contains("BlockEntityTag")){
- CompoundTag blockEntityTag = (CompoundTag)entityTag.get("BlockEntityTag");
- fixID(blockEntityTag, changed, data, recursive);
- /*ListTag items = blockEntityTag.getList("Items", Tag.TAG_COMPOUND);
- fixItemArrayWithID(items, changed, data, recursive);*/
- }
- }
- }
-
- private static List getAllPlayers(File dir) {
- List list = new ArrayList<>();
- dir = new File(dir, "playerdata");
- if (!dir.exists() || !dir.isDirectory()) {
- return list;
- }
- for (File file : dir.listFiles()) {
- if (file.isFile() && file.getName().endsWith(".dat")) {
- list.add(file);
- }
- }
- return list;
- }
-
- private static List getAllRegions(File dir, List list) {
- if (list == null) {
- list = new ArrayList<>();
- }
- for (File file : dir.listFiles()) {
- if (file.isDirectory()) {
- getAllRegions(file, list);
- } else if (file.isFile() && file.getName().endsWith(".mca")) {
- list.add(file);
- }
- }
- return list;
- }
-
- /**
- * register a new Patch
- *
- * @param patch A #Supplier that will instantiate the new Patch Object
- */
- public static void registerPatch(Supplier patch) {
- Patch.getALL().add(patch.get());
- }
-
- private static CompoundTag readNbt(File file) throws IOException {
- try {
- return NbtIo.readCompressed(file);
- } catch (ZipException | EOFException e){
- return NbtIo.read(file);
- }
- }
-
-}
diff --git a/src/main/java/ru/bclib/api/datafixer/ForcedLevelPatch.java b/src/main/java/ru/bclib/api/datafixer/ForcedLevelPatch.java
deleted file mode 100644
index 333d473b..00000000
--- a/src/main/java/ru/bclib/api/datafixer/ForcedLevelPatch.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package ru.bclib.api.datafixer;
-
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.ListTag;
-import org.jetbrains.annotations.NotNull;
-import ru.bclib.interfaces.PatchBiFunction;
-import ru.bclib.interfaces.PatchFunction;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-
-/**
- * A Patch for level.dat that is always executed no matter what Patchlevel is set in a world.
- */
-public abstract class ForcedLevelPatch extends Patch {
- protected ForcedLevelPatch(@NotNull String modID, String version) {
- super(modID, version, true);
- }
-
- @Override
- public final Map getIDReplacements() {
- return new HashMap();
- }
-
- @Override
- public final PatchFunction getWorldDataPatcher() { return null; }
-
- @Override
- public final PatchBiFunction getBlockStatePatcher() { return null; }
-
- @Override
- public final List getWorldDataIDPaths() {
- return null;
- }
-
- @Override
- public PatchFunction getLevelDatPatcher() { return this::runLevelDatPatch; }
-
- /**
- * Called with the contents of level.dat in {@code root}
- * @param root The contents of level.dat
- * @param profile The active migration profile
- * @return true, if the run did change the contents of root
- */
- abstract protected Boolean runLevelDatPatch(CompoundTag root, MigrationProfile profile);
-}
-
diff --git a/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java b/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java
deleted file mode 100644
index 831470ff..00000000
--- a/src/main/java/ru/bclib/api/datafixer/MigrationProfile.java
+++ /dev/null
@@ -1,322 +0,0 @@
-package ru.bclib.api.datafixer;
-
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.ListTag;
-import net.minecraft.nbt.NbtIo;
-import net.minecraft.nbt.Tag;
-import org.jetbrains.annotations.NotNull;
-import ru.bclib.BCLib;
-import ru.bclib.api.WorldDataAPI;
-import ru.bclib.interfaces.PatchBiFunction;
-import ru.bclib.interfaces.PatchFunction;
-import ru.bclib.util.ModUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public class MigrationProfile {
- final Set mods;
- final Map idReplacements;
- final List> levelPatchers;
- final List> statePatchers;
- final List worldDataPatchers;
- final Map> worldDataIDPaths;
-
- private final CompoundTag config;
- private CompoundTag level;
- private File levelBaseDir;
- private boolean prePatchChangedLevelDat;
- private boolean didRunPrePatch;
- private Exception prePatchException;
-
- MigrationProfile(CompoundTag config, boolean applyAll) {
- this.config = config;
-
- this.mods = Collections.unmodifiableSet(Patch.getALL()
- .stream()
- .map(p -> p.modID)
- .collect(Collectors.toSet()));
-
- HashMap replacements = new HashMap();
- List