Merge branch 'release-0.2'

This commit is contained in:
Petr Mrázek 2014-01-20 01:32:38 +01:00
commit 222d3c0dc5
217 changed files with 5911 additions and 1666 deletions

14
.gitignore vendored
View File

@ -1,17 +1,23 @@
Thumbs.db
.kdev4
MultiMC5.kdev4
MultiMC.pro.user
CMakeLists.txt.user
.user
.directory
build
resources/CMakeFiles
resources/MultiMCLauncher.jar
*~
*.swp
html/
# Project Files
MultiMC5.kdev4
MultiMC.pro.user
CMakeLists.txt.user
CMakeLists.txt.user.*
# Build dirs
build
/build-*
# Ctags File
tags

View File

@ -25,6 +25,8 @@ IF(UNIX)
SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
ENDIF()
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars)
######## Set compiler flags ########
IF(APPLE)
message(STATUS "Using APPLE CMAKE_CXX_FLAGS")
@ -90,8 +92,8 @@ SET(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch
######## Set version numbers ########
SET(MultiMC_VERSION_MAJOR 0)
SET(MultiMC_VERSION_MINOR 1)
SET(MultiMC_VERSION_HOTFIX 1)
SET(MultiMC_VERSION_MINOR 2)
SET(MultiMC_VERSION_HOTFIX 0)
# Build number
SET(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
@ -280,7 +282,6 @@ gui/dialogs/SettingsDialog.h
gui/dialogs/SettingsDialog.cpp
gui/dialogs/CopyInstanceDialog.h
gui/dialogs/CopyInstanceDialog.cpp
gui/dialogs/dialogs/
gui/dialogs/NewInstanceDialog.cpp
gui/dialogs/ProgressDialog.h
gui/dialogs/ProgressDialog.cpp
@ -391,6 +392,10 @@ logic/news/NewsChecker.cpp
logic/news/NewsEntry.h
logic/news/NewsEntry.cpp
# Status system
logic/status/StatusChecker.h
logic/status/StatusChecker.cpp
# legacy instances
logic/LegacyInstance.h
logic/LegacyInstance.cpp
@ -514,10 +519,16 @@ gui/widgets/MCModInfoFrame.ui
set (FILES_TO_TRANSLATE ${FILES_TO_TRANSLATE} ${MULTIMC_SOURCES} ${MULTIMC_UIS})
SET(MULTIMC_QRCS
resources/backgrounds/backgrounds.qrc
resources/multimc/multimc.qrc
resources/instances/instances.qrc
)
######## Windows resource files ########
IF(WIN32)
SET(MULTIMC_RCS multimc.rc)
SET(MULTIMC_RCS resources/multimc.rc)
ENDIF()
####### X11 Stuff #######
@ -532,13 +543,6 @@ ENDIF()
################################ COMPILE ################################
# ICNS file for OS X
IF(APPLE)
SET(MACOSX_BUNDLE_ICON_FILE MultiMC.icns)
SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_SOURCE_DIR}/MultiMC.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
SET(MULTIMC_SOURCES ${MULTIMC_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/MultiMC.icns)
ENDIF(APPLE)
# Link additional libraries
IF(WIN32)
SET(MultiMC_LINK_ADDITIONAL_LIBS ${MultiMC_LINK_ADDITIONAL_LIBS}
@ -564,12 +568,10 @@ ENDIF(MultiMC_CODE_COVERAGE)
# Qt 5 stuff
QT5_WRAP_UI(MULTIMC_UI ${MULTIMC_UIS})
CONFIGURE_FILE(generated.qrc.in generated.qrc)
QT5_ADD_RESOURCES(GENERATED_QRC ${CMAKE_CURRENT_BINARY_DIR}/generated.qrc)
QT5_ADD_RESOURCES(GRAPHICS_QRC graphics.qrc)
QT5_ADD_RESOURCES(MULTIMC_RESOURCES ${MULTIMC_QRCS})
# Add common library
ADD_LIBRARY(MultiMC_common STATIC ${MULTIMC_SOURCES} ${MULTIMC_UI} ${GENERATED_QRC} ${GRAPHICS_QRC})
ADD_LIBRARY(MultiMC_common STATIC ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_RESOURCES})
# Add executable
ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 main.cpp ${MULTIMC_RCS})
@ -579,7 +581,6 @@ TARGET_LINK_LIBRARIES(MultiMC MultiMC_common)
TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS})
QT5_USE_MODULES(MultiMC Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
QT5_USE_MODULES(MultiMC_common Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
ADD_DEPENDENCIES(MultiMC_common MultiMCLauncher JavaCheck)
################################ INSTALLATION AND PACKAGING ################################
@ -667,6 +668,11 @@ FILE(WRITE \"\${CMAKE_INSTALL_PREFIX}/${QTCONF_DEST_DIR}/qt.conf\" \"\")
COMPONENT Runtime
)
# ICNS file for OS X
IF(APPLE)
INSTALL(FILES resources/MultiMC.icns DESTINATION MultiMC.app/Contents/Resources)
ENDIF()
CONFIGURE_FILE(
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"

View File

@ -20,8 +20,11 @@
#include "logic/news/NewsChecker.h"
#include "logic/status/StatusChecker.h"
#include "logic/InstanceLauncher.h"
#include "logic/net/HttpMetaCache.h"
#include "logic/net/URLConstants.h"
#include "logic/JavaUtils.h"
@ -44,8 +47,6 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override)
setOrganizationName("MultiMC");
setApplicationName("MultiMC5");
initTranslations();
setAttribute(Qt::AA_UseHighDpiPixmaps);
// Don't quit on hiding the last window
this->setQuitOnLastWindowClosed(false);
@ -172,6 +173,9 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override)
// load settings
initGlobalSettings();
// load translations
initTranslations();
// initialize the updater
m_updateChecker.reset(new UpdateChecker());
@ -181,6 +185,9 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override)
// initialize the news checker
m_newsChecker.reset(new NewsChecker(NEWS_RSS_URL));
// initialize the status checker
m_statusChecker.reset(new StatusChecker());
// and instances
auto InstDirSetting = m_settings->getSetting("InstanceDir");
m_instances.reset(new InstanceList(InstDirSetting->get().toString(), this));
@ -234,18 +241,20 @@ MultiMC::~MultiMC()
void MultiMC::initTranslations()
{
QLocale locale(m_settings->get("Language").toString());
QLocale::setDefault(locale);
QLOG_INFO() << "Your language is" << locale.bcp47Name();
m_qt_translator.reset(new QTranslator());
if (m_qt_translator->load("qt_" + QLocale::system().name(),
if (m_qt_translator->load("qt_" + locale.bcp47Name(),
QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
{
std::cout << "Loading Qt Language File for "
<< QLocale::system().name().toLocal8Bit().constData() << "...";
QLOG_DEBUG() << "Loading Qt Language File for"
<< locale.bcp47Name().toLocal8Bit().constData() << "...";
if (!installTranslator(m_qt_translator.get()))
{
std::cout << " failed.";
QLOG_ERROR() << "Loading Qt Language File failed.";
m_qt_translator.reset();
}
std::cout << std::endl;
}
else
{
@ -253,17 +262,15 @@ void MultiMC::initTranslations()
}
m_mmc_translator.reset(new QTranslator());
if (m_mmc_translator->load("mmc_" + QLocale::system().name(),
QDir("translations").absolutePath()))
if (m_mmc_translator->load("mmc_" + locale.bcp47Name(), MMC->root() + "/translations"))
{
std::cout << "Loading MMC Language File for "
<< QLocale::system().name().toLocal8Bit().constData() << "...";
QLOG_DEBUG() << "Loading MMC Language File for"
<< locale.bcp47Name().toLocal8Bit().constData() << "...";
if (!installTranslator(m_mmc_translator.get()))
{
std::cout << " failed.";
QLOG_ERROR() << "Loading MMC Language File failed.";
m_mmc_translator.reset();
}
std::cout << std::endl;
}
else
{
@ -366,9 +373,13 @@ void MultiMC::initGlobalSettings()
// Editors
m_settings->registerSetting("JsonEditor", QString());
// Language
m_settings->registerSetting("Language", QLocale(QLocale::system().language()).bcp47Name());
// Console
m_settings->registerSetting("ShowConsole", true);
m_settings->registerSetting("AutoCloseConsole", true);
m_settings->registerSetting("LogPrePostOutput", true);
// Console Colors
// m_settings->registerSetting("SysMessageColor", QColor(Qt::blue));

View File

@ -21,6 +21,7 @@ class JavaVersionList;
class UpdateChecker;
class NotificationChecker;
class NewsChecker;
class StatusChecker;
#if defined(MMC)
#undef MMC
@ -113,6 +114,11 @@ public:
return m_newsChecker;
}
std::shared_ptr<StatusChecker> statusChecker()
{
return m_statusChecker;
}
std::shared_ptr<LWJGLVersionList> lwjgllist();
std::shared_ptr<ForgeVersionList> forgelist();
@ -183,6 +189,7 @@ private:
std::shared_ptr<UpdateChecker> m_updateChecker;
std::shared_ptr<NotificationChecker> m_notificationChecker;
std::shared_ptr<NewsChecker> m_newsChecker;
std::shared_ptr<StatusChecker> m_statusChecker;
std::shared_ptr<MojangAccountList> m_accounts;
std::shared_ptr<IconList> m_icons;
std::shared_ptr<QNetworkAccessManager> m_qnam;

View File

@ -16,4 +16,16 @@
- Added additional information to the about dialog.
0.1.1:
- Hotfix - Changed the issue tracker URL to [GitHub issues](https://github.com/MultiMC/MultiMC5/issues).
0.2:
- Java memory settings have MB added to the number to make the units obvious.
- Complete rework of the launcher part. No more sensitive information in the process arguments.
- Cached downloads now do not destroy files on failure.
- Mojang service status is now on the MultiMC status bar.
- Java checker is no longer needed/used on instance launch.
- Support for private FTB packs.
- Fixed instance ID issues related to copying FTB packs without changing the instance name.
- Forge versions are better sorted (build numbers above 999 were sorted wrong).
- Fixed crash related to the MultiMC update channel picker in offline mode.
- Started using icon themes for the application icons, fixing many OSX graphical glitches.
- Icon sources have been located, along with icon licenses.
- Update to the German translation.

6
depends/javacheck/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.idea
*.iml
out
.classpath
.idea
.project

6
depends/launcher/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.idea
*.iml
out
.classpath
.idea
.project

View File

@ -3,20 +3,33 @@ project(launcher Java)
find_package(Java 1.6 REQUIRED COMPONENTS Development)
include(UseJava)
set(CMAKE_JAVA_JAR_ENTRY_POINT MultiMCLauncher)
set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint)
set(CMAKE_JAVA_COMPILE_FLAGS -target 1.6 -source 1.6 -Xlint:deprecation -Xlint:unchecked)
set(SRC
MultiMCLauncher.java
# OSX things
org/simplericity/macify/eawt/Application.java
org/simplericity/macify/eawt/ApplicationAdapter.java
org/simplericity/macify/eawt/ApplicationEvent.java
org/simplericity/macify/eawt/ApplicationListener.java
org/simplericity/macify/eawt/DefaultApplication.java
# legacy applet wrapper thing.
# The launcher has to be there for silly FML/Forge relauncher.
net/minecraft/Launcher.java
MCFrame.java
org/multimc/legacy/LegacyLauncher.java
org/multimc/legacy/LegacyFrame.java
# onesix launcher
org/multimc/onesix/OneSixLauncher.java
# generic launcher
org/multimc/EntryPoint.java
org/multimc/Launcher.java
org/multimc/ParseException.java
org/multimc/Utils.java
org/multimc/IconLoader.java
)
add_jar(NewLaunch ${SRC})
add_jar(MultiMCLauncher ${SRC})
INSTALL_JAR(MultiMCLauncher "${BINARY_DEST_DIR}/jars")
INSTALL_JAR(NewLaunch "${BINARY_DEST_DIR}/jars")

View File

@ -1,331 +0,0 @@
//
// Copyright 2012 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.
//
import java.applet.Applet;
import java.awt.Dimension;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import org.simplericity.macify.eawt.Application;
import org.simplericity.macify.eawt.DefaultApplication;
public class MultiMCLauncher
{
/**
* @param args
* The arguments you want to launch Minecraft with. New path,
* Username, Session ID.
*/
public static void main(String[] args)
{
if (args.length < 3)
{
System.out.println("Not enough arguments.");
System.exit(-1);
}
// Set the OSX application icon first, if we are on OSX.
Application application = new DefaultApplication();
if(application.isMac())
{
try
{
BufferedImage image = ImageIO.read(new File("icon.png"));
application.setApplicationIconImage(image);
}
catch (IOException e)
{
e.printStackTrace();
}
}
String userName = args[0];
String sessionId = args[1];
String windowtitle = args[2];
String windowParams = args[3];
String lwjgl = args[4];
String cwd = System.getProperty("user.dir");
Dimension winSize = new Dimension(854, 480);
boolean maximize = false;
boolean compatMode = false;
String[] dimStrings = windowParams.split("x");
if (windowParams.equalsIgnoreCase("compatmode"))
{
compatMode = true;
}
else if (windowParams.equalsIgnoreCase("max"))
{
maximize = true;
}
else if (dimStrings.length == 2)
{
try
{
winSize = new Dimension(Integer.parseInt(dimStrings[0]),
Integer.parseInt(dimStrings[1]));
}
catch (NumberFormatException e)
{
System.out.println("Invalid Window size argument, " +
"using default.");
}
}
else
{
System.out.println("Invalid Window size argument, " +
"using default.");
}
try
{
File binDir = new File(cwd, "bin");
File lwjglDir;
if(lwjgl.equalsIgnoreCase("Mojang"))
lwjglDir = binDir;
else
lwjglDir = new File(lwjgl);
System.out.println("Loading jars...");
String[] lwjglJars = new String[] {
"lwjgl.jar", "lwjgl_util.jar", "jinput.jar"
};
URL[] urls = new URL[4];
try
{
File f = new File(binDir, "minecraft.jar");
urls[0] = f.toURI().toURL();
System.out.println("Loading URL: " + urls[0].toString());
for (int i = 1; i < urls.length; i++)
{
File jar = new File(lwjglDir, lwjglJars[i-1]);
urls[i] = jar.toURI().toURL();
System.out.println("Loading URL: " + urls[i].toString());
}
}
catch (MalformedURLException e)
{
System.err.println("MalformedURLException, " + e.toString());
System.exit(5);
}
System.out.println("Loading natives...");
String nativesDir = new File(lwjglDir, "natives").toString();
System.setProperty("org.lwjgl.librarypath", nativesDir);
System.setProperty("net.java.games.input.librarypath", nativesDir);
URLClassLoader cl =
new URLClassLoader(urls, MultiMCLauncher.class.getClassLoader());
// Get the Minecraft Class.
Class<?> mc = null;
try
{
mc = cl.loadClass("net.minecraft.client.Minecraft");
Field f = getMCPathField(mc);
if (f == null)
{
System.err.println("Could not find Minecraft path field. Launch failed.");
System.exit(-1);
}
f.setAccessible(true);
f.set(null, new File(cwd));
// And set it.
System.out.println("Fixed Minecraft Path: Field was " + f.toString());
}
catch (ClassNotFoundException e)
{
System.err.println("Can't find main class. Searching...");
// Look for any class that looks like the main class.
File mcJar = new File(new File(cwd, "bin"), "minecraft.jar");
ZipFile zip = null;
try
{
zip = new ZipFile(mcJar);
} catch (ZipException e1)
{
e1.printStackTrace();
System.err.println("Search failed.");
System.exit(-1);
} catch (IOException e1)
{
e1.printStackTrace();
System.err.println("Search failed.");
System.exit(-1);
}
Enumeration<? extends ZipEntry> entries = zip.entries();
ArrayList<String> classes = new ArrayList<String>();
while (entries.hasMoreElements())
{
ZipEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class"))
{
String entryName = entry.getName().substring(0, entry.getName().lastIndexOf('.'));
entryName = entryName.replace('/', '.');
System.out.println("Found class: " + entryName);
classes.add(entryName);
}
}
for (String clsName : classes)
{
try
{
Class<?> cls = cl.loadClass(clsName);
if (!Runnable.class.isAssignableFrom(cls))
{
continue;
}
else
{
System.out.println("Found class implementing runnable: " +
cls.getName());
}
if (getMCPathField(cls) == null)
{
continue;
}
else
{
System.out.println("Found class implementing runnable " +
"with mcpath field: " + cls.getName());
}
mc = cls;
break;
}
catch (ClassNotFoundException e1)
{
// Ignore
continue;
}
}
if (mc == null)
{
System.err.println("Failed to find Minecraft main class.");
System.exit(-1);
}
else
{
System.out.println("Found main class: " + mc.getName());
}
}
System.setProperty("minecraft.applet.TargetDirectory", cwd);
String[] mcArgs = new String[2];
mcArgs[0] = userName;
mcArgs[1] = sessionId;
if (compatMode)
{
System.out.println("Launching in compatibility mode...");
mc.getMethod("main", String[].class).invoke(null, (Object) mcArgs);
}
else
{
System.out.println("Launching with applet wrapper...");
try
{
Class<?> MCAppletClass = cl.loadClass(
"net.minecraft.client.MinecraftApplet");
Applet mcappl = (Applet) MCAppletClass.newInstance();
MCFrame mcWindow = new MCFrame(windowtitle);
mcWindow.start(mcappl, userName, sessionId, winSize, maximize);
} catch (InstantiationException e)
{
System.out.println("Applet wrapper failed! Falling back " +
"to compatibility mode.");
mc.getMethod("main", String[].class).invoke(null, (Object) mcArgs);
}
}
} catch (ClassNotFoundException e)
{
e.printStackTrace();
System.exit(1);
} catch (IllegalArgumentException e)
{
e.printStackTrace();
System.exit(2);
} catch (IllegalAccessException e)
{
e.printStackTrace();
System.exit(2);
} catch (InvocationTargetException e)
{
e.printStackTrace();
System.exit(3);
} catch (NoSuchMethodException e)
{
e.printStackTrace();
System.exit(3);
} catch (SecurityException e)
{
e.printStackTrace();
System.exit(4);
}
}
public static Field getMCPathField(Class<?> mc)
{
Field[] fields = mc.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
Field f = fields[i];
if (f.getType() != File.class)
{
// Has to be File
continue;
}
if (f.getModifiers() != (Modifier.PRIVATE + Modifier.STATIC))
{
// And Private Static.
continue;
}
return f;
}
return null;
}
}

View File

@ -1,18 +1,18 @@
//
// Copyright 2012 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.
//
/*
* Copyright 2012-2014 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 net.minecraft;

View File

@ -0,0 +1,135 @@
package org.multimc;/*
* Copyright 2012-2014 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.
*/
import org.multimc.legacy.LegacyLauncher;
import org.multimc.onesix.OneSixLauncher;
import org.simplericity.macify.eawt.Application;
import org.simplericity.macify.eawt.DefaultApplication;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
public class EntryPoint
{
private enum Action
{
Proceed,
Launch
}
public static void main(String[] args)
{
// Set the OSX application icon first, if we are on OSX.
Application application = new DefaultApplication();
if(application.isMac())
{
try
{
BufferedImage image = ImageIO.read(new File("icon.png"));
application.setApplicationIconImage(image);
}
catch (IOException e)
{
e.printStackTrace();
}
}
EntryPoint listener = new EntryPoint();
int retCode = listener.listen();
if (retCode != 0)
{
System.out.println("Exiting with " + retCode);
System.exit(retCode);
}
}
private Action parseLine(String inData) throws ParseException
{
String[] pair = inData.split(" ", 2);
if(pair.length != 2)
throw new ParseException();
String command = pair[0];
String param = pair[1];
if(command.equals("launch"))
{
if(param.equals("legacy"))
{
m_launcher = new LegacyLauncher();
Utils.log("Using legacy launcher.");
Utils.log();
return Action.Launch;
}
if(param.equals("onesix"))
{
m_launcher = new OneSixLauncher();
Utils.log("Using onesix launcher.");
Utils.log();
return Action.Launch;
}
else
throw new ParseException();
}
m_params.add(command, param);
//System.out.println(command + " : " + param);
return Action.Proceed;
}
public int listen()
{
BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in));
boolean isListening = true;
// Main loop
while (isListening)
{
String inData="";
try
{
// Read from the pipe one line at a time
inData = buffer.readLine();
if (inData != null)
{
if(parseLine(inData) == Action.Launch)
{
isListening = false;
}
}
}
catch (IOException e)
{
e.printStackTrace();
return 1;
}
catch (ParseException e)
{
e.printStackTrace();
return 1;
}
}
if(m_launcher != null)
{
return m_launcher.launch(m_params);
}
System.err.println("No valid launcher implementation specified.");
return 1;
}
private ParamBucket m_params = new ParamBucket();
private org.multimc.Launcher m_launcher;
}

View File

@ -0,0 +1,132 @@
package org.multimc;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
/*****************************************************************************
* A convenience class for loading icons from images.
*
* Icons loaded from this class are formatted to fit within the required
* dimension (16x16, 32x32, or 128x128). If the source image is larger than the
* target dimension, it is shrunk down to the minimum size that will fit. If it
* is smaller, then it is only scaled up if the new scale can be a per-pixel
* linear scale (i.e., x2, x3, x4, etc). In both cases, the image's width/height
* ratio is kept the same as the source image.
*
* @author Chris Molini
*****************************************************************************/
public class IconLoader
{
/*************************************************************************
* Loads an icon in ByteBuffer form.
*
* @param filepath
* The location of the Image to use as an icon.
*
* @return An array of ByteBuffers containing the pixel data for the icon in
* various sizes (as recommended by the OS).
*************************************************************************/
public static ByteBuffer[] load(String filepath)
{
BufferedImage image;
try {
image = ImageIO.read ( new File( filepath ) );
} catch ( IOException e ) {
e.printStackTrace();
return new ByteBuffer[0];
}
ByteBuffer[] buffers;
buffers = new ByteBuffer[1];
buffers[0] = loadInstance(image, 128);
return buffers;
}
/*************************************************************************
* Copies the supplied image into a square icon at the indicated size.
*
* @param image
* The image to place onto the icon.
* @param dimension
* The desired size of the icon.
*
* @return A ByteBuffer of pixel data at the indicated size.
*************************************************************************/
private static ByteBuffer loadInstance(BufferedImage image, int dimension)
{
BufferedImage scaledIcon = new BufferedImage(dimension, dimension,
BufferedImage.TYPE_INT_ARGB_PRE);
Graphics2D g = scaledIcon.createGraphics();
double ratio = getIconRatio(image, scaledIcon);
double width = image.getWidth() * ratio;
double height = image.getHeight() * ratio;
g.drawImage(image, (int) ((scaledIcon.getWidth() - width) / 2),
(int) ((scaledIcon.getHeight() - height) / 2), (int) (width),
(int) (height), null);
g.dispose();
return convertToByteBuffer(scaledIcon);
}
/*************************************************************************
* Gets the width/height ratio of the icon. This is meant to simplify
* scaling the icon to a new dimension.
*
* @param src
* The base image that will be placed onto the icon.
* @param icon
* The icon that will have the image placed on it.
*
* @return The amount to scale the source image to fit it onto the icon
* appropriately.
*************************************************************************/
private static double getIconRatio(BufferedImage src, BufferedImage icon)
{
double ratio = 1;
if (src.getWidth() > icon.getWidth())
ratio = (double) (icon.getWidth()) / src.getWidth();
else
ratio = (int) (icon.getWidth() / src.getWidth());
if (src.getHeight() > icon.getHeight())
{
double r2 = (double) (icon.getHeight()) / src.getHeight();
if (r2 < ratio)
ratio = r2;
}
else
{
double r2 = (int) (icon.getHeight() / src.getHeight());
if (r2 < ratio)
ratio = r2;
}
return ratio;
}
/*************************************************************************
* Converts a BufferedImage into a ByteBuffer of pixel data.
*
* @param image
* The image to convert.
*
* @return A ByteBuffer that contains the pixel data of the supplied image.
*************************************************************************/
public static ByteBuffer convertToByteBuffer(BufferedImage image)
{
byte[] buffer = new byte[image.getWidth() * image.getHeight() * 4];
int counter = 0;
for (int i = 0; i < image.getHeight(); i++)
for (int j = 0; j < image.getWidth(); j++)
{
int colorSpace = image.getRGB(j, i);
buffer[counter + 0] = (byte) ((colorSpace << 8) >> 24);
buffer[counter + 1] = (byte) ((colorSpace << 16) >> 24);
buffer[counter + 2] = (byte) ((colorSpace << 24) >> 24);
buffer[counter + 3] = (byte) (colorSpace >> 24);
counter += 4;
}
return ByteBuffer.wrap(buffer);
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2012-2014 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.multimc;
public interface Launcher
{
abstract int launch(ParamBucket params);
}

View File

@ -0,0 +1,21 @@
/*
* Copyright 2012-2014 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.multimc;
public class NotFoundException extends Exception
{
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2012-2014 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.multimc;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ParamBucket
{
public void add(String key, String value)
{
List<String> coll = null;
if(!m_params.containsKey(key))
{
coll = new ArrayList<String>();
m_params.put(key, coll);
}
else
{
coll = m_params.get(key);
}
coll.add(value);
}
public List<String> all(String key) throws NotFoundException
{
if(!m_params.containsKey(key))
throw new NotFoundException();
return m_params.get(key);
}
public List<String> allSafe(String key, List<String> def)
{
if(!m_params.containsKey(key) || m_params.get(key).size() < 1)
{
return def;
}
return m_params.get(key);
}
public List<String> allSafe(String key)
{
return allSafe(key, new ArrayList<String>());
}
public String first(String key) throws NotFoundException
{
List<String> list = all(key);
if(list.size() < 1)
{
throw new NotFoundException();
}
return list.get(0);
}
public String firstSafe(String key, String def)
{
if(!m_params.containsKey(key) || m_params.get(key).size() < 1)
{
return def;
}
return m_params.get(key).get(0);
}
public String firstSafe(String key)
{
return firstSafe(key, "");
}
private HashMap<String, List<String>> m_params = new HashMap<String, List<String>>();
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2012-2014 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.multimc;
public class ParseException extends java.lang.Exception
{
}

View File

@ -0,0 +1,179 @@
/*
* Copyright 2012-2014 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.multimc;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.List;
public class Utils
{
/**
* Combine two parts of a path.
* @param path1
* @param path2
* @return the paths, combined
*/
public static String combine (String path1, String path2)
{
File file1 = new File(path1);
File file2 = new File(file1, path2);
return file2.getPath();
}
/**
* Join a list of strings into a string using a separator!
* @param strings the string list to join
* @param separator the glue
* @return the result.
*/
public static String join (List<String> strings, String separator)
{
StringBuilder sb = new StringBuilder();
String sep = "";
for(String s: strings)
{
sb.append(sep).append(s);
sep = separator;
}
return sb.toString();
}
/**
* Adds the specified library to the classpath
*
* @param s the path to add
* @throws Exception
*/
public static void addToClassPath(String s) throws Exception
{
File f = new File(s);
URL u = f.toURI().toURL();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[]{u});
}
/**
* Adds many libraries to the classpath
*
* @param jars the paths to add
*/
public static boolean addToClassPath(List<String> jars)
{
boolean pure = true;
// initialize the class path
for (String jar : jars)
{
try
{
Utils.addToClassPath(jar);
} catch (Exception e)
{
System.err.println("Unable to load: " + jar);
e.printStackTrace(System.err);
pure = false;
}
}
return pure;
}
/**
* Adds the specified path to the java library path
*
* @param pathToAdd the path to add
* @throws Exception
*/
@Deprecated public static void addLibraryPath(String pathToAdd) throws Exception
{
final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
usrPathsField.setAccessible(true);
//get array of paths
final String[] paths = (String[]) usrPathsField.get(null);
//check if the path to add is already present
for (String path : paths)
{
if (path.equals(pathToAdd))
{
return;
}
}
//add the new path
final String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
newPaths[newPaths.length - 1] = pathToAdd;
usrPathsField.set(null, newPaths);
}
/**
* Finds a field that looks like a Minecraft base folder in a supplied class
*
* @param mc the class to scan
*/
public static Field getMCPathField(Class<?> mc)
{
Field[] fields = mc.getDeclaredFields();
for (Field f : fields)
{
if (f.getType() != File.class)
{
// Has to be File
continue;
}
if (f.getModifiers() != (Modifier.PRIVATE + Modifier.STATIC))
{
// And Private Static.
continue;
}
return f;
}
return null;
}
/**
* Log to the MultiMC console
*
* @param message A String containing the message
* @param level A String containing the level name. See MinecraftProcess::getLevel()
*/
public static void log(String message, String level)
{
// Kinda dirty
String tag = "!![" + level + "]!";
System.out.println(tag + message.replace("\n", "\n" + tag));
}
public static void log(String message)
{
log(message, "MultiMC");
}
public static void log()
{
System.out.println();
}
}

View File

@ -1,40 +1,39 @@
//
// Copyright 2012 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.multimc.legacy;/*
* Copyright 2012-2014 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.
*/
import net.minecraft.Launcher;
import javax.imageio.ImageIO;
import java.applet.Applet;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.*;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.io.IOException;
import java.io.File;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
public class MCFrame extends Frame implements WindowListener
public class LegacyFrame extends Frame implements WindowListener
{
private Launcher appletWrap = null;
public MCFrame ( String title )
public LegacyFrame(String title)
{
super ( title );
BufferedImage image = null;
BufferedImage image;
try {
image = ImageIO.read ( new File ( "icon.png" ) );
setIconImage ( image );
@ -49,12 +48,12 @@ public class MCFrame extends Frame implements WindowListener
try {
appletWrap = new Launcher( mcApplet, new URL ( "http://www.minecraft.net/game" ) );
} catch ( MalformedURLException ignored ) {}
appletWrap.setParameter ( "username", user );
appletWrap.setParameter ( "sessionid", session );
appletWrap.setParameter ( "stand-alone", "true" ); // Show the quit button.
appletWrap.setParameter ( "demo", "false" );
appletWrap.setParameter("fullscreen", "false");
mcApplet.setStub(appletWrap);
this.add ( appletWrap );
appletWrap.setPreferredSize ( winSize );
this.pack();
@ -63,7 +62,6 @@ public class MCFrame extends Frame implements WindowListener
if ( maximize ) {
this.setExtendedState ( MAXIMIZED_BOTH );
}
validate();
appletWrap.init();
appletWrap.start();

View File

@ -0,0 +1,178 @@
package org.multimc.legacy;/*
* Copyright 2012-2014 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.
*/
import org.multimc.Launcher;
import org.multimc.NotFoundException;
import org.multimc.ParamBucket;
import org.multimc.Utils;
import java.applet.Applet;
import java.awt.*;
import java.io.File;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class LegacyLauncher implements Launcher
{
@Override
public int launch(ParamBucket params)
{
String userName, sessionId, windowTitle, windowParams, lwjgl;
String mainClass = "net.minecraft.client.Minecraft";
try
{
userName = params.first("userName");
sessionId = params.first("sessionId");
windowTitle = params.first("windowTitle");
windowParams = params.first("windowParams");
lwjgl = params.first("lwjgl");
} catch (NotFoundException e)
{
System.err.println("Not enough arguments.");
return -1;
}
String cwd = System.getProperty("user.dir");
Dimension winSize = new Dimension(854, 480);
boolean maximize = false;
String[] dimStrings = windowParams.split("x");
if (windowParams.equalsIgnoreCase("max"))
{
maximize = true;
}
else if (dimStrings.length == 2)
{
try
{
winSize = new Dimension(Integer.parseInt(dimStrings[0]), Integer.parseInt(dimStrings[1]));
} catch (NumberFormatException ignored) {}
}
File binDir = new File(cwd, "bin");
File lwjglDir;
if (lwjgl.equalsIgnoreCase("Mojang"))
{
lwjglDir = binDir;
}
else
{
lwjglDir = new File(lwjgl);
}
URL[] classpath;
{
try
{
classpath = new URL[]
{
new File(binDir, "minecraft.jar").toURI().toURL(),
new File(lwjglDir, "lwjgl.jar").toURI().toURL(),
new File(lwjglDir, "lwjgl_util.jar").toURI().toURL(),
new File(lwjglDir, "jinput.jar").toURI().toURL(),
};
} catch (MalformedURLException e)
{
System.err.println("Class path entry is badly formed:");
e.printStackTrace(System.err);
return -1;
}
}
String nativesDir = new File(lwjglDir, "natives").toString();
System.setProperty("org.lwjgl.librarypath", nativesDir);
System.setProperty("net.java.games.input.librarypath", nativesDir);
// print the pretty things
{
Utils.log("Main Class:");
Utils.log(" " + mainClass);
Utils.log();
Utils.log("Class Path:");
for (URL s : classpath)
{
Utils.log(" " + s);
}
Utils.log();
Utils.log("Native Path:");
Utils.log(" " + nativesDir);
Utils.log();
}
URLClassLoader cl = new URLClassLoader(classpath, LegacyLauncher.class.getClassLoader());
// Get the Minecraft Class and set the base folder
Class<?> mc;
try
{
mc = cl.loadClass(mainClass);
Field f = Utils.getMCPathField(mc);
if (f == null)
{
System.err.println("Could not find Minecraft path field. Launch failed.");
return -1;
}
f.setAccessible(true);
f.set(null, new File(cwd));
} catch (Exception e)
{
System.err.println("Could not set base folder. Failed to find/access Minecraft main class:");
e.printStackTrace(System.err);
return -1;
}
System.setProperty("minecraft.applet.TargetDirectory", cwd);
String[] mcArgs = new String[2];
mcArgs[0] = userName;
mcArgs[1] = sessionId;
Utils.log("Launching with applet wrapper...");
try
{
Class<?> MCAppletClass = cl.loadClass("net.minecraft.client.MinecraftApplet");
Applet mcappl = (Applet) MCAppletClass.newInstance();
LegacyFrame mcWindow = new LegacyFrame(windowTitle);
mcWindow.start(mcappl, userName, sessionId, winSize, maximize);
} catch (Exception e)
{
Utils.log("Applet wrapper failed:", "Error");
e.printStackTrace(System.err);
Utils.log();
Utils.log("Falling back to compatibility mode.");
try
{
mc.getMethod("main", String[].class).invoke(null, (Object) mcArgs);
} catch (Exception e1)
{
Utils.log("Failed to invoke the Minecraft main class:", "Fatal");
e1.printStackTrace(System.err);
return -1;
}
}
return 0;
}
}

View File

@ -0,0 +1,210 @@
/* Copyright 2012-2014 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.multimc.onesix;
import org.multimc.*;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
public class OneSixLauncher implements Launcher
{
@Override
public int launch(ParamBucket params)
{
// get and process the launch script params
List<String> libraries;
List<String> mcparams;
List<String> mods;
String mainClass;
String natives;
final String windowTitle;
String windowParams;
try
{
libraries = params.all("cp");
mcparams = params.all("param");
mainClass = params.first("mainClass");
mods = params.allSafe("mods", new ArrayList<String>());
natives = params.first("natives");
windowTitle = params.first("windowTitle");
// windowParams = params.first("windowParams");
} catch (NotFoundException e)
{
System.err.println("Not enough arguments.");
e.printStackTrace(System.err);
return -1;
}
List<String> allJars = new ArrayList<String>();
allJars.addAll(mods);
allJars.addAll(libraries);
if(!Utils.addToClassPath(allJars))
{
System.err.println("Halting launch due to previous errors.");
return -1;
}
String property = System.getProperty("os.arch");
List<String> allNativePaths = new ArrayList<String>();
boolean is_64 = property.equalsIgnoreCase("x86_64") || property.equalsIgnoreCase("amd64");
allNativePaths.add(natives);
allNativePaths.add(natives + "/" + (is_64 ? "64" : "32"));
// print the pretty things
{
Utils.log("Main Class:");
Utils.log(" " + mainClass);
Utils.log();
Utils.log("Native paths:");
for (String s : allNativePaths)
{
Utils.log(" " + s);
}
Utils.log();
Utils.log("Libraries:");
for (String s : libraries)
{
Utils.log(" " + s);
}
Utils.log();
if(mods.size() > 0)
{
Utils.log("Class Path Mods:");
for (String s : mods)
{
Utils.log(" " + s);
}
Utils.log();
}
Utils.log("Params:");
Utils.log(" " + mcparams.toString());
Utils.log();
}
final ClassLoader cl = ClassLoader.getSystemClassLoader();
// set up the natives path(s).
String libpath = Utils.join(allNativePaths, String.valueOf(File.pathSeparatorChar));
System.setProperty("java.library.path", libpath);
Field fieldSysPath;
try
{
fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
fieldSysPath.setAccessible( true );
fieldSysPath.set( null, null );
} catch (Exception e)
{
System.err.println("Failed to set the native library path:");
e.printStackTrace(System.err);
return -1;
}
// Get the Minecraft Class.
Class<?> mc;
try
{
mc = cl.loadClass(mainClass);
} catch (ClassNotFoundException e)
{
System.err.println("Failed to find Minecraft main class:");
e.printStackTrace(System.err);
return -1;
}
// get the main method.
Method meth;
try
{
meth = mc.getMethod("main", String[].class);
} catch (NoSuchMethodException e)
{
System.err.println("Failed to acquire the main method:");
e.printStackTrace(System.err);
return -1;
}
// FIXME: works only on linux, we need a better solution
/*
final java.nio.ByteBuffer[] icons = IconLoader.load("icon.png");
new Thread() {
public void run() {
ClassLoader cl = ClassLoader.getSystemClassLoader();
try
{
Class<?> Display;
Method isCreated;
Method setTitle;
Method setIcon;
Display = cl.loadClass("org.lwjgl.opengl.Display");
isCreated = Display.getMethod("isCreated");
setTitle = Display.getMethod("setTitle", String.class);
setIcon = Display.getMethod("setIcon", java.nio.ByteBuffer[].class);
// set the window title? Maybe?
while(!(Boolean) isCreated.invoke(null))
{
try
{
Thread.sleep(150);
} catch (InterruptedException ignored) {}
}
// Give it a bit more time ;)
Thread.sleep(150);
// set the title
setTitle.invoke(null,windowTitle);
// only set icon when there's actually something to set...
if(icons.length > 0)
{
setIcon.invoke(null,(Object)icons);
}
}
catch (Exception e)
{
System.err.println("Couldn't set window icon or title.");
e.printStackTrace(System.err);
}
}
}
.start();
*/
// start Minecraft
String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); // init params accordingly
try
{
meth.invoke(null, (Object) paramsArray); // static method doesn't have an instance
} catch (Exception e)
{
System.err.println("Failed to start Minecraft:");
e.printStackTrace(System.err);
return -1;
}
return 0;
}
}

View File

@ -1,8 +0,0 @@
<RCC>
<!--
<qresource prefix="/java">
<file alias="launcher.jar">@MMC_BIN@/depends/launcher/MultiMCLauncher.jar</file>
<file alias="checker.jar">@MMC_BIN@/depends/javacheck/JavaCheck.jar</file>
</qresource>
-->
</RCC>

View File

@ -1,51 +0,0 @@
<RCC>
<qresource prefix="/icons/toolbar">
<file alias="about">resources/icons/toolbar/about.png</file>
<file alias="bug">resources/icons/toolbar/ReportBug.png</file>
<file alias="centralmods">resources/icons/toolbar/centralmods.png</file>
<file alias="checkupdate">resources/icons/toolbar/checkupdate.png</file>
<file alias="help">resources/icons/toolbar/help.png</file>
<file alias="new">resources/icons/toolbar/new.png</file>
<file alias="copy">resources/icons/toolbar/InstCopy.png</file>
<file alias="news">resources/icons/toolbar/NewsIcon.png</file>
<file alias="refresh">resources/icons/toolbar/refresh.png</file>
<file alias="settings">resources/icons/toolbar/settings.png</file>
<file alias="viewfolder">resources/icons/toolbar/viewfolder.png</file>
<file alias="cat">resources/icons/toolbar/Cat.png</file>
<file alias="noaccount">resources/icons/toolbar/NoAccount.png</file>
</qresource>
<qresource prefix="/icons/instances">
<file alias="brick">resources/icons/instances/brick.png</file>
<file alias="chicken">resources/icons/instances/chicken128.png</file>
<file alias="creeper">resources/icons/instances/creeper128.png</file>
<file alias="derp">resources/icons/instances/derp.png</file>
<file alias="diamond">resources/icons/instances/diamond.png</file>
<file alias="dirt">resources/icons/instances/dirt.png</file>
<file alias="enderman">resources/icons/instances/enderman.png</file>
<file alias="enderpearl">resources/icons/instances/enderpearl128.png</file>
<file alias="ftb-glow">resources/icons/instances/ftb_glow128.png</file>
<file alias="ftb-logo">resources/icons/instances/ftb_logo128.png</file>
<file alias="gear">resources/icons/instances/gear128.png</file>
<file alias="gold">resources/icons/instances/gold.png</file>
<file alias="grass">resources/icons/instances/grass.png</file>
<file alias="herobrine">resources/icons/instances/herobrine128.png</file>
<file alias="infinity">resources/icons/instances/infinity128.png</file>
<file alias="iron">resources/icons/instances/iron.png</file>
<file alias="magitech">resources/icons/instances/magitech128.png</file>
<file alias="meat">resources/icons/instances/meat128.png</file>
<file alias="netherstar">resources/icons/instances/netherstar128.png</file>
<file alias="planks">resources/icons/instances/planks.png</file>
<file alias="skeleton">resources/icons/instances/skeleton128.png</file>
<file alias="squarecreeper">resources/icons/instances/squarecreeper128.png</file>
<file alias="steve">resources/icons/instances/steve128.png</file>
<file alias="stone">resources/icons/instances/stone.png</file>
<file alias="tnt">resources/icons/instances/tnt.png</file>
</qresource>
<qresource prefix="/icons/multimc">
<file alias="scalable/apps/multimc.svg">resources/icons/multimc.svg</file>
<file alias="index.theme">resources/XdgIcon.theme</file>
</qresource>
<qresource prefix="/backgrounds">
<file alias="kitteh">resources/catbgrnd2.png</file>
</qresource>
</RCC>

View File

@ -140,6 +140,9 @@ void ConsoleWindow::write(QString data, MessageLevel::Enum mode)
else if (mode == MessageLevel::Debug)
while (iter.hasNext())
writeColor(iter.next(), "green");
else if (mode == MessageLevel::PrePost)
while (iter.hasNext())
writeColor(iter.next(), "grey");
// TODO: implement other MessageLevels
else
while (iter.hasNext())
@ -166,6 +169,10 @@ void ConsoleWindow::on_closeButton_clicked()
void ConsoleWindow::setMayClose(bool mayclose)
{
if(mayclose)
ui->closeButton->setText(tr("Close"));
else
ui->closeButton->setText(tr("Hide"));
m_mayclose = mayclose;
}

View File

@ -77,6 +77,8 @@
#include "logic/news/NewsChecker.h"
#include "logic/status/StatusChecker.h"
#include "logic/net/URLConstants.h"
#include "logic/BaseInstance.h"
@ -126,7 +128,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// Add the news label to the news toolbar.
{
newsLabel = new QToolButton();
newsLabel->setIcon(QIcon(":/icons/toolbar/news"));
newsLabel->setIcon(QIcon::fromTheme("news"));
newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel);
@ -199,7 +201,27 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
connect(MMC->instances().get(), SIGNAL(dataIsInvalid()), SLOT(selectionBad()));
m_statusLeft = new QLabel(tr("No instance selected"), this);
m_statusRight = new QLabel(tr("No status available"), this);
m_statusRefresh = new QToolButton(this);
m_statusRefresh->setCheckable(true);
m_statusRefresh->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_statusRefresh->setIcon(QIcon::fromTheme("refresh"));
statusBar()->addPermanentWidget(m_statusLeft, 1);
statusBar()->addPermanentWidget(m_statusRight, 0);
statusBar()->addPermanentWidget(m_statusRefresh, 0);
// Start status checker
{
connect(MMC->statusChecker().get(), &StatusChecker::statusLoaded, this, &MainWindow::updateStatusUI);
connect(MMC->statusChecker().get(), &StatusChecker::statusLoadingFailed, this, &MainWindow::updateStatusFailedUI);
connect(m_statusRefresh, &QAbstractButton::clicked, this, &MainWindow::reloadStatus);
connect(&statusTimer, &QTimer::timeout, this, &MainWindow::reloadStatus);
statusTimer.setSingleShot(true);
reloadStatus();
}
// Add "manage accounts" button, right align
QWidget *spacer = new QWidget();
@ -219,8 +241,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
accountMenuButton->setMenu(accountMenu);
accountMenuButton->setPopupMode(QToolButton::InstantPopup);
accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
accountMenuButton->setIcon(
QPixmap(":/icons/toolbar/noaccount").scaled(48, 48, Qt::KeepAspectRatio));
accountMenuButton->setIcon(QIcon::fromTheme("noaccount"));
QWidgetAction *accountMenuButtonAction = new QWidgetAction(this);
accountMenuButtonAction->setDefaultWidget(accountMenuButton);
@ -235,17 +256,20 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
connect(MMC->accounts().get(), &MojangAccountList::listChanged, [this]
{ repopulateAccountsMenu(); });
std::shared_ptr<MojangAccountList> accounts = MMC->accounts();
// Show initial account
activeAccountChanged();
auto accounts = MMC->accounts();
// TODO: Nicer way to iterate?
for (int i = 0; i < accounts->count(); i++)
{
MojangAccountPtr account = accounts->at(i);
auto account = accounts->at(i);
if (account != nullptr)
{
auto job = new NetJob("Startup player skins: " + account->username());
for (AccountProfile profile : account->profiles())
for (auto profile : account->profiles())
{
auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png");
auto action = CacheDownload::make(
@ -383,7 +407,7 @@ void MainWindow::repopulateAccountsMenu()
QAction *action = new QAction(tr("No Default Account"), this);
action->setCheckable(true);
action->setIcon(QPixmap(":/icons/toolbar/noaccount").scaled(48, 48, Qt::KeepAspectRatio));
action->setIcon(QIcon::fromTheme("noaccount"));
action->setData("");
if (active_username.isEmpty())
{
@ -437,8 +461,7 @@ void MainWindow::activeAccountChanged()
}
// Set the icon to the "no account" icon.
accountMenuButton->setIcon(
QPixmap(":/icons/toolbar/noaccount").scaled(48, 48, Qt::KeepAspectRatio));
accountMenuButton->setIcon(QIcon::fromTheme("noaccount"));
}
bool MainWindow::eventFilter(QObject *obj, QEvent *ev)
@ -495,6 +518,57 @@ void MainWindow::updateNewsLabel()
}
}
static QString convertStatus(const QString &status)
{
QString ret = "?";
if(status == "green") ret = "";
else if(status == "yellow") ret = "-";
else if(status == "red") ret="";
return "<span style=\"font-size:11pt; font-weight:600;\">" + ret + "</span>";
}
void MainWindow::reloadStatus()
{
m_statusRefresh->setChecked(true);
MMC->statusChecker()->reloadStatus();
//updateStatusUI();
}
static QString makeStatusString(const QMap<QString, QString> statuses)
{
QString status = "";
status += "Web: " + convertStatus(statuses["minecraft.net"]);
status += " Account: " + convertStatus(statuses["account.mojang.com"]);
status += " Skins: " + convertStatus(statuses["skins.minecraft.net"]);
status += " Auth: " + convertStatus(statuses["authserver.mojang.com"]);
status += " Session: " + convertStatus(statuses["sessionserver.mojang.com"]);
return status;
}
void MainWindow::updateStatusUI()
{
auto statusChecker = MMC->statusChecker();
auto statuses = statusChecker->getStatusEntries();
QString status = makeStatusString(statuses);
m_statusRefresh->setChecked(false);
m_statusRight->setText(status);
statusTimer.start(60 * 1000);
}
void MainWindow::updateStatusFailedUI()
{
m_statusRight->setText(makeStatusString(QMap<QString, QString>()));
m_statusRefresh->setChecked(false);
statusTimer.start(60 * 1000);
}
void MainWindow::updateAvailable(QString repo, QString versionName, int versionId)
{
UpdateDialog dlg;
@ -756,7 +830,7 @@ void MainWindow::on_actionChangeInstIcon_triggered()
if (dlg.result() == QDialog::Accepted)
{
m_selectedInstance->setIconKey(dlg.selectedIconKey);
auto ico = MMC->icons()->getIcon(dlg.selectedIconKey);
auto ico = MMC->icons()->getBigIcon(dlg.selectedIconKey);
ui->actionChangeInstIcon->setIcon(ico);
}
}
@ -765,14 +839,14 @@ void MainWindow::iconUpdated(QString icon)
{
if(icon == m_currentInstIcon)
{
ui->actionChangeInstIcon->setIcon(MMC->icons()->getIcon(m_currentInstIcon));
ui->actionChangeInstIcon->setIcon(MMC->icons()->getBigIcon(m_currentInstIcon));
}
}
void MainWindow::updateInstanceToolIcon(QString new_icon)
{
m_currentInstIcon = new_icon;
ui->actionChangeInstIcon->setIcon(MMC->icons()->getIcon(m_currentInstIcon));
ui->actionChangeInstIcon->setIcon(MMC->icons()->getBigIcon(m_currentInstIcon));
}
void MainWindow::setSelectedInstanceById(const QString &id)

View File

@ -17,6 +17,7 @@
#include <QMainWindow>
#include <QProcess>
#include <QTimer>
#include "logic/lists/InstanceList.h"
#include "logic/BaseInstance.h"
@ -169,6 +170,12 @@ slots:
void updateNewsLabel();
void updateStatusUI();
void updateStatusFailedUI();
void reloadStatus();
/*!
* Runs the DownloadUpdateTask and installs updates.
*/
@ -198,8 +205,12 @@ private:
Task *m_versionLoadTask;
QLabel *m_statusLeft;
QLabel *m_statusRight;
QToolButton *m_statusRefresh;
QMenu *accountMenu;
QToolButton *accountMenuButton;
QAction *manageAccountsAction;
QTimer statusTimer;
};

View File

@ -14,7 +14,7 @@
<string>MultiMC 5</string>
</property>
<property name="windowIcon">
<iconset resource="../graphics.qrc">
<iconset resource="../resources/multimc/multimc.qrc">
<normaloff>:/icons/multimc/scalable/apps/multimc.svg</normaloff>:/icons/multimc/scalable/apps/multimc.svg</iconset>
</property>
<widget class="QWidget" name="centralWidget">
@ -152,8 +152,7 @@
</widget>
<action name="actionAddInstance">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/new</normaloff>:/icons/toolbar/new</iconset>
<iconset theme="new"/>
</property>
<property name="text">
<string>Add Instance</string>
@ -167,8 +166,7 @@
</action>
<action name="actionViewInstanceFolder">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/viewfolder</normaloff>:/icons/toolbar/viewfolder</iconset>
<iconset theme="viewfolder"/>
</property>
<property name="text">
<string>View Instance Folder</string>
@ -182,8 +180,7 @@
</action>
<action name="actionRefresh">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/refresh</normaloff>:/icons/toolbar/refresh</iconset>
<iconset theme="refresh"/>
</property>
<property name="text">
<string>Refresh</string>
@ -197,8 +194,7 @@
</action>
<action name="actionViewCentralModsFolder">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/centralmods</normaloff>:/icons/toolbar/centralmods</iconset>
<iconset theme="centralmods"/>
</property>
<property name="text">
<string>View Central Mods Folder</string>
@ -212,8 +208,7 @@
</action>
<action name="actionCheckUpdate">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/checkupdate</normaloff>:/icons/toolbar/checkupdate</iconset>
<iconset theme="checkupdate"/>
</property>
<property name="text">
<string>Check for Updates</string>
@ -227,8 +222,7 @@
</action>
<action name="actionSettings">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/settings</normaloff>:/icons/toolbar/settings</iconset>
<iconset theme="settings"/>
</property>
<property name="text">
<string>Settings</string>
@ -245,8 +239,7 @@
</action>
<action name="actionReportBug">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/bug</normaloff>:/icons/toolbar/bug</iconset>
<iconset theme="bug"/>
</property>
<property name="text">
<string>Report a Bug</string>
@ -260,8 +253,7 @@
</action>
<action name="actionMoreNews">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/news</normaloff>:/icons/toolbar/news</iconset>
<iconset theme="news"/>
</property>
<property name="text">
<string>More News</string>
@ -278,8 +270,7 @@
</action>
<action name="actionAbout">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/about</normaloff>:/icons/toolbar/about</iconset>
<iconset theme="about"/>
</property>
<property name="text">
<string>About MultiMC</string>
@ -332,7 +323,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../graphics.qrc">
<iconset resource="../resources/instances/instances.qrc">
<normaloff>:/icons/instances/infinity</normaloff>:/icons/instances/infinity</iconset>
</property>
<property name="text">
@ -472,20 +463,18 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/cat</normaloff>:/icons/toolbar/cat</iconset>
<iconset theme="cat"/>
</property>
<property name="text">
<string>Meow</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-weight:600; color:#ff0004;&quot;&gt;Catnarok!&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;Or just a cat with a ball of yarn?&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;WHO KNOWS?!&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;:/icons/instances/tnt&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;It's a fluffy kitty :3&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</action>
<action name="actionCopyInstance">
<property name="icon">
<iconset resource="../graphics.qrc">
<normaloff>:/icons/toolbar/copy</normaloff>:/icons/toolbar/copy</iconset>
<iconset theme="copy"/>
</property>
<property name="text">
<string>Copy Instance</string>
@ -508,7 +497,9 @@
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
<include location="../graphics.qrc"/>
<include location="../resources/instances/instances.qrc"/>
<include location="../resources/multimc/multimc.qrc"/>
<include location="../resources/backgrounds/backgrounds.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -24,6 +24,8 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
MultiMCPlatform::fixWM_CLASS(this);
ui->setupUi(this);
ui->urlLabel->setOpenExternalLinks(true);
ui->icon->setPixmap(QIcon(":/icons/multimc/scalable/apps/multimc.svg").pixmap(64));
ui->title->setText("MultiMC 5 " + MMC->version().toString());

View File

@ -104,7 +104,7 @@
<x>0</x>
<y>0</y>
<width>689</width>
<height>331</height>
<height>311</height>
</rect>
</property>
<attribute name="label">
@ -229,7 +229,7 @@
<x>0</x>
<y>0</y>
<width>689</width>
<height>331</height>
<height>311</height>
</rect>
</property>
<attribute name="label">
@ -245,7 +245,7 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;&quot;&gt;MultiMC&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;Andrew Okin &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:forkk@forkk.net&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;forkk@forkk.net&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;Petr Mrázek &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:peterix@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;peterix@gmail.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
@ -282,7 +282,7 @@ p, li { white-space: pre-wrap; }
<x>0</x>
<y>0</y>
<width>689</width>
<height>331</height>
<height>311</height>
</rect>
</property>
<attribute name="label">
@ -309,7 +309,7 @@ p, li { white-space: pre-wrap; }
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;MultiMC&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Copyright 2012-2014 MultiMC Contributors&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Licensed under the Apache License, Version 2.0 (the &amp;quot;License&amp;quot;);&lt;/span&gt;&lt;/p&gt;
@ -430,7 +430,36 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; *&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * This file has been put into the public domain.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * You can do whatever you want with this file.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; */&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; */&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;center&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;&quot;&gt;Java IconLoader class&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Copyright (c) 2011, Chris Molini&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;All rights reserved.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;Redistribution and use in source and binary forms, with or without&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;modification, are permitted provided that the following conditions are met:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Redistributions of source code must retain the above copyright&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; notice, this list of conditions and the following disclaimer.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Redistributions in binary form must reproduce the above copyright&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; notice, this list of conditions and the following disclaimer in the&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; documentation and/or other materials provided with the distribution.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; * Neither the name of the &amp;lt;organization&amp;gt; nor the&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; names of its contributors may be used to endorse or promote products&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt; derived from this software without specific prior written permission.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &amp;quot;AS IS&amp;quot; AND&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;DISCLAIMED. IN NO EVENT SHALL &amp;lt;COPYRIGHT HOLDER&amp;gt; BE LIABLE FOR ANY&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8pt;&quot;&gt;SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
@ -442,7 +471,7 @@ p, li { white-space: pre-wrap; }
<x>0</x>
<y>0</y>
<width>689</width>
<height>331</height>
<height>311</height>
</rect>
</property>
<attribute name="label">
@ -455,12 +484,12 @@ p, li { white-space: pre-wrap; }
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;Part of the reason for using the Apache license is we don't want people using the &amp;quot;MultiMC&amp;quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &amp;quot;MultiMC&amp;quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork &lt;/span&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:600;&quot;&gt;without&lt;/span&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt; implying that you have our blessing.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Part of the reason for using the Apache license is we don't want people using the &amp;quot;MultiMC&amp;quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &amp;quot;MultiMC&amp;quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork &lt;span style=&quot; font-weight:600;&quot;&gt;without&lt;/span&gt; implying that you have our blessing.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>

View File

@ -168,6 +168,12 @@
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="1">
<widget class="QSpinBox" name="maxMemSpinBox">
<property name="toolTip">
<string>The maximum amount of memory Minecraft is allowed to use.</string>
</property>
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>512</number>
</property>
@ -198,6 +204,12 @@
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="minMemSpinBox">
<property name="toolTip">
<string>The amount of memory Minecraft is started with.</string>
</property>
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>256</number>
</property>
@ -214,6 +226,12 @@
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="permGenSpinBox">
<property name="toolTip">
<string>The amount of memory available to store loaded Java classes.</string>
</property>
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>64</number>
</property>

View File

@ -50,8 +50,8 @@ void showWebsiteForMod(QWidget *parentDlg, Mod &m)
else
{
CustomMessageBox::selectable(
parentDlg, parentDlg->tr("How sad!"),
parentDlg->tr("The mod author didn't provide a website link for this mod."),
parentDlg, QObject::tr("How sad!"),
QObject::tr("The mod author didn't provide a website link for this mod."),
QMessageBox::Warning);
}
}

View File

@ -269,6 +269,10 @@ void SettingsDialog::refreshUpdateChannelDesc()
// Get the channel list.
QList<UpdateChecker::ChannelListEntry> channelList = MMC->updateChecker()->getChannelList();
int selectedIndex = ui->updateChannelComboBox->currentIndex();
if(selectedIndex < 0)
{
return;
}
if (selectedIndex < channelList.count())
{
// Find the channel list entry with the given index.
@ -284,6 +288,9 @@ void SettingsDialog::refreshUpdateChannelDesc()
void SettingsDialog::applySettings(SettingsObject *s)
{
// Language
s->set("Language", ui->languageBox->itemData(ui->languageBox->currentIndex()).toLocale().bcp47Name());
// Updates
s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
s->set("UpdateChannel", m_currentUpdateChannel);
@ -365,6 +372,19 @@ void SettingsDialog::applySettings(SettingsObject *s)
void SettingsDialog::loadSettings(SettingsObject *s)
{
// Language
ui->languageBox->clear();
ui->languageBox->addItem(tr("English"), QLocale(QLocale::English));
foreach(const QString & lang,
QDir(MMC->root() + "/translations").entryList(QStringList() << "*.qm", QDir::Files))
{
QLocale locale(lang.section(QRegExp("[_\.]"), 1));
ui->languageBox->addItem(
QLocale::languageToString(locale.language()),
locale);
}
ui->languageBox->setCurrentIndex(ui->languageBox->findData(QLocale(s->get("Language").toString())));
// Updates
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
m_currentUpdateChannel = s->get("UpdateChannel").toString();

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>526</width>
<height>723</height>
<width>545</width>
<height>609</height>
</rect>
</property>
<property name="sizePolicy">
@ -35,43 +35,11 @@
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="generalTab">
<widget class="QWidget" name="featuresTab">
<attribute name="title">
<string>General</string>
<string>Features</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="sortingModeBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Sorting Mode</string>
</property>
<layout class="QHBoxLayout" name="sortingModeBoxLayout">
<item>
<widget class="QRadioButton" name="sortLastLaunchedBtn">
<property name="text">
<string>By last launched</string>
</property>
<attribute name="buttonGroup">
<string notr="true">sortingModeGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="sortByNameBtn">
<property name="text">
<string>By name</string>
</property>
<attribute name="buttonGroup">
<string notr="true">sortingModeGroup</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QGroupBox" name="updateSettingsBox">
<property name="title">
@ -85,8 +53,6 @@
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="channelVerticalLayout">
<item>
<widget class="QLabel" name="updateChannelLabel">
<property name="text">
@ -112,8 +78,6 @@
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
@ -280,6 +244,78 @@
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="generalTab">
<attribute name="title">
<string>User Interface</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Language (needs restart):</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="languageBox"/>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="sortingModeBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Sorting Mode</string>
</property>
<layout class="QHBoxLayout" name="sortingModeBoxLayout">
<item>
<widget class="QRadioButton" name="sortLastLaunchedBtn">
<property name="text">
<string>By last launched</string>
</property>
<attribute name="buttonGroup">
<string notr="true">sortingModeGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="sortByNameBtn">
<property name="text">
<string>By name</string>
</property>
<attribute name="buttonGroup">
<string notr="true">sortingModeGroup</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="editorsBox">
<property name="title">
@ -427,6 +463,247 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="javaTab">
<attribute name="title">
<string>Java</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QGroupBox" name="memoryGroupBox">
<property name="title">
<string>Memory</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="1">
<widget class="QSpinBox" name="maxMemSpinBox">
<property name="toolTip">
<string>The maximum amount of memory Minecraft is allowed to use.</string>
</property>
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>512</number>
</property>
<property name="maximum">
<number>65536</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>1024</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelMinMem">
<property name="text">
<string>Minimum memory allocation:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelMaxMem">
<property name="text">
<string>Maximum memory allocation:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="minMemSpinBox">
<property name="toolTip">
<string>The amount of memory Minecraft is started with.</string>
</property>
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>256</number>
</property>
<property name="maximum">
<number>65536</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>256</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelPermGen">
<property name="text">
<string>PermGen:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="permGenSpinBox">
<property name="toolTip">
<string>The amount of memory available to store loaded Java classes.</string>
</property>
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>64</number>
</property>
<property name="maximum">
<number>999999999</number>
</property>
<property name="singleStep">
<number>8</number>
</property>
<property name="value">
<number>64</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="javaSettingsGroupBox">
<property name="title">
<string>Java Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="labelJavaPath">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Java path:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="javaDetectBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Auto-detect...</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="javaTestBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Test</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>JVM arguments:</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="javaPathTextBox"/>
</item>
<item>
<widget class="QPushButton" name="javaBrowseBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>28</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="jvmArgsTextBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="customCommandsGroupBox">
<property name="title">
<string>Custom Commands</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0">
<widget class="QLabel" name="labelPostExitCmd">
<property name="text">
<string>Post-exit command:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelPreLaunchCmd">
<property name="text">
<string>Pre-launch command:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="preLaunchCmdTextBox"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="postExitCmdTextBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="labelCustomCmdsDescription">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Pre-launch command runs before the instance launches and post-exit command runs after it exits. Both will be run in MultiMC's working directory with INST_ID, INST_DIR, and INST_NAME as environment variables.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="networkTab">
<property name="toolTip">
<string>Network settings.</string>
@ -586,229 +863,6 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="javaTab">
<attribute name="title">
<string>Java</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QGroupBox" name="memoryGroupBox">
<property name="title">
<string>Memory</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="1">
<widget class="QSpinBox" name="maxMemSpinBox">
<property name="minimum">
<number>512</number>
</property>
<property name="maximum">
<number>65536</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>1024</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelMinMem">
<property name="text">
<string>Minimum memory allocation:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelMaxMem">
<property name="text">
<string>Maximum memory allocation:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="minMemSpinBox">
<property name="minimum">
<number>256</number>
</property>
<property name="maximum">
<number>65536</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>256</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelPermGen">
<property name="text">
<string>PermGen:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="permGenSpinBox">
<property name="minimum">
<number>64</number>
</property>
<property name="maximum">
<number>999999999</number>
</property>
<property name="singleStep">
<number>8</number>
</property>
<property name="value">
<number>64</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="javaSettingsGroupBox">
<property name="title">
<string>Java Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="labelJavaPath">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Java path:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="javaDetectBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Auto-detect...</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="javaTestBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Test</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>JVM arguments:</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="javaPathTextBox"/>
</item>
<item>
<widget class="QPushButton" name="javaBrowseBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>28</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="jvmArgsTextBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="customCommandsGroupBox">
<property name="title">
<string>Custom Commands</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0">
<widget class="QLabel" name="labelPostExitCmd">
<property name="text">
<string>Post-exit command:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelPreLaunchCmd">
<property name="text">
<string>Pre-launch command:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="preLaunchCmdTextBox"/>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="postExitCmdTextBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="labelCustomCmdsDescription">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Pre-launch command runs before the instance launches and post-exit command runs after it exits. Both will be run in MultiMC's working directory with INST_ID, INST_DIR, and INST_NAME as environment variables.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
@ -827,20 +881,6 @@
<tabstop>buttonBox</tabstop>
<tabstop>sortLastLaunchedBtn</tabstop>
<tabstop>sortByNameBtn</tabstop>
<tabstop>autoUpdateCheckBox</tabstop>
<tabstop>trackFtbBox</tabstop>
<tabstop>ftbLauncherBox</tabstop>
<tabstop>ftbLauncherBrowseBtn</tabstop>
<tabstop>ftbBox</tabstop>
<tabstop>ftbBrowseBtn</tabstop>
<tabstop>instDirTextBox</tabstop>
<tabstop>instDirBrowseBtn</tabstop>
<tabstop>modsDirTextBox</tabstop>
<tabstop>modsDirBrowseBtn</tabstop>
<tabstop>lwjglDirTextBox</tabstop>
<tabstop>lwjglDirBrowseBtn</tabstop>
<tabstop>iconsDirTextBox</tabstop>
<tabstop>iconsDirBrowseBtn</tabstop>
<tabstop>jsonEditorTextBox</tabstop>
<tabstop>jsonEditorBrowseBtn</tabstop>
<tabstop>maximizedCheckBox</tabstop>

View File

@ -26,6 +26,7 @@
#include "overridesetting.h"
#include "pathutils.h"
#include <cmdutils.h>
#include "lists/MinecraftVersionList.h"
#include "logic/icons/IconList.h"
@ -81,6 +82,7 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
settings().registerSetting("OverrideConsole", false);
settings().registerOverride(globalSettings->getSetting("ShowConsole"));
settings().registerOverride(globalSettings->getSetting("AutoCloseConsole"));
settings().registerOverride(globalSettings->getSetting("LogPrePostOutput"));
}
void BaseInstance::iconUpdated(QString key)
@ -248,8 +250,19 @@ void BaseInstance::setName(QString val)
d->m_settings->set("name", val);
emit propertiesChanged(this);
}
QString BaseInstance::name() const
{
I_D(BaseInstance);
return d->m_settings->get("name").toString();
}
QString BaseInstance::windowTitle() const
{
return "MultiMC: " + name();
}
QStringList BaseInstance::extraArguments() const
{
return Util::Commandline::splitArgs(settings().get("JvmArgs").toString());
}

View File

@ -57,7 +57,7 @@ public:
/// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to
/// be unique.
QString id() const;
virtual QString id() const;
/// get the type of this instance
QString instanceType() const;
@ -71,6 +71,9 @@ public:
QString name() const;
void setName(QString val);
/// Value used for instance window titles
QString windowTitle() const;
QString iconKey() const;
void setIconKey(QString val);
@ -81,6 +84,8 @@ public:
void setGroupInitial(QString val);
void setGroupPost(QString val);
QStringList extraArguments() const;
virtual QString intendedVersionId() const = 0;
virtual bool setIntendedVersionId(QString version) = 0;

View File

@ -39,6 +39,15 @@ struct BaseVersion
* the kind of version this is (Stable, Beta, Snapshot, whatever)
*/
virtual QString typeString() const = 0;
virtual bool operator<(BaseVersion &a)
{
return name() < a.name();
};
virtual bool operator>(BaseVersion &a)
{
return name() > a.name();
};
};
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;

View File

@ -20,6 +20,9 @@ void JavaChecker::performCheck()
process->setArguments(args);
process->setProgram(path);
process->setProcessChannelMode(QProcess::SeparateChannels);
QLOG_DEBUG() << "Running java checker!";
QLOG_DEBUG() << "Java: " + path;
QLOG_DEBUG() << "Args: {" + args.join("|") + "}";
connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this,
SLOT(finished(int, QProcess::ExitStatus)));
@ -42,15 +45,19 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.path = path;
result.id = id;
}
QLOG_DEBUG() << "Java checker finished with status " << status << " exit code " << exitcode;
if (status == QProcess::CrashExit || exitcode == 1)
{
QLOG_DEBUG() << "Java checker failed!";
emit checkFinished(result);
return;
}
bool success = true;
QString p_stdout = _process->readAllStandardOutput();
QLOG_DEBUG() << p_stdout;
QMap<QString, QString> results;
QStringList lines = p_stdout.split("\n", QString::SkipEmptyParts);
for(QString line : lines)
@ -70,6 +77,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
if(!results.contains("os.arch") || !results.contains("java.version") || !success)
{
QLOG_DEBUG() << "Java checker failed - couldn't extract required information.";
emit checkFinished(result);
return;
}
@ -84,7 +92,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch;
result.javaVersion = java_version;
QLOG_DEBUG() << "Java checker succeeded.";
emit checkFinished(result);
}
@ -93,7 +101,7 @@ void JavaChecker::error(QProcess::ProcessError err)
if(err == QProcess::FailedToStart)
{
killTimer.stop();
QLOG_DEBUG() << "Java checker has failed to start.";
JavaCheckResult result;
{
result.path = path;
@ -110,6 +118,7 @@ void JavaChecker::timeout()
// NO MERCY. NO ABUSE.
if(process)
{
QLOG_DEBUG() << "Java checker has been killed by timeout.";
process->kill();
}
}

View File

@ -14,3 +14,8 @@ bool LegacyFTBInstance::menuActionEnabled(QString action_name) const
{
return false;
}
QString LegacyFTBInstance::id() const
{
return "FTB/" + BaseInstance::id();
}

View File

@ -10,4 +10,5 @@ public:
QObject *parent = 0);
virtual QString getStatusbarDescription();
virtual bool menuActionEnabled(QString action_name) const;
virtual QString id() const;
};

