aboutsummaryrefslogtreecommitdiff
path: root/libraries/launcher/org/polymc
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/launcher/org/polymc')
-rw-r--r--libraries/launcher/org/polymc/EntryPoint.java164
-rw-r--r--libraries/launcher/org/polymc/Launcher.java23
-rw-r--r--libraries/launcher/org/polymc/LauncherFactory.java80
-rw-r--r--libraries/launcher/org/polymc/applet/LegacyFrame.java162
-rw-r--r--libraries/launcher/org/polymc/exception/ParameterNotFoundException.java25
-rw-r--r--libraries/launcher/org/polymc/exception/ParseException.java25
-rw-r--r--libraries/launcher/org/polymc/impl/OneSixLauncher.java189
-rw-r--r--libraries/launcher/org/polymc/utils/Parameters.java78
-rw-r--r--libraries/launcher/org/polymc/utils/Utils.java49
9 files changed, 795 insertions, 0 deletions
diff --git a/libraries/launcher/org/polymc/EntryPoint.java b/libraries/launcher/org/polymc/EntryPoint.java
new file mode 100644
index 00000000..20f418eb
--- /dev/null
+++ b/libraries/launcher/org/polymc/EntryPoint.java
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea, <fr3shtea@outlook.com>
+ *
+ * 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.polymc;
+
+import org.polymc.exception.ParseException;
+import org.polymc.utils.Parameters;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public final class EntryPoint {
+
+ private static final Logger LOGGER = Logger.getLogger("EntryPoint");
+
+ private final Parameters params = new Parameters();
+
+ public static void main(String[] args) {
+ EntryPoint listener = new EntryPoint();
+
+ int retCode = listener.listen();
+
+ if (retCode != 0) {
+ LOGGER.info("Exiting with " + retCode);
+
+ System.exit(retCode);
+ }
+ }
+
+ private Action parseLine(String inData) throws ParseException {
+ String[] tokens = inData.split("\\s+", 2);
+
+ if (tokens.length == 0)
+ throw new ParseException("Unexpected empty string!");
+
+ switch (tokens[0]) {
+ case "launch": {
+ return Action.Launch;
+ }
+
+ case "abort": {
+ return Action.Abort;
+ }
+
+ default: {
+ if (tokens.length != 2)
+ throw new ParseException("Error while parsing:" + inData);
+
+ params.add(tokens[0], tokens[1]);
+
+ return Action.Proceed;
+ }
+ }
+ }
+
+ public int listen() {
+ Action action = Action.Proceed;
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ System.in,
+ StandardCharsets.UTF_8
+ ))) {
+ String line;
+
+ while (action == Action.Proceed) {
+ if ((line = reader.readLine()) != null) {
+ action = parseLine(line);
+ } else {
+ action = Action.Abort;
+ }
+ }
+ } catch (IOException | ParseException e) {
+ LOGGER.log(Level.SEVERE, "Launcher ABORT due to exception:", e);
+
+ return 1;
+ }
+
+ // Main loop
+ if (action == Action.Abort) {
+ LOGGER.info("Launch aborted by the launcher.");
+
+ return 1;
+ }
+
+ try {
+ Launcher launcher =
+ LauncherFactory
+ .getInstance()
+ .createLauncher(params);
+
+ launcher.launch();
+
+ return 0;
+ } catch (IllegalArgumentException e) {
+ LOGGER.log(Level.SEVERE, "Wrong argument.", e);
+
+ return 1;
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Exception caught from launcher.", e);
+
+ return 1;
+ }
+ }
+
+ private enum Action {
+ Proceed,
+ Launch,
+ Abort
+ }
+
+}
diff --git a/libraries/launcher/org/polymc/Launcher.java b/libraries/launcher/org/polymc/Launcher.java
new file mode 100644
index 00000000..5bff123e
--- /dev/null
+++ b/libraries/launcher/org/polymc/Launcher.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012-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.polymc;
+
+public interface Launcher {
+
+ void launch() throws Exception;
+
+}
diff --git a/libraries/launcher/org/polymc/LauncherFactory.java b/libraries/launcher/org/polymc/LauncherFactory.java
new file mode 100644
index 00000000..86862929
--- /dev/null
+++ b/libraries/launcher/org/polymc/LauncherFactory.java
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea, <fr3shtea@outlook.com>
+ *
+ * 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.polymc;
+
+import org.polymc.impl.OneSixLauncher;
+import org.polymc.utils.Parameters;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public final class LauncherFactory {
+
+ private static final LauncherFactory INSTANCE = new LauncherFactory();
+
+ private final Map<String, LauncherProvider> launcherRegistry = new HashMap<>();
+
+ private LauncherFactory() {
+ launcherRegistry.put("onesix", new LauncherProvider() {
+ @Override
+ public Launcher provide(Parameters parameters) {
+ return new OneSixLauncher(parameters);
+ }
+ });
+ }
+
+ public Launcher createLauncher(Parameters parameters) {
+ String name = parameters.first("launcher");
+
+ LauncherProvider launcherProvider = launcherRegistry.get(name);
+
+ if (launcherProvider == null)
+ throw new IllegalArgumentException("Invalid launcher type: " + name);
+
+ return launcherProvider.provide(parameters);
+ }
+
+ public static LauncherFactory getInstance() {
+ return INSTANCE;
+ }
+
+ public interface LauncherProvider {
+
+ Launcher provide(Parameters parameters);
+
+ }
+
+}
diff --git a/libraries/launcher/org/polymc/applet/LegacyFrame.java b/libraries/launcher/org/polymc/applet/LegacyFrame.java
new file mode 100644
index 00000000..2cdd17d7
--- /dev/null
+++ b/libraries/launcher/org/polymc/applet/LegacyFrame.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2012-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.polymc.applet;
+
+import net.minecraft.Launcher;
+
+import javax.imageio.ImageIO;
+import java.applet.Applet;
+import java.awt.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public final class LegacyFrame extends Frame {
+
+ private static final Logger LOGGER = Logger.getLogger("LegacyFrame");
+
+ private final Launcher appletWrap;
+
+ public LegacyFrame(String title, Applet mcApplet) {
+ super(title);
+
+ appletWrap = new Launcher(mcApplet);
+
+ mcApplet.setStub(appletWrap);
+
+ try {
+ setIconImage(ImageIO.read(new File("icon.png")));
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Unable to read Minecraft icon!", e);
+ }
+
+ addWindowListener(new ForceExitHandler());
+ }
+
+ public void start (
+ String user,
+ String session,
+ int winSizeW,
+ int winSizeH,
+ boolean maximize,
+ String serverAddress,
+ String serverPort
+ ) {
+ // Implements support for launching in to multiplayer on classic servers using a mpticket
+ // file generated by an external program and stored in the instance's root folder.
+
+ Path mpticketFile =
+ Paths.get(System.getProperty("user.dir"), "..", "mpticket");
+
+ Path mpticketFileCorrupt =
+ Paths.get(System.getProperty("user.dir"), "..", "mpticket.corrupt");
+
+ if (Files.exists(mpticketFile)) {
+ try {
+ List<String> lines = Files.readAllLines(mpticketFile, StandardCharsets.UTF_8);
+
+ if (lines.size() < 3) {
+ Files.move(
+ mpticketFile,
+ mpticketFileCorrupt,
+ StandardCopyOption.REPLACE_EXISTING
+ );
+
+ LOGGER.warning("Mpticket file is corrupted!");
+ } else {
+ // Assumes parameters are valid and in the correct order
+ appletWrap.setParameter("server", lines.get(0));
+ appletWrap.setParameter("port", lines.get(1));
+ appletWrap.setParameter("mppass", lines.get(2));
+ }
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Unable to read mpticket file!", e);
+ }
+ }
+
+ if (serverAddress != null) {
+ appletWrap.setParameter("server", serverAddress);
+ appletWrap.setParameter("port", serverPort);
+ }
+
+ appletWrap.setParameter("username", user);
+ appletWrap.setParameter("sessionid", session);
+ appletWrap.setParameter("stand-alone", "true"); // Show the quit button.
+ appletWrap.setParameter("haspaid", "true"); // Some old versions need this for world saves to work.
+ appletWrap.setParameter("demo", "false");
+ appletWrap.setParameter("fullscreen", "false");
+
+ add(appletWrap);
+
+ appletWrap.setPreferredSize(new Dimension(winSizeW, winSizeH));
+
+ pack();
+
+ setLocationRelativeTo(null);
+ setResizable(true);
+
+ if (maximize)
+ this.setExtendedState(MAXIMIZED_BOTH);
+
+ validate();
+
+ appletWrap.init();
+ appletWrap.start();
+
+ setVisible(true);
+ }
+
+ private final class ForceExitHandler extends WindowAdapter {
+
+ @Override
+ public void windowClosing(WindowEvent e) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(30000L);
+ } catch (InterruptedException localInterruptedException) {
+ localInterruptedException.printStackTrace();
+ }
+
+ LOGGER.info("Forcing exit!");
+
+ System.exit(0);
+ }
+ }).start();
+
+ if (appletWrap != null) {
+ appletWrap.stop();
+ appletWrap.destroy();
+ }
+
+ // old minecraft versions can hang without this >_<
+ System.exit(0);
+ }
+
+ }
+
+}
diff --git a/libraries/launcher/org/polymc/exception/ParameterNotFoundException.java b/libraries/launcher/org/polymc/exception/ParameterNotFoundException.java
new file mode 100644
index 00000000..2044814e
--- /dev/null
+++ b/libraries/launcher/org/polymc/exception/ParameterNotFoundException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012-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.polymc.exception;
+
+public final class ParameterNotFoundException extends IllegalArgumentException {
+
+ public ParameterNotFoundException(String key) {
+ super("Unknown parameter name: " + key);
+ }
+
+}
diff --git a/libraries/launcher/org/polymc/exception/ParseException.java b/libraries/launcher/org/polymc/exception/ParseException.java
new file mode 100644
index 00000000..2f2f8294
--- /dev/null
+++ b/libraries/launcher/org/polymc/exception/ParseException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012-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.polymc.exception;
+
+public final class ParseException extends IllegalArgumentException {
+
+ public ParseException(String message) {
+ super(message);
+ }
+
+}
diff --git a/libraries/launcher/org/polymc/impl/OneSixLauncher.java b/libraries/launcher/org/polymc/impl/OneSixLauncher.java
new file mode 100644
index 00000000..362ff8d6
--- /dev/null
+++ b/libraries/launcher/org/polymc/impl/OneSixLauncher.java
@@ -0,0 +1,189 @@
+/* Copyright 2012-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.polymc.impl;
+
+import org.polymc.Launcher;
+import org.polymc.applet.LegacyFrame;
+import org.polymc.utils.Parameters;
+import org.polymc.utils.Utils;
+
+import java.applet.Applet;
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public final class OneSixLauncher implements Launcher {
+
+ private static final int DEFAULT_WINDOW_WIDTH = 854;
+ private static final int DEFAULT_WINDOW_HEIGHT = 480;
+
+ private static final Logger LOGGER = Logger.getLogger("OneSixLauncher");
+
+ // parameters, separated from ParamBucket
+ private final List<String> mcParams;
+ private final List<String> traits;
+ private final String appletClass;
+ private final String mainClass;
+ private final String userName, sessionId;
+ private final String windowTitle;
+
+ // secondary parameters
+ private final int winSizeW;
+ private final int winSizeH;
+ private final boolean maximize;
+ private final String cwd;
+
+ private final String serverAddress;
+ private final String serverPort;
+
+ private final ClassLoader classLoader;
+
+ public OneSixLauncher(Parameters params) {
+ classLoader = ClassLoader.getSystemClassLoader();
+
+ mcParams = params.allSafe("param", Collections.<String>emptyList());
+ mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft");
+ appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet");
+ traits = params.allSafe("traits", Collections.<String>emptyList());
+
+ userName = params.first("userName");
+ sessionId = params.first("sessionId");
+ windowTitle = params.firstSafe("windowTitle", "Minecraft");
+
+ serverAddress = params.firstSafe("serverAddress", null);
+ serverPort = params.firstSafe("serverPort", null);
+
+ cwd = System.getProperty("user.dir");
+
+ String windowParams = params.firstSafe("windowParams", null);
+
+ if (windowParams != null) {
+ String[] dimStrings = windowParams.split("x");
+
+ if (windowParams.equalsIgnoreCase("max")) {
+ maximize = true;
+
+ winSizeW = DEFAULT_WINDOW_WIDTH;
+ winSizeH = DEFAULT_WINDOW_HEIGHT;
+ } else if (dimStrings.length == 2) {
+ maximize = false;
+
+ winSizeW = Integer.parseInt(dimStrings[0]);
+ winSizeH = Integer.parseInt(dimStrings[1]);
+ } else {
+ throw new IllegalArgumentException("Unexpected window size parameter value: " + windowParams);
+ }
+ } else {
+ maximize = false;
+
+ winSizeW = DEFAULT_WINDOW_WIDTH;
+ winSizeH = DEFAULT_WINDOW_HEIGHT;
+ }
+ }
+
+ private void invokeMain(Class<?> mainClass) throws Exception {
+ Method method = mainClass.getMethod("main", String[].class);
+
+ method.invoke(null, (Object) mcParams.toArray(new String[0]));
+ }
+
+ private void legacyLaunch() throws Exception {
+ // Get the Minecraft Class and set the base folder
+ Class<?> minecraftClass = classLoader.loadClass(mainClass);
+
+ Field baseDirField = Utils.getMinecraftBaseDirField(minecraftClass);
+
+ if (baseDirField == null) {
+ LOGGER.warning("Could not find Minecraft path field.");
+ } else {
+ baseDirField.setAccessible(true);
+
+ baseDirField.set(null, new File(cwd));
+ }
+
+ System.setProperty("minecraft.applet.TargetDirectory", cwd);
+
+ if (!traits.contains("noapplet")) {
+ LOGGER.info("Launching with applet wrapper...");
+
+ try {
+ Class<?> mcAppletClass = classLoader.loadClass(appletClass);
+
+ Applet mcApplet = (Applet) mcAppletClass.getConstructor().newInstance();
+
+ LegacyFrame mcWindow = new LegacyFrame(windowTitle, mcApplet);
+
+ mcWindow.start(
+ userName,
+ sessionId,
+ winSizeW,
+ winSizeH,
+ maximize,
+ serverAddress,
+ serverPort
+ );
+
+ return;
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Applet wrapper failed: ", e);
+
+ LOGGER.warning("Falling back to using main class.");
+ }
+ }
+
+ invokeMain(minecraftClass);
+ }
+
+ private void launchWithMainClass() throws Exception {
+ // window size, title and state, onesix
+
+ // FIXME: there is no good way to maximize the minecraft window in onesix.
+ // the following often breaks linux screen setups
+ // mcparams.add("--fullscreen");
+
+ if (!maximize) {
+ mcParams.add("--width");
+ mcParams.add(Integer.toString(winSizeW));
+ mcParams.add("--height");
+ mcParams.add(Integer.toString(winSizeH));
+ }
+
+ if (serverAddress != null) {
+ mcParams.add("--server");
+ mcParams.add(serverAddress);
+ mcParams.add("--port");
+ mcParams.add(serverPort);
+ }
+
+ invokeMain(classLoader.loadClass(mainClass));
+ }
+
+ @Override
+ public void launch() throws Exception {
+ if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch")) {
+ // legacy launch uses the applet wrapper
+ legacyLaunch();
+ } else {
+ // normal launch just calls main()
+ launchWithMainClass();
+ }
+ }
+
+}
diff --git a/libraries/launcher/org/polymc/utils/Parameters.java b/libraries/launcher/org/polymc/utils/Parameters.java
new file mode 100644
index 00000000..864d3cd2
--- /dev/null
+++ b/libraries/launcher/org/polymc/utils/Parameters.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012-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.polymc.utils;
+
+import org.polymc.exception.ParameterNotFoundException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public final class Parameters {
+
+ private final Map<String, List<String>> paramsMap = new HashMap<>();
+
+ public void add(String key, String value) {
+ List<String> params = paramsMap.get(key);
+
+ if (params == null) {
+ params = new ArrayList<>();
+
+ paramsMap.put(key, params);
+ }
+
+ params.add(value);
+ }
+
+ public List<String> all(String key) throws ParameterNotFoundException {
+ List<String> params = paramsMap.get(key);
+
+ if (params == null)
+ throw new ParameterNotFoundException(key);
+
+ return params;
+ }
+
+ public List<String> allSafe(String key, List<String> def) {
+ List<String> params = paramsMap.get(key);
+
+ if (params == null || params.isEmpty())
+ return def;
+
+ return params;
+ }
+
+ public String first(String key) throws ParameterNotFoundException {
+ List<String> list = all(key);
+
+ if (list.isEmpty())
+ throw new ParameterNotFoundException(key);
+
+ return list.get(0);
+ }
+
+ public String firstSafe(String key, String def) {
+ List<String> params = paramsMap.get(key);
+
+ if (params == null || params.isEmpty())
+ return def;
+
+ return params.get(0);
+ }
+
+}
diff --git a/libraries/launcher/org/polymc/utils/Utils.java b/libraries/launcher/org/polymc/utils/Utils.java
new file mode 100644
index 00000000..12d6e1aa
--- /dev/null
+++ b/libraries/launcher/org/polymc/utils/Utils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012-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.polymc.utils;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+public final class Utils {
+
+ private Utils() {}
+
+ /**
+ * Finds a field that looks like a Minecraft base folder in a supplied class
+ *
+ * @param clazz the class to scan
+ */
+ public static Field getMinecraftBaseDirField(Class<?> clazz) {
+ for (Field f : clazz.getDeclaredFields()) {
+ // Has to be File
+ if (f.getType() != File.class)
+ continue;
+
+ // And Private Static.
+ if (!Modifier.isStatic(f.getModifiers()) || !Modifier.isPrivate(f.getModifiers()))
+ continue;
+
+ return f;
+ }
+
+ return null;
+ }
+
+}
+