diff --git a/pom.xml b/pom.xml index bc2f010..c477c5a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,11 @@ com.magmaguy ResourcePackManager - 1.0-SNAPSHOT + 1.6.0 - 22 - 22 + 17 + 17 UTF-8 @@ -22,6 +22,11 @@ central https://repo1.maven.org/maven2/ + + magmaguy-repo-releases + MagmaGuy's Repository + https://repo.magmaguy.com/releases + @@ -47,10 +52,39 @@ httpclient5 5.3.1 + + com.magmaguy + MagmaCore + 1.18-SNAPSHOT + compile + + + com.magmaguy + FreeMinecraftModels + 1.4.0 + provided + + + + org.bstats + bstats-bukkit + 3.0.2 + compile + + ResourcePackManager + + + maven-compiler-plugin + + 17 + 17 + + + org.apache.maven.plugins maven-shade-plugin @@ -68,6 +102,10 @@ org.apache.hc com.magmaguy.resourcepackmanager.org.apache.hc + + org.bstats + com.magmaguy.freeminecraftmodels + @@ -75,4 +113,15 @@ + + + magmaguy-repo-snapshots + https://magmaguy.com/snapshots + + + magmaguy-repo-snapshots + MagmaGuy's Repository + https://repo.magmaguy.com/releases + + diff --git a/src/main/java/com/magmaguy/resourcepackmanager/JsonMerger.java b/src/main/java/com/magmaguy/resourcepackmanager/JsonMerger.java deleted file mode 100644 index 0f9afb5..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/JsonMerger.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.magmaguy.resourcepackmanager; - -public class JsonMerger { -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/Logger.java b/src/main/java/com/magmaguy/resourcepackmanager/Logger.java deleted file mode 100644 index 3881571..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/Logger.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.magmaguy.resourcepackmanager; - -import org.bukkit.Bukkit; - -public class Logger { - private Logger(){} - public static void warn(String message){ - Bukkit.getLogger().warning("[ResourcePackManager] " + message); - } - - public static void info(String message){ - Bukkit.getLogger().info("[ResourcePackManager] " + message); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 179a3af..24e3adb 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -1,16 +1,26 @@ package com.magmaguy.resourcepackmanager; +import com.magmaguy.freeminecraftmodels.bukkit.Metrics; +import com.magmaguy.magmacore.MagmaCore; +import com.magmaguy.magmacore.command.CommandManager; +import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.autohost.AutoHost; -import com.magmaguy.resourcepackmanager.commands.CommandManager; +import com.magmaguy.resourcepackmanager.commands.DataComplianceRequestCommand; +import com.magmaguy.resourcepackmanager.commands.ReloadCommand; +import com.magmaguy.resourcepackmanager.config.BlueprintFolder; +import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; -import com.magmaguy.resourcepackmanager.mixer.Mix; +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfig; +import com.magmaguy.resourcepackmanager.playermanager.PlayerManager; +import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; +import com.magmaguy.resourcepackmanager.utils.VersionChecker; import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; +import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; public class ResourcePackManager extends JavaPlugin { - public static Plugin plugin; + public static JavaPlugin plugin; @Override public void onEnable() { @@ -20,17 +30,39 @@ public void onEnable() { " | /\\__ \\ _/ |\\/| / _` | ' \\/ _` / _` / -_) '_|\n" + " |_|_\\|___/_| |_| |_\\__,_|_||_\\__,_\\__, \\___|_| \n" + " |___/ "); - Logger.info("Enabling ResourcePackManager v." + this.getDescription().getVersion()); + Bukkit.getLogger().info("ResourcePackManager v." + this.getDescription().getVersion()); + MagmaCore.onEnable(); + plugin = this; - DefaultConfig.initializeConfig(); - Mix.initialize(); - AutoHost.initialize(); - new CommandManager(this); + new DataConfig(); + new DefaultConfig(); + new CompatiblePluginConfig(); + + BlueprintFolder.initialize(); + //This starts a watchdog to see if the resource packs change and updates the mixer if htey do. + ThirdPartyResourcePack.startResourcePackChangeWatchdog(); + if (DefaultConfig.isAutoHost()) + Bukkit.getPluginManager().registerEvents(new PlayerManager(), this); + CommandManager commandManager = new CommandManager(this, "resourcepackmanager"); + commandManager.registerCommand(new ReloadCommand()); + commandManager.registerCommand(new DataComplianceRequestCommand()); + Bukkit.getPluginManager().registerEvents(new VersionChecker.VersionCheckerEvents(), this); +// AutoHost.initialize(); + + Metrics metrics = new Metrics(this, 22867); + MagmaCore.checkVersionUpdate("118574", "https://www.spigotmc.org/resources/resource-pack-manager.118574/"); + } + + @Override + public void onLoad() { + MagmaCore.createInstance(this); } @Override public void onDisable() { Logger.info("Disabling ResourcePackManager"); + ThirdPartyResourcePack.shutdown(); AutoHost.shutdown(); + HandlerList.unregisterAll(this); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java new file mode 100644 index 0000000..736a17a --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java @@ -0,0 +1,82 @@ +package com.magmaguy.resourcepackmanager.api; + +import com.magmaguy.resourcepackmanager.commands.ReloadCommand; +import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; +import org.bukkit.Bukkit; + +import java.util.HashMap; + +public class ResourcePackManagerAPI { + public static HashMap thirdPartyResourcePackHashMap = new HashMap<>(); + + /** + * Registers a resource pack with the ResourcePackManager. + * Either localPath or url must be provided (non-null), but not both. + * + * @param pluginName The name of the plugin as it appears in the plugin list. Case-sensitive. + * @param localPath The relative path to the resource pack file (zipped or folder) from the plugins directory, or null if using URL. + * @param url The URL to download the resource pack from, or null if using local path. + * @param encrypts Whether the pack can be encrypted by the plugin. Currently does nothing. + * @param distributes Whether the plugin can distribute the pack. Currently does nothing. + * @param zips Whether the resource pack is already zipped. If false, ResourcePackManager will zip it. + * @param reloadCommand The reload command of the plugin adding a pack. Currently does nothing. + */ + public static void registerResourcePack(String pluginName, + String localPath, + String url, + boolean encrypts, + boolean distributes, + boolean zips, + String reloadCommand) { + thirdPartyResourcePackHashMap.put(pluginName, + new ThirdPartyResourcePack(pluginName, localPath, url, encrypts, distributes, zips, reloadCommand)); + } + + /** + * Registers a local resource pack with the ResourcePackManager. + * + * @param pluginName The name of the plugin as it appears in the plugin list. Case-sensitive. + * @param localPath The relative path to the resource pack file (zipped or folder) from the plugins directory. + * @param encrypts Whether the pack can be encrypted by the plugin. Currently does nothing. + * @param distributes Whether the plugin can distribute the pack. Currently does nothing. + * @param zips Whether the resource pack is already zipped. If false, ResourcePackManager will zip it. + * @param reloadCommand The reload command of the plugin adding a pack. Currently does nothing. + */ + public static void registerLocalResourcePack(String pluginName, + String localPath, + boolean encrypts, + boolean distributes, + boolean zips, + String reloadCommand) { + thirdPartyResourcePackHashMap.put(pluginName, + new ThirdPartyResourcePack(pluginName, localPath, null, encrypts, distributes, zips, reloadCommand)); + } + + /** + * Registers a remote resource pack with the ResourcePackManager. + * The resource pack will be downloaded from the provided URL. + * + * @param pluginName The name of the plugin as it appears in the plugin list. Case-sensitive. + * @param url The URL to download the resource pack from. + * @param encrypts Whether the pack can be encrypted by the plugin. Currently does nothing. + * @param distributes Whether the plugin can distribute the pack. Currently does nothing. + * @param zips Whether the resource pack from the URL is already zipped. If false, ResourcePackManager will zip it. + * @param reloadCommand The reload command of the plugin adding a pack. Currently does nothing. + */ + public static void registerRemoteResourcePack(String pluginName, + String url, + boolean encrypts, + boolean distributes, + boolean zips, + String reloadCommand) { + thirdPartyResourcePackHashMap.put(pluginName, + new ThirdPartyResourcePack(pluginName, null, url, encrypts, distributes, zips, reloadCommand)); + } + + /** + * Reloads the plugin, thereby redoing everything necessary to merge and host the resource pack + */ + public static void reloadResourcePack() { + ReloadCommand.reloadPlugin(Bukkit.getConsoleSender()); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index a520e44..6e83625 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -1,99 +1,347 @@ package com.magmaguy.resourcepackmanager.autohost; -import com.magmaguy.resourcepackmanager.Logger; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.mixer.Mix; +import lombok.Getter; +import lombok.Setter; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.http.io.entity.FileEntity; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; -import java.io.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; public class AutoHost { - private static final String hostURL = "http://localhost:3000/sha1"; - private static final String stillAliveURL = "http://localhost:3000/still_alive"; - private static final String rspURL = "http://localhost:3000/upload"; + private static final String finalURL = "https://magmaguy.com/rsp/"; + @Setter + private static boolean done = false; +// private static final String finalURL = "https://localhost:50000/"; + private static BukkitTask keepAlive = null; + @Getter + private static String rspUUID = null; + @Setter + private static boolean firstUpload = true; private AutoHost() { } + public static void sendResourcePack(Player player) { + Logger.info("Sending resource pack to " + player.getName() + ". rspUUID is null: " + (rspUUID == null) + ". done: " + done + ". firstUpload: " + firstUpload + "."); + if (rspUUID == null || !done) return; + Logger.info("Sending resource pack to " + player.getName()); + player.setResourcePack(finalURL + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); + } + public static void initialize() { if (!DefaultConfig.isAutoHost()) return; if (Mix.getFinalResourcePack() == null) return; + Logger.info("Starting autohost!"); + firstUpload = true; + done = false; + rspUUID = null; + if (keepAlive != null) keepAlive.cancel(); + keepAlive = new BukkitRunnable() { + int counter = 0; + @Override public void run() { - try { - if (!sendSHA1(Mix.getFinalSHA1(), hostURL)) { - uploadFile(Mix.getFinalResourcePack(), rspURL); + if (rspUUID != null) { + counter = 0; + try { + sendStillAlive(); + } catch (Exception e) { + rspUUID = null; + Logger.warn("Failed to autohost resource pack!"); + e.printStackTrace(); + } + } else { + checkFileExistence(); + if (rspUUID == null && counter % 10 == 0) { + Logger.warn("Failed to connect to remote server to autohost the resource pack!"); } - sendStillAlive(stillAliveURL); - } catch (Exception e) { - Logger.warn("Failed to autohost resource pack!"); - e.printStackTrace(); + counter++; } } - }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 0, 3 * 24 * 60 * 60 * 20L); + }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 0, 6 * 60 * 60 * 20L); + } + + private static void checkFileExistence() { + initializeLink(); + if (rspUUID == null) { + Logger.info("No resource pack found on the server! Uploading resource pack to the server..."); + return; + } + if (!sendSHA1()) uploadFile(); + else { + //Case if the remote server already has the resource pack + done = true; + if (firstUpload) { + //Recover from a reload by sending the pack to online players + for (Player player : Bukkit.getOnlinePlayers()) + AutoHost.sendResourcePack(player); + } + firstUpload = false; + } } - public static void uploadFile(File file, String url) throws IOException { + public static void initializeLink() { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost uploadFile = new HttpPost(url); + HttpPost httpPost = new HttpPost(finalURL + "initialize"); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("uuid", DataConfig.getRspUUID(), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + httpPost.setEntity(builder.build()); + + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + String responseString = EntityUtils.toString(response.getEntity()); + int statusCode = response.getCode(); - // Use FileEntity for streaming large files - FileEntity fileEntity = new FileEntity(file, ContentType.APPLICATION_OCTET_STREAM); - uploadFile.setEntity(fileEntity); + if (statusCode >= 200 && statusCode < 300) { + // Success - parse JSON response + try { + Gson gson = new Gson(); + JsonObject jsonResponse = gson.fromJson(responseString, JsonObject.class); + + // Check if response indicates success + if (jsonResponse.has("success") && jsonResponse.get("success").getAsBoolean()) { + rspUUID = jsonResponse.get("uuid").getAsString(); + DataConfig.setRspUUID(rspUUID); + Logger.info("Server initialized successfully: " + jsonResponse.get("message").getAsString()); + } else { + // Server returned error in success status code + Logger.warn("Server returned error in response: " + responseString); + rspUUID = null; + } + } catch (Exception e) { + // JSON parsing failed - validate if it looks like a UUID before using it + String trimmedResponse = responseString.trim(); + if (isValidUUID(trimmedResponse)) { + rspUUID = trimmedResponse; + DataConfig.setRspUUID(rspUUID); + Logger.info("Server initialized with UUID: " + rspUUID); + } else { + Logger.warn("Invalid response format from server: " + responseString); + rspUUID = null; + } + } + } else { + // Error - parse and log detailed error message + handleErrorResponse(responseString, statusCode, "initialization"); + rspUUID = null; + } + } catch (Exception e) { + Logger.warn("Failed to communicate with remote server!"); + e.printStackTrace(); + rspUUID = null; + } + } catch (Exception e) { + rspUUID = null; + Logger.warn("Failed remote server initialization."); + e.printStackTrace(); + } + } + + // Helper method to validate UUID format + private static boolean isValidUUID(String uuidString) { + if (uuidString == null || uuidString.isEmpty()) { + return false; + } + + // Check if it looks like a UUID (basic validation) + // UUIDs are typically 36 characters with dashes or 32 characters without + String cleanUuid = uuidString.replaceAll("-", ""); + if (cleanUuid.length() != 32) { + return false; + } + + // Check if it contains only valid hex characters + return cleanUuid.matches("[0-9a-fA-F]+"); + } + + public static void uploadFile() { + Logger.info("Uploading resource!"); + + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpPost uploadFile = new HttpPost(finalURL + "upload"); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("uuid", rspUUID, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + builder.addBinaryBody("file", Mix.getFinalResourcePack(), ContentType.APPLICATION_OCTET_STREAM, Mix.getFinalResourcePack().getName()); + + uploadFile.setEntity(builder.build()); try (CloseableHttpResponse response = httpClient.execute(uploadFile)) { String responseString = EntityUtils.toString(response.getEntity()); - System.out.println("Response from server: " + responseString); + int statusCode = response.getCode(); + + if (statusCode >= 200 && statusCode < 300) { + Logger.info("Uploaded resource pack for automatic hosting! url: " + finalURL + rspUUID); + done = true; + if (firstUpload) { + //Recover from a reload by sending the pack to online players + for (Player player : Bukkit.getOnlinePlayers()) + AutoHost.sendResourcePack(player); + } + firstUpload = false; + } else { + // Handle detailed error messages from server + handleErrorResponse(responseString, statusCode, "upload"); + } } catch (Exception e) { - throw new RuntimeException(e); + Logger.warn("Failed to communicate with remote server during upload!"); + e.printStackTrace(); } + } catch (IOException e) { + throw new RuntimeException(e); } } - private static Boolean sendSHA1(String stringData, String url) throws IOException { + private static boolean sendSHA1() { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost httpPost = new HttpPost(url); + HttpPost httpPost = new HttpPost(finalURL + "sha1"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.addTextBody("stringData", stringData, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); - httpPost.setEntity(builder.build()); + builder.addTextBody("uuid", rspUUID, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + builder.addTextBody("sha1", Mix.getFinalSHA1(), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + + HttpEntity entity = builder.build(); + httpPost.setEntity(entity); try (CloseableHttpResponse response = httpClient.execute(httpPost)) { String responseString = EntityUtils.toString(response.getEntity()); - System.out.println("Response from server: " + responseString); - return Boolean.parseBoolean(responseString.trim()); + int statusCode = response.getCode(); + + if (statusCode >= 200 && statusCode < 300) { + try { + // Parse JSON response + Gson gson = new Gson(); + JsonObject jsonResponse = gson.fromJson(responseString, JsonObject.class); + + // Check if response indicates success + if (jsonResponse.has("success") && jsonResponse.get("success").getAsBoolean()) { + boolean uploadNeeded = jsonResponse.get("uploadNeeded").getAsBoolean(); + + if (!uploadNeeded) { + Logger.info("Remote server already has this resource pack!"); + done = true; + if (firstUpload) { + //Recover from a reload by sending the pack to online players + for (Player player : Bukkit.getOnlinePlayers()) + AutoHost.sendResourcePack(player); + } + firstUpload = false; + } + return !uploadNeeded; // Return true if no upload needed + } else { + // Server returned error in success status code + Logger.warn("Server returned error in SHA1 response: " + responseString); + return false; + } + } catch (Exception e) { + // Fallback to boolean parsing (backward compatibility) + String trimmedResponse = responseString.trim(); + if (trimmedResponse.equals("true") || trimmedResponse.equals("false")) { + boolean result = Boolean.valueOf(trimmedResponse); + Logger.info("Remote server already has this resource pack! Response: " + responseString); + return result; + } else { + Logger.warn("Invalid SHA1 response format from server: " + responseString); + return false; + } + } + } else { + // Handle error response + handleErrorResponse(responseString, statusCode, "SHA1 check"); + return false; + } } catch (Exception e) { - Logger.warn("Failed to communicate with remote server!"); + Logger.warn("Failed to communicate with remote server during SHA1 check!"); e.printStackTrace(); - return null; + return false; } + } catch (Exception e) { + Logger.warn("Failed to create HTTP client for SHA1 check!"); + e.printStackTrace(); + return false; } } - private static void sendStillAlive(String url) throws IOException { + private static void sendStillAlive() throws IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost httpPost = new HttpPost(url); + HttpPost httpPost = new HttpPost(finalURL + "still_alive"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.addTextBody("status", "alive", ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + builder.addTextBody("uuid", rspUUID); httpPost.setEntity(builder.build()); try (CloseableHttpResponse response = httpClient.execute(httpPost)) { String responseString = EntityUtils.toString(response.getEntity()); - System.out.println("Response from server: " + responseString); + int statusCode = response.getCode(); + + if (statusCode >= 200 && statusCode < 300) { + // Success - optionally log the success message + // Logger.info("Still alive ping successful"); + } else { + // Handle error - this might indicate session expired + handleErrorResponse(responseString, statusCode, "still alive"); + // Reset UUID to trigger re-initialization + rspUUID = null; + } + } catch (Exception e) { + Logger.warn("Failed to communicate with remote server during still alive ping!"); + e.printStackTrace(); + } + } + } + + public static void dataComplianceRequest() throws IOException { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpPost httpPost = new HttpPost(finalURL + "data_compliance"); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("uuid", rspUUID); + httpPost.setEntity(builder.build()); + + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + HttpEntity responseEntity = response.getEntity(); + + if (responseEntity != null) { + // Save the response as a zip file + File zipFile = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "data_compliance" + File.separatorChar + "data.zip"); + if (!zipFile.getParentFile().exists()) zipFile.mkdirs(); + if (zipFile.exists()) zipFile.delete(); + zipFile.createNewFile(); + try (FileOutputStream outStream = new FileOutputStream(zipFile)) { + responseEntity.writeTo(outStream); + } + InputStream inputStream = ResourcePackManager.plugin.getResource("ReadMe.md"); + + File readMe = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "data_compliance" + File.separatorChar + "ReadMe.md"); + if (!readMe.exists()) readMe.createNewFile(); + + // Copy the InputStream to the file + Files.copy(inputStream, readMe.toPath(), StandardCopyOption.REPLACE_EXISTING); + } } catch (Exception e) { Logger.warn("Failed to communicate with remote server!"); e.printStackTrace(); @@ -103,5 +351,55 @@ private static void sendStillAlive(String url) throws IOException { public static void shutdown() { if (keepAlive != null) keepAlive.cancel(); + done = false; + rspUUID = null; + } + + private static void handleErrorResponse(String responseString, int statusCode, String operation) { + try { + Gson gson = new Gson(); + JsonObject errorResponse = gson.fromJson(responseString, JsonObject.class); + + if (errorResponse.has("error")) { + JsonObject error = errorResponse.getAsJsonObject("error"); + String errorCode = error.get("code").getAsString(); + String errorMessage = error.get("message").getAsString(); + String errorType = error.get("type").getAsString(); + + Logger.warn("=== Resource Pack " + operation.toUpperCase() + " ERROR ==="); + Logger.warn("Error Code: " + errorCode); + Logger.warn("Error Type: " + errorType); + Logger.warn("Message: " + errorMessage); + Logger.warn("HTTP Status: " + statusCode); + Logger.warn("====================================="); + + // Handle specific error types + switch (errorCode) { + case "MISSING_REQUIRED_FILES": + Logger.warn("Your resource pack structure is incorrect!"); + Logger.warn("Make sure pack.png and pack.mcmeta are in the root of your zip file."); + break; + case "FILE_TOO_LARGE": + Logger.warn("Your resource pack is too large! Please reduce the file size."); + break; + case "INVALID_FILE_FORMAT": + Logger.warn("Your resource pack file is corrupted or not a valid zip file."); + break; + case "SESSION_NOT_FOUND": + Logger.warn("Server session expired. Will attempt to reinitialize..."); + rspUUID = null; // Trigger re-initialization + break; + case "SERVER_UNAVAILABLE": + Logger.warn("Remote server is temporarily unavailable. Will retry later."); + break; + } + } else { + // Fallback for non-JSON error responses + Logger.warn("Server error during " + operation + " (HTTP " + statusCode + "): " + responseString); + } + } catch (Exception e) { + // Fallback if JSON parsing fails + Logger.warn("Server error during " + operation + " (HTTP " + statusCode + "): " + responseString); + } } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/AdvancedCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/AdvancedCommand.java deleted file mode 100644 index a6a651a..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/AdvancedCommand.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.magmaguy.resourcepackmanager.commands; - -import org.bukkit.command.CommandSender; - -import java.util.ArrayList; -import java.util.List; - -public abstract class AdvancedCommand { - public final List aliases; - public final String description; - public final String permission; - public final boolean onlyForPlayers; - public final boolean enabled = true; - public final String usage; - - public AdvancedCommand(List aliases, String description, String permission, boolean onlyForPlayers, String usage) { - this.aliases = aliases; - this.description = description; - this.permission = permission; - this.onlyForPlayers = onlyForPlayers; - this.usage = usage; - } - - public abstract void execute(CommandSender sender, String[] arguments); - - public abstract List onTabComplete(CommandSender commandSender, org.bukkit.command.Command command, String label, String[] args); - - protected List trimSuggestions(List suggestions, String input) { - if (input.isEmpty()) return suggestions; - List newList = new ArrayList<>(); - for (String suggestion : suggestions) - if (suggestion.contains(input)) - newList.add(suggestion); - return newList; - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java deleted file mode 100644 index 520ffff..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.magmaguy.resourcepackmanager.commands; - -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -import java.util.ArrayList; -import java.util.List; - -public class CommandManager implements CommandExecutor, TabCompleter { - public final List commands = new ArrayList<>(); - - public CommandManager(JavaPlugin javaPlugin) { - javaPlugin.getCommand("resourcepackmanager").setExecutor(this); - registerCommands(); - } - - private void registerCommands() { - registerCommand(new ReloadCommand()); - } - - public void registerCommand(AdvancedCommand command) { - commands.add(command); - } - - public void unregisterCommand(Command command) { - commands.remove(command); - } - - - private void sendMessage(CommandSender commandSender, String message) { - commandSender.sendMessage("[ResourcePackManager] " + message); - } - - @Override - public boolean onCommand(CommandSender commandSender, Command cmd, String label, String[] args) { - if (args.length == 0) { - sendMessage(commandSender, "Valid commands:"); - commands.forEach(command -> commandSender.sendMessage(command.usage)); - return true; - } - - for (AdvancedCommand command : commands) { - // We don't want to execute other commands or ones that are disabled - if (!(command.aliases.contains(args[0]) && command.enabled)) { - continue; - } - - if (command.onlyForPlayers && !(commandSender instanceof Player)) { - // Must be a player - commandSender.sendMessage("[ResourcePackManager] This command must be run as a player!"); - return false; - } - - if (!((commandSender.hasPermission(command.permission) || - command.permission.equalsIgnoreCase("") || - command.permission.equalsIgnoreCase("resourcepackmanager.")) && - command.enabled)) { - // No permissions - commandSender.sendMessage("[ResourcePackManager] You do not have the permission to run this command!"); - return false; - } - - command.execute(commandSender, args); - return true; - } - // Unknown command message - commandSender.sendMessage("[ResourcePackManager] Unknown command!"); - return false; - } - - @Override - public List onTabComplete(CommandSender commandSender, Command cmd, String label, String[] args) { - // Handle the tab completion if it's a sub-command. - if (args.length == 1) { - List result = new ArrayList<>(); - for (AdvancedCommand command : commands) { - for (String alias : command.aliases) { - if (alias.toLowerCase().startsWith(args[0].toLowerCase()) && ( - command.enabled && (commandSender.hasPermission(command.permission) - || command.permission.equalsIgnoreCase("") || command.permission - .equalsIgnoreCase("resourcepackmanager.")))) { - result.add(alias); - } - } - } - return result; - } - - // Let the sub-command handle the tab completion - for (AdvancedCommand command : commands) { - if (command.aliases.contains(args[0]) && (command.enabled && ( - commandSender.hasPermission(command.permission) || command.permission.equalsIgnoreCase("") - || command.permission.equalsIgnoreCase("resourcepackmanager.")))) { - return command.onTabComplete(commandSender, cmd, label, args); - } - } - return null; - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java new file mode 100644 index 0000000..6b4475c --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java @@ -0,0 +1,41 @@ +package com.magmaguy.resourcepackmanager.commands; + +import com.magmaguy.magmacore.command.AdvancedCommand; +import com.magmaguy.magmacore.command.CommandData; +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.autohost.AutoHost; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.List; + +public class DataComplianceRequestCommand extends AdvancedCommand { + + public DataComplianceRequestCommand() { + super(List.of("data_compliance_request")); + setDescription("Downloads a copy of all data associated to this server from the autohoster"); + setPermission("resourcepackmanager.*"); + setUsage("/fmm data_compliance_request"); + } + + @Override + public void execute(CommandData commandData) { + if (AutoHost.getRspUUID() == null) { + Logger.sendMessage(commandData.getCommandSender(), "Seems like the auto-hoster is either disabled or not working, no data is stored in remote servers because no connection to remote servers is established"); + return; + } + + new BukkitRunnable() { + @Override + public void run() { + try { + AutoHost.dataComplianceRequest(); + Logger.sendMessage(commandData.getCommandSender(), "Data compliance request completed, check ~/plugins/ResourcePackManager/data_compliance to see all data the remote server has stored for this server."); + } catch (Exception e) { + Logger.sendMessage(commandData.getCommandSender(), "Failed to request data, check console for error logs!"); + e.printStackTrace(); + } + } + }.runTaskAsynchronously(ResourcePackManager.plugin); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java index 7da4f08..5feb568 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java @@ -1,30 +1,31 @@ package com.magmaguy.resourcepackmanager.commands; -import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.magmacore.command.AdvancedCommand; +import com.magmaguy.magmacore.command.CommandData; +import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.ResourcePackManager; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import java.util.List; public class ReloadCommand extends AdvancedCommand { public ReloadCommand() { - super(List.of("reload"), "Reloads the plugin", "*", false, "/fmm reload"); + super(List.of("reload")); + setDescription("Reloads the plugin"); + setPermission("resourcepackmanager.*"); + setUsage("/rspm reload"); } public static void reloadPlugin(CommandSender sender) { ResourcePackManager.plugin.onDisable(); ResourcePackManager.plugin.onEnable(); - sender.sendMessage("[ResourcePackManager] Reloaded plugin!"); + Logger.sendMessage(sender, "Reloaded the plugin!"); } @Override - public void execute(CommandSender sender, String[] arguments) { - reloadPlugin(sender); - } - - @Override - public List onTabComplete(CommandSender commandSender, Command command, String label, String[] args) { - return null; + public void execute(CommandData commandData) { + ResourcePackManager.plugin.onDisable(); + ResourcePackManager.plugin.onEnable(); + Logger.sendMessage(commandData.getCommandSender(), "Reloaded the plugin!"); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java new file mode 100644 index 0000000..3a8c9f4 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java @@ -0,0 +1,47 @@ +package com.magmaguy.resourcepackmanager.config; + +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.magmacore.util.ZipFile; +import com.magmaguy.resourcepackmanager.ResourcePackManager; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; + +public class BlueprintFolder { + private BlueprintFolder() { + } + + public static void initialize() { + Logger.info("Creating blueprint folder"); + File blueprintDirectory = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "blueprint"); + if (!blueprintDirectory.exists()) blueprintDirectory.mkdir(); + Logger.info("Copying image"); + File imageFile = new File(blueprintDirectory.getAbsolutePath() + File.separatorChar + "pack.png"); + if (!imageFile.exists()) { + try { + InputStream inputStream = ResourcePackManager.plugin.getResource("pack.png"); + Files.copy(inputStream, imageFile.toPath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + Logger.info("Copying mcmeta"); + File mcmetaFile = new File(blueprintDirectory.getAbsolutePath() + File.separatorChar + "pack.mcmeta"); + if (!mcmetaFile.exists()) { + try { + InputStream inputStream = ResourcePackManager.plugin.getResource("pack.mcmeta"); + Files.copy(inputStream, mcmetaFile.toPath()); + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + try { + ZipFile.ZipUtility.zip(blueprintDirectory, blueprintDirectory.getAbsolutePath() + File.separatorChar + "blueprint.zip"); + } catch (Exception e) { + Logger.warn("Failed to zip blueprint resource pack!"); + } + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java b/src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java deleted file mode 100644 index 7e6c8a0..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.magmaguy.resourcepackmanager.config; - -import com.magmaguy.resourcepackmanager.Logger; -import com.magmaguy.resourcepackmanager.ResourcePackManager; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.List; - -public class ConfigurationEngine { - - public static File fileCreator(String path, String fileName) { - File file = new File(ResourcePackManager.plugin.getDataFolder().getPath() + "/" + path + "/", fileName); - return fileCreator(file); - } - - public static File fileCreator(String fileName) { - File file = new File(ResourcePackManager.plugin.getDataFolder().getPath(), fileName); - return fileCreator(file); - } - - public static File fileCreator(File file) { - - if (!file.exists()) - try { - file.getParentFile().mkdirs(); - file.createNewFile(); - } catch (IOException ex) { - Bukkit.getLogger().warning("[EliteMobs] Error generating the plugin file: " + file.getName()); - } - - return file; - - } - - public static FileConfiguration fileConfigurationCreator(File file) { - try { - return YamlConfiguration.loadConfiguration(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); - } catch (Exception exception) { - Logger.warn("Failed to read configuration from file " + file.getName()); - return null; - } - } - - public static void fileSaverCustomValues(FileConfiguration fileConfiguration, File file) { - fileConfiguration.options().copyDefaults(true); - - try { - fileConfiguration.save(file); - } catch (IOException e) { - e.printStackTrace(); - } - - } - - public static void fileSaverOnlyDefaults(FileConfiguration fileConfiguration, File file) { - fileConfiguration.options().copyDefaults(true); - UnusedNodeHandler.clearNodes(fileConfiguration); - - try { - fileConfiguration.save(file); - } catch (IOException e) { - e.printStackTrace(); - } - - } - - private static void setComments(FileConfiguration fileConfiguration, String key, List comments) { - fileConfiguration.setComments(key, comments); - } - - public static Boolean setBoolean(FileConfiguration fileConfiguration, String key, boolean defaultValue) { - fileConfiguration.addDefault(key, defaultValue); - return fileConfiguration.getBoolean(key); - } - - public static Boolean setBoolean(List comments, FileConfiguration fileConfiguration, String key, boolean defaultValue) { - boolean value = setBoolean(fileConfiguration, key, defaultValue); - setComments(fileConfiguration, key, comments); - return value; - } - - - public static int setInt(FileConfiguration fileConfiguration, String key, int defaultValue) { - fileConfiguration.addDefault(key, defaultValue); - return fileConfiguration.getInt(key); - } - - public static int setInt(List comments, FileConfiguration fileConfiguration, String key, int defaultValue) { - int value = setInt(fileConfiguration, key, defaultValue); - setComments(fileConfiguration, key, comments); - return value; - } - - public static double setDouble(FileConfiguration fileConfiguration, String key, double defaultValue) { - fileConfiguration.addDefault(key, defaultValue); - return fileConfiguration.getDouble(key); - } - - public static double setDouble(List comments, FileConfiguration fileConfiguration, String key, double defaultValue) { - double value = setDouble(fileConfiguration, key, defaultValue); - setComments(fileConfiguration, key, comments); - return value; - } - - public static boolean writeValue(Object value, File file, FileConfiguration fileConfiguration, String path) { - fileConfiguration.set(path, value); - try { - fileSaverCustomValues(fileConfiguration, file); - } catch (Exception exception) { - Logger.warn("Failed to write value for " + path + " in file " + file.getName()); - return false; - } - return true; - } - - public static void removeValue(File file, FileConfiguration fileConfiguration, String path) { - writeValue(null, file, fileConfiguration, path); - } - - public static List setList(File file, FileConfiguration fileConfiguration, String key, List defaultValue) { - fileConfiguration.addDefault(key, defaultValue); - return fileConfiguration.getList(key); - } - - public static List setList(List comment, File file, FileConfiguration fileConfiguration, String key, List defaultValue) { - List value = setList(file, fileConfiguration, key, defaultValue); - setComments(fileConfiguration, key, comment); - return value; - } - - -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java new file mode 100644 index 0000000..d8b25a0 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java @@ -0,0 +1,95 @@ +package com.magmaguy.resourcepackmanager.config; + +import com.magmaguy.magmacore.config.ConfigurationFile; +import com.magmaguy.magmacore.util.Logger; + +import java.util.UUID; + +public class DataConfig extends ConfigurationFile { + private static DataConfig instance = null; + + public DataConfig() { + super("data.yml"); + instance = this; + } + + public static String getRspUUID() { + String uuid = instance.getFileConfiguration().getString("uuid"); + + // If UUID exists but is invalid, clear it and return null + if (uuid != null && !uuid.isEmpty() && !isValidUUID(uuid)) { + Logger.warn("Invalid UUID found in config file: " + uuid + ". Deleting invalid UUID from file."); + instance.clearInvalidUUID(); + return null; + } + + // Return null for empty/null UUIDs instead of empty string + return (uuid == null || uuid.isEmpty()) ? "" : uuid; + } + + public static void setRspUUID(String rspUUID) { + // Validate UUID if not null/empty + if (rspUUID != null && !rspUUID.isEmpty() && !isValidUUID(rspUUID)) { + throw new IllegalArgumentException("Invalid UUID format: " + rspUUID + + ". UUID must be in format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); + } + + // Store null as null, not empty string + instance.getFileConfiguration().set("uuid", rspUUID); + try { + instance.getFileConfiguration().save(instance.file); + if (rspUUID != null && !rspUUID.isEmpty()) { + Logger.info("Successfully saved UUID: " + rspUUID); + } else { + Logger.info("Successfully cleared UUID from config."); + } + } catch (Exception e) { + Logger.warn("Failed to save uuid!"); + e.printStackTrace(); + } + } + + /** + * Clears invalid UUID from config file + */ + private void clearInvalidUUID() { + getFileConfiguration().set("uuid", null); + try { + getFileConfiguration().save(file); + Logger.info("Invalid UUID cleared from config file."); + } catch (Exception e) { + Logger.warn("Failed to clear invalid UUID from config!"); + e.printStackTrace(); + } + } + + /** + * Validates if a string is a proper UUID format + * @param uuidString The string to validate + * @return true if valid UUID format, false otherwise + */ + private static boolean isValidUUID(String uuidString) { + if (uuidString == null || uuidString.trim().isEmpty()) { + return false; + } + + try { + // Use Java's built-in UUID validation + UUID.fromString(uuidString.trim()); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + @Override + public void initializeValues() { + // Validate existing UUID on initialization and clear if invalid + String existingUUID = getFileConfiguration().getString("uuid"); + if (existingUUID != null && !existingUUID.isEmpty() && !isValidUUID(existingUUID)) { + Logger.warn("Invalid UUID found in config file during initialization: " + existingUUID); + Logger.warn("Deleting invalid UUID from file. A new one will be generated on next server connection."); + clearInvalidUUID(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java index 702aea3..67c4d29 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java @@ -1,38 +1,71 @@ package com.magmaguy.resourcepackmanager.config; +import com.magmaguy.magmacore.config.ConfigurationEngine; +import com.magmaguy.magmacore.config.ConfigurationFile; import lombok.Getter; -import org.bukkit.configuration.file.FileConfiguration; -import java.io.File; import java.util.List; -public class DefaultConfig { - private static File file = null; - private static FileConfiguration fileConfiguration = null; +public class DefaultConfig extends ConfigurationFile { + @Getter private static List priorityOrder; @Getter private static boolean autoHost; + @Getter + private static boolean forceResourcePack; + @Getter + private static String resourcePackPrompt; + @Getter + private static String resourcePackRerouting; + - public static void initializeConfig() { - file = ConfigurationEngine.fileCreator("config.yml"); - fileConfiguration = ConfigurationEngine.fileConfigurationCreator(file); + public DefaultConfig() { + super("config.yml"); + } + @Override + public void initializeValues() { priorityOrder = ConfigurationEngine.setList( List.of( "Sets the list, from highest priority (top) to lowest priority (bottom), in which the resource" + " packs will automatically resolve merge conflicts.", "The defaults use plugin names. If you manually added your own resource pack in the mixer folder to be merged in, add its exact filename, including .zip in the name"), - file, fileConfiguration, "priorityOrder", - List.of("ResourcePackManager", "EliteMobs", "FreeMinecraftModels", "ModelEngine", "ItemsAdder", "Nova", "Oraxen")); + fileConfiguration, "priorityOrder", + List.of( + "ResourcePackManager", + "EliteMobs", + "FreeMinecraftModels", + "ModelEngine", + "Nova", + "ItemsAdder", + "Oraxen", + "BetterHUD", + "ValhallaMMO", + "MMOInventory", + "vane-core", + "RealisticSurvival", + "Nexo")); autoHost = ConfigurationEngine.setBoolean( List.of("Automatically host the resource pack on MagmaGuy's servers", "These servers cost money to keep running. There is no guarantee this will be an option forever."), fileConfiguration, "autoHost", true); - ConfigurationEngine.fileSaverOnlyDefaults(fileConfiguration, file); + forceResourcePack = ConfigurationEngine.setBoolean( + List.of("Sets whether the resource pack use will be forced to clients"), + fileConfiguration, "forceResourcePack", false); + resourcePackPrompt = ConfigurationEngine.setString( + List.of("Sets whether the resource pack use will be forced to clients"), + fileConfiguration, "resourcePackPrompt", "Use recommended resource pack?"); + resourcePackRerouting = ConfigurationEngine.setString( + List.of( + "OPTIONAL: Copy the merged directory to a custom directory location. Useful for unusual setups, like people trying to host with a different plugin.", + "If you are hosting with a different plugin make sure to disable Auto-hosting here!", + "This will use the plugin directory as the base directory.", + "As an example, if you wanted to target ResourcePackManager's output folder, you'd do:", + "ResourcePackManage/output", + "If you don't know what any of what is written here means, just don't touch this setting!"), + fileConfiguration, "resourcePackRerouting", ""); } - - } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/UnusedNodeHandler.java b/src/main/java/com/magmaguy/resourcepackmanager/config/UnusedNodeHandler.java deleted file mode 100644 index e05eb13..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/UnusedNodeHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.magmaguy.resourcepackmanager.config; - -import com.magmaguy.resourcepackmanager.Logger; -import org.bukkit.Bukkit; -import org.bukkit.configuration.Configuration; -import org.bukkit.configuration.file.FileConfiguration; - -public class UnusedNodeHandler { - - private UnusedNodeHandler() { - } - - public static Configuration clearNodes(FileConfiguration configuration) { - - for (String actual : configuration.getKeys(false)) { - boolean keyExists = false; - for (String defaults : configuration.getDefaults().getKeys(true)) - if (actual.equals(defaults)) { - keyExists = true; - break; - } - - if (!keyExists) { - configuration.set(actual, null); - Bukkit.getLogger().warning(actual); - Logger.warn("Deleting unused config values."); - } - } - return configuration; - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfig.java new file mode 100644 index 0000000..b1223fe --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfig.java @@ -0,0 +1,19 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins; + +import com.magmaguy.magmacore.config.CustomConfig; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +public class CompatiblePluginConfig extends CustomConfig { + @Getter + private static Map compatiblePlugins = new HashMap<>(); + + public CompatiblePluginConfig() { + super("compatible_plugins", "com.magmaguy.resourcepackmanager.config.compatibleplugins.premade", CompatiblePluginConfigFields.class); + compatiblePlugins = new HashMap<>(); + for (String key : super.getCustomConfigFieldsHashMap().keySet()) + compatiblePlugins.put(key, (CompatiblePluginConfigFields) super.getCustomConfigFieldsHashMap().get(key)); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java new file mode 100644 index 0000000..2f6b2f2 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java @@ -0,0 +1,57 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins; + +import com.magmaguy.magmacore.config.CustomConfigFields; +import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; +import lombok.Getter; +import lombok.Setter; + +public class CompatiblePluginConfigFields extends CustomConfigFields { + + @Getter + @Setter + private String pluginName = "placeholder"; + @Getter + @Setter + private String url = null; + @Getter + @Setter + private String localPath = null; + @Getter + @Setter + private boolean encrypts = false; + @Getter + @Setter + private boolean distributes = false; + @Getter + @Setter + private boolean zips = true; + @Getter + @Setter + private String reloadCommand; + @Getter + @Setter + private boolean resourcePackUpdated = false; + @Getter + @Setter + private String mixerFilename; + + public CompatiblePluginConfigFields(String filename, boolean isEnabled) { + super(filename, isEnabled); + } + + @Override + public void processConfigFields() { + this.isEnabled = processBoolean("isEnabled", isEnabled, isEnabled, true); + this.pluginName = processString("pluginName", pluginName, pluginName, true); + this.url = processString("url", url, url, true); + this.encrypts = processBoolean("encrypts", encrypts, encrypts, true); + this.distributes = processBoolean("distributes", distributes, distributes, true); + this.zips = processBoolean("zips", zips, zips, true); + this.reloadCommand = processString("reloadCommand", reloadCommand, reloadCommand, true); + this.resourcePackUpdated = processBoolean("resourcePackUpdated", resourcePackUpdated, resourcePackUpdated, true); + this.mixerFilename = processString("mixerFilename", mixerFilename, mixerFilename, true); + this.localPath = processString("localPath", localPath, localPath, true); + + if (isEnabled) ThirdPartyResourcePack.initializeThirdPartyResourcePack(this); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BackpackPlus.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BackpackPlus.java new file mode 100644 index 0000000..d829860 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BackpackPlus.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class BackpackPlus extends CompatiblePluginConfigFields { + public BackpackPlus() { + super("backpack_plus", true); + setPluginName("BackpackPlus"); + setLocalPath("BackpackPlus" + File.separatorChar + "pack" + File.separatorChar + "resourcepack.zip"); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java new file mode 100644 index 0000000..56235e9 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java @@ -0,0 +1,15 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class BetterHUD extends CompatiblePluginConfigFields { + public BetterHUD() { + super("better_hud", true); + setPluginName("BetterHUD"); + setLocalPath("BackpackPlus" + File.separatorChar + "pack" + File.separatorChar + "resourcepack.zip"); + setEncrypts(true); + setReloadCommand("betterhud reload"); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java new file mode 100644 index 0000000..3db7b9a --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class EliteMobs extends CompatiblePluginConfigFields { + public EliteMobs() { + super("elitemobs", true); + setPluginName("EliteMobs"); + setLocalPath("EliteMobs" + File.separatorChar + "exports" + File.separatorChar + "elitemobs_resource_pack.zip"); + setReloadCommand("elitemobs reload"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/FreeMinecraftModels.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/FreeMinecraftModels.java new file mode 100644 index 0000000..44ab1f0 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/FreeMinecraftModels.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class FreeMinecraftModels extends CompatiblePluginConfigFields { + public FreeMinecraftModels() { + super("free_minecraft_models", true); + setPluginName("FreeMinecraftModels"); + setLocalPath("FreeMinecraftModels" + File.separatorChar + "output" + File.separatorChar + "FreeMinecraftModels.zip"); + setReloadCommand("freeminecraftmodels reload"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/InfiniteVehicles.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/InfiniteVehicles.java new file mode 100644 index 0000000..18f2587 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/InfiniteVehicles.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class InfiniteVehicles extends CompatiblePluginConfigFields { + public InfiniteVehicles() { + super("infinite_vehicles", true); + setPluginName("InfiniteVehicles"); + setLocalPath("InfiniteVehicles" + File.separatorChar + "InfiniteModelPack.zip"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java new file mode 100644 index 0000000..01b18d0 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java @@ -0,0 +1,15 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class ItemsAdder extends CompatiblePluginConfigFields { + public ItemsAdder() { + super("items_adder", true); + setPluginName("ItemsAdder"); + setLocalPath("ItemsAdder" + File.separatorChar + "output" + File.separatorChar + "generated.zip"); + setEncrypts(true); + setDistributes(true); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MMOInventory.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MMOInventory.java new file mode 100644 index 0000000..d357c0d --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MMOInventory.java @@ -0,0 +1,11 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +public class MMOInventory extends CompatiblePluginConfigFields { + public MMOInventory() { + super("mmo_inventory", true); + setPluginName("MMOInventory"); + setUrl("https://www.dropbox.com/s/1lftxmzh0q4b5yu/mmoinv_rp_3.zip?dl=1"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ModelEngine.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ModelEngine.java new file mode 100644 index 0000000..d16a585 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ModelEngine.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class ModelEngine extends CompatiblePluginConfigFields { + public ModelEngine() { + super("model_engine", true); + setPluginName("ModelEngine"); + setLocalPath("ModelEngine" + File.separatorChar + "resource pack.zip"); + setReloadCommand("meg reload"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nexo.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nexo.java new file mode 100644 index 0000000..552fc7b --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nexo.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class Nexo extends CompatiblePluginConfigFields { + public Nexo() { + super("nexo", true); + setPluginName("Nexo"); + setLocalPath("Nexo" + File.separatorChar + "pack" + File.separatorChar + "pack.zip"); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nova.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nova.java new file mode 100644 index 0000000..ce68b14 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nova.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class Nova extends CompatiblePluginConfigFields { + public Nova() { + super("nova", true); + setPluginName("Nova"); + setLocalPath("Nova" + File.separatorChar + "resource_pack" + File.separatorChar + "ResourcePack.zip"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Oraxen.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Oraxen.java new file mode 100644 index 0000000..68ca0a5 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Oraxen.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class Oraxen extends CompatiblePluginConfigFields { + public Oraxen() { + super("oraxen", true); + setPluginName("Oraxen"); + setLocalPath("Oraxen" + File.separatorChar + "pack" + File.separatorChar + "pack.zip"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/RealisticSurvival.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/RealisticSurvival.java new file mode 100644 index 0000000..774f6f6 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/RealisticSurvival.java @@ -0,0 +1,11 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +public class RealisticSurvival extends CompatiblePluginConfigFields { + public RealisticSurvival() { + super("realistic_survival", true); + setPluginName("RealisticSurvival"); + setUrl("https://www.dropbox.com/scl/fi/5ggc7t20bfxmrzsbb1kok/Realistic-Survival-RP-1.2.8-RELEASE.zip?rlkey=55llpn4zj146usfmm8okf1ier&dl=1"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ResourcePackManager.java new file mode 100644 index 0000000..b69b50a --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ResourcePackManager.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class ResourcePackManager extends CompatiblePluginConfigFields { + public ResourcePackManager() { + super("resource_pack_manager", true); + setPluginName("ResourcePackManager"); + setLocalPath("ResourcePackManager" + File.separatorChar + "blueprint" + File.separatorChar + "blueprint.zip"); + setReloadCommand("rspm reload"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ValhallaMMO.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ValhallaMMO.java new file mode 100644 index 0000000..e4a506b --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ValhallaMMO.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class ValhallaMMO extends CompatiblePluginConfigFields { + public ValhallaMMO() { + super("valhalla_mmo", true); + setPluginName("ValhallaMMO"); + setLocalPath("ValhallaMMO" + File.separatorChar + "resourcepack"); + setZips(false); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java new file mode 100644 index 0000000..da73fa3 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java @@ -0,0 +1,12 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +public class VaneCore extends CompatiblePluginConfigFields { + public VaneCore() { + super("vane_core", true); + setPluginName("vane-core"); + setLocalPath("vane-resource-pack.zip"); + setDistributes(true); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 608d64e..cd43a84 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -2,14 +2,14 @@ import com.google.gson.*; import com.google.gson.stream.JsonReader; -import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.magmacore.util.ZipFile; import com.magmaguy.resourcepackmanager.ResourcePackManager; -import com.magmaguy.resourcepackmanager.thirdparty.EliteMobs; -import com.magmaguy.resourcepackmanager.thirdparty.FreeMinecraftModels; -import com.magmaguy.resourcepackmanager.thirdparty.ModelEngine; +import com.magmaguy.resourcepackmanager.api.ResourcePackManagerAPI; +import com.magmaguy.resourcepackmanager.autohost.AutoHost; +import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; -import com.magmaguy.resourcepackmanager.utils.ZipFile; import lombok.Getter; import java.io.File; @@ -18,34 +18,37 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributeView; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileTime; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import java.util.concurrent.TimeUnit; +import java.util.Objects; public class Mix { private static final String resourcePackName = "ResourcePackManager_RSP"; - private static List thirdPartyResourcePacks; + private static List resourcePacks; + private static List orderedResourcePacks; @Getter private static File finalResourcePack; @Getter private static String finalSHA1; + @Getter + private static byte[] finalSHA1Bytes; + private static File mixerFolder; private Mix() { } - public static void initialize() { + public static void mixResourcePacks() { if (!initializeDefaultPluginFolders()) return; initializeThirdPartyResourcePacks(); - cloneToOutPutAndUnzip(); + cloneToOutputAndUnzip(); createOutputDefaultElements(); + AutoHost.initialize(); } private static boolean initializeDefaultPluginFolders() { try { - File mixerFolder = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "mixer"); + mixerFolder = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "mixer"); if (!mixerFolder.exists()) mixerFolder.mkdir(); File outputFolder = getOutputFolder(); @@ -59,47 +62,119 @@ private static boolean initializeDefaultPluginFolders() { } private static void initializeThirdPartyResourcePacks() { - ArrayList tempList = new ArrayList<>(); - EliteMobs eliteMobs = new EliteMobs(); - if (eliteMobs.isEnabled()) - tempList.add(eliteMobs); - FreeMinecraftModels freeMinecraftModels = new FreeMinecraftModels(); - if (freeMinecraftModels.isEnabled()) - tempList.add(freeMinecraftModels); - ModelEngine modelEngine = new ModelEngine(); - if (modelEngine.isEnabled()) - tempList.add(modelEngine); - //todo: add the rest - thirdPartyResourcePacks = new ArrayList<>(); - for (int i = 0; i < tempList.size(); i++) { - for (ThirdPartyResourcePack thirdPartyResourcePack : tempList) { - if (thirdPartyResourcePack.getPriority() == i) { - thirdPartyResourcePacks.add(thirdPartyResourcePack); - tempList.remove(thirdPartyResourcePack); - break; + orderedResourcePacks = new ArrayList<>(); + + // Use a mutable list so we can addAll() to it + List resourcePackManagers = new ArrayList<>(ThirdPartyResourcePack.thirdPartyResourcePacks.stream().toList()); + + // Add any API-registered packs + resourcePackManagers.addAll(ResourcePackManagerAPI.thirdPartyResourcePackHashMap.values()); + + // Filter enabled packs into a working list + List tempList = new ArrayList<>(); + resourcePackManagers.stream() + .filter(ThirdPartyResourcePack::isEnabled) + .forEach(tempList::add); + + resourcePacks = new ArrayList<>(); + List customFiles = new ArrayList<>(); + List thirdPartyFiles = new ArrayList<>(); + + // Collect actual pack files from each enabled pack + tempList.forEach(rsp -> { + File mixerPack = rsp.getMixerResourcePack(); + if (mixerPack != null) { + thirdPartyFiles.add(mixerPack); + } + }); + + // Separate out truly custom files in the mixer folder + for (File file : mixerFolder.listFiles()) { + boolean isThirdParty = thirdPartyFiles.stream() + .anyMatch(tp -> tp.getName().equals(file.getName())); + if (!isThirdParty) { + customFiles.add(file); + } + } + + // Add in order of configured priority + List priorityOrder = DefaultConfig.getPriorityOrder(); + for (int i = 0; i < priorityOrder.size(); i++) { + boolean foundAtThisPriority = false; + + Iterator it = tempList.iterator(); + while (it.hasNext()) { + ThirdPartyResourcePack pack = it.next(); + if (pack.getPriority() == i) { + File f = pack.getMixerResourcePack(); + if (f != null) { + resourcePacks.add(f); + orderedResourcePacks.add(f.getName().replace(".zip", "")); + it.remove(); + foundAtThisPriority = true; + break; + } + } + } + + if (!foundAtThisPriority) { + Iterator fileIt = customFiles.iterator(); + while (fileIt.hasNext()) { + File custom = fileIt.next(); + int prio = priorityOrder.indexOf(custom.getName()); + if (prio == i) { + resourcePacks.add(custom); + orderedResourcePacks.add(custom.getName().replace(".zip", "")); + fileIt.remove(); + } } } } - thirdPartyResourcePacks.addAll(tempList); + + // Any remaining third-party packs without explicit priority + tempList.stream() + .map(ThirdPartyResourcePack::getMixerResourcePack) + .filter(Objects::nonNull) + .forEach(packFile -> { + resourcePacks.add(packFile); + orderedResourcePacks.add(packFile.getName().replace(".zip", "")); + Logger.info("Added supported resource pack " + packFile.getName() + " without a priority!"); + }); + + // Any leftover custom files without explicit priority + customFiles.forEach(customFile -> { + resourcePacks.add(customFile); + orderedResourcePacks.add(customFile.getName().replace(".zip", "")); + Logger.info("Added custom resource pack " + customFile.getName() + " without a priority!"); + }); } - private static void cloneToOutPutAndUnzip() { - thirdPartyResourcePacks.forEach(thirdPartyResourcePack -> { + private static void cloneToOutputAndUnzip() { + resourcePacks.forEach(resourcePack -> { try { - File file = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "output" + File.separatorChar + thirdPartyResourcePack.getMixerResourcePack().getName().replace(".zip", "")); - ZipFile.unzip(thirdPartyResourcePack.getMixerResourcePack(), file); + if (resourcePack == null) { + Logger.warn("A resource pack was null by the time it was meant to be unzipped!"); + return; + } + File file = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "output" + File.separatorChar + resourcePack.getName().replace(".zip", "")); + ZipFile.unzip(resourcePack, file); stripDirectoryMetadata(file); } catch (Exception e) { - Logger.warn("Failed to extract file " + thirdPartyResourcePack.getMixerResourcePack().getAbsolutePath() + " ! The file might be encrypted."); + if (resourcePack == null) + Logger.warn("Failed to extract resource pack! A file might be encrypted."); + else + Logger.warn("Failed to extract resource pack " + resourcePack.getName() + " ! The file might be encrypted."); e.printStackTrace(); } }); } private static void createOutputDefaultElements() { + //Clear old resource pack if (getOutputResourcePackFolder().exists()) { recursivelyDeleteDirectory(getOutputResourcePackFolder()); } + //Make sure new resource pack exists try { getOutputResourcePackFolder().mkdir(); } catch (Exception e) { @@ -107,8 +182,13 @@ private static void createOutputDefaultElements() { throw new RuntimeException(e); } - for (File file : getOutputFolder().listFiles()) { - if (file.getName().equals(resourcePackName)) continue; + List orderedFiles = new ArrayList<>(); + for (String filename : orderedResourcePacks) { + orderedFiles.add(new File(getOutputFolder().getPath() + File.separatorChar + filename)); + } + + for (File file : orderedFiles) { + if (file.getName().equals(resourcePackName + ".zip")) continue; if (!file.isDirectory()) { if (file.getName().endsWith(".zip")) continue; Logger.warn("Somehow a non-folder file made its way to the output folder! This isn't good. File: " + file.getAbsolutePath()); @@ -122,11 +202,29 @@ private static void createOutputDefaultElements() { Logger.warn("Failed to zip merged resource pack!"); return; } + + if (!DefaultConfig.getResourcePackRerouting().isEmpty() && !DefaultConfig.getResourcePackRerouting().isBlank()) { + try { + File rerouteFolder = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().getAbsolutePath() + File.separatorChar + DefaultConfig.getResourcePackRerouting()); + if (!rerouteFolder.exists()) { + Logger.warn("Failed to reroute zipped file to " + rerouteFolder.getAbsolutePath() + " because that folder does not exist!"); + } else if (!rerouteFolder.isDirectory()) { + Logger.warn("Failed to reroute zipped file to " + rerouteFolder.getAbsolutePath() + " because that is a file and not a folder!"); + } else if (!ZipFile.zip(getOutputResourcePackFolder(), rerouteFolder.getPath() + ".zip")) { + Logger.warn("Failed to zip merged resource pack into reroute directory!"); + return; + } + } catch (Exception e) { + Logger.warn("Failed to reroute zipped file to " + DefaultConfig.getResourcePackRerouting()); + } + } + for (File file : getOutputFolder().listFiles()) { if (file.getName().equals(resourcePackName + ".zip")) { finalResourcePack = file; try { finalSHA1 = SHA1Generator.sha1CodeString(finalResourcePack); + finalSHA1Bytes = SHA1Generator.sha1CodeByteArray(finalResourcePack); } catch (Exception e) { Logger.warn("Failed to get SHA1 from zipped resource pack!"); finalResourcePack = null; @@ -135,6 +233,9 @@ private static void createOutputDefaultElements() { } recursivelyDeleteDirectory(file); } + +// // ADD THE BEDROCK CONVERSION CALL HERE - AFTER finalResourcePack IS SET: +// generateBedrockResourcePack(); } private static File getOutputFolder() { @@ -154,14 +255,12 @@ private static void recursivelyDeleteDirectory(File directory) { Files.delete(directory.toPath()); } catch (Exception e) { Logger.warn("Failed to delete directory " + directory.getPath()); -// e.printStackTrace(); } } else { try { Files.delete(directory.toPath()); } catch (IOException e) { Logger.warn("Failed to delete file " + directory.getPath()); -// e.printStackTrace(); } } } @@ -280,4 +379,6 @@ private static JsonArray mergeJsonArrays(JsonArray array1, JsonArray array2) { return mergedArray; } + } + diff --git a/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java b/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java new file mode 100644 index 0000000..d75e125 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.playermanager; + +import com.magmaguy.resourcepackmanager.autohost.AutoHost; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +public class PlayerManager implements Listener { + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + AutoHost.sendResourcePack(event.getPlayer()); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java deleted file mode 100644 index 6a69bb7..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import lombok.Getter; - -import java.io.File; - -public class EliteMobs extends ThirdPartyResourcePack { - public EliteMobs() { - super("EliteMobs", - "EliteMobs" + File.separatorChar + "exports" + File.separatorChar + "elitemobs_resource_pack.zip", - false, - false, - "elitemobs reload"); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java deleted file mode 100644 index 7697c26..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class FreeMinecraftModels extends ThirdPartyResourcePack { - public FreeMinecraftModels() { - super("FreeMinecraftModels", - "FreeMinecraftModels" + File.separatorChar + "output" + File.separatorChar + "FreeMinecraftModels.zip", - false, - false, - "freeminecraftmodels reload"); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java index 7f99791..a3a3e38 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java @@ -1,10 +1,11 @@ package com.magmaguy.resourcepackmanager.thirdparty; -import java.nio.file.Path; - public interface GeneratorInterface { - public void decrypt(); - public void unpublish(); - public void cloneResourcePackFile(); - public void reload(); + void decrypt(); + + void unpublish(); + + void cloneResourcePackFile(); + + void reload(); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java deleted file mode 100644 index 3edad18..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -public class ItemsAdder { -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java deleted file mode 100644 index e3125e9..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class ModelEngine extends ThirdPartyResourcePack { - public ModelEngine() { - super("ModelEngine", - "ModelEngine" + File.separatorChar + "resource pack.zip", - false, - false, - "meg reload"); - } -} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java deleted file mode 100644 index 0494ff6..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -public class Nova { -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java deleted file mode 100644 index 3d409ac..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -public class Oraxen { -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 59e6c2f..4883e8a 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -1,53 +1,187 @@ package com.magmaguy.resourcepackmanager.thirdparty; -import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.magmacore.util.ZipFile; import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.config.DefaultConfig; +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; +import com.magmaguy.resourcepackmanager.mixer.Mix; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; +import lombok.Setter; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpEntity; import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import java.io.BufferedInputStream; import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.HashSet; +import java.util.Objects; public class ThirdPartyResourcePack implements GeneratorInterface { + public static HashSet thirdPartyResourcePacks = new HashSet<>(); + + @Getter + private final String pluginName; @Getter - private final File file; - private final boolean encrypts; - private final boolean distributes; - private final String reloadCommand; + private final String mixerFilename; + private final String localPath; + private final String url; + @Getter + private File file = null; + private boolean encrypts; + private boolean distributes; + private boolean zips; + private String reloadCommand; @Getter private boolean isEnabled; private String SHA1; - private boolean resourcePackUpdated = false; @Getter private File mixerResourcePack = null; @Getter private int priority = -1; + @Getter + private boolean done = false; + + private int ticksWithoutChange = 0; + private boolean consideredStable = false; + @Setter + private boolean stableResourcePackSent = false; + + public ThirdPartyResourcePack(String pluginName, String localPath, String url, boolean encrypts, boolean distributes, boolean zips, String reloadCommand) { + this.pluginName = pluginName; + this.mixerFilename = pluginName + "_resource_pack.zip"; + this.url = url; + this.localPath = localPath; - public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, boolean distributes, String reloadCommand) { isEnabled = Bukkit.getPluginManager().isPluginEnabled(pluginName); - if (isEnabled) - Logger.info("Initializing " + pluginName + "'s resource pack"); - this.file = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + path); - if (!file.exists()) { - Logger.warn("Found " + pluginName + " but could not find resource pack at location " + file.getPath() + " ! ResourcePackManager will not be able to merge the resource pack from this plugin."); + if (!isEnabled) { + done = true; + return; + } + + if (localPath != null && !processLocal(localPath)) { + done = true; + return; + } else if (localPath == null && url == null) { + Logger.warn("Plugin " + pluginName + " has no resource pack path specified! ResourcePackManager will not be able to merge the resource pack from this plugin."); isEnabled = false; + done = true; + return; } + this.encrypts = encrypts; this.distributes = distributes; this.reloadCommand = reloadCommand; - if (isEnabled) SHA1 = getSHA1(file); + this.zips = zips; + + if (!zips) zipThirdPartyPack(); + + if (localPath != null) SHA1 = getSHA1(file); + process(); + if (DefaultConfig.getPriorityOrder().contains(pluginName)) priority = DefaultConfig.getPriorityOrder().indexOf(pluginName); + + thirdPartyResourcePacks.add(this); + done = true; + } + + private static BukkitTask resourcePackChangeWatcher = null; + + public static void startResourcePackChangeWatchdog() { + resourcePackChangeWatcher = new BukkitRunnable() { + @Override + public void run() { + boolean readyToSend = true; + boolean stableAlreadySent = true; + for (ThirdPartyResourcePack thirdPartyResourcePack : thirdPartyResourcePacks) { + if (!thirdPartyResourcePack.isEnabled || thirdPartyResourcePack.file == null) continue; + if (!Objects.equals(thirdPartyResourcePack.getSHA1(thirdPartyResourcePack.file), thirdPartyResourcePack.SHA1)) { + thirdPartyResourcePack.ticksWithoutChange = 0; + thirdPartyResourcePack.SHA1 = thirdPartyResourcePack.getSHA1(thirdPartyResourcePack.file); + if (thirdPartyResourcePack.consideredStable) { + thirdPartyResourcePack.consideredStable = false; + thirdPartyResourcePack.stableResourcePackSent = false; + Logger.info("Resource pack for " + thirdPartyResourcePack.pluginName + " has changed, considering it unstable."); + } + } + if (!thirdPartyResourcePack.stableResourcePackSent) stableAlreadySent = false; + if (!thirdPartyResourcePack.consideredStable) readyToSend = false; + if (thirdPartyResourcePack.consideredStable) continue; + thirdPartyResourcePack.ticksWithoutChange++; + if (thirdPartyResourcePack.ticksWithoutChange == 3) { + //Is nothing has changed for 1 second, then it should be considered to be stable + thirdPartyResourcePack.consideredStable = true; + Logger.info("Resource pack for " + thirdPartyResourcePack.pluginName + " has not changed for 3 seconds, considering it stable."); + } + } + + if (!stableAlreadySent && readyToSend) { + Logger.info("All resource packs are stable, sending resource pack to players."); + tagAsResourcePackSent(); + Mix.mixResourcePacks(); + } + } + }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 20, 20); + } + + public static void tagAsResourcePackSent(){ + for (ThirdPartyResourcePack thirdPartyResourcePack : thirdPartyResourcePacks) { + thirdPartyResourcePack.stableResourcePackSent = true; + } + } + + public static void shutdown() { + thirdPartyResourcePacks.clear(); + resourcePackChangeWatcher.cancel(); + } + + public static void initializeThirdPartyResourcePack(CompatiblePluginConfigFields compatiblePluginConfigFields) { + new ThirdPartyResourcePack( + compatiblePluginConfigFields.getPluginName(), + compatiblePluginConfigFields.getLocalPath(), + compatiblePluginConfigFields.getUrl(), + compatiblePluginConfigFields.isEncrypts(), + compatiblePluginConfigFields.isDistributes(), + compatiblePluginConfigFields.isZips(), + compatiblePluginConfigFields.getReloadCommand()); + } + + private void zipThirdPartyPack() { + ZipFile.zip(file, getTarget().toString()); + file = new File(getTarget().toUri()); + mixerResourcePack = file; + } + + private boolean processLocal(String localPath) { + this.file = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + localPath); + + if (!file.exists()) { + Logger.warn("Found " + pluginName + " but could not find resource pack at location " + file.getPath() + " ! ResourcePackManager will not be able to merge the resource pack from this plugin."); + isEnabled = false; + return false; + } + + return true; } public void process() { if (!isEnabled) return; - if (mixerCloneExists()) { + + //Check if a copy already exists in the mixer folder and if it is up-to-date + if (localPath != null && mixerCloneExists()) { if (getSHA1(new File(getTarget().toUri())).equals(SHA1)) { mixerResourcePack = getTarget().toFile(); return; @@ -56,8 +190,10 @@ public void process() { getTarget().toFile().delete(); } } + if (encrypts) decrypt(); if (distributes) unpublish(); + cloneResourcePackFile(); } @@ -82,15 +218,66 @@ public void unpublish() { @Override public void cloneResourcePackFile() { + if (localPath != null) cloneLocalRSP(); + else cloneRemoteRSP(); + } + + private void cloneLocalRSP() { try { - Logger.info("Cloning resource pack from " + file.toPath()); - mixerResourcePack = Files.copy(Path.of(file.getAbsolutePath()), Path.of(getTarget().toAbsolutePath().toString()), StandardCopyOption.REPLACE_EXISTING).toFile(); + if (zips) { + Logger.info("Cloning resource pack from " + file.toPath()); + mixerResourcePack = Files.copy(Path.of(file.getAbsolutePath()), Path.of(getTarget().toAbsolutePath().toString()), StandardCopyOption.REPLACE_EXISTING).toFile(); + } } catch (Exception e) { Logger.warn("Failed to clone resource pack from " + file.getPath() + " to the mixer folder!"); e.printStackTrace(); } + } + + public void cloneRemoteRSP() { + Logger.info("Getting resource pack from remote URL! This is not ideal but not optional for some plugins. URL: " + url); + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpGet httpGet = new HttpGet(url); + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + int statusCode = response.getCode(); + Logger.info("Response status code: " + statusCode); - resourcePackUpdated = true; + if (statusCode == 200) { + HttpEntity responseEntity = response.getEntity(); + if (responseEntity != null) { + // Save the response as a zip file + File zipFile = getTarget().toFile(); + if (zipFile.exists()) { + Logger.info("Target file exists, deleting it: " + zipFile.getAbsolutePath()); + zipFile.delete(); + } + zipFile.createNewFile(); + + try (InputStream inStream = new BufferedInputStream(responseEntity.getContent()); + FileOutputStream outStream = new FileOutputStream(zipFile)) { + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inStream.read(buffer)) != -1) { + outStream.write(buffer, 0, bytesRead); + } + + Logger.info("Successfully downloaded the resource pack to " + zipFile.getAbsolutePath()); + } catch (Exception e) { + Logger.warn("Failed to write resource pack from remote!"); + } + } else { + Logger.warn("Response entity is null"); + } + } else { + Logger.warn("Unexpected response status: " + statusCode); + } + } catch (Exception e) { + Logger.warn("Failed to communicate with remote server when downloading resource pack for plugin " + pluginName + "!"); + } + } catch (Exception e) { + Logger.warn("Failed to connect to url " + url + " to download resource pack for plugin " + pluginName); + } } @Override @@ -103,6 +290,6 @@ private boolean mixerCloneExists() { } private Path getTarget() { - return Path.of(ResourcePackManager.plugin.getDataFolder().toString(), "mixer", file.getName()); + return Path.of(ResourcePackManager.plugin.getDataFolder().toString(), "mixer", mixerFilename); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java new file mode 100644 index 0000000..cfcae6d --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java @@ -0,0 +1,24 @@ +package com.magmaguy.resourcepackmanager.utils; + +import org.bukkit.ChatColor; + +import java.util.ArrayList; +import java.util.List; + +public class ChatColorConverter { + private ChatColorConverter() { + } + + public static String convert(String string) { + if (string == null) return ""; + return ChatColor.translateAlternateColorCodes('&', string); + } + + public static List convert(List list) { + if (list == null) return new ArrayList<>(); + List convertedList = new ArrayList<>(); + for (Object value : list) + convertedList.add(convert(value + "")); + return convertedList; + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java index c1d4edb..5cd8649 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java @@ -1,8 +1,11 @@ package com.magmaguy.resourcepackmanager.utils; +import com.magmaguy.magmacore.util.Logger; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -21,6 +24,28 @@ public static String sha1CodeString(File file) throws IOException, NoSuchAlgorit } } + public static String sha1CodeString(String text) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + byte[] bytes = text.getBytes(StandardCharsets.UTF_8); + byte[] resultByteArry = digest.digest(bytes); + return bytesToHexString(resultByteArry); + } catch (NoSuchAlgorithmException e) { + Logger.warn("Failed to find SHA-1 algorithm."); + } + return null; + } + + public static byte[] sha1CodeByteArray(File file) throws IOException, NoSuchAlgorithmException { + FileInputStream fileInputStream = new FileInputStream(file); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + DigestInputStream digestInputStream = new DigestInputStream(fileInputStream, digest); + byte[] bytes = new byte[1024]; + // read all file content + while (digestInputStream.read(bytes) > 0) digest = digestInputStream.getMessageDigest(); + return digest.digest(); + } + public static String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java new file mode 100644 index 0000000..5341e96 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java @@ -0,0 +1,106 @@ +package com.magmaguy.resourcepackmanager.utils; + +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.resourcepackmanager.ResourcePackManager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.scheduler.BukkitRunnable; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; + +public class VersionChecker { + private static boolean pluginIsUpToDate = true; + + private VersionChecker() { + } + + public static void checkPluginVersion() { + new BukkitRunnable() { + @Override + public void run() { + String currentVersion = ResourcePackManager.plugin.getDescription().getVersion(); + boolean snapshot = false; + if (currentVersion.contains("SNAPSHOT")) { + snapshot = true; + currentVersion = currentVersion.split("-")[0]; + } + String publicVersion = ""; + + try { + Logger.info("Latest public release is " + VersionChecker.readStringFromURL("https://api.spigotmc.org/legacy/update.php?resource=118574")); + Logger.info("Your version is " + ResourcePackManager.plugin.getDescription().getVersion()); + publicVersion = VersionChecker.readStringFromURL("https://api.spigotmc.org/legacy/update.php?resource=118574"); + } catch (IOException e) { + Logger.warn("Couldn't check latest version"); + return; + } + + if (Double.parseDouble(currentVersion.split("\\.")[0]) < Double.parseDouble(publicVersion.split("\\.")[0])) { + outOfDateHandler(); + return; + } + + if (Double.parseDouble(currentVersion.split("\\.")[0]) == Double.parseDouble(publicVersion.split("\\.")[0])) { + + if (Double.parseDouble(currentVersion.split("\\.")[1]) < Double.parseDouble(publicVersion.split("\\.")[1])) { + outOfDateHandler(); + return; + } + + if (Double.parseDouble(currentVersion.split("\\.")[1]) == Double.parseDouble(publicVersion.split("\\.")[1])) { + if (Double.parseDouble(currentVersion.split("\\.")[2]) < Double.parseDouble(publicVersion.split("\\.")[2])) { + outOfDateHandler(); + return; + } + } + } + + if (!snapshot) + Logger.info("You are running the latest version!"); + else + Logger.info("You are running a snapshot version! You can check for updates in the #releases channel on the Nightbreak Discord!"); + + pluginIsUpToDate = true; + } + }.runTaskAsynchronously(ResourcePackManager.plugin); + } + + private static String readStringFromURL(String url) throws IOException { + + try (Scanner scanner = new Scanner(new URL(url).openStream(), + StandardCharsets.UTF_8)) { + scanner.useDelimiter("\\A"); + return scanner.hasNext() ? scanner.next() : ""; + } + + } + + private static void outOfDateHandler() { + Logger.warn("A newer version of this plugin is available for download!"); + pluginIsUpToDate = false; + } + + public static class VersionCheckerEvents implements Listener { + @EventHandler + public void onPlayerLogin(PlayerJoinEvent event) { + + if (!event.getPlayer().hasPermission("elitemobs.versionnotification")) return; + + new BukkitRunnable() { + @Override + public void run() { + if (!event.getPlayer().isOnline()) return; + if (!pluginIsUpToDate) + event.getPlayer().sendMessage(com.magmaguy.magmacore.util.ChatColorConverter.convert("&cYour version of ResourcePackManager is outdated." + + " &aYou can download the latest version from &3&n&ohttps://www.spigotmc.org/resources/resource-pack-manager.118574/")); + } + }.runTaskLater(ResourcePackManager.plugin, 20L * 3); + + } + } + +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/ZipFile.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/ZipFile.java deleted file mode 100644 index 757dee4..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/utils/ZipFile.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.magmaguy.resourcepackmanager.utils; - -import com.magmaguy.resourcepackmanager.Logger; - -import java.io.*; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -public class ZipFile { - private ZipFile() { - } - - public static boolean zip(File directory, String targetZipPath) { - if (!directory.exists()) { - Logger.warn("Failed to zip directory " + directory.getPath() + " because it does not exist!"); - return false; - } - - try { - ZipUtility.zip(directory, targetZipPath); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - - public static File unzip(File zippedFile, File destinationUnzippedFile) throws IOException { - byte[] buffer = new byte[1024]; - ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zippedFile)); - ZipEntry zipEntry = zipInputStream.getNextEntry(); - while (zipEntry != null) { - File newFile = newFile(destinationUnzippedFile, zipEntry); - if (zipEntry.isDirectory()) { - if (!newFile.isDirectory() && !newFile.mkdirs()) { - throw new IOException("Failed to create directory " + newFile); - } - } else { - // Fix for Windows-created archives - File parent = newFile.getParentFile(); - if (!parent.isDirectory() && !parent.mkdirs()) { - throw new IOException("Failed to create directory " + parent); - } - - // Write file content - FileOutputStream fileOutputStream = new FileOutputStream(newFile); - int len; - while ((len = zipInputStream.read(buffer)) > 0) { - fileOutputStream.write(buffer, 0, len); - } - fileOutputStream.close(); - } - newFile.setLastModified(zipEntry.getTime()); - zipEntry = zipInputStream.getNextEntry(); - } - zipInputStream.closeEntry(); - zipInputStream.close(); - return destinationUnzippedFile; - } - - private static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException { - File destFile = new File(destinationDir, zipEntry.getName()); - - String destDirPath = destinationDir.getCanonicalPath(); - String destFilePath = destFile.getCanonicalPath(); - - if (!destFilePath.startsWith(destDirPath + File.separatorChar)) { - throw new IOException("Entry is outside of the target dir: " + zipEntry.getName()); - } - - return destFile; - } - - public static class ZipUtility { - /** - * A constant for buffer size used to read/write data - */ - private static final int BUFFER_SIZE = 4096; - - /** - * Compresses a list of files to a destination zip file - * - * @param file File to zip - * @param destZipFile The path of the destination zip file - * @throws FileNotFoundException - * @throws IOException - */ - public static void zip(File file, String destZipFile) throws FileNotFoundException, IOException { - FileOutputStream fileOutputStream = new FileOutputStream(destZipFile); - ZipOutputStream zos = new ZipOutputStream(fileOutputStream); - // This slight tweak avoids making the directory zipped be in the zipped file when what we are looking for is to - // zip the contents of the directory, outside of the directory itself - if (file.isDirectory()) { - for (File file1 : file.listFiles()) { - if (file1.isDirectory()) - zipDirectory(file1, file1.getName(), zos); - else - zipFile(file1, zos); - } - } else { - zipFile(file, zos); - } - zos.flush(); - zos.close(); - fileOutputStream.close(); - } - - /** - * Adds a directory to the current zip output stream - * - * @param folder the directory to be added - * @param parentFolder the path of parent directory - * @param zos the current zip output stream - * @throws FileNotFoundException - * @throws IOException - */ - private static void zipDirectory(File folder, String parentFolder, ZipOutputStream zos) throws FileNotFoundException, IOException { - for (File file : folder.listFiles()) { - if (file.isDirectory()) { - zipDirectory(file, parentFolder + "/" + file.getName(), zos); - continue; - } - ZipEntry zipEntry = new ZipEntry(parentFolder + "/" + file.getName()); - zippedySplit(zos, file, zipEntry); - } - } - - /** - * Adds a file to the current zip output stream - * - * @param file the file to be added - * @param zos the current zip output stream - * @throws FileNotFoundException - * @throws IOException - */ - private static void zipFile(File file, ZipOutputStream zos) throws FileNotFoundException, IOException { - ZipEntry zipEntry = new ZipEntry(file.getName()); - zippedySplit(zos, file, zipEntry); - } - - private static void zippedySplit(ZipOutputStream zos, File file, ZipEntry zipEntry) throws IOException { - zipEntry.setTime(0L); - zos.putNextEntry(zipEntry); - BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); - byte[] bytesIn = new byte[BUFFER_SIZE]; - int read; - while ((read = bis.read(bytesIn)) != -1) { - zos.write(bytesIn, 0, read); - } - zos.closeEntry(); - bis.close(); - } - } -} diff --git a/src/main/resources/ReadMe.md b/src/main/resources/ReadMe.md new file mode 100644 index 0000000..15939ec --- /dev/null +++ b/src/main/resources/ReadMe.md @@ -0,0 +1,74 @@ +### ResourcePackManager Data Policy and Compliance + +**ResourcePackManager**, developed by MagmaGuy for the Nightbreak game studio, includes an optional auto-host feature +that temporarily hosts resource pack data on a remote server. + +As of this writing, the hosted data is fully anonymous and serves the sole purpose of simplifying the distribution of +resource packs to clients of servers utilizing this service. Future versions of this document may be updated to reflect +any changes in data policy and other related matters. + +This system complies with: + +- **Directive 2000/31/EC** of the European Parliament and of the Council of 8 June 2000 +- **Regulation (EU) 2022/2065** of the European Parliament and of the Council of 19 October 2022 + +For data hosting transparency and compliance with these and other European norms, it is possible to obtain all data +related to a server via the `/resourcepackmanager data_compliance_request` command. This command packages a full copy of +all files and data associated with the requesting server. + +To request the takedown of your server's data, contact MagmaGuy at `magmaguy/at\nightbreak.io` (replace `/at\ ` with +`@`). However, due to the system's design, data is only retained for up to 24h after a server using ResourcePackManager +shuts down, making email requests largely unnecessary. Nonetheless, the option remains available to ensure full +compliance with European norms. + +### Data Handling by ResourcePackManager and Nightbreak Servers + +1. **Resource Pack Creation** + - ResourcePackManager aggregates resource packs on your server into a single zipped file containing all custom + content. + +2. **Initialization Request** + - An initialization request is sent to remote servers, creating a `.txt` file with a random UUID. This file can be + obtained via the `/resourcepackmanager data_compliance_request`. + +3. **SHA1 Request** + - ResourcePackManager transmits the SHA1 code of your resource pack to the remote server, which is saved in the + `.txt` file. + +4. **File Transmission** + - The zipped resource pack file is sent to the remote server, assigned the same UUID as the `.txt` file. This file + can be obtained through `/resourcepackmanager data_compliance_request` and verified to be identical to the + original in your output folder, as it is not modified by the Nightbreak servers. + +5. **"Still Alive" Ping** + - ResourcePackManager sends a "still alive" ping every 6 hours, transmitting the UUID to the server, which updates + the timestamp in the `.txt` file. + - If no "still alive" ping is received for over 24 hours, all data associated with that UUID (the `.txt` file + and the resource pack) is deleted from the Nightbreak servers. + +### Data Policy + +- **Pseudonymous Identification:** Nightbreak assigns a random UUID to your server's files each time the server reboots, + ensuring no IP address or identifiable information is stored unless users manually add such information to their + resource packs. +- **No Download Logging:** Nightbreak does not log any data related to download requests by Minecraft clients. +- **No Data Sales:** Data uploaded to Nightbreak is not, has never been, and will never be sold. +- **Compliance with Takedown Requests:** Nightbreak will comply with takedown requests from both server administrators + and law enforcement agencies. +- **Automatic Data Removal:** All data associated with your server is automatically removed 24 hours after your server + shuts down and is reuploaded on every restart for as long as ResourcePackManager is in use and using the auto-host + feature. + +### Terms of service + +As of writing this, the hosting service is provided for free for all users of ResourcePackManager. + +It is the user's responsibility to ensure that the data uploaded to servers is not illegal and complies with any Mojang +TOS as defined in their EULA. + +Abusing the service to host material other than resource packages may result in a permanent denial of service for the +offending IP. + +The service may, at any time, cease or be modified in such a way that makes old versions unable to connect to it. + +We reserve the right to unilaterally terminate this service at any time and for any reason. \ No newline at end of file diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..31d8f2e --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "pack_format": 34, + "description": "= ResourcePackManagerRSP =" + } +} \ No newline at end of file diff --git a/src/main/resources/pack.png b/src/main/resources/pack.png new file mode 100644 index 0000000..485d0af Binary files /dev/null and b/src/main/resources/pack.png differ diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index b02ba46..e32d532 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,17 +1,22 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.0.0-SNAPSHOT +version: 1.6.0 api-version: 1.14 +author: MagmaGuy softdepend: -- EliteMobs -- FreeMinecraftModels -- ModelEngine -- Nova -- Oraxen -- ItemsAdder + - EliteMobs + - FreeMinecraftModels + - ModelEngine + - Nova + - Oraxen + - ItemsAdder + - BackpackPlus + - vane-core + - RealisticSurvival + - Nexo commands: resourcepackmanager: aliases: - rspm - description: "Main command" \ No newline at end of file + description: "Main command"