Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a0cdf2d3d | ||
|
|
e1f542b5b0 | ||
|
|
15920aa9d0 | ||
|
|
e17364de6b | ||
|
|
b911a3834c | ||
|
|
94c2c363b2 | ||
|
|
df82d8fadb | ||
|
|
851a77d5bb | ||
|
|
41caf7976d | ||
|
|
fc3c0b0971 | ||
|
|
94cb5c7d77 | ||
|
|
911ac19a56 | ||
|
|
7f2a16917e | ||
|
|
8a8c4193e6 | ||
|
|
927217c7f0 | ||
|
|
a6a5241e12 | ||
|
|
e6ca58a89e | ||
|
|
aefa73ad11 | ||
|
|
4f6cd65c13 | ||
|
|
5099964c67 | ||
|
|
9e80ddb040 | ||
|
|
489cb4dbf5 | ||
|
|
e3b9b30302 | ||
|
|
93ae21abfc | ||
|
|
cf616efb5d | ||
|
|
0902fd5bec | ||
|
|
de48f102bd | ||
|
|
0f3d88cb14 | ||
|
|
8fda1595f7 | ||
|
|
e9c8ca02ba | ||
|
|
95203df9ca | ||
|
|
27732d66b4 | ||
|
|
b3f717c582 | ||
|
|
cba22f7ee0 | ||
|
|
3d8f6ac640 | ||
|
|
605a334057 | ||
|
|
5fa36f67b3 | ||
|
|
0cd8ded4d4 | ||
|
|
9d724e0fe4 | ||
|
|
a9dfe6d7ec | ||
|
|
7de007502c | ||
|
|
e955b1c6f9 | ||
|
|
c8f468057e | ||
|
|
6ded1168b0 | ||
|
|
adecc53df8 | ||
|
|
c9465e9e86 | ||
|
|
f14c1888a9 | ||
|
|
ca60784a44 | ||
|
|
565dab24b5 | ||
|
|
eea347b82c | ||
|
|
0b60e50af8 | ||
|
|
0959aeb046 | ||
|
|
89edc3e15e | ||
|
|
f67ca674c4 | ||
|
|
3ed5b1570b | ||
|
|
4674aad125 | ||
|
|
bf1632e4ed | ||
|
|
b286b93281 | ||
|
|
632c087483 | ||
|
|
318a945d74 | ||
|
|
b6d7ffab47 | ||
|
|
04b36a3e55 | ||
|
|
3f2152c14d | ||
|
|
8a6c64ef62 | ||
|
|
6e42d51283 | ||
|
|
86830967b6 | ||
|
|
1657313105 | ||
|
|
a00fb1e8da | ||
|
|
50d18a06d5 | ||
|
|
3cd2b898e5 | ||
|
|
17d4947b30 | ||
|
|
dd7b6642a3 | ||
|
|
eb246c4fa7 | ||
|
|
6ef38d0873 | ||
|
|
482ad250a4 | ||
|
|
f9169654c5 | ||
|
|
e58e2643ca | ||
|
|
42e305bb9d | ||
|
|
8594cc8f6c | ||
|
|
ad9d082f57 | ||
|
|
52ff6a4140 | ||
|
|
1ef6ec4178 | ||
|
|
72bc860983 |
@@ -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
|
||||
|
||||
2
BUILD.md
@@ -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
@@ -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
@@ -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;
|
||||
1187
CMakeLists.txt
376
HandleCrash.cpp
Normal 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
@@ -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();
|
||||
|
||||
79
MultiMC.cpp
@@ -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());
|
||||
|
||||
17
MultiMC.h
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
895
cmake/GetPrerequisites.cmake
Normal 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
@@ -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
@@ -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
@@ -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()
|
||||
40
config.h.in
@@ -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@"
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 <<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>></p>"
|
||||
"<p>Petr Mrázek <<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>></p>"
|
||||
"<p>Sky <<a href='https://www.twitter.com/drayshak'>@drayshak</a>></p>"
|
||||
"<p>Jan (02JanDal) <<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>></p>"
|
||||
""
|
||||
"<h3>With thanks to</h3>"
|
||||
"<p>Orochimarufan <<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>></p>"
|
||||
"<p>TakSuyu <<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>></p>"
|
||||
"<p>Kilobyte <<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>></p>"
|
||||
"<p>Robotbrain <<a href='https://twitter.com/skylordelros'>@skylordelros</a>></p>"
|
||||
"<p>Rootbear75 <<a href='https://twitter.com/rootbear75'>@rootbear75</a>> (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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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><!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, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;">MultiMC</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Andrew Okin &lt;</span><a href="mailto:forkk@forkk.net"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">forkk@forkk.net</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Petr Mrázek &lt;</span><a href="mailto:peterix@gmail.com"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">peterix@gmail.com</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Sky &lt;</span><a href="https://www.twitter.com/drayshak"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">@drayshak</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p>
|
||||
<p style="-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;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:10pt; font-weight:600;">With thanks to</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Orochimarufan &lt;</span><a href="mailto:orochimarufan.x3@gmail.com"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">orochimarufan.x3@gmail.com</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">TakSuyu &lt;</span><a href="mailto:taksuyu@gmail.com"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">taksuyu@gmail.com</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Kilobyte &lt;</span><a href="mailto:stiepen22@gmx.de"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">stiepen22@gmx.de</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Jan (02JanDal) &lt;</span><a href="mailto:02jandal@gmail.com"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">02jandal@gmail.com</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Robotbrain &lt;</span><a href="https://twitter.com/skylordelros"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">@skylordelros</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt;</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">Rootbear75 &lt;</span><a href="https://twitter.com/rootbear75"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; text-decoration: underline; color:#0000ff;">@rootbear75</span></a><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt;">&gt; (build server)</span></p></body></html></string>
|
||||
</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></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><!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, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'DejaVu Sans Mono'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||
</style></head><body style=" font-family:'DejaVu Sans Mono'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;">MultiMC</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Copyright 2012-2014 MultiMC Contributors</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);</span></p>
|
||||
@@ -433,7 +422,7 @@ p, li { white-space: pre-wrap; }
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;"> */</span></p>
|
||||
<p style="-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;"><br /></p>
|
||||
<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:18pt; font-weight:600;">Java IconLoader class</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style="-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;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Copyright (c) 2011, Chris Molini</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">All rights reserved.</span></p>
|
||||
<p style="-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;"><br /></p>
|
||||
@@ -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><!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, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">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.</p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Part of the reason for using the Apache license is we don't want people using the &quot;MultiMC&quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &quot;MultiMC&quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).</p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">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 <span style=" font-weight:600;">without</span> implying that you have our blessing.</p></body></html></string>
|
||||
</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">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.</span></p>
|
||||
<p style="-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;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">Part of the reason for using the Apache license is we don't want people using the &quot;MultiMC&quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &quot;MultiMC&quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).</span></p>
|
||||
<p style="-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;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">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 </span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:600;">without</span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;"> implying that you have our blessing.</span></p></body></html></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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
77
gui/dialogs/LoginDialog.ui
Normal 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>
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
37
gui/widgets/LineSeparator.cpp
Normal 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());
|
||||
}
|
||||
18
gui/widgets/LineSeparator.h
Normal 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;
|
||||
};
|
||||
115
gui/widgets/ServerStatus.cpp
Normal 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);
|
||||
}
|
||||
34
gui/widgets/ServerStatus.h
Normal 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;
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
};
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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/");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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!
|
||||
|
||||
11
main.cpp
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
BIN
resources/multimc/16x16/patreon.png
Normal file
|
After Width: | Height: | Size: 682 B |
BIN
resources/multimc/16x16/status-bad.png
Normal file
|
After Width: | Height: | Size: 643 B |
BIN
resources/multimc/16x16/status-good.png
Normal file
|
After Width: | Height: | Size: 714 B |
BIN
resources/multimc/22x22/patreon.png
Normal file
|
After Width: | Height: | Size: 976 B |
BIN
resources/multimc/22x22/status-bad.png
Normal file
|
After Width: | Height: | Size: 968 B |
BIN
resources/multimc/22x22/status-good.png
Normal file
|
After Width: | Height: | Size: 994 B |
BIN
resources/multimc/24x24/patreon.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |