From c61d87714be5fad45f71dacd60b5d3d5fe378cb7 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 9 Apr 2020 00:20:41 +0100 Subject: [PATCH] On demand downloading and loading of language files --- .../network/session/GeyserSession.java | 4 + .../geysermc/connector/utils/LocaleUtils.java | 142 +++++++++++------- .../geysermc/connector/utils/WebUtils.java | 25 +++ 3 files changed, 116 insertions(+), 55 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 7ce1f4c6..7b133e48 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -64,6 +64,7 @@ import org.geysermc.connector.network.session.cache.*; import org.geysermc.connector.network.translators.Registry; import org.geysermc.connector.network.translators.block.BlockTranslator; import org.geysermc.connector.utils.ChunkUtils; +import org.geysermc.connector.utils.LocaleUtils; import org.geysermc.connector.utils.Toolbox; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -251,6 +252,9 @@ public class GeyserSession implements CommandSender { connector.getLogger().info(authData.getName() + " (logged in as: " + protocol.getProfile().getName() + ")" + " has connected to remote java server on address " + remoteServer.getAddress()); playerEntity.setUuid(protocol.getProfile().getId()); playerEntity.setUsername(protocol.getProfile().getName()); + + // Download and load the language for the player + LocaleUtils.downloadAndLoadLocale(clientData.getLanguageCode()); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index 55540ea3..c057d4f7 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + package org.geysermc.connector.utils; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -15,77 +40,70 @@ public class LocaleUtils { public static final Map> LOCALE_MAPPINGS = new HashMap<>(); + private static final Map ASSET_MAP = new HashMap<>(); + static { - /* Load the language mappings */ - InputStream stream = Toolbox.getResource("mappings/locales.json"); - JsonNode locales; - try { - locales = Toolbox.JSON_MAPPER.readTree(stream); - } catch (Exception e) { - throw new AssertionError("Unable to load Java locale list", e); - } - + // Create the locales folder File localesFolder = new File("locales/"); + localesFolder.mkdir(); - if (!localesFolder.exists()) { - GeyserConnector.getInstance().getLogger().info("Locales not cached, downloading... (this may take some time depending on your internet connection)"); - ObjectMapper mapper = new ObjectMapper(); - try { - VersionManifest versionManifest = mapper.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); - String latestInfoURL = ""; - for (Version version : versionManifest.getVersions()) { - if (version.getId().equals(versionManifest.getLatestVersion().getRelease())) { - latestInfoURL = version.getUrl(); - break; - } + // Download the latest asset list and cache it + generateAssetCache(); + downloadAndLoadLocale(GeyserConnector.getInstance().getConfig().getDefaultLocale()); + } + + private static void generateAssetCache() { + try { + VersionManifest versionManifest = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); + String latestInfoURL = ""; + for (Version version : versionManifest.getVersions()) { + if (version.getId().equals(versionManifest.getLatestVersion().getRelease())) { + latestInfoURL = version.getUrl(); + break; } - - if (latestInfoURL.isEmpty()) { - throw new Exception("Unable to get latest Minecraft version"); - } - - VersionInfo versionInfo = mapper.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); - JsonNode assets = mapper.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); - - localesFolder.mkdir(); - - for (JsonNode localeNode : locales.get("locales")) { - String currentLocale = localeNode.asText(); - - if (currentLocale.equals("en_us")) { continue; } - - GeyserConnector.getInstance().getLogger().info("Downloading locale: " + currentLocale); - Asset asset = mapper.treeToValue(assets.get("minecraft/lang/" + currentLocale + ".json"), Asset.class); - String hash = asset.getHash(); - FileUtils.writeFile("locales/" + currentLocale + ".json", WebUtils.getBody("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash).toCharArray()); - } - } catch (Exception e) { - GeyserConnector.getInstance().getLogger().info("Failed to load locales: " + (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace())); } - } - if (localesFolder.exists()) { - for (JsonNode localeNode : locales.get("locales")) { - String currentLocale = localeNode.asText(); - loadLocale(currentLocale); + if (latestInfoURL.isEmpty()) { + throw new Exception("Unable to get latest Minecraft version"); } + + VersionInfo versionInfo = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); + JsonNode assets = Toolbox.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); + + Iterator> assetIterator = assets.fields(); + while (assetIterator.hasNext()) { + Map.Entry entry = assetIterator.next(); + Asset asset = Toolbox.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); + ASSET_MAP.put(entry.getKey(), asset); + } + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().info("Failed to load locale asset cache: " + (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace())); } } public static void downloadAndLoadLocale(String locale) { + locale = locale.toLowerCase(); + if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) { + GeyserConnector.getInstance().getLogger().warning("Invalid locale requested to download and load: " + locale); + return; + } + + GeyserConnector.getInstance().getLogger().debug("Downloading and loading locale: " + locale); + downloadLocale(locale); loadLocale(locale); } private static void downloadLocale(String locale) { - - } - - private static void loadLocale(String locale) { File localeFile = new File("locales/" + locale + ".json"); + if (localeFile.exists()) { + GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale); + return; + } + // Create the en_us locale - if (!localeFile.exists() && locale.equals("en_us")) { + if (locale.equals("en_us")) { try { InputStreamReader isReader = new InputStreamReader(Toolbox.getResource("mappings/lang/en_us.json")); BufferedReader reader = new BufferedReader(isReader); @@ -99,8 +117,22 @@ public class LocaleUtils { } catch (Exception e) { throw new AssertionError("Unable to load en_us locale!", e); } + + return; } + String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); + + try { + FileUtils.writeFile("locales/" + locale + ".json", WebUtils.getBody("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash).toCharArray()); + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().warning("Failed to download locale " + locale + ": " + (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace())); + } + } + + private static void loadLocale(String locale) { + File localeFile = new File("locales/" + locale + ".json"); + // Load the locale if (localeFile.exists()) { // Read the localefile @@ -112,15 +144,15 @@ public class LocaleUtils { } // Parse the file as json - JsonNode locale; + JsonNode localeObj; try { - locale = Toolbox.JSON_MAPPER.readTree(localeStream); + localeObj = Toolbox.JSON_MAPPER.readTree(localeStream); } catch (Exception e) { throw new AssertionError("Unable to load Java lang map for " + locale, e); } // Parse all the locale fields - Iterator> localeIterator = locale.fields(); + Iterator> localeIterator = localeObj.fields(); Map langMap = new HashMap<>(); while (localeIterator.hasNext()) { Map.Entry entry = localeIterator.next(); diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java index 9a6556be..17029fd9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + package org.geysermc.connector.utils; import org.geysermc.connector.GeyserConnector;