Merge pull request #443 from TheKodeToad/skinfix

Add legacy skin fix
This commit is contained in:
TheKodeToad 2023-10-15 21:46:29 +01:00 committed by GitHub
commit c4882e7162
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1520 additions and 84 deletions

View File

@ -582,6 +582,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IgnoreJavaCompatibility", false); m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false); m_settings->registerSetting("IgnoreJavaWizard", false);
// Legacy settings
m_settings->registerSetting("OnlineFixes", false);
// Native library workarounds // Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false); m_settings->registerSetting("UseNativeOpenAL", false);
m_settings->registerSetting("CustomOpenALPath", ""); m_settings->registerSetting("CustomOpenALPath", "");

View File

@ -45,10 +45,12 @@ QString JavaVersion::toString() const
bool JavaVersion::requiresPermGen() bool JavaVersion::requiresPermGen()
{ {
if (m_parseable) { return !m_parseable || m_major < 8;
return m_major < 8; }
}
return true; bool JavaVersion::isModular()
{
return m_parseable && m_major >= 9;
} }
bool JavaVersion::operator<(const JavaVersion& rhs) bool JavaVersion::operator<(const JavaVersion& rhs)

View File

@ -25,6 +25,8 @@ class JavaVersion {
bool requiresPermGen(); bool requiresPermGen();
bool isModular();
QString toString() const; QString toString() const;
int major() { return m_major; } int major() { return m_major; }

View File

@ -184,6 +184,10 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride); m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), 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"); m_settings->set("InstanceType", "OneSix");
} }
@ -513,20 +517,28 @@ QStringList MinecraftInstance::javaArguments()
args << "-Duser.language=en"; 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; return args;
} }
QString MinecraftInstance::getLauncher() QString MinecraftInstance::getLauncher()
{ {
auto profile = m_components->getProfile();
// use legacy launcher if the traits are set // 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 "legacy";
return "standard"; return "standard";
} }
bool MinecraftInstance::shouldApplyOnlineFixes()
{
return traits().contains("legacyServices") && settings()->get("OnlineFixes").toBool();
}
QMap<QString, QString> MinecraftInstance::getVariables() QMap<QString, QString> MinecraftInstance::getVariables()
{ {
QMap<QString, QString> out; QMap<QString, QString> out;
@ -716,6 +728,9 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
launchScript += "traits " + trait + "\n"; launchScript += "traits " + trait + "\n";
} }
if (shouldApplyOnlineFixes())
launchScript += "onlineFixes true\n";
launchScript += "launcher " + getLauncher() + "\n"; launchScript += "launcher " + getLauncher() + "\n";
// qDebug() << "Generated launch script:" << launchScript; // qDebug() << "Generated launch script:" << launchScript;

View File

@ -129,6 +129,7 @@ class MinecraftInstance : public BaseInstance {
/// get arguments passed to java /// get arguments passed to java
QStringList javaArguments(); QStringList javaArguments();
QString getLauncher(); QString getLauncher();
bool shouldApplyOnlineFixes();
/// get variables for launch command variable substitution/environment /// get variables for launch command variable substitution/environment
QMap<QString, QString> getVariables() override; QMap<QString, QString> getVariables() override;

View File

@ -105,6 +105,17 @@ void LauncherPartLaunch::executeTask()
auto instance = m_parent->instance(); auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance); std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(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); m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin);
QStringList args = minecraftInstance->javaArguments(); QStringList args = minecraftInstance->javaArguments();
QString allArgs = args.join(", "); QString allArgs = args.join(", ");
@ -120,6 +131,9 @@ void LauncherPartLaunch::executeTask()
auto classPath = minecraftInstance->getClassPath(); auto classPath = minecraftInstance->getClassPath();
classPath.prepend(jarPath); classPath.prepend(jarPath);
if (!legacyJarPath.isEmpty())
classPath.prepend(legacyJarPath);
auto natPath = minecraftInstance->getNativePath(); auto natPath = minecraftInstance->getNativePath();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (!fitsInLocal8bit(natPath)) { if (!fitsInLocal8bit(natPath)) {

View File

@ -119,6 +119,9 @@ void MinecraftPage::applySettings()
// Miscellaneous // Miscellaneous
s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
// Legacy settings
s->set("OnlineFixes", ui->onlineFixes->isChecked());
} }
void MinecraftPage::loadSettings() void MinecraftPage::loadSettings()
@ -170,6 +173,8 @@ void MinecraftPage::loadSettings()
ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool());
ui->onlineFixes->setChecked(s->get("OnlineFixes").toBool());
} }
void MinecraftPage::retranslate() void MinecraftPage::retranslate()

View File