View File

@ -58,58 +58,27 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(MojangAccountPtr account)
auto pixmap = icon.pixmap(128, 128);
pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG");
// extract the legacy launcher
QString launcherJar = PathCombine(MMC->bin(), "jars", "MultiMCLauncher.jar");
// set the process arguments
// create the launch script
QString launchScript;
{
QStringList args;
// window size
QString windowSize;
QString windowParams;
if (settings().get("LaunchMaximized").toBool())
windowSize = "max";
windowParams = "max";
else
windowSize = QString("%1x%2").arg(settings().get("MinecraftWinWidth").toInt()).arg(
windowParams = QString("%1x%2").arg(settings().get("MinecraftWinWidth").toInt()).arg(
settings().get("MinecraftWinHeight").toInt());
// window title
QString windowTitle;
windowTitle.append("MultiMC: ").append(name());
// Java arguments
args.append(Util::Commandline::splitArgs(settings().get("JvmArgs").toString()));
#ifdef OSX
// OSX dock icon and name
args << "-Xdock:icon=icon.png";
args << QString("-Xdock:name=\"%1\"").arg(windowTitle);
#endif
QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion())
.absolutePath();
// launcher arguments
args << QString("-Xms%1m").arg(settings().get("MinMemAlloc").toInt());
args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt());
args << QString("-XX:PermSize=%1m").arg(settings().get("PermGen").toInt());
/**
* HACK: Stupid hack for Intel drivers.
* See: https://mojang.atlassian.net/browse/MCL-767
*/
#ifdef Q_OS_WIN32
args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
"minecraft.exe.heapdump");
#endif
args << "-jar" << launcherJar;
args << account->currentProfile()->name;
args << account->sessionId();
args << windowTitle;
args << windowSize;
args << lwjgl;
proc->setArguments(args);
launchScript += "userName " + account->currentProfile()->name + "\n";
launchScript += "sessionId " + account->sessionId() + "\n";
launchScript += "windowTitle " + windowTitle() + "\n";
launchScript += "windowParams " + windowParams + "\n";
launchScript += "lwjgl " + lwjgl + "\n";
launchScript += "launch legacy\n";
}
proc->setLaunchScript(launchScript);
// set the process work path
proc->setWorkdir(minecraftRoot());

