Move legacy support classes to another jar

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
TheKodeToad 2023-01-06 09:21:09 +00:00
parent cb32711077
commit 17317ea308
23 changed files with 150 additions and 161 deletions

View File

@ -440,7 +440,7 @@ QStringList MinecraftInstance::javaArguments()
args << "-Duser.language=en"; args << "-Duser.language=en";
if (javaVersion.isModular() && traits().contains("legacyServices") && settings()->get("OnlineFixes").toBool()) if (javaVersion.isModular() && shouldApplyOnlineFixes())
// allow reflective access to java.net - required by the skin fix // allow reflective access to java.net - required by the skin fix
args << "--add-opens" args << "--add-opens"
<< "java.base/java.net=ALL-UNNAMED"; << "java.base/java.net=ALL-UNNAMED";
@ -448,7 +448,8 @@ QStringList MinecraftInstance::javaArguments()
return args; return args;
} }
QString MinecraftInstance::getLauncher() { QString MinecraftInstance::getLauncher()
{
// use legacy launcher if the traits are set // use legacy launcher if the traits are set
if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch")) if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch"))
return "legacy"; return "legacy";
@ -456,6 +457,11 @@ QString MinecraftInstance::getLauncher() {
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;
@ -665,7 +671,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
launchScript += "traits " + trait + "\n"; launchScript += "traits " + trait + "\n";
} }
if (profile->getTraits().contains("legacyServices") && settings()->get("OnlineFixes").toBool()) if (shouldApplyOnlineFixes())
launchScript += "onlineFixes true\n"; launchScript += "onlineFixes true\n";
launchScript += "launcher " + getLauncher() + "\n"; launchScript += "launcher " + getLauncher() + "\n";

View File

@ -132,6 +132,7 @@ public:
/// 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

@ -107,6 +107,19 @@ 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(", ");
@ -122,6 +135,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

@ -11,25 +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/fix/Fixes.java
org/prismlauncher/fix/online/OnlineFixes.java
org/prismlauncher/fix/online/SkinFix.java
org/prismlauncher/fix/online/Handler.java
org/prismlauncher/utils/Base64.java
org/prismlauncher/utils/JsonParser.java
org/prismlauncher/utils/Parameters.java org/prismlauncher/utils/Parameters.java
org/prismlauncher/utils/ReflectionUtils.java org/prismlauncher/utils/ReflectionUtils.java
org/prismlauncher/utils/api/MojangApi.java
org/prismlauncher/utils/api/Texture.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
org/prismlauncher/utils/url/UrlUtils.java org/prismlauncher/legacy/LegacyProxy.java
org/prismlauncher/utils/url/CustomUrlConnection.java
net/minecraft/Launcher.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/JsonParseException.java
legacy/org/prismlauncher/legacy/utils/JsonParser.java
legacy/org/prismlauncher/legacy/utils/api/MojangApi.java
legacy/org/prismlauncher/legacy/utils/api/Texture.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

@ -52,7 +52,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.prismlauncher.launcher.impl.legacy; package org.prismlauncher.legacy;
import java.applet.Applet; import java.applet.Applet;
import java.awt.Dimension; import java.awt.Dimension;
@ -74,7 +74,7 @@ import org.prismlauncher.utils.logging.Log;
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;

View File

