Compare commits

..

83 Commits
0.3.1 ... 0.3.6

Author SHA1 Message Date
Petr Mrázek
3a0cdf2d3d Tagging 0.3.6 2014-06-03 01:44:19 +02:00
Jan Dalheimer
e1f542b5b0 Still trying to fix FTB 2014-05-23 18:41:22 +02:00
Jan Dalheimer
15920aa9d0 Attempt at fixing FTB 2014-05-23 18:19:20 +02:00
Jan Dalheimer
e17364de6b Fix FTB? 2014-05-23 16:39:14 +02:00
Jan Dalheimer
b911a3834c More FTB debug stuff 2014-05-23 16:16:44 +02:00
Jan Dalheimer
94c2c363b2 Some more FTB related debug info 2014-05-23 15:46:12 +02:00
Petr Mrázek
df82d8fadb QDir::exists is not static in Qt 5.1.1 2014-05-22 09:12:04 +02:00
Petr Mrázek
851a77d5bb Merge pull request #275 from MultiMC/feature_ftb_new_paths
Fix FTB paths on windows
2014-05-22 09:04:03 +02:00
Jan Dalheimer
41caf7976d FTB paths changed on windows. Fixes #255 2014-05-22 07:49:45 +02:00
Jan Dalheimer
fc3c0b0971 Merge branch 'feature_crashreport' into develop 2014-05-21 15:57:34 +02:00
Petr Mrázek
94cb5c7d77 Restore manage screenshots in main window. 2014-05-18 23:12:35 +02:00
Petr Mrázek
911ac19a56 Screenshot upload dialog(s) now have the console window as parent. 2014-05-18 19:07:01 +02:00
Petr Mrázek
7f2a16917e Add static data path for ... static data. Like translations. Move translations there. 2014-05-17 18:21:32 +02:00
Petr Mrázek
8a8c4193e6 Finish status pills. 2014-05-17 16:23:48 +02:00
Petr Mrázek
927217c7f0 Status pills. This doesn't build yet. 2014-05-15 23:20:38 +02:00
Petr Mrázek
a6a5241e12 Bump version to 0.3.5 2014-05-15 22:48:00 +02:00
Forkk
e6ca58a89e Add a missing letter 2014-05-10 15:10:24 -05:00
Forkk
aefa73ad11 Fix stupid tabs.
Thanks, QtCreator... ._.
2014-05-10 14:56:44 -05:00
Andrew
4f6cd65c13 Remove unused function 2014-05-10 14:19:13 -05:00
Andrew
5099964c67 Implement backtraces on Windows.
Much !!FUN!! was had
2014-05-10 14:16:27 -05:00
Andrew
9e80ddb040 Implement crash report system on Windows. 2014-05-09 20:08:07 -05:00
Forkk
489cb4dbf5 Change dump file extension 2014-05-09 18:21:15 -05:00
Forkk
e3b9b30302 Remove some unnecessary dummy functions. 2014-05-09 17:36:53 -05:00
Forkk
93ae21abfc Implement crash handling on Linux
This will allow us to generate crash dumps and have users report
crashes.
2014-05-09 17:33:32 -05:00
Petr Mrázek
cf616efb5d Fix for #257 2014-05-08 19:00:48 +02:00
Petr Mrázek
0902fd5bec Fix version select dialog filtering 2014-05-04 13:20:42 +02:00
Petr Mrázek
de48f102bd Merge pull request #248 from MultiMC/feature_fix_ftb_lib
Fix FTB local libraries bug
2014-05-03 15:47:57 +02:00
Jan Dalheimer
0f3d88cb14 Fix FTB local libraries bug 2014-05-03 15:40:46 +02:00
Petr Mrázek
8fda1595f7 Merge branch 'develop' into release-0.3.4 2014-05-03 04:12:38 +02:00
Petr Mrázek
e9c8ca02ba Inherit module path from main build environment in the prerequisites script. 2014-05-02 01:08:03 +02:00
Petr Mrázek
95203df9ca It's a fatal error to not find the dependency checker tool. 2014-05-02 00:52:08 +02:00
Petr Mrázek
27732d66b4 Add a safety net for missing dumpbin.exe 2014-05-02 00:37:29 +02:00
Forkk
b3f717c582 Include ICU in distributed builds. 2014-05-01 13:48:09 -05:00
Forkk
cba22f7ee0 Bump version and update changelog. 2014-05-01 13:10:46 -05:00
Forkk
3d8f6ac640 Merge branch 'feature_credits' into develop 2014-05-01 12:45:55 -05:00
Forkk
605a334057 Show Patreon patrons in the about dialog 2014-05-01 12:43:55 -05:00
Petr Mrázek
5fa36f67b3 Update the icon, again 2014-04-22 21:08:09 +02:00
Petr Mrázek
0cd8ded4d4 Update the OSX icon
Closes #230
2014-04-22 00:53:05 +02:00
Petr Mrázek
9d724e0fe4 Merge remote-tracking branch 'origin/feature_cmake_style' into develop
Conflicts:
	CMakeLists.txt