View File

@ -14,14 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MultiMC.h"
#include "MinecraftProcess.h"
#include <QDataStream>
#include <QFile>
#include <QDir>
//#include <QImage>
#include <QProcessEnvironment>
#include <QRegularExpression>
#include "BaseInstance.h"
@ -42,6 +43,7 @@ MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst)
#ifdef LINUX
// Strip IBus
// IBus is a Linux IME framework. For some reason, it breaks MC?
if (env.value("XMODIFIERS").contains(IBUS))
env.insert("XMODIFIERS", env.value("XMODIFIERS").replace(IBUS, ""));
#endif
@ -57,11 +59,15 @@ MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst)
// std channels
connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr()));
connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut()));
}
void MinecraftProcess::setArguments(QStringList args)
// Log prepost launch command output (can be disabled.)
if (m_instance->settings().get("LogPrePostOutput").toBool())
{
m_args = args;
connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardError,
this, &MinecraftProcess::on_prepost_stdErr);
connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput,
this, &MinecraftProcess::on_prepost_stdOut);
}
}
void MinecraftProcess::setWorkdir(QString path)
@ -90,47 +96,145 @@ QString MinecraftProcess::censorPrivateInfo(QString in)
in.replace(profileId, "<PROFILE ID>");
in.replace(profileName, "<PROFILE NAME>");
}
auto i = m_account->user().properties.begin();
while (i != m_account->user().properties.end())
{
in.replace(i.value(), "<" + i.key().toUpper() + ">");
++i;
}
return in;
}
// console window
MessageLevel::Enum MinecraftProcess::guessLevel(const QString &line, MessageLevel::Enum level)
{
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") ||
line.contains("[FINER]") || line.contains("[FINEST]"))
level = MessageLevel::Message;
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
level = MessageLevel::Error;
if (line.contains("[WARNING]"))
level = MessageLevel::Warning;
if (line.contains("Exception in thread") || line.contains(" at "))
level = MessageLevel::Fatal;
if (line.contains("[DEBUG]"))
level = MessageLevel::Debug;
return level;
}
MessageLevel::Enum MinecraftProcess::getLevel(const QString &levelName)
{
if (levelName == "MultiMC")
return MessageLevel::MultiMC;
else if (levelName == "Debug")
return MessageLevel::Debug;
else if (levelName == "Info")
return MessageLevel::Info;
else if (levelName == "Message")
return MessageLevel::Message;
else if (levelName == "Warning")
return MessageLevel::Warning;
else if (levelName == "Error")
return MessageLevel::Error;
else if (levelName == "Fatal")
return MessageLevel::Fatal;
// Skip PrePost, it's not exposed to !![]!
else
return MessageLevel::Message;
}
void MinecraftProcess::logOutput(const QStringList &lines,
MessageLevel::Enum defaultLevel,
bool guessLevel, bool censor)
{
for (int i = 0; i < lines.size(); ++i)
logOutput(lines[i], defaultLevel, guessLevel, censor);
}
void MinecraftProcess::logOutput(QString line,
MessageLevel::Enum defaultLevel,
bool guessLevel, bool censor)
{
MessageLevel::Enum level = defaultLevel;
// Level prefix
int endmark = line.indexOf("]!");
if (line.startsWith("!![") && endmark != -1)
{
level = getLevel(line.left(endmark).mid(3));
line = line.mid(endmark + 2);
}
// Guess level
else if (guessLevel)
level = this->guessLevel(line, defaultLevel);
if (censor)
line = censorPrivateInfo(line);
emit log(line, level);
}
void MinecraftProcess::on_stdErr()
{
QByteArray data = readAllStandardError();
QString str = m_err_leftover + QString::fromLocal8Bit(data);
m_err_leftover.clear();
QStringList lines = str.split("\n");
bool complete = str.endsWith("\n");
for (int i = 0; i < lines.size() - 1; i++)
{
QString &line = lines[i];
emit log(censorPrivateInfo(line), getLevel(line, MessageLevel::Error));
}
if (!complete)
m_err_leftover = lines.last();
QStringList lines = str.split("\n");
m_err_leftover = lines.takeLast();
logOutput(lines, MessageLevel::Error);
}
void MinecraftProcess::on_stdOut()
{
QByteArray data = readAllStandardOutput();
QString str = m_out_leftover + QString::fromLocal8Bit(data);
m_out_leftover.clear();
QStringList lines = str.split("\n");
bool complete = str.endsWith("\n");
for (int i = 0; i < lines.size() - 1; i++)
{
QString &line = lines[i];
emit log(censorPrivateInfo(line), getLevel(line, MessageLevel::Message));
QStringList lines = str.split("\n");
m_out_leftover = lines.takeLast();
logOutput(lines);
}
if (!complete)
m_out_leftover = lines.last();
void MinecraftProcess::on_prepost_stdErr()
{
QByteArray data = m_prepostlaunchprocess.readAllStandardError();
QString str = m_err_leftover + QString::fromLocal8Bit(data);
QStringList lines = str.split("\n");
m_err_leftover = lines.takeLast();
logOutput(lines, MessageLevel::PrePost, false, false);
}
void MinecraftProcess::on_prepost_stdOut()
{
QByteArray data = m_prepostlaunchprocess.readAllStandardOutput();
QString str = m_out_leftover + QString::fromLocal8Bit(data);
QStringList lines = str.split("\n");
m_out_leftover = lines.takeLast();
logOutput(lines, MessageLevel::PrePost, false, false);
}
// exit handler
void MinecraftProcess::finish(int code, ExitStatus status)
{
// Flush console window
if (!m_err_leftover.isEmpty())
{
logOutput(m_err_leftover, MessageLevel::Error);
m_err_leftover.clear();
}
if (!m_out_leftover.isEmpty())
{
logOutput(m_out_leftover);
m_out_leftover.clear();
}
if (!killed)
{
if (status == NormalExit)
@ -153,15 +257,32 @@ void MinecraftProcess::finish(int code, ExitStatus status)
m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code));
// run post-exit
if (!m_instance->settings().get("PostExitCommand").toString().isEmpty())
QString postlaunch_cmd = m_instance->settings().get("PostExitCommand").toString();
if (!postlaunch_cmd.isEmpty())
{
m_prepostlaunchprocess.start(m_instance->settings().get("PostExitCommand").toString());
emit log(tr("Running Post-Launch command: %1").arg(postlaunch_cmd));
m_prepostlaunchprocess.start(postlaunch_cmd);
m_prepostlaunchprocess.waitForFinished();
// Flush console window
if (!m_err_leftover.isEmpty())
{
logOutput(m_err_leftover, MessageLevel::PrePost);
m_err_leftover.clear();
}
if (!m_out_leftover.isEmpty())
{
logOutput(m_out_leftover, MessageLevel::PrePost);
m_out_leftover.clear();
}
if (m_prepostlaunchprocess.exitStatus() != NormalExit)
{
emit log(tr("Post-Launch command failed with code %1.\n\n").arg(m_prepostlaunchprocess.exitCode()),
MessageLevel::Error);
emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(),
m_prepostlaunchprocess.exitStatus());
}
else
emit log(tr("Post-Launch command ran successfully.\n\n"));
}
m_instance->cleanupAfterRun();
emit ended(m_instance, code, status);
@ -175,27 +296,78 @@ void MinecraftProcess::killMinecraft()
void MinecraftProcess::launch()
{
if (!m_instance->settings().get("PreLaunchCommand").toString().isEmpty())
emit log("MultiMC version: " + MMC->version().toString() + "\n\n");
emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n");
QString prelaunch_cmd = m_instance->settings().get("PreLaunchCommand").toString();
if (!prelaunch_cmd.isEmpty())
{
m_prepostlaunchprocess.start(m_instance->settings().get("PreLaunchCommand").toString());
// Launch
emit log(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd));
m_prepostlaunchprocess.start(prelaunch_cmd);
// Wait
m_prepostlaunchprocess.waitForFinished();
// Flush console window
if (!m_err_leftover.isEmpty())
{
logOutput(m_err_leftover, MessageLevel::PrePost);
m_err_leftover.clear();
}
if (!m_out_leftover.isEmpty())
{
logOutput(m_out_leftover, MessageLevel::PrePost);
m_out_leftover.clear();
}
// Process return values
if (m_prepostlaunchprocess.exitStatus() != NormalExit)
{
emit log(tr("Pre-Launch command failed with code %1.\n\n").arg(m_prepostlaunchprocess.exitCode()),
MessageLevel::Fatal);
m_instance->cleanupAfterRun();
emit prelaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(),
m_prepostlaunchprocess.exitStatus());
return;
}
else
emit log(tr("Pre-Launch command ran successfully.\n\n"));
}
m_instance->setLastLaunch();
auto &settings = m_instance->settings();
//////////// java arguments ////////////
QStringList args;
{
// custom args go first. we want to override them if we have our own here.
args.append(m_instance->extraArguments());
// OSX dock icon and name
#ifdef OSX
args << "-Xdock:icon=icon.png";
args << QString("-Xdock:name=\"%1\"").arg(m_instance->windowTitle());
#endif
// HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767
#ifdef Q_OS_WIN32
args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
"minecraft.exe.heapdump");
#endif
args << QString("-Xms%1m").arg(settings.get("MinMemAlloc").toInt());
args << QString("-Xmx%1m").arg(settings.get("MaxMemAlloc").toInt());
args << QString("-XX:PermSize=%1m").arg(settings.get("PermGen").toInt());
if(!m_nativeFolder.isEmpty())
args << QString("-Djava.library.path=%1").arg(m_nativeFolder);
args << "-jar" << PathCombine(MMC->bin(), "jars", "NewLaunch.jar");
}
emit log(QString("Minecraft folder is: '%1'").arg(workingDirectory()));
QString JavaPath = m_instance->settings().get("JavaPath").toString();
emit log(QString("Java path: '%1'").arg(JavaPath));
QString allArgs = m_args.join("' '");
emit log(QString("Arguments: '%1'").arg(censorPrivateInfo(allArgs)));
start(JavaPath, m_args);
emit log("Java path is:\n" + JavaPath + "\n\n");
QString allArgs = args.join(", ");
emit log("Java Arguments:\n[" + censorPrivateInfo(allArgs) + "]\n\n");
// instantiate the launcher part
start(JavaPath, args);
if (!waitForStarted())
{
//: Error message displayed if instace can't start
@ -204,21 +376,7 @@ void MinecraftProcess::launch()
emit launch_failed(m_instance);
return;
}
}
MessageLevel::Enum MinecraftProcess::getLevel(const QString &line, MessageLevel::Enum level)
{
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") ||
line.contains("[FINER]") || line.contains("[FINEST]"))
level = MessageLevel::Message;
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
level = MessageLevel::Error;
if (line.contains("[WARNING]"))
level = MessageLevel::Warning;
if (line.contains("Exception in thread") || line.contains(" at "))
level = MessageLevel::Fatal;
if (line.contains("[DEBUG]"))
level = MessageLevel::Debug;
return level;
// send the launch script to the launcher part
QByteArray bytes = launchScript.toUtf8();
writeData(bytes.constData(), bytes.length());
}

