diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 11e42f163..fa8ea8dfa 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -582,6 +582,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("IgnoreJavaCompatibility", false); m_settings->registerSetting("IgnoreJavaWizard", false); + // Legacy settings + m_settings->registerSetting("OnlineFixes", false); + // Native library workarounds m_settings->registerSetting("UseNativeOpenAL", false); m_settings->registerSetting("CustomOpenALPath", ""); diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp index f9ac47824..b77bf2adf 100644 --- a/launcher/java/JavaVersion.cpp +++ b/launcher/java/JavaVersion.cpp @@ -45,10 +45,12 @@ QString JavaVersion::toString() const bool JavaVersion::requiresPermGen() { - if (m_parseable) { - return m_major < 8; - } - return true; + return !m_parseable || m_major < 8; +} + +bool JavaVersion::isModular() +{ + return m_parseable && m_major >= 9; } bool JavaVersion::operator<(const JavaVersion& rhs) diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h index 7e66269cb..421578ea1 100644 --- a/launcher/java/JavaVersion.h +++ b/launcher/java/JavaVersion.h @@ -25,6 +25,8 @@ class JavaVersion { bool requiresPermGen(); + bool isModular(); + QString toString() const; int major() { return m_major; } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index f9833b972..55fd864bb 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -184,6 +184,10 @@ void MinecraftInstance::loadSpecificSettings() m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride); m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride); + // Legacy-related options + auto legacySettings = m_settings->registerSetting("OverrideLegacySettings", false); + m_settings->registerOverride(global_settings->getSetting("OnlineFixes"), legacySettings); + m_settings->set("InstanceType", "OneSix"); } @@ -513,20 +517,28 @@ QStringList MinecraftInstance::javaArguments() args << "-Duser.language=en"; + if (javaVersion.isModular() && shouldApplyOnlineFixes()) + // allow reflective access to java.net - required by the skin fix + args << "--add-opens" + << "java.base/java.net=ALL-UNNAMED"; + return args; } QString MinecraftInstance::getLauncher() { - auto profile = m_components->getProfile(); - // use legacy launcher if the traits are set - if (profile->getTraits().contains("legacyLaunch") || profile->getTraits().contains("alphaLaunch")) + if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch")) return "legacy"; return "standard"; } +bool MinecraftInstance::shouldApplyOnlineFixes() +{ + return traits().contains("legacyServices") && settings()->get("OnlineFixes").toBool(); +} + QMap MinecraftInstance::getVariables() { QMap out; @@ -716,6 +728,9 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS launchScript += "traits " + trait + "\n"; } + if (shouldApplyOnlineFixes()) + launchScript += "onlineFixes true\n"; + launchScript += "launcher " + getLauncher() + "\n"; // qDebug() << "Generated launch script:" << launchScript; diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index dabd44ba7..a816a3e60 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -129,6 +129,7 @@ class MinecraftInstance : public BaseInstance { /// get arguments passed to java QStringList javaArguments(); QString getLauncher(); + bool shouldApplyOnlineFixes(); /// get variables for launch command variable substitution/environment QMap getVariables() override; diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 44e5d0a63..aa94edb5d 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -105,6 +105,17 @@ void LauncherPartLaunch::executeTask() auto instance = m_parent->instance(); std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + QString legacyJarPath; + if (minecraftInstance->getLauncher() == "legacy" || minecraftInstance->shouldApplyOnlineFixes()) { + legacyJarPath = APPLICATION->getJarPath("NewLaunchLegacy.jar"); + if (legacyJarPath.isEmpty()) { + const char* reason = QT_TR_NOOP("Legacy launcher library could not be found. Please check your installation."); + emit logLine(tr(reason), MessageLevel::Fatal); + emitFailed(tr(reason)); + return; + } + } + m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin); QStringList args = minecraftInstance->javaArguments(); QString allArgs = args.join(", "); @@ -120,6 +131,9 @@ void LauncherPartLaunch::executeTask() auto classPath = minecraftInstance->getClassPath(); classPath.prepend(jarPath); + if (!legacyJarPath.isEmpty()) + classPath.prepend(legacyJarPath); + auto natPath = minecraftInstance->getNativePath(); #ifdef Q_OS_WIN if (!fitsInLocal8bit(natPath)) { diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp index 553cefd83..a9530effc 100644 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ b/launcher/ui/pages/global/MinecraftPage.cpp @@ -119,6 +119,9 @@ void MinecraftPage::applySettings() // Miscellaneous s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); + + // Legacy settings + s->set("OnlineFixes", ui->onlineFixes->isChecked()); } void MinecraftPage::loadSettings() @@ -170,6 +173,8 @@ void MinecraftPage::loadSettings() ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); + + ui->onlineFixes->setChecked(s->get("OnlineFixes").toBool()); } void MinecraftPage::retranslate() diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index b5cfa659f..2a3c0d96d 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -138,7 +138,7 @@ - + Show time spent playing in hours @@ -197,6 +197,25 @@ Tweaks + + + + Legacy settings + + + + + + <html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>This currently allows modern skins to be used.</p></body></html> + + + Enable online fixes (experimental) + + + + + + diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 7aa6bd322..c0ced2415 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad * * 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 @@ -253,6 +254,14 @@ void InstanceSettingsPage::applySettings() m_settings->reset("InstanceAccountId"); } + bool overrideLegacySettings = ui->legacySettingsGroupBox->isChecked(); + m_settings->set("OverrideLegacySettings", overrideLegacySettings); + if (overrideLegacySettings) { + m_settings->set("OnlineFixes", ui->onlineFixes->isChecked()); + } else { + m_settings->reset("OnlineFixes"); + } + // FIXME: This should probably be called by a signal instead m_instance->updateRuntimeContext(); } @@ -356,6 +365,9 @@ void InstanceSettingsPage::loadSettings() ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool()); updateAccountsMenu(); + + ui->legacySettingsGroupBox->setChecked(m_settings->get("OverrideLegacySettings").toBool()); + ui->onlineFixes->setChecked(m_settings->get("OnlineFixes").toBool()); } void InstanceSettingsPage::on_javaDetectBtn_clicked() diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 81cf7093e..8defaccb3 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -583,6 +583,31 @@ Miscellaneous + + + + Legacy settings + + + true + + + false + + + + + + <html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>This currently allows modern skins to be used.</p></body></html> + + + Enable online fixes (experimental) + + + + + + diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index 55ed58756..7bf160760 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -11,15 +11,33 @@ set(SRC org/prismlauncher/launcher/Launcher.java org/prismlauncher/launcher/impl/AbstractLauncher.java org/prismlauncher/launcher/impl/StandardLauncher.java - org/prismlauncher/launcher/impl/legacy/LegacyLauncher.java - org/prismlauncher/launcher/impl/legacy/LegacyFrame.java org/prismlauncher/exception/ParameterNotFoundException.java org/prismlauncher/exception/ParseException.java org/prismlauncher/utils/Parameters.java org/prismlauncher/utils/ReflectionUtils.java org/prismlauncher/utils/logging/Level.java org/prismlauncher/utils/logging/Log.java - net/minecraft/Launcher.java + org/prismlauncher/legacy/LegacyProxy.java ) + +set(LEGACY_SRC + legacy/org/prismlauncher/legacy/LegacyFrame.java + legacy/org/prismlauncher/legacy/LegacyLauncher.java + legacy/org/prismlauncher/legacy/fix/online/Handler.java + legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java + legacy/org/prismlauncher/legacy/fix/online/SkinFix.java + legacy/org/prismlauncher/legacy/utils/Base64.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 + legacy/org/prismlauncher/legacy/utils/json/JsonParser.java + legacy/org/prismlauncher/legacy/utils/url/CustomUrlConnection.java + legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java + legacy/net/minecraft/Launcher.java + legacy/org/prismlauncher/legacy/LegacyProxy.java +) + add_jar(NewLaunch ${SRC}) +add_jar(NewLaunchLegacy ${LEGACY_SRC} INCLUDE_JARS NewLaunch) install_jar(NewLaunch "${JARS_DEST_DIR}") +install_jar(NewLaunchLegacy "${JARS_DEST_DIR}") diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/legacy/net/minecraft/Launcher.java similarity index 98% rename from libraries/launcher/net/minecraft/Launcher.java rename to libraries/launcher/legacy/net/minecraft/Launcher.java index 10cfa2ac0..933a81433 100644 --- a/libraries/launcher/net/minecraft/Launcher.java +++ b/libraries/launcher/legacy/net/minecraft/Launcher.java @@ -92,12 +92,11 @@ public final class Launcher extends Applet implements AppletStub { try { if (documentBase == null) { - if (applet.getClass().getPackage().getName().startsWith("com.mojang.")) { + if (applet.getClass().getPackage().getName().startsWith("com.mojang")) // Special case only for Classic versions documentBase = new URL("http://www.minecraft.net:80/game/"); - } else { + else documentBase = new URL("http://www.minecraft.net/game/"); - } } } catch (MalformedURLException e) { throw new AssertionError(e); diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyFrame.java b/libraries/launcher/legacy/org/prismlauncher/legacy/LegacyFrame.java similarity index 97% rename from libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyFrame.java rename to libraries/launcher/legacy/org/prismlauncher/legacy/LegacyFrame.java index 6cfe35d86..8276c23ef 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyFrame.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/LegacyFrame.java @@ -52,7 +52,7 @@ * limitations under the License. */ -package org.prismlauncher.launcher.impl.legacy; +package org.prismlauncher.legacy; import org.prismlauncher.utils.logging.Log; @@ -74,7 +74,7 @@ import javax.swing.JFrame; import net.minecraft.Launcher; -public final class LegacyFrame extends JFrame { +final class LegacyFrame extends JFrame { private static final long serialVersionUID = 1L; private final Launcher launcher; @@ -130,7 +130,7 @@ public final class LegacyFrame extends JFrame { launcher.setParameter("username", user); launcher.setParameter("sessionid", session); - launcher.setParameter("stand-alone", true); // Show the quit button. TODO: why won't this work? + launcher.setParameter("stand-alone", true); // Show the quit button. This often doesn't seem to work. launcher.setParameter("haspaid", true); // Some old versions need this for world saves to work. launcher.setParameter("demo", demo); launcher.setParameter("fullscreen", false); diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyLauncher.java b/libraries/launcher/legacy/org/prismlauncher/legacy/LegacyLauncher.java similarity index 75% rename from libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyLauncher.java rename to libraries/launcher/legacy/org/prismlauncher/legacy/LegacyLauncher.java index 5c5f86d47..02f77e039 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/legacy/LegacyLauncher.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/LegacyLauncher.java @@ -53,23 +53,27 @@ * limitations under the License. */ -package org.prismlauncher.launcher.impl.legacy; +package org.prismlauncher.legacy; import org.prismlauncher.launcher.impl.AbstractLauncher; import org.prismlauncher.utils.Parameters; import org.prismlauncher.utils.ReflectionUtils; import org.prismlauncher.utils.logging.Log; +import java.applet.Applet; import java.io.File; import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.Collections; import java.util.List; /** - * Used to launch old versions that support applets. + * Used to launch old versions which support applets. */ -public final class LegacyLauncher extends AbstractLauncher { +final class LegacyLauncher extends AbstractLauncher { private final String user, session; private final String title; private final String appletClass; @@ -93,11 +97,9 @@ public final class LegacyLauncher extends AbstractLauncher { @Override public void launch() throws Throwable { Class main = ClassLoader.getSystemClassLoader().loadClass(mainClassName); - Field gameDirField = ReflectionUtils.findMinecraftGameDirField(main); + Field gameDirField = findMinecraftGameDirField(main); - if (gameDirField == null) - Log.warning("Could not find Minecraft folder field"); - else { + if (gameDirField != null) { gameDirField.setAccessible(true); gameDirField.set(null, new File(gameDir)); } @@ -106,7 +108,7 @@ public final class LegacyLauncher extends AbstractLauncher { System.setProperty("minecraft.applet.TargetDirectory", gameDir); try { - LegacyFrame window = new LegacyFrame(title, ReflectionUtils.createAppletClass(appletClass)); + LegacyFrame window = new LegacyFrame(title, createAppletClass(appletClass)); window.start(user, session, width, height, maximize, serverAddress, serverPort, gameArgs.contains("--demo")); return; @@ -115,9 +117,39 @@ public final class LegacyLauncher extends AbstractLauncher { } } - // find and invoke the main method, this time without size parameters - // in all versions that support applets, these are ignored + // find and invoke the main method, this time without size parameters - in all + // versions that support applets, these are ignored MethodHandle method = ReflectionUtils.findMainMethod(main); method.invokeExact(gameArgs.toArray(new String[0])); } + + private static Applet createAppletClass(String clazz) throws Throwable { + Class appletClass = ClassLoader.getSystemClassLoader().loadClass(clazz); + + MethodHandle appletConstructor = MethodHandles.lookup().findConstructor(appletClass, MethodType.methodType(void.class)); + return (Applet) appletConstructor.invoke(); + } + + private static Field findMinecraftGameDirField(Class clazz) { + // search for private static File + for (Field field : clazz.getDeclaredFields()) { + if (field.getType() != File.class) + continue; + + int fieldModifiers = field.getModifiers(); + + if (!Modifier.isStatic(fieldModifiers)) + continue; + + if (!Modifier.isPrivate(fieldModifiers)) + continue; + + if (Modifier.isFinal(fieldModifiers)) + continue; + + return field; + } + + return null; + } } diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/LegacyProxy.java b/libraries/launcher/legacy/org/prismlauncher/legacy/LegacyProxy.java new file mode 100644 index 000000000..4c5c28c52 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/LegacyProxy.java @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.prismlauncher.legacy; + +import org.prismlauncher.launcher.Launcher; +import org.prismlauncher.legacy.fix.online.OnlineFixes; +import org.prismlauncher.utils.Parameters; + +// implementation of LegacyProxy +public final class LegacyProxy { + public static Launcher createLauncher(Parameters params) { + return new LegacyLauncher(params); + } + + public static void applyOnlineFixes(Parameters parameters) { + OnlineFixes.apply(parameters); + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/Handler.java b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/Handler.java new file mode 100644 index 000000000..f85a8bc79 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/Handler.java @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.legacy.fix.online; + +import org.prismlauncher.legacy.utils.url.UrlUtils; + +import java.io.IOException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +final class Handler extends URLStreamHandler { + @Override + protected URLConnection openConnection(URL address) throws IOException { + return openConnection(address, null); + } + + @Override + protected URLConnection openConnection(URL address, Proxy proxy) throws IOException { + URLConnection result; + + // try skin fix + result = SkinFix.openConnection(address, proxy); + if (result != null) + return result; + + return UrlUtils.openConnection(address, proxy); + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java new file mode 100644 index 000000000..88facff69 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/OnlineFixes.java @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.legacy.fix.online; + +import org.prismlauncher.legacy.utils.Base64; +import org.prismlauncher.legacy.utils.url.UrlUtils; +import org.prismlauncher.utils.Parameters; +import org.prismlauncher.utils.logging.Log; + +import java.net.URL; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; + +/** + * Fixes skins by redirecting to other URLs. + * + * @see {@link Handler} + * @see {@link UrlUtils} + */ +public final class OnlineFixes implements URLStreamHandlerFactory { + public static void apply(Parameters params) { + if (!"true".equals(params.getString("onlineFixes", null))) + return; + + if (!UrlUtils.isSupported() || !Base64.isSupported()) { + Log.warning("Cannot access the necessary Java internals for skin fix"); + Log.warning("Turning off online fixes in the settings will silence the warnings"); + return; + } + + try { + URL.setURLStreamHandlerFactory(new OnlineFixes()); + } catch (Error e) { + Log.warning("Cannot apply skin fix: URLStreamHandlerFactory is already set"); + Log.warning("Turning off online fixes in the settings will silence the warnings"); + } + } + + @Override + public URLStreamHandler createURLStreamHandler(String protocol) { + if ("http".equals(protocol)) + return new Handler(); + + return null; + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java new file mode 100644 index 000000000..e734bdbc7 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/fix/online/SkinFix.java @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.prismlauncher.legacy.fix.online; + +import org.prismlauncher.legacy.utils.api.MojangApi; +import org.prismlauncher.legacy.utils.api.Texture; +import org.prismlauncher.legacy.utils.url.CustomUrlConnection; +import org.prismlauncher.legacy.utils.url.UrlUtils; + +import java.awt.AlphaComposite; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; + +import javax.imageio.ImageIO; + +final class SkinFix { + static URLConnection openConnection(URL address, Proxy proxy) throws IOException { + String skinOwner = findSkinOwner(address); + if (skinOwner != null) + // we need to correct the skin + return getSkinConnection(skinOwner, proxy); + + String capeOwner = findCapeOwner(address); + 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"); + if (texture == null) + return null; + + return UrlUtils.openConnection(texture.getUrl(), proxy); + } + + return null; + } + + private static URLConnection getSkinConnection(String owner, Proxy proxy) throws IOException { + Texture texture = MojangApi.getTexture(MojangApi.getUuid(owner), "SKIN"); + if (texture == null) + return null; + + URLConnection connection = UrlUtils.openConnection(texture.getUrl(), proxy); + try (InputStream in = connection.getInputStream()) { + // thank you craftycodie! + // this is heavily based on + // https://github.com/craftycodie/MineOnline/blob/4f4f86f9d051e0a6fd7ff0b95b2a05f7437683d7/src/main/java/gg/codie/mineonline/gui/textures/TextureHelper.java#L17 + BufferedImage image = ImageIO.read(in); + Graphics2D graphics = image.createGraphics(); + graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); + + BufferedImage subimage; + + if (image.getHeight() > 32) { + // flatten second layers + subimage = image.getSubimage(0, 32, 56, 16); + graphics.drawImage(subimage, 0, 16, null); + } + + if (texture.isSlim()) { + // convert slim to classic + subimage = image.getSubimage(45, 16, 9, 16); + graphics.drawImage(subimage, 46, 16, null); + + subimage = image.getSubimage(49, 16, 2, 4); + graphics.drawImage(subimage, 50, 16, null); + + subimage = image.getSubimage(53, 20, 2, 12); + graphics.drawImage(subimage, 54, 20, null); + } + + graphics.dispose(); + + // crop the image - old versions disregard all secondary layers besides the hat + ByteArrayOutputStream out = new ByteArrayOutputStream(); + image = image.getSubimage(0, 0, 64, 32); + ImageIO.write(image, "png", out); + + return new CustomUrlConnection(out.toByteArray()); + } + } + + private static String findSkinOwner(URL address) { + switch (address.getHost()) { + case "www.minecraft.net": + return stripIfPrefixed(address.getPath(), "/skin/"); + + case "s3.amazonaws.com": + case "skins.minecraft.net": + return stripIfPrefixed(address.getPath(), "/MinecraftSkins/"); + } + + return null; + } + + private static String findCapeOwner(URL address) { + switch (address.getHost()) { + case "www.minecraft.net": + if (!address.getPath().equals("/cloak/get.jsp")) + return null; + + return stripIfPrefixed(address.getQuery(), "user="); + + case "s3.amazonaws.com": + case "skins.minecraft.net": + return stripIfPrefixed(address.getPath(), "/MinecraftCloaks/"); + } + + return null; + } + + private static String stripIfPrefixed(String string, String prefix) { + if (string != null && string.startsWith(prefix)) { + string = string.substring(prefix.length()); + + if (string.endsWith(".png")) + string = string.substring(0, string.lastIndexOf('.')); + + return string; + } + + return null; + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/Base64.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/Base64.java new file mode 100644 index 000000000..a7076f21f --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/Base64.java @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.legacy.utils; + +import org.prismlauncher.utils.logging.Log; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.nio.charset.StandardCharsets; + +/** + * Uses Base64 with Java 8 or later, otherwise DatatypeConverter. In the latter + * case, reflection is used to allow using newer compilers. + */ +public final class Base64 { + private static boolean supported = true; + private static MethodHandle legacy; + + static { + try { + Class.forName("java.util.Base64"); + } catch (ClassNotFoundException e) { + try { + Class datatypeConverter = Class.forName("javax.xml.bind.DatatypeConverter"); + legacy = MethodHandles.lookup().findStatic( + datatypeConverter, "parseBase64Binary", MethodType.methodType(byte[].class, String.class)); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e1) { + Log.error("Base64 not supported", e1); + supported = false; + } + } + } + + /** + * Determines whether base64 is supported. + * + * @return true if base64 can be parsed + */ + public static boolean isSupported() { + return supported; + } + + public static byte[] decode(String input) { + if (!isSupported()) + throw new UnsupportedOperationException(); + + if (legacy == null) + return java.util.Base64.getDecoder().decode(input.getBytes(StandardCharsets.UTF_8)); + + try { + return (byte[]) legacy.invokeExact(input); + } catch (Error | RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new Error(e); + } + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java new file mode 100644 index 000000000..41f7f9114 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * 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; + +import org.prismlauncher.legacy.utils.Base64; +import org.prismlauncher.legacy.utils.json.JsonParser; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Map; + +/** + * Basic wrapper for Mojang's Minecraft API. + */ +@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 Texture getTexture(String player, String id) throws IOException { + Map map = getTextures(player); + + if (map != null) { + map = (Map) map.get(id); + if (map == null) + return null; + + URL url = new URL((String) map.get("url")); + boolean slim = false; + + if (id.equals("SKIN")) { + map = (Map) map.get("metadata"); + if (map != null && "slim".equals(map.get("model"))) + slim = true; + } + + return new Texture(url, slim); + } + + return null; + } + + public static Map getTextures(String player) throws IOException { + try (InputStream profileIn = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + player).openStream()) { + Map profile = (Map) JsonParser.parse(profileIn); + + for (Map property : (Iterable>) profile.get("properties")) { + if (property.get("name").equals("textures")) { + Map result = + (Map) JsonParser.parse(new String(Base64.decode((String) property.get("value")))); + result = (Map) result.get("textures"); + + return result; + } + } + + return null; + } + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/Texture.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/Texture.java new file mode 100644 index 000000000..094b08b98 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/Texture.java @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * 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; + +import java.net.URL; + +/** + * Represents a texture from the Mojang API. + */ +public final class Texture { + private final URL url; + private final boolean slim; + + public Texture(URL url, boolean slim) { + this.url = url; + this.slim = slim; + } + + public URL getUrl() { + return url; + } + + public boolean isSlim() { + return slim; + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/json/JsonParseException.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/json/JsonParseException.java new file mode 100644 index 000000000..a43876c55 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/json/JsonParseException.java @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.legacy.utils.json; + +import java.io.IOException; + +public final class JsonParseException extends IOException { + private static final long serialVersionUID = 1L; + + public JsonParseException(String message) { + super(message); + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/json/JsonParser.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/json/JsonParser.java new file mode 100644 index 000000000..9ce24de13 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/json/JsonParser.java @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.legacy.utils.json; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A lightweight portable JSON parser used instead of GSON since it is not + * available in a lot of versions. + */ +public final class JsonParser { + private final Reader in; + private char[] buffer; + private int pos, length; + + public static Object parse(String in) throws IOException { + return parse(new StringReader(in)); + } + + public static Object parse(InputStream in) throws IOException { + return parse(new InputStreamReader(in, StandardCharsets.UTF_8)); + } + + public static Object parse(Reader in) throws IOException { + return new JsonParser(in).readSingleValue(); + } + + private JsonParser(Reader in) throws IOException { + this.in = in; + pos = length = 0; + read(); + } + + private int character() { + if (length == -1) + return -1; + + return buffer[pos]; + } + + private int read() throws IOException { + if (length == -1) + return -1; + + if (buffer == null || pos++ == length - 1) { + pos = 0; + buffer = new char[8192]; + length = in.read(buffer); + } + + return character(); + } + + private void assertCharacter(char character) throws JsonParseException { + if (character() != character) + throw new JsonParseException( + "Expected '" + character + "' but got " + (character() != -1 ? ("'" + (char) character() + "'") : "EOF")); + } + + private void assertNoEOF(String expected) throws JsonParseException { + if (character() == -1) + throw new JsonParseException("Expected " + expected + " but got EOF"); + } + + private void skipWhitespace() throws IOException { + while (isWhitespace()) read(); + } + + private boolean isWhitespace() { + return character() == ' ' || character() == '\n' || character() == '\r' || character() == '\t'; + } + + private Object readSingleValue() throws IOException { + skipWhitespace(); + Object result = readValue(); + + if (!(result instanceof Double)) + read(); + + skipWhitespace(); + + if (character() != -1) + throw new JsonParseException("Found trailing non-whitespace characters"); + + return result; + } + + private Object readValue() throws IOException { + assertNoEOF("a value"); + + int character = character(); + + switch (character) { + case '{': + return readObject(); + + case '[': + return readArray(); + + case '"': + return readString(); + + case 't': + case 'f': + // probably boolean + Boolean bool = readBoolean(); + if (bool != null) + return bool; + + break; + + case 'n': + // probably null + if (readNull()) + return null; + + break; + } + + if (character == '-' || isDigit()) + // probably a number + return readNumber(); + + throw new JsonParseException("Expected a JSON value but got '" + (char) character + "'"); + } + + private Map readObject() throws IOException { + assertCharacter('{'); + Map obj = new HashMap<>(); + boolean comma = false; + + read(); + skipWhitespace(); + + while (character() != '}') { + if (comma) { + assertCharacter(','); + read(); + skipWhitespace(); + } + + String key = readString(); + read(); + skipWhitespace(); + assertCharacter(':'); + read(); + skipWhitespace(); + + Object value = readValue(); + obj.put(key, value); + + if (!(value instanceof Double)) + read(); + + skipWhitespace(); + comma = true; + } + + return obj; + } + + private List readArray() throws IOException { + assertCharacter('['); + List array = new ArrayList<>(); + boolean comma = false; + + read(); + skipWhitespace(); + + while (character() != ']') { + if (comma) { + assertCharacter(','); + read(); + skipWhitespace(); + } + + Object value = readValue(); + array.add(value); + + if (!(value instanceof Double)) + read(); + + skipWhitespace(); + comma = true; + } + + return array; + } + + private String readString() throws IOException { + assertCharacter('"'); + + StringBuilder result = new StringBuilder(); + + while (read() != '"') { + int character = character(); + + if (character >= '\u0000' && character <= '\u001F') + throw new JsonParseException("Found unescaped control character within string"); + + switch (character) { + case -1: + throw new JsonParseException("Expected '\"' but got EOF"); + + case 0x7F: + if (read() == '"') { + return result.toString(); + } + continue; + + case '\\': + int seq = read(); + + switch (seq) { + case -1: + throw new JsonParseException("Expected an escape sequence but got EOF"); + + case '\\': + break; + + case '/': + case '\"': + character = seq; + break; + + case 'b': + character = '\b'; + break; + + case 'f': + character = '\f'; + break; + + case 'n': + character = '\n'; + break; + + case 'r': + character = '\r'; + break; + + case 't': + character = '\t'; + break; + + case 'u': + // char array to allow allocation in advance. + char[] digits = new char[4]; + + for (int index = 0; index < digits.length; index++) { + character = read(); + if (index == 0 && character() == '-') { + throw new JsonParseException("Hex sequence may not be negative"); + } else if (character() == -1) { + throw new JsonParseException("Expected a hex sequence but got EOF"); + } + digits[index] = (char) character; + } + + String digitsString = new String(digits); + + try { + character = Integer.parseInt(digitsString, 16); + } catch (NumberFormatException e) { + throw new JsonParseException("Could not parse hex sequence \"" + digitsString + "\""); + } + + break; + default: + throw new JsonParseException("Invalid escape sequence: \\" + (char) seq); + } + break; + } + + result.append((char) character); + } + + return result.toString(); + } + + private boolean isDigit() { + return character() >= '0' && character() <= '9'; + } + + private Double readNumber() throws IOException { + StringBuilder result = new StringBuilder(); + + if (character() == '-') { + result.append((char) character()); + read(); + } + + if (character() == '0') { + result.append((char) character()); + read(); + + if (isDigit()) + throw new JsonParseException("Found superfluous leading zero"); + } else if (!isDigit()) + throw new JsonParseException("Expected digits"); + + while (character() != -1 && isDigit()) { + result.append((char) character()); + read(); + } + + if (character() == '.') { + result.append('.'); + + read(); + assertNoEOF("digits"); + + if (!isDigit()) + throw new JsonParseException("Expected digits after decimal point"); + + while (character() != -1 && isDigit()) { + result.append((char) character()); + read(); + } + } + + if (character() == 'e' || character() == 'E') { + result.append('E'); + + read(); + assertNoEOF("digits"); + + if (character() == '+' || character() == '-') { + result.append((char) character()); + read(); + } + + if (!(character() == '+' || character() == '-' || isDigit())) + throw new JsonParseException("Expected exponent digits"); + + while (character() != -1 && isDigit()) { + result.append((char) character()); + read(); + } + } + + String resultStr = result.toString(); + + try { + return Double.parseDouble(resultStr); + } catch (NumberFormatException e) { + throw new JsonParseException("Failed to parse number '" + resultStr + "'"); + } + } + + private Boolean readBoolean() throws IOException { + if (character() == 't') { + if (read() == 'r' && read() == 'u' && read() == 'e') { + return true; + } + } else if (character() == 'f' && read() == 'a' && read() == 'l' && read() == 's' && read() == 'e') { + return false; + } + + return null; + } + + private boolean readNull() throws IOException { + return character() == 'n' && read() == 'u' && read() == 'l' && read() == 'l'; + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/CustomUrlConnection.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/CustomUrlConnection.java new file mode 100644 index 000000000..71b0e68f2 --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/CustomUrlConnection.java @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.legacy.utils.url; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; + +public final class CustomUrlConnection extends HttpURLConnection { + private final InputStream in; + + public CustomUrlConnection(byte[] data) { + this(new ByteArrayInputStream(data)); + } + + public CustomUrlConnection(InputStream in) { + super(null); + this.in = in; + } + + @Override + public void connect() throws IOException { + responseCode = 200; + } + + @Override + public void disconnect() { + try { + in.close(); + } catch (IOException e) { + } + } + + @Override + public InputStream getInputStream() throws IOException { + return in; + } + + @Override + public boolean usingProxy() { + return false; + } +} diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java new file mode 100644 index 000000000..b0072485e --- /dev/null +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/url/UrlUtils.java @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.legacy.utils.url; + +import org.prismlauncher.utils.logging.Log; + +import java.io.IOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +/** + * A utility class for URLs which uses reflection to access constructors for + * internal classes. + */ +public final class UrlUtils { + private static URLStreamHandler http; + private static MethodHandle openConnection; + + static { + try { + // we first obtain the stock URLStreamHandler for http as we overwrite it later + Method getURLStreamHandler = URL.class.getDeclaredMethod("getURLStreamHandler", String.class); + getURLStreamHandler.setAccessible(true); + http = (URLStreamHandler) getURLStreamHandler.invoke(null, "http"); + + // we next find the openConnection method + Method openConnectionReflect = URLStreamHandler.class.getDeclaredMethod("openConnection", URL.class, Proxy.class); + openConnectionReflect.setAccessible(true); + openConnection = MethodHandles.lookup().unreflect(openConnectionReflect); + } catch (Throwable e) { + Log.error("URL reflection failed - some features may not work", e); + } + } + + /** + * Determines whether all the features of this class are available. + * + * @return true if all features can be used + */ + public static boolean isSupported() { + return http != null && openConnection != null; + } + + public static URLConnection openConnection(URL url, Proxy proxy) throws IOException { + if (http == null) + throw new UnsupportedOperationException(); + + if (url.getProtocol().equals("http")) + return openConnection(http, url, proxy); + + // fall back to Java's default method + // at this point, this should not cause a StackOverflowError unless we've missed + // a protocol out from the if statements + return url.openConnection(); + } + + public static URLConnection openConnection(URLStreamHandler handler, URL url, Proxy proxy) throws IOException { + if (openConnection == null) + throw new UnsupportedOperationException(); + + try { + return (URLConnection) openConnection.invokeExact(handler, url, proxy); + } catch (IOException | Error | RuntimeException e) { + throw e; // rethrow if possible + } catch (Throwable e) { + throw new AssertionError(e); // oh dear! this isn't meant to happen + } + } +} diff --git a/libraries/launcher/org/prismlauncher/EntryPoint.java b/libraries/launcher/org/prismlauncher/EntryPoint.java index 4b59c1da6..3ef161b40 100644 --- a/libraries/launcher/org/prismlauncher/EntryPoint.java +++ b/libraries/launcher/org/prismlauncher/EntryPoint.java @@ -57,7 +57,7 @@ package org.prismlauncher; import org.prismlauncher.exception.ParseException; import org.prismlauncher.launcher.Launcher; import org.prismlauncher.launcher.impl.StandardLauncher; -import org.prismlauncher.launcher.impl.legacy.LegacyLauncher; +import org.prismlauncher.legacy.LegacyProxy; import org.prismlauncher.utils.Parameters; import org.prismlauncher.utils.logging.Log; @@ -106,6 +106,8 @@ public final class EntryPoint { } try { + LegacyProxy.applyOnlineFixes(params); + Launcher launcher; String type = params.getString("launcher"); @@ -115,7 +117,7 @@ public final class EntryPoint { break; case "legacy": - launcher = new LegacyLauncher(params); + launcher = LegacyProxy.createLauncher(params); break; default: diff --git a/libraries/launcher/org/prismlauncher/legacy/LegacyProxy.java b/libraries/launcher/org/prismlauncher/legacy/LegacyProxy.java new file mode 100644 index 000000000..133558c9a --- /dev/null +++ b/libraries/launcher/org/prismlauncher/legacy/LegacyProxy.java @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * 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. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.prismlauncher.legacy; + +import org.prismlauncher.launcher.Launcher; +import org.prismlauncher.utils.Parameters; + +// used as a fallback if NewLaunchLegacy is not on the classpath +// if it is, this class will be replaced +public final class LegacyProxy { + public static Launcher createLauncher(Parameters params) { + throw new AssertionError("NewLaunchLegacy is not loaded"); + } + + public static void applyOnlineFixes(Parameters params) {} +} diff --git a/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java b/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java index ad222bd2a..3b2bfd9bb 100644 --- a/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java +++ b/libraries/launcher/org/prismlauncher/utils/ReflectionUtils.java @@ -68,61 +68,6 @@ public final class ReflectionUtils { private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final ClassLoader LOADER = ClassLoader.getSystemClassLoader(); - /** - * Construct a Java applet by its class name. - * - * @param clazz The class name - * @return The applet instance - * @throws Throwable - */ - public static Applet createAppletClass(String clazz) throws Throwable { - Class appletClass = LOADER.loadClass(clazz); - - MethodHandle appletConstructor = LOOKUP.findConstructor(appletClass, MethodType.methodType(void.class)); - return (Applet) appletConstructor.invoke(); - } - - /** - * Best guess of the game directory field within net.minecraft.client.Minecraft. - * Designed for legacy versions - newer versions do not use a static field. - * - * @param clazz The class - * @return The first field matching criteria - */ - public static Field findMinecraftGameDirField(Class clazz) { - Log.debug("Resolving minecraft game directory field"); - - // search for private static File - for (Field field : clazz.getDeclaredFields()) { - if (field.getType() != File.class) { - continue; - } - - int fieldModifiers = field.getModifiers(); - - if (!Modifier.isStatic(fieldModifiers)) { - Log.debug("Rejecting field " + field.getName() + " because it is not static"); - continue; - } - - if (!Modifier.isPrivate(fieldModifiers)) { - Log.debug("Rejecting field " + field.getName() + " because it is not private"); - continue; - } - - if (Modifier.isFinal(fieldModifiers)) { - Log.debug("Rejecting field " + field.getName() + " because it is final"); - continue; - } - - Log.debug("Identified field " + field.getName() + " to match conditions for game directory field"); - - return field; - } - - return null; - } - /** * Gets the main method within a class. *