2014-04-21 23:33:00 +02:00
Petr Mrázek
a9dfe6d7ec Fix quit shortcut connect() call 2014-04-21 21:48:58 +02:00
Petr Mrázek
7de007502c Update FR translation some more. 2014-04-21 21:46:12 +02:00
Petr Mrázek
e955b1c6f9 Merge remote-tracking branch 'origin/feature_new_login_dialog2' into develop 2014-04-21 21:39:02 +02:00
Petr Mrázek
c8f468057e Merge remote-tracking branch 'origin/feature_close_shortcut' into develop 2014-04-21 21:38:24 +02:00
Petr Mrázek
6ded1168b0 Fix french translation. 2014-04-21 21:37:15 +02:00
Taeyeon Mori
adecc53df8 Add a quit shortcut to the main window (#200)
The shortcut is Ctrl+Q on every platform but windows, which doesn have one.
See also: http://qt-project.org/doc/qt-5/qkeysequence.html#details (StandardKey Quit)
2014-04-21 21:37:15 +02:00
Petr Mrázek
c9465e9e86 Merge remote-tracking branch 'origin/feature_raise_console' into develop 2014-04-21 21:25:39 +02:00
Petr Mrázek
f14c1888a9 Merge remote-tracking branch 'origin/feature_french' into develop 2014-04-21 21:24:24 +02:00
Petr Mrázek
ca60784a44 Add proper FML libs URL 2014-04-21 20:41:37 +02:00
Petr Mrázek
565dab24b5 Download and cache FML libs for legacy minecraft versions.
* minor fix for version filtering (1.5 no longer shows forge for 1.5.1 and 1.5.2)
* FML libs are downloaded to mods/minecraftforge/libs and cached
* FML libs are copied to instances which contain FML or forge
2014-04-19 21:24:11 +02:00
Elros
eea347b82c Add french translation from #217
Closes #217
2014-04-19 12:46:23 -04:00
Taeyeon Mori
0b60e50af8 Add a quit shortcut to the main window (#200)
The shortcut is Ctrl+Q on every platform but windows, which doesn have one.
See also: http://qt-project.org/doc/qt-5/qkeysequence.html#details (StandardKey Quit)
2014-04-17 14:49:03 +02:00
Taeyeon Mori
0959aeb046 Make the console window raise itself after minecraft closes (#193)
This needs further testing:
http://stackoverflow.com/questions/6087887/bring-window-to-front-raise-show-activatewindow-dont-work
2014-04-17 14:13:16 +02:00
Jan Dalheimer
89edc3e15e Comments and reformating
[ci skip]
2014-04-16 18:03:48 +02:00
Jan Dalheimer
f67ca674c4 Fix bug 2014-04-16 17:54:07 +02:00
Jan Dalheimer
3ed5b1570b LoginDialog changes 2014-04-16 17:38:26 +02:00
Taeyeon Mori
4674aad125 Create a new login dialog 2014-04-16 16:19:12 +02:00
Petr Mrázek
bf1632e4ed Add to the changelog 2014-04-16 01:05:03 +02:00
Petr Mrázek
b286b93281 Give more feedback for YggdrasilTask network errors. 2014-04-16 00:46:41 +02:00
Sky
632c087483 Put changelog additions under right version (0.3.3) 2014-04-15 23:34:51 +01:00
Sky
318a945d74 Update changelog 2014-04-15 23:31:46 +01:00
Petr Mrázek
b6d7ffab47 Detect and report missing local libraries. 2014-04-13 23:06:28 +02:00
Sky
04b36a3e55 Merge pull request #212 from MultiMC/feature_travis_clang
Get Travis to build GCC and Clang versions on Linux as we consider moving to Clang on Linux in general
2014-04-12 17:30:21 +01:00
Jan Dalheimer
3f2152c14d Let travis build a clang version 2014-04-12 18:11:30 +02:00
Sky
8a6c64ef62 Remove specific OpenSSL version from BUILD.md 2014-04-12 04:40:16 +01:00
Sky
6e42d51283 Merge pull request #211 from Drayshak/develop
Add Patreon button with logo
2014-04-12 02:11:51 +01:00
Sky
86830967b6 Give the Patreon button a logo 2014-04-12 02:05:42 +01:00
Sky
1657313105 Add Patreon button. Needs an icon (used I for now). 2014-04-11 02:59:30 +01:00
Jan Dalheimer
a00fb1e8da Only use tabs for intendention 2014-04-09 15:41:49 +02:00
Petr Mrázek
50d18a06d5 Context menu tweaks
* Add create/copy instance actions to the context menu.
* Context menu for the instance view background.
* Top of the context menu is now a header, fixing the misclick problems.
2014-04-08 00:16:49 +02:00
Jan Dalheimer
3cd2b898e5 Merge remote-tracking branch 'origin/develop' into feature_cmake_style
Conflicts:
	CMakeLists.txt
2014-04-07 17:44:52 +02:00
Petr Mrázek
17d4947b30 Merge remote-tracking branch 'origin/feature_dotcmake' into develop 2014-04-06 20:56:40 +02:00
Jan Dalheimer
dd7b6642a3 Use the same style of CMake files everywhere 2014-04-06 19:43:09 +02:00
Jan Dalheimer
eb246c4fa7 .cmake files to reduce "code" duplicate 2014-04-06 18:12:48 +02:00
Jan Dalheimer
6ef38d0873 Coverity build and upload target. Also GitFunctions. 2014-04-06 18:02:28 +02:00
Petr Mrázek
482ad250a4 Workaround for dirty build folders.
My disgust just turned into barely contained rage :<
2014-04-06 03:59:37 +02:00
Petr Mrázek
f9169654c5 Build fixage for the changed build config 2014-04-06 03:48:59 +02:00
Petr Mrázek
e58e2643ca Merge branch 'patch-1' of https://github.com/max96at/MultiMC5 into develop 2014-04-06 00:41:19 +02:00
Petr Mrázek
42e305bb9d Get rid of long rebuilds because of minor cmake config changes 2014-04-06 00:33:33 +02:00
max96at
8594cc8f6c Fix adding icons to custom icon directories. 2014-04-03 19:28:23 +02:00
Petr Mrázek
ad9d082f57 Do not spam logs too much, bump version and add to changelog. 2014-04-01 23:03:04 +02:00
Petr Mrázek
52ff6a4140 Deny april fools outside of first april. 2014-04-01 22:33:15 +02:00
Petr Mrázek
1ef6ec4178 Fix library replace issue 2014-04-01 21:58:15 +02:00
Petr Mrázek
72bc860983 Fix for invalid prelaunch commands 2014-04-01 00:04:26 +02:00
122 changed files with 8952 additions and 2435 deletions

View File

@@ -1,6 +1,7 @@
language: cpp
compiler:
- gcc
- clang
cache: apt
before_install:
- sudo apt-add-repository -y ppa:beineri/opt-qt521
@@ -9,7 +10,7 @@ before_install:
- sudo apt-get update -qq
install:
- sudo apt-get install -y -qq cmake qt52base qt52svg qt52tools qt52x11extras
- if [ "$CXX" = "g++" ]; then sudo apt-get install -y -qq g++-4.8; fi
- sudo apt-get install -y -qq g++-4.8
- if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi
before_script:
- mkdir build

View File

@@ -56,7 +56,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
## Dependencies
* Qt 5.1.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Windows")
* OpenSSL (http://slproweb.com/products/Win32OpenSSL.html) ("Win32 OpenSSL v1.0.1e Light")
* OpenSSL (http://slproweb.com/products/Win32OpenSSL.html) ("Win32 OpenSSL \<version\> Light")
- Microsoft Visual C++ 2008 Redist. is required for this, there's a link on the OpenSSL download page above next to the main download.
* CMake (http://www.cmake.org/cmake/resources/software.html) ("Windows (Win32 Installer)")
* A copy of the MultiMC source (clone it with git)

65
BuildConfig.cpp.in Normal file
View File

@@ -0,0 +1,65 @@
#include "BuildConfig.h"
Config BuildConfig;
Config::Config()
{
// Version information
VERSION_MAJOR = @MultiMC_VERSION_MAJOR@;
VERSION_MINOR = @MultiMC_VERSION_MINOR@;
VERSION_HOTFIX = @MultiMC_VERSION_HOTFIX@;
VERSION_BUILD = @MultiMC_VERSION_BUILD@;
VERSION_TYPE = "@MultiMC_VERSION_TYPE@";
if(VERSION_TYPE == "Release")
versionTypeEnum = Release;
else if(VERSION_TYPE == "ReleaseCandidate")
versionTypeEnum = ReleaseCandidate;
else if(VERSION_TYPE == "Development")
versionTypeEnum = Development;
else
versionTypeEnum = Custom;
VERSION_CHANNEL = "@MultiMC_VERSION_CHANNEL@";
BUILD_PLATFORM = "@MultiMC_BUILD_PLATFORM@";
CHANLIST_URL = "@MultiMC_CHANLIST_URL@";
NOTIFICATION_URL = "@MultiMC_NOTIFICATION_URL@";
FULL_VERSION_STR = "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@";
UPDATER_DRY_RUN = @MultiMC_UPDATER_DRY_RUN_value@;
UPDATER_FORCE_LOCAL = @MultiMC_UPDATER_FORCE_LOCAL_value@;
GIT_COMMIT = "@MultiMC_GIT_COMMIT@";
GIT_COMMIT_CSTR = "@MultiMC_GIT_COMMIT@";
VERSION_STR = "@MultiMC_VERSION_STRING@";
VERSION_CSTR = "@MultiMC_VERSION_STRING@";
NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@";
}
QString Config::versionTypeName() const
{
switch (versionTypeEnum)
{
case Release:
return "Stable Release";
case ReleaseCandidate:
return "Release Candidate";
case Development:
return "Development";
case Custom:
default:
return "Custom";
}
}
QString Config::printableVersionString() const
{
QString vstr = QString("%1.%2").arg(QString::number(VERSION_MAJOR), QString::number(VERSION_MINOR));
if (VERSION_HOTFIX > 0) vstr += "." + QString::number(VERSION_HOTFIX);
// If the build is a development build or release candidate, add that info to the end.
if (versionTypeEnum == Development) vstr += "-dev" + QString::number(VERSION_BUILD);
else if (versionTypeEnum == ReleaseCandidate) vstr += "-rc" + QString::number(VERSION_BUILD);
return vstr;
}

90
BuildConfig.h Normal file
View File

@@ -0,0 +1,90 @@
#pragma once
#include <QString>
/**
* \brief The Config class holds all the build-time information passed from the build system.
*/
class Config
{
public:
Config();
/// The major version number.
int VERSION_MAJOR;
/// The minor version number.
int VERSION_MINOR;
/// The hotfix number.
int VERSION_HOTFIX;
/// The build number.
int VERSION_BUILD;
/// The build type, as specified at build time.
QString VERSION_TYPE;
/// The build type, transformed.
enum Type
{
/// Version type for stable release builds.
Release,
/// Version type for release candidates.
ReleaseCandidate,
/// Version type for development builds.
Development,
/// Version type for custom builds. This is the default when no version type is specified.
Custom
} versionTypeEnum;
/**
* The version channel
* This is used by the updater to determine what channel the current version came from.
*/
QString VERSION_CHANNEL;
/// A short string identifying this build's platform. For example, "lin64" or "win32".
QString BUILD_PLATFORM;
/// URL for the updater's channel
QString CHANLIST_URL;
/// URL for notifications
QString NOTIFICATION_URL;
/// Used for matching notifications
QString FULL_VERSION_STR;
/// enabled for updater dry run
bool UPDATER_DRY_RUN;
/// enabled for updater dry run
bool UPDATER_FORCE_LOCAL;
/// The commit hash of this build
QString GIT_COMMIT;
const char* GIT_COMMIT_CSTR;
/// This is printed on start to standard output
QString VERSION_STR;
/// Version string as a char string. Used by the crash handling system to avoid touching heap memory.
const char* VERSION_CSTR;
/**
* This is used to fetch the news RSS feed.
* It defaults in CMakeLists.txt to "http://multimc.org/rss.xml"
*/
QString NEWS_RSS_URL;
/**
* \brief Converts the Version to a string.
* \return The version number in string format (major.minor.revision.build).
*/
QString printableVersionString() const;
/**
* returns a string representation of the version channel type, suitable for printing.
*/
QString versionTypeName() const;
};
extern Config BuildConfig;

File diff suppressed because it is too large Load Diff

376
HandleCrash.cpp Normal file
View File

@@ -0,0 +1,376 @@
/* Copyright 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.
*/
// This is the Unix implementation of MultiMC's crash handling system.
#include <stdio.h>
#include <iostream>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <MultiMC.h>
#if defined Q_OS_UNIX
#include <sys/utsname.h>
#include <execinfo.h>
#elif defined Q_OS_WIN32
#include <windows.h>
#include <dbghelp.h>
#include <WinBacktrace.h>
#endif
#include "BuildConfig.h"
#include "HandleCrash.h"
// The maximum number of frames to include in the backtrace.
#define BT_SIZE 20
#define DUMPF_NAME_FMT "mmc-crash-%X.bm" // Black magic? Bowel movement? Dump?
// 1234567890 1234
// The maximum number of digits in a unix timestamp when encoded in hexadecimal is about 17.
// Our format string is ~14 characters long.
// The maximum length of the dump file's filename should be well over both of these. 42 is a good number.
#define DUMPF_NAME_LEN 42
// {{{ Platform hackery
#if defined Q_OS_UNIX
struct CrashData
{
int signal = 0;
};
// This has to be declared here, after the CrashData struct, but before the function that uses it.
void handleCrash(CrashData);
void handler(int sig)
{
CrashData cData;
cData.signal = sig;
handleCrash(cData);
}
#elif defined Q_OS_WIN32
// Struct for storing platform specific crash information.
// This gets passed into the generic handler, which will use
// it to access platform specific information.
struct CrashData
{
EXCEPTION_RECORD* exceptionInfo;
CONTEXT* context;
};
void handleCrash(CrashData);
LONG WINAPI ExceptionFilter(EXCEPTION_POINTERS* eInfo)
{
CrashData cData;
cData.exceptionInfo = eInfo->ExceptionRecord;
cData.context = eInfo->ContextRecord;
handleCrash(cData);
return EXCEPTION_EXECUTE_HANDLER;
}
#endif
// }}}
// {{{ Handling
#ifdef Q_OS_WIN32
// #ThanksMicrosoft
// Blame Microsoft for this atrocity.
void dprintf(int fd, const char* fmt...)
{
va_list args;
va_start(args, fmt);
char buffer[10240];
// Just sprintf to a really long string and hope it works...
// This is a hack, but I can't think of a better way to do it easily.
int len = vsnprintf(buffer, 10240, fmt, args);
printf(buffer, fmt, args);
write(fd, buffer, len);
va_end(args);
}
#endif
void getVsnType(char* out);
void readFromTo(int from, int to);
void dumpErrorInfo(int dumpFile, CrashData crash)
{
#ifdef Q_OS_UNIX
// TODO: Moar unix
dprintf(dumpFile, "Signal: %d\n", crash.signal);
#elif defined Q_OS_WIN32
EXCEPTION_RECORD* excInfo = crash.exceptionInfo;
dprintf(dumpFile, "Exception Code: %d\n", excInfo->ExceptionCode);
dprintf(dumpFile, "Exception Address: 0x%0X\n", excInfo->ExceptionAddress);
#endif
}
void dumpMiscInfo(int dumpFile)
{
char vsnType[42]; // The version type. If it's more than 42 chars, the universe might implode...
// Get MMC info.
getVsnType(vsnType);
// Get MMC info.
getVsnType(vsnType);
dprintf(dumpFile, "MultiMC Version: %s\n", BuildConfig.VERSION_CSTR);
dprintf(dumpFile, "MultiMC Version Type: %s\n", vsnType);
}
void dumpBacktrace(int dumpFile, CrashData crash)
{
#ifdef Q_OS_UNIX
// Variables for storing crash info.
void* trace[BT_SIZE]; // Backtrace frames
size_t size; // The backtrace size
// Get the backtrace.
size = backtrace(trace, BT_SIZE);
// Dump the backtrace
dprintf(dumpFile, "---- BEGIN BACKTRACE ----\n");
backtrace_symbols_fd(trace, size, dumpFile);
dprintf(dumpFile, "---- END BACKTRACE ----\n");
#elif defined Q_OS_WIN32
dprintf(dumpFile, "---- BEGIN BACKTRACE ----\n");
StackFrame stack[BT_SIZE];
size_t size;
SYMBOL_INFO *symbol;
HANDLE process;
size = getBacktrace(stack, BT_SIZE, *crash.context);
// FIXME: Accessing heap memory is supposedly "dangerous",
// but I can't find another way of doing this.
// Initialize
process = GetCurrentProcess();
if (!SymInitialize(process, NULL, true))
{
dprintf(dumpFile, "Failed to initialize symbol handler. Can't print stack trace.\n");
dprintf(dumpFile, "Here's a list of addresses in the call stack instead:\n");
for(int i = 0; i < size; i++)
{
dprintf(dumpFile, "0x%0X\n", (DWORD64)stack[i].address);
}
} else {
// Allocate memory... ._.
symbol = (SYMBOL_INFO *) calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
// Dump stacktrace
for(int i = 0; i < size; i++)
{
DWORD64 addr = (DWORD64)stack[i].address;
if (!SymFromAddr(process, (DWORD64)(addr), 0, symbol))
dprintf(dumpFile, "?? - 0x%0X\n", addr);
else
dprintf(dumpFile, "%s - 0x%0X\n", symbol->Name, symbol->Address);
}
free(symbol);
}
dprintf(dumpFile, "---- END BACKTRACE ----\n");
#endif
}
void dumpSysInfo(int dumpFile)
{
#ifdef Q_OS_UNIX
bool gotSysInfo = false; // True if system info check succeeded
utsname sysinfo; // System information
// Dump system info
if (uname(&sysinfo) >= 0)
{
dprintf(dumpFile, "OS System: %s\n", sysinfo.sysname);
dprintf(dumpFile, "OS Machine: %s\n", sysinfo.machine);
dprintf(dumpFile, "OS Release: %s\n", sysinfo.release);
dprintf(dumpFile, "OS Version: %s\n", sysinfo.version);
} else {
dprintf(dumpFile, "OS System: Unknown Unix");
}
#else
// TODO: Get more information here.
dprintf(dumpFile, "OS System: Windows");
#endif
}
void dumpLogs(int dumpFile)
{
int otherFile;
// Attempt to attach the log file if the logger was initialized.
dprintf(dumpFile, "---- BEGIN LOGS ----\n");
if (loggerInitialized)
{
otherFile = open("MultiMC-0.log", O_RDONLY);
readFromTo(otherFile, dumpFile);
} else {
dprintf(dumpFile, "Logger not initialized.\n");
}
dprintf(dumpFile, "---- END LOGS ----\n");
}
// The signal handler. If this function is called, it means shit has probably collided with some sort of device one might use to keep oneself cool.
// This is the generic part of the code that will be called after platform specific handling is finished.
void handleCrash(CrashData crash)
{
#ifdef Q_OS_UNIX
fprintf(stderr, "Fatal error! Received signal %d\n", crash.signal);
#endif
time_t unixTime = 0; // Unix timestamp. Used to give our crash dumps "unique" names.
char dumpFileName[DUMPF_NAME_LEN]; // The name of the file we're dumping to.
int dumpFile; // File descriptor for our dump file.
// Determine what our dump file should be called.
// We'll just call it "mmc-crash-<unixtime>.dump"
// First, check the time.
time(&unixTime);
// Now we get to do some !!FUN!! hackery to ensure we don't use the stack when we convert
// the timestamp from an int to a string. To do this, we just allocate a fixed size array
// of chars on the stack, and sprintf into it. We know the timestamp won't ever be longer
// than a certain number of digits, so this should work just fine.
// sprintf doesn't support writing signed values as hex, so this breaks on negative timestamps.
// It really shouldn't matter, though...
sprintf(dumpFileName, DUMPF_NAME_FMT, unixTime);
// Now, we need to open the file.
// Fail if it already exists. This should never happen.
dumpFile = open(dumpFileName, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (dumpFile >= 0)
{
// If we opened the dump file successfully.
// Dump everything we can and GTFO.
fprintf(stderr, "Dumping crash report to %s\n", dumpFileName);
// Dump misc info
dprintf(dumpFile, "Unix Time: %d\n", unixTime);
dumpErrorInfo(dumpFile, crash);
dumpMiscInfo(dumpFile);
dprintf(dumpFile, "\n");
dumpSysInfo(dumpFile);
dprintf(dumpFile, "\n");
dumpBacktrace(dumpFile, crash);
dprintf(dumpFile, "\n");
// DIE DIE DIE!
exit(1);
}
else
{
fprintf(stderr, "Failed to open dump file %s to write crash info (ERRNO: %d)\n", dumpFileName, errno);
exit(2);
}
}
// Reads data from the file descriptor on the first argument into the second argument.
void readFromTo(int from, int to)
{
char buffer[1024];
size_t lastread = 1;
while (lastread > 0)
{
lastread = read(from, buffer, 1024);
if (lastread > 0) write(to, buffer, lastread);
}
}
// Writes the current version type to the given char buffer.
void getVsnType(char* out)
{
switch (BuildConfig.versionTypeEnum)
{
case Config::Release:
sprintf(out, "Release");
break;
case Config::ReleaseCandidate:
sprintf(out, "ReleaseCandidate");
break;
case Config::Development:
sprintf(out, "Development");
break;
default:
sprintf(out, "Unknown");
break;
}
}
// }}}
// {{{ Misc
#if defined TEST_SEGV
// Causes a crash. For testing.
void testCrash()
{
char* lol = (char*)MMC->settings().get();
lol -= 8;
// Throw shit at the fan.
for (int i = 0; i < 8; i++)
lol[i] = 'f';
}
#endif
// Initializes the Unix crash handler.
void initBlackMagic()
{
#ifdef Q_OS_UNIX
// Register the handler.
signal(SIGSEGV, handler);
signal(SIGABRT, handler);
#elif defined Q_OS_WIN32
// I hate Windows
SetUnhandledExceptionFilter(ExceptionFilter);
#endif
#ifdef TEST_SEGV
testCrash();
#endif
}
// }}}

18
HandleCrash.h Normal file
View File

@@ -0,0 +1,18 @@
// This is a simple header file for the crash handling system. It exposes only one method,
// initBlackMagic, which initializes the system, registering signal handlers, or doing
// whatever stupid things need to be done on Windows.
// The platform specific implementations for this system are in UnixCrash.cpp and
// WinCrash.cpp.
#if defined Q_OS_WIN
#warning Crash handling is not yet implemented on Windows.
#elif defined Q_OS_UNIX
#else
#warning Crash handling is not supported on this platform.
#endif
/**
* Initializes the crash handling system.
*/
void initBlackMagic();

View File

@@ -1,4 +1,6 @@
#include "MultiMC.h"
#include "BuildConfig.h"
#include <iostream>
#include <QDir>
#include <QFileInfo>
@@ -50,9 +52,7 @@ static const int APPDATA_BUFFER_SIZE = 1024;
using namespace Util::Commandline;
MultiMC::MultiMC(int &argc, char **argv, bool root_override)
: QApplication(argc, argv),
m_version{VERSION_MAJOR, VERSION_MINOR, VERSION_HOTFIX, VERSION_BUILD,
MultiMCVersion::VERSION_TYPE, VERSION_CHANNEL, BUILD_PLATFORM}
: QApplication(argc, argv)
{
setOrganizationName("MultiMC");
setApplicationName("MultiMC5");
@@ -86,6 +86,7 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override)
parser.addShortOpt("launch", 'l');
parser.addDocumentation("launch", "tries to launch the given instance", "<inst>");
*/
// parse the arguments
try
{
@@ -111,8 +112,8 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override)
// display version and exit
if (args["version"].toBool())
{
std::cout << "Version " << VERSION_STR << std::endl;
std::cout << "Git " << GIT_COMMIT << std::endl;
std::cout << "Version " << BuildConfig.VERSION_STR.toStdString() << std::endl;
std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl;
m_status = MultiMC::Succeeded;
return;
}
@@ -161,12 +162,23 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override)
#endif
}
// static data paths... mostly just for translations
#ifdef Q_OS_LINUX
QDir foo(PathCombine(binPath, ".."));
staticDataPath = foo.absolutePath();
#elif defined(Q_OS_WIN32)
staticDataPath = binPath;
#elif defined(Q_OS_MAC)
QDir foo(PathCombine(rootPath, "Contents/Resources"));
staticDataPath = foo.absolutePath();
#endif
// init the logger
initLogger();
QLOG_INFO() << "MultiMC 5, (c) 2013 MultiMC Contributors";
QLOG_INFO() << "Version : " << VERSION_STR;
QLOG_INFO() << "Git commit : " << GIT_COMMIT;
QLOG_INFO() << "Version : " << BuildConfig.VERSION_STR;
QLOG_INFO() << "Git commit : " << BuildConfig.GIT_COMMIT;
if (adjustedBy.size())
{
QLOG_INFO() << "Work dir before adjustment : " << origcwdPath;
@@ -179,6 +191,7 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override)
}
QLOG_INFO() << "Binary path : " << binPath;
QLOG_INFO() << "Application root path : " << rootPath;
QLOG_INFO() << "Static data path : " << staticDataPath;
// load settings
initGlobalSettings();
@@ -193,7 +206,7 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override)
m_notificationChecker.reset(new NotificationChecker());
// initialize the news checker
m_newsChecker.reset(new NewsChecker(NEWS_RSS_URL));
m_newsChecker.reset(new NewsChecker(BuildConfig.NEWS_RSS_URL));
// initialize the status checker
m_statusChecker.reset(new StatusChecker());
@@ -287,7 +300,8 @@ void MultiMC::initTranslations()
}
m_mmc_translator.reset(new QTranslator());
if (m_mmc_translator->load("mmc_" + locale.bcp47Name(), MMC->root() + "/translations"))
if (m_mmc_translator->load("mmc_" + locale.bcp47Name(),
MMC->staticData() + "/translations"))
{
QLOG_DEBUG() << "Loading MMC Language File for"
<< locale.bcp47Name().toLocal8Bit().constData() << "...";
@@ -327,13 +341,16 @@ void MultiMC::initLogger()
logger.addDestination(m_debugDestination.get());
// log all the things
logger.setLoggingLevel(QsLogging::TraceLevel);
loggerInitialized = true;
}
bool loggerInitialized = false;
void MultiMC::initGlobalSettings()
{
m_settings.reset(new INISettingsObject("multimc.cfg", this));
// Updates
m_settings->registerSetting("UpdateChannel", version().channel);
m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
m_settings->registerSetting("AutoUpdate", true);
// Notifications
@@ -341,31 +358,57 @@ void MultiMC::initGlobalSettings()
// FTB
m_settings->registerSetting("TrackFTBInstances", false);
QString ftbDataDefault;
#ifdef Q_OS_LINUX
QString ftbDefault = QDir::home().absoluteFilePath(".ftblauncher");
QString ftbDefault = ftbDataDefault = QDir::home().absoluteFilePath(".ftblauncher");
#elif defined(Q_OS_WIN32)
wchar_t buf[APPDATA_BUFFER_SIZE];
QString ftbDefault;
if(!GetEnvironmentVariableW(L"APPDATA", buf, APPDATA_BUFFER_SIZE))
wchar_t newBuf[APPDATA_BUFFER_SIZE];
QString ftbDefault, newFtbDefault, oldFtbDefault;
if (!GetEnvironmentVariableW(L"LOCALAPPDATA", newBuf, APPDATA_BUFFER_SIZE))
{
QLOG_FATAL() << "Your LOCALAPPDATA folder is missing! If you are on windows, this means your system is broken. If you aren't on windows, how the **** are you running the windows build????";
}
else
{
newFtbDefault = QDir(QString::fromWCharArray(newBuf)).absoluteFilePath("ftblauncher");
}
if (!GetEnvironmentVariableW(L"APPDATA", buf, APPDATA_BUFFER_SIZE))
{
QLOG_FATAL() << "Your APPDATA folder is missing! If you are on windows, this means your system is broken. If you aren't on windows, how the **** are you running the windows build????";
}
else
{
ftbDefault = PathCombine(QString::fromWCharArray(buf), "ftblauncher");
oldFtbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher");
}
if (QFile::exists(QDir(newFtbDefault).absoluteFilePath("ftblaunch.cfg")))
{
QLOG_INFO() << "Old FTB setup";
ftbDefault = ftbDataDefault = oldFtbDefault;
}
else
{
QLOG_INFO() << "New FTB setup";
ftbDefault = oldFtbDefault;
ftbDataDefault = newFtbDefault;
}
#elif defined(Q_OS_MAC)
QString ftbDefault =
PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher");
QString ftbDefault = ftbDataDefault =
PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher");
#endif
m_settings->registerSetting("FTBLauncherDataRoot", ftbDataDefault);
m_settings->registerSetting("FTBLauncherRoot", ftbDefault);
QLOG_INFO() << "FTB Launcher paths:"
<< m_settings->get("FTBLauncherDataRoot").toString()
<< "and"
<< m_settings->get("FTBLauncherRoot").toString();
m_settings->registerSetting("FTBRoot");
if (m_settings->get("FTBRoot").isNull())
{
QString ftbRoot;
QFile f(QDir(m_settings->get("FTBLauncherRoot").toString())
.absoluteFilePath("ftblaunch.cfg"));
.absoluteFilePath("ftblaunch.cfg"));
QLOG_INFO() << "Attempting to read" << f.fileName();
if (f.open(QFile::ReadOnly))
{
@@ -412,6 +455,7 @@ void MultiMC::initGlobalSettings()
// Console
m_settings->registerSetting("ShowConsole", true);
m_settings->registerSetting("RaiseConsole", true);
m_settings->registerSetting("AutoCloseConsole", true);
m_settings->registerSetting("LogPrePostOutput", true);
@@ -470,6 +514,7 @@ void MultiMC::initHttpMetaCache()
m_metacache->addBase("versions", QDir("versions").absolutePath());
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir(root()).absolutePath());

View File

@@ -1,8 +1,6 @@
#pragma once
#include "config.h"
#include <QApplication>
#include "MultiMCVersion.h"
#include <memory>
#include "logger/QsLog.h"
#include "logger/QsLogDest.h"
@@ -50,6 +48,9 @@ enum UpdateFlag
Q_DECLARE_FLAGS(UpdateFlags, UpdateFlag);
Q_DECLARE_OPERATORS_FOR_FLAGS(UpdateFlags);
// Global var used by the crash handling system to determine if a log file should be included in a crash report.
extern bool loggerInitialized;
class MultiMC : public QApplication
{
Q_OBJECT
@@ -87,11 +88,6 @@ public:
return m_status;
}
MultiMCVersion version()
{
return m_version;
}
std::shared_ptr<QNetworkAccessManager> qnam()
{
return m_qnam;
@@ -154,6 +150,11 @@ public:
*/
bool openJsonEditor(const QString &filename);
/// this is the static data. it stores things that don't move.
const QString &staticData()
{
return staticDataPath;
}
/// this is the root of the 'installation'. Used for automatic updates
const QString &root()
{
@@ -222,10 +223,10 @@ private:
UpdateFlags m_updateOnExitFlags = None;
QString rootPath;
QString staticDataPath;
QString binPath;
QString dataPath;
QString origcwdPath;
Status m_status = MultiMC::Failed;
MultiMCVersion m_version;
};

View File

@@ -1,96 +0,0 @@
/* 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 <QString>
/*!
* \brief The Version class represents a MultiMC version.
*/
struct MultiMCVersion
{
enum Type
{
//! Version type for stable release builds.
Release,
//! Version type for release candidates.
ReleaseCandidate,
//! Version type for development builds.
Development,
//! Version type for custom builds. This is the default when no version type is specified.
Custom
};
/*!
* \brief Converts the Version to a string.
* \return The version number in string format (major.minor.revision.build).
*/
QString toString() const
{
QString vstr = QString("%1.%2").arg(
QString::number(major),
QString::number(minor));
if (hotfix > 0) vstr += "." + QString::number(hotfix);
// If the build is a development build or release candidate, add that info to the end.
if (type == Development) vstr += "-dev" + QString::number(build);
else if (type == ReleaseCandidate) vstr += "-rc" + QString::number(build);
return vstr;
}
QString typeName() const
{
switch (type)
{
case Release:
return "Stable Release";
case ReleaseCandidate:
return "Release Candidate";
case Development:
return "Development";
case Custom:
default:
return "Custom";
}
}
//! The major version number.
int major;
//! The minor version number.
int minor;
//! The hotfix number.
int hotfix;
//! The build number.
int build;
//! The build type.
Type type;
//! The build channel.
QString channel;
//! A short string identifying the platform that this version is for. For example, lin64 or win32.
QString platform;
};

77
WinBacktrace.cpp Normal file
View File

@@ -0,0 +1,77 @@
/* Copyright 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.
*/
// CAUTION:
// This file contains all manner of hackery and insanity.
// I will not be responsible for any loss of sanity due to reading this code.
// Here be dragons!
#include "WinBacktrace.h"
#include <windows.h>
#ifndef __i386__
#error WinBacktrace is only supported on x86 architectures.
#endif
// We need to do some crazy shit to walk through the stack.
// Windows unwinds the stack when an exception is thrown, so we
// need to examine the EXCEPTION_POINTERS's CONTEXT.
size_t getBacktrace(StackFrame *stack, size_t size, CONTEXT ctx)
{
// Written using information and a bit of pseudocode from
// http://www.eptacom.net/pubblicazioni/pub_eng/except.html
// This is probably one of the most horrifying things I've ever written.
// This tracks whether the current EBP is valid.
// When an invalid EBP is encountered, we stop walking the stack.
bool validEBP = true;
DWORD ebp = ctx.Ebp; // The current EBP (Extended Base Pointer)
DWORD eip = ctx.Eip;
int i;
for (i = 0; i < size; i++)
{
if (ebp & 3)
validEBP = false;
// FIXME: This function is obsolete, according to MSDN.
else if (IsBadReadPtr((void*) ebp, 8))
validEBP = false;
if (!validEBP) break;
// Find the caller.
// On the first iteration, the caller is whatever EIP points to.
// On successive iterations, the caller is the byte after EBP.
BYTE* caller = !i ? (BYTE*)eip : *((BYTE**) ebp + 1);
// The first ebp is the EBP from the CONTEXT.
// On successive iterations, the EBP is the DWORD that the previous EBP points to.
ebp = !i ? ebp : *(DWORD*)ebp;
// Find the caller's module.
// We'll use VirtualQuery to get information about the caller's address.
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(caller, &mbi, sizeof(mbi));
// We can get the instance handle from the allocation base.
HINSTANCE hInst = (HINSTANCE)mbi.AllocationBase;
// If the handle is 0, then the EBP is invalid.
if (hInst == 0) validEBP = false;
// Otherwise, dump info about the caller.
else stack[i].address = (void*)caller;
}
return i;
}

44
WinBacktrace.h Normal file
View File

@@ -0,0 +1,44 @@
/* Copyright 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.
*/
#pragma once
#include <windows.h>
#ifndef SF_STR_LEN
// The max length of all strings in the StackFrame struct.
// Because it must be stack allocated, this must be known at compile time.
// Stuff longer than this will be truncated.
// Defaults to 4096 (4kB)
#define SF_STR_LEN 4096
#endif
// Data structure for holding information about a stack frame.
// There's some more hackery in here so it can be allocated on the stack.
struct StackFrame
{
// The address of this stack frame.
void* address;
// The name of the function at this address.
char funcName[SF_STR_LEN];
};
// This function walks through the given CONTEXT structure, extracting a
// backtrace from it.
// The backtrace will be put into the array given by the `stack` argument
// with a maximum length of `size`.
// This function returns the size of the backtrace retrieved.
size_t getBacktrace(StackFrame* stack, size_t size, CONTEXT ctx);

View File

@@ -57,3 +57,28 @@
0.3.1:
- Fix copying of FTB instances (instance type is changed properly now)
- Customizing FTB pack versions will remove the FTB pack patch file
0.3.2:
- Fix issues with libraries not getting replaced properly (fixes instance startup for new instances)
- Fix april fools
0.3.3:
- Tweak context menu to prevent accidental clicks
- Fix adding icons to custom icon directories
- Added a Patreon button to the toolbar
- Minecraft authentication tasks now provide better error reports
0.3.4:
- Show a list of Patreon patrons in credits section of the about dialog
- Make the console window raise itself after minecraft closes
- Add Control/Command+q shortcut to quit from the main window
- Add french translation
- Download and cache FML libs for legacy versions
- Update the OS X icon
- Fix FTB libraries not being used properly
0.3.5
- More versions are now selectable when changing instance versions
- Fix for Forge/FML changing its mcmod.info metadata format
0.3.6
- New server status - now with more color
- Fix for FTB tracking issues
- Fix for translations on OSX not working
- Screenshot dialog should be harder to lose track of when used from the console window
- A crash handler implementation has been added.

786
cmake/BundleUtilities.cmake Normal file
View File

@@ -0,0 +1,786 @@
# - Functions to help assemble a standalone bundle application.
# A collection of CMake utility functions useful for dealing with .app
# bundles on the Mac and bundle-like directories on any OS.
#
# The following functions are provided by this module:
# fixup_bundle
# copy_and_fixup_bundle
# verify_app
# get_bundle_main_executable
# get_dotapp_dir
# get_bundle_and_executable
# get_bundle_all_executables
# get_item_key
# clear_bundle_keys
# set_bundle_key_values
# get_bundle_keys
# copy_resolved_item_into_bundle
# copy_resolved_framework_into_bundle
# fixup_bundle_item
# verify_bundle_prerequisites
# verify_bundle_symlinks
# Requires CMake 2.6 or greater because it uses function, break and
# PARENT_SCOPE. Also depends on GetPrerequisites.cmake.
#
# FIXUP_BUNDLE(<app> <libs> <dirs>)
# Fix up a bundle in-place and make it standalone, such that it can be
# drag-n-drop copied to another machine and run on that machine as long as all
# of the system libraries are compatible.
#
# If you pass plugins to fixup_bundle as the libs parameter, you should install
# them or copy them into the bundle before calling fixup_bundle. The "libs"
# parameter is a list of libraries that must be fixed up, but that cannot be
# determined by otool output analysis. (i.e., plugins)
#
# Gather all the keys for all the executables and libraries in a bundle, and
# then, for each key, copy each prerequisite into the bundle. Then fix each one
# up according to its own list of prerequisites.
#
# Then clear all the keys and call verify_app on the final bundle to ensure
# that it is truly standalone.
#
# COPY_AND_FIXUP_BUNDLE(<src> <dst> <libs> <dirs>)
# Makes a copy of the bundle <src> at location <dst> and then fixes up the
# new copied bundle in-place at <dst>...
#
# VERIFY_APP(<app>)
# Verifies that an application <app> appears valid based on running analysis
# tools on it. Calls "message(FATAL_ERROR" if the application is not verified.
#
# GET_BUNDLE_MAIN_EXECUTABLE(<bundle> <result_var>)
# The result will be the full path name of the bundle's main executable file
# or an "error:" prefixed string if it could not be determined.
#
# GET_DOTAPP_DIR(<exe> <dotapp_dir_var>)
# Returns the nearest parent dir whose name ends with ".app" given the full
# path to an executable. If there is no such parent dir, then simply return
# the dir containing the executable.
#
# The returned directory may or may not exist.
#
# GET_BUNDLE_AND_EXECUTABLE(<app> <bundle_var> <executable_var> <valid_var>)
# Takes either a ".app" directory name or the name of an executable
# nested inside a ".app" directory and returns the path to the ".app"
# directory in <bundle_var> and the path to its main executable in
# <executable_var>
#
# GET_BUNDLE_ALL_EXECUTABLES(<bundle> <exes_var>)
# Scans the given bundle recursively for all executable files and accumulates
# them into a variable.
#
# GET_ITEM_KEY(<item> <key_var>)
# Given a file (item) name, generate a key that should be unique considering
# the set of libraries that need copying or fixing up to make a bundle
# standalone. This is essentially the file name including extension with "."
# replaced by "_"
#
# This key is used as a prefix for CMake variables so that we can associate a
# set of variables with a given item based on its key.
#
# CLEAR_BUNDLE_KEYS(<keys_var>)
# Loop over the list of keys, clearing all the variables associated with each
# key. After the loop, clear the list of keys itself.
#
# Caller of get_bundle_keys should call clear_bundle_keys when done with list
# of keys.
#
# SET_BUNDLE_KEY_VALUES(<keys_var> <context> <item> <exepath> <dirs>
# <copyflag>)
# Add a key to the list (if necessary) for the given item. If added,
# also set all the variables associated with that key.
#
# GET_BUNDLE_KEYS(<app> <libs> <dirs> <keys_var>)
# Loop over all the executable and library files within the bundle (and given
# as extra <libs>) and accumulate a list of keys representing them. Set
# values associated with each key such that we can loop over all of them and
# copy prerequisite libs into the bundle and then do appropriate
# install_name_tool fixups.
#
# COPY_RESOLVED_ITEM_INTO_BUNDLE(<resolved_item> <resolved_embedded_item>)
# Copy a resolved item into the bundle if necessary. Copy is not necessary if
# the resolved_item is "the same as" the resolved_embedded_item.
#
# COPY_RESOLVED_FRAMEWORK_INTO_BUNDLE(<resolved_item> <resolved_embedded_item>)
# Copy a resolved framework into the bundle if necessary. Copy is not necessary
# if the resolved_item is "the same as" the resolved_embedded_item.
#
# By default, BU_COPY_FULL_FRAMEWORK_CONTENTS is not set. If you want full
# frameworks embedded in your bundles, set BU_COPY_FULL_FRAMEWORK_CONTENTS to
# ON before calling fixup_bundle. By default,
# COPY_RESOLVED_FRAMEWORK_INTO_BUNDLE copies the framework dylib itself plus
# the framework Resources directory.
#
# FIXUP_BUNDLE_ITEM(<resolved_embedded_item> <exepath> <dirs>)
# Get the direct/non-system prerequisites of the resolved embedded item. For
# each prerequisite, change the way it is referenced to the value of the
# _EMBEDDED_ITEM keyed variable for that prerequisite. (Most likely changing to
# an "@executable_path" style reference.)
#
# This function requires that the resolved_embedded_item be "inside" the bundle
# already. In other words, if you pass plugins to fixup_bundle as the libs
# parameter, you should install them or copy them into the bundle before
# calling fixup_bundle. The "libs" parameter is a list of libraries that must
# be fixed up, but that cannot be determined by otool output analysis. (i.e.,
# plugins)
#
# Also, change the id of the item being fixed up to its own _EMBEDDED_ITEM
# value.
#
# Accumulate changes in a local variable and make *one* call to
# install_name_tool at the end of the function with all the changes at once.
#
# If the BU_CHMOD_BUNDLE_ITEMS variable is set then bundle items will be
# marked writable before install_name_tool tries to change them.
#
# VERIFY_BUNDLE_PREREQUISITES(<bundle> <result_var> <info_var>)
# Verifies that the sum of all prerequisites of all files inside the bundle
# are contained within the bundle or are "system" libraries, presumed to exist
# everywhere.
#
# VERIFY_BUNDLE_SYMLINKS(<bundle> <result_var> <info_var>)
# Verifies that any symlinks found in the bundle point to other files that are
# already also in the bundle... Anything that points to an external file causes
# this function to fail the verification.
#=============================================================================
# Copyright 2008-2009 Kitware, Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
# The functions defined in this file depend on the get_prerequisites function
# (and possibly others) found in:
#
get_filename_component(BundleUtilities_cmake_dir "${CMAKE_CURRENT_LIST_FILE}" PATH)
include("${BundleUtilities_cmake_dir}/GetPrerequisites.cmake")
function(get_bundle_main_executable bundle result_var)
set(result "error: '${bundle}/Contents/Info.plist' file does not exist")
if(EXISTS "${bundle}/Contents/Info.plist")
set(result "error: no CFBundleExecutable in '${bundle}/Contents/Info.plist' file")
set(line_is_main_executable 0)
set(bundle_executable "")
# Read Info.plist as a list of lines:
#
set(eol_char "E")
file(READ "${bundle}/Contents/Info.plist" info_plist)
string(REGEX REPLACE ";" "\\\\;" info_plist "${info_plist}")
string(REGEX REPLACE "\n" "${eol_char};" info_plist "${info_plist}")
# Scan the lines for "<key>CFBundleExecutable</key>" - the line after that
# is the name of the main executable.
#
foreach(line ${info_plist})
if(line_is_main_executable)
string(REGEX REPLACE "^.*<string>(.*)</string>.*$" "\\1" bundle_executable "${line}")
break()
endif()
if(line MATCHES "^.*<key>CFBundleExecutable</key>.*$")
set(line_is_main_executable 1)
endif()
endforeach()
if(NOT "${bundle_executable}" STREQUAL "")
if(EXISTS "${bundle}/Contents/MacOS/${bundle_executable}")
set(result "${bundle}/Contents/MacOS/${bundle_executable}")
else()
# Ultimate goal:
# If not in "Contents/MacOS" then scan the bundle for matching files. If
# there is only one executable file that matches, then use it, otherwise
# it's an error...
#
#file(GLOB_RECURSE file_list "${bundle}/${bundle_executable}")
# But for now, pragmatically, it's an error. Expect the main executable
# for the bundle to be in Contents/MacOS, it's an error if it's not:
#
set(result "error: '${bundle}/Contents/MacOS/${bundle_executable}' does not exist")
endif()
endif()
else()
#
# More inclusive technique... (This one would work on Windows and Linux
# too, if a developer followed the typical Mac bundle naming convention...)
#
# If there is no Info.plist file, try to find an executable with the same
# base name as the .app directory:
#
endif()
set(${result_var} "${result}" PARENT_SCOPE)
endfunction()
function(get_dotapp_dir exe dotapp_dir_var)
set(s "${exe}")
if(s MATCHES "^.*/.*\\.app/.*$")
# If there is a ".app" parent directory,
# ascend until we hit it:
# (typical of a Mac bundle executable)
#
set(done 0)
while(NOT ${done})
get_filename_component(snamewe "${s}" NAME_WE)
get_filename_component(sname "${s}" NAME)
get_filename_component(sdir "${s}" PATH)
set(s "${sdir}")
if(sname MATCHES "\\.app$")
set(done 1)
set(dotapp_dir "${sdir}/${sname}")
endif()
endwhile()
else()
# Otherwise use a directory containing the exe
# (typical of a non-bundle executable on Mac, Windows or Linux)
#
is_file_executable("${s}" is_executable)
if(is_executable)
get_filename_component(sdir "${s}" PATH)
set(dotapp_dir "${sdir}")
else()
set(dotapp_dir "${s}")
endif()
endif()
set(${dotapp_dir_var} "${dotapp_dir}" PARENT_SCOPE)
endfunction()
function(get_bundle_and_executable app bundle_var executable_var valid_var)
set(valid 0)
if(EXISTS "${app}")
# Is it a directory ending in .app?
if(IS_DIRECTORY "${app}")
if(app MATCHES "\\.app$")
get_bundle_main_executable("${app}" executable)
if(EXISTS "${app}" AND EXISTS "${executable}")
set(${bundle_var} "${app}" PARENT_SCOPE)
set(${executable_var} "${executable}" PARENT_SCOPE)
set(valid 1)
#message(STATUS "info: handled .app directory case...")
else()
message(STATUS "warning: *NOT* handled - .app directory case...")
endif()
else()
message(STATUS "warning: *NOT* handled - directory but not .app case...")
endif()
else()
# Is it an executable file?
is_file_executable("${app}" is_executable)
if(is_executable)
get_dotapp_dir("${app}" dotapp_dir)
if(EXISTS "${dotapp_dir}")
set(${bundle_var} "${dotapp_dir}" PARENT_SCOPE)
set(${executable_var} "${app}" PARENT_SCOPE)
set(valid 1)
#message(STATUS "info: handled executable file in .app dir case...")
else()
get_filename_component(app_dir "${app}" PATH)
set(${bundle_var} "${app_dir}" PARENT_SCOPE)
set(${executable_var} "${app}" PARENT_SCOPE)
set(valid 1)
#message(STATUS "info: handled executable file in any dir case...")
endif()
else()
message(STATUS "warning: *NOT* handled - not .app dir, not executable file...")
endif()
endif()
else()
message(STATUS "warning: *NOT* handled - directory/file does not exist...")
endif()
if(NOT valid)
set(${bundle_var} "error: not a bundle" PARENT_SCOPE)
set(${executable_var} "error: not a bundle" PARENT_SCOPE)
endif()
set(${valid_var} ${valid} PARENT_SCOPE)
endfunction()
function(get_bundle_all_executables bundle exes_var)
set(exes "")
file(GLOB_RECURSE file_list "${bundle}/*")
foreach(f ${file_list})
is_file_executable("${f}" is_executable)
if(is_executable)
set(exes ${exes} "${f}")
endif()
endforeach()
set(${exes_var} "${exes}" PARENT_SCOPE)
endfunction()
function(get_item_key item key_var)
get_filename_component(item_name "${item}" NAME)
if(WIN32)
string(TOLOWER "${item_name}" item_name)
endif()
string(REGEX REPLACE "\\." "_" ${key_var} "${item_name}")
set(${key_var} ${${key_var}} PARENT_SCOPE)
endfunction()
function(clear_bundle_keys keys_var)
foreach(key ${${keys_var}})
set(${key}_ITEM PARENT_SCOPE)
set(${key}_RESOLVED_ITEM PARENT_SCOPE)
set(${key}_DEFAULT_EMBEDDED_PATH PARENT_SCOPE)
set(${key}_EMBEDDED_ITEM PARENT_SCOPE)
set(${key}_RESOLVED_EMBEDDED_ITEM PARENT_SCOPE)
set(${key}_COPYFLAG PARENT_SCOPE)
endforeach()
set(${keys_var} PARENT_SCOPE)
endfunction()
function(set_bundle_key_values keys_var context item exepath dirs copyflag)
get_filename_component(item_name "${item}" NAME)
get_item_key("${item}" key)
list(LENGTH ${keys_var} length_before)
gp_append_unique(${keys_var} "${key}")
list(LENGTH ${keys_var} length_after)
if(NOT length_before EQUAL length_after)
gp_resolve_item("${context}" "${item}" "${exepath}" "${dirs}" resolved_item)
gp_item_default_embedded_path("${item}" default_embedded_path)
if(item MATCHES "[^/]+\\.framework/")
# For frameworks, construct the name under the embedded path from the
# opening "${item_name}.framework/" to the closing "/${item_name}":
#
string(REGEX REPLACE "^.*(${item_name}.framework/.*/?${item_name}).*$" "${default_embedded_path}/\\1" embedded_item "${item}")
else()
# For other items, just use the same name as the original, but in the
# embedded path:
#
set(embedded_item "${default_embedded_path}/${item_name}")
endif()
# Replace @executable_path and resolve ".." references:
#
string(REPLACE "@executable_path" "${exepath}" resolved_embedded_item "${embedded_item}")
get_filename_component(resolved_embedded_item "${resolved_embedded_item}" ABSOLUTE)
# *But* -- if we are not copying, then force resolved_embedded_item to be
# the same as resolved_item. In the case of multiple executables in the
# original bundle, using the default_embedded_path results in looking for
# the resolved executable next to the main bundle executable. This is here
# so that exes in the other sibling directories (like "bin") get fixed up
# properly...
#
if(NOT copyflag)
set(resolved_embedded_item "${resolved_item}")
endif()
set(${keys_var} ${${keys_var}} PARENT_SCOPE)
set(${key}_ITEM "${item}" PARENT_SCOPE)
set(${key}_RESOLVED_ITEM "${resolved_item}" PARENT_SCOPE)
set(${key}_DEFAULT_EMBEDDED_PATH "${default_embedded_path}" PARENT_SCOPE)
set(${key}_EMBEDDED_ITEM "${embedded_item}" PARENT_SCOPE)
set(${key}_RESOLVED_EMBEDDED_ITEM "${resolved_embedded_item}" PARENT_SCOPE)
set(${key}_COPYFLAG "${copyflag}" PARENT_SCOPE)
else()
#message("warning: item key '${key}' already in the list, subsequent references assumed identical to first")
endif()
endfunction()
function(get_bundle_keys app libs dirs keys_var)
set(${keys_var} PARENT_SCOPE)
get_bundle_and_executable("${app}" bundle executable valid)
if(valid)
# Always use the exepath of the main bundle executable for @executable_path
# replacements:
#
get_filename_component(exepath "${executable}" PATH)
# But do fixups on all executables in the bundle:
#
get_bundle_all_executables("${bundle}" exes)
# For each extra lib, accumulate a key as well and then also accumulate
# any of its prerequisites. (Extra libs are typically dynamically loaded
# plugins: libraries that are prerequisites for full runtime functionality
# but that do not show up in otool -L output...)
#
foreach(lib ${libs})
set_bundle_key_values(${keys_var} "${lib}" "${lib}" "${exepath}" "${dirs}" 0)
set(prereqs "")
get_prerequisites("${lib}" prereqs 1 1 "${exepath}" "${dirs}")
foreach(pr ${prereqs})
set_bundle_key_values(${keys_var} "${lib}" "${pr}" "${exepath}" "${dirs}" 1)
endforeach()
endforeach()
# For each executable found in the bundle, accumulate keys as we go.
# The list of keys should be complete when all prerequisites of all
# binaries in the bundle have been analyzed.
#
foreach(exe ${exes})
# Add the exe itself to the keys:
#
set_bundle_key_values(${keys_var} "${exe}" "${exe}" "${exepath}" "${dirs}" 0)
# Add each prerequisite to the keys:
#
set(prereqs "")
get_prerequisites("${exe}" prereqs 1 1 "${exepath}" "${dirs}")
foreach(pr ${prereqs})
set_bundle_key_values(${keys_var} "${exe}" "${pr}" "${exepath}" "${dirs}" 1)
endforeach()
endforeach()
# Propagate values to caller's scope:
#
set(${keys_var} ${${keys_var}} PARENT_SCOPE)
foreach(key ${${keys_var}})
set(${key}_ITEM "${${key}_ITEM}" PARENT_SCOPE)
set(${key}_RESOLVED_ITEM "${${key}_RESOLVED_ITEM}" PARENT_SCOPE)
set(${key}_DEFAULT_EMBEDDED_PATH "${${key}_DEFAULT_EMBEDDED_PATH}" PARENT_SCOPE)
set(${key}_EMBEDDED_ITEM "${${key}_EMBEDDED_ITEM}" PARENT_SCOPE)
set(${key}_RESOLVED_EMBEDDED_ITEM "${${key}_RESOLVED_EMBEDDED_ITEM}" PARENT_SCOPE)
set(${key}_COPYFLAG "${${key}_COPYFLAG}" PARENT_SCOPE)
endforeach()
endif()
endfunction()
function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item)
if(WIN32)
# ignore case on Windows
string(TOLOWER "${resolved_item}" resolved_item_compare)
string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare)
else()
set(resolved_item_compare "${resolved_item}")
set(resolved_embedded_item_compare "${resolved_embedded_item}")
endif()
if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}")
message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...")
else()
#message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}")
if(UNIX AND NOT APPLE)
file(RPATH_REMOVE FILE "${resolved_embedded_item}")
endif()
endif()
endfunction()
function(copy_resolved_framework_into_bundle resolved_item resolved_embedded_item)
if(WIN32)
# ignore case on Windows
string(TOLOWER "${resolved_item}" resolved_item_compare)
string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare)
else()
set(resolved_item_compare "${resolved_item}")
set(resolved_embedded_item_compare "${resolved_embedded_item}")
endif()
if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}")
message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...")
else()
if(BU_COPY_FULL_FRAMEWORK_CONTENTS)
# Full Framework (everything):
get_filename_component(resolved_dir "${resolved_item}" PATH)
get_filename_component(resolved_dir "${resolved_dir}/../.." ABSOLUTE)
get_filename_component(resolved_embedded_dir "${resolved_embedded_item}" PATH)
get_filename_component(resolved_embedded_dir "${resolved_embedded_dir}/../.." ABSOLUTE)
#message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_dir}' '${resolved_embedded_dir}'")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${resolved_dir}" "${resolved_embedded_dir}")
else()
# Framework lib itself:
#message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}")
# Plus Resources, if they exist:
string(REGEX REPLACE "^(.*)/[^/]+/[^/]+/[^/]+$" "\\1/Resources" resolved_resources "${resolved_item}")
string(REGEX REPLACE "^(.*)/[^/]+/[^/]+/[^/]+$" "\\1/Resources" resolved_embedded_resources "${resolved_embedded_item}")
if(EXISTS "${resolved_resources}")
#message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_resources}' '${resolved_embedded_resources}'")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${resolved_resources}" "${resolved_embedded_resources}")
endif()
endif()
if(UNIX AND NOT APPLE)
file(RPATH_REMOVE FILE "${resolved_embedded_item}")
endif()
endif()
endfunction()
function(fixup_bundle_item resolved_embedded_item exepath dirs)
# This item's key is "ikey":
#
get_item_key("${resolved_embedded_item}" ikey)
# Ensure the item is "inside the .app bundle" -- it should not be fixed up if
# it is not in the .app bundle... Otherwise, we'll modify files in the build
# tree, or in other varied locations around the file system, with our call to
# install_name_tool. Make sure that doesn't happen here:
#
get_dotapp_dir("${exepath}" exe_dotapp_dir)
string(LENGTH "${exe_dotapp_dir}/" exe_dotapp_dir_length)
string(LENGTH "${resolved_embedded_item}" resolved_embedded_item_length)
set(path_too_short 0)
set(is_embedded 0)
if(${resolved_embedded_item_length} LESS ${exe_dotapp_dir_length})
set(path_too_short 1)
endif()
if(NOT path_too_short)
string(SUBSTRING "${resolved_embedded_item}" 0 ${exe_dotapp_dir_length} item_substring)
if("${exe_dotapp_dir}/" STREQUAL "${item_substring}")
set(is_embedded 1)
endif()
endif()
if(NOT is_embedded)
message(" exe_dotapp_dir/='${exe_dotapp_dir}/'")
message(" item_substring='${item_substring}'")
message(" resolved_embedded_item='${resolved_embedded_item}'")
message("")
message("Install or copy the item into the bundle before calling fixup_bundle.")
message("Or maybe there's a typo or incorrect path in one of the args to fixup_bundle?")
message("")
message(FATAL_ERROR "cannot fixup an item that is not in the bundle...")
endif()
set(prereqs "")
get_prerequisites("${resolved_embedded_item}" prereqs 1 0 "${exepath}" "${dirs}")
set(changes "")
foreach(pr ${prereqs})
# Each referenced item's key is "rkey" in the loop:
#
get_item_key("${pr}" rkey)
if(NOT "${${rkey}_EMBEDDED_ITEM}" STREQUAL "")
set(changes ${changes} "-change" "${pr}" "${${rkey}_EMBEDDED_ITEM}")
else()
message("warning: unexpected reference to '${pr}'")
endif()
endforeach()
if(BU_CHMOD_BUNDLE_ITEMS)
execute_process(COMMAND chmod u+w "${resolved_embedded_item}")
endif()
# Change this item's id and all of its references in one call
# to install_name_tool:
#
execute_process(COMMAND install_name_tool
${changes} -id "${${ikey}_EMBEDDED_ITEM}" "${resolved_embedded_item}"
)
endfunction()
function(fixup_bundle app libs dirs)
message(STATUS "fixup_bundle")
message(STATUS " app='${app}'")
message(STATUS " libs='${libs}'")
message(STATUS " dirs='${dirs}'")
get_bundle_and_executable("${app}" bundle executable valid)
if(valid)
get_filename_component(exepath "${executable}" PATH)
message(STATUS "fixup_bundle: preparing...")
get_bundle_keys("${app}" "${libs}" "${dirs}" keys)
message(STATUS "fixup_bundle: copying...")
list(LENGTH keys n)
math(EXPR n ${n}*2)
set(i 0)
foreach(key ${keys})
math(EXPR i ${i}+1)
if(${${key}_COPYFLAG})
message(STATUS "${i}/${n}: copying '${${key}_RESOLVED_ITEM}'")
else()
message(STATUS "${i}/${n}: *NOT* copying '${${key}_RESOLVED_ITEM}'")
endif()
set(show_status 0)
if(show_status)
message(STATUS "key='${key}'")
message(STATUS "item='${${key}_ITEM}'")
message(STATUS "resolved_item='${${key}_RESOLVED_ITEM}'")
message(STATUS "default_embedded_path='${${key}_DEFAULT_EMBEDDED_PATH}'")
message(STATUS "embedded_item='${${key}_EMBEDDED_ITEM}'")
message(STATUS "resolved_embedded_item='${${key}_RESOLVED_EMBEDDED_ITEM}'")
message(STATUS "copyflag='${${key}_COPYFLAG}'")
message(STATUS "")
endif()
if(${${key}_COPYFLAG})
set(item "${${key}_ITEM}")
if(item MATCHES "[^/]+\\.framework/")
copy_resolved_framework_into_bundle("${${key}_RESOLVED_ITEM}"
"${${key}_RESOLVED_EMBEDDED_ITEM}")
else()
copy_resolved_item_into_bundle("${${key}_RESOLVED_ITEM}"
"${${key}_RESOLVED_EMBEDDED_ITEM}")
endif()
endif()
endforeach()
message(STATUS "fixup_bundle: fixing...")
foreach(key ${keys})
math(EXPR i ${i}+1)
if(APPLE)
message(STATUS "${i}/${n}: fixing up '${${key}_RESOLVED_EMBEDDED_ITEM}'")
fixup_bundle_item("${${key}_RESOLVED_EMBEDDED_ITEM}" "${exepath}" "${dirs}")
else()
message(STATUS "${i}/${n}: fix-up not required on this platform '${${key}_RESOLVED_EMBEDDED_ITEM}'")
endif()
endforeach()
message(STATUS "fixup_bundle: cleaning up...")
clear_bundle_keys(keys)
message(STATUS "fixup_bundle: verifying...")
verify_app("${app}")
else()
message(SEND_ERROR "error: fixup_bundle: not a valid bundle")
endif()
message(STATUS "fixup_bundle: done")
endfunction()
function(copy_and_fixup_bundle src dst libs dirs)
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${src}" "${dst}")
fixup_bundle("${dst}" "${libs}" "${dirs}")
endfunction()
function(verify_bundle_prerequisites bundle result_var info_var)
set(result 1)
set(info "")
set(count 0)
get_bundle_main_executable("${bundle}" main_bundle_exe)
file(GLOB_RECURSE file_list "${bundle}/*")
foreach(f ${file_list})
is_file_executable("${f}" is_executable)
if(is_executable)
get_filename_component(exepath "${f}" PATH)
math(EXPR count "${count} + 1")
message(STATUS "executable file ${count}: ${f}")
set(prereqs "")
get_prerequisites("${f}" prereqs 1 1 "${exepath}" "")
# On the Mac,
# "embedded" and "system" prerequisites are fine... anything else means
# the bundle's prerequisites are not verified (i.e., the bundle is not
# really "standalone")
#
# On Windows (and others? Linux/Unix/...?)
# "local" and "system" prereqs are fine...
#
set(external_prereqs "")
foreach(p ${prereqs})
set(p_type "")
gp_file_type("${f}" "${p}" p_type)
if(APPLE)
if(NOT "${p_type}" STREQUAL "embedded" AND NOT "${p_type}" STREQUAL "system")
set(external_prereqs ${external_prereqs} "${p}")
endif()
else()
if(NOT "${p_type}" STREQUAL "local" AND NOT "${p_type}" STREQUAL "system")
set(external_prereqs ${external_prereqs} "${p}")
endif()
endif()
endforeach()
if(external_prereqs)
# Found non-system/somehow-unacceptable prerequisites:
set(result 0)
set(info ${info} "external prerequisites found:\nf='${f}'\nexternal_prereqs='${external_prereqs}'\n")
endif()
endif()
endforeach()
if(result)
set(info "Verified ${count} executable files in '${bundle}'")
endif()
set(${result_var} "${result}" PARENT_SCOPE)
set(${info_var} "${info}" PARENT_SCOPE)
endfunction()
function(verify_bundle_symlinks bundle result_var info_var)
set(result 1)
set(info "")
set(count 0)
# TODO: implement this function for real...
# Right now, it is just a stub that verifies unconditionally...
set(${result_var} "${result}" PARENT_SCOPE)
set(${info_var} "${info}" PARENT_SCOPE)
endfunction()
function(verify_app app)
set(verified 0)
set(info "")
get_bundle_and_executable("${app}" bundle executable valid)
message(STATUS "===========================================================================")
message(STATUS "Analyzing app='${app}'")
message(STATUS "bundle='${bundle}'")
message(STATUS "executable='${executable}'")
message(STATUS "valid='${valid}'")
# Verify that the bundle does not have any "external" prerequisites:
#
verify_bundle_prerequisites("${bundle}" verified info)
message(STATUS "verified='${verified}'")
message(STATUS "info='${info}'")
message(STATUS "")
if(verified)
# Verify that the bundle does not have any symlinks to external files:
#
verify_bundle_symlinks("${bundle}" verified info)
message(STATUS "verified='${verified}'")
message(STATUS "info='${info}'")
message(STATUS "")
endif()
if(NOT verified)
message(FATAL_ERROR "error: verify_app failed")
endif()
endfunction()

