mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-09 15:29:20 -04:00
minecraft assets per version
This commit is contained in:
parent
14904e969f
commit
1924e8d69b
@ -21,8 +21,8 @@ Minosoft is an open source minecraft client, written from scratch in java. It ai
|
||||
|
||||
- CPU: Minosoft benefits from multiple cores (at least for most stuff). CPU is kind of important, but for the rendering clock speed is more important. 4 cores recommended with at least 2 Ghz.
|
||||
- RAM: Our goal was it to run without compromises on an 8 Gib computer and at least limited on an 4 Gib one.
|
||||
- Disk space: Minosoft itself is pretty small (2-3 Mib), the libraries are a bit bigger (up to 100 MB). You also need to have the "normal" minecraft assets (~ 300 MB). So a total of 500 MB is recommended.
|
||||
- GPU: Currently only needed for rendering, no clue yet.
|
||||
- Disk space: Minosoft itself is pretty small (2-3 Mib), the libraries are a bit bigger. You also need to have the "normal" minecraft assets (~ 300 MB for one version). Assets never get saved duplicated, so if you use multiple versions, it is not a multiple of that value! If you only play 1 version, 500 MB is recommended. If not, use at least 1 GB.
|
||||
- GPU: Currently only needed for rendering, no clue yet.
|
||||
- Java 15 (This is really important, we use specific (experimental) features that are only available in the latest version. Java 8 is **not** supported).
|
||||
OpenJDK is (of course) also supported.
|
||||
|
||||
|
@ -25,8 +25,10 @@ If not, we will download it and store it gzip compressed.
|
||||
|
||||
## Valid (Relevant) files
|
||||
Before downloading a file, the file is checked for relevance. Relevant files are prefixed with the following strings (or the file path):
|
||||
- `minecraft/lang/` -> Language files
|
||||
- `minecraft/sounds/` -> Sounds
|
||||
|
||||
- `minecraft/lang/` -> Language files
|
||||
- `minecraft/sounds.json` -> Sound meta data and index file
|
||||
- `minecraft/sounds/` -> Sounds
|
||||
- `minecraft/textures/` -> Textures
|
||||
- `minecraft/font/` -> Fonts
|
||||
|
||||
|
@ -20,9 +20,8 @@ import de.bixilon.minosoft.config.Configuration;
|
||||
import de.bixilon.minosoft.config.ConfigurationPaths;
|
||||
import de.bixilon.minosoft.config.StaticConfiguration;
|
||||
import de.bixilon.minosoft.data.accounts.Account;
|
||||
import de.bixilon.minosoft.data.assets.AssetsManager;
|
||||
import de.bixilon.minosoft.data.assets.Resources;
|
||||
import de.bixilon.minosoft.data.locale.LocaleManager;
|
||||
import de.bixilon.minosoft.data.locale.minecraft.MinecraftLocaleManager;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Versions;
|
||||
import de.bixilon.minosoft.gui.main.GUITools;
|
||||
import de.bixilon.minosoft.gui.main.Launcher;
|
||||
@ -130,6 +129,8 @@ public final class Minosoft {
|
||||
long mappingStartLoadingTime = System.currentTimeMillis();
|
||||
Versions.loadAvailableVersions(Util.readJsonAsset("mapping/versions.json"));
|
||||
Log.info(String.format("Loaded %d versions in %dms", Versions.getVersionIdMap().size(), (System.currentTimeMillis() - mappingStartLoadingTime)));
|
||||
Resources.load();
|
||||
Log.info("Loaded all resources!");
|
||||
}, "Version mappings", "Load available minecraft versions inclusive mappings", Priorities.NORMAL, TaskImportance.REQUIRED, "Configuration"));
|
||||
|
||||
taskWorker.addTask(new Task(progress -> {
|
||||
@ -140,10 +141,6 @@ public final class Minosoft {
|
||||
|
||||
taskWorker.addTask(new Task(ModLoader::loadMods, "ModLoading", "Load all minosoft mods", Priorities.NORMAL, TaskImportance.REQUIRED, "Configuration"));
|
||||
|
||||
taskWorker.addTask(new Task(AssetsManager::downloadAllAssets, "Assets", "Download and verify all minecraft assets", Priorities.HIGH, TaskImportance.REQUIRED, "Configuration"));
|
||||
|
||||
taskWorker.addTask(new Task(progress -> MinecraftLocaleManager.load(config.getString(ConfigurationPaths.StringPaths.GENERAL_LANGUAGE)), "Mojang language", "Load minecraft language files", Priorities.HIGH, TaskImportance.REQUIRED, "Assets"));
|
||||
|
||||
taskWorker.addTask(new Task(progress -> {
|
||||
if (!config.getBoolean(ConfigurationPaths.BooleanPaths.NETWORK_SHOW_LAN_SERVERS)) {
|
||||
return;
|
||||
@ -151,7 +148,7 @@ public final class Minosoft {
|
||||
LANServerListener.listen();
|
||||
}, "LAN Server Listener", "Listener for LAN Servers", Priorities.LOWEST, TaskImportance.OPTIONAL, "Configuration"));
|
||||
|
||||
taskWorker.addTask(new Task(progress -> CLI.initialize(), "CLI", "Initialize CLI", Priorities.LOW, TaskImportance.OPTIONAL, "Assets", "Mojang language"));
|
||||
taskWorker.addTask(new Task(progress -> CLI.initialize(), "CLI", "Initialize CLI", Priorities.LOW, TaskImportance.OPTIONAL));
|
||||
|
||||
if (!StaticConfiguration.HEADLESS_MODE) {
|
||||
taskWorker.addTask(new Task((progress) -> StartProgressWindow.start(), "JavaFX Toolkit", "Initialize JavaFX", Priorities.HIGHEST));
|
||||
|
@ -18,6 +18,7 @@ public enum Mappings {
|
||||
BLOCKS,
|
||||
ENTITIES;
|
||||
|
||||
public static final Mappings[] VALUES = values();
|
||||
public String getFilename() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package de.bixilon.minosoft.data.assets
|
||||
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version
|
||||
|
||||
data class AssetVersion(
|
||||
val version: Version,
|
||||
val indexVersion: String?,
|
||||
val indexHash: String?,
|
||||
val clientJarHash: String?,
|
||||
val jarAssetsHash: String?,
|
||||
val minosoftMappings: String?
|
||||
)
|
@ -13,7 +13,6 @@
|
||||
|
||||
package de.bixilon.minosoft.data.assets;
|
||||
|
||||
import com.google.errorprone.annotations.DoNotCall;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
@ -23,10 +22,8 @@ import de.bixilon.minosoft.config.ConfigurationPaths;
|
||||
import de.bixilon.minosoft.config.StaticConfiguration;
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
import de.bixilon.minosoft.util.CountUpAndDownLatch;
|
||||
import de.bixilon.minosoft.util.HTTP;
|
||||
import de.bixilon.minosoft.util.Util;
|
||||
import de.bixilon.minosoft.util.logging.Log;
|
||||
import de.bixilon.minosoft.util.logging.LogLevels;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.MessageDigest;
|
||||
@ -38,114 +35,25 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class AssetsManager {
|
||||
public static final String ASSETS_INDEX_VERSION = "1.17"; // version.json -> assetIndex -> id
|
||||
public static final String ASSETS_INDEX_HASH = "e022240e3d70866f41dd88a3b342cf842a7b31bd"; // version.json -> assetIndex -> sha1
|
||||
public static final String ASSETS_CLIENT_JAR_VERSION = "20w51a"; // version.json -> id
|
||||
public static final String ASSETS_CLIENT_JAR_HASH = "1e4d125252481d833930371c9827d72d0fb35cfd"; // sha1 hash of file generated by minosoft (client jar file mappings: name -> hash)
|
||||
public static final String[] RELEVANT_ASSETS = {"minecraft/lang/", "minecraft/sounds/", "minecraft/textures/", "minecraft/font/"};
|
||||
private static final String[] RELEVANT_ASSETS = {"minecraft/lang/", "minecraft/sounds.json", "minecraft/sounds/", "minecraft/textures/", "minecraft/font/"}; // whitelist for all assets we care (we have our own block models, etc)
|
||||
private final boolean verifyHash;
|
||||
private final AssetVersion assetVersion;
|
||||
private final HashMap<String, String> assetsMap = new HashMap<>();
|
||||
|
||||
private static final HashMap<String, String> ASSETS_MAP = new HashMap<>();
|
||||
|
||||
public static void downloadAssetsIndex() throws IOException {
|
||||
Util.downloadFileAsGz(String.format("https://launchermeta.mojang.com/v1/packages/%s/%s.json", ASSETS_INDEX_HASH, ASSETS_INDEX_VERSION), getAssetDiskPath(ASSETS_INDEX_HASH));
|
||||
public AssetsManager(boolean verifyHash, AssetVersion assetVersion) {
|
||||
this.verifyHash = verifyHash;
|
||||
this.assetVersion = assetVersion;
|
||||
}
|
||||
|
||||
private static HashMap<String, String> parseAssetsIndex(String hash) throws IOException {
|
||||
return parseAssetsIndex(readJsonAssetByHash(hash).getAsJsonObject());
|
||||
}
|
||||
|
||||
private static HashMap<String, String> parseAssetsIndex(JsonObject json) {
|
||||
if (json.has("objects")) {
|
||||
json = json.getAsJsonObject("objects");
|
||||
}
|
||||
HashMap<String, String> ret = new HashMap<>();
|
||||
for (String key : json.keySet()) {
|
||||
JsonElement value = json.get(key);
|
||||
if (value.isJsonPrimitive()) {
|
||||
ret.put(key, value.getAsString());
|
||||
continue;
|
||||
}
|
||||
ret.put(key, value.getAsJsonObject().get("hash").getAsString());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void downloadAllAssets(CountUpAndDownLatch latch) throws IOException, InterruptedException {
|
||||
if (!ASSETS_MAP.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
downloadAssetsIndex();
|
||||
} catch (Exception e) {
|
||||
Log.printException(e, LogLevels.DEBUG);
|
||||
Log.warn("Could not download assets index. Please check your internet connection");
|
||||
}
|
||||
ASSETS_MAP.putAll(verifyAssets(AssetsSource.MOJANG, latch, parseAssetsIndex(ASSETS_INDEX_HASH)));
|
||||
ASSETS_MAP.putAll(verifyAssets(AssetsSource.MINOSOFT_GIT, latch, parseAssetsIndex(Util.readJsonAsset("mapping/resources.json"))));
|
||||
latch.addCount(1); // client jar
|
||||
// download assets
|
||||
generateJarAssets();
|
||||
ASSETS_MAP.putAll(parseAssetsIndex(ASSETS_CLIENT_JAR_HASH));
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
private static HashMap<String, String> verifyAssets(AssetsSource source, CountUpAndDownLatch latch, HashMap<String, String> assets) {
|
||||
latch.addCount(assets.size());
|
||||
assets.keySet().parallelStream().forEach((filename) -> {
|
||||
try {
|
||||
String hash = assets.get(filename);
|
||||
boolean compressed = (source == AssetsSource.MOJANG);
|
||||
if (StaticConfiguration.DEBUG_SLOW_LOADING) {
|
||||
Thread.sleep(100L);
|
||||
}
|
||||
if (!verifyAssetHash(hash, compressed)) {
|
||||
downloadAsset(source, hash);
|
||||
}
|
||||
latch.countDown();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
return assets;
|
||||
}
|
||||
|
||||
public static boolean doesAssetExist(String name) {
|
||||
return ASSETS_MAP.containsKey(name);
|
||||
}
|
||||
|
||||
public static HashMap<String, String> getAssetsMap() {
|
||||
return ASSETS_MAP;
|
||||
}
|
||||
|
||||
public static InputStreamReader readAsset(String name) throws IOException {
|
||||
return readAssetByHash(ASSETS_MAP.get(name));
|
||||
}
|
||||
|
||||
public static InputStream readAssetAsStream(String name) throws IOException {
|
||||
return readAssetAsStreamByHash(ASSETS_MAP.get(name));
|
||||
}
|
||||
|
||||
public static JsonElement readJsonAsset(String name) throws IOException {
|
||||
return readJsonAssetByHash(ASSETS_MAP.get(name));
|
||||
}
|
||||
|
||||
private static void downloadAsset(AssetsSource source, String hash) throws Exception {
|
||||
switch (source) {
|
||||
case MOJANG -> downloadAsset(String.format(ProtocolDefinition.MOJANG_URL_RESOURCES, hash.substring(0, 2), hash), hash);
|
||||
case MINOSOFT_GIT -> downloadAsset(String.format(Minosoft.getConfig().getString(ConfigurationPaths.StringPaths.RESOURCES_URL), hash.substring(0, 2), hash), hash, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static InputStreamReader readAssetByHash(String hash) throws IOException {
|
||||
public static InputStreamReader readAssetByHash(String hash) throws IOException {
|
||||
return new InputStreamReader(readAssetAsStreamByHash(hash));
|
||||
}
|
||||
|
||||
private static InputStream readAssetAsStreamByHash(String hash) throws IOException {
|
||||
public static InputStream readAssetAsStreamByHash(String hash) throws IOException {
|
||||
return new GZIPInputStream(new FileInputStream(getAssetDiskPath(hash)));
|
||||
}
|
||||
|
||||
private static JsonElement readJsonAssetByHash(String hash) throws IOException {
|
||||
public static JsonElement readJsonAssetByHash(String hash) throws IOException {
|
||||
return JsonParser.parseReader(readAssetByHash(hash));
|
||||
}
|
||||
|
||||
@ -157,89 +65,6 @@ public class AssetsManager {
|
||||
return file.length();
|
||||
}
|
||||
|
||||
private static boolean verifyAssetHash(String hash, boolean compressed) throws FileNotFoundException {
|
||||
// file does not exist
|
||||
if (getAssetSize(hash) == -1) {
|
||||
return false;
|
||||
}
|
||||
if (!Minosoft.getConfig().getBoolean(ConfigurationPaths.BooleanPaths.DEBUG_VERIFY_ASSETS)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
if (compressed) {
|
||||
return hash.equals(Util.sha1Gzip(new File(getAssetDiskPath(hash))));
|
||||
}
|
||||
return hash.equals(Util.sha1(new File(getAssetDiskPath(hash))));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean verifyAssetHash(String hash) {
|
||||
try {
|
||||
return verifyAssetHash(hash, true);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void generateJarAssets() throws IOException, InterruptedException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
Log.verbose("Generating client.jar assets...");
|
||||
if (verifyAssetHash(ASSETS_CLIENT_JAR_HASH)) {
|
||||
// ToDo: Verify all jar assets
|
||||
readAssetAsStreamByHash(ASSETS_CLIENT_JAR_HASH);
|
||||
Log.verbose("client.jar assets probably already generated, skipping");
|
||||
return;
|
||||
}
|
||||
JsonObject manifest = HTTP.getJson(ProtocolDefinition.MOJANG_URL_VERSION_MANIFEST).getAsJsonObject();
|
||||
String assetsVersionJsonUrl = null;
|
||||
for (JsonElement versionElement : manifest.getAsJsonArray("versions")) {
|
||||
JsonObject version = versionElement.getAsJsonObject();
|
||||
if (version.get("id").getAsString().equals(ASSETS_CLIENT_JAR_VERSION)) {
|
||||
assetsVersionJsonUrl = version.get("url").getAsString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (assetsVersionJsonUrl == null) {
|
||||
throw new RuntimeException(String.format("Invalid version manifest or invalid ASSETS_CLIENT_JAR_VERSION (%s)", ASSETS_CLIENT_JAR_VERSION));
|
||||
}
|
||||
String versionJsonHash = assetsVersionJsonUrl.replace(ProtocolDefinition.MOJANG_URL_PACKAGES, "").replace(String.format("/%s.json", ASSETS_CLIENT_JAR_VERSION), "");
|
||||
downloadAsset(assetsVersionJsonUrl, versionJsonHash);
|
||||
// download jar
|
||||
JsonObject clientJarJson = readJsonAssetByHash(versionJsonHash).getAsJsonObject().getAsJsonObject("downloads").getAsJsonObject("client");
|
||||
downloadAsset(clientJarJson.get("url").getAsString(), clientJarJson.get("sha1").getAsString());
|
||||
|
||||
HashMap<String, String> clientJarAssetsHashMap = new HashMap<>();
|
||||
ZipInputStream versionJar = new ZipInputStream(readAssetAsStreamByHash(clientJarJson.get("sha1").getAsString()));
|
||||
ZipEntry currentFile;
|
||||
while ((currentFile = versionJar.getNextEntry()) != null) {
|
||||
if (!currentFile.getName().startsWith("assets") || currentFile.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
boolean relevant = false;
|
||||
for (String prefix : RELEVANT_ASSETS) {
|
||||
if (currentFile.getName().startsWith("assets/" + prefix)) {
|
||||
relevant = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!relevant) {
|
||||
continue;
|
||||
}
|
||||
String hash = saveAsset(versionJar);
|
||||
|
||||
clientJarAssetsHashMap.put(currentFile.getName().substring("assets/".length()), hash);
|
||||
}
|
||||
JsonObject clientJarAssetsMapping = new JsonObject();
|
||||
clientJarAssetsHashMap.forEach(clientJarAssetsMapping::addProperty);
|
||||
String json = new GsonBuilder().create().toJson(clientJarAssetsMapping);
|
||||
String assetHash = saveAsset(json.getBytes());
|
||||
Log.verbose(String.format("Generated jar assets in %dms (elements=%d, hash=%s)", (System.currentTimeMillis() - startTime), clientJarAssetsHashMap.size(), assetHash));
|
||||
}
|
||||
|
||||
@DoNotCall
|
||||
private static String saveAsset(byte[] data) throws IOException {
|
||||
String hash = Util.sha1(data);
|
||||
String destination = getAssetDiskPath(hash);
|
||||
@ -254,6 +79,13 @@ public class AssetsManager {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads/Copies an asset from a given stream
|
||||
*
|
||||
* @param data: Data to save
|
||||
* @return SHA-1 hash of file
|
||||
* @throws IOException On error
|
||||
*/
|
||||
private static String saveAsset(InputStream data) throws IOException {
|
||||
File tempDestinationFile = null;
|
||||
while (tempDestinationFile == null || tempDestinationFile.exists()) { // file exist? lol
|
||||
@ -295,11 +127,45 @@ public class AssetsManager {
|
||||
return hash;
|
||||
}
|
||||
|
||||
private static void downloadAsset(String url, String hash) throws IOException {
|
||||
private static String getAssetDiskPath(String hash) throws FileNotFoundException {
|
||||
if (hash == null) {
|
||||
throw new FileNotFoundException("Could not find asset with hash: null");
|
||||
}
|
||||
return StaticConfiguration.HOME_DIRECTORY + String.format("assets/objects/%s/%s.gz", hash.substring(0, 2), hash);
|
||||
}
|
||||
|
||||
private boolean verifyAssetHash(String hash, boolean compressed) throws FileNotFoundException {
|
||||
// file does not exist
|
||||
if (getAssetSize(hash) == -1) {
|
||||
return false;
|
||||
}
|
||||
if (!this.verifyHash) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
if (compressed) {
|
||||
return hash.equals(Util.sha1Gzip(new File(getAssetDiskPath(hash))));
|
||||
}
|
||||
return hash.equals(Util.sha1(new File(getAssetDiskPath(hash))));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean verifyAssetHash(String hash) {
|
||||
try {
|
||||
return verifyAssetHash(hash, true);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void downloadAsset(String url, String hash) throws IOException {
|
||||
downloadAsset(url, hash, true);
|
||||
}
|
||||
|
||||
private static void downloadAsset(String url, String hash, boolean compressed) throws IOException {
|
||||
private void downloadAsset(String url, String hash, boolean compressed) throws IOException {
|
||||
if (verifyAssetHash(hash)) {
|
||||
return;
|
||||
}
|
||||
@ -312,10 +178,137 @@ public class AssetsManager {
|
||||
Util.downloadFile(url, getAssetDiskPath(hash));
|
||||
}
|
||||
|
||||
private static String getAssetDiskPath(String hash) throws FileNotFoundException {
|
||||
if (hash == null) {
|
||||
throw new FileNotFoundException("Could not find asset with hash: null");
|
||||
public void downloadAssetsIndex() throws IOException {
|
||||
Util.downloadFileAsGz(String.format(ProtocolDefinition.MOJANG_URL_PACKAGES + ".json", this.assetVersion.getIndexHash(), this.assetVersion.getIndexVersion()), getAssetDiskPath(this.assetVersion.getIndexHash()));
|
||||
}
|
||||
|
||||
private HashMap<String, String> parseAssetsIndex(String hash) throws IOException {
|
||||
return parseAssetsIndex(readJsonAssetByHash(hash).getAsJsonObject());
|
||||
}
|
||||
|
||||
private HashMap<String, String> parseAssetsIndex(JsonObject json) {
|
||||
if (json.has("objects")) {
|
||||
json = json.getAsJsonObject("objects");
|
||||
}
|
||||
return StaticConfiguration.HOME_DIRECTORY + String.format("assets/objects/%s/%s.gz", hash.substring(0, 2), hash);
|
||||
HashMap<String, String> ret = new HashMap<>();
|
||||
for (String key : json.keySet()) {
|
||||
JsonElement value = json.get(key);
|
||||
if (value.isJsonPrimitive()) {
|
||||
ret.put(key, value.getAsString());
|
||||
continue;
|
||||
}
|
||||
ret.put(key, value.getAsJsonObject().get("hash").getAsString());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void downloadAllAssets(CountUpAndDownLatch latch) throws Exception {
|
||||
if (!this.assetsMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// download minecraft assets
|
||||
if (!doesAssetExist(this.assetVersion.getIndexHash())) {
|
||||
downloadAssetsIndex();
|
||||
}
|
||||
this.assetsMap.putAll(verifyAssets(AssetsSource.MOJANG, latch, parseAssetsIndex(this.assetVersion.getIndexHash())));
|
||||
|
||||
// generate jar assets index
|
||||
generateJarAssets();
|
||||
|
||||
// download minosoft mappings
|
||||
|
||||
downloadAsset(AssetsSource.MINOSOFT_GIT, this.assetVersion.getMinosoftMappings());
|
||||
}
|
||||
|
||||
private HashMap<String, String> verifyAssets(AssetsSource source, CountUpAndDownLatch latch, HashMap<String, String> assets) {
|
||||
latch.addCount(assets.size());
|
||||
assets.keySet().parallelStream().forEach((filename) -> {
|
||||
try {
|
||||
String hash = assets.get(filename);
|
||||
boolean compressed = (source == AssetsSource.MOJANG);
|
||||
if (StaticConfiguration.DEBUG_SLOW_LOADING) {
|
||||
Thread.sleep(100L);
|
||||
}
|
||||
if (!verifyAssetHash(hash, compressed)) {
|
||||
downloadAsset(source, hash);
|
||||
}
|
||||
latch.countDown();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
return assets;
|
||||
}
|
||||
|
||||
private void downloadAsset(AssetsSource source, String hash) throws IOException {
|
||||
switch (source) {
|
||||
case MINECRAFT -> downloadAsset(String.format(ProtocolDefinition.MINECRAFT_URL_RESOURCES, hash.substring(0, 2), hash), hash);
|
||||
case MINOSOFT_GIT -> downloadAsset(String.format(Minosoft.getConfig().getString(ConfigurationPaths.StringPaths.RESOURCES_URL), hash.substring(0, 2), hash), hash, false);
|
||||
}
|
||||
}
|
||||
|
||||
public String generateJarAssets() throws IOException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
Log.verbose("Generating client.jar assets for %s...", this.assetVersion.getVersion());
|
||||
if (verifyAssetHash(this.assetVersion.getJarAssetsHash())) {
|
||||
// ToDo: Verify all jar assets
|
||||
Log.verbose("client.jar assets probably already generated for %s, skipping", this.assetVersion.getVersion());
|
||||
return this.assetVersion.getJarAssetsHash();
|
||||
}
|
||||
// download jar
|
||||
downloadAsset(String.format(ProtocolDefinition.MOJANG_LAUNCHER_URL_PACKAGES, this.assetVersion.getClientJarHash(), "client.jar"), this.assetVersion.getClientJarHash());
|
||||
|
||||
HashMap<String, String> clientJarAssetsHashMap = new HashMap<>();
|
||||
ZipInputStream versionJar = new ZipInputStream(readAssetAsStreamByHash(this.assetVersion.getClientJarHash()));
|
||||
ZipEntry currentFile;
|
||||
while ((currentFile = versionJar.getNextEntry()) != null) {
|
||||
if (!currentFile.getName().startsWith("assets") || currentFile.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
boolean relevant = false;
|
||||
for (String prefix : RELEVANT_ASSETS) {
|
||||
if (currentFile.getName().startsWith("assets/" + prefix)) {
|
||||
relevant = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!relevant) {
|
||||
continue;
|
||||
}
|
||||
String hash = saveAsset(versionJar);
|
||||
|
||||
clientJarAssetsHashMap.put(currentFile.getName().substring("assets/".length()), hash);
|
||||
}
|
||||
JsonObject clientJarAssetsMapping = new JsonObject();
|
||||
clientJarAssetsHashMap.forEach(clientJarAssetsMapping::addProperty);
|
||||
String json = new GsonBuilder().create().toJson(clientJarAssetsMapping);
|
||||
String assetHash = saveAsset(json.getBytes());
|
||||
Log.verbose(String.format("Generated jar assets in %dms (elements=%d, hash=%s)", (System.currentTimeMillis() - startTime), clientJarAssetsHashMap.size(), assetHash));
|
||||
return assetHash;
|
||||
}
|
||||
|
||||
public boolean doesAssetExist(String name) {
|
||||
return this.assetsMap.containsKey(name);
|
||||
}
|
||||
|
||||
public HashMap<String, String> getAssetsMap() {
|
||||
return this.assetsMap;
|
||||
}
|
||||
|
||||
public InputStreamReader readAsset(String name) throws IOException {
|
||||
return readAssetByHash(this.assetsMap.get(name));
|
||||
}
|
||||
|
||||
public InputStream readAssetAsStream(String name) throws IOException {
|
||||
return readAssetAsStreamByHash(this.assetsMap.get(name));
|
||||
}
|
||||
|
||||
public JsonElement readJsonAsset(String name) throws IOException {
|
||||
return readJsonAssetByHash(this.assetsMap.get(name));
|
||||
}
|
||||
|
||||
public AssetVersion getAssetVersion() {
|
||||
return this.assetVersion;
|
||||
}
|
||||
}
|
||||
|
@ -15,5 +15,6 @@ package de.bixilon.minosoft.data.assets;
|
||||
|
||||
public enum AssetsSource {
|
||||
MOJANG,
|
||||
MINECRAFT,
|
||||
MINOSOFT_GIT
|
||||
}
|
||||
|
44
src/main/java/de/bixilon/minosoft/data/assets/Resources.java
Normal file
44
src/main/java/de/bixilon/minosoft/data/assets/Resources.java
Normal file
@ -0,0 +1,44 @@
|
||||
package de.bixilon.minosoft.data.assets;
|
||||
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Versions;
|
||||
import de.bixilon.minosoft.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class Resources {
|
||||
private static final HashBiMap<Version, AssetVersion> ASSETS_VERSIONS = HashBiMap.create();
|
||||
|
||||
public static void load() throws IOException {
|
||||
JsonObject json = Util.readJsonAsset("mapping/resources.json");
|
||||
|
||||
JsonObject versions = json.getAsJsonObject("versions");
|
||||
for (Map.Entry<String, JsonElement> versionEntry : versions.entrySet()) {
|
||||
loadVersion(versionEntry.getKey(), versionEntry.getValue().getAsJsonObject());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void loadVersion(String versionName, JsonObject json) {
|
||||
loadVersion(Versions.getVersionByName(versionName), json);
|
||||
}
|
||||
|
||||
public static void loadVersion(Version version, JsonObject json) {
|
||||
String indexVersion = json.has("index_version") ? json.get("index_version").getAsString() : null;
|
||||
String indexHash = json.has("index_hash") ? json.get("index_hash").getAsString() : null;
|
||||
String clientJarHash = json.has("client_jar_hash") ? json.get("client_jar_hash").getAsString() : null;
|
||||
String jarAssetsHash = json.has("jar_assets_hash") ? json.get("jar_assets_hash").getAsString() : null;
|
||||
String minosoftMapping = json.has("mappings") ? json.get("mappings").getAsString() : null;
|
||||
|
||||
AssetVersion assetVersion = new AssetVersion(version, indexVersion, indexHash, clientJarHash, jarAssetsHash, minosoftMapping);
|
||||
ASSETS_VERSIONS.put(version, assetVersion);
|
||||
}
|
||||
|
||||
public static AssetVersion getAssetVersionByVersion(Version version) {
|
||||
return ASSETS_VERSIONS.get(version);
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ class ComponentParser : CommandParser() {
|
||||
@Throws(CommandParseException::class)
|
||||
override fun parse(connection: Connection, properties: ParserProperties?, stringReader: CommandStringReader): Any? {
|
||||
try {
|
||||
return BaseComponent(stringReader.readJson().asJsonObject)
|
||||
return BaseComponent(connection.version, stringReader.readJson().asJsonObject)
|
||||
} catch (exception: Exception) {
|
||||
stringReader.skip(-1)
|
||||
throw InvalidComponentCommandParseException(stringReader, stringReader.read().toString(), exception)
|
||||
|
@ -40,7 +40,7 @@ class ItemStackParser : CommandParser() {
|
||||
if (stringReader.peek() == '{') {
|
||||
nbt = stringReader.readNBTCompoundTag()
|
||||
}
|
||||
return Slot(connection.mapping, Item(argument.value.mod, argument.value.identifier), 1, nbt)
|
||||
return Slot(connection.version, Item(argument.value.mod, argument.value.identifier), 1, nbt)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -28,7 +28,7 @@ public class CampfireBlockEntityMetaData extends BlockEntityMetaData {
|
||||
public CampfireBlockEntityMetaData(ListTag nbt) {
|
||||
this.items = new Slot[4];
|
||||
for (CompoundTag tag : nbt.<CompoundTag>getValue()) {
|
||||
this.items[tag.getByteTag("Slot").getValue()] = new Slot(new Item(tag.getStringTag("id").getValue()), tag.getByteTag("Count").getValue());
|
||||
this.items[tag.getByteTag("Slot").getValue()] = new Slot(null, new Item(tag.getStringTag("id").getValue()), tag.getByteTag("Count").getValue()); // ToDo: version should not be null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import java.util.UUID;
|
||||
|
||||
public class LargeFireball extends Fireball {
|
||||
private static final Slot DEFAULT_ITEM = new Slot(new Item("fire_charge"));
|
||||
private static final Slot DEFAULT_ITEM = new Slot(null, new Item("fire_charge")); // ToDo: version should not be null
|
||||
|
||||
public LargeFireball(Connection connection, int entityId, UUID uuid, Location location, EntityRotation rotation) {
|
||||
super(connection, entityId, uuid, location, rotation);
|
||||
|
@ -22,7 +22,7 @@ import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SmallFireball extends Fireball {
|
||||
private static final Slot DEFAULT_ITEM = new Slot(new Item("fire_charge"));
|
||||
private static final Slot DEFAULT_ITEM = new Slot(null, new Item("fire_charge")); // ToDo: version should not be null
|
||||
|
||||
public SmallFireball(Connection connection, int entityId, UUID uuid, Location location, EntityRotation rotation) {
|
||||
super(connection, entityId, uuid, location, rotation);
|
||||
|
@ -22,7 +22,7 @@ import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ThrownEgg extends ThrowableItemProjectile {
|
||||
private static final Slot DEFAULT_ITEM = new Slot(new Item("egg"));
|
||||
private static final Slot DEFAULT_ITEM = new Slot(null, new Item("egg")); // ToDo: version should not be null
|
||||
|
||||
public ThrownEgg(Connection connection, int entityId, UUID uuid, Location location, EntityRotation rotation) {
|
||||
super(connection, entityId, uuid, location, rotation);
|
||||
|
@ -22,7 +22,7 @@ import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ThrownEnderPearl extends ThrowableItemProjectile {
|
||||
private static final Slot DEFAULT_ITEM = new Slot(new Item("ender_pearl"));
|
||||
private static final Slot DEFAULT_ITEM = new Slot(null, new Item("ender_pearl")); // ToDo: version should not be null
|
||||
|
||||
public ThrownEnderPearl(Connection connection, int entityId, UUID uuid, Location location, EntityRotation rotation) {
|
||||
super(connection, entityId, uuid, location, rotation);
|
||||
|
@ -22,7 +22,7 @@ import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ThrownExperienceBottle extends ThrowableItemProjectile {
|
||||
private static final Slot DEFAULT_ITEM = new Slot(new Item("experience_bottle"));
|
||||
private static final Slot DEFAULT_ITEM = new Slot(null, new Item("experience_bottle")); // ToDo: version should not be null
|
||||
|
||||
public ThrownExperienceBottle(Connection connection, int entityId, UUID uuid, Location location, EntityRotation rotation) {
|
||||
super(connection, entityId, uuid, location, rotation);
|
||||
|
@ -25,7 +25,7 @@ import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ThrownEyeOfEnder extends Entity {
|
||||
private static final Slot DEFAULT_ITEM = new Slot(new Item("ender_eye"));
|
||||
private static final Slot DEFAULT_ITEM = new Slot(null, new Item("ender_eye")); // ToDo: version should not be null
|
||||
|
||||
public ThrownEyeOfEnder(Connection connection, int entityId, UUID uuid, Location location, EntityRotation rotation) {
|
||||
super(connection, entityId, uuid, location, rotation);
|
||||
|
@ -22,7 +22,7 @@ import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ThrownSnowball extends ThrowableItemProjectile {
|
||||
private static final Slot DEFAULT_ITEM = new Slot(new Item("snowball"));
|
||||
private static final Slot DEFAULT_ITEM = new Slot(null, new Item("snowball")); // ToDo: version should not be null
|
||||
|
||||
public ThrownSnowball(Connection connection, int entityId, UUID uuid, Location location, EntityRotation rotation) {
|
||||
super(connection, entityId, uuid, location, rotation);
|
||||
|
@ -13,9 +13,9 @@
|
||||
|
||||
package de.bixilon.minosoft.data.inventory;
|
||||
|
||||
import de.bixilon.minosoft.data.locale.minecraft.MinecraftLocaleManager;
|
||||
import de.bixilon.minosoft.data.mappings.Enchantment;
|
||||
import de.bixilon.minosoft.data.mappings.Item;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version;
|
||||
import de.bixilon.minosoft.data.mappings.versions.VersionMapping;
|
||||
import de.bixilon.minosoft.data.text.ChatComponent;
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
@ -31,6 +31,7 @@ public class Slot {
|
||||
private final Item item;
|
||||
private final HashMap<Enchantment, Integer> enchantments = new HashMap<>();
|
||||
private final ArrayList<ChatComponent> lore = new ArrayList<>();
|
||||
private final Version version;
|
||||
int itemCount;
|
||||
short itemMetadata;
|
||||
int repairCost;
|
||||
@ -41,20 +42,21 @@ public class Slot {
|
||||
byte hideFlags;
|
||||
|
||||
|
||||
public Slot(VersionMapping mapping, Item item, int itemCount, CompoundTag nbt) {
|
||||
this(item);
|
||||
public Slot(Version version, Item item, int itemCount, CompoundTag nbt) {
|
||||
this(version, item);
|
||||
this.itemCount = itemCount;
|
||||
setNBT(mapping, nbt);
|
||||
setNBT(nbt);
|
||||
}
|
||||
|
||||
public Slot(VersionMapping mapping, Item item, byte itemCount, short itemMetadata, CompoundTag nbt) {
|
||||
this(item);
|
||||
public Slot(Version version, Item item, byte itemCount, short itemMetadata, CompoundTag nbt) {
|
||||
this(version, item);
|
||||
this.itemMetadata = itemMetadata;
|
||||
this.itemCount = itemCount;
|
||||
setNBT(mapping, nbt);
|
||||
setNBT(nbt);
|
||||
}
|
||||
|
||||
public Slot(Item item) {
|
||||
public Slot(Version version, Item item) {
|
||||
this.version = version;
|
||||
if (item.getFullIdentifier().equals("minecraft:air")) {
|
||||
this.item = null;
|
||||
} else {
|
||||
@ -62,12 +64,12 @@ public class Slot {
|
||||
}
|
||||
}
|
||||
|
||||
public Slot(Item item, byte itemCount) {
|
||||
this(item);
|
||||
public Slot(Version version, Item item, byte itemCount) {
|
||||
this(version, item);
|
||||
this.itemCount = itemCount;
|
||||
}
|
||||
|
||||
private void setNBT(VersionMapping mapping, CompoundTag nbt) {
|
||||
private void setNBT(CompoundTag nbt) {
|
||||
if (nbt == null) {
|
||||
return;
|
||||
}
|
||||
@ -77,11 +79,11 @@ public class Slot {
|
||||
if (nbt.containsKey("display")) {
|
||||
CompoundTag display = nbt.getCompoundTag("display");
|
||||
if (display.containsKey("Name")) {
|
||||
this.customDisplayName = ChatComponent.valueOf(display.getStringTag("Name").getValue());
|
||||
this.customDisplayName = ChatComponent.valueOf(this.version, display.getStringTag("Name").getValue());
|
||||
}
|
||||
if (display.containsKey("Lore")) {
|
||||
for (StringTag lore : display.getListTag("Lore").<StringTag>getValue()) {
|
||||
this.lore.add(ChatComponent.valueOf(lore.getValue()));
|
||||
this.lore.add(ChatComponent.valueOf(this.version, lore.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -101,7 +103,7 @@ public class Slot {
|
||||
}
|
||||
} else if (nbt.containsKey("ench")) {
|
||||
for (CompoundTag enchantment : nbt.getListTag("ench").<CompoundTag>getValue()) {
|
||||
this.enchantments.put(mapping.getEnchantmentById(enchantment.getNumberTag("id").getAsInt()), enchantment.getNumberTag("lvl").getAsInt());
|
||||
this.enchantments.put(this.version.getMapping().getEnchantmentById(enchantment.getNumberTag("id").getAsInt()), enchantment.getNumberTag("lvl").getAsInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -244,8 +246,8 @@ public class Slot {
|
||||
// ToDo: What if an item identifier changed between versions? oOo
|
||||
String[] keys = {String.format("item.%s.%s", this.item.getMod(), this.item.getIdentifier()), String.format("block.%s.%s", this.item.getMod(), this.item.getIdentifier())};
|
||||
for (String key : keys) {
|
||||
if (MinecraftLocaleManager.getLanguage().canTranslate(key)) {
|
||||
return MinecraftLocaleManager.translate(key);
|
||||
if (this.version.getLocaleManager().canTranslate(key)) {
|
||||
return this.version.getLocaleManager().translate(key);
|
||||
}
|
||||
}
|
||||
return this.item.getFullIdentifier();
|
||||
|
@ -13,35 +13,44 @@
|
||||
|
||||
package de.bixilon.minosoft.data.locale.minecraft;
|
||||
|
||||
import de.bixilon.minosoft.data.assets.AssetsManager;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version;
|
||||
import de.bixilon.minosoft.util.logging.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MinecraftLocaleManager {
|
||||
private static MinecraftLanguage language;
|
||||
private final Version version;
|
||||
private MinecraftLanguage language;
|
||||
|
||||
public static MinecraftLanguage getLanguage() {
|
||||
return language;
|
||||
public MinecraftLocaleManager(Version version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public static String translate(String key, Object... data) {
|
||||
return language.translate(key, data);
|
||||
public MinecraftLanguage getLanguage() {
|
||||
return this.language;
|
||||
}
|
||||
|
||||
private static MinecraftLanguage loadLanguage(String language) throws IOException {
|
||||
return new MinecraftLanguage(language, AssetsManager.readJsonAsset(String.format("minecraft/lang/%s.json", language.toLowerCase())).getAsJsonObject());
|
||||
public String translate(String key, Object... data) {
|
||||
return this.language.translate(key, data);
|
||||
}
|
||||
|
||||
public static void load(String language) {
|
||||
private MinecraftLanguage loadLanguage(String language) throws IOException {
|
||||
return new MinecraftLanguage(language, this.version.getAssetsManager().readJsonAsset(String.format("minecraft/lang/%s.json", language.toLowerCase())).getAsJsonObject());
|
||||
}
|
||||
|
||||
public void load(String language) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
Log.verbose(String.format("Loading minecraft language file (%s)", language));
|
||||
Log.verbose(String.format("Loading minecraft language file (%s) for %s", language, this.version));
|
||||
try {
|
||||
MinecraftLocaleManager.language = loadLanguage(language);
|
||||
this.language = loadLanguage(language);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.warn(String.format("Could not load minecraft language file: %s", language));
|
||||
Log.warn("Could not load minecraft language file: %s for %s", language, this.version);
|
||||
}
|
||||
Log.verbose(String.format("Loaded minecraft language files successfully in %dms", (System.currentTimeMillis() - startTime)));
|
||||
Log.verbose("Loaded minecraft language files for %s successfully in %dms", this.version, (System.currentTimeMillis() - startTime));
|
||||
}
|
||||
|
||||
public boolean canTranslate(String key) {
|
||||
return getLanguage().canTranslate(key);
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,23 @@
|
||||
package de.bixilon.minosoft.data.mappings.versions;
|
||||
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.gson.JsonObject;
|
||||
import de.bixilon.minosoft.Minosoft;
|
||||
import de.bixilon.minosoft.config.ConfigurationPaths;
|
||||
import de.bixilon.minosoft.data.Mappings;
|
||||
import de.bixilon.minosoft.data.assets.AssetsManager;
|
||||
import de.bixilon.minosoft.data.assets.Resources;
|
||||
import de.bixilon.minosoft.data.locale.minecraft.MinecraftLocaleManager;
|
||||
import de.bixilon.minosoft.protocol.protocol.ConnectionStates;
|
||||
import de.bixilon.minosoft.protocol.protocol.Packets;
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
import de.bixilon.minosoft.util.CountUpAndDownLatch;
|
||||
import de.bixilon.minosoft.util.Util;
|
||||
import de.bixilon.minosoft.util.logging.Log;
|
||||
import de.bixilon.minosoft.util.logging.LogLevels;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class Version {
|
||||
@ -28,6 +41,8 @@ public class Version {
|
||||
String versionName;
|
||||
VersionMapping mapping;
|
||||
boolean isGettingLoaded;
|
||||
private AssetsManager assetsManager;
|
||||
private MinecraftLocaleManager localeManager;
|
||||
|
||||
public Version(String versionName, int versionId, int protocolId, HashMap<ConnectionStates, HashBiMap<Packets.Serverbound, Integer>> serverboundPacketMapping, HashMap<ConnectionStates, HashBiMap<Packets.Clientbound, Integer>> clientboundPacketMapping) {
|
||||
this.versionName = versionName;
|
||||
@ -122,4 +137,92 @@ public class Version {
|
||||
public boolean isLoaded() {
|
||||
return getMapping() != null && getMapping().isFullyLoaded();
|
||||
}
|
||||
|
||||
public AssetsManager getAssetsManager() {
|
||||
return this.assetsManager;
|
||||
}
|
||||
|
||||
public void setAssetsManager(AssetsManager assetsManager) {
|
||||
this.assetsManager = assetsManager;
|
||||
}
|
||||
|
||||
public MinecraftLocaleManager getLocaleManager() {
|
||||
return this.localeManager;
|
||||
}
|
||||
|
||||
public void initializeAssetManger(CountUpAndDownLatch latch) throws Exception {
|
||||
if (this.assetsManager == null) {
|
||||
this.assetsManager = new AssetsManager(Minosoft.getConfig().getBoolean(ConfigurationPaths.BooleanPaths.DEBUG_VERIFY_ASSETS), Resources.getAssetVersionByVersion(this));
|
||||
this.assetsManager.downloadAllAssets(latch);
|
||||
this.localeManager = new MinecraftLocaleManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadMappings(CountUpAndDownLatch latch) throws IOException {
|
||||
if (isLoaded()) {
|
||||
// already loaded
|
||||
return;
|
||||
}
|
||||
Version preFlatteningVersion = Versions.getVersionById(ProtocolDefinition.PRE_FLATTENING_VERSION_ID);
|
||||
if (!isFlattened() && this != preFlatteningVersion && !preFlatteningVersion.isLoaded()) {
|
||||
// no matter what, we need the version mapping for all pre flattening versions
|
||||
preFlatteningVersion.loadMappings(latch);
|
||||
}
|
||||
if (isGettingLoaded()) {
|
||||
// async: we don't wanna load this version twice, skip
|
||||
return;
|
||||
}
|
||||
latch.countUp();
|
||||
this.isGettingLoaded = true;
|
||||
Log.verbose(String.format("Loading mappings for version %s...", this));
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
HashMap<String, JsonObject> files;
|
||||
try {
|
||||
files = Util.readJsonTarStream(AssetsManager.readAssetAsStreamByHash(this.assetsManager.getAssetVersion().getMinosoftMappings()));
|
||||
} catch (Exception e) {
|
||||
// should not happen, but if this version is not flattened, we can fallback to the flatten mappings. Some things might not work...
|
||||
Log.printException(e, LogLevels.VERBOSE);
|
||||
if (isFlattened() || getVersionId() == ProtocolDefinition.FLATTING_VERSION_ID) {
|
||||
throw e;
|
||||
}
|
||||
files = new HashMap<>();
|
||||
}
|
||||
|
||||
latch.addCount(Mappings.VALUES.length);
|
||||
for (Mappings mapping : Mappings.VALUES) {
|
||||
JsonObject data = null;
|
||||
if (files.containsKey(mapping.getFilename() + ".json")) {
|
||||
data = files.get(mapping.getFilename() + ".json");
|
||||
}
|
||||
if (data == null) {
|
||||
loadVersionMappings(mapping, ProtocolDefinition.DEFAULT_MOD, null);
|
||||
latch.countDown();
|
||||
continue;
|
||||
}
|
||||
for (String mod : data.keySet()) {
|
||||
loadVersionMappings(mapping, mod, data.getAsJsonObject(mod));
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
if (!files.isEmpty()) {
|
||||
Log.verbose(String.format("Loaded mappings for version %s in %dms (%s)", this, (System.currentTimeMillis() - startTime), getVersionName()));
|
||||
} else {
|
||||
Log.verbose(String.format("Could not load mappings for version %s. Some features will be unavailable.", this));
|
||||
}
|
||||
this.isGettingLoaded = false;
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void loadVersionMappings(Mappings type, String mod, @Nullable JsonObject data) {
|
||||
if (this.mapping == null) {
|
||||
this.mapping = new VersionMapping(this);
|
||||
}
|
||||
this.mapping.load(type, mod, data, this);
|
||||
|
||||
if (getVersionId() == ProtocolDefinition.PRE_FLATTENING_VERSION_ID && Versions.PRE_FLATTENING_MAPPING == null) {
|
||||
Versions.PRE_FLATTENING_MAPPING = this.mapping;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,26 +16,16 @@ package de.bixilon.minosoft.data.mappings.versions;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import de.bixilon.minosoft.data.Mappings;
|
||||
import de.bixilon.minosoft.data.assets.AssetsManager;
|
||||
import de.bixilon.minosoft.protocol.protocol.ConnectionStates;
|
||||
import de.bixilon.minosoft.protocol.protocol.Packets;
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
import de.bixilon.minosoft.util.Util;
|
||||
import de.bixilon.minosoft.util.logging.Log;
|
||||
import de.bixilon.minosoft.util.logging.LogLevels;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class Versions {
|
||||
public static final Version LOWEST_VERSION_SUPPORTED = new Version("Automatic", -1, -1, null, null);
|
||||
private static final HashBiMap<Integer, Version> VERSION_ID_MAP = HashBiMap.create(500);
|
||||
private static final HashBiMap<Integer, Version> VERSION_PROTOCOL_ID_MAP = HashBiMap.create(500);
|
||||
private static final HashBiMap<String, Version> VERSION_NAME_MAP = HashBiMap.create(500);
|
||||
private static final HashSet<Version> LOADED_VERSIONS = new HashSet<>();
|
||||
public static VersionMapping PRE_FLATTENING_MAPPING;
|
||||
|
||||
public static Version getVersionById(int versionId) {
|
||||
@ -106,72 +96,6 @@ public class Versions {
|
||||
return version;
|
||||
}
|
||||
|
||||
public static void loadVersionMappings(Version version) throws IOException {
|
||||
if (version.isLoaded()) {
|
||||
// already loaded
|
||||
return;
|
||||
}
|
||||
Version preFlatteningVersion = VERSION_ID_MAP.get(ProtocolDefinition.PRE_FLATTENING_VERSION_ID);
|
||||
if (!version.isFlattened() && version != preFlatteningVersion && !preFlatteningVersion.isLoaded()) {
|
||||
// no matter what, we need the version mapping for all pre flattening versions
|
||||
loadVersionMappings(preFlatteningVersion);
|
||||
}
|
||||
if (version.isGettingLoaded()) {
|
||||
// async: we don't wanna load this version twice, skip
|
||||
return;
|
||||
}
|
||||
version.setGettingLoaded(true);
|
||||
Log.verbose(String.format("Loading mappings for version %s...", version));
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
HashMap<String, JsonObject> files;
|
||||
try {
|
||||
files = Util.readJsonTarStream(AssetsManager.readAssetAsStream(String.format("mappings/%s", version.getVersionName())));
|
||||
} catch (Exception e) {
|
||||
// should not happen, but if this version is not flattened, we can fallback to the flatten mappings. Some things might not work...
|
||||
Log.printException(e, LogLevels.VERBOSE);
|
||||
if (version.isFlattened() || version.getVersionId() == ProtocolDefinition.FLATTING_VERSION_ID) {
|
||||
throw e;
|
||||
}
|
||||
files = new HashMap<>();
|
||||
}
|
||||
|
||||
for (Mappings mapping : Mappings.values()) {
|
||||
JsonObject data = null;
|
||||
if (files.containsKey(mapping.getFilename() + ".json")) {
|
||||
data = files.get(mapping.getFilename() + ".json");
|
||||
}
|
||||
if (data == null) {
|
||||
loadVersionMappings(mapping, ProtocolDefinition.DEFAULT_MOD, null, version);
|
||||
continue;
|
||||
}
|
||||
for (String mod : data.keySet()) {
|
||||
loadVersionMappings(mapping, mod, data.getAsJsonObject(mod), version);
|
||||
}
|
||||
}
|
||||
if (!files.isEmpty()) {
|
||||
Log.verbose(String.format("Loaded mappings for version %s in %dms (%s)", version, (System.currentTimeMillis() - startTime), version.getVersionName()));
|
||||
} else {
|
||||
Log.verbose(String.format("Could not load mappings for version %s. Some features will be unavailable.", version));
|
||||
}
|
||||
version.setGettingLoaded(false);
|
||||
}
|
||||
|
||||
public static void loadVersionMappings(Mappings type, String mod, @Nullable JsonObject data, Version version) {
|
||||
VersionMapping mapping;
|
||||
mapping = version.getMapping();
|
||||
if (mapping == null) {
|
||||
mapping = new VersionMapping(version);
|
||||
version.setMapping(mapping);
|
||||
}
|
||||
mapping.load(type, mod, data, version);
|
||||
|
||||
if (version.getVersionId() == ProtocolDefinition.PRE_FLATTENING_VERSION_ID && PRE_FLATTENING_MAPPING == null) {
|
||||
PRE_FLATTENING_MAPPING = mapping;
|
||||
}
|
||||
LOADED_VERSIONS.add(version);
|
||||
}
|
||||
|
||||
public static HashBiMap<Integer, Version> getVersionIdMap() {
|
||||
return VERSION_ID_MAP;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version;
|
||||
import de.bixilon.minosoft.modding.event.events.annotations.Unsafe;
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
import de.bixilon.minosoft.util.hash.BetterHashSet;
|
||||
@ -35,11 +36,11 @@ public class BaseComponent extends ChatComponent {
|
||||
public BaseComponent() {
|
||||
}
|
||||
|
||||
public BaseComponent(String text) {
|
||||
this(null, text);
|
||||
public BaseComponent(Version version, String text) {
|
||||
this(version, null, text);
|
||||
}
|
||||
|
||||
public BaseComponent(@Nullable ChatComponent parent, String text) {
|
||||
public BaseComponent(Version version, @Nullable ChatComponent parent, String text) {
|
||||
// legacy String
|
||||
StringBuilder currentText = new StringBuilder();
|
||||
RGBColor color = null;
|
||||
@ -92,19 +93,19 @@ public class BaseComponent extends ChatComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public BaseComponent(JsonObject json) {
|
||||
this(null, json);
|
||||
public BaseComponent(Version version, JsonObject json) {
|
||||
this(version, null, json);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public BaseComponent(@Nullable TextComponent parent, JsonElement data) {
|
||||
public BaseComponent(Version version, @Nullable TextComponent parent, JsonElement data) {
|
||||
MultiChatComponent thisTextComponent = null;
|
||||
if (data instanceof JsonObject json) {
|
||||
if (json.has("text")) {
|
||||
String text = json.get("text").getAsString();
|
||||
if (text.contains(String.valueOf(ProtocolDefinition.TEXT_COMPONENT_SPECIAL_PREFIX_CHAR))) {
|
||||
// legacy text component
|
||||
this.parts.add(new BaseComponent(text));
|
||||
this.parts.add(new BaseComponent(version, text));
|
||||
return;
|
||||
}
|
||||
RGBColor color;
|
||||
@ -160,14 +161,14 @@ public class BaseComponent extends ChatComponent {
|
||||
final TextComponent parentParameter = thisTextComponent == null ? parent : thisTextComponent;
|
||||
if (json.has("extra")) {
|
||||
JsonArray extras = json.getAsJsonArray("extra");
|
||||
extras.forEach((extra -> this.parts.add(new BaseComponent(parentParameter, extra))));
|
||||
extras.forEach((extra -> this.parts.add(new BaseComponent(version, parentParameter, extra))));
|
||||
}
|
||||
|
||||
if (json.has("translate")) {
|
||||
this.parts.add(new TranslatableComponent(parentParameter, json.get("translate").getAsString(), json.getAsJsonArray("with")));
|
||||
this.parts.add(new TranslatableComponent(version, parentParameter, json.get("translate").getAsString(), json.getAsJsonArray("with")));
|
||||
}
|
||||
} else if (data instanceof JsonPrimitive primitive) {
|
||||
this.parts.add(new BaseComponent(parent, primitive.getAsString()));
|
||||
this.parts.add(new BaseComponent(version, parent, primitive.getAsString()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,8 +218,8 @@ public class BaseComponent extends ChatComponent {
|
||||
return this;
|
||||
}
|
||||
|
||||
public BaseComponent append(String message) {
|
||||
this.parts.add(new BaseComponent(message));
|
||||
public BaseComponent append(Version version, String message) {
|
||||
this.parts.add(new BaseComponent(version, message));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
@ -25,10 +26,14 @@ import javax.annotation.Nullable;
|
||||
|
||||
public abstract class ChatComponent {
|
||||
public static ChatComponent valueOf(Object raw) {
|
||||
return valueOf(null, raw);
|
||||
return valueOf(null, null, raw);
|
||||
}
|
||||
|
||||
public static ChatComponent valueOf(@Nullable TextComponent parent, Object raw) {
|
||||
public static ChatComponent valueOf(Version version, Object raw) {
|
||||
return valueOf(version, null, raw);
|
||||
}
|
||||
|
||||
public static ChatComponent valueOf(Version version, @Nullable TextComponent parent, Object raw) {
|
||||
if (raw == null) {
|
||||
return new BaseComponent();
|
||||
}
|
||||
@ -43,13 +48,13 @@ public abstract class ChatComponent {
|
||||
try {
|
||||
json = JsonParser.parseString((String) raw).getAsJsonObject();
|
||||
} catch (JsonParseException | IllegalStateException ignored) {
|
||||
return new BaseComponent((String) raw);
|
||||
return new BaseComponent(version, (String) raw);
|
||||
}
|
||||
} else {
|
||||
return new BaseComponent(parent, raw.toString());
|
||||
return new BaseComponent(version, parent, raw.toString());
|
||||
// throw new IllegalArgumentException(String.format("%s is not a valid type here!", raw.getClass().getSimpleName()));
|
||||
}
|
||||
return new BaseComponent(parent, json);
|
||||
return new BaseComponent(version, parent, json);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,7 +14,7 @@
|
||||
package de.bixilon.minosoft.data.text;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import de.bixilon.minosoft.data.locale.minecraft.MinecraftLocaleManager;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version;
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
@ -27,12 +27,14 @@ public class TranslatableComponent extends ChatComponent {
|
||||
private final ArrayList<ChatComponent> data = new ArrayList<>();
|
||||
private final String key;
|
||||
private final TextComponent parent;
|
||||
private final Version version;
|
||||
|
||||
public TranslatableComponent(String key, JsonArray data) {
|
||||
this(null, key, data);
|
||||
public TranslatableComponent(Version version, String key, JsonArray data) {
|
||||
this(version, null, key, data);
|
||||
}
|
||||
|
||||
public TranslatableComponent(@Nullable TextComponent parent, String key, JsonArray data) {
|
||||
public TranslatableComponent(Version version, @Nullable TextComponent parent, String key, JsonArray data) {
|
||||
this.version = version;
|
||||
this.parent = parent;
|
||||
this.key = key;
|
||||
if (data == null) {
|
||||
@ -40,9 +42,9 @@ public class TranslatableComponent extends ChatComponent {
|
||||
}
|
||||
data.forEach((jsonElement -> {
|
||||
if (jsonElement.isJsonPrimitive()) {
|
||||
this.data.add(ChatComponent.valueOf(parent, jsonElement.getAsString()));
|
||||
this.data.add(ChatComponent.valueOf(version, parent, jsonElement.getAsString()));
|
||||
} else {
|
||||
this.data.add(new BaseComponent(parent, jsonElement.getAsJsonObject()));
|
||||
this.data.add(new BaseComponent(version, parent, jsonElement.getAsJsonObject()));
|
||||
}
|
||||
}));
|
||||
}
|
||||
@ -67,7 +69,7 @@ public class TranslatableComponent extends ChatComponent {
|
||||
// ToDo fix nested base component (formatting), not just a string
|
||||
|
||||
// This is just a dirty workaround to enable formatting and coloring. Still need to do hover, click, ... stuff
|
||||
return new BaseComponent(getLegacyText()).getJavaFXText(nodes);
|
||||
return new BaseComponent(this.version, getLegacyText()).getJavaFXText(nodes);
|
||||
}
|
||||
|
||||
// just used reflections to not write this twice anc only change the method name
|
||||
@ -94,7 +96,7 @@ public class TranslatableComponent extends ChatComponent {
|
||||
});
|
||||
}
|
||||
}
|
||||
builder.append(MinecraftLocaleManager.translate(this.key, data));
|
||||
builder.append(this.version.getLocaleManager().translate(this.key, data));
|
||||
for (ChatFormattingCode code : this.parent.getFormatting()) {
|
||||
if (code instanceof PostChatFormattingCodes postCode) {
|
||||
builder.append(switch (methodName) {
|
||||
@ -106,7 +108,7 @@ public class TranslatableComponent extends ChatComponent {
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
String text = MinecraftLocaleManager.translate(this.key, data);
|
||||
String text = this.version.getLocaleManager().translate(this.key, data);
|
||||
if (text == null) {
|
||||
// Error, can not translate
|
||||
text = "{invalid=true, key=" + this.key + ", data=" + Arrays.toString(data);
|
||||
|
@ -0,0 +1,46 @@
|
||||
package de.bixilon.minosoft.generator;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import de.bixilon.minosoft.data.assets.AssetsManager;
|
||||
import de.bixilon.minosoft.data.assets.Resources;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class JarHashGenerator {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
if (args.length != 1) {
|
||||
System.err.println("Usage: JarHashGenerator <Version>");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Version version = new Version(args[0], -1, -1, null, null);
|
||||
|
||||
JsonObject json = JsonParser.parseReader(new InputStreamReader(new FileInputStream("src/main/resources/assets/mapping/resources.json"))).getAsJsonObject();
|
||||
|
||||
|
||||
JsonObject versions = json.getAsJsonObject("versions");
|
||||
|
||||
JsonObject versionJson = versions.getAsJsonObject(version.getVersionName());
|
||||
|
||||
Resources.loadVersion(version, versionJson);
|
||||
|
||||
AssetsManager assetsManager = new AssetsManager(true, Resources.getAssetVersionByVersion(version));
|
||||
String jarAssetsHash = assetsManager.generateJarAssets();
|
||||
|
||||
versionJson.addProperty("jar_assets_hash", jarAssetsHash);
|
||||
|
||||
|
||||
File file = new File("src/main/resources/assets/mapping/resources.json");
|
||||
FileWriter writer = new FileWriter(file.getAbsoluteFile());
|
||||
writer.write(new Gson().toJson(json));
|
||||
writer.close();
|
||||
System.exit(0);
|
||||
} catch (Exception e) {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ import de.bixilon.minosoft.data.accounts.Account;
|
||||
import de.bixilon.minosoft.data.locale.LocaleManager;
|
||||
import de.bixilon.minosoft.data.locale.Strings;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Versions;
|
||||
import de.bixilon.minosoft.data.text.BaseComponent;
|
||||
import de.bixilon.minosoft.data.text.ChatComponent;
|
||||
import de.bixilon.minosoft.protocol.protocol.LANServerListener;
|
||||
import de.bixilon.minosoft.util.DNSUtil;
|
||||
import de.bixilon.minosoft.util.logging.Log;
|
||||
@ -180,7 +180,7 @@ public class MainWindow implements Initializable {
|
||||
|
||||
submitButton.setOnAction(actionEvent -> {
|
||||
Server server1 = server;
|
||||
BaseComponent serverName = new BaseComponent(serverNameField.getText());
|
||||
ChatComponent serverName = ChatComponent.valueOf(serverNameField.getText());
|
||||
String serverAddress = DNSUtil.correctHostName(serverAddressField.getText());
|
||||
int desiredVersionId = GUITools.VERSION_COMBO_BOX.getSelectionModel().getSelectedItem().getVersionId();
|
||||
|
||||
|
@ -52,7 +52,7 @@ public class Server {
|
||||
}
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
this.addressName = new BaseComponent(address);
|
||||
this.addressName = ChatComponent.valueOf(address);
|
||||
this.desiredVersion = desiredVersion;
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ public class Server {
|
||||
|
||||
public Server(ServerAddress address) {
|
||||
this.id = getNextServerId();
|
||||
this.name = new BaseComponent(String.format("LAN Server #%d", LANServerListener.getServerMap().size()));
|
||||
this.name = ChatComponent.valueOf(String.format("LAN Server #%d", LANServerListener.getServerMap().size()));
|
||||
this.address = address.toString();
|
||||
this.desiredVersion = -1; // Automatic
|
||||
this.readOnly = true;
|
||||
@ -130,7 +130,7 @@ public class Server {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(BaseComponent name) {
|
||||
public void setName(ChatComponent name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ public class Server {
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
this.addressName = new BaseComponent(address);
|
||||
this.addressName = ChatComponent.valueOf(address);
|
||||
}
|
||||
|
||||
public void ping() {
|
||||
|
@ -32,6 +32,7 @@ import de.bixilon.minosoft.protocol.network.Connection;
|
||||
import de.bixilon.minosoft.protocol.ping.ForgeModInfo;
|
||||
import de.bixilon.minosoft.protocol.ping.ServerListPing;
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
import de.bixilon.minosoft.util.CountUpAndDownLatch;
|
||||
import de.bixilon.minosoft.util.logging.Log;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
@ -284,17 +285,21 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
|
||||
if (!this.canConnect || this.server.getLastPing() == null) {
|
||||
return;
|
||||
}
|
||||
Connection connection = new Connection(Connection.lastConnectionId++, this.server.getAddress(), new Player(Minosoft.getConfig().getSelectedAccount()));
|
||||
Version version;
|
||||
if (this.server.getDesiredVersionId() == ProtocolDefinition.QUERY_PROTOCOL_VERSION_ID) {
|
||||
version = this.server.getLastPing().getVersion();
|
||||
} else {
|
||||
version = Versions.getVersionById(this.server.getDesiredVersionId());
|
||||
}
|
||||
this.optionsConnect.setDisable(true);
|
||||
connection.connect(this.server.getLastPing().getAddress(), version);
|
||||
connection.registerEvent(new EventInvokerCallback<>(ConnectionStateChangeEvent.class, this::handleConnectionCallback));
|
||||
this.server.addConnection(connection);
|
||||
new Thread(() -> {
|
||||
Connection connection = new Connection(Connection.lastConnectionId++, this.server.getAddress(), new Player(Minosoft.getConfig().getSelectedAccount()));
|
||||
Version version;
|
||||
if (this.server.getDesiredVersionId() == ProtocolDefinition.QUERY_PROTOCOL_VERSION_ID) {
|
||||
version = this.server.getLastPing().getVersion();
|
||||
} else {
|
||||
version = Versions.getVersionById(this.server.getDesiredVersionId());
|
||||
}
|
||||
this.optionsConnect.setDisable(true);
|
||||
// ToDo: show progress dialog
|
||||
|
||||
connection.connect(this.server.getLastPing().getAddress(), version, new CountUpAndDownLatch(1));
|
||||
connection.registerEvent(new EventInvokerCallback<>(ConnectionStateChangeEvent.class, this::handleConnectionCallback));
|
||||
this.server.addConnection(connection);
|
||||
}, "ConnectThread").start();
|
||||
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ import de.bixilon.minosoft.protocol.ping.ServerListPing;
|
||||
import de.bixilon.minosoft.protocol.protocol.*;
|
||||
import de.bixilon.minosoft.terminal.CLI;
|
||||
import de.bixilon.minosoft.terminal.commands.commands.Command;
|
||||
import de.bixilon.minosoft.util.CountUpAndDownLatch;
|
||||
import de.bixilon.minosoft.util.DNSUtil;
|
||||
import de.bixilon.minosoft.util.ServerAddress;
|
||||
import de.bixilon.minosoft.util.logging.Log;
|
||||
@ -123,10 +124,21 @@ public class Connection {
|
||||
this.network.connect(this.address);
|
||||
}
|
||||
|
||||
public void connect(ServerAddress address, Version version) {
|
||||
public void connect(ServerAddress address, Version version, CountUpAndDownLatch latch) {
|
||||
this.address = address;
|
||||
this.reason = ConnectionReasons.CONNECT;
|
||||
setVersion(version);
|
||||
try {
|
||||
version.initializeAssetManger(latch); // ToDo
|
||||
version.loadMappings(latch);
|
||||
this.customMapping.setVersion(version);
|
||||
this.customMapping.setParentMapping(version.getMapping());
|
||||
} catch (Exception e) {
|
||||
Log.printException(e, LogLevels.DEBUG);
|
||||
Log.fatal(String.format("Could not load mapping for %s. This version seems to be unsupported!", version));
|
||||
this.lastException = new MappingsLoadingException("Mappings could not be loaded", e);
|
||||
setConnectionState(ConnectionStates.FAILED_NO_RETRY);
|
||||
}
|
||||
Log.info(String.format("Connecting to server: %s", address));
|
||||
this.network.connect(address);
|
||||
}
|
||||
@ -149,18 +161,9 @@ public class Connection {
|
||||
}
|
||||
|
||||
this.version = version;
|
||||
try {
|
||||
Versions.loadVersionMappings(version);
|
||||
this.customMapping.setVersion(version);
|
||||
this.customMapping.setParentMapping(version.getMapping());
|
||||
} catch (Exception e) {
|
||||
Log.printException(e, LogLevels.DEBUG);
|
||||
Log.fatal(String.format("Could not load mapping for %s. This version seems to be unsupported!", version));
|
||||
this.lastException = new MappingsLoadingException("Mappings could not be loaded", e);
|
||||
setConnectionState(ConnectionStates.FAILED_NO_RETRY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void handle(ClientboundPacket p) {
|
||||
this.handlingQueue.add(p);
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import java.math.BigInteger;
|
||||
import java.security.PublicKey;
|
||||
|
||||
public class PacketEncryptionRequest extends ClientboundPacket {
|
||||
|
||||
String serverId; // normally empty
|
||||
byte[] publicKey;
|
||||
byte[] verifyToken;
|
||||
|
@ -30,7 +30,7 @@ public class PacketStatusResponse extends ClientboundPacket {
|
||||
|
||||
@Override
|
||||
public boolean read(InByteBuffer buffer) {
|
||||
this.response = new ServerListPing(buffer.readJSON());
|
||||
this.response = new ServerListPing(buffer.getConnection().getVersion(), buffer.readJSON());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
package de.bixilon.minosoft.protocol.ping;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import de.bixilon.minosoft.data.mappings.versions.Version;
|
||||
import de.bixilon.minosoft.data.text.BaseComponent;
|
||||
import de.bixilon.minosoft.data.text.ChatComponent;
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
|
||||
@ -30,7 +31,7 @@ public class ServerListPing {
|
||||
private final String serverBrand;
|
||||
byte[] favicon;
|
||||
|
||||
public ServerListPing(JsonObject json) {
|
||||
public ServerListPing(Version version, JsonObject json) {
|
||||
int protocolId = json.getAsJsonObject("version").get("protocol").getAsInt();
|
||||
if (protocolId == ProtocolDefinition.QUERY_PROTOCOL_VERSION_ID) {
|
||||
// Server did not send us a version, trying 1.8
|
||||
@ -48,7 +49,7 @@ public class ServerListPing {
|
||||
if (json.get("description").isJsonPrimitive()) {
|
||||
this.motd = ChatComponent.valueOf(json.get("description").getAsString());
|
||||
} else {
|
||||
this.motd = new BaseComponent(json.getAsJsonObject("description"));
|
||||
this.motd = new BaseComponent(version, json.getAsJsonObject("description"));
|
||||
}
|
||||
this.serverBrand = json.getAsJsonObject("version").get("name").getAsString();
|
||||
|
||||
|
@ -202,7 +202,7 @@ public class InByteBuffer {
|
||||
}
|
||||
|
||||
public ChatComponent readChatComponent() {
|
||||
return ChatComponent.valueOf(readString());
|
||||
return ChatComponent.valueOf(this.connection.getVersion(), readString());
|
||||
}
|
||||
|
||||
@IntRange(from = 0)
|
||||
@ -227,7 +227,7 @@ public class InByteBuffer {
|
||||
if (this.versionId < V_17W45A) {
|
||||
// old particle format
|
||||
return switch (type.getFullIdentifier()) {
|
||||
case "minecraft:iconcrack" -> new ItemParticleData(new Slot(this.connection.getMapping().getItemByLegacy(readVarInt(), readVarInt())), type);
|
||||
case "minecraft:iconcrack" -> new ItemParticleData(new Slot(this.connection.getVersion(), this.connection.getMapping().getItemByLegacy(readVarInt(), readVarInt())), type);
|
||||
case "minecraft:blockcrack", "minecraft:blockdust", "minecraft:falling_dust" -> new BlockParticleData(this.connection.getMapping().getBlockById(readVarInt() << 4), type);
|
||||
default -> new ParticleData(type);
|
||||
};
|
||||
@ -298,10 +298,10 @@ public class InByteBuffer {
|
||||
metaData = readShort();
|
||||
}
|
||||
CompoundTag nbt = (CompoundTag) readNBT(this.versionId < V_14W28B);
|
||||
return new Slot(this.connection.getMapping(), this.connection.getMapping().getItemByLegacy(id, metaData), count, metaData, nbt);
|
||||
return new Slot(this.connection.getVersion(), this.connection.getMapping().getItemByLegacy(id, metaData), count, metaData, nbt);
|
||||
}
|
||||
if (readBoolean()) {
|
||||
return new Slot(this.connection.getMapping(), this.connection.getMapping().getItemById(readVarInt()), readByte(), (CompoundTag) readNBT());
|
||||
return new Slot(this.connection.getVersion(), this.connection.getMapping().getItemById(readVarInt()), readByte(), (CompoundTag) readNBT());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -62,8 +62,9 @@ public final class ProtocolDefinition {
|
||||
|
||||
|
||||
public static final String MOJANG_URL_VERSION_MANIFEST = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
||||
public static final String MOJANG_URL_RESOURCES = "https://resources.download.minecraft.net/%s/%s";
|
||||
public static final String MOJANG_URL_PACKAGES = "https://launchermeta.mojang.com/v1/packages/";
|
||||
public static final String MINECRAFT_URL_RESOURCES = "https://resources.download.minecraft.net/%s/%s";
|
||||
public static final String MOJANG_URL_PACKAGES = "https://launchermeta.mojang.com/v1/packages/%s/%s";
|
||||
public static final String MOJANG_LAUNCHER_URL_PACKAGES = "https://launcher.mojang.com/v1/objects/%s/%s";
|
||||
|
||||
public static final String MOJANG_URL_BLOCKED_SERVERS = "https://sessionserver.mojang.com/blockedservers";
|
||||
public static final String MOJANG_URL_LOGIN = "https://authserver.mojang.com/authenticate";
|
||||
|
141
util/block_model_generator.py
Normal file
141
util/block_model_generator.py
Normal file
@ -0,0 +1,141 @@
|
||||
# Minosoft
|
||||
# Copyright (C) 2020 Lukas Eisenhauer
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
|
||||
import io
|
||||
import sys
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
import ujson
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: %s <destination path> <jar url>" % sys.argv[0])
|
||||
exit(1)
|
||||
|
||||
blockStates = {}
|
||||
blockModels = {}
|
||||
|
||||
modName = "minecraft"
|
||||
|
||||
print("Downloading minecraft jar...")
|
||||
|
||||
# thanks: https://stackoverflow.com/questions/60171502/requests-get-is-very-slow
|
||||
|
||||
response = urllib.request.urlopen(sys.argv[2])
|
||||
print("Unpacking minecraft jar...")
|
||||
|
||||
zip = zipfile.ZipFile(io.BytesIO(response.read()), "r")
|
||||
|
||||
files = zip.namelist()
|
||||
|
||||
print("Loading blockstates...")
|
||||
|
||||
|
||||
def readRotations(apply, current):
|
||||
if "x" in current:
|
||||
apply["x"] = current["x"]
|
||||
if "y" in current:
|
||||
apply["y"] = current["y"]
|
||||
if "z" in current:
|
||||
apply["z"] = current["z"]
|
||||
|
||||
|
||||
def readPart(part):
|
||||
properties = []
|
||||
if "when" in part:
|
||||
when = part["when"]
|
||||
if "OR" in when:
|
||||
for item in when["OR"]:
|
||||
properties.append(item)
|
||||
else:
|
||||
properties.append(part["when"])
|
||||
apply = {}
|
||||
current = part["apply"]
|
||||
if type(current) == type([]):
|
||||
current = current[0]
|
||||
if "/" in current["model"]:
|
||||
apply["model"] = current["model"].split("/")[1]
|
||||
else:
|
||||
apply["model"] = current["model"]
|
||||
readRotations(apply, current)
|
||||
result = []
|
||||
for item in properties:
|
||||
state = {"properties": item}
|
||||
for i in apply:
|
||||
state[i] = apply[i]
|
||||
result.append(state)
|
||||
if len(result) == 0:
|
||||
result.append(apply)
|
||||
return result
|
||||
|
||||
|
||||
for blockStateFile in [f for f in files if f.startswith('assets/minecraft/blockstates/')]:
|
||||
with zip.open(blockStateFile) as file:
|
||||
tempData = file.read().decode("utf-8")
|
||||
if tempData.endswith("n"):
|
||||
# why the hell are mojangs json files incorrect?
|
||||
# in 19w02a (https://launcher.mojang.com/v1/objects/8664f5d1b428d5ba8a936ab9c097cc78821d06e6/client.jar) the json ends with a random "n"
|
||||
tempData = tempData[:-1]
|
||||
data = ujson.loads(tempData)
|
||||
block = {}
|
||||
if "variants" in data:
|
||||
variants = data["variants"]
|
||||
states = []
|
||||
for variant in variants:
|
||||
state = {}
|
||||
properties = {}
|
||||
if variant != "" and variant != "normal" and variant != "map" and variant != "all":
|
||||
for part in variant.split(","):
|
||||
properties[part.split("=")[0]] = part.split("=")[1]
|
||||
state["properties"] = properties
|
||||
current = variants[variant]
|
||||
if type(current) == type([]):
|
||||
current = current[0]
|
||||
|
||||
if "/" in current["model"]:
|
||||
state["model"] = current["model"].split("/")[1]
|
||||
else:
|
||||
state["model"] = current["model"]
|
||||
readRotations(state, current)
|
||||
states.append(state)
|
||||
block = {
|
||||
"states": states
|
||||
}
|
||||
elif "multipart" in data:
|
||||
parts = data["multipart"]
|
||||
conditional = []
|
||||
for part in parts:
|
||||
conditional.extend(readPart(part))
|
||||
block = {
|
||||
"conditional": conditional
|
||||
}
|
||||
blockStates[blockStateFile.split(".")[0].split("/")[-1]] = block
|
||||
|
||||
print("Loading models...")
|
||||
for blockModelFile in [f for f in files if f.startswith('assets/minecraft/models/block/')]:
|
||||
with zip.open(blockModelFile) as file:
|
||||
data = ujson.load(file)
|
||||
blockModels[blockModelFile.split(".")[0].split("/")[-1]] = data
|
||||
|
||||
print("Combining files...")
|
||||
finalJson = {
|
||||
modName: {
|
||||
"blockStates": blockStates,
|
||||
"blockModels": blockModels
|
||||
}
|
||||
}
|
||||
|
||||
print("Saving...")
|
||||
with open(sys.argv[1], "w+") as file:
|
||||
finalJson = ujson.dumps(finalJson)
|
||||
file.write(finalJson.replace("minecraft:", ""))
|
||||
|
||||
print("Finished successfully")
|
@ -12,10 +12,12 @@
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
import traceback
|
||||
|
||||
import requests
|
||||
import ujson
|
||||
|
||||
print("Minecraft mappings downloader (and generator)")
|
||||
@ -24,7 +26,7 @@ PRE_FLATTENING_UPDATE_VERSION = "17w46a"
|
||||
DATA_FOLDER = "../data/resources/"
|
||||
TEMP_FOLDER = DATA_FOLDER + "tmp/"
|
||||
OPTIONAL_FILES_PER_VERSION = ["entities.json"]
|
||||
FILES_PER_VERSION = ["blocks.json", "registries.json"] + OPTIONAL_FILES_PER_VERSION
|
||||
FILES_PER_VERSION = ["blocks.json", "registries.json", "block_models.json"] + OPTIONAL_FILES_PER_VERSION
|
||||
DOWNLOAD_BASE_URL = "https://apimon.de/mcdata/"
|
||||
RESOURCE_MAPPINGS_INDEX = ujson.load(open("../src/main/resources/assets/mapping/resources.json"))
|
||||
MOJANG_MINOSOFT_FIELD_MAPPINGS = ujson.load(open("entitiesFieldMojangMinosoftMappings.json"))
|
||||
@ -128,14 +130,50 @@ if not os.path.isdir(DATA_FOLDER):
|
||||
if not os.path.isdir(TEMP_FOLDER):
|
||||
os.mkdir(TEMP_FOLDER)
|
||||
|
||||
|
||||
# compile minosoft
|
||||
|
||||
|
||||
def generateJarAssets(versionId):
|
||||
generateProcess = ""
|
||||
try:
|
||||
generateProcess = subprocess.run(r'mvn exec:java -Dexec.mainClass="de.bixilon.minosoft.generator.JarHashGenerator" -Dexec.args="%s"' % versionId, cwd=r'../', shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
# reload mappings
|
||||
global RESOURCE_MAPPINGS_INDEX
|
||||
RESOURCE_MAPPINGS_INDEX = ujson.load(open("../src/main/resources/assets/mapping/resources.json"))
|
||||
except Exception:
|
||||
print(generateProcess.stdout)
|
||||
print(generateProcess.stderr)
|
||||
|
||||
|
||||
print("Compiling minosoft...")
|
||||
compileProcess = ""
|
||||
try:
|
||||
compileProcess = subprocess.run(r'mvn compile', shell=True, check=True, cwd=r'../', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except Exception:
|
||||
print(compileProcess.stdout)
|
||||
print(compileProcess.stderr)
|
||||
exit(1)
|
||||
print("Minosoft compiled!")
|
||||
|
||||
for version in VERSION_MANIFEST["versions"]:
|
||||
if version["id"] == PRE_FLATTENING_UPDATE_VERSION:
|
||||
break
|
||||
versionTempBaseFolder = TEMP_FOLDER + version["id"] + "/"
|
||||
resourcesJsonKey = ("mappings/%s" % version["id"])
|
||||
if resourcesJsonKey in RESOURCE_MAPPINGS_INDEX and os.path.isfile(DATA_FOLDER + RESOURCE_MAPPINGS_INDEX[resourcesJsonKey][:2] + "/" + RESOURCE_MAPPINGS_INDEX[resourcesJsonKey] + ".tar.gz"):
|
||||
print("Skipping %s" % (version["id"]))
|
||||
continue
|
||||
resourcesVersion = {}
|
||||
if version["id"] in RESOURCE_MAPPINGS_INDEX["versions"]:
|
||||
resourcesVersion = RESOURCE_MAPPINGS_INDEX["versions"][version["id"]]
|
||||
if os.path.isfile(DATA_FOLDER + resourcesVersion["mappings"][:2] + "/" + resourcesVersion["mappings"] + ".tar.gz"):
|
||||
if "jar_assets_hash" not in resourcesVersion:
|
||||
print("=== %s === " % version["id"])
|
||||
try:
|
||||
generateJarAssets(version["id"])
|
||||
except Exception:
|
||||
failedVersionIds.append(version["id"])
|
||||
continue
|
||||
else:
|
||||
print("Skipping %s" % (version["id"]))
|
||||
continue
|
||||
print()
|
||||
print("=== %s === " % version["id"])
|
||||
|
||||
@ -306,6 +344,10 @@ for version in VERSION_MANIFEST["versions"]:
|
||||
with open(versionTempBaseFolder + "entities.json", 'w') as file:
|
||||
file.write(ujson.dumps({"minecraft": entities}))
|
||||
|
||||
elif fileName == "block_models.json":
|
||||
# blockModelsCombiner.py will do the trick for us
|
||||
os.popen('python3 block_model_generator.py \"%s\" %s' % (versionTempBaseFolder + "block_models.json", versionJson['downloads']['client']['url'])).read()
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
print("ERR: Could not generate %s for %s" % (fileName, version["id"]))
|
||||
@ -337,20 +379,32 @@ for version in VERSION_MANIFEST["versions"]:
|
||||
os.mkdir(DATA_FOLDER + sha1[:2])
|
||||
os.rename(versionTempBaseFolder + version["id"] + ".tar.gz", DATA_FOLDER + sha1[:2] + "/" + sha1 + ".tar.gz")
|
||||
|
||||
if resourcesJsonKey in RESOURCE_MAPPINGS_INDEX:
|
||||
if "mappings" in resourcesVersion:
|
||||
# this file already has a mapping, delete it
|
||||
hashToDelete = RESOURCE_MAPPINGS_INDEX[resourcesJsonKey]
|
||||
hashToDelete = resourcesVersion["mappings"]
|
||||
filenameToDelete = DATA_FOLDER + hashToDelete[:2] + "/" + hashToDelete + ".tar.gz"
|
||||
if os.path.isfile(filenameToDelete):
|
||||
shutil.rmtree(filenameToDelete)
|
||||
|
||||
RESOURCE_MAPPINGS_INDEX[resourcesJsonKey] = sha1
|
||||
resourcesVersion["mappings"] = sha1
|
||||
resourcesVersion["index_version"] = versionJson["assetIndex"]["id"]
|
||||
resourcesVersion["index_hash"] = versionJson["assetIndex"]["sha1"]
|
||||
resourcesVersion["client_jar_hash"] = versionJson["downloads"]["client"]["sha1"]
|
||||
|
||||
RESOURCE_MAPPINGS_INDEX["versions"][version["id"]] = resourcesVersion
|
||||
# cleanup (delete temp folder)
|
||||
shutil.rmtree(versionTempBaseFolder)
|
||||
# dump resources index
|
||||
with open("../src/main/resources/assets/mapping/resources.json", 'w') as file:
|
||||
ujson.dump(RESOURCE_MAPPINGS_INDEX, file)
|
||||
|
||||
# start jar hash generator
|
||||
# todo: don't download jar twice
|
||||
try:
|
||||
generateJarAssets(version["id"])
|
||||
except Exception:
|
||||
failedVersionIds.append(version["id"])
|
||||
|
||||
print()
|
||||
print()
|
||||
print("Done")
|
||||
|
Loading…
x
Reference in New Issue
Block a user