@ -53,11 +53,15 @@
* limitations under the License. * limitations under the License.
*/ */
package org.prismlauncher.launcher.impl.legacy; package org.prismlauncher.legacy;
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;
@ -69,7 +73,7 @@ import org.prismlauncher.utils.logging.Log;
/** /**
* Used to launch old versions that support applets. * Used to launch old versions that 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;
@ -94,11 +98,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));
} }
@ -107,7 +109,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, window.start(user, session, width, height, maximize, serverAddress, serverPort,
gameArgs.contains("--demo")); gameArgs.contains("--demo"));
@ -123,4 +125,37 @@ public final class LegacyLauncher extends AbstractLauncher {
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) {
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))
continue;
if (!Modifier.isPrivate(fieldModifiers))
continue;
if (Modifier.isFinal(fieldModifiers))
continue;
return field;
}
return null;
}
} }

View File

@ -0,0 +1,17 @@
package org.prismlauncher.legacy;
import org.prismlauncher.launcher.Launcher;
import org.prismlauncher.legacy.fix.online.OnlineFixes;
import org.prismlauncher.utils.Parameters;
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

@ -33,7 +33,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.prismlauncher.fix.online; package org.prismlauncher.legacy.fix.online;
import java.io.IOException; import java.io.IOException;
import java.net.Proxy; import java.net.Proxy;
@ -41,7 +41,7 @@ import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLStreamHandler; import java.net.URLStreamHandler;
import org.prismlauncher.utils.url.UrlUtils; import org.prismlauncher.legacy.utils.url.UrlUtils;
final class Handler extends URLStreamHandler { final class Handler extends URLStreamHandler {

View File

@ -33,15 +33,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.prismlauncher.fix.online; package org.prismlauncher.legacy.fix.online;
import java.net.URL; import java.net.URL;
import java.net.URLStreamHandler; import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory; import java.net.URLStreamHandlerFactory;
import org.prismlauncher.utils.Base64; 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 org.prismlauncher.utils.logging.Log;
import org.prismlauncher.utils.url.UrlUtils;
/** /**
* Fixes skins by redirecting to other URLs. * Fixes skins by redirecting to other URLs.
@ -51,7 +52,10 @@ import org.prismlauncher.utils.url.UrlUtils;
*/ */
public final class OnlineFixes implements URLStreamHandlerFactory { public final class OnlineFixes implements URLStreamHandlerFactory {
public static void apply() { public static void apply(Parameters params) {
if (!"true".equals(params.getString("onlineFixes", null)))
return;
if (!UrlUtils.isSupported() || !Base64.isSupported()) { if (!UrlUtils.isSupported() || !Base64.isSupported()) {
Log.warning("Cannot access the necessary Java internals for skin fix"); Log.warning("Cannot access the necessary Java internals for skin fix");
Log.warning("Turning off legacy skin fix in Settings > Miscellaneous will silence the warnings"); Log.warning("Turning off legacy skin fix in Settings > Miscellaneous will silence the warnings");

View File

@ -1,4 +1,4 @@
package org.prismlauncher.fix.online; package org.prismlauncher.legacy.fix.online;
import java.awt.AlphaComposite; import java.awt.AlphaComposite;
import java.awt.Graphics2D; import java.awt.Graphics2D;
@ -12,10 +12,8 @@ import java.net.URLConnection;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.prismlauncher.utils.api.MojangApi; import org.prismlauncher.legacy.utils.api.*;
import org.prismlauncher.utils.api.Texture; import org.prismlauncher.legacy.utils.url.*;
import org.prismlauncher.utils.url.CustomUrlConnection;
import org.prismlauncher.utils.url.UrlUtils;
final class SkinFix { final class SkinFix {

View File

@ -33,7 +33,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.prismlauncher.utils; package org.prismlauncher.legacy.utils;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;

View File

@ -33,7 +33,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.prismlauncher.exception; package org.prismlauncher.legacy.utils;
import java.io.IOException; import java.io.IOException;

View File

@ -33,7 +33,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.prismlauncher.utils; package org.prismlauncher.legacy.utils;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -46,8 +46,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.prismlauncher.exception.JsonParseException;
/** /**
* Single-file JSON parser to allow for usage in versions without GSON. * Single-file JSON parser to allow for usage in versions without GSON.
*/ */

View File

@ -33,15 +33,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.prismlauncher.utils.api; package org.prismlauncher.legacy.utils.api;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.Map; import java.util.Map;
import org.prismlauncher.utils.Base64; import org.prismlauncher.legacy.utils.*;
import org.prismlauncher.utils.JsonParser;
/** /**
* Basic access to Mojang's Minecraft API. * Basic access to Mojang's Minecraft API.

View File

@ -33,7 +33,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.prismlauncher.utils.api; package org.prismlauncher.legacy.utils.api;
import java.net.URL; import java.net.URL;

View File

@ -33,7 +33,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.prismlauncher.utils.url; package org.prismlauncher.legacy.utils.url;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;

View File

@ -33,7 +33,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.prismlauncher.utils.url; package org.prismlauncher.legacy.utils.url;
import java.io.IOException; import java.io.IOException;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;

View File

@ -59,10 +59,9 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.prismlauncher.exception.ParseException; import org.prismlauncher.exception.ParseException;
import org.prismlauncher.fix.Fixes;
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;
@ -108,7 +107,7 @@ public final class EntryPoint {
} }
try { try {
Fixes.apply(params); LegacyProxy.applyOnlineFixes(params);
Launcher launcher; Launcher launcher;
String type = params.getString("launcher"); String type = params.getString("launcher");
@ -119,7 +118,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

@ -1,48 +0,0 @@
// 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.fix;
import org.prismlauncher.fix.online.OnlineFixes;
import org.prismlauncher.utils.Parameters;
public final class Fixes {
public static void apply(Parameters params) {
if ("true".equalsIgnoreCase(params.getString("onlineFixes", null)))
OnlineFixes.apply();
}
}

View File

@ -0,0 +1,17 @@
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

@ -54,76 +54,15 @@
package org.prismlauncher.utils; package org.prismlauncher.utils;
import java.applet.Applet;
import java.io.File;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import org.prismlauncher.utils.logging.Log;
public final class ReflectionUtils { 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.
* *

View File

@ -45,7 +45,7 @@ import java.io.PrintStream;
public final class Log { public final class Log {
// original before possibly overridden by MC // original before possibly overridden by MC
private static final PrintStream OUT = new PrintStream(System.out), ERR = new PrintStream(System.err); private static final PrintStream OUT = new PrintStream(System.out), ERR = new PrintStream(System.err);
private static final boolean DEBUG = Boolean.getBoolean("org.prismlauncher.debug"); private static final boolean DEBUG = Boolean.getBoolean("org.prismlauncher.debug");
public static void launcher(String message) { public static void launcher(String message) {