View File

@ -18,7 +18,7 @@
#pragma once
#include <QProcess>
#include <QString>
#include "BaseInstance.h"
/**
@ -35,7 +35,8 @@ enum Enum
Message, /**< Standard Messages */
Warning, /**< Warnings */
Error, /**< Errors */
Fatal /**< Fatal Errors */
Fatal, /**< Fatal Errors */
PrePost, /**< Pre/Post Launch command output */
};
}
@ -65,7 +66,15 @@ public:
void setWorkdir(QString path);
void setArguments(QStringList args);
void setLaunchScript(QString script)
{
launchScript = script;
}
void setNativeFolder(QString natives)
{
m_nativeFolder = natives;
}
void killMinecraft();
@ -104,21 +113,30 @@ signals:
protected:
BaseInstance *m_instance = nullptr;
QStringList m_args;
QString m_err_leftover;
QString m_out_leftover;
QProcess m_prepostlaunchprocess;
bool killed = false;
MojangAccountPtr m_account;
QString launchScript;
QString m_nativeFolder;
protected
slots:
void finish(int, QProcess::ExitStatus status);
void on_stdErr();
void on_stdOut();
void on_prepost_stdOut();
void on_prepost_stdErr();
void logOutput(const QStringList &lines,
MessageLevel::Enum defaultLevel = MessageLevel::Message,
bool guessLevel = true, bool censor = true);
void logOutput(QString line,
MessageLevel::Enum defaultLevel = MessageLevel::Message,
bool guessLevel = true, bool censor = true);
private:
QString censorPrivateInfo(QString in);
MessageLevel::Enum getLevel(const QString &message, MessageLevel::Enum defaultLevel);
MessageLevel::Enum guessLevel(const QString &message, MessageLevel::Enum defaultLevel);
MessageLevel::Enum getLevel(const QString &levelName);
};

