Allow for implementations to provide a custom resource loader

This will allow Geyser-Fabric to work without resource loading issues. This commit also ensures try-with-resources is used anywhere a resource is accessed.
This commit is contained in:
Camotoy 2021-12-03 11:01:06 -05:00
parent 9084c59003
commit 763743a845
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
22 changed files with 220 additions and 211 deletions

View file

@ -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) {

View file

@ -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 {

View file

@ -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 {

View file

@ -90,6 +90,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
boolean useGuiOpts = bootstrap.useGui;
String configFilenameOpt = bootstrap.configFilename;
GeyserLocale.init(bootstrap);
List<BeanPropertyDefinition> 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();

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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");

View file

@ -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) {
}

View file

@ -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<String, Obj
// The server sends the corresponding Java network IDs, so we don't need to worry about that now.
// Reference variable for Jackson to read off of
TypeReference<Map<String, BiomeEntry>> biomeEntriesType = new TypeReference<Map<String, BiomeEntry>>() { };
TypeReference<Map<String, BiomeEntry>> biomeEntriesType = new TypeReference<>() { };
Map<String, BiomeEntry> 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);

View file

@ -65,10 +65,8 @@ public class CollisionRegistryLoader extends MultiResourceRegistryLoader<String,
}
// Load collision mappings file
InputStream stream = FileUtils.getResource(input.value());
List<BoundingBox[]> 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) {

View file

@ -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<T> implements RegistryLoader<String,
public void loadFile(String input) {
if (!loadedFiles.containsKey(input)) {
InputStream effectsStream = FileUtils.getResource(input);
JsonNode effects;
try {
effects = GeyserImpl.JSON_MAPPER.readTree(effectsStream);
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource(input)) {
effects = GeyserImpl.JSON_MAPPER.readTree(stream);
} catch (Exception e) {
throw new AssertionError("Unable to load registrations for " + input, e);
}

View file

@ -29,12 +29,11 @@ import com.fasterxml.jackson.databind.JsonNode;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.MinecraftProtocol;
import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment;
import org.geysermc.geyser.network.MinecraftProtocol;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.EnchantmentData;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.FileUtils;
import java.io.InputStream;
import java.util.EnumMap;
@ -45,9 +44,8 @@ import java.util.Map;
public class EnchantmentRegistryLoader implements RegistryLoader<String, Map<JavaEnchantment, EnchantmentData>> {
@Override
public Map<JavaEnchantment, EnchantmentData> 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);

View file

@ -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<String, NbtMap> {
@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);

View file

@ -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<String, Map<String, S
@Override
public Map<String, SoundMapping> 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);

View file

@ -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<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> palette : BLOCK_MAPPERS.entrySet()) {
InputStream stream = FileUtils.getResource(String.format("bedrock/block_palette.%s.nbt", palette.getKey().key()));
NbtList<NbtMap> 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<NbtMap>) 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);

View file

@ -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<Map<String, GeyserMappingItem>> mappingItemsType = new TypeReference<>() { };
Map<String, GeyserMappingItem> 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<String, PaletteVersion> palette : PALETTE_VERSIONS.entrySet()) {
stream = FileUtils.getResource(String.format("bedrock/runtime_item_states.%s.json", palette.getKey()));
TypeReference<List<PaletteItem>> 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<String> itemNames = new ArrayList<>();
List<PaletteItem> 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);

View file

@ -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);

View file

@ -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();
}

View file

@ -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

View file

@ -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<String, Properties> 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
}
}

View file

@ -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> T loadYaml(InputStream src, Class<T> 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> T loadJson(InputStream src, Class<T> 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<String, String> 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<String, String> format) throws IOException {
public static File fileOrCopiedFromResource(File file, String name, Function<String, String> 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<Class<?>> 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);
}
}
}