2020-04-08 23:20:41 +00:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2020-04-08 20:11:56 +00:00
|
|
|
package org.geysermc.connector.utils;
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
|
|
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
2020-06-27 05:58:52 +00:00
|
|
|
import com.github.steveice10.mc.protocol.MinecraftConstants;
|
2020-04-08 20:11:56 +00:00
|
|
|
import lombok.Getter;
|
|
|
|
import org.geysermc.connector.GeyserConnector;
|
|
|
|
|
|
|
|
import java.io.*;
|
2020-04-09 13:36:32 +00:00
|
|
|
import java.nio.file.Files;
|
2020-06-20 17:54:40 +00:00
|
|
|
import java.nio.file.Path;
|
2020-04-09 13:36:32 +00:00
|
|
|
import java.nio.file.Paths;
|
2020-06-27 05:58:52 +00:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
2020-04-09 13:36:32 +00:00
|
|
|
import java.util.zip.ZipFile;
|
2020-04-08 20:11:56 +00:00
|
|
|
|
|
|
|
public class LocaleUtils {
|
|
|
|
|
|
|
|
public static final Map<String, Map<String, String>> LOCALE_MAPPINGS = new HashMap<>();
|
|
|
|
|
2020-04-08 23:20:41 +00:00
|
|
|
private static final Map<String, Asset> ASSET_MAP = new HashMap<>();
|
2020-04-08 20:11:56 +00:00
|
|
|
|
2020-04-09 13:36:32 +00:00
|
|
|
private static String smallestURL = "";
|
|
|
|
|
2020-04-08 23:20:41 +00:00
|
|
|
static {
|
|
|
|
// Create the locales folder
|
2020-06-20 17:54:40 +00:00
|
|
|
File localesFolder = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales").toFile();
|
|
|
|
//noinspection ResultOfMethodCallIgnored
|
2020-04-08 23:20:41 +00:00
|
|
|
localesFolder.mkdir();
|
2020-04-08 20:11:56 +00:00
|
|
|
|
2020-04-08 23:20:41 +00:00
|
|
|
// Download the latest asset list and cache it
|
|
|
|
generateAssetCache();
|
2020-07-05 23:35:51 +00:00
|
|
|
downloadAndLoadLocale(LanguageUtils.getDefaultLocale());
|
2020-04-08 23:20:41 +00:00
|
|
|
}
|
2020-04-08 20:11:56 +00:00
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
/**
|
|
|
|
* Fetch the latest versions asset cache from Mojang so we can grab the locale files later
|
|
|
|
*/
|
2020-04-08 23:20:41 +00:00
|
|
|
private static void generateAssetCache() {
|
|
|
|
try {
|
2020-04-21 05:28:44 +00:00
|
|
|
// Get the version manifest from Mojang
|
2020-05-25 01:07:05 +00:00
|
|
|
VersionManifest versionManifest = GeyserConnector.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class);
|
2020-04-21 05:28:44 +00:00
|
|
|
|
|
|
|
// Get the url for the latest version of the games manifest
|
2020-04-08 23:20:41 +00:00
|
|
|
String latestInfoURL = "";
|
|
|
|
for (Version version : versionManifest.getVersions()) {
|
2020-06-27 05:58:52 +00:00
|
|
|
if (version.getId().equals(MinecraftConstants.GAME_VERSION)) {
|
2020-04-08 23:20:41 +00:00
|
|
|
latestInfoURL = version.getUrl();
|
|
|
|
break;
|
2020-04-08 20:11:56 +00:00
|
|
|
}
|
2020-04-08 23:20:41 +00:00
|
|
|
}
|
2020-04-08 20:11:56 +00:00
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
// Make sure we definitely got a version
|
2020-04-08 23:20:41 +00:00
|
|
|
if (latestInfoURL.isEmpty()) {
|
2020-07-05 23:35:51 +00:00
|
|
|
throw new Exception(LanguageUtils.getLocaleStringLog("geyser.locale.fail.latest_version"));
|
2020-04-08 20:11:56 +00:00
|
|
|
}
|
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
// Get the individual version manifest
|
2020-05-25 01:07:05 +00:00
|
|
|
VersionInfo versionInfo = GeyserConnector.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class);
|
2020-04-09 13:36:32 +00:00
|
|
|
|
2020-06-27 05:58:52 +00:00
|
|
|
// Get the client jar for use when downloading the en_us locale
|
|
|
|
GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(versionInfo.getDownloads()));
|
|
|
|
VersionDownload download = versionInfo.getDownloads().get("client");
|
|
|
|
GeyserConnector.getInstance().getLogger().debug(GeyserConnector.JSON_MAPPER.writeValueAsString(download));
|
|
|
|
smallestURL = download.getUrl();
|
2020-04-09 13:36:32 +00:00
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
// Get the assets list
|
2020-05-25 01:07:05 +00:00
|
|
|
JsonNode assets = GeyserConnector.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects");
|
2020-04-08 23:20:41 +00:00
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
// Put each asset into an array for use later
|
2020-04-08 23:20:41 +00:00
|
|
|
Iterator<Map.Entry<String, JsonNode>> assetIterator = assets.fields();
|
|
|
|
while (assetIterator.hasNext()) {
|
|
|
|
Map.Entry<String, JsonNode> entry = assetIterator.next();
|
2020-05-25 01:07:05 +00:00
|
|
|
Asset asset = GeyserConnector.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class);
|
2020-04-08 23:20:41 +00:00
|
|
|
ASSET_MAP.put(entry.getKey(), asset);
|
2020-04-08 20:11:56 +00:00
|
|
|
}
|
2020-04-08 23:20:41 +00:00
|
|
|
} catch (Exception e) {
|
2020-07-05 23:35:51 +00:00
|
|
|
GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.fail.asset_cache", (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace())));
|
2020-04-08 20:11:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
/**
|
|
|
|
* Downloads a locale from Mojang if its not already loaded
|
|
|
|
*
|
|
|
|
* @param locale Locale to download and load
|
|
|
|
*/
|
2020-04-08 20:11:56 +00:00
|
|
|
public static void downloadAndLoadLocale(String locale) {
|
2020-04-08 23:20:41 +00:00
|
|
|
locale = locale.toLowerCase();
|
2020-04-21 05:28:44 +00:00
|
|
|
|
|
|
|
// Check the locale isn't already loaded
|
2020-04-08 23:20:41 +00:00
|
|
|
if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) {
|
2020-07-05 23:35:51 +00:00
|
|
|
GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.locale.fail.invalid", locale));
|
2020-04-08 23:20:41 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
GeyserConnector.getInstance().getLogger().debug("Downloading and loading locale: " + locale);
|
|
|
|
|
2020-04-08 20:11:56 +00:00
|
|
|
downloadLocale(locale);
|
|
|
|
loadLocale(locale);
|
|
|
|
}
|
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
/**
|
|
|
|
* Downloads the specified locale if its not already downloaded
|
|
|
|
*
|
|
|
|
* @param locale Locale to download
|
|
|
|
*/
|
2020-04-08 20:11:56 +00:00
|
|
|
private static void downloadLocale(String locale) {
|
2020-06-20 17:54:40 +00:00
|
|
|
File localeFile = Paths.get(GeyserConnector.getInstance().getBootstrap().getConfigFolder().toString(),"locales",locale + ".json").toFile();
|
2020-04-08 20:11:56 +00:00
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
// Check if we have already downloaded the locale file
|
2020-04-08 23:20:41 +00:00
|
|
|
if (localeFile.exists()) {
|
|
|
|
GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-04-08 20:11:56 +00:00
|
|
|
// Create the en_us locale
|
2020-04-08 23:20:41 +00:00
|
|
|
if (locale.equals("en_us")) {
|
2020-04-09 13:36:32 +00:00
|
|
|
downloadEN_US(localeFile);
|
2020-04-08 23:20:41 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-04-09 13:36:32 +00:00
|
|
|
// Get the hash and download the locale
|
2020-04-08 23:20:41 +00:00
|
|
|
String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
|
2020-06-20 17:54:40 +00:00
|
|
|
WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
|
2020-04-08 23:20:41 +00:00
|
|
|
}
|
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
/**
|
|
|
|
* Loads a locale already downloaded, if the file doesn't exist it just logs a warning
|
|
|
|
*
|
|
|
|
* @param locale Locale to load
|
|
|
|
*/
|
2020-04-08 23:20:41 +00:00
|
|
|
private static void loadLocale(String locale) {
|
2020-06-27 05:58:52 +00:00
|
|
|
File localeFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile();
|
2020-04-08 20:11:56 +00:00
|
|
|
|
|
|
|
// Load the locale
|
|
|
|
if (localeFile.exists()) {
|
|
|
|
// Read the localefile
|
|
|
|
InputStream localeStream;
|
|
|
|
try {
|
|
|
|
localeStream = new FileInputStream(localeFile);
|
|
|
|
} catch (FileNotFoundException e) {
|
2020-07-05 23:35:51 +00:00
|
|
|
throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.file", locale, e.getMessage()));
|
2020-04-08 20:11:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the file as json
|
2020-04-08 23:20:41 +00:00
|
|
|
JsonNode localeObj;
|
2020-04-08 20:11:56 +00:00
|
|
|
try {
|
2020-05-25 01:07:05 +00:00
|
|
|
localeObj = GeyserConnector.JSON_MAPPER.readTree(localeStream);
|
2020-04-08 20:11:56 +00:00
|
|
|
} catch (Exception e) {
|
2020-07-05 23:35:51 +00:00
|
|
|
throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.json", locale), e);
|
2020-04-08 20:11:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse all the locale fields
|
2020-04-08 23:20:41 +00:00
|
|
|
Iterator<Map.Entry<String, JsonNode>> localeIterator = localeObj.fields();
|
2020-04-08 20:11:56 +00:00
|
|
|
Map<String, String> langMap = new HashMap<>();
|
|
|
|
while (localeIterator.hasNext()) {
|
|
|
|
Map.Entry<String, JsonNode> entry = localeIterator.next();
|
|
|
|
langMap.put(entry.getKey(), entry.getValue().asText());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert the locale into the mappings
|
|
|
|
LOCALE_MAPPINGS.put(locale.toLowerCase(), langMap);
|
|
|
|
} else {
|
2020-07-05 23:35:51 +00:00
|
|
|
GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.locale.fail.missing", locale));
|
2020-04-08 20:11:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
/**
|
|
|
|
* Download then en_us locale by downloading the server jar and extracting it from there.
|
|
|
|
*
|
|
|
|
* @param localeFile File to save the locale to
|
|
|
|
*/
|
2020-04-09 13:36:32 +00:00
|
|
|
private static void downloadEN_US(File localeFile) {
|
|
|
|
try {
|
|
|
|
// Let the user know we are downloading the JAR
|
2020-07-05 23:35:51 +00:00
|
|
|
GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.download.en_us"));
|
2020-04-09 13:36:32 +00:00
|
|
|
GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL);
|
|
|
|
|
|
|
|
// Download the smallest JAR (client or server)
|
2020-06-20 17:54:40 +00:00
|
|
|
Path tmpFilePath = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("tmp_locale.jar");
|
|
|
|
WebUtils.downloadFile(smallestURL, tmpFilePath.toString());
|
2020-04-09 13:36:32 +00:00
|
|
|
|
|
|
|
// Load in the JAR as a zip and extract the file
|
2020-06-20 17:54:40 +00:00
|
|
|
ZipFile localeJar = new ZipFile(tmpFilePath.toString());
|
2020-06-27 05:58:52 +00:00
|
|
|
InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"));
|
|
|
|
FileOutputStream outStream = new FileOutputStream(localeFile);
|
2020-04-09 13:36:32 +00:00
|
|
|
|
|
|
|
// Write the file to the locale dir
|
2020-06-27 05:58:52 +00:00
|
|
|
byte[] buf = new byte[fileStream.available()];
|
|
|
|
int length;
|
|
|
|
while ((length = fileStream.read(buf)) != -1) {
|
|
|
|
outStream.write(buf, 0, length);
|
2020-04-09 13:36:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Flush all changes to disk and cleanup
|
2020-06-27 05:58:52 +00:00
|
|
|
outStream.flush();
|
|
|
|
outStream.close();
|
2020-04-09 13:36:32 +00:00
|
|
|
|
2020-06-27 05:58:52 +00:00
|
|
|
fileStream.close();
|
2020-04-09 13:36:32 +00:00
|
|
|
localeJar.close();
|
|
|
|
|
|
|
|
// Delete the nolonger needed client/server jar
|
2020-06-20 17:54:40 +00:00
|
|
|
Files.delete(tmpFilePath);
|
2020-04-09 13:36:32 +00:00
|
|
|
} catch (Exception e) {
|
2020-07-05 23:35:51 +00:00
|
|
|
throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.en_us"), e);
|
2020-04-09 13:36:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-21 05:28:44 +00:00
|
|
|
/**
|
|
|
|
* Translate the given language string into the given locale, or falls back to the default locale
|
|
|
|
*
|
|
|
|
* @param messageText Language string to translate
|
|
|
|
* @param locale Locale to translate to
|
|
|
|
* @return Translated string or the original message if it was not found in the given locale
|
|
|
|
*/
|
2020-04-08 20:11:56 +00:00
|
|
|
public static String getLocaleString(String messageText, String locale) {
|
|
|
|
Map<String, String> localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase());
|
|
|
|
if (localeStrings == null)
|
2020-07-05 23:35:51 +00:00
|
|
|
localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale());
|
2020-04-08 20:11:56 +00:00
|
|
|
|
|
|
|
return localeStrings.getOrDefault(messageText, messageText);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void init() {
|
|
|
|
// no-op
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
|
|
@Getter
|
|
|
|
class VersionManifest {
|
|
|
|
@JsonProperty("latest")
|
|
|
|
private LatestVersion latestVersion;
|
|
|
|
|
|
|
|
@JsonProperty("versions")
|
|
|
|
private List<Version> versions;
|
|
|
|
}
|
|
|
|
|
|
|
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
|
|
@Getter
|
|
|
|
class LatestVersion {
|
|
|
|
@JsonProperty("release")
|
|
|
|
private String release;
|
|
|
|
|
|
|
|
@JsonProperty("snapshot")
|
|
|
|
private String snapshot;
|
|
|
|
}
|
|
|
|
|
|
|
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
|
|
@Getter
|
|
|
|
class Version {
|
|
|
|
@JsonProperty("id")
|
|
|
|
private String id;
|
|
|
|
|
|
|
|
@JsonProperty("type")
|
|
|
|
private String type;
|
|
|
|
|
|
|
|
@JsonProperty("url")
|
|
|
|
private String url;
|
|
|
|
|
|
|
|
@JsonProperty("time")
|
|
|
|
private String time;
|
|
|
|
|
|
|
|
@JsonProperty("releaseTime")
|
|
|
|
private String releaseTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
|
|
@Getter
|
|
|
|
class VersionInfo {
|
|
|
|
@JsonProperty("id")
|
|
|
|
private String id;
|
|
|
|
|
|
|
|
@JsonProperty("type")
|
|
|
|
private String type;
|
|
|
|
|
|
|
|
@JsonProperty("time")
|
|
|
|
private String time;
|
|
|
|
|
|
|
|
@JsonProperty("releaseTime")
|
|
|
|
private String releaseTime;
|
|
|
|
|
|
|
|
@JsonProperty("assetIndex")
|
|
|
|
private AssetIndex assetIndex;
|
2020-04-09 13:36:32 +00:00
|
|
|
|
|
|
|
@JsonProperty("downloads")
|
|
|
|
private Map<String, VersionDownload> downloads;
|
|
|
|
}
|
|
|
|
|
|
|
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
|
|
@Getter
|
|
|
|
class VersionDownload {
|
|
|
|
@JsonProperty("sha1")
|
|
|
|
private String sha1;
|
|
|
|
|
|
|
|
@JsonProperty("size")
|
|
|
|
private int size;
|
|
|
|
|
|
|
|
@JsonProperty("url")
|
|
|
|
private String url;
|
2020-04-08 20:11:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
|
|
@Getter
|
|
|
|
class AssetIndex {
|
|
|
|
@JsonProperty("id")
|
|
|
|
private String id;
|
|
|
|
|
|
|
|
@JsonProperty("sha1")
|
|
|
|
private String sha1;
|
|
|
|
|
|
|
|
@JsonProperty("size")
|
|
|
|
private int size;
|
|
|
|
|
|
|
|
@JsonProperty("totalSize")
|
|
|
|
private int totalSize;
|
|
|
|
|
|
|
|
@JsonProperty("url")
|
|
|
|
private String url;
|
|
|
|
}
|
|
|
|
|
|
|
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
|
|
@Getter
|
|
|
|
class Asset {
|
|
|
|
@JsonProperty("hash")
|
|
|
|
private String hash;
|
|
|
|
|
|
|
|
@JsonProperty("size")
|
|
|
|
private int size;
|
2020-06-27 05:58:52 +00:00
|
|
|
}
|