View File

@ -23,8 +23,8 @@ void checkJVMArgs(QString jvmargs, QWidget *parent)
if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegExp("-Xm[sx]")))
{
CustomMessageBox::selectable(
parent, parent->tr("JVM arguments warning"),
parent->tr("You tried to manually set a JVM memory option (using "
parent, QObject::tr("JVM arguments warning"),
QObject::tr("You tried to manually set a JVM memory option (using "
" \"-XX:PermSize\", \"-Xmx\" or \"-Xms\") - there"
" are dedicated boxes for these in the settings (Java"
" tab, in the Memory group at the top).\n"

View File

@ -92,6 +92,11 @@ OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *set
}
}
QString OneSixFTBInstance::id() const
{
return "FTB/" + BaseInstance::id();
}
QString OneSixFTBInstance::getStatusbarDescription()
{
return "OneSix FTB: " + intendedVersionId();

View File

@ -15,6 +15,8 @@ public:
virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override;
virtual QString id() const;
private:
std::shared_ptr<OneSixLibrary> m_forge;
};

View File

@ -13,12 +13,14 @@
* limitations under the License.
*/
#include "MultiMC.h"
#include "OneSixInstance.h"
#include "OneSixInstance_p.h"
#include "OneSixUpdate.h"
#include "MinecraftProcess.h"
#include "OneSixVersion.h"
#include "JavaChecker.h"
#include "logic/icons/IconList.h"
#include <setting.h>
#include <pathutils.h>
@ -27,6 +29,7 @@
#include "gui/dialogs/OneSixModEditDialog.h"
#include "logger/QsLog.h"
#include "logic/assets/AssetsUtils.h"
#include <QIcon>
OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_obj,
QObject *parent)
@ -84,14 +87,13 @@ QDir OneSixInstance::reconstructAssets(std::shared_ptr<OneSixVersion> version)
return virtualRoot;
}
QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path();
QLOG_DEBUG() << "reconstructAssets" << assetsDir.path() << indexDir.path()
<< objectDir.path() << virtualDir.path() << virtualRoot.path();
AssetsIndex index;
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index);
if(loadAssetsIndex)
{
if(index.isVirtual)
if (loadAssetsIndex && index.isVirtual)
{
QLOG_INFO() << "Reconstructing virtual assets folder at" << virtualRoot.path();
@ -103,23 +105,27 @@ QDir OneSixInstance::reconstructAssets(std::shared_ptr<OneSixVersion> version)
QString tlk = asset_object.hash.left(2);
QString original_path = PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash);
QString original_path =
PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash);
QFile original(original_path);
if(!original.exists())
continue;
if (!target.exists())
{
QFileInfo info(target_path);
QDir target_dir = info.dir();
// QLOG_DEBUG() << target_dir;
if(!target_dir.exists()) QDir("").mkpath(target_dir.path());
if (!target_dir.exists())
QDir("").mkpath(target_dir.path());
bool couldCopy = original.copy(target_path);
QLOG_DEBUG() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy);// << original.errorString();
QLOG_DEBUG() << " Copying" << original_path << "to" << target_path
<< QString::number(couldCopy); // << original.errorString();
}
}
// TODO: Write last used time to virtualRoot/.lastused
}
}
return virtualRoot;
}
@ -180,71 +186,56 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(MojangAccountPtr account)
{
I_D(OneSixInstance);
QString natives_dir_raw = PathCombine(instanceRoot(), "natives/");
QIcon icon = MMC->icons()->getIcon(iconKey());
auto pixmap = icon.pixmap(128, 128);
pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG");
auto version = d->version;
if (!version)
return nullptr;
QStringList args;
args.append(Util::Commandline::splitArgs(settings().get("JvmArgs").toString()));
args << QString("-Xms%1m").arg(settings().get("MinMemAlloc").toInt());
args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt());
args << QString("-XX:PermSize=%1m").arg(settings().get("PermGen").toInt());
/**
* HACK: Stupid hack for Intel drivers.
* See: https://mojang.atlassian.net/browse/MCL-767
*/
#ifdef Q_OS_WIN32
args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_"
"minecraft.exe.heapdump");
#endif
QDir natives_dir(natives_dir_raw);
args << QString("-Djava.library.path=%1").arg(natives_dir.absolutePath());
QString classPath;
QString launchScript;
{
auto libs = version->getActiveNormalLibs();
for (auto lib : libs)
{
QFileInfo fi(QString("libraries/") + lib->storagePath());
classPath.append(fi.absoluteFilePath());
#ifdef Q_OS_WIN32
classPath.append(';');
#else
classPath.append(':');
#endif
launchScript += "cp " + fi.absoluteFilePath() + "\n";
}
QString targetstr = "versions/" + version->id + "/" + version->id + ".jar";
QFileInfo fi(targetstr);
classPath.append(fi.absoluteFilePath());
launchScript += "cp " + fi.absoluteFilePath() + "\n";
}
if (classPath.size())
launchScript += "mainClass " + version->mainClass + "\n";
for (auto param : processMinecraftArgs(account))
{
args << "-cp";
args << classPath;
launchScript += "param " + param + "\n";
}
args << version->mainClass;
args.append(processMinecraftArgs(account));
// Set the width and height for 1.6 instances
bool maximize = settings().get("LaunchMaximized").toBool();
if (maximize)
{
// this is probably a BAD idea
// args << QString("--fullscreen");
// launchScript += "param --fullscreen\n";
}
else
{
args << QString("--width") << settings().get("MinecraftWinWidth").toString();
args << QString("--height") << settings().get("MinecraftWinHeight").toString();
launchScript +=
"param --width\nparam " + settings().get("MinecraftWinWidth").toString() + "\n";
launchScript +=
"param --height\nparam " + settings().get("MinecraftWinHeight").toString() + "\n";
}
QDir natives_dir(PathCombine(instanceRoot(), "natives/"));
launchScript += "windowTitle " + windowTitle() + "\n";
launchScript += "natives " + natives_dir.absolutePath() + "\n";
launchScript += "launch onesix\n";
// create the process and set its parameters
MinecraftProcess *proc = new MinecraftProcess(this);
proc->setArguments(args);
proc->setWorkdir(minecraftRoot());
proc->setLaunchScript(launchScript);
// proc->setNativeFolder(natives_dir.absolutePath());
return proc;
}

