From f431c13100ce0f6fd29ae2ad2478e0e30aef2095 Mon Sep 17 00:00:00 2001 From: Sn0wStorm Date: Fri, 18 Oct 2019 16:30:03 +0200 Subject: [PATCH] Load Scramble Seed key from Config Remember all used Scramble seeds to unscramble potions --- pom.xml | 6 + resources/config/v12/de/config.yml | 12 +- resources/config/v12/en/config.yml | 9 +- resources/config/v12/fr/config.yml | 9 +- resources/config/v12/it/config.yml | 9 +- resources/config/v13/de/config.yml | 12 +- resources/config/v13/en/config.yml | 9 +- resources/config/v13/fr/config.yml | 9 +- resources/config/v13/it/config.yml | 9 +- resources/config/v13/zh/config.yml | 8 ++ src/com/dre/brewery/BPlayer.java | 3 + src/com/dre/brewery/Barrel.java | 17 ++- src/com/dre/brewery/Brew.java | 112 +++++++++++------- src/com/dre/brewery/api/BreweryApi.java | 7 ++ .../brewery/api/events/brew/BrewEvent.java | 5 +- .../api/events/brew/BrewModifyEvent.java | 6 +- src/com/dre/brewery/filedata/BConfig.java | 4 + src/com/dre/brewery/filedata/BData.java | 2 +- .../dre/brewery/filedata/ConfigUpdater.java | 25 ++++ src/com/dre/brewery/filedata/DataSave.java | 2 +- .../brewery/listeners/InventoryListener.java | 4 +- src/com/dre/brewery/lore/BrewLore.java | 9 +- src/com/dre/brewery/lore/SeedInputStream.java | 4 +- .../dre/brewery/lore/XORScrambleStream.java | 36 ++++++ .../dre/brewery/lore/XORUnscrambleStream.java | 68 ++++++++++- 25 files changed, 327 insertions(+), 69 deletions(-) diff --git a/pom.xml b/pom.xml index 09a7613..2e5246f 100644 --- a/pom.xml +++ b/pom.xml @@ -232,6 +232,12 @@ 1.5 compile + + org.jetbrains + annotations + 16.0.2 + compile + diff --git a/resources/config/v12/de/config.yml b/resources/config/v12/de/config.yml index 8be0305..ac799da 100644 --- a/resources/config/v12/de/config.yml +++ b/resources/config/v12/de/config.yml @@ -3,7 +3,6 @@ # -- Verschiedene Einstellungen -- # Standardeinstellungen sind in [] angegeben -# Löschen einzelner Einstellungen deaktiviert sie # Sprachedatei die genutzt werden sollte (befindet sich in plugins/Brewery/languages) language: de @@ -57,8 +56,12 @@ openLargeBarrelEverywhere: true # Wie viele Brewery Getränke in die Minecraft Fässer getan werden können [6] maxBrewsInMCBarrels: 6 -# In den Serverlog loggen was der Spieler tatsächlich geschrieben hat, bevor seine Worte verändert wurden [false] -logRealChat: false +# Benutzte Zutaten und andere Brau-Daten werden in allen Brewery Tränken gespeichert. Um zu verhindern, +# dass gehackte clients diese Daten auslesen um Rezepte herauszufinden, können diese encodiert werden. +# Einziger Nachteil: Tränke können nur auf Servern mit dem gleichen encodeKey benutzt werden. +# Dies kann also aktiviert werden um Rezept-cheating schwerer zu machen, aber keine Tränke per World Download, Schematic, o.ä. geteilt werden. [false] +enableEncode: false +encodeKey: 0 # Aktiviert das Suchen nach Updates für Brewery mit der curseforge api [true] # Wenn ein Update gefunden wurde, wird dies bei Serverstart im log angezeigt, sowie OPs benachrichtigt @@ -286,6 +289,9 @@ useLogBlock: true # Unten kann noch eingestellt werden wie und was verändert wird enableChatDistortion: true +# In den Serverlog loggen was der Spieler tatsächlich geschrieben hat, bevor seine Worte verändert wurden [false] +logRealChat: false + # Text nach den angegebenen Kommandos wird bei Trunkenheit ebenfalls Verändert (Liste) [- /gl] distortCommands: - /gl diff --git a/resources/config/v12/en/config.yml b/resources/config/v12/en/config.yml index 38eadb4..1845a69 100644 --- a/resources/config/v12/en/config.yml +++ b/resources/config/v12/en/config.yml @@ -3,7 +3,6 @@ # -- Settings -- # Defaults are written in [] -# Deleting of single settings disables them # Languagefile to be used (found in plugins/Brewery/languages) language: en @@ -57,6 +56,14 @@ openLargeBarrelEverywhere: true # How many Brewery drinks can be put into the Minecraft barrels [6] maxBrewsInMCBarrels: 6 +# The used Ingredients and other brewing-data is saved to all Brewery Items. To prevent +# hacked clients from reading what exactly was used to brew an item, the data can be encoded/scrambled. +# This is a fast process to stop players from hacking out recipes, once they get hold of a brew. +# Only drawback: brew items can only be used on another server with the same encodeKey. +# So enable this if you want to make recipe cheating harder, but don't share any brews by world download, schematics, or other means. [false] +enableEncode: false +encodeKey: 0 + # Enable checking for Updates, Checks the curseforge api for updates to Brewery [true] # If an Update is found a Message is logged on Server-start and displayed to OPs joining the game updateCheck: true diff --git a/resources/config/v12/fr/config.yml b/resources/config/v12/fr/config.yml index e7673d6..82cd49e 100644 --- a/resources/config/v12/fr/config.yml +++ b/resources/config/v12/fr/config.yml @@ -3,7 +3,6 @@ # -- Paramètres -- # Les paramètres par défaut sont entre [] -# Supprimer un paramètre le désactive # Fichier de langage utilisé (trouvable dans plugins/Brewery/languages) language: fr @@ -57,6 +56,14 @@ openLargeBarrelEverywhere: true # How many Brewery drinks can be put into the Minecraft barrels [6] maxBrewsInMCBarrels: 6 +# The used Ingredients and other brewing-data is saved to all Brewery Items. To prevent +# hacked clients from reading what exactly was used to brew an item, the data can be encoded/scrambled. +# This is a fast process to stop players from hacking out recipes, once they get hold of a brew. +# Only drawback: brew items can only be used on another server with the same encodeKey. +# So enable this if you want to make recipe cheating harder, but don't share any brews by world download, schematics, or other means. [false] +enableEncode: false +encodeKey: 0 + # Enable checking for Updates, Checks the curseforge api for updates to Brewery [true] # If an Update is found a Message is logged on Server-start and displayed to OPs joining the game updateCheck: true diff --git a/resources/config/v12/it/config.yml b/resources/config/v12/it/config.yml index 70af3f2..335cf03 100644 --- a/resources/config/v12/it/config.yml +++ b/resources/config/v12/it/config.yml @@ -3,7 +3,6 @@ # -- Opzioni -- # I valori di default sono scritti fra [] -# Cancellare una voce la disabilita # Lingua da usare (fra quelle in plugins/Brewery/languages) language: it @@ -57,6 +56,14 @@ openLargeBarrelEverywhere: true # How many Brewery drinks can be put into the Minecraft barrels [6] maxBrewsInMCBarrels: 6 +# The used Ingredients and other brewing-data is saved to all Brewery Items. To prevent +# hacked clients from reading what exactly was used to brew an item, the data can be encoded/scrambled. +# This is a fast process to stop players from hacking out recipes, once they get hold of a brew. +# Only drawback: brew items can only be used on another server with the same encodeKey. +# So enable this if you want to make recipe cheating harder, but don't share any brews by world download, schematics, or other means. [false] +enableEncode: false +encodeKey: 0 + # Abilita il controllo degli aggiornamenti, controlla l'API di CurseForge per eventuali aggiornamenti di Brewery [true] # Se quando un aggiornamento viene trovato un messaggio è loggato e mostrato agli OPs quando entrano in gioco. updateCheck: true diff --git a/resources/config/v13/de/config.yml b/resources/config/v13/de/config.yml index f46df44..681e8fb 100644 --- a/resources/config/v13/de/config.yml +++ b/resources/config/v13/de/config.yml @@ -3,7 +3,6 @@ # -- Verschiedene Einstellungen -- # Standardeinstellungen sind in [] angegeben -# Löschen einzelner Einstellungen deaktiviert sie # Sprachedatei die genutzt werden sollte (befindet sich in plugins/Brewery/languages) language: de @@ -57,8 +56,12 @@ openLargeBarrelEverywhere: true # Wie viele Brewery Getränke in die Minecraft Fässer getan werden können [6] maxBrewsInMCBarrels: 6 -# In den Serverlog loggen was der Spieler tatsächlich geschrieben hat, bevor seine Worte verändert wurden [false] -logRealChat: false +# Benutzte Zutaten und andere Brau-Daten werden in allen Brewery Tränken gespeichert. Um zu verhindern, +# dass gehackte clients diese Daten auslesen um Rezepte herauszufinden, können diese encodiert werden. +# Einziger Nachteil: Tränke können nur auf Servern mit dem gleichen encodeKey benutzt werden. +# Dies kann also aktiviert werden um Rezept-cheating schwerer zu machen, aber keine Tränke per World Download, Schematic, o.ä. geteilt werden. [false] +enableEncode: false +encodeKey: 0 # Aktiviert das Suchen nach Updates für Brewery mit der curseforge api [true] # Wenn ein Update gefunden wurde, wird dies bei Serverstart im log angezeigt, sowie OPs benachrichtigt @@ -277,6 +280,9 @@ useLogBlock: true # Unten kann noch eingestellt werden wie und was verändert wird enableChatDistortion: true +# In den Serverlog loggen was der Spieler tatsächlich geschrieben hat, bevor seine Worte verändert wurden [false] +logRealChat: false + # Text nach den angegebenen Kommandos wird bei Trunkenheit ebenfalls Verändert (Liste) [- /gl] distortCommands: - /gl diff --git a/resources/config/v13/en/config.yml b/resources/config/v13/en/config.yml index cc68467..376d920 100644 --- a/resources/config/v13/en/config.yml +++ b/resources/config/v13/en/config.yml @@ -3,7 +3,6 @@ # -- Settings -- # Defaults are written in [] -# Deleting of single settings disables them # Languagefile to be used (found in plugins/Brewery/languages) language: en @@ -57,6 +56,14 @@ openLargeBarrelEverywhere: true # How many Brewery drinks can be put into the Minecraft barrels [6] maxBrewsInMCBarrels: 6 +# The used Ingredients and other brewing-data is saved to all Brewery Items. To prevent +# hacked clients from reading what exactly was used to brew an item, the data can be encoded/scrambled. +# This is a fast process to stop players from hacking out recipes, once they get hold of a brew. +# Only drawback: brew items can only be used on another server with the same encodeKey. +# So enable this if you want to make recipe cheating harder, but don't share any brews by world download, schematics, or other means. [false] +enableEncode: false +encodeKey: 0 + # Enable checking for Updates, Checks the curseforge api for updates to Brewery [true] # If an Update is found a Message is logged on Server-start and displayed to OPs joining the game updateCheck: true diff --git a/resources/config/v13/fr/config.yml b/resources/config/v13/fr/config.yml index cc000f0..98f15cf 100644 --- a/resources/config/v13/fr/config.yml +++ b/resources/config/v13/fr/config.yml @@ -3,7 +3,6 @@ # -- Paramètres -- # Les paramètres par défaut sont entre [] -# Supprimer un paramètre le désactive # Fichier de langage utilisé (trouvable dans plugins/Brewery/languages) language: fr @@ -57,6 +56,14 @@ openLargeBarrelEverywhere: true # How many Brewery drinks can be put into the Minecraft barrels [6] maxBrewsInMCBarrels: 6 +# The used Ingredients and other brewing-data is saved to all Brewery Items. To prevent +# hacked clients from reading what exactly was used to brew an item, the data can be encoded/scrambled. +# This is a fast process to stop players from hacking out recipes, once they get hold of a brew. +# Only drawback: brew items can only be used on another server with the same encodeKey. +# So enable this if you want to make recipe cheating harder, but don't share any brews by world download, schematics, or other means. [false] +enableEncode: false +encodeKey: 0 + # Enable checking for Updates, Checks the curseforge api for updates to Brewery [true] # If an Update is found a Message is logged on Server-start and displayed to OPs joining the game updateCheck: true diff --git a/resources/config/v13/it/config.yml b/resources/config/v13/it/config.yml index 55649f8..64895cd 100644 --- a/resources/config/v13/it/config.yml +++ b/resources/config/v13/it/config.yml @@ -3,7 +3,6 @@ # -- Opzioni -- # I valori di default sono scritti fra [] -# Cancellare una voce la disabilita # Lingua da usare (fra quelle in plugins/Brewery/languages) language: it @@ -57,6 +56,14 @@ openLargeBarrelEverywhere: true # How many Brewery drinks can be put into the Minecraft barrels [6] maxBrewsInMCBarrels: 6 +# The used Ingredients and other brewing-data is saved to all Brewery Items. To prevent +# hacked clients from reading what exactly was used to brew an item, the data can be encoded/scrambled. +# This is a fast process to stop players from hacking out recipes, once they get hold of a brew. +# Only drawback: brew items can only be used on another server with the same encodeKey. +# So enable this if you want to make recipe cheating harder, but don't share any brews by world download, schematics, or other means. [false] +enableEncode: false +encodeKey: 0 + # Abilita il controllo degli aggiornamenti, controlla l'API di CurseForge per eventuali aggiornamenti di Brewery [true] # Se quando un aggiornamento viene trovato un messaggio è loggato e mostrato agli OPs quando entrano in gioco. updateCheck: true diff --git a/resources/config/v13/zh/config.yml b/resources/config/v13/zh/config.yml index ad157f0..b1dfda5 100644 --- a/resources/config/v13/zh/config.yml +++ b/resources/config/v13/zh/config.yml @@ -58,6 +58,14 @@ openLargeBarrelEverywhere: false # MC自带的桶内可以存放多少饮品 [6] maxBrewsInMCBarrels: 6 +# The used Ingredients and other brewing-data is saved to all Brewery Items. To prevent +# hacked clients from reading what exactly was used to brew an item, the data can be encoded/scrambled. +# This is a fast process to stop players from hacking out recipes, once they get hold of a brew. +# Only drawback: brew items can only be used on another server with the same encodeKey. +# So enable this if you want to make recipe cheating harder, but don't share any brews by world download, schematics, or other means. [false] +enableEncode: false +encodeKey: 0 + # 是否检查更新.[true] # 若有更新, 服务端后台与上线时的管理员会收到通知. updateCheck: true diff --git a/src/com/dre/brewery/BPlayer.java b/src/com/dre/brewery/BPlayer.java index 857e57c..7db9de3 100644 --- a/src/com/dre/brewery/BPlayer.java +++ b/src/com/dre/brewery/BPlayer.java @@ -21,6 +21,7 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -53,6 +54,7 @@ public class BPlayer { players.put(name, this); } + @Nullable public static BPlayer get(Player player) { if (!players.isEmpty()) { return players.get(BUtil.playerString(player)); @@ -61,6 +63,7 @@ public class BPlayer { } // This method may be slow and should not be used if not needed + @Nullable public static BPlayer getByName(String playerName) { if (P.useUUID) { for (Map.Entry entry : players.entrySet()) { diff --git a/src/com/dre/brewery/Barrel.java b/src/com/dre/brewery/Barrel.java index e5cc718..f42bb90 100644 --- a/src/com/dre/brewery/Barrel.java +++ b/src/com/dre/brewery/Barrel.java @@ -23,6 +23,7 @@ import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.scheduler.BukkitRunnable; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; @@ -42,6 +43,11 @@ public class Barrel implements InventoryHolder { public Barrel(Block spigot, byte signoffset) { this.spigot = spigot; + if (isLarge()) { + inventory = P.p.getServer().createInventory(this, 27, P.p.languageReader.get("Etc_Barrel")); + } else { + inventory = P.p.getServer().createInventory(this, 9, P.p.languageReader.get("Etc_Barrel")); + } body = new BarrelBody(this, signoffset); } @@ -163,6 +169,7 @@ public class Barrel implements InventoryHolder { public void playOpeningSound() { float randPitch = (float) (Math.random() * 0.1); Location location = getSpigot().getLocation(); + if (location.getWorld() == null) return; if (isLarge()) { location.getWorld().playSound(location, Sound.BLOCK_CHEST_OPEN, SoundCategory.BLOCKS, 0.4f, 0.55f + randPitch); //getSpigot().getWorld().playSound(getSpigot().getLocation(), Sound.ITEM_BUCKET_EMPTY, SoundCategory.BLOCKS, 0.5f, 0.6f + randPitch); @@ -175,6 +182,7 @@ public class Barrel implements InventoryHolder { public void playClosingSound() { float randPitch = (float) (Math.random() * 0.1); Location location = getSpigot().getLocation(); + if (location.getWorld() == null) return; if (isLarge()) { location.getWorld().playSound(location, Sound.BLOCK_BARREL_CLOSE, SoundCategory.BLOCKS, 0.5f, 0.5f + randPitch); location.getWorld().playSound(location, Sound.ITEM_BUCKET_EMPTY, SoundCategory.BLOCKS, 0.2f, 0.6f + randPitch); @@ -184,14 +192,17 @@ public class Barrel implements InventoryHolder { } @Override + @NotNull public Inventory getInventory() { return inventory; } + @NotNull public Block getSpigot() { return spigot; } + @NotNull public BarrelBody getBody() { return body; } @@ -239,7 +250,7 @@ public class Barrel implements InventoryHolder { for (Barrel barrel : barrels) { if (barrel.body.isSignOfBarrel(signoffset)) { - if (barrel.body.equals(spigot)) { + if (barrel.spigot.equals(spigot)) { if (barrel.body.getSignoffset() == 0 && signoffset != 0) { // Barrel has no signOffset even though we clicked a sign, may be old barrel.body.setSignoffset(signoffset); @@ -316,7 +327,7 @@ public class Barrel implements InventoryHolder { P.p.getServer().getPluginManager().callEvent(event); if (inventory != null) { - List viewers = new ArrayList(inventory.getViewers()); + List viewers = new ArrayList<>(inventory.getViewers()); // Copy List to fix ConcModExc for (HumanEntity viewer : viewers) { viewer.closeInventory(); @@ -368,7 +379,7 @@ public class Barrel implements InventoryHolder { // is this a Small barrel? public boolean isSmall() { - return !LegacyUtil.isSign(spigot.getType()); + return LegacyUtil.isSign(spigot.getType()); } // returns the Sign of a large barrel, the spigot if there is none diff --git a/src/com/dre/brewery/Brew.java b/src/com/dre/brewery/Brew.java index df4ee1e..ff76efe 100644 --- a/src/com/dre/brewery/Brew.java +++ b/src/com/dre/brewery/Brew.java @@ -2,6 +2,7 @@ package com.dre.brewery; import com.dre.brewery.api.events.brew.BrewModifyEvent; import com.dre.brewery.filedata.BConfig; +import com.dre.brewery.filedata.ConfigUpdater; import com.dre.brewery.lore.*; import com.dre.brewery.utility.PotionColor; import org.bukkit.Material; @@ -12,20 +13,24 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; import java.io.IOException; import java.security.InvalidKeyException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class Brew { - // represents the liquid in the brewed Potions private static long saveSeed; + private static List prevSaveSeeds = new ArrayList<>(); // Save Seeds that have been used in the past, stored to decode brews made at that time public static Map legacyPotions = new HashMap<>(); public static long installTime = System.currentTimeMillis(); // plugin install time in millis after epoch @@ -71,22 +76,21 @@ public class Brew { } // returns a Brew by ItemMeta + @Nullable public static Brew get(ItemMeta meta) { if (!meta.hasLore()) return null; - if (meta instanceof PotionMeta && ((PotionMeta) meta).hasCustomEffect(PotionEffectType.REGENERATION)) { - Brew brew = load(meta); - if (brew != null) { - // Load Legacy - brew = getFromPotionEffect(((PotionMeta) meta), false); - } - return brew; - } else { - return load(meta); + Brew brew = load(meta); + + if (brew == null && meta instanceof PotionMeta && ((PotionMeta) meta).hasCustomEffect(PotionEffectType.REGENERATION)) { + // Load Legacy + return getFromPotionEffect(((PotionMeta) meta), false); } + return brew; } // returns a Brew by ItemStack + @Nullable public static Brew get(ItemStack item) { if (item.getType() != Material.POTION) return null; if (!item.hasItemMeta()) return null; @@ -94,21 +98,17 @@ public class Brew { ItemMeta meta = item.getItemMeta(); if (!meta.hasLore()) return null; - if (meta instanceof PotionMeta && ((PotionMeta) meta).hasCustomEffect(PotionEffectType.REGENERATION)) { - Brew brew = load(meta); - if (brew != null) { - ((PotionMeta) meta).removeCustomEffect(PotionEffectType.REGENERATION); - } else { - // Load Legacy and convert - brew = getFromPotionEffect(((PotionMeta) meta), true); - if (brew == null) return null; - brew.save(meta); - } + Brew brew = load(meta); + + if (brew == null && meta instanceof PotionMeta && ((PotionMeta) meta).hasCustomEffect(PotionEffectType.REGENERATION)) { + // Load Legacy and convert + brew = getFromPotionEffect(((PotionMeta) meta), true); + if (brew == null) return null; + new BrewLore(brew, ((PotionMeta) meta)).removeLegacySpacing(); + brew.save(meta); item.setItemMeta(meta); - return brew; - } else { - return load(meta); } + return brew; } // Legacy Brew Loading @@ -117,7 +117,16 @@ public class Brew { if (effect.getType().equals(PotionEffectType.REGENERATION)) { if (effect.getDuration() < -1) { if (remove) { - return legacyPotions.remove(effect.getDuration()); + Brew b = legacyPotions.get(effect.getDuration()); + if (b != null) { + potionMeta.removeCustomEffect(PotionEffectType.REGENERATION); + if (b.persistent) { + return b; + } else { + return legacyPotions.remove(effect.getDuration()); + } + } + return null; } else { return legacyPotions.get(effect.getDuration()); } @@ -612,6 +621,7 @@ public class Brew { * @param event Set event to true if a BrewModifyEvent type CREATE should be called and may be cancelled. Only then may this method return null * @return The created Item, null if the Event is cancelled */ + @Contract("_, false -> !null") public ItemStack createItem(BRecipe recipe, boolean event) { if (recipe == null) { recipe = getCurrentRecipe(); @@ -654,9 +664,10 @@ public class Brew { try { loreStream = new LoreLoadStream(meta, 0); } catch (IllegalArgumentException ignored) { + // No Brew data found in Meta return null; } - XORUnscrambleStream unscrambler = new XORUnscrambleStream(new Base91DecoderStream(loreStream), saveSeed); + XORUnscrambleStream unscrambler = new XORUnscrambleStream(new Base91DecoderStream(loreStream), saveSeed, prevSaveSeeds); try (DataInputStream in = new DataInputStream(unscrambler)) { boolean parityFailed = false; if (in.readByte() != 86) { @@ -683,7 +694,7 @@ public class Brew { P.p.errorLog("IO Error while loading Brew"); e.printStackTrace(); } catch (InvalidKeyException e) { - P.p.errorLog("Failed to load Brew, has the data key 'BrewDataSeed' in the data.yml been changed?"); + P.p.errorLog("Failed to load Brew, has the data key 'encodeKey' in the config.yml been changed?"); e.printStackTrace(); } return null; @@ -718,7 +729,11 @@ public class Brew { try (DataOutputStream out = new DataOutputStream(scrambler)) { out.writeByte(86); // Parity/sanity out.writeByte(1); // Version - scrambler.start(); + if (BConfig.enableEncode) { + scrambler.start(); + } else { + scrambler.startUnscrambled(); + } saveToStream(out); } catch (IOException e) { P.p.errorLog("IO Error while saving Brew"); @@ -768,17 +783,33 @@ public class Brew { ingredients.save(out); } - public static void writeSeed(ConfigurationSection section) { - section.set("BrewDataSeed", saveSeed); + public static void loadPrevSeeds(ConfigurationSection section) { + if (section.contains("prevSaveSeeds")) { + prevSaveSeeds = section.getLongList("prevSaveSeeds"); + if (!prevSaveSeeds.contains(saveSeed)) { + prevSaveSeeds.add(saveSeed); + } + } } - public static void loadSeed(ConfigurationSection section) { - if (section.contains("BrewDataSeed")) { - saveSeed = section.getLong("BrewDataSeed"); - } else { + public static void writePrevSeeds(ConfigurationSection section) { + if (!prevSaveSeeds.isEmpty()) { + section.set("prevSaveSeeds", prevSaveSeeds); + } + } + + public static void loadSeed(ConfigurationSection config, File file) { + saveSeed = config.getLong("encodeKey", 0); + if (saveSeed == 0) { while (saveSeed == 0) { saveSeed = new SecureRandom().nextLong(); } + ConfigUpdater updater = new ConfigUpdater(file); + updater.setEncodeKey(saveSeed); + updater.saveConfig(); + } + if (!prevSaveSeeds.contains(saveSeed)) { + prevSaveSeeds.add(saveSeed); } } @@ -793,31 +824,26 @@ public class Brew { legacyPotions.put(uid, brew); } - // remove legacy potiondata from item + // remove legacy potiondata for an item public static void removeLegacy(ItemStack item) { if (legacyPotions.isEmpty()) return; if (!item.hasItemMeta()) return; ItemMeta meta = item.getItemMeta(); if (!(meta instanceof PotionMeta)) return; - for (PotionEffect effect : ((PotionMeta) meta).getCustomEffects()) { - if (effect.getType().equals(PotionEffectType.REGENERATION)) { - if (effect.getDuration() < -1) { - legacyPotions.remove(effect.getDuration()); - return; - } - } - } + getFromPotionEffect(((PotionMeta) meta), true); } - public void convertLegacy(ItemStack item) { + public void convertPre19(ItemStack item) { removeLegacy(item); PotionMeta potionMeta = ((PotionMeta) item.getItemMeta()); if (hasRecipe()) { BrewLore lore = new BrewLore(this, potionMeta); lore.removeEffects(); PotionColor.fromString(currentRecipe.getColor()).colorBrew(potionMeta, item, canDistill()); + lore.removeLegacySpacing(); } else { PotionColor.GREY.colorBrew(potionMeta, item, canDistill()); + new BrewLore(this, potionMeta).removeLegacySpacing(); } save(potionMeta); item.setItemMeta(potionMeta); diff --git a/src/com/dre/brewery/api/BreweryApi.java b/src/com/dre/brewery/api/BreweryApi.java index ca75e72..0a4d536 100644 --- a/src/com/dre/brewery/api/BreweryApi.java +++ b/src/com/dre/brewery/api/BreweryApi.java @@ -10,6 +10,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; public class BreweryApi { @@ -46,6 +47,7 @@ public class BreweryApi { * Checks if item is actually a Brew * Returns null if item is not a Brew */ + @Nullable public static Brew getBrew(ItemStack item) { return Brew.get(item); } @@ -56,6 +58,7 @@ public class BreweryApi { * Checks if meta has a Brew saved * Returns null if meta is not a Brew */ + @Nullable public static Brew getBrew(ItemMeta meta) { return Brew.get(meta); } @@ -68,6 +71,7 @@ public class BreweryApi { * May be any Wood, Fence, Sign that is part of a Barrel * Returns null if block is not part of a Barrel */ + @Nullable public static Barrel getBarrel(Block block) { return Barrel.get(block); } @@ -77,6 +81,7 @@ public class BreweryApi { * May be any Wood, Fence or Sign that is part of a Barrel * Returns null if block is not part of a Barrel */ + @Nullable public static Inventory getBarrelInventory(Block block) { Barrel barrel = Barrel.get(block); if (barrel != null) { @@ -115,6 +120,7 @@ public class BreweryApi { * Get a BCauldron from a Block * Returns null if block is not a BCauldron */ + @Nullable public static BCauldron getCauldron(Block block) { return BCauldron.get(block); } @@ -136,6 +142,7 @@ public class BreweryApi { * The name is the middle one of the three if three are set in the config * Returns null if recipe with that name does not exist */ + @Nullable public static BRecipe getRecipe(String name) { return BRecipe.get(name); } diff --git a/src/com/dre/brewery/api/events/brew/BrewEvent.java b/src/com/dre/brewery/api/events/brew/BrewEvent.java index e3ccd53..c6fee36 100644 --- a/src/com/dre/brewery/api/events/brew/BrewEvent.java +++ b/src/com/dre/brewery/api/events/brew/BrewEvent.java @@ -3,20 +3,23 @@ package com.dre.brewery.api.events.brew; import com.dre.brewery.Brew; import org.bukkit.event.Event; import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; public abstract class BrewEvent extends Event { protected final Brew brew; protected final ItemMeta meta; - public BrewEvent(Brew brew, ItemMeta meta) { + public BrewEvent(@NotNull Brew brew, @NotNull ItemMeta meta) { this.brew = brew; this.meta = meta; } + @NotNull public Brew getBrew() { return brew; } + @NotNull public ItemMeta getItemMeta() { return meta; } diff --git a/src/com/dre/brewery/api/events/brew/BrewModifyEvent.java b/src/com/dre/brewery/api/events/brew/BrewModifyEvent.java index def9f4b..12c3c07 100644 --- a/src/com/dre/brewery/api/events/brew/BrewModifyEvent.java +++ b/src/com/dre/brewery/api/events/brew/BrewModifyEvent.java @@ -6,6 +6,7 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.PotionMeta; +import org.jetbrains.annotations.NotNull; /* * A Brew has been created or modified @@ -19,15 +20,17 @@ public class BrewModifyEvent extends BrewEvent implements Cancellable { private boolean cancelled; - public BrewModifyEvent(Brew brew, ItemMeta meta, Type type) { + public BrewModifyEvent(@NotNull Brew brew, @NotNull ItemMeta meta, @NotNull Type type) { super(brew, meta); this.type = type; } + @NotNull public Type getType() { return type; } + @NotNull public BrewLore getLore() { return new BrewLore(getBrew(), (PotionMeta) getItemMeta()); } @@ -46,6 +49,7 @@ public class BrewModifyEvent extends BrewEvent implements Cancellable { this.cancelled = cancelled; } + @NotNull @Override public HandlerList getHandlers() { return handlers; diff --git a/src/com/dre/brewery/filedata/BConfig.java b/src/com/dre/brewery/filedata/BConfig.java index 63a40c2..5e6dd2d 100644 --- a/src/com/dre/brewery/filedata/BConfig.java +++ b/src/com/dre/brewery/filedata/BConfig.java @@ -54,6 +54,7 @@ public class BConfig { //Brew public static boolean colorInBarrels; // color the Lore while in Barrels public static boolean colorInBrewer; // color the Lore while in Brewer + public static boolean enableEncode; public static P p = P.p; @@ -173,9 +174,12 @@ public class BConfig { homeType = config.getString("homeType", null); colorInBarrels = config.getBoolean("colorInBarrels", false); colorInBrewer = config.getBoolean("colorInBrewer", false); + enableEncode = config.getBoolean("enableEncode", false); openEverywhere = config.getBoolean("openLargeBarrelEverywhere", false); MCBarrel.maxBrews = config.getInt("maxBrewsInMCBarrels", 6); + Brew.loadSeed(config, file); + // loading recipes ConfigurationSection configSection = config.getConfigurationSection("recipes"); if (configSection != null) { diff --git a/src/com/dre/brewery/filedata/BData.java b/src/com/dre/brewery/filedata/BData.java index 0d4bed5..6d4a29a 100644 --- a/src/com/dre/brewery/filedata/BData.java +++ b/src/com/dre/brewery/filedata/BData.java @@ -31,7 +31,7 @@ public class BData { Brew.installTime = data.getLong("installTime", System.currentTimeMillis()); MCBarrel.mcBarrelTime = data.getLong("MCBarrelTime", 0); - Brew.loadSeed(data); + Brew.loadPrevSeeds(data); // Check if data is the newest version String version = data.getString("Version", null); diff --git a/src/com/dre/brewery/filedata/ConfigUpdater.java b/src/com/dre/brewery/filedata/ConfigUpdater.java index 60b1f71..b03e232 100644 --- a/src/com/dre/brewery/filedata/ConfigUpdater.java +++ b/src/com/dre/brewery/filedata/ConfigUpdater.java @@ -82,6 +82,31 @@ public class ConfigUpdater { + // ---- Updating Scramble Seed ---- + + public void setEncodeKey(long key) { + int index = indexOfStart("encodeKey:"); + if (index != -1) { + setLine(index, "encodeKey: " + key); + return; + } + + // Old key not present + index = indexOfStart("enableEncode:"); + if (index == -1) { + index = indexOfStart("# So enable this if you want to make recipe cheating harder"); + } + if (index == -1) { + index = indexOfStart("version:"); + } + if (index != -1) { + addLines(index + 1, "encodeKey: " + key); + } else { + addLines(1, "encodeKey: " + key); + } + + } + // ---- Updating to newer Versions ---- // Update from a specified Config version and language to the newest version diff --git a/src/com/dre/brewery/filedata/DataSave.java b/src/com/dre/brewery/filedata/DataSave.java index 0cd86ed..579f93e 100644 --- a/src/com/dre/brewery/filedata/DataSave.java +++ b/src/com/dre/brewery/filedata/DataSave.java @@ -64,7 +64,7 @@ public class DataSave extends BukkitRunnable { configFile.set("installTime", Brew.installTime); configFile.set("MCBarrelTime", MCBarrel.mcBarrelTime); - Brew.writeSeed(configFile); + Brew.writePrevSeeds(configFile); if (!Brew.legacyPotions.isEmpty()) { Brew.save(configFile.createSection("Brew")); diff --git a/src/com/dre/brewery/listeners/InventoryListener.java b/src/com/dre/brewery/listeners/InventoryListener.java index b180a48..bcc53b0 100644 --- a/src/com/dre/brewery/listeners/InventoryListener.java +++ b/src/com/dre/brewery/listeners/InventoryListener.java @@ -111,14 +111,14 @@ public class InventoryListener implements Listener { if (P.use1_9 && !potion.hasItemFlag(ItemFlag.HIDE_POTION_EFFECTS)) { Brew brew = Brew.get(potion); if (brew != null) { - brew.convertLegacy(item); + brew.convertPre19(item); } } Brew brew = Brew.get(item); if (brew != null) { P.p.log(brew.toString()); P.p.log(potion.getLore().get(0).replaceAll("§", "")); - P.p.log("similar to beispiel? " + BRecipe.get("Beispiel").createBrew(10).isSimilar(brew)); + //P.p.log("similar to beispiel? " + BRecipe.get("Beispiel").createBrew(10).isSimilar(brew)); brew.touch(); diff --git a/src/com/dre/brewery/lore/BrewLore.java b/src/com/dre/brewery/lore/BrewLore.java index 07cd4b5..0213911 100644 --- a/src/com/dre/brewery/lore/BrewLore.java +++ b/src/com/dre/brewery/lore/BrewLore.java @@ -179,7 +179,7 @@ public class BrewLore { // Removes all effects public void removeEffects() { if (meta.hasCustomEffects()) { - for (PotionEffect effect : meta.getCustomEffects()) { + for (PotionEffect effect : new ArrayList<>(meta.getCustomEffects())) { PotionEffectType type = effect.getType(); //if (!type.equals(PotionEffectType.REGENERATION)) { meta.removeCustomEffect(type); @@ -188,6 +188,13 @@ public class BrewLore { } } + public void removeLegacySpacing() { + if (lore.size() > 0 && lore.get(0).equals("")) { + lore.remove(0); + write(); + } + } + // True if the PotionMeta has Lore in quality color public static boolean hasColorLore(PotionMeta meta) { if (!meta.hasLore()) return false; diff --git a/src/com/dre/brewery/lore/SeedInputStream.java b/src/com/dre/brewery/lore/SeedInputStream.java index c293297..2d892c1 100644 --- a/src/com/dre/brewery/lore/SeedInputStream.java +++ b/src/com/dre/brewery/lore/SeedInputStream.java @@ -1,5 +1,7 @@ package com.dre.brewery.lore; +import org.jetbrains.annotations.NotNull; + import java.io.InputStream; import java.util.Arrays; @@ -34,7 +36,7 @@ public class SeedInputStream extends InputStream { } @Override - public int read(byte[] b, int off, int len) { + public int read(@NotNull byte[] b, int off, int len) { for (int i = off; i < len; i++) { if (reader >= 4) { genNext(); diff --git a/src/com/dre/brewery/lore/XORScrambleStream.java b/src/com/dre/brewery/lore/XORScrambleStream.java index f37dd43..d860088 100644 --- a/src/com/dre/brewery/lore/XORScrambleStream.java +++ b/src/com/dre/brewery/lore/XORScrambleStream.java @@ -5,17 +5,35 @@ import java.io.IOException; import java.io.OutputStream; import java.util.Random; +/** + * A Scramble Stream that uses XOR operations to scramble an outputstream. + * a byte generator feeded with the seed is used as xor source + * The resulting data can be unscrambled by the XORUnscrambleStream + */ public class XORScrambleStream extends FilterOutputStream { private final long seed; private SeedInputStream xorStream; private boolean running; + /** + * Create a new instance of an XORScrambler, scrambling the given outputstream + * + * @param out The Outputstream to be scrambled + * @param seed The seed used for scrambling + */ public XORScrambleStream(OutputStream out, long seed) { super(out); this.seed = seed; } + /** + * To start the scrambling process this has to be called before writing any data to this stream. + * Before starting the scrambler, any data will just be passed through unscrambled to the underlying stream. + * The Scrambling can be started and stopped arbitrarily at any point, allowing for parts of unscrambled data in the stream. + * + * @throws IOException IOException + */ public void start() throws IOException { running = true; if (xorStream == null) { @@ -30,10 +48,28 @@ public class XORScrambleStream extends FilterOutputStream { } } + /** + * Stop the scrambling, any following data will be passed through unscrambled. + * The scrambling can be started again at any point after calling this + */ public void stop() { running = false; } + /** + * Mark the stream as unscrambled, any effort of unscrambing the data later will automatically read the already unscrambled data + * Useful if a stream may be scrambled or unscrambled, the unscrambler will automatically identify either way. + * + * @throws IOException IOException + * @throws IllegalStateException If the Scrambler was started in normal scrambling mode before + */ + public void startUnscrambled() throws IOException, IllegalStateException { + if (xorStream != null) throw new IllegalStateException("The Scrambler was started in scrambling mode before"); + short id = 0; + out.write((byte) (id >> 8)); + out.write((byte) id); + } + @Override public void write(int b) throws IOException { if (!running) { diff --git a/src/com/dre/brewery/lore/XORUnscrambleStream.java b/src/com/dre/brewery/lore/XORUnscrambleStream.java index 25e45f5..f409cf3 100644 --- a/src/com/dre/brewery/lore/XORUnscrambleStream.java +++ b/src/com/dre/brewery/lore/XORUnscrambleStream.java @@ -1,38 +1,99 @@ package com.dre.brewery.lore; +import com.dre.brewery.P; +import org.jetbrains.annotations.NotNull; + import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.security.InvalidKeyException; +import java.util.List; +/** + * A Scramble Stream that uses XOR operations to unscramble an inputstream. + * a byte generator feeded with the seed is used as xor source + * Used to unscramble data generated by the XORScrambleStream + */ public class XORUnscrambleStream extends FilterInputStream { - private final long seed; + private long seed; + private final List prevSeeds; private SeedInputStream xorStream; private boolean running; private boolean markRunning; private boolean markxor; + /** + * Create a new instance of an XORUnscrambler, unscrambling the given inputstream + * + * @param in The Inputstream to be unscrambled + * @param seed The seed used for unscrambling + */ public XORUnscrambleStream(InputStream in, long seed) { super(in); this.seed = seed; + prevSeeds = null; } + /** + * Create a new instance of an XORUnscrambler, unscrambling the given inputstream + * If given a List of previous Seeds, the unscrambler will try all of them for unscrambling the stream in case the seed fails. + * + * @param in The Inputstream to be unscrambled + * @param seed The seed used for unscrambling + */ + public XORUnscrambleStream(InputStream in, long seed, List prevSeeds) { + super(in); + this.seed = seed; + this.prevSeeds = prevSeeds; + } + + /** + * Before unscrambling, this has to be called to tell the unscrambler that scrambled data will follow. + * Before starting the unscrambler, any data will just be passed through unmodified to the underlying stream. + * The Unscrambling can be started and stopped arbitrarily at any point, allowing for parts of already unscrambled data in the stream. + * + * @throws IOException IOException + * @throws InvalidKeyException If the scrambled data could not be read, very likely caused by a wrong seed. Thrown after checking all previous seeds. + */ public void start() throws IOException, InvalidKeyException { running = true; if (xorStream == null) { short id = (short) (in.read() << 8 | in.read()); if (id == 0) { running = false; + P.p.debugLog("Unscrambled data"); return; } + int parity = in.read(); xorStream = new SeedInputStream(seed ^ id); - if (read() != ((int) (seed >> 48) & 0xFF)) { // Parity/Sanity + boolean success = checkParity(parity); + if (success) P.p.debugLog("Using main Seed to unscramble"); + + if (!success && prevSeeds != null) { + for (int i = prevSeeds.size() - 1; i >= 0; i--) { + seed = prevSeeds.get(i); + xorStream = new SeedInputStream(seed ^ id); + if (success = checkParity(parity)) { + P.p.debugLog("Had to use prevSeed to unscramble"); + break; + } + } + } + if (!success) { throw new InvalidKeyException("Could not read scrambled data, is the seed wrong?"); } } } + private boolean checkParity(int parity) { + return ((parity ^ xorStream.read()) & 0xFF) == ((int) (seed >> 48) & 0xFF); // Parity/Sanity + } + + /** + * Stop the unscrambling, any following data will be passed through unmodified. + * The unscrambling can be started again at any point after calling this + */ public void stop() { running = false; } @@ -46,7 +107,7 @@ public class XORUnscrambleStream extends FilterInputStream { } @Override - public int read(byte[] b, int off, int len) throws IOException { + public int read(@NotNull byte[] b, int off, int len) throws IOException { if (!running) { return in.read(b, off, len); } @@ -57,6 +118,7 @@ public class XORUnscrambleStream extends FilterInputStream { return len; } + @SuppressWarnings("ResultOfMethodCallIgnored") @Override public long skip(long n) throws IOException { long skipped = in.skip(n);