diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 26f7520f6..36e6ffb1d 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -63,17 +63,21 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { @Override public void onEnable() { + GeyserLocale.init(this); + if (!getDataFolder().exists()) getDataFolder().mkdir(); try { if (!getDataFolder().exists()) getDataFolder().mkdir(); - File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), + "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserBungeeConfiguration.class); } catch (IOException ex) { - getLogger().log(Level.WARNING, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); + return; } if (getProxy().getConfig().getListeners().size() == 1) { diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index 0b89580bb..12a27190d 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -79,16 +79,21 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { @Override public void onEnable() { + GeyserLocale.init(this); + // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed try { if (!getDataFolder().exists()) { getDataFolder().mkdir(); } - File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + File configFile = FileUtils.fileOrCopiedFromResource(new File(getDataFolder(), "config.yml"), "config.yml", + (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpigotConfiguration.class); } catch (IOException ex) { - getLogger().log(Level.WARNING, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + getLogger().log(Level.SEVERE, GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); + Bukkit.getPluginManager().disablePlugin(this); + return; } try { diff --git a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java index 33b71fb63..f20f94a79 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/geyser/platform/sponge/GeyserSpongePlugin.java @@ -71,15 +71,19 @@ public class GeyserSpongePlugin implements GeyserBootstrap { @Override public void onEnable() { + GeyserLocale.init(this); + if (!configDir.exists()) configDir.mkdirs(); - File configFile = null; + File configFile; try { - configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml", (file) -> file.replaceAll("generateduuid", UUID.randomUUID().toString())); + configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml", + (file) -> file.replaceAll("generateduuid", UUID.randomUUID().toString()), this); } catch (IOException ex) { - logger.warn(GeyserLocale.getLocaleStringLog("geyser.config.failed")); + logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed")); ex.printStackTrace(); + return; } try { diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java index da215a399..b599550e3 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java @@ -90,6 +90,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { boolean useGuiOpts = bootstrap.useGui; String configFilenameOpt = bootstrap.configFilename; + GeyserLocale.init(bootstrap); + List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); for (int i = 0; i < args.length; i++) { @@ -188,7 +190,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { LoopbackUtil.checkLoopback(geyserLogger); try { - File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", + (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); handleArgsConfigOptions(); diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java index 5666fff7f..b0e39352c 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityPlugin.java @@ -83,16 +83,19 @@ public class GeyserVelocityPlugin implements GeyserBootstrap { @Override public void onEnable() { + GeyserLocale.init(this); + try { if (!configFolder.toFile().exists()) //noinspection ResultOfMethodCallIgnored configFolder.toFile().mkdirs(); File configFile = FileUtils.fileOrCopiedFromResource(configFolder.resolve("config.yml").toFile(), - "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); + "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString()), this); this.geyserConfig = FileUtils.loadConfig(configFile, GeyserVelocityConfiguration.class); } catch (IOException ex) { - logger.warn(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); + logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"), ex); ex.printStackTrace(); + return; } InetSocketAddress javaAddr = proxyServer.getBoundAddress(); diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index b313af9bb..8797d972a 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -25,16 +25,16 @@ package org.geysermc.geyser; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.configuration.GeyserConfiguration; -import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.command.CommandManager; +import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; import org.geysermc.geyser.level.GeyserWorldManager; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.ping.IGeyserPingPassthrough; +import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.io.InputStream; import java.net.SocketAddress; import java.nio.file.Path; import java.nio.file.Paths; @@ -126,4 +126,29 @@ public interface GeyserBootstrap { default Path getLogsPath() { return Paths.get("logs/latest.log"); } + + /** + * Get an InputStream for the given resource path. + * Overridden on platforms that have different class loader properties. + * + * @param resource Resource to get + * @return InputStream of the given resource, or null if not found + */ + default @Nullable InputStream getResourceOrNull(String resource) { + return GeyserBootstrap.class.getClassLoader().getResourceAsStream(resource); + } + + /** + * Get an InputStream for the given resource path, throws AssertionError if resource is not found. + * + * @param resource Resource to get + * @return InputStream of the given resource + */ + default @Nonnull InputStream getResource(String resource) { + InputStream stream = getResourceOrNull(resource); + if (stream == null) { + throw new AssertionError("Unable to find resource: " + resource); + } + return stream; + } } diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index eaadd15fa..bac5e0735 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -70,6 +70,7 @@ import org.geysermc.geyser.util.*; import javax.naming.directory.Attribute; import javax.naming.directory.InitialDirContext; +import java.io.InputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; @@ -139,6 +140,8 @@ public class GeyserImpl implements GeyserApi { this.platformType = platformType; + GeyserLocale.finalizeDefaultLocale(this); + logger.info("******************************************"); logger.info(""); logger.info(GeyserLocale.getLocaleStringLog("geyser.core.load", NAME, VERSION)); @@ -214,9 +217,9 @@ public class GeyserImpl implements GeyserApi { String branch = "unknown"; int buildNumber = -1; if (this.productionEnvironment()) { - try { + try (InputStream stream = bootstrap.getResource("git.properties")) { Properties gitProperties = new Properties(); - gitProperties.load(FileUtils.getResource("git.properties")); + gitProperties.load(stream); branch = gitProperties.getProperty("git.branch"); String build = gitProperties.getProperty("git.build.number"); if (build != null) { diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java index 706395d5d..dbec4d4f8 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/VersionCommand.java @@ -30,14 +30,14 @@ import org.geysermc.common.PlatformType; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.command.CommandSender; import org.geysermc.geyser.command.GeyserCommand; -import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.network.MinecraftProtocol; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.util.FileUtils; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.WebUtils; import java.io.IOException; +import java.io.InputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.List; @@ -69,9 +69,9 @@ public class VersionCommand extends GeyserCommand { // Disable update checking in dev mode and for players in Geyser Standalone if (GeyserImpl.getInstance().productionEnvironment() && !(!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE)) { sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.checking", sender.getLocale())); - try { + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { Properties gitProp = new Properties(); - gitProp.load(FileUtils.getResource("git.properties")); + gitProp.load(stream); String buildXML = WebUtils.getBody("https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/" + URLEncoder.encode(gitProp.getProperty("git.branch"), StandardCharsets.UTF_8.toString()) + "/lastSuccessfulBuild/api/xml?xpath=//buildNumber"); diff --git a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java index 5a77b84fb..da98d45b5 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -47,6 +47,7 @@ import org.geysermc.floodgate.util.FloodgateInfoHolder; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -78,9 +79,9 @@ public class DumpInfo { public DumpInfo(boolean addLog) { this.versionInfo = new VersionInfo(); - try { + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) { this.gitInfo = new Properties(); - this.gitInfo.load(FileUtils.getResource("git.properties")); + this.gitInfo.load(stream); } catch (IOException ignored) { } diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java index f510b3592..e685c8760 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/BiomeIdentifierRegistryLoader.java @@ -30,7 +30,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.util.FileUtils; import java.io.IOException; import java.io.InputStream; @@ -45,10 +44,10 @@ public class BiomeIdentifierRegistryLoader implements RegistryLoader> biomeEntriesType = new TypeReference>() { }; + TypeReference> biomeEntriesType = new TypeReference<>() { }; Map biomeEntries; - try (InputStream stream = FileUtils.getResource("mappings/biomes.json")) { + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("mappings/biomes.json")) { biomeEntries = GeyserImpl.JSON_MAPPER.readValue(stream, biomeEntriesType); } catch (IOException e) { throw new AssertionError("Unable to load Bedrock runtime biomes", e); diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java index 1587a5420..949c3ee96 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/CollisionRegistryLoader.java @@ -65,10 +65,8 @@ public class CollisionRegistryLoader extends MultiResourceRegistryLoader collisionList; - try { + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(input.value())) { ArrayNode collisionNode = (ArrayNode) GeyserImpl.JSON_MAPPER.readTree(stream); collisionList = loadBoundingBoxes(collisionNode); } catch (Exception e) { diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/EffectRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/EffectRegistryLoader.java index 7da3a3b6c..8d46d2639 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/EffectRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/EffectRegistryLoader.java @@ -27,7 +27,6 @@ package org.geysermc.geyser.registry.loader; import com.fasterxml.jackson.databind.JsonNode; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.util.FileUtils; import java.io.InputStream; import java.util.Map; @@ -43,10 +42,9 @@ public abstract class EffectRegistryLoader implements RegistryLoader> { @Override public Map load(String input) { - InputStream enchantmentsStream = FileUtils.getResource(input); JsonNode enchantmentsNode; - try { + try (InputStream enchantmentsStream = GeyserImpl.getInstance().getBootstrap().getResource(input)) { enchantmentsNode = GeyserImpl.JSON_MAPPER.readTree(enchantmentsStream); } catch (Exception e) { throw new AssertionError("Unable to load enchantment data", e); diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/NbtRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/NbtRegistryLoader.java index 6134e8d98..e4006c0f2 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/NbtRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/NbtRegistryLoader.java @@ -28,9 +28,7 @@ package org.geysermc.geyser.registry.loader; import com.nukkitx.nbt.NBTInputStream; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtUtils; -import org.geysermc.geyser.util.FileUtils; - -import java.io.InputStream; +import org.geysermc.geyser.GeyserImpl; /** * Loads NBT data from the given resource path. @@ -39,8 +37,8 @@ public class NbtRegistryLoader implements RegistryLoader { @Override public NbtMap load(String input) { - InputStream stream = FileUtils.getResource(input); - try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(stream, true, true)) { + try (NBTInputStream nbtInputStream = NbtUtils.createNetworkReader(GeyserImpl.getInstance().getBootstrap().getResource(input), + true, true)) { return (NbtMap) nbtInputStream.readTag(); } catch (Exception e) { throw new AssertionError("Failed to load registrations for " + input, e); diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java index d87b2921b..0cdb4ea5d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java +++ b/core/src/main/java/org/geysermc/geyser/registry/loader/SoundRegistryLoader.java @@ -28,7 +28,6 @@ package org.geysermc.geyser.registry.loader; import com.fasterxml.jackson.databind.JsonNode; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.registry.type.SoundMapping; -import org.geysermc.geyser.util.FileUtils; import java.io.IOException; import java.io.InputStream; @@ -43,9 +42,8 @@ public class SoundRegistryLoader implements RegistryLoader load(String input) { - InputStream stream = FileUtils.getResource(input); JsonNode soundsTree; - try { + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(input)) { soundsTree = GeyserImpl.JSON_MAPPER.readTree(stream); } catch (IOException e) { throw new AssertionError("Unable to load sound mappings", e); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index 8df6f0a7f..301dfa1da 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -43,7 +43,6 @@ import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.util.BlockUtils; -import org.geysermc.geyser.util.FileUtils; import java.io.DataInputStream; import java.io.InputStream; @@ -83,9 +82,9 @@ public class BlockRegistryPopulator { private static void registerBedrockBlocks() { for (Map.Entry, BiFunction> palette : BLOCK_MAPPERS.entrySet()) { - InputStream stream = FileUtils.getResource(String.format("bedrock/block_palette.%s.nbt", palette.getKey().key())); NbtList blocksTag; - try (NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)), true, true)) { + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(String.format("bedrock/block_palette.%s.nbt", palette.getKey().key())); + NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)), true, true)) { NbtMap blockPalette = (NbtMap) nbtInputStream.readTag(); blocksTag = (NbtList) blockPalette.getList("blocks", NbtType.COMPOUND); } catch (Exception e) { @@ -208,10 +207,8 @@ public class BlockRegistryPopulator { } private static void registerJavaBlocks() { - InputStream stream = FileUtils.getResource("mappings/blocks.json"); - JsonNode blocksJson; - try { + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("mappings/blocks.json")) { blocksJson = GeyserImpl.JSON_MAPPER.readTree(stream); } catch (Exception e) { throw new AssertionError("Unable to load Java block mappings", e); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 340c6a45c..6a730010e 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -38,14 +38,17 @@ import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import com.nukkitx.protocol.bedrock.v465.Bedrock_v465; import com.nukkitx.protocol.bedrock.v471.Bedrock_v471; import com.nukkitx.protocol.bedrock.v475.Bedrock_v475; -import it.unimi.dsi.fastutil.ints.*; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.objects.*; +import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.*; -import org.geysermc.geyser.util.FileUtils; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -69,13 +72,13 @@ public class ItemRegistryPopulator { } public static void populate() { - // Load item mappings from Java Edition to Bedrock Edition - InputStream stream = FileUtils.getResource("mappings/items.json"); + GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); TypeReference> mappingItemsType = new TypeReference<>() { }; Map items; - try { + try (InputStream stream = bootstrap.getResource("mappings/items.json")) { + // Load item mappings from Java Edition to Bedrock Edition items = GeyserImpl.JSON_MAPPER.readValue(stream, mappingItemsType); } catch (Exception e) { throw new AssertionError("Unable to load Java runtime item IDs", e); @@ -83,8 +86,6 @@ public class ItemRegistryPopulator { /* Load item palette */ for (Map.Entry palette : PALETTE_VERSIONS.entrySet()) { - stream = FileUtils.getResource(String.format("bedrock/runtime_item_states.%s.json", palette.getKey())); - TypeReference> paletteEntriesType = new TypeReference<>() {}; // Used to get the Bedrock namespaced ID (in instances where there are small differences) @@ -94,7 +95,7 @@ public class ItemRegistryPopulator { List itemNames = new ArrayList<>(); List itemEntries; - try { + try (InputStream stream = bootstrap.getResource(String.format("bedrock/runtime_item_states.%s.json", palette.getKey()))) { itemEntries = GeyserImpl.JSON_MAPPER.readValue(stream, paletteEntriesType); } catch (Exception e) { throw new AssertionError("Unable to load Bedrock runtime item IDs", e); @@ -112,10 +113,8 @@ public class ItemRegistryPopulator { // Load creative items // We load this before item mappings to get overridden block runtime ID mappings - stream = FileUtils.getResource(String.format("bedrock/creative_items.%s.json", palette.getKey())); - JsonNode creativeItemEntries; - try { + try (InputStream stream = bootstrap.getResource(String.format("bedrock/creative_items.%s.json", palette.getKey()))) { creativeItemEntries = GeyserImpl.JSON_MAPPER.readTree(stream).get("items"); } catch (Exception e) { throw new AssertionError("Unable to load creative items", e); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java index ecc3fbb29..7af45f74d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/RecipeRegistryPopulator.java @@ -40,12 +40,11 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; -import org.geysermc.geyser.util.FileUtils; import org.geysermc.geyser.text.GeyserLocale; +import org.geysermc.geyser.translator.inventory.item.ItemTranslator; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -60,10 +59,8 @@ import static org.geysermc.geyser.util.InventoryUtils.LAST_RECIPE_NET_ID; public class RecipeRegistryPopulator { public static void populate() { - InputStream stream = FileUtils.getResource("mappings/recipes.json"); - JsonNode items; - try { + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("mappings/recipes.json")) { items = GeyserImpl.JSON_MAPPER.readTree(stream); } catch (Exception e) { throw new AssertionError(GeyserLocale.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); diff --git a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java index e7b08ad3f..e51882036 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java +++ b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkin.java @@ -26,37 +26,36 @@ package org.geysermc.geyser.skin; import lombok.Getter; +import org.geysermc.geyser.GeyserImpl; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; public class ProvidedSkin { @Getter private byte[] skin; public ProvidedSkin(String internalUrl) { try { - BufferedImage image = ImageIO.read(ProvidedSkin.class.getClassLoader().getResource(internalUrl)); + BufferedImage image; + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(internalUrl)) { + image = ImageIO.read(stream); + } ByteArrayOutputStream outputStream = new ByteArrayOutputStream(image.getWidth() * 4 + image.getHeight() * 4); - try { - for (int y = 0; y < image.getHeight(); y++) { - for (int x = 0; x < image.getWidth(); x++) { - int rgba = image.getRGB(x, y); - outputStream.write((rgba >> 16) & 0xFF); // Red - outputStream.write((rgba >> 8) & 0xFF); // Green - outputStream.write(rgba & 0xFF); // Blue - outputStream.write((rgba >> 24) & 0xFF); // Alpha - } + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + int rgba = image.getRGB(x, y); + outputStream.write((rgba >> 16) & 0xFF); // Red + outputStream.write((rgba >> 8) & 0xFF); // Green + outputStream.write(rgba & 0xFF); // Blue + outputStream.write((rgba >> 24) & 0xFF); // Alpha } - image.flush(); - skin = outputStream.toByteArray(); - } finally { - try { - outputStream.close(); - } catch (IOException ignored) {} } + image.flush(); + skin = outputStream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index e6807e3f7..91c555f3d 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -94,19 +94,19 @@ public class SkinProvider { static { /* Load in the normal ears geometry */ - EARS_GEOMETRY = new String(FileUtils.readAllBytes(FileUtils.getResource("bedrock/skin/geometry.humanoid.ears.json")), StandardCharsets.UTF_8); + EARS_GEOMETRY = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.ears.json"), StandardCharsets.UTF_8); /* Load in the slim ears geometry */ - EARS_GEOMETRY_SLIM = new String(FileUtils.readAllBytes(FileUtils.getResource("bedrock/skin/geometry.humanoid.earsSlim.json")), StandardCharsets.UTF_8); + EARS_GEOMETRY_SLIM = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.earsSlim.json"), StandardCharsets.UTF_8); /* Load in the custom skull geometry */ - String skullData = new String(FileUtils.readAllBytes(FileUtils.getResource("bedrock/skin/geometry.humanoid.customskull.json")), StandardCharsets.UTF_8); + String skullData = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.customskull.json"), StandardCharsets.UTF_8); SKULL_GEOMETRY = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.customskull\"}}", skullData, false); /* Load in the player head skull geometry */ - String wearingCustomSkull = new String(FileUtils.readAllBytes(FileUtils.getResource("bedrock/skin/geometry.humanoid.wearingCustomSkull.json")), StandardCharsets.UTF_8); + String wearingCustomSkull = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkull.json"), StandardCharsets.UTF_8); WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull, false); - String wearingCustomSkullSlim = new String(FileUtils.readAllBytes(FileUtils.getResource("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json")), StandardCharsets.UTF_8); + String wearingCustomSkullSlim = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json"), StandardCharsets.UTF_8); WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim, false); // Schedule Daily Image Expiry if we are caching them diff --git a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java index 36e5aaae1..8fc98402a 100644 --- a/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java +++ b/core/src/main/java/org/geysermc/geyser/text/GeyserLocale.java @@ -25,9 +25,10 @@ package org.geysermc.geyser.text; +import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.util.FileUtils; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; @@ -40,48 +41,106 @@ import java.util.Properties; public class GeyserLocale { /** - * If we determine the locale that the user wishes to use, use that locale + * If we determine the default locale that the user wishes to use, use that locale */ - private static String CACHED_LOCALE; + private static String DEFAULT_LOCALE; + /** + * Whether the system locale cannot be loaded by Geyser. + */ + private static boolean SYSTEM_LOCALE_INVALID; private static final Map LOCALE_MAPPINGS = new HashMap<>(); - static { - // Load it as a backup in case something goes really wrong - if (!"en_US".equals(formatLocale(getDefaultLocale()))) { // getDefaultLocale() loads the locale automatically - loadGeyserLocale("en_US"); + /** + * Loads the initial locale(s) with the help of the bootstrap. + */ + public static void init(GeyserBootstrap bootstrap) { + String defaultLocale = formatLocale(Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry()); + String loadedLocale = loadGeyserLocale(defaultLocale, bootstrap); + if (loadedLocale != null) { + DEFAULT_LOCALE = loadedLocale; + // Load English as a backup in case something goes really wrong + if (!"en_US".equals(loadedLocale)) { + loadGeyserLocale("en_US", bootstrap); + } + SYSTEM_LOCALE_INVALID = false; + } else { + DEFAULT_LOCALE = loadGeyserLocale("en_US", bootstrap); + if (DEFAULT_LOCALE == null) { + // en_US can't be loaded? + throw new IllegalStateException("English locale not found in Geyser. Did you clone the submodules? (git submodule update --init)"); + } + SYSTEM_LOCALE_INVALID = true; } } + /** + * Finalize the default locale, now that we know what the default locale should be. + */ + public static void finalizeDefaultLocale(GeyserImpl geyser) { + String newDefaultLocale = geyser.getConfig().getDefaultLocale(); + if (newDefaultLocale == null) { + // We want to use the system locale which is already loaded + return; + } + String loadedNewLocale = loadGeyserLocale(newDefaultLocale, geyser.getBootstrap()); + if (loadedNewLocale != null) { + // The config's locale is valid + DEFAULT_LOCALE = loadedNewLocale; + } else if (SYSTEM_LOCALE_INVALID) { + geyser.getLogger().warning(Locale.getDefault().toString() + " is not a valid Bedrock language."); + } + } + + public static String getDefaultLocale() { + return DEFAULT_LOCALE; + } + /** * Loads a Geyser locale from resources, if the file doesn't exist it just logs a warning * * @param locale Locale to load */ public static void loadGeyserLocale(String locale) { + GeyserImpl geyser = GeyserImpl.getInstance(); + if (geyser == null) { + throw new IllegalStateException("Geyser instance cannot be null when loading a locale!"); + } + loadGeyserLocale(locale, geyser.getBootstrap()); + } + + private static String loadGeyserLocale(String locale, GeyserBootstrap bootstrap) { locale = formatLocale(locale); // Don't load the locale if it's already loaded. if (LOCALE_MAPPINGS.containsKey(locale)) { - return; + return locale; } - InputStream localeStream = GeyserImpl.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties"); + InputStream localeStream = bootstrap.getResourceOrNull("languages/texts/" + locale + ".properties"); // Load the locale if (localeStream != null) { - Properties localeProp = new Properties(); - try (InputStreamReader reader = new InputStreamReader(localeStream, StandardCharsets.UTF_8)) { - localeProp.load(reader); - } catch (Exception e) { - throw new AssertionError(getLocaleStringLog("geyser.language.load_failed", locale), e); - } + try { + Properties localeProp = new Properties(); + try (InputStreamReader reader = new InputStreamReader(localeStream, StandardCharsets.UTF_8)) { + localeProp.load(reader); + } catch (Exception e) { + throw new AssertionError(getLocaleStringLog("geyser.language.load_failed", locale), e); + } - // Insert the locale into the mappings - LOCALE_MAPPINGS.put(locale, localeProp); + // Insert the locale into the mappings + LOCALE_MAPPINGS.put(locale, localeProp); + return locale; + } finally { + try { + localeStream.close(); + } catch (IOException ignored) {} + } } else { - if (GeyserImpl.getInstance() != null && GeyserImpl.getInstance().getLogger() != null) { + if (GeyserImpl.getInstance() != null) { GeyserImpl.getInstance().getLogger().warning("Missing locale: " + locale); } + return null; } } @@ -157,66 +216,4 @@ public class GeyserLocale { String country = locale.substring(3); return language.toLowerCase(Locale.ENGLISH) + "_" + country.toUpperCase(Locale.ENGLISH); } - - /** - * Get the default locale that Geyser should use - * @return the current default locale - */ - public static String getDefaultLocale() { - if (CACHED_LOCALE != null) { - return CACHED_LOCALE; // We definitely know the locale the user is using - } - - String locale; - boolean isValid = true; - if (GeyserImpl.getInstance() != null && - GeyserImpl.getInstance().getConfig() != null && - GeyserImpl.getInstance().getConfig().getDefaultLocale() != null) { // If the config option for getDefaultLocale does not equal null, use that - locale = formatLocale(GeyserImpl.getInstance().getConfig().getDefaultLocale()); - if (isValidLanguage(locale)) { - CACHED_LOCALE = locale; - return locale; - } else { - isValid = false; - } - } - locale = formatLocale(Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry()); - if (!isValidLanguage(locale)) { // Bedrock does not support this language - locale = "en_US"; - loadGeyserLocale(locale); - } - if (GeyserImpl.getInstance() != null && - GeyserImpl.getInstance().getConfig() != null && (GeyserImpl.getInstance().getConfig().getDefaultLocale() == null || !isValid)) { // Means we should use the system locale for sure - CACHED_LOCALE = locale; - } - return locale; - } - - /** - * Ensures that the given locale is supported by Bedrock - * @param locale the locale to validate - * @return true if the given locale is supported by Bedrock and by extension Geyser - */ - private static boolean isValidLanguage(String locale) { - boolean result = true; - if (FileUtils.class.getResource("/languages/texts/" + locale + ".properties") == null) { - result = false; - if (GeyserImpl.getInstance() != null && GeyserImpl.getInstance().getLogger() != null) { // Could be too early for these to be initialized - if (locale.equals("en_US")) { - GeyserImpl.getInstance().getLogger().error("English locale not found in Geyser. Did you clone the submodules? (git submodule update --init)"); - } else { - GeyserImpl.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language - } - } - } else { - if (!LOCALE_MAPPINGS.containsKey(locale)) { - loadGeyserLocale(locale); - } - } - return result; - } - - public static void init() { - // no-op - } } diff --git a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java index 0671c5df7..691ebff44 100644 --- a/core/src/main/java/org/geysermc/geyser/util/FileUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/FileUtils.java @@ -25,10 +25,9 @@ package org.geysermc.geyser.util; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import java.io.*; @@ -58,28 +57,11 @@ public class FileUtils { return objectMapper.readValue(src, valueType); } - public static T loadYaml(InputStream src, Class valueType) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()).enable(JsonParser.Feature.IGNORE_UNDEFINED).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - return objectMapper.readValue(src, valueType); - } - public static T loadJson(InputStream src, Class valueType) throws IOException { // Read specifically with UTF-8 to allow any non-UTF-encoded JSON to read return GeyserImpl.JSON_MAPPER.readValue(new InputStreamReader(src, StandardCharsets.UTF_8), valueType); } - /** - * Open the specified file or copy if from resources - * - * @param name File and resource name - * @param fallback Formatting callback - * @return File handle of the specified file - * @throws IOException if the file failed to copy from resource - */ - public static File fileOrCopiedFromResource(String name, Function fallback) throws IOException { - return fileOrCopiedFromResource(new File(name), name, fallback); - } - /** * Open the specified file or copy if from resources * @@ -89,12 +71,12 @@ public class FileUtils { * @return File handle of the specified file * @throws IOException if the file failed to copy from resource */ - public static File fileOrCopiedFromResource(File file, String name, Function format) throws IOException { + public static File fileOrCopiedFromResource(File file, String name, Function format, GeyserBootstrap bootstrap) throws IOException { if (!file.exists()) { //noinspection ResultOfMethodCallIgnored file.createNewFile(); try (FileOutputStream fos = new FileOutputStream(file)) { - try (InputStream input = GeyserImpl.class.getResourceAsStream("/" + name)) { // resources need leading "/" prefix + try (InputStream input = bootstrap.getResource(name)) { byte[] bytes = new byte[input.available()]; //noinspection ResultOfMethodCallIgnored @@ -144,20 +126,6 @@ public class FileUtils { writeFile(new File(name), data); } - /** - * Get an InputStream for the given resource path, throws AssertionError if resource is not found - * - * @param resource Resource to get - * @return InputStream of the given resource - */ - public static InputStream getResource(String resource) { - InputStream stream = FileUtils.class.getClassLoader().getResourceAsStream(resource); - if (stream == null) { - throw new AssertionError("Unable to find resource: " + resource); - } - return stream; - } - /** * Calculate the SHA256 hash of a file * @@ -208,6 +176,18 @@ public class FileUtils { } } + /** + * @param resource the internal resource to read off from + * @return the byte array of an InputStream + */ + public static byte[] readAllBytes(String resource) { + try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(resource)) { + return readAllBytes(stream); + } catch (IOException e) { + throw new RuntimeException("Error while trying to read internal input stream!", e); + } + } + /** * @param stream the InputStream to read off of * @return the byte array of an InputStream @@ -265,15 +245,18 @@ public class FileUtils { * @return a set of all the classes annotated by the given annotation */ public static Set> getGeneratedClassesForAnnotation(String input) { - InputStream annotatedClass = FileUtils.getResource(input); - BufferedReader reader = new BufferedReader(new InputStreamReader(annotatedClass)); - return reader.lines().map(className -> { - try { - return Class.forName(className); - } catch (ClassNotFoundException ex) { - GeyserImpl.getInstance().getLogger().error("Failed to find class " + className, ex); - throw new RuntimeException(ex); - } - }).collect(Collectors.toSet()); + try (InputStream annotatedClass = GeyserImpl.getInstance().getBootstrap().getResource(input); + BufferedReader reader = new BufferedReader(new InputStreamReader(annotatedClass))) { + return reader.lines().map(className -> { + try { + return Class.forName(className); + } catch (ClassNotFoundException ex) { + GeyserImpl.getInstance().getLogger().error("Failed to find class " + className, ex); + throw new RuntimeException(ex); + } + }).collect(Collectors.toSet()); + } catch (IOException e) { + throw new RuntimeException(e); + } } }