View File

@ -19,6 +19,9 @@
#include "OneSixRule.h"
#include "OpSys.h"
#include "logic/net/URLConstants.h"
#include <pathutils.h>
#include <JlCompress.h>
#include "logger/QsLog.h"
void OneSixLibrary::finalize()
{
@ -133,6 +136,90 @@ QString OneSixLibrary::hint()
return m_hint;
}
bool OneSixLibrary::filesExist()
{
QString storage = storagePath();
if (storage.contains("${arch}"))
{
QString cooked_storage = storage;
cooked_storage.replace("${arch}", "32");
QFileInfo info32(PathCombine("libraries", cooked_storage));
if (!info32.exists())
{
return false;
}
cooked_storage = storage;
cooked_storage.replace("${arch}", "64");
QFileInfo info64(PathCombine("libraries", cooked_storage));
if (!info64.exists())
{
return false;
}
}
else
{
QFileInfo info(PathCombine("libraries", storage));
if (!info.exists())
{
return false;
}
}
return true;
}
bool OneSixLibrary::extractTo(QString target_dir)
{
QString storage = storagePath();
if (storage.contains("${arch}"))
{
QString cooked_storage = storage;
cooked_storage.replace("${arch}", "32");
QString origin = PathCombine("libraries", cooked_storage);
QString target_dir_cooked = PathCombine(target_dir, "32");
if(!ensureFolderPathExists(target_dir_cooked))
{
QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked;
return false;
}
if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes)
.isEmpty())
{
QLOG_ERROR() << "Couldn't extract " + origin;
return false;
}
cooked_storage = storage;
cooked_storage.replace("${arch}", "64");
origin = PathCombine("libraries", cooked_storage);
target_dir_cooked = PathCombine(target_dir, "32");
if(!ensureFolderPathExists(target_dir_cooked))
{
QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked;
return false;
}
if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes)
.isEmpty())
{
QLOG_ERROR() << "Couldn't extract " + origin;
return false;
}
}
else
{
if(!ensureFolderPathExists(target_dir))
{
QLOG_ERROR() << "Couldn't create folder " + target_dir;
return false;
}
QString path = PathCombine("libraries", storage);
if (JlCompress::extractWithExceptions(path, target_dir, extract_excludes).isEmpty())
{
QLOG_ERROR() << "Couldn't extract " + path;
return false;
}
}
return true;
}
QJsonObject OneSixLibrary::toJson()
{
QJsonObject libRoot;

View File

@ -126,4 +126,7 @@ public:
/// set a hint about how to treat the library. This is an MMC extension.
void setHint(QString hint);
QString hint();
bool extractTo(QString target_dir);
bool filesExist();
};