@ -138,7 +138,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="showGameTimeWithoutDays"> <widget class="QCheckBox" name="showGameTimeWithoutDays">
<property name="text"> <property name="text">
<string>Show time spent playing in hours</string> <string>Show time spent playing in hours</string>
@ -197,6 +197,25 @@
<string>Tweaks</string> <string>Tweaks</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_12"> <layout class="QVBoxLayout" name="verticalLayout_12">
<item>
<widget class="QGroupBox" name="legacySettingsGroupBox">
<property name="title">
<string>Legacy settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="onlineFixes">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;This currently allows modern skins to be used.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable online fixes (experimental)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox"> <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
<property name="title"> <property name="title">

View File

@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -253,6 +254,14 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("InstanceAccountId"); 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 // FIXME: This should probably be called by a signal instead
m_instance->updateRuntimeContext(); m_instance->updateRuntimeContext();
} }
@ -356,6 +365,9 @@ void InstanceSettingsPage::loadSettings()
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool()); ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
updateAccountsMenu(); updateAccountsMenu();
ui->legacySettingsGroupBox->setChecked(m_settings->get("OverrideLegacySettings").toBool());
ui->onlineFixes->setChecked(m_settings->get("OnlineFixes").toBool());
} }
void InstanceSettingsPage::on_javaDetectBtn_clicked() void InstanceSettingsPage::on_javaDetectBtn_clicked()

View File

@ -583,6 +583,31 @@
<string>Miscellaneous</string> <string>Miscellaneous</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_9"> <layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QGroupBox" name="legacySettingsGroupBox">
<property name="title">
<string>Legacy settings</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_17">
<item>
<widget class="QCheckBox" name="onlineFixes">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;This currently allows modern skins to be used.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable online fixes (experimental)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="gameTimeGroupBox"> <widget class="QGroupBox" name="gameTimeGroupBox">
<property name="enabled"> <property name="enabled">

View File

@ -11,15 +11,33 @@ set(SRC
org/prismlauncher/launcher/Launcher.java org/prismlauncher/launcher/Launcher.java
org/prismlauncher/launcher/impl/AbstractLauncher.java org/prismlauncher/launcher/impl/AbstractLauncher.java
org/prismlauncher/launcher/impl/StandardLauncher.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/ParameterNotFoundException.java
org/prismlauncher/exception/ParseException.java org/prismlauncher/exception/ParseException.java
org/prismlauncher/utils/Parameters.java org/prismlauncher/utils/Parameters.java
org/prismlauncher/utils/ReflectionUtils.java org/prismlauncher/utils/ReflectionUtils.java
org/prismlauncher/utils/logging/Level.java org/prismlauncher/utils/logging/Level.java
org/prismlauncher/utils/logging/Log.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(NewLaunch ${SRC})
add_jar(NewLaunchLegacy ${LEGACY_SRC} INCLUDE_JARS NewLaunch)
install_jar(NewLaunch "${JARS_DEST_DIR}") install_jar(NewLaunch "${JARS_DEST_DIR}")
install_jar(NewLaunchLegacy "${JARS_DEST_DIR}")

View File

@ -92,12 +92,11 @@ public final class Launcher extends Applet implements AppletStub {
try { try {
if (documentBase == null) { 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 // Special case only for Classic versions
documentBase = new URL("http://www.minecraft.net:80/game/"); documentBase = new URL("http://www.minecraft.net:80/game/");
} else { else
documentBase = new URL("http://www.minecraft.net/game/"); documentBase = new URL("http://www.minecraft.net/game/");
}
} }
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
throw new AssertionError(e); throw new AssertionError(e);

View File

@ -52,7 +52,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.prismlauncher.launcher.impl.legacy; package org.prismlauncher.legacy;
import org.prismlauncher.utils.logging.Log; import org.prismlauncher.utils.logging.Log;
@ -74,7 +74,7 @@ import javax.swing.JFrame;
import net.minecraft.Launcher; import net.minecraft.Launcher;
public final class LegacyFrame extends JFrame { final class LegacyFrame extends JFrame {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final Launcher launcher; private final Launcher launcher;
@ -130,7 +130,7 @@ public final class LegacyFrame extends JFrame {
launcher.setParameter("username", user); launcher.setParameter("username", user);
launcher.setParameter("sessionid", session); 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("haspaid", true); // Some old versions need this for world saves to work.
launcher.setParameter("demo", demo); launcher.setParameter("demo", demo);
launcher.setParameter("fullscreen", false); launcher.setParameter("fullscreen", false);

View File

@ -53,23 +53,27 @@
* limitations under the License. * limitations under the License.
*/ */
package org.prismlauncher.launcher.impl.legacy; package org.prismlauncher.legacy;
import org.prismlauncher.launcher.impl.AbstractLauncher; import org.prismlauncher.launcher.impl.AbstractLauncher;
import org.prismlauncher.utils.Parameters; import org.prismlauncher.utils.Parameters;
import org.prismlauncher.utils.ReflectionUtils; import org.prismlauncher.utils.ReflectionUtils;
import org.prismlauncher.utils.logging.Log; import org.prismlauncher.utils.logging.Log;
import java.applet.Applet;
import java.io.File; import java.io.File;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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 user, session;
private final String title; private final String title;
private final String appletClass; private final String appletClass;
@ -93,11 +97,9 @@ public final class LegacyLauncher extends AbstractLauncher {
@Override @Override
public void launch() throws Throwable { public void launch() throws Throwable {
Class<?> main = ClassLoader.getSystemClassLoader().loadClass(mainClassName); Class<?> main = ClassLoader.getSystemClassLoader().loadClass(mainClassName);
Field gameDirField = ReflectionUtils.findMinecraftGameDirField(main); Field gameDirField = findMinecraftGameDirField(main);
if (gameDirField == null) if (gameDirField != null) {
Log.warning("Could not find Minecraft folder field");
else {
gameDirField.setAccessible(true); gameDirField.setAccessible(true);
gameDirField.set(null, new File(gameDir)); gameDirField.set(null, new File(gameDir));
} }
@ -106,7 +108,7 @@ public final class LegacyLauncher extends AbstractLauncher {
System.setProperty("minecraft.applet.TargetDirectory", gameDir); System.setProperty("minecraft.applet.TargetDirectory", gameDir);
try { 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")); window.start(user, session, width, height, maximize, serverAddress, serverPort, gameArgs.contains("--demo"));
return; return;
@ -115,9 +117,39 @@ public final class LegacyLauncher extends AbstractLauncher {
} }
} }
// find and invoke the main method, this time without size parameters // find and invoke the main method, this time without size parameters - in all
// in all versions that support applets, these are ignored // versions that support applets, these are ignored
MethodHandle method = ReflectionUtils.findMainMethod(main); MethodHandle method = ReflectionUtils.findMainMethod(main);
method.invokeExact(gameArgs.toArray(new String[0])); 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;
}
} }

View File

@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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);
}
}

View File

@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,179 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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;
}
}

