From 8b48084ef6eb4f3d4c79576f8bf8cd00bf9a903e Mon Sep 17 00:00:00 2001 From: SBDeveloper Date: Thu, 4 Aug 2022 20:12:56 +0200 Subject: [PATCH] :sparkles: Possibly fixed inventory view issue, added update checker --- .../mapreflectionapi/MapReflectionAPI.java | 39 +++ .../mapreflectionapi/api/MapWrapper.java | 73 +++-- .../managers/Configuration.java | 6 + .../mapreflectionapi/utils/UpdateManager.java | 284 ++++++++++++++++++ src/main/resources/config.yml | 8 +- 5 files changed, 385 insertions(+), 25 deletions(-) create mode 100644 src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java index 8cbbdfd..f32511e 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java @@ -33,6 +33,7 @@ import tech.sbdevelopment.mapreflectionapi.listeners.MapListener; import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; +import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager; import java.util.logging.Level; @@ -118,6 +119,44 @@ public class MapReflectionAPI extends JavaPlugin { Bukkit.getPluginManager().registerEvents(new MapListener(), this); ProtocolLibrary.getProtocolManager().addPacketListener(new PacketListener(this)); + if (Configuration.getInstance().isUpdaterCheck()) { + UpdateManager updateManager = new UpdateManager(this, UpdateManager.CheckType.SPIGOT); + + updateManager.handleResponse((versionResponse, version) -> { + switch (versionResponse) { + case FOUND_NEW: + getLogger().warning("There is a new version available! Current: " + getDescription().getVersion() + " New: " + version.get()); + if (Configuration.getInstance().isUpdaterDownload()) { + getLogger().info("Trying to download the update. This could take some time..."); + + updateManager.handleDownloadResponse((downloadResponse, fileName) -> { + switch (downloadResponse) { + case DONE: + getLogger().info("Update downloaded! If you restart your server, it will be loaded. Filename: " + fileName); + break; + case ERROR: + getLogger().severe("Something went wrong when trying downloading the latest version."); + break; + case UNAVAILABLE: + getLogger().warning("Unable to download the latest version."); + break; + } + }).runUpdate(); + } + break; + case LATEST: + getLogger().info("You are running the latest version [" + getDescription().getVersion() + "]!"); + break; + case THIS_NEWER: + getLogger().info("You are running a newer version [" + getDescription().getVersion() + "]! This is probably fine."); + break; + case UNAVAILABLE: + getLogger().severe("Unable to perform an update check."); + break; + } + }).check(); + } + getLogger().info("MapReflectionAPI is enabled!"); getLogger().info("----------------"); } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 20401dc..f713cae 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -28,6 +28,7 @@ import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.MapMeta; import org.bukkit.metadata.FixedMetadataValue; import org.jetbrains.annotations.NotNull; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; @@ -42,6 +43,12 @@ public class MapWrapper { private static final String REFERENCE_METADATA = "MAP_WRAPPER_REF"; protected ArrayImage content; + public static Material MAP_MATERIAL; + + static { + MAP_MATERIAL = ReflectionUtil.supports(13) ? Material.FILLED_MAP : Material.MAP; + } + /** * Construct a new {@link MapWrapper} * @@ -172,25 +179,30 @@ public class MapWrapper { inventoryMenuName = "defaultContainer"; } Object inventoryMenu = ReflectionUtil.getField(playerHandle, inventoryMenuName); - int windowId = (int) ReflectionUtil.getField(inventoryMenu, ReflectionUtil.supports(17) ? "j" : "windowId"); +// int windowId = (int) ReflectionUtil.getField(inventoryMenu, ReflectionUtil.supports(17) ? "j" : "windowId"); - ItemStack stack = new ItemStack(ReflectionUtil.supports(13) ? Material.FILLED_MAP : Material.MAP, 1); + ItemStack stack; + if (ReflectionUtil.supports(13)) { + stack = new ItemStack(MAP_MATERIAL, 1); + } else { + stack = new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player)); + } - Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack); + Object nmsStack = createCraftItemStack(stack, (short) getMapId(player)); Object packet; if (ReflectionUtil.supports(17)) { //1.17+ int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, ReflectionUtil.supports(18) ? "j" : "getStateId"); packet = ReflectionUtil.callConstructor(setSlotPacketClass, - windowId, + 0, //0 = Player inventory stateId, slot, nmsStack ); } else { //1.16- packet = ReflectionUtil.callConstructor(setSlotPacketClass, - windowId, + 0, //0 = Player inventory slot, nmsStack ); @@ -286,27 +298,40 @@ public class MapWrapper { return null; } + private Object createCraftItemStack(ItemStack stack, int mapId) { + Object nmsStack; + if (ReflectionUtil.supports(16)) { //TODO Check why 1.16+ can use this, and 1.15- requires NMS + MapMeta meta = (MapMeta) stack.getItemMeta(); + meta.setMapId(mapId); + stack.setItemMeta(meta); + nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack); + } else { + nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack); + + if (ReflectionUtil.supports(13)) { +// String nbtObjectName; +// if (ReflectionUtil.supports(19)) { //1.19 +// nbtObjectName = "v"; +// } else if (ReflectionUtil.supports(18)) { //1.18 +// nbtObjectName = ReflectionUtil.VER_MINOR == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u +// } else { //1.13 - 1.17 +// nbtObjectName = "getOrCreateTag"; +// } + Object nbtObject = ReflectionUtil.callMethod(nmsStack, + //nbtObjectName + "getOrCreateTag"); + + ReflectionUtil.callMethod(nbtObject, + //ReflectionUtil.supports(18) ? "a" : + "setInt", "map", mapId); + } + } + return nmsStack; + } + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { - Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack); + Object nmsStack = createCraftItemStack(stack, mapId); - String nbtObjectName; - if (ReflectionUtil.supports(19)) { //1.19 - nbtObjectName = "v"; - } else if (ReflectionUtil.supports(18)) { //1.18 - nbtObjectName = ReflectionUtil.VER_MINOR == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u - } else if (ReflectionUtil.supports(13)) { //1.13 - 1.17 - nbtObjectName = "getOrCreateTag"; - } else { //1.12 - nbtObjectName = "getTag"; - } - Object nbtObject = ReflectionUtil.callMethod(nmsStack, nbtObjectName); - - if (!ReflectionUtil.supports(13) && nbtObject == null) { //1.12 has no getOrCreate, call create if null! - Object tagCompound = ReflectionUtil.callConstructor(tagCompoundClass); - ReflectionUtil.callMethod(nbtObject, "setTag", tagCompound); - } - - ReflectionUtil.callMethod(nbtObject, ReflectionUtil.supports(18) ? "a" : "setInt", "map", mapId); Object dataWatcher = ReflectionUtil.callConstructorNull(dataWatcherClass, entityClass); Object packet = ReflectionUtil.callConstructor(entityMetadataPacketClass, diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java index 3afc641..005fa85 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java @@ -35,6 +35,10 @@ public class Configuration { private boolean allowVanilla = true; @Getter private boolean imageCache = true; + @Getter + private boolean updaterCheck = true; + @Getter + private boolean updaterDownload = true; private Configuration(JavaPlugin plugin) { this.file = new YamlFile(plugin, "config"); @@ -54,5 +58,7 @@ public class Configuration { public void reload() { allowVanilla = this.file.getFile().getBoolean("allowVanilla"); imageCache = this.file.getFile().getBoolean("imageCache"); + updaterCheck = this.file.getFile().getBoolean("updater.check"); + updaterDownload = this.file.getFile().getBoolean("updater.download"); } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java new file mode 100644 index 0000000..362240a --- /dev/null +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java @@ -0,0 +1,284 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.utils; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.util.function.BiConsumer; + +/** + * Update checker class + * + * @author Stijn [SBDeveloper] + * @version 2.2 [17-04-2022] - Added Polymart support + * @since 05-03-2020 + */ +public class UpdateManager { + private static final String SPIGOT_API = "https://api.spigotmc.org/legacy/update.php?resource=%d"; + private static final String SPIGOT_DOWNLOAD = "https://api.spiget.org/v2/resources/%s/download"; + + private static final String POLYMART_API = "https://api.polymart.org/v1/getResourceInfoSimple/?resource_id=%d&key=version"; + private static final String POLYMART_DOWNLOAD = "https://api.polymart.org/v1/requestUpdateURL/?inject_version=%d&resource_id=%d&user_id=%d&nonce=%d&download_agent=%d&download_time=%d&download_token=%s"; + + private final Plugin plugin; + private final Version currentVersion; + private final CheckType type; + + //Spigot & Polymart + private final int resourceID; + + //Polymart only + private int injector_version; + private int user_id; + private int nonce; + private int download_agent; + private int download_time; + private String download_token; + + private BiConsumer versionResponse; + private BiConsumer downloadResponse; + + /** + * Construct a new UpdateManager + * + * @param plugin The plugin instance + */ + public UpdateManager(Plugin plugin, CheckType type) { + this.plugin = plugin; + this.currentVersion = new Version(plugin.getDescription().getVersion()); + this.type = type; + this.resourceID = Integer.parseInt("%%__RESOURCE__%%"); + if (type == CheckType.POLYMART_PAID) { + this.injector_version = Integer.parseInt("%%__INJECT_VER__%%"); + this.user_id = Integer.parseInt("%%__USER__%%"); + this.nonce = Integer.parseInt("%%__NONCE__%%"); + this.download_agent = Integer.parseInt("%%__AGENT__%%"); + this.download_time = Integer.parseInt("%%__TIMESTAMP__%%"); + this.download_token = "%%__VERIFY_TOKEN__%%"; + } + } + + /** + * Handle the response given by check(); + * + * @param versionResponse The response + * @return The updatemanager + */ + public UpdateManager handleResponse(BiConsumer versionResponse) { + this.versionResponse = versionResponse; + return this; + } + + public UpdateManager handleDownloadResponse(BiConsumer downloadResponse) { + this.downloadResponse = downloadResponse; + return this; + } + + /** + * Check for a new version + */ + public void check() { + Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { + try { + HttpsURLConnection con; + if (type == CheckType.POLYMART_PAID) { + con = (HttpsURLConnection) new URL(String.format(POLYMART_API, this.resourceID)).openConnection(); + } else { + con = (HttpsURLConnection) new URL(String.format(SPIGOT_API, this.resourceID)).openConnection(); + } + con.setRequestMethod("GET"); + con.setRequestProperty("User-Agent", "SBDChecker/2.1"); + + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuilder response = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + Version onlineVersion = new Version(response.toString()); + + VersionResponse verRes = this.currentVersion.check(onlineVersion); + + Bukkit.getScheduler().runTask(this.plugin, () -> this.versionResponse.accept(verRes, onlineVersion)); + } catch (IOException | NullPointerException e) { + e.printStackTrace(); + Bukkit.getScheduler().runTask(this.plugin, () -> this.versionResponse.accept(VersionResponse.UNAVAILABLE, null)); + } + }); + } + + public void runUpdate() { + File pluginFile = getPluginFile(); // /plugins/XXX.jar + if (pluginFile == null) { + this.downloadResponse.accept(DownloadResponse.ERROR, null); + Bukkit.getLogger().info("Pluginfile is null"); + return; + } + File updateFolder = Bukkit.getUpdateFolderFile(); + if (!updateFolder.exists()) { + if (!updateFolder.mkdirs()) { + this.downloadResponse.accept(DownloadResponse.ERROR, null); + Bukkit.getLogger().info("Updatefolder doesn't exists, and can't be made"); + return; + } + } + final File updateFile = new File(updateFolder, pluginFile.getName()); + + Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { + ReadableByteChannel channel; + try { + //https://stackoverflow.com/questions/921262/how-to-download-and-save-a-file-from-internet-using-java + HttpsURLConnection connection; + if (type == CheckType.POLYMART_PAID) { + connection = (HttpsURLConnection) new URL(String.format(POLYMART_DOWNLOAD, this.injector_version, this.resourceID, this.user_id, this.nonce, this.download_agent, this.download_time, this.download_token)).openConnection(); + } else { + connection = (HttpsURLConnection) new URL(String.format(SPIGOT_DOWNLOAD, this.resourceID)).openConnection(); + } + connection.setRequestProperty("User-Agent", "Mozilla/5.0"); + + InputStream stream = connection.getInputStream(); + if (connection.getResponseCode() != 200) { + BufferedReader in = new BufferedReader(new InputStreamReader(stream)); + + String inputLine; + StringBuilder responsestr = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + responsestr.append(inputLine); + } + in.close(); + + throw new RuntimeException("Download returned status #" + connection.getResponseCode(), new Throwable(responsestr.toString())); + } + + channel = Channels.newChannel(stream); + } catch (IOException e) { + Bukkit.getScheduler().runTask(this.plugin, () -> this.downloadResponse.accept(DownloadResponse.ERROR, null)); + e.printStackTrace(); + return; + } + + FileChannel fileChannel = null; + try { + FileOutputStream fosForDownloadedFile = new FileOutputStream(updateFile); + fileChannel = fosForDownloadedFile.getChannel(); + + fileChannel.transferFrom(channel, 0, Long.MAX_VALUE); + } catch (IOException e) { + Bukkit.getScheduler().runTask(this.plugin, () -> this.downloadResponse.accept(DownloadResponse.ERROR, null)); + e.printStackTrace(); + return; + } finally { + if (channel != null) { + try { + channel.close(); + } catch (IOException ioe) { + System.out.println("Error while closing response body channel"); + } + } + + if (fileChannel != null) { + try { + fileChannel.close(); + } catch (IOException ioe) { + System.out.println("Error while closing file channel for downloaded file"); + } + } + } + + Bukkit.getScheduler().runTask(this.plugin, () -> this.downloadResponse.accept(DownloadResponse.DONE, updateFile.getPath())); + }); + } + + private File getPluginFile() { + if (!(this.plugin instanceof JavaPlugin)) { + return null; + } + try { + Method method = JavaPlugin.class.getDeclaredMethod("getFile"); + method.setAccessible(true); + return (File) method.invoke(this.plugin); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Could not get plugin file", e); + } + } + + public enum CheckType { + SPIGOT, POLYMART_PAID + } + + public enum VersionResponse { + LATEST, //Latest version + FOUND_NEW, //Newer available + THIS_NEWER, //Local version is newer? + UNAVAILABLE //Error + } + + public enum DownloadResponse { + DONE, ERROR, UNAVAILABLE + } + + public static class Version { + + private final String version; + + public final String get() { + return this.version; + } + + private Version(String version) { + if (version == null) + throw new IllegalArgumentException("Version can not be null"); + if (!version.matches("[0-9]+(\\.[0-9]+)*")) + throw new IllegalArgumentException("Invalid version format"); + this.version = version; + } + + private VersionResponse check(Version that) { + String[] thisParts = this.get().split("\\."); + String[] thatParts = that.get().split("\\."); + + int length = Math.max(thisParts.length, thatParts.length); + for (int i = 0; i < length; i++) { + int thisPart = i < thisParts.length ? Integer.parseInt(thisParts[i]) : 0; + int thatPart = i < thatParts.length ? Integer.parseInt(thatParts[i]) : 0; + if (thisPart < thatPart) + return VersionResponse.FOUND_NEW; + if (thisPart > thatPart) + return VersionResponse.THIS_NEWER; + } + return VersionResponse.LATEST; + } + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2af08c3..ffd0212 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -4,4 +4,10 @@ allowVanilla: true # Should the plugin cache images? If so, it will check if the images is already used. # Less efficient for the first image, but more efficient for the next ones. -imageCache: true \ No newline at end of file +imageCache: true + +# Do you want to check for updates? +# If download is set to true, the plugin tries to download a new update which gets applied after a restart. +updater: + check: true + download: true \ No newline at end of file