View File

@ -54,17 +54,7 @@ void OneSixUpdate::executeTask()
if (m_only_prepare)
{
/*
* FIXME: in offline mode, do not proceed!
*/
setStatus(tr("Testing the Java installation..."));
QString java_path = m_inst->settings().get("JavaPath").toString();
checker.reset(new JavaChecker());
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
SLOT(checkFinishedOffline(JavaCheckResult)));
checker->path = java_path;
checker->performCheck();
prepareForLaunch();
return;
}
@ -83,46 +73,8 @@ void OneSixUpdate::executeTask()
}
else
{
checkJavaOnline();
}
}
void OneSixUpdate::checkJavaOnline()
{
setStatus(tr("Testing the Java installation..."));
QString java_path = m_inst->settings().get("JavaPath").toString();
checker.reset(new JavaChecker());
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
SLOT(checkFinishedOnline(JavaCheckResult)));
checker->path = java_path;
checker->performCheck();
}
void OneSixUpdate::checkFinishedOnline(JavaCheckResult result)
{
if (result.valid)
{
java_is_64bit = result.is_64bit;
jarlibStart();
}
else
{
emitFailed("The java binary doesn't work. Check the settings and correct the problem");
}
}
void OneSixUpdate::checkFinishedOffline(JavaCheckResult result)
{
if (result.valid)
{
java_is_64bit = result.is_64bit;
prepareForLaunch();
}
else
{
emitFailed("The java binary doesn't work. Check the settings and correct the problem");
}
}
void OneSixUpdate::versionFileStart()
@ -130,7 +82,8 @@ void OneSixUpdate::versionFileStart()
QLOG_INFO() << m_inst->name() << ": getting version file.";
setStatus(tr("Getting the version files from Mojang..."));
QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json";
QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS +
targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json";
auto job = new NetJob("Version index");
job->addNetAction(ByteArrayDownload::make(QUrl(urlstr)));
specificVersionDownloadJob.reset(job);
@ -186,7 +139,7 @@ void OneSixUpdate::versionFileFinished()
}
inst->reloadFullVersion();
checkJavaOnline();
jarlibStart();
}
void OneSixUpdate::versionFileFailed()
@ -277,8 +230,6 @@ void OneSixUpdate::assetsFailed()
emitFailed("Failed to download assets!");
}
void OneSixUpdate::jarlibStart()
{
setStatus(tr("Getting the library files from Mojang..."));
@ -318,13 +269,12 @@ void OneSixUpdate::jarlibStart()
{
if (lib->hint() == "local")
continue;
QString subst = java_is_64bit ? "64" : "32";
QString storage = lib->storagePath();
QString dl = lib->downloadUrl();
storage.replace("${arch}", subst);
dl.replace("${arch}", subst);
QString raw_storage = lib->storagePath();
QString raw_dl = lib->downloadUrl();
auto f = [&](QString storage, QString dl)
{
auto entry = metacache->resolveEntry("libraries", storage);
if (entry->stale)
{
@ -337,6 +287,20 @@ void OneSixUpdate::jarlibStart()
jarlibDownloadJob->addNetAction(CacheDownload::make(dl, entry));
}
}
};
if (raw_storage.contains("${arch}"))
{
QString cooked_storage = raw_storage;
QString cooked_dl = raw_dl;
f(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32"));
cooked_storage = raw_storage;
cooked_dl = raw_dl;
f(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"));
}
else
{
f(raw_storage, raw_dl);
}
}
// TODO: think about how to propagate this from the original json file... or IF AT ALL
QString forgeMirrorList = "http://files.minecraftforge.net/mirror-brand.list";
@ -376,7 +340,6 @@ void OneSixUpdate::prepareForLaunch()
// delete any leftovers, if they are present.
onesix_inst->cleanupAfterRun();
// Acquire swag
QString natives_dir_raw = PathCombine(onesix_inst->instanceRoot(), "natives/");
auto version = onesix_inst->getFullVersion();
if (!version)
@ -385,38 +348,30 @@ void OneSixUpdate::prepareForLaunch()
"it or changing the version.");
return;
}
auto libs_to_extract = version->getActiveNativeLibs();
// Acquire bag
bool success = ensureFolderPathExists(natives_dir_raw);
if (!success)
{
emitFailed("Could not create the native library folder:\n" + natives_dir_raw +
"\nMake sure MultiMC has appropriate permissions and there is enough space "
/*
* emitFailed("Could not create the native library folder:\n" + natives_dir_raw +
"\nMake sure MultiMC has appropriate permissions and there is enough
space "
"on the storage device.");
*/
for (auto lib : version->getActiveNativeLibs())
{
if (!lib->filesExist())
{
emitFailed("Native library is missing some files:\n" + lib->storagePath() +
"\n\nRun the instance at least once in online mode to get all the "
"required files.");
return;
}
// Put swag in the bag
QString subst = java_is_64bit ? "64" : "32";
for (auto lib : libs_to_extract)
if (!lib->extractTo(natives_dir_raw))
{
QString storage = lib->storagePath();
storage.replace("${arch}", subst);
QString path = "libraries/" + storage;
QLOG_INFO() << "Will extract " << path.toLocal8Bit();
if (JlCompress::extractWithExceptions(path, natives_dir_raw, lib->extract_excludes)
.isEmpty())
{
emitFailed(
"Could not extract the native library:\n" + path +
"\nMake sure MultiMC has appropriate permissions and there is enough space "
"on the storage device.");
emitFailed("Could not extract the native library:\n" + lib->storagePath() + " to " +
natives_dir_raw +
"\n\nMake sure MultiMC has appropriate permissions and there is enough "
"space on the storage device.");
return;
}
}
// Show them your war face!
emitSucceeded();
}

View File

@ -21,7 +21,6 @@
#include "logic/net/NetJob.h"
#include "logic/tasks/Task.h"
#include "logic/JavaChecker.h"
class MinecraftVersion;
class BaseInstance;
@ -50,10 +49,6 @@ slots:
void assetsFinished();
void assetsFailed();
void checkJavaOnline();
void checkFinishedOnline(JavaCheckResult result);
void checkFinishedOffline(JavaCheckResult result);
// extract the appropriate libraries
void prepareForLaunch();
@ -65,7 +60,4 @@ private:
std::shared_ptr<MinecraftVersion> targetVersion;
BaseInstance *m_inst = nullptr;
bool m_only_prepare = false;
std::shared_ptr<JavaChecker> checker;
bool java_is_64bit = false;
};

View File

@ -19,5 +19,5 @@
namespace SkinUtils
{
QPixmap getFaceFromCache(QString username, int height = 48, int width = 48);
QPixmap getFaceFromCache(QString username, int height = 64, int width = 64);
}

View File

@ -198,7 +198,11 @@ void MojangAccount::authFailed(QString reason)
{
// This is emitted when the yggdrasil tasks time out or are cancelled.
// -> we treat the error as no-op
if (reason != "Yggdrasil task cancelled.")
if (reason == "Yggdrasil task cancelled.")
{
// do nothing
}
else
{
m_online = false;
m_accessToken = QString();

View File

@ -336,6 +336,23 @@ QIcon IconList::getIcon(QString key)
return QIcon();
}
QIcon IconList::getBigIcon(QString key)
{
int icon_index = getIconIndex(key);
if (icon_index == -1)
key = "infinity";
// Fallback for icons that don't exist.
icon_index = getIconIndex(key);
if (icon_index == -1)
return QIcon();
QPixmap bigone = icons[icon_index].icon().pixmap(256,256).scaled(256,256);
return QIcon(bigone);
}
int IconList::getIconIndex(QString key)
{
if (key == "default")

View File

@ -34,6 +34,7 @@ public:
virtual ~IconList() {};
QIcon getIcon(QString key);
QIcon getBigIcon(QString key);
int getIconIndex(QString key);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;

View File

@ -187,7 +187,7 @@ bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out)
QByteArray data;
{
auto dlJob = listDownload;
auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->m_target_path;
auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->getTargetFilepath();
QFile listFile(filename);
if (!listFile.open(QIODevice::ReadOnly))
{
@ -303,7 +303,7 @@ bool ForgeListLoadTask::parseForgeGradleList(QList<BaseVersionPtr> &out)
QByteArray data;
{
auto dlJob = gradleListDownload;
auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->m_target_path;
auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->getTargetFilepath();
QFile listFile(filename);
if (!listFile.open(QIODevice::ReadOnly))
{
@ -404,12 +404,8 @@ void ForgeListLoadTask::listDownloaded()
{
return;
}
qSort(list.begin(), list.end(), [](const BaseVersionPtr & p1, const BaseVersionPtr & p2)
{
// TODO better comparison (takes major/minor/build number into account)
return p1->name() > p2->name();
});
std::sort(list.begin(), list.end(), [](const BaseVersionPtr & l, const BaseVersionPtr & r)
{ return (*l > *r); });
m_list->updateListData(list);

View File

@ -29,25 +29,38 @@ typedef std::shared_ptr<ForgeVersion> ForgeVersionPtr;
struct ForgeVersion : public BaseVersion
{
virtual QString descriptor()
virtual QString descriptor() override
{
return filename;
}
;
virtual QString name()
virtual QString name() override
{
return "Forge " + jobbuildver;
}
;
virtual QString typeString() const
virtual QString typeString() const override
{
if (installer_url.isEmpty())
return "Universal";
else
return "Installer";
}
;
virtual bool operator<(BaseVersion &a) override
{
ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a);
if(!pa)
return true;
return m_buildnr < pa->m_buildnr;
}
virtual bool operator>(BaseVersion &a) override
{
ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a);
if(!pa)
return false;
return m_buildnr > pa->m_buildnr;
}
int m_buildnr = 0;
QString universal_url;
QString changelog_url;

View File

@ -308,11 +308,16 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
return;
}
dir.cd("ModPacks");
auto fpath = dir.absoluteFilePath("modpacks.xml");
auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name);
for(auto filename: allFiles)
{
if(!filename.endsWith(".xml"))
continue;
auto fpath = dir.absoluteFilePath(filename);
QFile f(fpath);
QLOG_INFO() << "Discovering FTB instances -- " << fpath;
if (!f.open(QFile::ReadOnly))
return;
continue;
// read the FTB packs XML.
QXmlStreamReader reader(&f);
@ -347,6 +352,8 @@ void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
}
}
f.close();
}
if(!records.size())
{
QLOG_INFO() << "No FTB instances to load.";

View File

@ -33,25 +33,44 @@ CacheDownload::CacheDownload(QUrl url, MetaEntryPtr entry)
void CacheDownload::start()
{
m_status = Job_InProgress;
if (!m_entry->stale)
{
m_status = Job_Finished;
emit succeeded(m_index_within_job);
return;
}
m_output_file.setFileName(m_target_path);
// create a new save file
m_output_file.reset(new QSaveFile(m_target_path));
// if there already is a file and md5 checking is in effect and it can be opened
if (!ensureFilePathExists(m_target_path))
{
QLOG_ERROR() << "Could not create folder for " + m_target_path;
m_status = Job_Failed;
emit failed(m_index_within_job);
return;
}
if (!m_output_file->open(QIODevice::WriteOnly))
{
QLOG_ERROR() << "Could not open " + m_target_path + " for writing";
m_status = Job_Failed;
emit failed(m_index_within_job);
return;
}
QLOG_INFO() << "Downloading " << m_url.toString();
QNetworkRequest request(m_url);
// check file consistency first.
QFile current(m_target_path);
if(current.exists() && current.size() != 0)
{
if (m_entry->remote_changed_timestamp.size())
request.setRawHeader(QString("If-Modified-Since").toLatin1(),
m_entry->remote_changed_timestamp.toLatin1());
if (m_entry->etag.size())
request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->etag.toLatin1());
}
request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
@ -83,28 +102,41 @@ void CacheDownload::downloadError(QNetworkReply::NetworkError error)
void CacheDownload::downloadFinished()
{
// if the download succeeded
if (m_status != Job_Failed)
if (m_status == Job_Failed)
{
m_output_file->cancelWriting();
m_reply.reset();
emit failed(m_index_within_job);
return;
}
// nothing went wrong...
m_status = Job_Finished;
if (m_output_file.isOpen())
// if we wrote any data to the save file, we try to commit the data to the real file.
if (wroteAnyData)
{
// save the data to the downloadable if we aren't saving to file
m_output_file.close();
// nothing went wrong...
if (m_output_file->commit())
{
m_status = Job_Finished;
m_entry->md5sum = md5sum.result().toHex().constData();
}
else
{
if (m_output_file.open(QIODevice::ReadOnly))
QLOG_ERROR() << "Failed to commit changes to " << m_target_path;
m_output_file->cancelWriting();
m_reply.reset();
m_status = Job_Failed;
emit failed(m_index_within_job);
return;
}
}
else
{
m_entry->md5sum =
QCryptographicHash::hash(m_output_file.readAll(), QCryptographicHash::Md5)
.toHex()
.constData();
m_output_file.close();
}
m_status = Job_Finished;
}
// then get rid of the save file
m_output_file.reset();
QFileInfo output_file_info(m_target_path);
m_entry->etag = m_reply->rawHeader("ETag").constData();
@ -121,32 +153,17 @@ void CacheDownload::downloadFinished()
emit succeeded(m_index_within_job);
return;
}
// else the download failed
else
{
m_output_file.close();
m_output_file.remove();
m_reply.reset();
emit failed(m_index_within_job);
return;
}
}
void CacheDownload::downloadReadyRead()
{
if (!m_output_file.isOpen())
{
if (!m_output_file.open(QIODevice::WriteOnly))
{
/*
* Can't open the file... the job failed
*/
m_reply->abort();
emit failed(m_index_within_job);
return;
}
}
QByteArray ba = m_reply->readAll();
md5sum.addData(ba);
m_output_file.write(ba);
if (m_output_file->write(ba) != ba.size())
{
QLOG_ERROR() << "Failed writing into " + m_target_path;
m_status = Job_Failed;
m_reply->abort();
emit failed(m_index_within_job);
}
wroteAnyData = true;
}