13
cmake/Coverage.cmake Normal file
View File

@@ -0,0 +1,13 @@
if(__COVERAGE_CMAKE__)
return()
endif()
set(__COVERAGE_CMAKE__ TRUE)
if(MultiMC_CODE_COVERAGE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 --coverage")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 --coverage")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 --coverage")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0 --coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --coverage")
set(CMAKE_BUILD_TYPE "Debug")
endif(MultiMC_CODE_COVERAGE)

35
cmake/Coverity.cmake Normal file
View File

@@ -0,0 +1,35 @@
if(__COVERITY_CMAKE__)
return()
endif()
set(__COVERITY_CMAKE__ TRUE)
include(GitFunctions)
git_run(COMMAND config --get user.email DEFAULT "" OUTPUT_VAR GIT_EMAIL)
git_run(COMMAND describe DEFAULT "" OUTPUT_VAR GIT_VERSION)
set(MultiMC_COVERITY_TOKEN "" CACHE STRING "Coverity access token")
set(MultiMC_COVERITY_EMAIL "${GIT_EMAIL}" CACHE STRING "Coverity email")
set(MultiMC_COVERITY_TOOLS_DIR "${CMAKE_BINARY_DIR}/coverity_tools" CACHE PATH "Path to the coverity tools")
find_program(CURL_EXECUTABLE NAMES curl PATHS /usr/bin)
if(NOT CURL_EXECUTABLE STREQUAL "" AND NOT MultiMC_COVERITY_TOKEN STREQUAL "" AND NOT MultiMC_COVERITY_EMAIL STREQUAL "")
add_custom_target(coverity_configure
COMMAND ${MultiMC_COVERITY_TOOLS_DIR}/bin/cov-configure --comptype gcc --compiler ${CMAKE_C_COMPILER}
)
add_custom_target(coverity_create_tarball
COMMAND ${CMAKE_COMMAND} -E echo "Cleaning..." && ${CMAKE_MAKE_PROGRAM} clean
COMMAND ${CMAKE_COMMAND} -E echo "Building..." && ${MultiMC_COVERITY_TOOLS_DIR}/bin/cov-build --dir cov-int ${CMAKE_MAKE_PROGRAM} -j3
COMMAND ${CMAKE_COMMAND} -E echo "Creating tarball..." && ${CMAKE_COMMAND} -E tar cfz multimc_coverity.tgz cov-int/
COMMENT "Creating coverity build..."
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(coverity_upload
COMMAND ${CURL_EXECUTABLE} --form project=02JanDal/MultiMC5 --form token=${MultiMC_COVERITY_TOKEN} --form email=${MultiMC_COVERITY_EMAIL} --form file=@multimc_coverity.tgz --form version=${MultiMC_GIT_COMMIT} --form description=${GIT_VERSION} http://scan5.coverity.com/cgi-bin/upload.py
DEPENDS coverity_create_tarball
COMMENT "Uploading to coverity..."
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
endif()

View File

@@ -0,0 +1,895 @@
# - Functions to analyze and list executable file prerequisites.
# This module provides functions to list the .dll, .dylib or .so
# files that an executable or shared library file depends on. (Its
# prerequisites.)
#
# It uses various tools to obtain the list of required shared library files:
# dumpbin (Windows)
# objdump (MinGW on Windows)
# ldd (Linux/Unix)
# otool (Mac OSX)
# The following functions are provided by this module:
# get_prerequisites
# list_prerequisites
# list_prerequisites_by_glob
# gp_append_unique
# is_file_executable
# gp_item_default_embedded_path
# (projects can override with gp_item_default_embedded_path_override)
# gp_resolve_item
# (projects can override with gp_resolve_item_override)
# gp_resolved_file_type
# (projects can override with gp_resolved_file_type_override)
# gp_file_type
# Requires CMake 2.6 or greater because it uses function, break, return and
# PARENT_SCOPE.
#
# GET_PREREQUISITES(<target> <prerequisites_var> <exclude_system> <recurse>
# <exepath> <dirs>)
# Get the list of shared library files required by <target>. The list in
# the variable named <prerequisites_var> should be empty on first entry to
# this function. On exit, <prerequisites_var> will contain the list of
# required shared library files.
#
# <target> is the full path to an executable file. <prerequisites_var> is the
# name of a CMake variable to contain the results. <exclude_system> must be 0
# or 1 indicating whether to include or exclude "system" prerequisites. If
# <recurse> is set to 1 all prerequisites will be found recursively, if set to
# 0 only direct prerequisites are listed. <exepath> is the path to the top
# level executable used for @executable_path replacment on the Mac. <dirs> is
# a list of paths where libraries might be found: these paths are searched
# first when a target without any path info is given. Then standard system
# locations are also searched: PATH, Framework locations, /usr/lib...
#
# LIST_PREREQUISITES(<target> [<recurse> [<exclude_system> [<verbose>]]])
# Print a message listing the prerequisites of <target>.
#
# <target> is the name of a shared library or executable target or the full
# path to a shared library or executable file. If <recurse> is set to 1 all
# prerequisites will be found recursively, if set to 0 only direct
# prerequisites are listed. <exclude_system> must be 0 or 1 indicating whether
# to include or exclude "system" prerequisites. With <verbose> set to 0 only
# the full path names of the prerequisites are printed, set to 1 extra
# informatin will be displayed.
#
# LIST_PREREQUISITES_BY_GLOB(<glob_arg> <glob_exp>)
# Print the prerequisites of shared library and executable files matching a
# globbing pattern. <glob_arg> is GLOB or GLOB_RECURSE and <glob_exp> is a
# globbing expression used with "file(GLOB" or "file(GLOB_RECURSE" to retrieve
# a list of matching files. If a matching file is executable, its prerequisites
# are listed.
#
# Any additional (optional) arguments provided are passed along as the
# optional arguments to the list_prerequisites calls.
#
# GP_APPEND_UNIQUE(<list_var> <value>)
# Append <value> to the list variable <list_var> only if the value is not
# already in the list.
#
# IS_FILE_EXECUTABLE(<file> <result_var>)
# Return 1 in <result_var> if <file> is a binary executable, 0 otherwise.
#
# GP_ITEM_DEFAULT_EMBEDDED_PATH(<item> <default_embedded_path_var>)
# Return the path that others should refer to the item by when the item
# is embedded inside a bundle.
#
# Override on a per-project basis by providing a project-specific
# gp_item_default_embedded_path_override function.
#
# GP_RESOLVE_ITEM(<context> <item> <exepath> <dirs> <resolved_item_var>)
# Resolve an item into an existing full path file.
#
# Override on a per-project basis by providing a project-specific
# gp_resolve_item_override function.
#
# GP_RESOLVED_FILE_TYPE(<original_file> <file> <exepath> <dirs> <type_var>)
# Return the type of <file> with respect to <original_file>. String
# describing type of prerequisite is returned in variable named <type_var>.
#
# Use <exepath> and <dirs> if necessary to resolve non-absolute <file>
# values -- but only for non-embedded items.
#
# Possible types are:
# system
# local
# embedded
# other
# Override on a per-project basis by providing a project-specific
# gp_resolved_file_type_override function.
#
# GP_FILE_TYPE(<original_file> <file> <type_var>)
# Return the type of <file> with respect to <original_file>. String
# describing type of prerequisite is returned in variable named <type_var>.
#
# Possible types are:
# system
# local
# embedded
# other
#=============================================================================
# Copyright 2008-2009 Kitware, Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
function(gp_append_unique list_var value)
set(contains 0)
foreach(item ${${list_var}})
if("${item}" STREQUAL "${value}")
set(contains 1)
break()
endif()
endforeach()
if(NOT contains)
set(${list_var} ${${list_var}} "${value}" PARENT_SCOPE)
endif()
endfunction()
function(is_file_executable file result_var)
#
# A file is not executable until proven otherwise:
#
set(${result_var} 0 PARENT_SCOPE)
get_filename_component(file_full "${file}" ABSOLUTE)
string(TOLOWER "${file_full}" file_full_lower)
# If file name ends in .exe on Windows, *assume* executable:
#
if(WIN32 AND NOT UNIX)
if("${file_full_lower}" MATCHES "\\.exe$")
set(${result_var} 1 PARENT_SCOPE)
return()
endif()
# A clause could be added here that uses output or return value of dumpbin
# to determine ${result_var}. In 99%+? practical cases, the exe name
# match will be sufficient...
#
endif()
# Use the information returned from the Unix shell command "file" to
# determine if ${file_full} should be considered an executable file...
#
# If the file command's output contains "executable" and does *not* contain
# "text" then it is likely an executable suitable for prerequisite analysis
# via the get_prerequisites macro.
#
if(UNIX)
if(NOT file_cmd)
find_program(file_cmd "file")
mark_as_advanced(file_cmd)
endif()
if(file_cmd)
execute_process(COMMAND "${file_cmd}" "${file_full}"
OUTPUT_VARIABLE file_ov
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Replace the name of the file in the output with a placeholder token
# (the string " _file_full_ ") so that just in case the path name of
# the file contains the word "text" or "executable" we are not fooled
# into thinking "the wrong thing" because the file name matches the
# other 'file' command output we are looking for...
#
string(REPLACE "${file_full}" " _file_full_ " file_ov "${file_ov}")
string(TOLOWER "${file_ov}" file_ov)
#message(STATUS "file_ov='${file_ov}'")
if("${file_ov}" MATCHES "executable")
#message(STATUS "executable!")
if("${file_ov}" MATCHES "text")
#message(STATUS "but text, so *not* a binary executable!")
else()
set(${result_var} 1 PARENT_SCOPE)
return()
endif()
endif()
# Also detect position independent executables on Linux,
# where "file" gives "shared object ... (uses shared libraries)"
if("${file_ov}" MATCHES "shared object.*\(uses shared libs\)")
set(${result_var} 1 PARENT_SCOPE)
return()
endif()
else()
message(STATUS "warning: No 'file' command, skipping execute_process...")
endif()
endif()
endfunction()
function(gp_item_default_embedded_path item default_embedded_path_var)
# On Windows and Linux, "embed" prerequisites in the same directory
# as the executable by default:
#
set(path "@executable_path")
set(overridden 0)
# On the Mac, relative to the executable depending on the type
# of the thing we are embedding:
#
if(APPLE)
#
# The assumption here is that all executables in the bundle will be
# in same-level-directories inside the bundle. The parent directory
# of an executable inside the bundle should be MacOS or a sibling of
# MacOS and all embedded paths returned from here will begin with
# "@executable_path/../" and will work from all executables in all
# such same-level-directories inside the bundle.
#
# By default, embed things right next to the main bundle executable:
#
set(path "@executable_path/../../Contents/MacOS")
# Embed .dylibs right next to the main bundle executable:
#
if(item MATCHES "\\.dylib$")
set(path "@executable_path/../MacOS")
set(overridden 1)
endif()
# Embed frameworks in the embedded "Frameworks" directory (sibling of MacOS):
#
if(NOT overridden)
if(item MATCHES "[^/]+\\.framework/")
set(path "@executable_path/../Frameworks")
set(overridden 1)
endif()
endif()
endif()
# Provide a hook so that projects can override the default embedded location
# of any given library by whatever logic they choose:
#
if(COMMAND gp_item_default_embedded_path_override)
gp_item_default_embedded_path_override("${item}" path)
endif()
set(${default_embedded_path_var} "${path}" PARENT_SCOPE)
endfunction()
function(gp_resolve_item context item exepath dirs resolved_item_var)
set(resolved 0)
set(resolved_item "${item}")
# Is it already resolved?
#
if(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}")
set(resolved 1)
endif()
if(NOT resolved)
if(item MATCHES "@executable_path")
#
# @executable_path references are assumed relative to exepath
#
string(REPLACE "@executable_path" "${exepath}" ri "${item}")
get_filename_component(ri "${ri}" ABSOLUTE)
if(EXISTS "${ri}")
#message(STATUS "info: embedded item exists (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
else()
message(STATUS "warning: embedded item does not exist '${ri}'")
endif()
endif()
endif()
if(NOT resolved)
if(item MATCHES "@loader_path")
#
# @loader_path references are assumed relative to the
# PATH of the given "context" (presumably another library)
#
get_filename_component(contextpath "${context}" PATH)
string(REPLACE "@loader_path" "${contextpath}" ri "${item}")
get_filename_component(ri "${ri}" ABSOLUTE)
if(EXISTS "${ri}")
#message(STATUS "info: embedded item exists (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
else()
message(STATUS "warning: embedded item does not exist '${ri}'")
endif()
endif()
endif()
if(NOT resolved)
if(item MATCHES "@rpath")
#
# @rpath references are relative to the paths built into the binaries with -rpath
# We handle this case like we do for other Unixes
#
string(REPLACE "@rpath/" "" norpath_item "${item}")
set(ri "ri-NOTFOUND")
find_file(ri "${norpath_item}" ${exepath} ${dirs} NO_DEFAULT_PATH)
if(ri)
#message(STATUS "info: 'find_file' in exepath/dirs (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
set(ri "ri-NOTFOUND")
endif()
endif()
endif()
if(NOT resolved)
set(ri "ri-NOTFOUND")
find_file(ri "${item}" ${exepath} ${dirs} NO_DEFAULT_PATH)
find_file(ri "${item}" ${exepath} ${dirs} /usr/lib)
if(ri)
#message(STATUS "info: 'find_file' in exepath/dirs (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
set(ri "ri-NOTFOUND")
endif()
endif()
if(NOT resolved)
if(item MATCHES "[^/]+\\.framework/")
set(fw "fw-NOTFOUND")
find_file(fw "${item}"
"~/Library/Frameworks"
"/Library/Frameworks"
"/System/Library/Frameworks"
)
if(fw)
#message(STATUS "info: 'find_file' found framework (${fw})")
set(resolved 1)
set(resolved_item "${fw}")
set(fw "fw-NOTFOUND")
endif()
endif()
endif()
# Using find_program on Windows will find dll files that are in the PATH.
# (Converting simple file names into full path names if found.)
#
if(WIN32 AND NOT UNIX)
if(NOT resolved)
set(ri "ri-NOTFOUND")
find_program(ri "${item}" PATHS "${exepath};${dirs}" NO_DEFAULT_PATH)
find_program(ri "${item}" PATHS "${exepath};${dirs}")
if(ri)
#message(STATUS "info: 'find_program' in exepath/dirs (${ri})")
set(resolved 1)
set(resolved_item "${ri}")
set(ri "ri-NOTFOUND")
endif()
endif()
endif()
# Provide a hook so that projects can override item resolution
# by whatever logic they choose:
#
if(COMMAND gp_resolve_item_override)
gp_resolve_item_override("${context}" "${item}" "${exepath}" "${dirs}" resolved_item resolved)
endif()
if(NOT resolved)
message(STATUS "
warning: cannot resolve item '${item}'
possible problems:
need more directories?
need to use InstallRequiredSystemLibraries?
run in install tree instead of build tree?
")
# message(STATUS "
#******************************************************************************
#warning: cannot resolve item '${item}'
#
# possible problems:
# need more directories?
# need to use InstallRequiredSystemLibraries?
# run in install tree instead of build tree?
#
# context='${context}'
# item='${item}'
# exepath='${exepath}'
# dirs='${dirs}'
# resolved_item_var='${resolved_item_var}'
#******************************************************************************
#")
endif()
set(${resolved_item_var} "${resolved_item}" PARENT_SCOPE)
endfunction()
function(gp_resolved_file_type original_file file exepath dirs type_var)
#message(STATUS "**")
if(NOT IS_ABSOLUTE "${original_file}")
message(STATUS "warning: gp_resolved_file_type expects absolute full path for first arg original_file")
endif()
set(is_embedded 0)
set(is_local 0)
set(is_system 0)
set(resolved_file "${file}")
if("${file}" MATCHES "^@(executable|loader)_path")
set(is_embedded 1)
endif()
if(NOT is_embedded)
if(NOT IS_ABSOLUTE "${file}")
gp_resolve_item("${original_file}" "${file}" "${exepath}" "${dirs}" resolved_file)
endif()
string(TOLOWER "${original_file}" original_lower)
string(TOLOWER "${resolved_file}" lower)
if(UNIX)
if(resolved_file MATCHES "^(/lib/|/lib32/|/lib64/|/usr/lib/|/usr/lib32/|/usr/lib64/|/usr/X11R6/|/usr/bin/)")
set(is_system 1)
endif()
endif()
if(APPLE)
if(resolved_file MATCHES "^(/System/Library/|/usr/lib/)")
set(is_system 1)
endif()
endif()
if(WIN32)
string(TOLOWER "$ENV{SystemRoot}" sysroot)
string(REGEX REPLACE "\\\\" "/" sysroot "${sysroot}")
string(TOLOWER "$ENV{windir}" windir)
string(REGEX REPLACE "\\\\" "/" windir "${windir}")
if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)")
set(is_system 1)
endif()
if(UNIX)
# if cygwin, we can get the properly formed windows paths from cygpath
find_program(CYGPATH_EXECUTABLE cygpath)
if(CYGPATH_EXECUTABLE)
execute_process(COMMAND ${CYGPATH_EXECUTABLE} -W
OUTPUT_VARIABLE env_windir
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${CYGPATH_EXECUTABLE} -S
OUTPUT_VARIABLE env_sysdir
OUTPUT_STRIP_TRAILING_WHITESPACE)
string(TOLOWER "${env_windir}" windir)
string(TOLOWER "${env_sysdir}" sysroot)
if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)")
set(is_system 1)
endif()
endif()
endif()
endif()
if(NOT is_system)
get_filename_component(original_path "${original_lower}" PATH)
get_filename_component(path "${lower}" PATH)
if("${original_path}" STREQUAL "${path}")
set(is_local 1)
else()
string(LENGTH "${original_path}/" original_length)
string(LENGTH "${lower}" path_length)
if(${path_length} GREATER ${original_length})
string(SUBSTRING "${lower}" 0 ${original_length} path)
if("${original_path}/" STREQUAL "${path}")
set(is_embedded 1)
endif()
endif()
endif()
endif()
endif()
# Return type string based on computed booleans:
#
set(type "other")
if(is_system)
set(type "system")
elseif(is_embedded)
set(type "embedded")
elseif(is_local)
set(type "local")
endif()
#message(STATUS "gp_resolved_file_type: '${file}' '${resolved_file}'")
#message(STATUS " type: '${type}'")
if(NOT is_embedded)
if(NOT IS_ABSOLUTE "${resolved_file}")
if(lower MATCHES "^msvc[^/]+dll" AND is_system)
message(STATUS "info: non-absolute msvc file '${file}' returning type '${type}'")
else()
message(STATUS "warning: gp_resolved_file_type non-absolute file '${file}' returning type '${type}' -- possibly incorrect")
endif()
endif()
endif()
# Provide a hook so that projects can override the decision on whether a
# library belongs to the system or not by whatever logic they choose:
#
if(COMMAND gp_resolved_file_type_override)
gp_resolved_file_type_override("${resolved_file}" type)
endif()
set(${type_var} "${type}" PARENT_SCOPE)
#message(STATUS "**")
endfunction()
function(gp_file_type original_file file type_var)
if(NOT IS_ABSOLUTE "${original_file}")
message(STATUS "warning: gp_file_type expects absolute full path for first arg original_file")
endif()
get_filename_component(exepath "${original_file}" PATH)
set(type "")
gp_resolved_file_type("${original_file}" "${file}" "${exepath}" "" type)
set(${type_var} "${type}" PARENT_SCOPE)
endfunction()
function(get_prerequisites target prerequisites_var exclude_system recurse exepath dirs)
set(verbose 0)
set(eol_char "E")
if(NOT IS_ABSOLUTE "${target}")
message("warning: target '${target}' is not absolute...")
endif()
if(NOT EXISTS "${target}")
message("warning: target '${target}' does not exist...")
endif()
set(gp_cmd_paths ${gp_cmd_paths}
"C:/Program Files/Microsoft Visual Studio 9.0/VC/bin"
"C:/Program Files (x86)/Microsoft Visual Studio 9.0/VC/bin"
"C:/Program Files/Microsoft Visual Studio 8/VC/BIN"
"C:/Program Files (x86)/Microsoft Visual Studio 8/VC/BIN"
"C:/Program Files/Microsoft Visual Studio .NET 2003/VC7/BIN"
"C:/Program Files (x86)/Microsoft Visual Studio .NET 2003/VC7/BIN"
"/usr/local/bin"
"/usr/bin"
)
# <setup-gp_tool-vars>
#
# Try to choose the right tool by default. Caller can set gp_tool prior to
# calling this function to force using a different tool.
#
if("${gp_tool}" STREQUAL "")
set(gp_tool "ldd")
if(APPLE)
set(gp_tool "otool")
endif()
if(WIN32 AND NOT UNIX) # This is how to check for cygwin, har!
find_program(gp_dumpbin "dumpbin" PATHS ${gp_cmd_paths})
if(gp_dumpbin)
set(gp_tool "dumpbin")
else() # Try harder. Maybe we're on MinGW
set(gp_tool "objdump")
endif()
endif()
endif()
find_program(gp_cmd ${gp_tool} PATHS ${gp_cmd_paths})
if(NOT gp_cmd)
message(FATAL_ERROR "FATAL ERROR: could not find '${gp_tool}' - cannot analyze prerequisites!")
return()
endif()
set(gp_tool_known 0)
if("${gp_tool}" STREQUAL "ldd")
set(gp_cmd_args "")
set(gp_regex "^[\t ]*[^\t ]+ => ([^\t\(]+) .*${eol_char}$")
set(gp_regex_error "not found${eol_char}$")
set(gp_regex_fallback "^[\t ]*([^\t ]+) => ([^\t ]+).*${eol_char}$")
set(gp_regex_cmp_count 1)
set(gp_tool_known 1)
endif()
if("${gp_tool}" STREQUAL "otool")
set(gp_cmd_args "-L")
set(gp_regex "^\t([^\t]+) \\(compatibility version ([0-9]+.[0-9]+.[0-9]+), current version ([0-9]+.[0-9]+.[0-9]+)\\)${eol_char}$")
set(gp_regex_error "")
set(gp_regex_fallback "")
set(gp_regex_cmp_count 3)
set(gp_tool_known 1)
endif()
if("${gp_tool}" STREQUAL "dumpbin")
set(gp_cmd_args "/dependents")
set(gp_regex "^ ([^ ].*[Dd][Ll][Ll])${eol_char}$")
set(gp_regex_error "")
set(gp_regex_fallback "")
set(gp_regex_cmp_count 1)
set(gp_tool_known 1)
endif()
if("${gp_tool}" STREQUAL "objdump")
set(gp_cmd_args "-p")
set(gp_regex "^\t*DLL Name: (.*\\.[Dd][Ll][Ll])${eol_char}$")
set(gp_regex_error "")
set(gp_regex_fallback "")
set(gp_regex_cmp_count 1)
set(gp_tool_known 1)
endif()
if(NOT gp_tool_known)
message(STATUS "warning: gp_tool='${gp_tool}' is an unknown tool...")
message(STATUS "CMake function get_prerequisites needs more code to handle '${gp_tool}'")
message(STATUS "Valid gp_tool values are dumpbin, ldd, objdump and otool.")
return()
endif()
if("${gp_tool}" STREQUAL "dumpbin")
# When running dumpbin, it also needs the "Common7/IDE" directory in the
# PATH. It will already be in the PATH if being run from a Visual Studio
# command prompt. Add it to the PATH here in case we are running from a
# different command prompt.
#
get_filename_component(gp_cmd_dir "${gp_cmd}" PATH)
get_filename_component(gp_cmd_dlls_dir "${gp_cmd_dir}/../../Common7/IDE" ABSOLUTE)
# Use cmake paths as a user may have a PATH element ending with a backslash.
# This will escape the list delimiter and create havoc!
if(EXISTS "${gp_cmd_dlls_dir}")
# only add to the path if it is not already in the path
set(gp_found_cmd_dlls_dir 0)
file(TO_CMAKE_PATH "$ENV{PATH}" env_path)
foreach(gp_env_path_element ${env_path})
if("${gp_env_path_element}" STREQUAL "${gp_cmd_dlls_dir}")
set(gp_found_cmd_dlls_dir 1)
endif()
endforeach()
if(NOT gp_found_cmd_dlls_dir)
file(TO_NATIVE_PATH "${gp_cmd_dlls_dir}" gp_cmd_dlls_dir)
set(ENV{PATH} "$ENV{PATH};${gp_cmd_dlls_dir}")
endif()
endif()
endif()
#
# </setup-gp_tool-vars>
if("${gp_tool}" STREQUAL "ldd")
set(old_ld_env "$ENV{LD_LIBRARY_PATH}")
foreach(dir ${exepath} ${dirs})
set(ENV{LD_LIBRARY_PATH} "${dir}:$ENV{LD_LIBRARY_PATH}")
endforeach()
endif()
# Track new prerequisites at each new level of recursion. Start with an
# empty list at each level:
#
set(unseen_prereqs)
# Run gp_cmd on the target:
#
execute_process(
COMMAND ${gp_cmd} ${gp_cmd_args} ${target}
OUTPUT_VARIABLE gp_cmd_ov
)
if("${gp_tool}" STREQUAL "ldd")
set(ENV{LD_LIBRARY_PATH} "${old_ld_env}")
endif()
if(verbose)
message(STATUS "<RawOutput cmd='${gp_cmd} ${gp_cmd_args} ${target}'>")
message(STATUS "gp_cmd_ov='${gp_cmd_ov}'")
message(STATUS "</RawOutput>")
endif()
get_filename_component(target_dir "${target}" PATH)
# Convert to a list of lines:
#
string(REGEX REPLACE ";" "\\\\;" candidates "${gp_cmd_ov}")
string(REGEX REPLACE "\n" "${eol_char};" candidates "${candidates}")
# check for install id and remove it from list, since otool -L can include a
# reference to itself
set(gp_install_id)
if("${gp_tool}" STREQUAL "otool")
execute_process(
COMMAND otool -D ${target}
OUTPUT_VARIABLE gp_install_id_ov
)
# second line is install name
string(REGEX REPLACE ".*:\n" "" gp_install_id "${gp_install_id_ov}")
if(gp_install_id)
# trim
string(REGEX MATCH "[^\n ].*[^\n ]" gp_install_id "${gp_install_id}")
#message("INSTALL ID is \"${gp_install_id}\"")
endif()
endif()
# Analyze each line for file names that match the regular expression:
#
foreach(candidate ${candidates})
if("${candidate}" MATCHES "${gp_regex}")
# Extract information from each candidate:
if(gp_regex_error AND "${candidate}" MATCHES "${gp_regex_error}")
string(REGEX REPLACE "${gp_regex_fallback}" "\\1" raw_item "${candidate}")
else()
string(REGEX REPLACE "${gp_regex}" "\\1" raw_item "${candidate}")
endif()
if(gp_regex_cmp_count GREATER 1)
string(REGEX REPLACE "${gp_regex}" "\\2" raw_compat_version "${candidate}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" compat_major_version "${raw_compat_version}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" compat_minor_version "${raw_compat_version}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" compat_patch_version "${raw_compat_version}")
endif()
if(gp_regex_cmp_count GREATER 2)
string(REGEX REPLACE "${gp_regex}" "\\3" raw_current_version "${candidate}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" current_major_version "${raw_current_version}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" current_minor_version "${raw_current_version}")
string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" current_patch_version "${raw_current_version}")
endif()
# Use the raw_item as the list entries returned by this function. Use the
# gp_resolve_item function to resolve it to an actual full path file if
# necessary.
#
set(item "${raw_item}")
# Add each item unless it is excluded:
#
set(add_item 1)
if("${item}" STREQUAL "${gp_install_id}")
set(add_item 0)
endif()
if(add_item AND ${exclude_system})
set(type "")
gp_resolved_file_type("${target}" "${item}" "${exepath}" "${dirs}" type)
if("${type}" STREQUAL "system")
set(add_item 0)
endif()
endif()
if(add_item)
list(LENGTH ${prerequisites_var} list_length_before_append)
gp_append_unique(${prerequisites_var} "${item}")
list(LENGTH ${prerequisites_var} list_length_after_append)
if(${recurse})
# If item was really added, this is the first time we have seen it.
# Add it to unseen_prereqs so that we can recursively add *its*
# prerequisites...
#
# But first: resolve its name to an absolute full path name such
# that the analysis tools can simply accept it as input.
#
if(NOT list_length_before_append EQUAL list_length_after_append)
gp_resolve_item("${target}" "${item}" "${exepath}" "${dirs}" resolved_item)
set(unseen_prereqs ${unseen_prereqs} "${resolved_item}")
endif()
endif()
endif()
else()
if(verbose)
message(STATUS "ignoring non-matching line: '${candidate}'")
endif()
endif()
endforeach()
list(LENGTH ${prerequisites_var} prerequisites_var_length)
if(prerequisites_var_length GREATER 0)
list(SORT ${prerequisites_var})
endif()
if(${recurse})
set(more_inputs ${unseen_prereqs})
foreach(input ${more_inputs})
get_prerequisites("${input}" ${prerequisites_var} ${exclude_system} ${recurse} "${exepath}" "${dirs}")
endforeach()
endif()
set(${prerequisites_var} ${${prerequisites_var}} PARENT_SCOPE)
endfunction()
function(list_prerequisites target)
if("${ARGV1}" STREQUAL "")
set(all 1)
else()
set(all "${ARGV1}")
endif()
if("${ARGV2}" STREQUAL "")
set(exclude_system 0)
else()
set(exclude_system "${ARGV2}")
endif()
if("${ARGV3}" STREQUAL "")
set(verbose 0)
else()
set(verbose "${ARGV3}")
endif()
set(count 0)
set(count_str "")
set(print_count "${verbose}")
set(print_prerequisite_type "${verbose}")
set(print_target "${verbose}")
set(type_str "")
get_filename_component(exepath "${target}" PATH)
set(prereqs "")
get_prerequisites("${target}" prereqs ${exclude_system} ${all} "${exepath}" "")
if(print_target)
message(STATUS "File '${target}' depends on:")
endif()
foreach(d ${prereqs})
math(EXPR count "${count} + 1")
if(print_count)
set(count_str "${count}. ")
endif()
if(print_prerequisite_type)
gp_file_type("${target}" "${d}" type)
set(type_str " (${type})")
endif()
message(STATUS "${count_str}${d}${type_str}")
endforeach()
endfunction()
function(list_prerequisites_by_glob glob_arg glob_exp)
message(STATUS "=============================================================================")
message(STATUS "List prerequisites of executables matching ${glob_arg} '${glob_exp}'")
message(STATUS "")
file(${glob_arg} file_list ${glob_exp})
foreach(f ${file_list})
is_file_executable("${f}" is_f_executable)
if(is_f_executable)
message(STATUS "=============================================================================")
list_prerequisites("${f}" ${ARGN})
message(STATUS "")
endif()
endforeach()
endfunction()

37
cmake/GitFunctions.cmake Normal file
View File

@@ -0,0 +1,37 @@
if(__GITFUNCTIONS_CMAKE__)
return()
endif()
set(__GITFUNCTIONS_CMAKE__ TRUE)
find_package(Git QUIET)
include(CMakeParseArguments)
if(GIT_FOUND)
function(git_run)
set(oneValueArgs OUTPUT_VAR DEFAULT)
set(multiValueArgs COMMAND)
cmake_parse_arguments(GIT_RUN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
execute_process(COMMAND ${GIT_EXECUTABLE} ${GIT_RUN_COMMAND}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
RESULT_VARIABLE GIT_RESULTVAR
OUTPUT_VARIABLE GIT_OUTVAR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(GIT_RESULTVAR EQUAL 0)
set(${GIT_RUN_OUTPUT_VAR} "${GIT_OUTVAR}" PARENT_SCOPE)
else()
set(${GIT_RUN_OUTPUT_VAR} ${GIT_RUN_DEFAULT})
message(STATUS "Failed to run Git: ${GIT_OUTVAR}")
endif()
endfunction()
else()
function(git_run)
set(oneValueArgs OUTPUT_VAR DEFAULT)
set(multiValueArgs COMMAND)
cmake_parse_arguments(GIT_RUN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(${GIT_RUN_OUTPUT_VAR} ${GIT_RUN_DEFAULT})
endfunction(git_run)
endif()

14
cmake/QMakeQuery.cmake Normal file
View File

@@ -0,0 +1,14 @@
if(__QMAKEQUERY_CMAKE__)
return()
endif()
set(__QMAKEQUERY_CMAKE__ TRUE)
get_target_property(QMAKE_EXECUTABLE Qt5::qmake LOCATION)
function(QUERY_QMAKE VAR RESULT)
exec_program(${QMAKE_EXECUTABLE} ARGS "-query ${VAR}" RETURN_VALUE return_code OUTPUT_VARIABLE output )
if(NOT return_code)
file(TO_CMAKE_PATH "${output}" output)
set(${RESULT} ${output} PARENT_SCOPE)
endif(NOT return_code)
endfunction(QUERY_QMAKE)

13
cmake/UseCXX11.cmake Normal file
View File

@@ -0,0 +1,13 @@
if(__USECXX11_CMAKE__)
return()
endif()
set(__USECXX11_CMAKE__ TRUE)
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++11")
elseif(UNIX)
# assume GCC, add C++0x/C++11 stuff
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(MINGW)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
endif()

View File

@@ -1,40 +0,0 @@
#pragma once
// Version information
#define VERSION_MAJOR @MultiMC_VERSION_MAJOR@
#define VERSION_MINOR @MultiMC_VERSION_MINOR@
#define VERSION_HOTFIX @MultiMC_VERSION_HOTFIX@
#define VERSION_BUILD @MultiMC_VERSION_BUILD@
#define VERSION_TYPE @MultiMC_VERSION_TYPE@
// The version channel. This is used by the updater to determine what channel the current version came from.
#define VERSION_CHANNEL "@MultiMC_VERSION_CHANNEL@"
// A short string identifying this build's platform. For example, "lin64" or "win32".
#define BUILD_PLATFORM "@MultiMC_BUILD_PLATFORM@"
// URL for the updater's channel
#define CHANLIST_URL "@MultiMC_CHANLIST_URL@"
// URL for notifications
#define NOTIFICATION_URL "@MultiMC_NOTIFICATION_URL@"
// Used for matching notifications
#define FULL_VERSION_STR "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@"
// enabled for updater dry run
#cmakedefine MultiMC_UPDATER_DRY_RUN
// enabled for updater dry run
#cmakedefine MultiMC_UPDATER_FORCE_LOCAL
// The commit hash of this build
#define GIT_COMMIT "@MultiMC_GIT_COMMIT@"
// This is printed on start to standard output
#define VERSION_STR "@MultiMC_VERSION_STRING@"
// This is used to fetch the news RSS feed.
// It defaults in CMakeLists.txt to "http://multimc.org/rss.xml"
#define NEWS_RSS_URL "@MultiMC_NEWS_RSS_URL@"

View File

@@ -1,33 +0,0 @@
cmake_minimum_required(VERSION 2.8.9)
message(STATUS "Running install script...")
SET(Qt5_DIR @Qt5_DIR@)
IF(WIN32)
SET(LIB_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
ELSE()
SET(LIB_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/lib)
ENDIF()
INCLUDE(GetPrerequisites)
GET_PREREQUISITES(@BINARY_LOCATION@ MULTIMC_PREREQS 1 1 "" "")
message(STATUS "Prerequisites: ${MULTIMC_PREREQS}")
FOREACH(PREREQ ${MULTIMC_PREREQS})
GET_FILENAME_COMPONENT(PREREQ_NAME "${PREREQ}" NAME)
GET_FILENAME_COMPONENT(PREREQ_ACTUAL "${PREREQ}" REALPATH)
IF(WIN32)
SET(PREREQ_ACTUAL "${Qt5_DIR}/bin/${PREREQ}")
ENDIF()
message(STATUS "Adding install prerequisite: ${PREREQ_NAME}")
FILE(INSTALL
DESTINATION "${LIB_INSTALL_PREFIX}"
TYPE PROGRAM
RENAME "${PREREQ_NAME}"
FILES "${PREREQ_ACTUAL}"
)
ENDFOREACH()

View File

@@ -8,7 +8,7 @@ find_package(Qt5Core REQUIRED)
# Include Qt headers.
include_directories(${Qt5Base_INCLUDE_DIRS})
SET(CLASSPARSER_HEADERS
set(CLASSPARSER_HEADERS
include/classparser_config.h
# Public headers
@@ -23,13 +23,13 @@ src/javaendian.h
src/membuffer.h
)
SET(CLASSPARSER_SOURCES
set(CLASSPARSER_SOURCES
src/javautils.cpp
src/annotations.cpp
)
# Set the include dir path.
SET(LIBGROUPVIEW_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
set(LIBGROUPVIEW_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
# Include self.
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

View File

@@ -7,9 +7,9 @@ set(CMAKE_JAVA_JAR_ENTRY_POINT JavaCheck)
set(CMAKE_JAVA_COMPILE_FLAGS -target 1.6 -source 1.6 -Xlint:deprecation -Xlint:unchecked)
set(SRC
JavaCheck.java
JavaCheck.java
)
add_jar(JavaCheck ${SRC})
INSTALL_JAR(JavaCheck "${BINARY_DEST_DIR}/jars")
install_jar(JavaCheck "${BINARY_DEST_DIR}/jars")

View File

@@ -8,28 +8,28 @@ set(CMAKE_JAVA_COMPILE_FLAGS -target 1.6 -source 1.6 -Xlint:deprecation -Xlint:u
set(SRC
# 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
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
org/multimc/legacy/LegacyLauncher.java
org/multimc/legacy/LegacyFrame.java
net/minecraft/Launcher.java
org/multimc/legacy/LegacyLauncher.java
org/multimc/legacy/LegacyFrame.java
# onesix launcher
org/multimc/onesix/OneSixLauncher.java
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
org/multimc/Launcher.java
org/multimc/ParseException.java
org/multimc/Utils.java
org/multimc/IconLoader.java
)
add_jar(NewLaunch ${SRC})
INSTALL_JAR(NewLaunch "${BINARY_DEST_DIR}/jars")
install_jar(NewLaunch "${BINARY_DEST_DIR}/jars")

View File

@@ -1,59 +1,59 @@
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
cmake_minimum_required(VERSION 2.6)
IF(WIN32)
if(WIN32)
# In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows
cmake_policy(SET CMP0020 OLD)
ENDIF()
endif()
project(unpack200)
# Find ZLIB for quazip
# Use system zlib on unix and Qt ZLIB on Windows
IF(UNIX)
if(UNIX)
find_package(ZLIB REQUIRED)
ELSE(UNIX)
get_filename_component (ZLIB_FOUND_DIR "${Qt5Core_DIR}/../../../include/QtZlib" ABSOLUTE)
SET(ZLIB_INCLUDE_DIRS ${ZLIB_FOUND_DIR} CACHE PATH "Path to ZLIB headers of Qt")
SET(ZLIB_LIBRARIES "")
IF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h")
MESSAGE("Please specify a valid zlib include dir")
ENDIF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h")
ENDIF(UNIX)
else(UNIX)
get_filename_component(ZLIB_FOUND_DIR "${Qt5Core_DIR}/../../../include/QtZlib" ABSOLUTE)
set(ZLIB_INCLUDE_DIRS ${ZLIB_FOUND_DIR} CACHE PATH "Path to ZLIB headers of Qt")
set(ZLIB_LIBRARIES "")
if(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h")
message("Please specify a valid zlib include dir")
endif(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h")
endif(UNIX)
SET(PACK200_SRC
include/unpack200.h
src/bands.cpp
src/bands.h
src/bytes.cpp
src/bytes.h
src/coding.cpp
src/coding.h
src/constants.h
src/defines.h
src/unpack200.cpp
src/unpack.cpp
src/unpack.h
src/utils.cpp
src/utils.h
src/zip.cpp
src/zip.h
set(PACK200_SRC
include/unpack200.h
src/bands.cpp
src/bands.h
src/bytes.cpp
src/bytes.h
src/coding.cpp
src/coding.h
src/constants.h
src/defines.h
src/unpack200.cpp
src/unpack.cpp
src/unpack.h
src/utils.cpp
src/utils.h
src/zip.cpp
src/zip.h
)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
SET(PACK200_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
set(PACK200_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
include_directories(
include
${ZLIB_INCLUDE_DIRS}
)
add_library(unpack200 STATIC ${PACK200_SRC})
IF(UNIX)
if(UNIX)
target_link_libraries(unpack200 ${ZLIB_LIBRARIES})
ELSE()
else()
# zlib is part of Qt on windows. use it.
QT5_USE_MODULES(unpack200 Core)
ENDIF()
qt5_use_modules(unpack200 Core)
endif()
add_executable(anti200 anti200.cpp)
target_link_libraries(anti200 unpack200)

View File

@@ -2,16 +2,16 @@ project(quazip)
# Find ZLIB for quazip
# Use system zlib on unix and Qt ZLIB on Windows
IF(UNIX)
if(UNIX)
find_package(ZLIB REQUIRED)
ELSE(UNIX)
get_filename_component (ZLIB_FOUND_DIR "${Qt5Core_DIR}/../../../include/QtZlib" ABSOLUTE)
SET(ZLIB_INCLUDE_DIRS ${ZLIB_FOUND_DIR} CACHE PATH "Path to ZLIB headers of Qt")
SET(ZLIB_LIBRARIES "")
IF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h")
MESSAGE("Please specify a valid zlib include dir")
ENDIF(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h")
ENDIF(UNIX)
else(UNIX)
get_filename_component(ZLIB_FOUND_DIR "${Qt5Core_DIR}/../../../include/QtZlib" ABSOLUTE)
set(ZLIB_INCLUDE_DIRS ${ZLIB_FOUND_DIR} CACHE PATH "Path to ZLIB headers of Qt")
set(ZLIB_LIBRARIES "")
if(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h")
message("Please specify a valid zlib include dir")
endif(NOT EXISTS "${ZLIB_INCLUDE_DIRS}/zlib.h")
endif(UNIX)
# set all include directories for in and out of source builds
include_directories(
@@ -20,23 +20,12 @@ include_directories(
${ZLIB_INCLUDE_DIRS}
)
# include with QT_USE selected library parts
# INCLUDE(${QT_USE_FILE})
file(GLOB SRCS "*.c" "*.cpp")
file(GLOB PUBLIC_HEADERS "*.h")
# Static link!
ADD_DEFINITIONS(-DQUAZIP_STATIC)
#qt5_wrap_cpp(MOC_SRCS ${PUBLIC_HEADERS})
#set(SRCS ${SRCS} ${MOC_SRCS})
#set(CMAKE_POSITION_INDEPENDENT_CODE ON)
add_definitions(-DQUAZIP_STATIC)
add_library(quazip STATIC ${SRCS})
QT5_USE_MODULES(quazip Core)
qt5_use_modules(quazip Core)
target_link_libraries(quazip ${ZLIB_LIBRARIES})
#install(FILES ${PUBLIC_HEADERS} DESTINATION include/quazip)
#install(TARGETS quazip LIBRARY DESTINATION ${LIB_DESTINATION} ARCHIVE DESTINATION ${LIB_DESTINATION} RUNTIME DESTINATION ${LIB_DESTINATION})

View File

@@ -6,42 +6,36 @@ find_package(Qt5Core REQUIRED)
# Include Qt headers.
include_directories(${Qt5Base_INCLUDE_DIRS})
include(UseCXX11)
include(Coverage)
SET(LIBSETTINGS_SOURCES
libsettings_config.h
set(LIBSETTINGS_SOURCES
libsettings_config.h
inifile.h
inifile.cpp
inifile.h
inifile.cpp
settingsobject.h
settingsobject.cpp
inisettingsobject.h
inisettingsobject.cpp
settingsobject.h
settingsobject.cpp
inisettingsobject.h
inisettingsobject.cpp
setting.h
setting.cpp
overridesetting.h
overridesetting.cpp
setting.h
setting.cpp
overridesetting.h
overridesetting.cpp
)
# Set the include dir path.
SET(LIBSETTINGS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE)
set(LIBSETTINGS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE)
# Static link!
ADD_DEFINITIONS(-DLIBSETTINGS_STATIC)
add_definitions(-DLIBSETTINGS_STATIC)
add_definitions(-DLIBSETTINGS_LIBRARY)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
IF(MultiMC_CODE_COVERAGE)
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 --coverage")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 --coverage")
SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 --coverage")
SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O0 --coverage")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --coverage")
ENDIF(MultiMC_CODE_COVERAGE)
add_library(libSettings STATIC ${LIBSETTINGS_SOURCES})
qt5_use_modules(libSettings Core)
target_link_libraries(libSettings)

View File

@@ -1,64 +1,42 @@
project(libUtil)
######## Set compiler flags ########
IF(APPLE)
# assume clang 4.1.0+, add C++0x/C++11 stuff
message(STATUS "Using APPLE CMAKE_CXX_FLAGS")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
ELSEIF(UNIX)
# assume GCC, add C++0x/C++11 stuff
MESSAGE(STATUS "Using UNIX CMAKE_CXX_FLAGS")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
ELSEIF(MINGW)
MESSAGE(STATUS "Using MINGW CMAKE_CXX_FLAGS")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x")
ENDIF()
include(UseCXX11)
include(Coverage)
# Find Qt
find_package(Qt5Core REQUIRED)
# Include Qt headers.
include_directories(${Qt5Base_INCLUDE_DIRS})
# include_directories(${Qt5Network_INCLUDE_DIRS})
SET(LIBUTIL_SOURCES
include/libutil_config.h
set(LIBUTIL_SOURCES
include/libutil_config.h
include/pathutils.h
src/pathutils.cpp
include/pathutils.h
src/pathutils.cpp
include/osutils.h
include/osutils.h
include/userutils.h
src/userutils.cpp
include/userutils.h
src/userutils.cpp
include/cmdutils.h
src/cmdutils.cpp
include/cmdutils.h
src/cmdutils.cpp
include/modutils.h
src/modutils.cpp
include/modutils.h
src/modutils.cpp
)
# Set the include dir path.
SET(LIBUTIL_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
set(LIBUTIL_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
# Static link!
ADD_DEFINITIONS(-DLIBUTIL_STATIC)
add_definitions(-DLIBUTIL_STATIC)
add_definitions(-DLIBUTIL_LIBRARY)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
IF(MultiMC_CODE_COVERAGE)
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 --coverage")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 --coverage")
SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 --coverage")
SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g -O0 --coverage")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 --coverage")
ENDIF(MultiMC_CODE_COVERAGE)
add_library(libUtil STATIC ${LIBUTIL_SOURCES})
# qt5_use_modules(libUtil Core Network)
qt5_use_modules(libUtil Core)
target_link_libraries(libUtil)

View File

@@ -8,22 +8,22 @@ option(XZ_BUILD_MINIDEC "Build a tiny utility that decompresses xz streams" OFF)
set(CMAKE_C_FLAGS "-std=c99")
include_directories(include)
SET(XZ_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
set(XZ_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE)
# See include/xz.h for manual feature configuration
# tweak this list and xz.h to fit your needs
set(XZ_SOURCES
include/xz.h
src/xz_config.h
src/xz_crc32.c
src/xz_crc64.c
src/xz_dec_lzma2.c
src/xz_dec_stream.c
src/xz_lzma2.h
src/xz_private.h
src/xz_stream.h
# src/xz_dec_bcj.c
include/xz.h
src/xz_config.h
src/xz_crc32.c
src/xz_crc64.c
src/xz_dec_lzma2.c
src/xz_dec_stream.c
src/xz_lzma2.h
src/xz_private.h
src/xz_stream.h
# src/xz_dec_bcj.c
)
# TODO: look into what would be needed for plain old lzma

View File

@@ -24,9 +24,11 @@
#include <gui/Platform.h>
#include <gui/dialogs/CustomMessageBox.h>
#include <gui/dialogs/ProgressDialog.h>
#include "dialogs/ScreenshotDialog.h"
#include "logic/net/PasteUpload.h"
#include "logic/icons/IconList.h"
#include <logic/screenshots/ScreenshotList.h>
ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent)
: QMainWindow(parent), ui(new Ui::ConsoleWindow), proc(mcproc)
@@ -35,14 +37,12 @@ ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent)
ui->setupUi(this);
connect(mcproc, SIGNAL(log(QString, MessageLevel::Enum)), this,
SLOT(write(QString, MessageLevel::Enum)));
connect(mcproc, SIGNAL(ended(BaseInstance *, int, QProcess::ExitStatus)), this,
SLOT(onEnded(BaseInstance *, int, QProcess::ExitStatus)));
connect(mcproc, SIGNAL(prelaunch_failed(BaseInstance *, int, QProcess::ExitStatus)), this,
SLOT(onEnded(BaseInstance *, int, QProcess::ExitStatus)));
connect(mcproc, SIGNAL(launch_failed(BaseInstance *)), this,
SLOT(onLaunchFailed(BaseInstance *)));
connect(ui->btnScreenshots, &QPushButton::clicked, this, &ConsoleWindow::uploadScreenshots);
connect(mcproc, SIGNAL(ended(InstancePtr, int, QProcess::ExitStatus)), this,
SLOT(onEnded(InstancePtr, int, QProcess::ExitStatus)));
connect(mcproc, SIGNAL(prelaunch_failed(InstancePtr, int, QProcess::ExitStatus)), this,
SLOT(onEnded(InstancePtr, int, QProcess::ExitStatus)));
connect(mcproc, SIGNAL(launch_failed(InstancePtr)), this,
SLOT(onLaunchFailed(InstancePtr)));
restoreState(
QByteArray::fromBase64(MMC->settings()->get("ConsoleWindowState").toByteArray()));
@@ -172,6 +172,26 @@ void ConsoleWindow::on_closeButton_clicked()
close();
}
void ConsoleWindow::on_btnScreenshots_clicked()
{
ScreenshotList *list = new ScreenshotList(proc->instance());
Task *task = list->load();
ProgressDialog prog(this);
prog.exec(task);
if (!task->successful())
{
CustomMessageBox::selectable(this, tr("Failed to load screenshots!"),
task->failReason(), QMessageBox::Warning)->exec();
return;
}
ScreenshotDialog dialog(list, this);
if (dialog.exec() == ScreenshotDialog::Accepted)
{
CustomMessageBox::selectable(this, tr("Done uploading!"), dialog.message(),
QMessageBox::Information)->exec();
}
}
void ConsoleWindow::setMayClose(bool mayclose)
{
if(mayclose)
@@ -242,7 +262,7 @@ void ConsoleWindow::on_btnKillMinecraft_clicked()
ui->btnKillMinecraft->setEnabled(true);
}
void ConsoleWindow::onEnded(BaseInstance *instance, int code, QProcess::ExitStatus status)
void ConsoleWindow::onEnded(InstancePtr instance, int code, QProcess::ExitStatus status)
{
bool peacefulExit = code == 0 && status != QProcess::CrashExit;
ui->btnKillMinecraft->setEnabled(false);
@@ -265,9 +285,16 @@ void ConsoleWindow::onEnded(BaseInstance *instance, int code, QProcess::ExitStat
*/
if (!isVisible())
show();
// Raise Window
if (MMC->settings()->get("RaiseConsole").toBool())
{
raise();
activateWindow();
}
}
void ConsoleWindow::onLaunchFailed(BaseInstance *instance)
void ConsoleWindow::onLaunchFailed(InstancePtr instance)
{
ui->btnKillMinecraft->setEnabled(false);

View File

@@ -51,7 +51,6 @@ private:
signals:
void isClosing();
void uploadScreenshots();
public
slots:
@@ -71,9 +70,11 @@ slots:
private
slots:
void on_closeButton_clicked();
void on_btnScreenshots_clicked();
void on_btnKillMinecraft_clicked();
void onEnded(BaseInstance *instance, int code, QProcess::ExitStatus status);
void onLaunchFailed(BaseInstance *instance);
void onEnded(InstancePtr instance, int code, QProcess::ExitStatus status);
void onLaunchFailed(InstancePtr instance);
// FIXME: add handlers for the other MinecraftProcess signals (pre/post launch command
// failures)

View File

@@ -17,6 +17,7 @@
* limitations under the License.
*/
#include "MultiMC.h"
#include "BuildConfig.h"
#include "MainWindow.h"
#include "ui_MainWindow.h"
@@ -34,6 +35,7 @@
#include <QToolButton>
#include <QWidgetAction>
#include <QProgressDialog>
#include <QShortcut>
#include "osutils.h"
#include "userutils.h"
@@ -45,6 +47,7 @@
#include "gui/Platform.h"
#include "gui/widgets/LabeledToolButton.h"
#include "widgets/ServerStatus.h"
#include "gui/dialogs/SettingsDialog.h"
#include "gui/dialogs/NewInstanceDialog.h"
@@ -61,8 +64,8 @@
#include "gui/dialogs/AccountSelectDialog.h"
#include "gui/dialogs/UpdateDialog.h"
#include "gui/dialogs/EditAccountDialog.h"
#include "gui/dialogs/ScreenshotDialog.h"
#include "gui/dialogs/NotificationDialog.h"
#include "dialogs/ScreenshotDialog.h"
#include "gui/ConsoleWindow.h"
@@ -107,14 +110,21 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
MultiMCPlatform::fixWM_CLASS(this);
ui->setupUi(this);
QString winTitle = QString("MultiMC 5 - Version %1").arg(MMC->version().toString());
if (!MMC->version().platform.isEmpty())
winTitle += " on " + MMC->version().platform;
QString winTitle = QString("MultiMC 5 - Version %1").arg(BuildConfig.printableVersionString());
if (!BuildConfig.BUILD_PLATFORM.isEmpty())
winTitle += " on " + BuildConfig.BUILD_PLATFORM;
setWindowTitle(winTitle);
// OSX magic.
// setUnifiedTitleAndToolBarOnMac(true);
// Global shortcuts
{
//FIXME: This is kinda weird. and bad. We need some kind of managed shutdown.
auto q = new QShortcut(QKeySequence::Quit, this);
connect(q, SIGNAL(activated()), qApp, SLOT(quit()));
}
// The instance action toolbar customizations
{
// disabled until we have an instance selected
@@ -207,29 +217,9 @@ 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"));
m_statusRight = new ServerStatus(this);
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();
@@ -352,25 +342,52 @@ void MainWindow::skinJobFinished()
void MainWindow::showInstanceContextMenu(const QPoint &pos)
{
if (!view->indexAt(pos).isValid())
QList<QAction *> actions;
QAction *actionSep = new QAction("", this);
actionSep->setSeparator(true);
bool onInstance = view->indexAt(pos).isValid();
if (onInstance)
{
return;
actions = ui->instanceToolBar->actions();
QAction *actionVoid = new QAction(m_selectedInstance->name(), this);
actionVoid->setEnabled(false);
QAction *actionRename = new QAction(tr("Rename"), this);
actionRename->setToolTip(ui->actionRenameInstance->toolTip());
QAction *actionCopyInstance = new QAction(tr("Copy instance"), this);
actionCopyInstance->setToolTip(ui->actionCopyInstance->toolTip());
connect(actionRename, SIGNAL(triggered(bool)), SLOT(on_actionRenameInstance_triggered()));
connect(actionCopyInstance, SIGNAL(triggered(bool)), SLOT(on_actionCopyInstance_triggered()));
actions.replace(1, actionRename);
actions.prepend(actionSep);
actions.prepend(actionVoid);
actions.append(actionCopyInstance);
}
else
{
QAction *actionVoid = new QAction(tr("MultiMC"), this);
actionVoid->setEnabled(false);
QList<QAction *> actions = ui->instanceToolBar->actions();
QAction *actionCreateInstance = new QAction(tr("Create instance"), this);
actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip());
// HACK: Filthy rename button hack because the instance view is getting rewritten anyway
QAction *actionRename;
actionRename = new QAction(tr("Rename"), this);
actionRename->setToolTip(ui->actionRenameInstance->toolTip());
connect(actionRename, SIGNAL(triggered(bool)), SLOT(on_actionRenameInstance_triggered()));
actions.replace(1, actionRename);
connect(actionCreateInstance, SIGNAL(triggered(bool)), SLOT(on_actionAddInstance_triggered()));
actions.prepend(actionSep);
actions.prepend(actionVoid);
actions.append(actionCreateInstance);
}
QMenu myMenu;
myMenu.addActions(actions);
myMenu.setEnabled(m_selectedInstance->canLaunch());
if(onInstance)
myMenu.setEnabled(m_selectedInstance->canLaunch());
myMenu.exec(view->mapToGlobal(pos));
}
@@ -584,60 +601,6 @@ 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;
@@ -709,9 +672,8 @@ void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit
if (updateDlg.exec(&updateTask))
{
UpdateFlags baseFlags = None;
#ifdef MultiMC_UPDATER_DRY_RUN
baseFlags |= DryRun;
#endif
if(BuildConfig.UPDATER_DRY_RUN)
baseFlags |= DryRun;
if (installOnExit)
MMC->installUpdates(updateTask.updateFilesDir(), baseFlags | OnExit);
else
@@ -747,6 +709,11 @@ void MainWindow::setCatBackground(bool enabled)
void MainWindow::on_actionAddInstance_triggered()
{
#ifdef TEST_SEGV
// For further testing stuff.
int v = *((int*)-1);
#endif
if (!MMC->minecraftlist()->isLoaded() && m_versionLoadTask &&
m_versionLoadTask->isRunning())
{
@@ -988,6 +955,11 @@ void MainWindow::on_actionReportBug_triggered()
openWebPage(QUrl("https://github.com/MultiMC/MultiMC5/issues"));
}
void MainWindow::on_actionPatreon_triggered()
{
openWebPage(QUrl("http://www.patreon.com/multimc"));
}
void MainWindow::on_actionMoreNews_triggered()
{
openWebPage(QUrl("http://multimc.org/posts.html"));
@@ -1272,15 +1244,19 @@ void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, Ba
Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL");
Q_ASSERT_X(session.get() != nullptr, "launchInstance", "session is NULL");
proc = instance->prepareForLaunch(session);
if (!proc)
QString launchScript;
if(!instance->prepareForLaunch(session, launchScript))
return;
MinecraftProcess *proc = new MinecraftProcess(instance);
proc->setLaunchScript(launchScript);
proc->setWorkdir(instance->minecraftRoot());
this->hide();
console = new ConsoleWindow(proc);
connect(console, SIGNAL(isClosing()), this, SLOT(instanceEnded()));
connect(console, &ConsoleWindow::uploadScreenshots, this, &MainWindow::on_actionScreenshots_triggered);
proc->setLogin(session);
proc->arm();
@@ -1302,7 +1278,7 @@ void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, Ba
dialog.setLabelText(tr("Waiting for profiler..."));
connect(&dialog, &QProgressDialog::canceled, profilerInstance, &BaseProfiler::abortProfiling);
dialog.show();
connect(profilerInstance, &BaseProfiler::readyToLaunch, [&dialog, this](const QString &message)
connect(profilerInstance, &BaseProfiler::readyToLaunch, [&dialog, this, proc](const QString &message)
{
dialog.accept();
QMessageBox msg;
@@ -1315,7 +1291,7 @@ void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, Ba
msg.exec();
proc->launch();
});
connect(profilerInstance, &BaseProfiler::abortLaunch, [&dialog, this](const QString &message)
connect(profilerInstance, &BaseProfiler::abortLaunch, [&dialog, this, proc](const QString &message)
{
dialog.accept();
QMessageBox msg;
@@ -1393,7 +1369,7 @@ void MainWindow::on_actionChangeInstMCVersion_triggered()
VersionSelectDialog vselect(m_selectedInstance->versionList().get(),
tr("Change Minecraft version"), this);
vselect.setFilter(1, "OneSix");
vselect.setFuzzyFilter(1, "*OneSix*");
if (!vselect.exec() || !vselect.selectedVersion())
return;

View File

@@ -85,6 +85,8 @@ slots:
void on_actionReportBug_triggered();
void on_actionPatreon_triggered();
void on_actionMoreNews_triggered();
void newsButtonClicked();
@@ -109,6 +111,8 @@ slots:
void on_actionEditInstNotes_triggered();
void on_actionScreenshots_triggered();
/*!
* Launches the currently selected instance with the default account.
* If no default account is selected, prompts the user to pick an account.
@@ -142,8 +146,6 @@ slots:
void showInstanceContextMenu(const QPoint &);
void on_actionScreenshots_triggered();
void updateToolsMenu();
void skinJobFinished();
@@ -169,12 +171,6 @@ slots:
void updateNewsLabel();
void updateStatusUI();
void updateStatusFailedUI();
void reloadStatus();
/*!
* Runs the DownloadUpdateTask and installs updates.
*/
@@ -204,12 +200,9 @@ private:
Task *m_versionLoadTask;
QLabel *m_statusLeft;
QLabel *m_statusRight;
QToolButton *m_statusRefresh;
class ServerStatus *m_statusRight;
QMenu *accountMenu;
QToolButton *accountMenuButton;
QAction *manageAccountsAction;
QTimer statusTimer;
};

View File

@@ -74,6 +74,7 @@
<addaction name="actionReportBug"/>
<addaction name="actionAbout"/>
<addaction name="separator"/>
<addaction name="actionPatreon"/>
<addaction name="actionCAT"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
@@ -268,6 +269,22 @@
<string>Open the bug tracker to report a bug with MultiMC.</string>
</property>
</action>
<action name="actionPatreon">
<property name="icon">
<iconset theme="patreon">
<normaloff/>
</iconset>
</property>
<property name="text">
<string>Support us on Patreon!</string>
</property>
<property name="toolTip">
<string>Open the MultiMC Patreon page.</string>
</property>
<property name="statusTip">
<string>Open the MultiMC Patreon page.</string>
</property>
</action>
<action name="actionMoreNews">
<property name="icon">
<iconset theme="news">

View File

@@ -18,37 +18,111 @@
#include <QIcon>
#include "MultiMC.h"
#include "gui/Platform.h"
#include "BuildConfig.h"
#include <logic/net/NetJob.h>
// Credits
// This is a hack, but I can't think of a better way to do this easily without screwing with QTextDocument...
QString getCreditsHtml(QStringList patrons)
{
QString creditsHtml =
"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN' 'http://www.w3.org/TR/REC-html40/strict.dtd'>"
"<html>"
""
"<head>"
"<meta name='qrichtext' content='1' />"
"<style type='text/css'>"
"p { white-space: pre-wrap; margin-top:2px; margin-bottom:2px; }"
"</style>"
"</head>"
""
"<body style=' font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;'>"
""
"<h3>MultiMC Developers</h3>"
"<p>Andrew Okin &lt;<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>&gt;</p>"
"<p>Petr Mrázek &lt;<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>&gt;</p>"
"<p>Sky &lt;<a href='https://www.twitter.com/drayshak'>@drayshak</a>&gt;</p>"
"<p>Jan (02JanDal) &lt;<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>&gt;</p>"
""
"<h3>With thanks to</h3>"
"<p>Orochimarufan &lt;<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>&gt;</p>"
"<p>TakSuyu &lt;<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>&gt;</p>"
"<p>Kilobyte &lt;<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>&gt;</p>"
"<p>Robotbrain &lt;<a href='https://twitter.com/skylordelros'>@skylordelros</a>&gt;</p>"
"<p>Rootbear75 &lt;<a href='https://twitter.com/rootbear75'>@rootbear75</a>&gt; (build server)</p>"
""
"<h3>Patreon Patrons</h3>"
"%1"
""
"</body>"
"</html>";
if (patrons.isEmpty())
return creditsHtml.arg("<p>Loading...</p>");
else
{
QString patronsStr;
for (QString patron : patrons)
{
patronsStr.append(QString("<p>%1</p>").arg(patron));
}
return creditsHtml.arg(patronsStr);
}
}
AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog)
{
MultiMCPlatform::fixWM_CLASS(this);
ui->setupUi(this);
QString chtml = getCreditsHtml(QStringList());
ui->creditsText->setHtml(chtml);
ui->urlLabel->setOpenExternalLinks(true);
ui->icon->setPixmap(QIcon(":/icons/multimc/scalable/apps/multimc.svg").pixmap(64));
ui->title->setText("MultiMC 5 " + MMC->version().toString());
ui->title->setText("MultiMC 5 " + BuildConfig.printableVersionString());
ui->versionLabel->setText(tr("Version") +": " + MMC->version().toString());
ui->vtypeLabel->setText(tr("Version Type") +": " + MMC->version().typeName());
ui->platformLabel->setText(tr("Platform") +": " + MMC->version().platform);
ui->versionLabel->setText(tr("Version") +": " + BuildConfig.printableVersionString());
ui->vtypeLabel->setText(tr("Version Type") +": " + BuildConfig.versionTypeName());
ui->platformLabel->setText(tr("Platform") +": " + BuildConfig.BUILD_PLATFORM);
if (MMC->version().build >= 0)
ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(MMC->version().build));
if (BuildConfig.VERSION_BUILD >= 0)
ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD));
else
ui->buildNumLabel->setVisible(false);
if (!MMC->version().channel.isEmpty())
ui->channelLabel->setText(tr("Channel") +": " + MMC->version().channel);
if (!BuildConfig.VERSION_CHANNEL.isEmpty())
ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL);
else
ui->channelLabel->setVisible(false);
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
MMC->connect(ui->aboutQt, SIGNAL(clicked()), SLOT(aboutQt()));
loadPatronList();
}
AboutDialog::~AboutDialog()
{
delete ui;
}
void AboutDialog::loadPatronList()
{
NetJob* job = new NetJob("Patreon Patron List");
patronListDownload = ByteArrayDownload::make(QUrl("http://files.multimc.org/patrons.txt"));
job->addNetAction(patronListDownload);
connect(job, &NetJob::succeeded, this, &AboutDialog::patronListLoaded);
job->start();
}
void AboutDialog::patronListLoaded()
{
QString patronListStr(patronListDownload->m_data);
QString html = getCreditsHtml(patronListStr.split("\n", QString::SkipEmptyParts));
ui->creditsText->setHtml(html);
}

View File

@@ -17,6 +17,8 @@
#include <QDialog>
#include <logic/net/ByteArrayDownload.h>
namespace Ui
{
class AboutDialog;
@@ -30,6 +32,16 @@ public:
explicit AboutDialog(QWidget *parent = 0);
~AboutDialog();
public
slots:
/// Starts loading a list of Patreon patrons.
void loadPatronList();
/// Slot for when the patron list loads successfully.
void patronListLoaded();
private:
Ui::AboutDialog *ui;
ByteArrayDownloadPtr patronListDownload;
};

View File

@@ -59,7 +59,7 @@
<string/>
</property>
<property name="pixmap">
<pixmap resource="../../graphics.qrc">:/icons/multimc/scalable/apps/multimc.svg</pixmap>
<pixmap resource="../../resources/multimc/multimc.qrc">:/icons/multimc/scalable/apps/multimc.svg</pixmap>
</property>
</widget>
</item>
@@ -96,7 +96,7 @@
<item>
<widget class="QToolBox" name="toolBox">
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="aboutPage">
<property name="geometry">
@@ -104,7 +104,7 @@
<x>0</x>
<y>0</y>
<width>689</width>
<height>311</height>
<height>331</height>
</rect>
</property>
<attribute name="label">
@@ -229,7 +229,7 @@
<x>0</x>
<y>0</y>
<width>689</width>
<height>311</height>
<height>331</height>
</rect>
</property>
<attribute name="label">
@@ -245,19 +245,8 @@
<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:'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;
&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;Sky &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://www.twitter.com/drayshak&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@drayshak&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;-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:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;&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:'Ubuntu'; font-size:10pt; font-weight:600;&quot;&gt;With thanks to&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;Orochimarufan &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:orochimarufan.x3@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;orochimarufan.x3@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;
&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;TakSuyu &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:taksuyu@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;taksuyu@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;
&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;Kilobyte &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:stiepen22@gmx.de&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;stiepen22@gmx.de&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;Jan (02JanDal) &amp;lt;&lt;/span&gt;&lt;a href=&quot;mailto:02jandal@gmail.com&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;02jandal@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;
&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;Robotbrain &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/skylordelros&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@skylordelros&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;Rootbear75 &amp;lt;&lt;/span&gt;&lt;a href=&quot;https://twitter.com/rootbear75&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;@rootbear75&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt;&quot;&gt;&amp;gt; (build server)&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:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&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;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
@@ -282,7 +271,7 @@ p, li { white-space: pre-wrap; }
<x>0</x>
<y>0</y>
<width>689</width>
<height>311</height>
<height>331</height>
</rect>
</property>
<attribute name="label">
@@ -309,7 +298,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:11pt; 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:9pt; 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;
@@ -433,7 +422,7 @@ 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;-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;-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: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-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;
@@ -471,7 +460,7 @@ p, li { white-space: pre-wrap; }
<x>0</x>
<y>0</y>
<width>689</width>
<height>311</height>
<height>331</height>
</rect>
</property>
<attribute name="label">
@@ -484,12 +473,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:'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>
&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>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
@@ -537,7 +526,7 @@ p, li { white-space: pre-wrap; }
</layout>
</widget>
<resources>
<include location="../../graphics.qrc"/>
<include location="../../resources/multimc/multimc.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -26,6 +26,7 @@
#include <gui/dialogs/EditAccountDialog.h>
#include <gui/dialogs/ProgressDialog.h>
#include <gui/dialogs/AccountSelectDialog.h>
#include <gui/dialogs/LoginDialog.h>
#include "CustomMessageBox.h"
#include <logic/tasks/Task.h>
#include <logic/auth/YggdrasilTask.h>
@@ -45,10 +46,11 @@ AccountListDialog::AccountListDialog(QWidget *parent)
// Expand the account column
ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
QItemSelectionModel* selectionModel = ui->listView->selectionModel();
QItemSelectionModel *selectionModel = ui->listView->selectionModel();
connect(selectionModel, &QItemSelectionModel::selectionChanged,
[this] (const QItemSelection& sel, const QItemSelection& dsel) { updateButtonStates(); });
connect(selectionModel, &QItemSelectionModel::selectionChanged,
[this](const QItemSelection &sel, const QItemSelection &dsel)
{ updateButtonStates(); });
connect(m_accounts.get(), SIGNAL(listChanged()), SLOT(listChanged()));
connect(m_accounts.get(), SIGNAL(activeAccountChanged()), SLOT(listChanged()));
@@ -68,7 +70,8 @@ void AccountListDialog::listChanged()
void AccountListDialog::on_addAccountBtn_clicked()
{
addAccount(tr("Please enter your Mojang or Minecraft account username and password to add your account."));
addAccount(tr("Please enter your Mojang or Minecraft account username and password to add "
"your account."));
}
void AccountListDialog::on_rmAccountBtn_clicked()
@@ -87,7 +90,8 @@ void AccountListDialog::on_setDefaultBtn_clicked()
if (selection.size() > 0)
{
QModelIndex selected = selection.first();
MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>();
MojangAccountPtr account =
selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>();
m_accounts->setActiveAccount(account->username());
}
}
@@ -113,48 +117,29 @@ void AccountListDialog::updateButtonStates()
ui->noDefaultBtn->setDown(m_accounts->activeAccount().get() == nullptr);
}
void AccountListDialog::addAccount(const QString& errMsg)
void AccountListDialog::addAccount(const QString &errMsg)
{
// TODO: We can use the login dialog for this for now, but we'll have to make something better for it eventually.
EditAccountDialog loginDialog(errMsg, this, EditAccountDialog::UsernameField | EditAccountDialog::PasswordField);
loginDialog.exec();
// TODO: The login dialog isn't quite done yet
MojangAccountPtr account = LoginDialog::newAccount(this, errMsg);
if (loginDialog.result() == QDialog::Accepted)
if (account != nullptr)
{
QString username(loginDialog.username());
QString password(loginDialog.password());
m_accounts->addAccount(account);
if (m_accounts->count() == 1)
m_accounts->setActiveAccount(account->username());
MojangAccountPtr account = MojangAccount::createFromUsername(username);
ProgressDialog progDialog(this);
auto task = account->login(nullptr, password);
progDialog.exec(task.get());
if(task->successful())
// Grab associated player skins
auto job = new NetJob("Player skins: " + account->username());
for (AccountProfile profile : account->profiles())
{
m_accounts->addAccount(account);
if (m_accounts->count() == 1)
m_accounts->setActiveAccount(account->username());
// Grab associated player skins
auto job = new NetJob("Player skins: " + account->username());
for(AccountProfile profile : account->profiles())
{
auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png");
auto action = CacheDownload::make(
QUrl("http://" + URLConstants::SKINS_BASE + profile.name + ".png"),
meta);
job->addNetAction(action);
meta->stale = true;
}
job->start();
}
else
{
auto reason = task->failReason();
auto dlg = CustomMessageBox::selectable(this, tr("Login error."), reason, QMessageBox::Critical);
dlg->exec();
delete dlg;
auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png");
auto action = CacheDownload::make(
QUrl("http://" + URLConstants::SKINS_BASE + profile.name + ".png"), meta);
job->addNetAction(action);
meta->stale = true;
}
job->start();
}
}

View File

@@ -208,7 +208,7 @@ void LegacyModEditDialog::on_addCoreBtn_clicked()
void LegacyModEditDialog::on_addForgeBtn_clicked()
{
VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this);
vselect.setFilter(1, m_inst->intendedVersionId());
vselect.setExactFilter(1, m_inst->intendedVersionId());
if (vselect.exec() && vselect.selectedVersion())
{
ForgeVersionPtr forge =

110
gui/dialogs/LoginDialog.cpp Normal file
View File

@@ -0,0 +1,110 @@
/* Copyright 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.
*/
#include "LoginDialog.h"
#include "ui_LoginDialog.h"
#include "logic/auth/YggdrasilTask.h"
#include <QtWidgets/QPushButton>
LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LoginDialog)
{
ui->setupUi(this);
ui->progressBar->setVisible(false);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
LoginDialog::~LoginDialog()
{
delete ui;
}
// Stage 1: User interaction
void LoginDialog::accept()
{
setUserInputsEnabled(false);
ui->progressBar->setVisible(true);
// Setup the login task and start it
m_account = MojangAccount::createFromUsername(ui->userTextBox->text());
m_loginTask = m_account->login(nullptr, ui->passTextBox->text());
connect(m_loginTask.get(), &ProgressProvider::failed, this, &LoginDialog::onTaskFailed);
connect(m_loginTask.get(), &ProgressProvider::succeeded, this,
&LoginDialog::onTaskSucceeded);
connect(m_loginTask.get(), &ProgressProvider::status, this, &LoginDialog::onTaskStatus);
connect(m_loginTask.get(), &ProgressProvider::progress, this, &LoginDialog::onTaskProgress);
m_loginTask->start();
}
void LoginDialog::setUserInputsEnabled(bool enable)
{
ui->userTextBox->setEnabled(enable);
ui->passTextBox->setEnabled(enable);
ui->buttonBox->setEnabled(enable);
}
// Enable the OK button only when both textboxes contain something.
void LoginDialog::on_userTextBox_textEdited(const QString &newText)
{
ui->buttonBox->button(QDialogButtonBox::Ok)
->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty());
}
void LoginDialog::on_passTextBox_textEdited(const QString &newText)
{
ui->buttonBox->button(QDialogButtonBox::Ok)
->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty());
}
void LoginDialog::onTaskFailed(const QString &reason)
{
// Set message
ui->label->setText("<span style='color:red'>" + reason + "</span>");
// Re-enable user-interaction
setUserInputsEnabled(true);
ui->progressBar->setVisible(false);
}
void LoginDialog::onTaskSucceeded()
{
QDialog::accept();
}
void LoginDialog::onTaskStatus(const QString &status)
{
ui->label->setText(status);
}
void LoginDialog::onTaskProgress(qint64 current, qint64 total)
{
ui->progressBar->setMaximum(total);
ui->progressBar->setValue(current);
}
// Public interface
MojangAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg)
{
LoginDialog dlg(parent);
dlg.ui->label->setText(msg);
if (dlg.exec() == QDialog::Accepted)
{
return dlg.m_account;
}
return 0;
}

58
gui/dialogs/LoginDialog.h Normal file
View File

@@ -0,0 +1,58 @@
/* Copyright 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.
*/
#pragma once
#include <QtWidgets/QDialog>
#include <QtCore/QEventLoop>
#include "logic/auth/MojangAccount.h"
namespace Ui
{
class LoginDialog;
}
class LoginDialog : public QDialog
{
Q_OBJECT
public:
~LoginDialog();
static MojangAccountPtr newAccount(QWidget *parent, QString message);
private:
explicit LoginDialog(QWidget *parent = 0);
void setUserInputsEnabled(bool enable);
protected
slots:
void accept();
void onTaskFailed(const QString &reason);
void onTaskSucceeded();
void onTaskStatus(const QString &status);
void onTaskProgress(qint64 current, qint64 total);
void on_userTextBox_textEdited(const QString &newText);
void on_passTextBox_textEdited(const QString &newText);
private:
Ui::LoginDialog *ui;
MojangAccountPtr m_account;
std::shared_ptr<Task> m_loginTask;
};

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LoginDialog</class>
<widget class="QDialog" name="LoginDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>162</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Add Account</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Message label placeholder.</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="userTextBox">
<property name="placeholderText">
<string>Email / Username</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passTextBox">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string>Password</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -222,7 +222,7 @@ void OneSixModEditDialog::on_forgeBtn_clicked()
reloadInstanceVersion();
}
VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this);
vselect.setFilter(1, m_inst->currentVersionId());
vselect.setExactFilter(1, m_inst->currentVersionId());
vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") +
m_inst->currentVersionId());
if (vselect.exec() && vselect.selectedVersion())
@@ -258,7 +258,7 @@ void OneSixModEditDialog::on_liteloaderBtn_clicked()
}
VersionSelectDialog vselect(MMC->liteloaderlist().get(), tr("Select LiteLoader version"),
this);
vselect.setFilter(1, m_inst->currentVersionId());
vselect.setExactFilter(1, m_inst->currentVersionId());
vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") +
m_inst->currentVersionId());
if (vselect.exec() && vselect.selectedVersion())

View File

@@ -407,8 +407,8 @@ 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))
foreach(const QString & lang, QDir(MMC->staticData() + "/translations")
.entryList(QStringList() << "*.qm", QDir::Files))
{
QLocale locale(lang.section(QRegExp("[_\.]"), 1));
ui->languageBox->addItem(QLocale::languageToString(locale.language()), locale);

View File

@@ -97,21 +97,16 @@ void VersionSelectDialog::on_refreshButton_clicked()
loadList();
}
void VersionSelectDialog::setFilter(int column, QString filter)
void VersionSelectDialog::setExactFilter(int column, QString filter)
{
m_proxyModel->setFilterKeyColumn(column);
m_proxyModel->setFilterFixedString(filter);
/*
QStringList filteredTypes;
if (!ui->filterSnapshotsCheckbox->isChecked())
filteredTypes += "Snapshot";
if (!ui->filterMCNostalgiaCheckbox->isChecked())
filteredTypes += "Nostalgia";
QString regexStr = "^.*$";
if (filteredTypes.length() > 0)
regexStr = QString("^((?!%1).)*$").arg(filteredTypes.join('|'));
QLOG_DEBUG() << "Filter:" << regexStr;
*/
// m_proxyModel->setFilterFixedString(filter);
m_proxyModel->setFilterRegExp(QRegExp(QString("^%1$").arg(filter.replace(".", "\\.")),
Qt::CaseInsensitive, QRegExp::RegExp));
}
void VersionSelectDialog::setFuzzyFilter(int column, QString filter)
{
m_proxyModel->setFilterKeyColumn(column);
m_proxyModel->setFilterWildcard(filter);
}

View File

@@ -43,7 +43,8 @@ public:
BaseVersionPtr selectedVersion() const;
void setFilter(int column, QString filter);
void setFuzzyFilter(int column, QString filter);
void setExactFilter(int column, QString filter);
void setEmptyString(QString emptyString);
void setResizeOn(int column);

30
gui/widgets/IconLabel.cpp Normal file
View File

@@ -0,0 +1,30 @@
#include "IconLabel.h"
#include <QStyle>
#include <QStyleOption>
#include <QLayout>
#include <QPainter>
#include <QRect>
IconLabel::IconLabel(QWidget *parent, QIcon icon, QSize size)
: QWidget(parent), m_icon(icon), m_size(size)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
}
QSize IconLabel::sizeHint() const
{
return m_size;
}
void IconLabel::setIcon(QIcon icon)
{
m_icon = icon;
update();
}
void IconLabel::paintEvent(QPaintEvent *)
{
QPainter p(this);
m_icon.paint(&p, contentsRect());
}

26
gui/widgets/IconLabel.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <QWidget>
#include <QIcon>
class QStyleOption;
/**
* This is a trivial widget that paints a QIcon of the specified size.
*/
class IconLabel : public QWidget
{
Q_OBJECT
public:
/// Create a line separator. orientation is the orientation of the line.
explicit IconLabel(QWidget *parent, QIcon icon, QSize size);
virtual QSize sizeHint() const;
virtual void paintEvent(QPaintEvent *);
void setIcon(QIcon icon);
private:
QSize m_size;
QIcon m_icon;
};

View File

@@ -0,0 +1,37 @@
#include "LineSeparator.h"
#include <QStyle>
#include <QStyleOption>
#include <QLayout>
#include <QPainter>
void LineSeparator::initStyleOption(QStyleOption *option) const
{
option->initFrom(this);
// in a horizontal layout, the line is vertical (and vice versa)
if (m_orientation == Qt::Vertical)
option->state |= QStyle::State_Horizontal;
}
LineSeparator::LineSeparator(QWidget *parent, Qt::Orientation orientation)
: QWidget(parent), m_orientation(orientation)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
}
QSize LineSeparator::sizeHint() const
{
QStyleOption opt;
initStyleOption(&opt);
const int extent =
style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, &opt, parentWidget());
return QSize(extent, extent);
}
void LineSeparator::paintEvent(QPaintEvent *)
{
QPainter p(this);
QStyleOption opt;
initStyleOption(&opt);
style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, parentWidget());
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include <QWidget>
class QStyleOption;
class LineSeparator : public QWidget
{
Q_OBJECT
public:
/// Create a line separator. orientation is the orientation of the line.
explicit LineSeparator(QWidget *parent, Qt::Orientation orientation = Qt::Vertical);
QSize sizeHint() const;
void paintEvent(QPaintEvent *);
void initStyleOption(QStyleOption *option) const;
private:
Qt::Orientation m_orientation = Qt::Vertical;
};

View File

@@ -0,0 +1,115 @@
#include "ServerStatus.h"
#include "LineSeparator.h"
#include "IconLabel.h"
#include "logic/status/StatusChecker.h"
#include "MultiMC.h"
#include <QHBoxLayout>
#include <QFrame>
#include <QLabel>
#include <QMap>
#include <QToolButton>
#include <QAction>
ServerStatus::ServerStatus(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f)
{
layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
goodIcon = QIcon::fromTheme("status-good");
badIcon = QIcon::fromTheme("status-bad");
addStatus("minecraft.net", tr("Web"));
addLine();
addStatus("account.mojang.com", tr("Account"));
addLine();
addStatus("skins.minecraft.net", tr("Skins"));
addLine();
addStatus("authserver.mojang.com", tr("Auth"));
addLine();
addStatus("sessionserver.mojang.com", tr("Session"));
m_statusRefresh = new QToolButton(this);
m_statusRefresh->setCheckable(true);
m_statusRefresh->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_statusRefresh->setIcon(QIcon::fromTheme("refresh"));
layout->addWidget(m_statusRefresh);
setLayout(layout);
// Start status checker
{
auto reloader = MMC->statusChecker().get();
connect(reloader, &StatusChecker::statusChanged, this, &ServerStatus::StatusChanged);
connect(reloader, &StatusChecker::statusLoading, this, &ServerStatus::StatusReloading);
connect(m_statusRefresh, &QAbstractButton::clicked, this, &ServerStatus::reloadStatus);
MMC->statusChecker()->startTimer(60000);
reloadStatus();
}
}
ServerStatus::~ServerStatus()
{
}
void ServerStatus::reloadStatus()
{
MMC->statusChecker()->reloadStatus();
}
void ServerStatus::addLine()
{
layout->addWidget(new LineSeparator(this));
}
void ServerStatus::addStatus(QString key, QString name)
{
{
auto label = new IconLabel(this, badIcon, QSize(16, 16));
label->setToolTip(key);
serverLabels[key] = label;
layout->addWidget(label);
}
{
auto label = new QLabel(this);
label->setText(name);
label->setToolTip(key);
layout->addWidget(label);
}
}
void ServerStatus::setStatus(QString key, bool value)
{
if (!serverLabels.contains(key))
return;
IconLabel *label = serverLabels[key];
label->setIcon(value ? goodIcon : badIcon);
}
void ServerStatus::StatusChanged(const QMap<QString, QString> statusEntries)
{
auto convertStatus = [&](QString status)->bool
{
if (status == "green")
return true;
else if (status == "yellow")
return false;
else if (status == "red")
return false;
return false;
}
;
auto iter = statusEntries.begin();
while (iter != statusEntries.end())
{
QString key = iter.key();
bool value = convertStatus(iter.value());
setStatus(key, value);
iter++;
}
}
void ServerStatus::StatusReloading(bool is_reloading)
{
m_statusRefresh->setChecked(is_reloading);
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <QString>
#include <QWidget>
#include <QMap>
#include <QIcon>
#include <memory>
class IconLabel;
class QToolButton;
class QHBoxLayout;
class ServerStatus: public QWidget
{
Q_OBJECT
public:
explicit ServerStatus(QWidget *parent = nullptr, Qt::WindowFlags f = 0);
virtual ~ServerStatus();
;
public slots:
void reloadStatus();
void StatusChanged(const QMap<QString, QString> statuses);
void StatusReloading(bool is_reloading);
private: /* methods */
void addLine();
void addStatus(QString key, QString name);
void setStatus(QString key, bool value);
private: /* data */
QHBoxLayout * layout = nullptr;
QToolButton *m_statusRefresh = nullptr;
QMap<QString, IconLabel *> serverLabels;
QIcon goodIcon;
QIcon badIcon;
};

View File

@@ -1,4 +1,6 @@
FILE(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@")
set(CMAKE_MODULE_PATH "@CMAKE_MODULE_PATH@")
file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@")
function(gp_resolved_file_type_override resolved_file type_var)
if(resolved_file MATCHES "^/usr/lib/libQt")
message("resolving ${resolved_file} as other")
@@ -6,12 +8,15 @@ function(gp_resolved_file_type_override resolved_file type_var)
elseif(resolved_file MATCHES "^/usr/lib(.+)?/libxcb")
message("resolving ${resolved_file} as other")
set(${type_var} other PARENT_SCOPE)
endif()
elseif(resolved_file MATCHES "^/usr/lib(.+)?/libicu")
message("resolving ${resolved_file} as other")
set(${type_var} other PARENT_SCOPE)
endif()
endfunction()
set(gp_tool "@CMAKE_GP_TOOL@")
set(gp_cmd_paths ${gp_cmd_paths}
"@CMAKE_GP_CMD_PATHS@"
"@CMAKE_GP_CMD_PATHS@"
)
include(BundleUtilities)

View File

@@ -163,7 +163,7 @@ public:
virtual std::shared_ptr<Task> doUpdate() = 0;
/// returns a valid minecraft process, ready for launch with the given account.
virtual MinecraftProcess *prepareForLaunch(AuthSessionPtr account) = 0;
virtual bool prepareForLaunch(AuthSessionPtr account, QString & launchScript) = 0;
/// do any necessary cleanups after the instance finishes. also runs before
/// 'prepareForLaunch'

View File

@@ -50,16 +50,13 @@ std::shared_ptr<Task> LegacyInstance::doUpdate()
return std::shared_ptr<Task>(new LegacyUpdate(this, this));
}
MinecraftProcess *LegacyInstance::prepareForLaunch(AuthSessionPtr account)
bool LegacyInstance::prepareForLaunch(AuthSessionPtr account, QString & launchScript)
{
MinecraftProcess *proc = new MinecraftProcess(this);
QIcon icon = MMC->icons()->getIcon(iconKey());
auto pixmap = icon.pixmap(128, 128);
pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG");
// create the launch script
QString launchScript;
{
// window size
QString windowParams;
@@ -79,12 +76,7 @@ MinecraftProcess *LegacyInstance::prepareForLaunch(AuthSessionPtr account)
launchScript += "lwjgl " + lwjgl + "\n";
launchScript += "launcher legacy\n";
}
proc->setLaunchScript(launchScript);
// set the process work path
proc->setWorkdir(minecraftRoot());
return proc;
return true;
}
void LegacyInstance::cleanupAfterRun()
@@ -159,6 +151,11 @@ QString LegacyInstance::binDir() const
return PathCombine(minecraftRoot(), "bin");
}
QString LegacyInstance::libDir() const
{
return PathCombine(minecraftRoot(), "lib");
}
QString LegacyInstance::savesDir() const
{
return PathCombine(minecraftRoot(), "saves");

View File

@@ -41,6 +41,7 @@ public:
std::shared_ptr<ModList> texturePackList();
////// Directories //////
QString libDir() const;
QString savesDir() const;
QString texturePacksDir() const;
QString jarModsDir() const;
@@ -78,7 +79,7 @@ public:
virtual void setShouldUpdate(bool val) override;
virtual std::shared_ptr<Task> doUpdate() override;
virtual MinecraftProcess *prepareForLaunch(AuthSessionPtr account) override;
virtual bool prepareForLaunch(AuthSessionPtr account, QString & launchScript) override;
virtual void cleanupAfterRun() override;
virtual QDialog *createModEditDialog(QWidget *parent) override;

View File

@@ -26,9 +26,56 @@
#include <JlCompress.h>
#include "logger/QsLog.h"
#include "logic/net/URLConstants.h"
#include <QStringList>
LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
{
// 1.3 - 1.3.2
auto libs13 = QList<FMLlib>{
{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false},
{"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false},
{"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}};
fmlLibsMapping["1.3.2"] = libs13;
auto libs14 = QList<FMLlib>{
{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false},
{"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false},
{"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false},
{"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}};
fmlLibsMapping["1.4"] = libs14;
fmlLibsMapping["1.4.1"] = libs14;
fmlLibsMapping["1.4.2"] = libs14;
fmlLibsMapping["1.4.3"] = libs14;
fmlLibsMapping["1.4.4"] = libs14;
fmlLibsMapping["1.4.5"] = libs14;
fmlLibsMapping["1.4.6"] = libs14;
fmlLibsMapping["1.4.7"] = libs14;
fmlLibsMapping["1.5"] = QList<FMLlib>{
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
{"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false},
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
fmlLibsMapping["1.5.1"] = QList<FMLlib>{
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
{"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false},
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
fmlLibsMapping["1.5.2"] = QList<FMLlib>{
{"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
{"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
{"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
{"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
{"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false},
{"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
}
void LegacyUpdate::executeTask()
@@ -50,10 +97,130 @@ void LegacyUpdate::executeTask()
else
{
*/
lwjglStart();
fmllibsStart();
//}
}
void LegacyUpdate::fmllibsStart()
{
// Get the mod list
LegacyInstance *inst = (LegacyInstance *)m_inst;
auto modList = inst->jarModList();
bool forge_present = false;
QString version = inst->intendedVersionId();
if (!fmlLibsMapping.contains(version))
{
lwjglStart();
return;
}
auto &libList = fmlLibsMapping[version];
// determine if we need some libs for FML or forge
setStatus(tr("Checking for FML libraries..."));
for (unsigned i = 0; i < modList->size(); i++)
{
auto &mod = modList->operator[](i);
// do not use disabled mods.
if (!mod.enabled())
continue;
if (mod.type() != Mod::MOD_ZIPFILE)
continue;
if (mod.mmc_id().contains("forge", Qt::CaseInsensitive))
{
forge_present = true;
break;
}
if (mod.mmc_id().contains("fml", Qt::CaseInsensitive))
{
forge_present = true;
break;
}
}
// we don't...
if (!forge_present)
{
lwjglStart();
return;
}
// now check the lib folder inside the instance for files.
for (auto &lib : libList)
{
QFileInfo libInfo(PathCombine(inst->libDir(), lib.name));
if (libInfo.exists())
continue;
fmlLibsToProcess.append(lib);
}
// if everything is in place, there's nothing to do here...
if (fmlLibsToProcess.isEmpty())
{
lwjglStart();
return;
}
// download missing libs to our place
setStatus(tr("Dowloading FML libraries..."));
auto dljob = new NetJob("FML libraries");
auto metacache = MMC->metacache();
for (auto &lib : fmlLibsToProcess)
{
auto entry = metacache->resolveEntry("fmllibs", lib.name);
QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.name
: URLConstants::FMLLIBS_FORGE_BASE_URL + lib.name;
dljob->addNetAction(CacheDownload::make(QUrl(urlString), entry));
}
connect(dljob, SIGNAL(succeeded()), SLOT(fmllibsFinished()));
connect(dljob, SIGNAL(failed()), SLOT(fmllibsFailed()));
connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
legacyDownloadJob.reset(dljob);
legacyDownloadJob->start();
}
void LegacyUpdate::fmllibsFinished()
{
legacyDownloadJob.reset();
if(!fmlLibsToProcess.isEmpty())
{
setStatus(tr("Copying FML libraries into the instance..."));
LegacyInstance *inst = (LegacyInstance *)m_inst;
auto metacache = MMC->metacache();
int index = 0;
for (auto &lib : fmlLibsToProcess)
{
progress(index, fmlLibsToProcess.size());
auto entry = metacache->resolveEntry("fmllibs", lib.name);
auto path = PathCombine(inst->libDir(), lib.name);
if(!ensureFilePathExists(path))
{
emitFailed(tr("Failed creating FML library folder inside the instance."));
return;
}
if (!QFile::copy(entry->getFullPath(), PathCombine(inst->libDir(), lib.name)))
{
emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.name));
return;
}
index++;
}
progress(index, fmlLibsToProcess.size());
}
lwjglStart();
}
void LegacyUpdate::fmllibsFailed()
{
emitFailed("Game update failed: it was impossible to fetch the required FML libraries.");
return;
}
void LegacyUpdate::lwjglStart()
{
LegacyInstance *inst = (LegacyInstance *)m_inst;

View File

@@ -27,6 +27,13 @@ class BaseInstance;
class QuaZip;
class Mod;
struct FMLlib
{
QString name;
QString checksum;
bool ours;
};
class LegacyUpdate : public Task
{
Q_OBJECT
@@ -44,6 +51,10 @@ slots:
void jarFinished();
void jarFailed();
void fmllibsStart();
void fmllibsFinished();
void fmllibsFailed();
void extractLwjgl();
void ModTheJar();
@@ -72,4 +83,6 @@ private:
private:
NetJobPtr legacyDownloadJob;
BaseInstance *m_inst = nullptr;
QList<FMLlib> fmlLibsToProcess;
QMap<QString, QList<FMLlib>> fmlLibsMapping;
};

View File

@@ -15,6 +15,7 @@
* limitations under the License.
*/
#include "MultiMC.h"
#include "BuildConfig.h"
#include "MinecraftProcess.h"
@@ -33,7 +34,7 @@
#define IBUS "@im=ibus"
// constructor
MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst)
MinecraftProcess::MinecraftProcess(InstancePtr inst) : m_instance(inst)
{
connect(this, SIGNAL(finished(int, QProcess::ExitStatus)),
SLOT(finish(int, QProcess::ExitStatus)));
@@ -280,6 +281,7 @@ bool MinecraftProcess::preLaunch()
m_prepostlaunchprocess.start(prelaunch_cmd);
if (!waitForPrePost())
{
emit log(tr("The command failed to start"), MessageLevel::Fatal);
return false;
}
// Flush console window
@@ -352,7 +354,8 @@ bool MinecraftProcess::postLaunch()
bool MinecraftProcess::waitForPrePost()
{
m_prepostlaunchprocess.waitForStarted();
if(!m_prepostlaunchprocess.waitForStarted())
return false;
QEventLoop eventLoop;
auto finisher = [this, &eventLoop](QProcess::ProcessState state)
{
@@ -430,11 +433,12 @@ QStringList MinecraftProcess::javaArguments() const
void MinecraftProcess::arm()
{
emit log("MultiMC version: " + MMC->version().toString() + "\n\n");
emit log("MultiMC version: " + BuildConfig.printableVersionString() + "\n\n");
emit log("Minecraft folder is:\n" + workingDirectory() + "\n\n");
if (!preLaunch())
{
emit ended(m_instance, 1, QProcess::CrashExit);
return;
}

View File

@@ -52,8 +52,13 @@ public:
* @brief MinecraftProcess constructor
* @param inst the Instance pointer to launch
*/
MinecraftProcess(BaseInstance *inst);
MinecraftProcess(InstancePtr inst);
virtual ~MinecraftProcess()
{
};
/**
* @brief start the launcher part with the provided launch script
*/
@@ -69,7 +74,7 @@ public:
*/
void abort();
BaseInstance *instance()
InstancePtr instance()
{
return m_instance;
}
@@ -97,22 +102,22 @@ signals:
/**
* @brief emitted when Minecraft immediately fails to run
*/
void launch_failed(BaseInstance *);
void launch_failed(InstancePtr);
/**
* @brief emitted when the PreLaunchCommand fails
*/
void prelaunch_failed(BaseInstance *, int code, QProcess::ExitStatus status);
void prelaunch_failed(InstancePtr, int code, QProcess::ExitStatus status);
/**
* @brief emitted when the PostLaunchCommand fails
*/
void postlaunch_failed(BaseInstance *, int code, QProcess::ExitStatus status);
void postlaunch_failed(InstancePtr, int code, QProcess::ExitStatus status);
/**
* @brief emitted when mc has finished and the PostLaunchCommand was run
*/
void ended(BaseInstance *, int code, QProcess::ExitStatus status);
void ended(InstancePtr, int code, QProcess::ExitStatus status);
/**
* @brief emitted when we want to log something
@@ -122,7 +127,7 @@ signals:
void log(QString text, MessageLevel::Enum level = MessageLevel::MultiMC);
protected:
BaseInstance *m_instance = nullptr;
InstancePtr m_instance;
QString m_err_leftover;
QString m_out_leftover;
QProcess m_prepostlaunchprocess;

View File

@@ -164,6 +164,7 @@ void Mod::ReadMCModInfo(QByteArray contents)
m_name = firstObj.value("name").toString();
m_version = firstObj.value("version").toString();
m_homeurl = firstObj.value("url").toString();
m_updateurl = firstObj.value("updateUrl").toString();
m_homeurl = m_homeurl.trimmed();
if(!m_homeurl.isEmpty())
{
@@ -203,6 +204,8 @@ void Mod::ReadMCModInfo(QByteArray contents)
else if (jsonDoc.isObject())
{
auto val = jsonDoc.object().value("modinfoversion");
if(val.isUndefined())
val = jsonDoc.object().value("modListVersion");
int version = val.toDouble();
if (version != 2)
{
@@ -211,6 +214,8 @@ void Mod::ReadMCModInfo(QByteArray contents)
return;
}
auto arrVal = jsonDoc.object().value("modlist");
if(arrVal.isUndefined())
arrVal = jsonDoc.object().value("modList");
if (arrVal.isArray())
{
getInfoFromArray(arrVal.toArray());

View File

@@ -121,6 +121,7 @@ protected:
QString m_version;
QString m_mcversion;
QString m_homeurl;
QString m_updateurl;
QString m_description;
QString m_authors;
QString m_credits;

View File

@@ -189,7 +189,7 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session)
return parts;
}
MinecraftProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session)
bool OneSixInstance::prepareForLaunch(AuthSessionPtr account, QString &launchScript)
{
I_D(OneSixInstance);
@@ -200,7 +200,6 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session)
auto version = d->version;
if (!version)
return nullptr;
QString launchScript;
{
auto libs = version->getActiveNormalLibs();
for (auto lib : libs)
@@ -212,7 +211,7 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session)
}
launchScript += "mainClass " + version->mainClass + "\n";
for (auto param : processMinecraftArgs(session))
for (auto param : processMinecraftArgs(account))
{
launchScript += "param " + param + "\n";
}
@@ -240,13 +239,7 @@ MinecraftProcess *OneSixInstance::prepareForLaunch(AuthSessionPtr session)
}
launchScript += "natives " + natives_dir.absolutePath() + "\n";
launchScript += "launcher onesix\n";
// create the process and set its parameters
MinecraftProcess *proc = new MinecraftProcess(this);
proc->setWorkdir(minecraftRoot());
proc->setLaunchScript(launchScript);
// proc->setNativeFolder(natives_dir.absolutePath());
return proc;
return true;
}
void OneSixInstance::cleanupAfterRun()

View File

@@ -40,7 +40,7 @@ public:
virtual QString instanceConfigFolder() const override;
virtual std::shared_ptr<Task> doUpdate() override;
virtual MinecraftProcess *prepareForLaunch(AuthSessionPtr session) override;
virtual bool prepareForLaunch(AuthSessionPtr account, QString & launchScript) override;
virtual void cleanupAfterRun() override;

View File

@@ -140,33 +140,33 @@ QString OneSixLibrary::hint() const
return m_hint;
}
bool OneSixLibrary::filesExist()
QStringList OneSixLibrary::files()
{
QStringList retval;
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;
}
retval.append(cooked_storage);
cooked_storage = storage;
cooked_storage.replace("${arch}", "64");
QFileInfo info64(PathCombine("libraries", cooked_storage));
if (!info64.exists())
{
return false;
}
retval.append(cooked_storage);
}
else
retval.append(storage);
return retval;
}
bool OneSixLibrary::filesExist(const QDir &base)
{
auto libFiles = files();
for(auto file: libFiles)
{
QFileInfo info(PathCombine("libraries", storage));
QFileInfo info(base, file);
QLOG_WARN() << info.absoluteFilePath() << "doesn't exist";
if (!info.exists())
{
return false;
}
}
return true;
}

View File

@@ -19,6 +19,7 @@
#include <QStringList>
#include <QMap>
#include <QJsonObject>
#include <QDir>
#include <memory>
#include "logic/net/URLConstants.h"
@@ -142,5 +143,6 @@ public:
QString hint() const;
bool extractTo(QString target_dir);
bool filesExist();
bool filesExist(const QDir &base);
QStringList files();
};

View File

@@ -268,10 +268,16 @@ void OneSixUpdate::jarlibStart()
auto metacache = MMC->metacache();
QList<ForgeXzDownloadPtr> ForgeLibs;
QList<std::shared_ptr<OneSixLibrary>> brokenLocalLibs;
for (auto lib : libs)
{
if (lib->hint() == "local")
{
if(!lib->filesExist(m_inst->librariesPath()))
brokenLocalLibs.append(lib);
continue;
}
QString raw_storage = lib->storagePath();
QString raw_dl = lib->downloadUrl();
@@ -305,6 +311,18 @@ void OneSixUpdate::jarlibStart()
f(raw_storage, raw_dl);
}
}
if(!brokenLocalLibs.empty())
{
jarlibDownloadJob.reset();
QStringList failed;
for(auto brokenLib : brokenLocalLibs)
{
failed.append(brokenLib->files());
}
QString failed_all = failed.join("\n");
emitFailed(tr("Some libraries marked as 'local' are missing their jar files:\n%1\n\nYou'll have to correct this problem manually. If this is an externally tracked instance, make sure to run it at least once outside of MultiMC.").arg(failed_all));
return;
}
// 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";
if (!ForgeLibs.empty())

View File

@@ -294,15 +294,19 @@ OneSixLibraryPtr VersionFile::createLibrary(RawLibraryPtr lib)
int VersionFile::findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle)
{
int retval = -1;
for (int i = 0; i < haystack.size(); ++i)
{
if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix)
.indexIn(haystack.at(i)->rawName()) != -1)
QString chunk = haystack.at(i)->rawName();
if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1)
{
return i;
// only one is allowed.
if(retval != -1)
return -1;
retval = i;
}
}
return -1;
return retval;
}
void VersionFile::applyTo(VersionFinal *version)
@@ -394,7 +398,7 @@ void VersionFile::applyTo(VersionFinal *version)
{
case RawLibrary::Apply:
{
// QLOG_INFO() << "Applying lib " << lib->name;
int index = findLibrary(version->libraries, lib->name);
if (index >= 0)
{
@@ -438,7 +442,7 @@ void VersionFile::applyTo(VersionFinal *version)
case RawLibrary::Append:
case RawLibrary::Prepend:
{
// QLOG_INFO() << "Adding lib " << lib->name;
const int startOfVersion = lib->name.lastIndexOf(':') + 1;
const int index = findLibrary(
version->libraries, QString(lib->name).replace(startOfVersion, INT_MAX, '*'));
@@ -507,14 +511,23 @@ void VersionFile::applyTo(VersionFinal *version)
}
case RawLibrary::Replace:
{
int index = findLibrary(version->libraries, lib->insertData);
QString toReplace;
if(lib->insertData.isEmpty())
{
const int startOfVersion = lib->name.lastIndexOf(':') + 1;
toReplace = QString(lib->name).replace(startOfVersion, INT_MAX, '*');
}
else
toReplace = lib->insertData;
// QLOG_INFO() << "Replacing lib " << toReplace << " with " << lib->name;
int index = findLibrary(version->libraries, toReplace);
if (index >= 0)
{
version->libraries.replace(index, createLibrary(lib));
}
else
{
QLOG_WARN() << "Couldn't find" << lib->insertData << "(skipping)";
QLOG_WARN() << "Couldn't find" << toReplace << "(skipping)";
}
break;
}
@@ -525,6 +538,7 @@ void VersionFile::applyTo(VersionFinal *version)
int index = findLibrary(version->libraries, lib);
if (index >= 0)
{
// QLOG_INFO() << "Removing lib " << lib;
version->libraries.removeAt(index);
}
else

View File

@@ -345,6 +345,13 @@ void VersionFinal::reapply(const bool alreadyReseting)
void VersionFinal::finalize()
{
// HACK: deny april fools. my head hurts enough already.
QDate now = QDate::currentDate();
bool isAprilFools = now.month() == 4 && now.day() == 1;
if (assets.endsWith("_af") && !isAprilFools)
{
assets = assets.left(assets.length() - 3);
}
if (assets.isEmpty())
{
assets = "legacy";

View File

@@ -220,7 +220,7 @@ void MojangAccount::authFailed(QString reason)
auto session = m_currentTask->getAssignedSession();
// 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 (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT)
{
if (session)
{

View File

@@ -29,11 +29,12 @@
YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent)
: Task(parent), m_account(account)
{
changeState(STATE_CREATED);
}
void YggdrasilTask::executeTask()
{
setStatus(getStateMessage(STATE_SENDING_REQUEST));
changeState(STATE_SENDING_REQUEST);
// Get the content of the request we're going to send to the server.
QJsonDocument doc(getRequestContent());
@@ -73,12 +74,16 @@ void YggdrasilTask::heartbeat()
void YggdrasilTask::abort()
{
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = YggdrasilTask::BY_USER;
m_netReply->abort();
}
void YggdrasilTask::abortByTimeout()
{
progress(timeout_max, timeout_max);
// TODO: actually use this in a meaningful way
m_aborted = YggdrasilTask::BY_TIMEOUT;
m_netReply->abort();
}
@@ -96,11 +101,21 @@ void YggdrasilTask::sslErrors(QList<QSslError> errors)
void YggdrasilTask::processReply()
{
setStatus(getStateMessage(STATE_PROCESSING_RESPONSE));
changeState(STATE_PROCESSING_RESPONSE);
if (m_netReply->error() == QNetworkReply::SslHandshakeFailedError)
switch (m_netReply->error())
{
emitFailed(
case QNetworkReply::NoError:
break;
case QNetworkReply::TimeoutError:
changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out."));
return;
case QNetworkReply::OperationCanceledError:
changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
return;
case QNetworkReply::SslHandshakeFailedError:
changeState(
STATE_FAILED_SOFT,
tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
"<ul>"
"<li>You use Windows XP and need to <a "
@@ -111,16 +126,13 @@ void YggdrasilTask::processReply()
"<li>Possibly something else. Check the MultiMC log file for details</li>"
"</ul>"));
return;
}
// any network errors lead to offline mode right now
if (m_netReply->error() >= QNetworkReply::ConnectionRefusedError &&
m_netReply->error() <= QNetworkReply::UnknownNetworkError)
{
// WARNING/FIXME: the value here is used in MojangAccount to detect the cancel/timeout
emitFailed("Yggdrasil task cancelled.");
QLOG_ERROR() << "Yggdrasil task cancelled because of: " << m_netReply->error() << " : "
<< m_netReply->errorString();
// used for invalid credentials and similar errors. Fall through.
case QNetworkReply::ContentOperationNotPermittedError:
break;
default:
changeState(STATE_FAILED_SOFT,
tr("Authentication operation failed due to a network error: %1 (%2)")
.arg(m_netReply->errorString()).arg(m_netReply->error()));
return;
}
@@ -140,22 +152,16 @@ void YggdrasilTask::processReply()
// pass an empty json object to the processResponse function.
if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0)
{
if (processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()))
{
emitSucceeded();
return;
}
// errors happened anyway?
emitFailed(m_error ? m_error->m_errorMessageVerbose
: tr("An unknown error occurred when processing the response "
"from the authentication server."));
processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
return;
}
else
{
emitFailed(tr("Failed to parse Yggdrasil JSON response: %1 at offset %2.")
.arg(jsonError.errorString())
.arg(jsonError.offset));
changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response "
"JSON response: %1 at offset %2.")
.arg(jsonError.errorString())
.arg(jsonError.offset));
QLOG_ERROR() << replyData;
}
return;
}
@@ -171,20 +177,21 @@ void YggdrasilTask::processReply()
// stuff there.
QLOG_DEBUG() << "The request failed, but the server gave us an error message. "
"Processing error.";
emitFailed(processError(doc.object()));
processError(doc.object());
}
else
{
// The server didn't say anything regarding the error. Give the user an unknown
// error.
QLOG_DEBUG() << "The request failed and the server gave no error message. "
"Unknown error.";
emitFailed(tr("An unknown error occurred when trying to communicate with the "
"authentication server: %1").arg(m_netReply->errorString()));
QLOG_DEBUG()
<< "The request failed and the server gave no error message. Unknown error.";
changeState(STATE_FAILED_SOFT,
tr("An unknown error occurred when trying to communicate with the "
"authentication server: %1").arg(m_netReply->errorString()));
}
}
QString YggdrasilTask::processError(QJsonObject responseData)
void YggdrasilTask::processError(QJsonObject responseData)
{
QJsonValue errorVal = responseData.value("error");
QJsonValue errorMessageValue = responseData.value("errorMessage");
@@ -194,24 +201,51 @@ QString YggdrasilTask::processError(QJsonObject responseData)
{
m_error = std::shared_ptr<Error>(new Error{
errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")});
return m_error->m_errorMessageVerbose;
changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
}
else
{
// Error is not in standard format. Don't set m_error and return unknown error.
return tr("An unknown Yggdrasil error occurred.");
changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
}
}
QString YggdrasilTask::getStateMessage(const YggdrasilTask::State state) const
QString YggdrasilTask::getStateMessage() const
{
switch (state)
switch (m_state)
{
case STATE_CREATED:
return "Waiting...";
case STATE_SENDING_REQUEST:
return tr("Sending request to auth servers...");
case STATE_PROCESSING_RESPONSE:
return tr("Processing response from servers...");
case STATE_SUCCEEDED:
return tr("Authentication task succeeded.");
case STATE_FAILED_SOFT:
return tr("Failed to contact the authentication server.");
case STATE_FAILED_HARD:
return tr("Failed to authenticate.");
default:
return tr("Processing. Please wait...");
return tr("...");
}
}
void YggdrasilTask::changeState(YggdrasilTask::State newState, QString reason)
{
m_state = newState;
setStatus(getStateMessage());
if (newState == STATE_SUCCEEDED)
{
emitSucceeded();
}
else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT)
{
emitFailed(reason);
}
}
YggdrasilTask::State YggdrasilTask::state()
{
return m_state;
}

View File

@@ -60,17 +60,28 @@ public:
QString m_cause;
};
protected:
enum AbortedBy
{
BY_NOTHING,
BY_USER,
BY_TIMEOUT
} m_aborted = BY_NOTHING;
/**
* Enum for describing the state of the current task.
* Used by the getStateMessage function to determine what the status message should be.
*/
enum State
{
STATE_CREATED,
STATE_SENDING_REQUEST,
STATE_PROCESSING_RESPONSE,
STATE_OTHER,
};
STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated
STATE_FAILED_HARD, //!< hard failure. auth is invalid
STATE_SUCCEEDED
} m_state = STATE_CREATED;
protected:
virtual void executeTask();
@@ -94,21 +105,21 @@ protected:
* Note: If the response from the server was blank, and the HTTP code was 200, this function is called with
* an empty QJsonObject.
*/
virtual bool processResponse(QJsonObject responseData) = 0;
virtual void processResponse(QJsonObject responseData) = 0;
/**
* Processes an error response received from the server.
* The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error.
* \returns a QString error message that will be passed to emitFailed.
*/
virtual QString processError(QJsonObject responseData);
virtual void processError(QJsonObject responseData);
/**
* Returns the state message for the given state.
* Used to set the status message for the task.
* Should be overridden by subclasses that want to change messages for a given state.
*/
virtual QString getStateMessage(const State state) const;
virtual QString getStateMessage() const;
protected
slots:
@@ -117,10 +128,12 @@ slots:
void heartbeat();
void sslErrors(QList<QSslError>);
void changeState(State newState, QString reason=QString());
public
slots:
virtual void abort() override;
void abortByTimeout();
State state();
protected:
// FIXME: segfault disaster waiting to happen
MojangAccount *m_account = nullptr;

View File

@@ -71,7 +71,7 @@ QJsonObject AuthenticateTask::getRequestContent() const
return req;
}
bool AuthenticateTask::processResponse(QJsonObject responseData)
void AuthenticateTask::processResponse(QJsonObject responseData)
{
// Read the response data. We need to get the client token, access token, and the selected
// profile.
@@ -84,16 +84,13 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
if (clientToken.isEmpty())
{
// Fail if the server gave us an empty client token
// TODO: Set an error properly to display to the user.
QLOG_ERROR() << "Server didn't send a client token.";
return false;
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
return;
}
if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
{
// The server changed our client token! Obey its wishes, but complain. That's what I do
// for my parents, so...
QLOG_WARN() << "Server changed our client token to '" << clientToken
<< "'. This shouldn't happen, but it isn't really a big deal.";
changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
return;
}
// Set the client token.
m_account->m_clientToken = clientToken;
@@ -104,8 +101,8 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
if (accessToken.isEmpty())
{
// Fail if the server didn't give us an access token.
// TODO: Set an error properly to display to the user.
QLOG_ERROR() << "Server didn't send an access token.";
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
return;
}
// Set the access token.
m_account->m_accessToken = accessToken;
@@ -149,16 +146,13 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
QString currentProfileId = currentProfile.value("id").toString("");
if (currentProfileId.isEmpty())
{
// TODO: Set an error to display to the user.
QLOG_ERROR() << "Server didn't specify a currently selected profile.";
return false;
changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium."));
return;
}
if (!m_account->setCurrentProfile(currentProfileId))
{
// TODO: Set an error to display to the user.
QLOG_ERROR() << "Server specified a selected profile that wasn't in the available "
"profiles list.";
return false;
changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list."));
return;
}
// this is what the vanilla launcher passes to the userProperties launch param
@@ -181,7 +175,7 @@ bool AuthenticateTask::processResponse(QJsonObject responseData)
// We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded.
QLOG_DEBUG() << "Finished reading authentication response.";
return true;
changeState(STATE_SUCCEEDED);
}
QString AuthenticateTask::getEndpoint() const
@@ -189,15 +183,15 @@ QString AuthenticateTask::getEndpoint() const
return "authenticate";
}
QString AuthenticateTask::getStateMessage(const YggdrasilTask::State state) const
QString AuthenticateTask::getStateMessage() const
{
switch (state)
switch (m_state)
{
case STATE_SENDING_REQUEST:
return tr("Authenticating: Sending request...");
case STATE_PROCESSING_RESPONSE:
return tr("Authenticating: Processing response...");
default:
return YggdrasilTask::getStateMessage(state);
return YggdrasilTask::getStateMessage();
}
}

View File

@@ -33,13 +33,13 @@ public:
AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0);
protected:
virtual QJsonObject getRequestContent() const;
virtual QJsonObject getRequestContent() const override;
virtual QString getEndpoint() const;
virtual QString getEndpoint() const override;
virtual bool processResponse(QJsonObject responseData);
virtual void processResponse(QJsonObject responseData) override;
QString getStateMessage(const YggdrasilTask::State state) const;
virtual QString getStateMessage() const override;
private:
QString m_password;

View File

@@ -60,7 +60,7 @@ QJsonObject RefreshTask::getRequestContent() const
return req;
}
bool RefreshTask::processResponse(QJsonObject responseData)
void RefreshTask::processResponse(QJsonObject responseData)
{
// Read the response data. We need to get the client token, access token, and the selected
// profile.
@@ -73,17 +73,13 @@ bool RefreshTask::processResponse(QJsonObject responseData)
if (clientToken.isEmpty())
{
// Fail if the server gave us an empty client token
// TODO: Set an error properly to display to the user.
QLOG_ERROR() << "Server didn't send a client token.";
return false;
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
return;
}
if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken)
{
// The server changed our client token! Obey its wishes, but complain. That's what I do
// for my parents, so...
QLOG_ERROR() << "Server changed our client token to '" << clientToken
<< "'. This shouldn't happen, but it isn't really a big deal.";
return false;
changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
return;
}
// Now, we set the access token.
@@ -92,9 +88,8 @@ bool RefreshTask::processResponse(QJsonObject responseData)
if (accessToken.isEmpty())
{
// Fail if the server didn't give us an access token.
// TODO: Set an error properly to display to the user.
QLOG_ERROR() << "Server didn't send an access token.";
return false;
changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
return;
}
// we validate that the server responded right. (our current profile = returned current
@@ -103,9 +98,8 @@ bool RefreshTask::processResponse(QJsonObject responseData)
QString currentProfileId = currentProfile.value("id").toString("");
if (m_account->currentProfile()->id != currentProfileId)
{
// TODO: Set an error to display to the user.
QLOG_ERROR() << "Server didn't specify the same selected profile as ours.";
return false;
changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected."));
return;
}
// this is what the vanilla launcher passes to the userProperties launch param
@@ -130,7 +124,7 @@ bool RefreshTask::processResponse(QJsonObject responseData)
QLOG_DEBUG() << "Finished reading refresh response.";
// Reset the access token.
m_account->m_accessToken = accessToken;
return true;
changeState(STATE_SUCCEEDED);
}
QString RefreshTask::getEndpoint() const
@@ -138,15 +132,15 @@ QString RefreshTask::getEndpoint() const
return "refresh";
}
QString RefreshTask::getStateMessage(const YggdrasilTask::State state) const
QString RefreshTask::getStateMessage() const
{
switch (state)
switch (m_state)
{
case STATE_SENDING_REQUEST:
return tr("Refreshing login token...");
case STATE_PROCESSING_RESPONSE:
return tr("Refreshing login token: Processing response...");
default:
return YggdrasilTask::getStateMessage(state);
return YggdrasilTask::getStateMessage();
}
}

View File

@@ -33,12 +33,12 @@ public:
RefreshTask(MojangAccount * account);
protected:
virtual QJsonObject getRequestContent() const;
virtual QJsonObject getRequestContent() const override;
virtual QString getEndpoint() const;
virtual QString getEndpoint() const override;
virtual bool processResponse(QJsonObject responseData);
virtual void processResponse(QJsonObject responseData) override;
QString getStateMessage(const YggdrasilTask::State state) const;
virtual QString getStateMessage() const override;
};

View File

@@ -38,11 +38,10 @@ QJsonObject ValidateTask::getRequestContent() const
return req;
}
bool ValidateTask::processResponse(QJsonObject responseData)
void ValidateTask::processResponse(QJsonObject responseData)
{
// Assume that if processError wasn't called, then the request was successful.
emitSucceeded();
return true;
changeState(YggdrasilTask::STATE_SUCCEEDED);
}
QString ValidateTask::getEndpoint() const
@@ -50,15 +49,15 @@ QString ValidateTask::getEndpoint() const
return "validate";
}
QString ValidateTask::getStateMessage(const YggdrasilTask::State state) const
QString ValidateTask::getStateMessage() const
{
switch (state)
switch (m_state)
{
case STATE_SENDING_REQUEST:
case YggdrasilTask::STATE_SENDING_REQUEST:
return tr("Validating access token: Sending request...");
case STATE_PROCESSING_RESPONSE:
case YggdrasilTask::STATE_PROCESSING_RESPONSE:
return tr("Validating access token: Processing response...");
default:
return YggdrasilTask::getStateMessage(state);
return YggdrasilTask::getStateMessage();
}
}

View File

@@ -35,13 +35,13 @@ public:
ValidateTask(MojangAccount *account, QObject *parent = 0);
protected:
virtual QJsonObject getRequestContent() const;
virtual QJsonObject getRequestContent() const override;
virtual QString getEndpoint() const;
virtual QString getEndpoint() const override;
virtual bool processResponse(QJsonObject responseData);
virtual void processResponse(QJsonObject responseData) override;
QString getStateMessage(const YggdrasilTask::State state) const;
virtual QString getStateMessage() const override;
private:
};

View File

@@ -255,7 +255,7 @@ void IconList::installIcons(QStringList iconFiles)
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
continue;
QString target = PathCombine("icons", fileinfo.fileName());
QString target = PathCombine(m_dir.dirName(), fileinfo.fileName());
QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")

View File

@@ -296,19 +296,18 @@ void InstanceList::loadGroupList(QMap<QString, QString> &groupMap)
QList<FTBRecord> InstanceList::discoverFTBInstances()
{
QList<FTBRecord> records;
QDir dir = QDir(MMC->settings()->get("FTBLauncherRoot").toString());
QDir dir = QDir(MMC->settings()->get("FTBLauncherDataRoot").toString());
QDir dataDir = QDir(MMC->settings()->get("FTBRoot").toString());
if (!dir.exists())
{
QLOG_INFO() << "The FTB launcher directory specified does not exist. Please check your "
"settings.";
return records;
}
else if (!dataDir.exists())
if (!dataDir.exists())
{
QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings";
return records;
}
else if (!dir.exists())
{
QLOG_INFO() << "The FTB launcher data directory specified does not exist. Please check your settings";
return records;
}
dir.cd("ModPacks");
auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name);
for (auto filename : allFiles)
@@ -337,6 +336,7 @@ QList<FTBRecord> InstanceList::discoverFTBInstances()
record.instanceDir = dataDir.absoluteFilePath(record.dirName);
record.templateDir = dir.absoluteFilePath(record.dirName);
QDir test(record.instanceDir);
QLOG_DEBUG() << dataDir.absolutePath() << record.instanceDir << record.dirName;
if (!test.exists())
continue;
record.name = attrs.value("name").toString();

View File

@@ -16,4 +16,6 @@ const QString MOJANG_STATUS_URL("http://status.mojang.com/check");
const QString MOJANG_STATUS_NEWS_URL("http://status.mojang.com/news");
const QString LITELOADER_URL("http://dl.liteloader.com/versions/versions.json");
const QString IMGUR_BASE_URL("https://api.imgur.com/3/");
const QString FMLLIBS_OUR_BASE_URL("http://files.multimc.org/fmllibs/");
const QString FMLLIBS_FORGE_BASE_URL("http://files.minecraftforge.net/fmllibs/");
}

View File

@@ -34,4 +34,6 @@ extern const QString MOJANG_STATUS_URL;
extern const QString MOJANG_STATUS_NEWS_URL;
extern const QString LITELOADER_URL;
extern const QString IMGUR_BASE_URL;
extern const QString FMLLIBS_OUR_BASE_URL;
extern const QString FMLLIBS_FORGE_BASE_URL;
}

View File

@@ -27,6 +27,12 @@ StatusChecker::StatusChecker()
}
void StatusChecker::timerEvent(QTimerEvent *e)
{
QObject::timerEvent(e);
reloadStatus();
}
void StatusChecker::reloadStatus()
{
if (isLoadingStatus())
@@ -42,13 +48,14 @@ void StatusChecker::reloadStatus()
QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished);
QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed);
m_statusNetJob.reset(job);
emit statusLoading(true);
job->start();
}
void StatusChecker::statusDownloadFinished()
{
QLOG_DEBUG() << "Finished loading status JSON.";
m_statusEntries.clear();
QByteArray data;
{
ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(m_statusNetJob->first());
@@ -121,17 +128,27 @@ QString StatusChecker::getLastLoadErrorMsg() const
void StatusChecker::succeed()
{
if(m_prevEntries != m_statusEntries)
{
emit statusChanged(m_statusEntries);
m_prevEntries = m_statusEntries;
}
m_lastLoadError = "";
QLOG_DEBUG() << "Status loading succeeded.";
m_statusNetJob.reset();
emit statusLoaded();
emit statusLoading(false);
}
void StatusChecker::fail(const QString& errorMsg)
{
if(m_prevEntries != m_statusEntries)
{
emit statusChanged(m_statusEntries);
m_prevEntries = m_statusEntries;
}
m_lastLoadError = errorMsg;
QLOG_DEBUG() << "Failed to load status:" << errorMsg;
m_statusNetJob.reset();
emit statusLoadingFailed(errorMsg);
emit statusLoading(false);
}

View File

@@ -29,26 +29,27 @@ public:
QString getLastLoadErrorMsg() const;
bool isStatusLoaded() const;
bool isLoadingStatus() const;
QMap<QString, QString> getStatusEntries() const;
void Q_SLOT reloadStatus();
protected:
virtual void timerEvent(QTimerEvent *);
signals:
void statusLoaded();
void statusLoadingFailed(QString errorMsg);
void statusLoading(bool loading);
void statusChanged(QMap<QString, QString> newStatus);
protected slots:
void statusDownloadFinished();
void statusDownloadFailed();
protected:
QMap<QString, QString> m_prevEntries;
QMap<QString, QString> m_statusEntries;
NetJobPtr m_statusNetJob;
bool m_loadedStatus;
QString m_lastLoadError;
void Q_SLOT succeed();

View File

@@ -16,6 +16,8 @@
#include "DownloadUpdateTask.h"
#include "MultiMC.h"
#include "BuildConfig.h"
#include "logic/updater/UpdateChecker.h"
#include "logic/net/NetJob.h"
#include "pathutils.h"
@@ -29,7 +31,7 @@
DownloadUpdateTask::DownloadUpdateTask(QString repoUrl, int versionId, QObject *parent)
: Task(parent)
{
m_cVersionId = MMC->version().build;
m_cVersionId = BuildConfig.VERSION_BUILD;
m_nRepoUrl = repoUrl;
m_nVersionId = versionId;
@@ -58,7 +60,7 @@ void DownloadUpdateTask::processChannels()
}
QList<UpdateChecker::ChannelListEntry> channels = checker->getChannelList();
QString channelId = MMC->version().channel;
QString channelId = BuildConfig.VERSION_CHANNEL;
m_cRepoUrl.clear();
// Search through the channel list for a channel with the correct ID.
@@ -405,17 +407,20 @@ DownloadUpdateTask::processFileLists(NetJob *job,
if (isUpdater)
{
#ifdef MultiMC_UPDATER_FORCE_LOCAL
QLOG_DEBUG() << "Skipping updater download and using local version.";
#else
auto cache_entry = MMC->metacache()->resolveEntry("root", entry.path);
QLOG_DEBUG() << "Updater will be in " << cache_entry->getFullPath();
// force check.
cache_entry->stale = true;
if(BuildConfig.UPDATER_FORCE_LOCAL)
{
QLOG_DEBUG() << "Skipping updater download and using local version.";
}
else
{
auto cache_entry = MMC->metacache()->resolveEntry("root", entry.path);
QLOG_DEBUG() << "Updater will be in " << cache_entry->getFullPath();
// force check.
cache_entry->stale = true;
auto download = CacheDownload::make(QUrl(source.url), cache_entry);
job->addNetAction(download);
#endif
auto download = CacheDownload::make(QUrl(source.url), cache_entry);
job->addNetAction(download);
}
}
else
{

View File

@@ -5,11 +5,11 @@
#include <QJsonArray>
#include "MultiMC.h"
#include "MultiMCVersion.h"
#include "BuildConfig.h"
#include "logic/net/CacheDownload.h"
NotificationChecker::NotificationChecker(QObject *parent)
: QObject(parent), m_notificationsUrl(QUrl(NOTIFICATION_URL))
: QObject(parent), m_notificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL))
{
// this will call checkForNotifications once the event loop is running
QMetaObject::invokeMethod(this, "checkForNotifications", Qt::QueuedConnection);
@@ -93,13 +93,12 @@ void NotificationChecker::downloadSucceeded(int)
bool NotificationChecker::NotificationEntry::applies() const
{
MultiMCVersion version = MMC->version();
bool channelApplies = channel.isEmpty() || channel == version.channel;
bool platformApplies = platform.isEmpty() || platform == version.platform;
bool channelApplies = channel.isEmpty() || channel == BuildConfig.VERSION_CHANNEL;
bool platformApplies = platform.isEmpty() || platform == BuildConfig.BUILD_PLATFORM;
bool fromApplies =
from.isEmpty() || from == FULL_VERSION_STR || !versionLessThan(FULL_VERSION_STR, from);
from.isEmpty() || from == BuildConfig.FULL_VERSION_STR || !versionLessThan(BuildConfig.FULL_VERSION_STR, from);
bool toApplies =
to.isEmpty() || to == FULL_VERSION_STR || !versionLessThan(to, FULL_VERSION_STR);
to.isEmpty() || to == BuildConfig.FULL_VERSION_STR || !versionLessThan(to, BuildConfig.FULL_VERSION_STR);
return channelApplies && platformApplies && fromApplies && toApplies;
}

View File

@@ -16,6 +16,7 @@
#include "UpdateChecker.h"
#include "MultiMC.h"
#include "BuildConfig.h"
#include "logger/QsLog.h"
@@ -30,7 +31,7 @@
UpdateChecker::UpdateChecker()
{
m_channelListUrl = CHANLIST_URL;
m_channelListUrl = BuildConfig.CHANLIST_URL;
m_updateChecking = false;
m_chanListLoading = false;
m_checkUpdateWaiting = false;
@@ -148,7 +149,7 @@ void UpdateChecker::updateCheckFinished(bool notifyNoUpdate)
// We've got the version with the greatest ID number. Now compare it to our current build
// number and update if they're different.
int newBuildNumber = newestVersion.value("Id").toVariant().toInt();
if (newBuildNumber != MMC->version().build)
if (newBuildNumber != BuildConfig.VERSION_BUILD)
{
QLOG_DEBUG() << "Found newer version with ID" << newBuildNumber;
// Update!

View File

@@ -1,6 +1,12 @@
#include "MultiMC.h"
#include "gui/MainWindow.h"
// Crash handling
#ifdef HANDLE_SEGV
#include <HandleCrash.h>
#endif
int main_gui(MultiMC &app)
{
// show main window
@@ -23,6 +29,11 @@ int main(int argc, char *argv[])
Q_INIT_RESOURCE(multimc);
Q_INIT_RESOURCE(backgrounds);
#ifdef HANDLE_SEGV
// Register signal handler for generating crash reports.
initBlackMagic();
#endif
switch (app.status())
{
case MultiMC::Initialized:

View File

@@ -6,18 +6,18 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
include_directories(depends)
if (WIN32)
include_directories(depends/win32cpp)
if(WIN32)
include_directories(depends/win32cpp)
# static all the things. The updater must have no dependencies, or it will fail.
if (MINGW)
if(MINGW)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc -static")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++ -static")
#set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "${CMAKE_SHARED_LIBRARY_LINK_C_FLAGS} -static-libgcc -s")
#set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "${CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS} -static-libgcc -static-libstdc++ -s")
endif()
if(MSVC)
if(MSVC)
# - Link the updater binary statically with the Visual C++ runtime
# so that the executable can function standalone.
# - Enable PDB generation for release builds
@@ -27,14 +27,14 @@ if (WIN32)
set(CMAKE_CXX_FLAGS_RELEASE "/MT /Zi /O2 /Ob2 /D NDEBUG")
set(CMAKE_C_FLAGS_RELEASE "/MT /Zi /O2 /Ob2 /D NDEBUG")
remove_definitions(-DUNICODE -D_UNICODE)
endif()
endif()
else()
# optimize for reduced code size
set(CMAKE_CXX_FLAGS_RELEASE "-Os")
set(CMAKE_C_FLAGS_RELEASE "-Os")
endif()
if (APPLE)
if(APPLE)
# Build the updater as a dual 32/64bit binary. If only one architecture
# is required, removing the other architecture will reduce the size
# of the updater binary

View File

@@ -4,79 +4,76 @@ add_subdirectory(tests)
find_package(Threads REQUIRED)
include(GenerateCppResourceFile)
set (UPDATER_SOURCES
AppInfo.cpp
AppInfo.h
DirIterator.cpp
DirIterator.h
FileUtils.cpp
FileUtils.h
Log.cpp
Log.h
ProcessUtils.cpp
ProcessUtils.h
StandardDirs.cpp
StandardDirs.h
UpdateDialog.cpp
UpdateInstaller.cpp
UpdateInstaller.h
UpdateScript.cpp
UpdateScript.h
UpdaterOptions.cpp
UpdaterOptions.h
set(UPDATER_SOURCES
AppInfo.cpp
AppInfo.h
DirIterator.cpp
DirIterator.h
FileUtils.cpp
FileUtils.h
Log.cpp
Log.h
ProcessUtils.cpp
ProcessUtils.h
StandardDirs.cpp
StandardDirs.h
UpdateDialog.cpp
UpdateInstaller.cpp
UpdateInstaller.h
UpdateScript.cpp
UpdateScript.h
UpdaterOptions.cpp
UpdaterOptions.h
)
add_definitions(-DTIXML_USE_STL)
if (WIN32)
if(WIN32)
set(UPDATER_SOURCES ${UPDATER_SOURCES} UpdateDialogWin32.cpp UpdateDialogWin32.h)
endif()
if (UNIX)
if(UNIX)
set(UPDATER_SOURCES ${UPDATER_SOURCES} UpdateDialogAscii.cpp UpdateDialogAscii.h)
add_definitions(-Wall -Wconversion)
if (APPLE)
set(MAC_DOCK_ICON_CPP_FILE ${CMAKE_CURRENT_BINARY_DIR}/mac_dock_icon.cpp)
set(MAC_INFO_PLIST_FILE ${CMAKE_CURRENT_BINARY_DIR}/mac_info_plist.cpp)
generate_cpp_resource_file(resource_macdockicon ${CMAKE_CURRENT_SOURCE_DIR}/resources/mac.icns ${MAC_DOCK_ICON_CPP_FILE})
generate_cpp_resource_file(resource_macplist ${CMAKE_CURRENT_SOURCE_DIR}/resources/Info.plist ${MAC_INFO_PLIST_FILE})
set(UPDATER_SOURCES ${UPDATER_SOURCES}
MacBundle.h
MacBundle.cpp
StandardDirs.mm
StlSymbolsLeopard.cpp
UpdateDialogCocoa.mm
UpdateDialogCocoa.h
mac_dock_icon.cpp
mac_info_plist.cpp
)
else() # linuxes and other similar systems
find_package(GTK2 REQUIRED gtk)
include_directories(${GTK2_INCLUDE_DIRS})
add_library(updatergtk SHARED UpdateDialogGtk.cpp UpdateDialogGtk.h)
target_link_libraries(updatergtk ${GTK2_LIBRARIES})
if(APPLE)
set(MAC_DOCK_ICON_CPP_FILE ${CMAKE_CURRENT_BINARY_DIR}/mac_dock_icon.cpp)
set(MAC_INFO_PLIST_FILE ${CMAKE_CURRENT_BINARY_DIR}/mac_info_plist.cpp)
generate_cpp_resource_file(resource_macdockicon ${CMAKE_CURRENT_SOURCE_DIR}/resources/mac.icns ${MAC_DOCK_ICON_CPP_FILE})
generate_cpp_resource_file(resource_macplist ${CMAKE_CURRENT_SOURCE_DIR}/resources/Info.plist ${MAC_INFO_PLIST_FILE})
set(UPDATER_SOURCES ${UPDATER_SOURCES}
MacBundle.h
MacBundle.cpp
StandardDirs.mm
StlSymbolsLeopard.cpp
UpdateDialogCocoa.mm
UpdateDialogCocoa.h
mac_dock_icon.cpp
mac_info_plist.cpp
)
else() # linuxes and other similar systems
find_package(GTK2 REQUIRED gtk)
include_directories(${GTK2_INCLUDE_DIRS})
add_library(updatergtk SHARED UpdateDialogGtk.cpp UpdateDialogGtk.h)
target_link_libraries(updatergtk ${GTK2_LIBRARIES})
# embed the GTK helper library into the updater binary.
# At runtime it will be extracted and loaded if the
# GTK libraries are available
get_property(GTK_UPDATER_LIB TARGET updatergtk PROPERTY LOCATION)
set(GTK_BIN_CPP_FILE ${CMAKE_CURRENT_BINARY_DIR}/libupdatergtk.cpp)
generate_cpp_resource_file(resource_updatergtk ${GTK_UPDATER_LIB} ${GTK_BIN_CPP_FILE})
add_dependencies(resource_updatergtk updatergtk)
# embed the GTK helper library into the updater binary.
# At runtime it will be extracted and loaded if the
# GTK libraries are available
get_property(GTK_UPDATER_LIB TARGET updatergtk PROPERTY LOCATION)
set(GTK_BIN_CPP_FILE ${CMAKE_CURRENT_BINARY_DIR}/libupdatergtk.cpp)
generate_cpp_resource_file(resource_updatergtk ${GTK_UPDATER_LIB} ${GTK_BIN_CPP_FILE})
add_dependencies(resource_updatergtk updatergtk)
set(UPDATER_SOURCES ${UPDATER_SOURCES} UpdateDialogGtkFactory.cpp UpdateDialogGtkFactory.h ${GTK_BIN_CPP_FILE})
endif()
set(UPDATER_SOURCES ${UPDATER_SOURCES} UpdateDialogGtkFactory.cpp UpdateDialogGtkFactory.h ${GTK_BIN_CPP_FILE})
endif()
endif()
add_library(updatershared STATIC ${UPDATER_SOURCES})
target_link_libraries(updatershared
anyoption
tinyxml
)
target_link_libraries(updatershared anyoption tinyxml)
if (UNIX)
if (APPLE)
if(UNIX)
if(APPLE)
find_library(COCOA_LIBRARY Cocoa)
find_library(SECURITY_LIBRARY Security)
target_link_libraries(updatershared ${SECURITY_LIBRARY} ${COCOA_LIBRARY})
@@ -86,36 +83,34 @@ if (UNIX)
target_link_libraries(updatershared pthread dl)
endif()
if (WIN32)
if(WIN32)
set(EXE_FLAGS WIN32 resources/updater.rc)
endif()
add_executable(updater ${EXE_FLAGS} main.cpp)
target_link_libraries(updater
updatershared
)
target_link_libraries(updater updatershared)
#### Updater Executable ####
IF(WIN32)
INSTALL(TARGETS updater
BUNDLE DESTINATION . COMPONENT Runtime
LIBRARY DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION . COMPONENT Runtime
)
ENDIF()
IF(UNIX)
IF(APPLE)
INSTALL(TARGETS updater
BUNDLE DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION MultiMC.app/Contents/MacOS COMPONENT Runtime
)
ELSE()
INSTALL(TARGETS updater
BUNDLE DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION bin COMPONENT Runtime
)
ENDIF()
ENDIF()
if(WIN32)
install(TARGETS updater
BUNDLE DESTINATION . COMPONENT Runtime
LIBRARY DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION . COMPONENT Runtime
)
endif()
if(UNIX)
if(APPLE)
install(TARGETS updater
BUNDLE DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION MultiMC.app/Contents/MacOS COMPONENT Runtime
)
else()
install(TARGETS updater
BUNDLE DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION bin COMPONENT Runtime
)
endif()
endif()

View File

@@ -1,8 +1,8 @@
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/..")
if (APPLE)
set(HELPER_SHARED_SOURCES ../StlSymbolsLeopard.cpp)
if(APPLE)
set(HELPER_SHARED_SOURCES ../StlSymbolsLeopard.cpp)
endif()
# # Create helper binaries for unit tests
@@ -17,14 +17,14 @@ endif()
# Install data files required by unit tests
set(TEST_FILES
file_list.xml
file_list.xml
)
foreach(TEST_FILE ${TEST_FILES})
execute_process(
COMMAND
"${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_FILE}" "${CMAKE_CURRENT_BINARY_DIR}"
)
execute_process(
COMMAND
"${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_FILE}" "${CMAKE_CURRENT_BINARY_DIR}"
)
endforeach()
# Add unit test binaries
@@ -32,13 +32,13 @@ macro(ADD_UPDATER_TEST CLASS)
set(TEST_TARGET updater_${CLASS})
unset(srcs)
list(APPEND srcs ${CLASS}.cpp)
if (WIN32)
if(WIN32)
list(APPEND srcs ${CMAKE_CURRENT_SOURCE_DIR}/test.rc)
endif()
add_executable(${TEST_TARGET} ${srcs})
target_link_libraries(${TEST_TARGET} updatershared)
add_test(NAME ${TEST_TARGET} COMMAND ${TEST_TARGET})
if (APPLE)
if(APPLE)
set_target_properties(${TEST_TARGET} PROPERTIES LINK_FLAGS "-framework Security -framework Cocoa")
endif()
endmacro()

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

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