diff --git a/launcher/Application.cpp b/launcher/Application.cpp index eac2c72f9..60aa79076 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -649,7 +649,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("IgnoreJavaWizard", false); // Legacy settings - m_settings->registerSetting("OnlineFixes", false); + m_settings->registerSetting("OnlineFixes", true); // Native library workarounds m_settings->registerSetting("UseNativeOpenAL", false); diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 951d74a9e..c1610e078 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -143,9 +143,8 @@ void LaunchController::login() return; } - if (m_accountToUse->usesCustomApiServers()) { - MinecraftInstancePtr inst = std::dynamic_pointer_cast(m_instance); - + MinecraftInstancePtr inst = std::dynamic_pointer_cast(m_instance); + if (m_accountToUse->usesCustomApiServers() && !inst->shouldApplyOnlineFixes()) { bool authlibInjectorInstalled = false; const auto& agents = inst->getPackProfile()->getProfile()->getAgents(); for (const auto& agent : agents) { diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index 4cd1ba58b..1239f9b2e 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -28,6 +28,7 @@ set(LEGACY_SRC legacy/org/prismlauncher/legacy/fix/online/OnlineModeFix.java legacy/org/prismlauncher/legacy/fix/online/SkinFix.java legacy/org/prismlauncher/legacy/utils/Base64.java + legacy/org/prismlauncher/legacy/utils/api/ApiServers.java legacy/org/prismlauncher/legacy/utils/api/MojangApi.java legacy/org/prismlauncher/legacy/utils/api/Texture.java legacy/org/prismlauncher/legacy/utils/json/JsonParseException.java diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineModeFix.java b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineModeFix.java index 1bab76d53..10a674d1e 100644 --- a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineModeFix.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineModeFix.java @@ -35,32 +35,80 @@ package org.prismlauncher.legacy.fix.online; +import org.prismlauncher.legacy.utils.api.ApiServers; +import org.prismlauncher.legacy.utils.api.MojangApi; +import org.prismlauncher.legacy.utils.url.ByteArrayUrlConnection; import org.prismlauncher.legacy.utils.url.UrlUtils; import java.io.IOException; -import java.net.MalformedURLException; +import java.io.OutputStream; +import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URL; import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; public final class OnlineModeFix { public static URLConnection openConnection(URL address, Proxy proxy) throws IOException { // we start with "http://www.minecraft.net/game/joinserver.jsp?user=..." - if (!(address.getHost().equals("www.minecraft.net") && address.getPath().equals("/game/joinserver.jsp"))) + if (!(address.getHost().equals("www.minecraft.net") && address.getPath().equals("/game/joinserver.jsp"))) { return null; + } + + Map params = new HashMap<>(); + String query = address.getQuery(); + String[] entries = query.split("&"); + for (String entry : entries) { + String[] pair = entry.split("="); + if (pair.length == 2) { + params.put(pair[0], pair[1]); + } + } - // change it to "https://session.minecraft.net/game/joinserver.jsp?user=..." - // this seems to be the modern version of the same endpoint... - // maybe Mojang planned to patch old versions of the game to use it - // if it ever disappears this should be changed to use sessionserver.mojang.com/session/minecraft/join - // which of course has a different usage requiring JSON serialisation... - URL url; - try { - url = new URL("https", "session.minecraft.net", address.getPort(), address.getFile()); - } catch (MalformedURLException e) { - throw new AssertionError("url should be valid", e); + String user = params.get("user"); + if (user == null) { + throw new AssertionError("missing user"); } + String serverId = params.get("serverId"); + if (serverId == null) { + throw new AssertionError("missing serverId"); + } + String sessionId = params.get("sessionId"); + if (sessionId == null) { + throw new AssertionError("missing sessionId"); + } + + // sessionId has the form: + // token:: + String accessToken = sessionId.split(":")[1]; - return UrlUtils.openConnection(url, proxy); + String uuid = null; + uuid = MojangApi.getUuid(user, proxy); + if (uuid == null) { + return new ByteArrayUrlConnection(("Couldn't find UUID of " + user).getBytes("utf-8")); + } + + URL url = new URL(ApiServers.getSessionURL() + "/session/minecraft/join"); + HttpURLConnection connection = (HttpURLConnection) UrlUtils.openConnection(url, proxy); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Accept", "application/json"); + try (OutputStream os = connection.getOutputStream()) { + String payload = "{" + + "\"accessToken\": \"" + accessToken + "\"," + + "\"selectedProfile\": \"" + uuid + "\"," + + "\"serverId\": \"" + serverId + "\"" + + "}"; + os.write(payload.getBytes("utf-8")); + } + int responseCode = connection.getResponseCode(); + + if (responseCode == 204) { + return new ByteArrayUrlConnection("OK".getBytes("utf-8")); + } else { + return new ByteArrayUrlConnection("Bad login".getBytes("utf-8")); + } } } diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java index d5b185450..8f2ad4185 100644 --- a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java @@ -80,7 +80,7 @@ static URLConnection openConnection(URL address, Proxy proxy) throws IOException if (capeOwner != null) { // since we do not need to process the image, open a direct connection bypassing // Handler - Texture texture = MojangApi.getTexture(MojangApi.getUuid(capeOwner), "CAPE"); + Texture texture = MojangApi.getTexture(MojangApi.getUuid(capeOwner, proxy), "CAPE"); if (texture == null) return null; @@ -91,7 +91,7 @@ static URLConnection openConnection(URL address, Proxy proxy) throws IOException } private static URLConnection getSkinConnection(String owner, Proxy proxy) throws IOException { - Texture texture = MojangApi.getTexture(MojangApi.getUuid(owner), "SKIN"); + Texture texture = MojangApi.getTexture(MojangApi.getUuid(owner, proxy), "SKIN"); if (texture == null) return null; diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/ApiServers.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/ApiServers.java new file mode 100644 index 000000000..6a90db0c4 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/ApiServers.java @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Fjord Launcher - Minecraft Launcher + * Copyright (C) 2024 Evan Goode + * + * 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, version 3. + * + * 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 . + */ + +package org.prismlauncher.legacy.utils.api; + +public class ApiServers { + public static String getAuthURL() { + return getPropertyWithFallback("minecraft.api.auth.host", "https://authserver.mojang.com"); + } + public static String getAccountURL() { + return getPropertyWithFallback("minecraft.api.account.host", "https://api.mojang.com"); + } + public static String getSessionURL() { + return getPropertyWithFallback("minecraft.api.session.host", "https://sessionserver.mojang.com"); + } + public static String getServicesURL() { + return getPropertyWithFallback("minecraft.api.services.host", "https://api.minecraftservices.com"); + } + private static String getPropertyWithFallback(String key, String fallback) { + String value = System.getProperty(key); + if (value == null) { + return fallback; + } + return value; + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java index 41f7f9114..4507950be 100644 --- a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java @@ -36,11 +36,17 @@ package org.prismlauncher.legacy.utils.api; import org.prismlauncher.legacy.utils.Base64; +import org.prismlauncher.legacy.utils.api.ApiServers; import org.prismlauncher.legacy.utils.json.JsonParser; +import org.prismlauncher.legacy.utils.url.UrlUtils; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; +import java.util.List; import java.util.Map; /** @@ -48,10 +54,20 @@ */ @SuppressWarnings("unchecked") public final class MojangApi { - public static String getUuid(String username) throws IOException { - try (InputStream in = new URL("https://api.mojang.com/users/profiles/minecraft/" + username).openStream()) { - Map map = (Map) JsonParser.parse(in); - return (String) map.get("id"); + public static String getUuid(String username, Proxy proxy) throws IOException { + URL url = new URL(ApiServers.getAccountURL() + "/profiles/minecraft"); + HttpURLConnection connection = (HttpURLConnection) UrlUtils.openConnection(url, proxy); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("Accept", "application/json"); + try (OutputStream os = connection.getOutputStream()) { + String payload = "[\"" + username + "\"]"; + os.write(payload.getBytes("utf-8")); + } + try (InputStream in = connection.getInputStream()) { + List> list = (List>) JsonParser.parse(in); + return (String) list.get(0).get("id"); } } @@ -79,7 +95,7 @@ public static Texture getTexture(String player, String id) throws IOException { } public static Map getTextures(String player) throws IOException { - try (InputStream profileIn = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + player).openStream()) { + try (InputStream profileIn = new URL(ApiServers.getSessionURL() + "/session/minecraft/profile/" + player).openStream()) { Map profile = (Map) JsonParser.parse(profileIn); for (Map property : (Iterable>) profile.get("properties")) {