View File

@ -17,29 +17,34 @@
#include "NetAction.h"
#include "HttpMetaCache.h"
#include <QFile>
#include <qcryptographichash.h>
#include <QCryptographicHash>
#include <QSaveFile>
typedef std::shared_ptr<class CacheDownload> CacheDownloadPtr;
class CacheDownload : public NetAction
{
Q_OBJECT
public:
private:
MetaEntryPtr m_entry;
/// if saving to file, use the one specified in this string
QString m_target_path;
/// this is the output file, if any
QFile m_output_file;
std::shared_ptr<QSaveFile> m_output_file;
/// the hash-as-you-download
QCryptographicHash md5sum;
bool wroteAnyData = false;
public:
explicit CacheDownload(QUrl url, MetaEntryPtr entry);
static CacheDownloadPtr make(QUrl url, MetaEntryPtr entry)
{
return CacheDownloadPtr(new CacheDownload(url, entry));
}
QString getTargetFilepath()
{
return m_target_path;
}
protected
slots:
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);

View File

@ -31,4 +31,6 @@ const QString SKINS_BASE("skins.minecraft.net/MinecraftSkins/");
const QString AUTH_BASE("authserver.mojang.com/");
const QString FORGE_LEGACY_URL("http://files.minecraftforge.net/minecraftforge/json");
const QString FORGE_GRADLE_URL("http://files.minecraftforge.net/maven/net/minecraftforge/forge/json");
const QString MOJANG_STATUS_URL("http://status.mojang.com/check");
const QString MOJANG_STATUS_NEWS_URL("http://status.mojang.com/news");
}

View File

@ -0,0 +1,137 @@
/* Copyright 2013 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.
*/
#include "StatusChecker.h"
#include <logic/net/URLConstants.h>
#include <QByteArray>
#include <QDomDocument>
#include <logger/QsLog.h>
StatusChecker::StatusChecker()
{
}
void StatusChecker::reloadStatus()
{
if (isLoadingStatus())
{
// QLOG_INFO() << "Ignored request to reload status. Currently reloading already.";
return;
}
// QLOG_INFO() << "Reloading status.";
NetJob* job = new NetJob("Status JSON");
job->addNetAction(ByteArrayDownload::make(URLConstants::MOJANG_STATUS_URL));
QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished);
QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed);
m_statusNetJob.reset(job);
job->start();
}
void StatusChecker::statusDownloadFinished()
{
QLOG_DEBUG() << "Finished loading status JSON.";
QByteArray data;
{
ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(m_statusNetJob->first());
data = dl->m_data;
m_statusNetJob.reset();
}
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
fail("Error parsing status JSON:" + jsonError.errorString());
return;
}
if (!jsonDoc.isArray())
{
fail("Error parsing status JSON: JSON root is not an array");
return;
}
QJsonArray root = jsonDoc.array();
for(auto status = root.begin(); status != root.end(); ++status)
{
QVariantMap map = (*status).toObject().toVariantMap();
for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter)
{
QString key = iter.key();
QVariant value = iter.value();
if(value.type() == QVariant::Type::String)
{
m_statusEntries.insert(key, value.toString());
//QLOG_DEBUG() << "Status JSON object: " << key << m_statusEntries[key];
}
else
{
fail("Malformed status JSON: expected status type to be a string.");
return;
}
}
}
succeed();
}
void StatusChecker::statusDownloadFailed()
{
fail("Failed to load status JSON.");
}
QMap<QString, QString> StatusChecker::getStatusEntries() const
{
return m_statusEntries;
}
bool StatusChecker::isLoadingStatus() const
{
return m_statusNetJob.get() != nullptr;
}
QString StatusChecker::getLastLoadErrorMsg() const
{
return m_lastLoadError;
}
void StatusChecker::succeed()
{
m_lastLoadError = "";
QLOG_DEBUG() << "Status loading succeeded.";
m_statusNetJob.reset();
emit statusLoaded();
}
void StatusChecker::fail(const QString& errorMsg)
{
m_lastLoadError = errorMsg;
QLOG_DEBUG() << "Failed to load status:" << errorMsg;
m_statusNetJob.reset();
emit statusLoadingFailed(errorMsg);
}

View File

@ -0,0 +1,57 @@
/* Copyright 2013 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.
*/
#pragma once
#include <QObject>
#include <QString>
#include <QList>
#include <logic/net/NetJob.h>
class StatusChecker : public QObject
{
Q_OBJECT
public:
StatusChecker();
QString getLastLoadErrorMsg() const;
bool isStatusLoaded() const;
bool isLoadingStatus() const;
QMap<QString, QString> getStatusEntries() const;
void Q_SLOT reloadStatus();
signals:
void statusLoaded();
void statusLoadingFailed(QString errorMsg);
protected slots:
void statusDownloadFinished();
void statusDownloadFailed();
protected:
QMap<QString, QString> m_statusEntries;
NetJobPtr m_statusNetJob;
bool m_loadedStatus;
QString m_lastLoadError;
void Q_SLOT succeed();
void Q_SLOT fail(const QString& errorMsg);
};

View File

@ -55,7 +55,7 @@ void NotificationChecker::downloadSucceeded(int)
{
m_entries.clear();
QFile file(m_download->m_output_file.fileName());
QFile file(m_download->getTargetFilepath());
if (file.open(QFile::ReadOnly))
{
QJsonArray root = QJsonDocument::fromJson(file.readAll()).array();

View File

@ -30,7 +30,6 @@
UpdateChecker::UpdateChecker()
{
m_currentChannel = VERSION_CHANNEL;
m_channelListUrl = CHANLIST_URL;
m_updateChecking = false;
m_chanListLoading = false;

View File

@ -27,7 +27,6 @@ public:
UpdateChecker();
void checkForUpdate(bool notifyNoUpdate);
void setCurrentChannel(const QString &channel) { m_currentChannel = channel; }
void setChannelListUrl(const QString &url) { m_channelListUrl = url; }
/*!
@ -83,7 +82,6 @@ private:
QString m_repoUrl;
QString m_channelListUrl;
QString m_currentChannel;
QList<ChannelListEntry> m_channels;

View File

@ -4,6 +4,7 @@
int main_gui(MultiMC &app)
{
// show main window
QIcon::setThemeName("multimc");
MainWindow mainWin;
mainWin.restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray()));
mainWin.restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray()));
@ -18,8 +19,9 @@ int main(int argc, char *argv[])
// initialize Qt
MultiMC app(argc, argv);
Q_INIT_RESOURCE(graphics);
Q_INIT_RESOURCE(generated);
Q_INIT_RESOURCE(instances);
Q_INIT_RESOURCE(multimc);
Q_INIT_RESOURCE(backgrounds);
switch (app.status())
{

View File

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -1,12 +0,0 @@
[Icon Theme]
Name=MultiMC
Comment=MultiMC Default Icons
Inherits=default
Directories=scalable/apps
[scalable/apps]
Size=48
Type=scalable
MinSize=1
MaxSize=512
Context=Applications

View File

@ -0,0 +1,6 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/backgrounds">
<file alias="kitteh">catbgrnd2.png</file>
</qresource>
</RCC>

View File

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 713 B

After

Width:  |  Height:  |  Size: 713 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 708 B

After

Width:  |  Height:  |  Size: 708 B

View File

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 482 B

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 978 B

After

Width:  |  Height:  |  Size: 978 B

View File

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 618 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,35 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/icons/instances">
<!-- Source: Mojang -->
<file alias="brick">brick.png</file>
<file alias="diamond">diamond.png</file>
<file alias="dirt">dirt.png</file>
<file alias="gold">gold.png</file>
<file alias="grass">grass.png</file>
<file alias="stone">stone.png</file>
<file alias="tnt">tnt.png</file>
<file alias="iron">iron.png</file>
<file alias="planks">planks.png</file>
<!-- Source: Unknown -->
<file alias="derp">derp.png</file>
<file alias="enderman">enderman.png</file>
<!-- Our own. -->
<file alias="chicken">chicken128.png</file>
<file alias="creeper">creeper128.png</file>
<file alias="enderpearl">enderpearl128.png</file>
<file alias="ftb-glow">ftb_glow128.png</file>
<file alias="ftb-logo">ftb_logo128.png</file>
<file alias="gear">gear128.png</file>
<file alias="herobrine">herobrine128.png</file>
<file alias="infinity">infinity128.png</file>
<file alias="magitech">magitech128.png</file>
<file alias="meat">meat128.png</file>
<file alias="netherstar">netherstar128.png</file>
<file alias="skeleton">skeleton128.png</file>
<file alias="squarecreeper">squarecreeper128.png</file>
<file alias="steve">steve128.png</file>
</qresource>
</RCC>

View File

Before

Width:  |  Height:  |  Size: 532 B

After

Width:  |  Height:  |  Size: 532 B

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Some files were not shown because too many files have changed in this diff Show More