View File

@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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 <code>true</code> 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);
}
}
}

View File

@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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<String, Object> map = (Map<String, Object>) JsonParser.parse(in);
return (String) map.get("id");
}
}
public static Texture getTexture(String player, String id) throws IOException {
Map<String, Object> map = getTextures(player);
if (map != null) {
map = (Map<String, Object>) 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<String, Object>) map.get("metadata");
if (map != null && "slim".equals(map.get("model")))
slim = true;
}
return new Texture(url, slim);
}
return null;
}
public static Map<String, Object> getTextures(String player) throws IOException {
try (InputStream profileIn = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + player).openStream()) {
Map<String, Object> profile = (Map<String, Object>) JsonParser.parse(profileIn);
for (Map<String, Object> property : (Iterable<Map<String, Object>>) profile.get("properties")) {
if (property.get("name").equals("textures")) {
Map<String, Object> result =
(Map<String, Object>) JsonParser.parse(new String(Base64.decode((String) property.get("value"))));
result = (Map<String, Object>) result.get("textures");
return result;
}
}
return null;
}
}
}

View File

@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,408 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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<String, Object> readObject() throws IOException {
assertCharacter('{');
Map<String, Object> 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<Object> readArray() throws IOException {
assertCharacter('[');
List<Object> 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';
}
}

View File

@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,107 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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 <code>true</code> 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
}
}
}

View File

@ -57,7 +57,7 @@ package org.prismlauncher;
import org.prismlauncher.exception.ParseException; import org.prismlauncher.exception.ParseException;
import org.prismlauncher.launcher.Launcher; import org.prismlauncher.launcher.Launcher;
import org.prismlauncher.launcher.impl.StandardLauncher; 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.Parameters;
import org.prismlauncher.utils.logging.Log; import org.prismlauncher.utils.logging.Log;
@ -106,6 +106,8 @@ public final class EntryPoint {
} }
try { try {
LegacyProxy.applyOnlineFixes(params);
Launcher launcher; Launcher launcher;
String type = params.getString("launcher"); String type = params.getString("launcher");
@ -115,7 +117,7 @@ public final class EntryPoint {
break; break;
case "legacy": case "legacy":
launcher = new LegacyLauncher(params); launcher = LegacyProxy.createLauncher(params);
break; break;
default: default:

View File

@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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) {}
}

View File

@ -68,61 +68,6 @@ public final class ReflectionUtils {
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final ClassLoader LOADER = ClassLoader.getSystemClassLoader(); 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. * Gets the main method within a class.
* *