diff options
208 files changed, 10699 insertions, 5570 deletions
diff --git a/BuildConfig.cpp.in b/BuildConfig.cpp.in index 1ea07374..76196b7e 100644 --- a/BuildConfig.cpp.in +++ b/BuildConfig.cpp.in @@ -29,7 +29,9 @@ Config::Config() 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@"; } diff --git a/BuildConfig.h b/BuildConfig.h index baf2ad6d..5e10beb0 100644 --- a/BuildConfig.h +++ b/BuildConfig.h @@ -61,10 +61,14 @@ public: /// 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" diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c01799c..9304547b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,15 +75,36 @@ if(${BIGENDIAN}) endif(${BIGENDIAN}) -######## Set URLs ######## +######## Dark magic crash reports ######## +option(MultiMC_HANDLE_SEGV "Handle fatal crashes and generate crash reports." OFF) +set(CRASH_HANDLER_IMPL "") +message(STATUS "Crash dumps are ${MultiMC_HANDLE_SEGV}") +if (MultiMC_HANDLE_SEGV) + add_definitions(-DHANDLE_SEGV) + if (WIN32) + find_package(DbgHelp) + set(MultiMC_LINK_ADDITIONAL_LIBS "${MultiMC_LINK_ADDITIONAL_LIBS}dbghelp") + set(MultiMC_CRASH_HANDLER_EXTRA_H "WinBacktrace.h") + set(MultiMC_CRASH_HANDLER_EXTRA_CPP "WinBacktrace.cpp") + endif () +endif () + +option(MultiMC_TEST_SEGV "Intentionally segfault sometimes to test crash handling." OFF) +if (MultiMC_TEST_SEGV) + # TODO: Make this a unit test instead. + message(WARNING "You have enabled crash handler testing. MULTIMC WILL INTENTIONALLY CRASH ITSELF. Crashes should occur on exit and when the new instance button is pressed.") + add_definitions(-DTEST_SEGV) +endif () + +######## Set URLs ######## set(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.") ######## Set version numbers ######## set(MultiMC_VERSION_MAJOR 0) -set(MultiMC_VERSION_MINOR 3) -set(MultiMC_VERSION_HOTFIX 5) +set(MultiMC_VERSION_MINOR 4) +set(MultiMC_VERSION_HOTFIX 0) # Build number set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") @@ -250,6 +271,12 @@ SET(MULTIMC_SOURCES BuildConfig.h ${PROJECT_BINARY_DIR}/BuildConfig.cpp + # Crash handling + HandleCrash.h + HandleCrash.cpp + ${MultiMC_CRASH_HANDLER_EXTRA_H} # Extra platform specific stuff + ${MultiMC_CRASH_HANDLER_EXTRA_CPP} + # Logging logger/QsDebugOutput.cpp logger/QsDebugOutput.h @@ -264,48 +291,62 @@ SET(MULTIMC_SOURCES gui/ConsoleWindow.h gui/ConsoleWindow.cpp + # GUI - page dialog pages + gui/pages/VersionPage.cpp + gui/pages/VersionPage.h + gui/pages/TexturePackPage.h + gui/pages/ResourcePackPage.h + gui/pages/ModFolderPage.cpp + gui/pages/ModFolderPage.h + gui/pages/NotesPage.cpp + gui/pages/NotesPage.h + gui/pages/LegacyUpgradePage.cpp + gui/pages/LegacyUpgradePage.h + gui/pages/LegacyJarModPage.cpp + gui/pages/LegacyJarModPage.h + gui/pages/LogPage.cpp + gui/pages/LogPage.h + gui/pages/InstanceSettingsPage.cpp + gui/pages/InstanceSettingsPage.h + gui/pages/ScreenshotsPage.cpp + gui/pages/ScreenshotsPage.h + # GUI - dialogs - gui/dialogs/SettingsDialog.h - gui/dialogs/SettingsDialog.cpp - gui/dialogs/CopyInstanceDialog.h - gui/dialogs/CopyInstanceDialog.cpp - gui/dialogs/NewInstanceDialog.cpp - gui/dialogs/ProgressDialog.h - gui/dialogs/ProgressDialog.cpp - gui/dialogs/AboutDialog.h gui/dialogs/AboutDialog.cpp - gui/dialogs/VersionSelectDialog.h - gui/dialogs/VersionSelectDialog.cpp - gui/dialogs/LwjglSelectDialog.h - gui/dialogs/LwjglSelectDialog.cpp - gui/dialogs/InstanceSettings.h - gui/dialogs/InstanceSettings.cpp - gui/dialogs/IconPickerDialog.h - gui/dialogs/IconPickerDialog.cpp - gui/dialogs/LegacyModEditDialog.h - gui/dialogs/LegacyModEditDialog.cpp - gui/dialogs/OneSixModEditDialog.h - gui/dialogs/OneSixModEditDialog.cpp - gui/dialogs/ModEditDialogCommon.h - gui/dialogs/ModEditDialogCommon.cpp - gui/dialogs/EditNotesDialog.h - gui/dialogs/EditNotesDialog.cpp - gui/dialogs/CustomMessageBox.h + gui/dialogs/AboutDialog.h + gui/dialogs/AccountListDialog.cpp + gui/dialogs/AccountListDialog.h + gui/dialogs/AccountSelectDialog.cpp + gui/dialogs/AccountSelectDialog.h + gui/dialogs/CopyInstanceDialog.cpp + gui/dialogs/CopyInstanceDialog.h gui/dialogs/CustomMessageBox.cpp - gui/dialogs/EditAccountDialog.h + gui/dialogs/CustomMessageBox.h gui/dialogs/EditAccountDialog.cpp - gui/dialogs/LoginDialog.h + gui/dialogs/EditAccountDialog.h + gui/dialogs/IconPickerDialog.cpp + gui/dialogs/IconPickerDialog.h gui/dialogs/LoginDialog.cpp - gui/dialogs/AccountListDialog.h - gui/dialogs/AccountListDialog.cpp - gui/dialogs/AccountSelectDialog.h - gui/dialogs/AccountSelectDialog.cpp - gui/dialogs/UpdateDialog.h - gui/dialogs/UpdateDialog.cpp - gui/dialogs/ScreenshotDialog.h - gui/dialogs/ScreenshotDialog.cpp - gui/dialogs/NotificationDialog.h + gui/dialogs/LoginDialog.h + gui/dialogs/LwjglSelectDialog.cpp + gui/dialogs/LwjglSelectDialog.h + gui/dialogs/ModEditDialogCommon.cpp + gui/dialogs/ModEditDialogCommon.h + gui/dialogs/NewInstanceDialog.cpp + gui/dialogs/NewInstanceDialog.h gui/dialogs/NotificationDialog.cpp + gui/dialogs/NotificationDialog.h + gui/pagedialog/PageDialog.cpp + gui/pagedialog/PageDialog.h + gui/dialogs/ProgressDialog.cpp + gui/dialogs/ProgressDialog.h + gui/dialogs/SettingsDialog.cpp + gui/dialogs/SettingsDialog.h + gui/dialogs/UpdateDialog.cpp + gui/dialogs/UpdateDialog.h + gui/dialogs/VersionSelectDialog.cpp + gui/dialogs/VersionSelectDialog.h + # GUI - widgets gui/widgets/Common.cpp @@ -320,6 +361,9 @@ SET(MULTIMC_SOURCES gui/widgets/MCModInfoFrame.h gui/widgets/ModListView.cpp gui/widgets/ModListView.h + gui/widgets/PageContainer.cpp + gui/widgets/PageContainer.h + gui/widgets/PageContainer_p.h gui/widgets/ServerStatus.cpp gui/widgets/ServerStatus.h gui/widgets/VersionListView.cpp @@ -336,25 +380,43 @@ SET(MULTIMC_SOURCES gui/groupview/InstanceDelegate.cpp gui/groupview/InstanceDelegate.h - # Base classes and infrastructure + # LOGIC - Base classes and infrastructure logic/BaseVersion.h - logic/MinecraftVersion.h logic/InstanceFactory.h logic/InstanceFactory.cpp logic/BaseInstance.h logic/BaseInstance.cpp logic/BaseInstance_p.h - - logic/MinecraftProcess.h - logic/MinecraftProcess.cpp logic/Mod.h logic/Mod.cpp logic/ModList.h logic/ModList.cpp + + # sets and maps for deciding based on versions + logic/VersionFilterData.h + logic/VersionFilterData.cpp - # Basic instance launcher for starting from terminal + # Instance launch logic/InstanceLauncher.h logic/InstanceLauncher.cpp + logic/MinecraftProcess.h + logic/MinecraftProcess.cpp + + # URN parser/resolver + logic/URNResolver.cpp + logic/URNResolver.h + + # Annoying nag screen logic + logic/NagUtils.h + logic/NagUtils.cpp + + # Player skin utilities + logic/SkinUtils.h + logic/SkinUtils.cpp + + # misc model filter + logic/EnabledItemFilter.h + logic/EnabledItemFilter.cpp # JSON parsing helpers logic/MMCJson.h @@ -368,10 +430,6 @@ SET(MULTIMC_SOURCES logic/net/ByteArrayDownload.cpp logic/net/CacheDownload.h logic/net/CacheDownload.cpp - logic/net/ForgeMirrors.h - logic/net/ForgeMirrors.cpp - logic/net/ForgeXzDownload.h - logic/net/ForgeXzDownload.cpp logic/net/NetJob.h logic/net/NetJob.cpp logic/net/HttpMetaCache.h @@ -422,9 +480,6 @@ SET(MULTIMC_SOURCES logic/LegacyUpdate.h logic/LegacyUpdate.cpp - logic/LegacyForge.h - logic/LegacyForge.cpp - # OneSix instances logic/OneSixUpdate.h logic/OneSixUpdate.cpp @@ -433,30 +488,42 @@ SET(MULTIMC_SOURCES logic/OneSixInstance_p.h # OneSix version json infrastructure - logic/OneSixVersionBuilder.h - logic/OneSixVersionBuilder.cpp - logic/VersionFile.h - logic/VersionFile.cpp - logic/VersionFinal.h - logic/VersionFinal.cpp - logic/OneSixLibrary.h - logic/OneSixLibrary.cpp - logic/OneSixRule.h - logic/OneSixRule.cpp - logic/OpSys.h - logic/OpSys.cpp - - # Mod installers + logic/minecraft/InstanceVersion.cpp + logic/minecraft/InstanceVersion.h + logic/minecraft/JarMod.cpp + logic/minecraft/JarMod.h + logic/minecraft/MinecraftVersion.cpp + logic/minecraft/MinecraftVersion.h + logic/minecraft/MinecraftVersionList.cpp + logic/minecraft/MinecraftVersionList.h + logic/minecraft/OneSixLibrary.cpp + logic/minecraft/OneSixLibrary.h + logic/minecraft/OneSixRule.cpp + logic/minecraft/OneSixRule.h + logic/minecraft/OpSys.cpp + logic/minecraft/OpSys.h + logic/minecraft/ParseUtils.cpp + logic/minecraft/ParseUtils.h + logic/minecraft/RawLibrary.cpp + logic/minecraft/RawLibrary.h + logic/minecraft/VersionBuilder.cpp + logic/minecraft/VersionBuilder.h + logic/minecraft/VersionBuildError.h + logic/minecraft/VersionFile.cpp + logic/minecraft/VersionFile.h + logic/minecraft/VersionPatch.h + logic/minecraft/VersionSource.h + + # Various base classes logic/BaseInstaller.h logic/BaseInstaller.cpp - logic/ForgeInstaller.h - logic/ForgeInstaller.cpp - logic/LiteLoaderInstaller.h - logic/LiteLoaderInstaller.cpp + logic/BaseVersionList.h + logic/BaseVersionList.cpp - # Nostalgia - logic/NostalgiaInstance.h - logic/NostalgiaInstance.cpp + logic/InstanceList.h + logic/InstanceList.cpp + logic/LwjglVersionList.h + logic/LwjglVersionList.cpp # FTB logic/OneSixFTBInstance.h @@ -464,27 +531,8 @@ SET(MULTIMC_SOURCES logic/LegacyFTBInstance.h logic/LegacyFTBInstance.cpp - # Lists - logic/lists/InstanceList.h - logic/lists/InstanceList.cpp - logic/lists/BaseVersionList.h - logic/lists/BaseVersionList.cpp - logic/lists/MinecraftVersionList.h - logic/lists/MinecraftVersionList.cpp - logic/lists/LwjglVersionList.h - logic/lists/LwjglVersionList.cpp - logic/lists/ForgeVersionList.h - logic/lists/ForgeVersionList.cpp - logic/lists/JavaVersionList.h - logic/lists/JavaVersionList.cpp - logic/lists/LiteLoaderVersionList.h - logic/lists/LiteLoaderVersionList.cpp - # the screenshots feature logic/screenshots/Screenshot.h - logic/screenshots/Screenshot.cpp - logic/screenshots/ScreenshotList.h - logic/screenshots/ScreenshotList.cpp logic/screenshots/ImgurUpload.h logic/screenshots/ImgurUpload.cpp logic/screenshots/ImgurAlbumCreation.h @@ -496,11 +544,6 @@ SET(MULTIMC_SOURCES logic/icons/IconList.h logic/icons/IconList.cpp - - # misc model/view - logic/EnabledItemFilter.h - logic/EnabledItemFilter.cpp - # Tasks logic/tasks/ProgressProvider.h logic/tasks/Task.h @@ -510,17 +553,15 @@ SET(MULTIMC_SOURCES logic/tasks/SequentialTask.h logic/tasks/SequentialTask.cpp - # Utilities - logic/JavaChecker.h - logic/JavaChecker.cpp - logic/JavaUtils.h - logic/JavaUtils.cpp - logic/NagUtils.h - logic/NagUtils.cpp - logic/SkinUtils.h - logic/SkinUtils.cpp - logic/JavaCheckerJob.h - logic/JavaCheckerJob.cpp + # Java related code + logic/java/JavaChecker.h + logic/java/JavaChecker.cpp + logic/java/JavaUtils.h + logic/java/JavaUtils.cpp + logic/java/JavaVersionList.h + logic/java/JavaVersionList.cpp + logic/java/JavaCheckerJob.h + logic/java/JavaCheckerJob.cpp # Assets logic/assets/AssetsMigrateTask.h @@ -539,6 +580,27 @@ SET(MULTIMC_SOURCES logic/tools/JProfiler.cpp logic/tools/JVisualVM.h logic/tools/JVisualVM.cpp + + # Forge and all things forge related + logic/forge/ForgeVersion.h + logic/forge/ForgeVersion.cpp + logic/forge/ForgeVersionList.h + logic/forge/ForgeVersionList.cpp + logic/forge/ForgeMirror.h + logic/forge/ForgeMirrors.h + logic/forge/ForgeMirrors.cpp + logic/forge/ForgeXzDownload.h + logic/forge/ForgeXzDownload.cpp + logic/forge/LegacyForge.h + logic/forge/LegacyForge.cpp + logic/forge/ForgeInstaller.h + logic/forge/ForgeInstaller.cpp + + # Liteloader and related things + logic/liteloader/LiteLoaderInstaller.h + logic/liteloader/LiteLoaderInstaller.cpp + logic/liteloader/LiteLoaderVersionList.h + logic/liteloader/LiteLoaderVersionList.cpp ) @@ -546,7 +608,16 @@ SET(MULTIMC_SOURCES SET(MULTIMC_UIS # Windows gui/MainWindow.ui - gui/ConsoleWindow.ui + + # Option pages + gui/pages/VersionPage.ui + gui/pages/ModFolderPage.ui + gui/pages/LegacyUpgradePage.ui + gui/pages/LegacyJarModPage.ui + gui/pages/LogPage.ui + gui/pages/InstanceSettingsPage.ui + gui/pages/NotesPage.ui + gui/pages/ScreenshotsPage.ui # Dialogs gui/dialogs/SettingsDialog.ui @@ -555,18 +626,13 @@ SET(MULTIMC_UIS gui/dialogs/AboutDialog.ui gui/dialogs/VersionSelectDialog.ui gui/dialogs/LwjglSelectDialog.ui - gui/dialogs/InstanceSettings.ui gui/dialogs/ProgressDialog.ui gui/dialogs/IconPickerDialog.ui - gui/dialogs/LegacyModEditDialog.ui - gui/dialogs/OneSixModEditDialog.ui - gui/dialogs/EditNotesDialog.ui gui/dialogs/AccountListDialog.ui gui/dialogs/AccountSelectDialog.ui gui/dialogs/EditAccountDialog.ui gui/dialogs/LoginDialog.ui gui/dialogs/UpdateDialog.ui - gui/dialogs/ScreenshotDialog.ui gui/dialogs/NotificationDialog.ui # Widgets/other @@ -590,6 +656,7 @@ set(MULTIMC_QRCS resources/pe_dark/pe_dark.qrc resources/pe_light/pe_light.qrc resources/instances/instances.qrc + resources/versions/versions.qrc ) @@ -770,8 +837,6 @@ include(CPack) include(Coverity) -include_directories(${PROJECT_BINARY_DIR}/include) - # Translations add_subdirectory(translations) diff --git a/HandleCrash.cpp b/HandleCrash.cpp new file mode 100644 index 00000000..ed319cf1 --- /dev/null +++ b/HandleCrash.cpp @@ -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 +} + +// }}} + diff --git a/HandleCrash.h b/HandleCrash.h new file mode 100644 index 00000000..3b2e7d00 --- /dev/null +++ b/HandleCrash.h @@ -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(); + diff --git a/MultiMC.cpp b/MultiMC.cpp index 358d15fb..bd72c139 100644 --- a/MultiMC.cpp +++ b/MultiMC.cpp @@ -12,13 +12,14 @@ #include <QDesktopServices> #include "gui/dialogs/VersionSelectDialog.h" -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" #include "logic/auth/MojangAccountList.h" #include "logic/icons/IconList.h" -#include "logic/lists/LwjglVersionList.h" -#include "logic/lists/MinecraftVersionList.h" -#include "logic/lists/ForgeVersionList.h" -#include "logic/lists/LiteLoaderVersionList.h" +#include "logic/LwjglVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/liteloader/LiteLoaderVersionList.h" + +#include "logic/forge/ForgeVersionList.h" #include "logic/news/NewsChecker.h" @@ -28,7 +29,7 @@ #include "logic/net/HttpMetaCache.h" #include "logic/net/URLConstants.h" -#include "logic/JavaUtils.h" +#include "logic/java/JavaUtils.h" #include "logic/updater/UpdateChecker.h" #include "logic/updater/NotificationChecker.h" @@ -37,6 +38,8 @@ #include "logic/tools/JVisualVM.h" #include "logic/tools/MCEditTool.h" +#include "logic/URNResolver.h" + #include "pathutils.h" #include "cmdutils.h" #include <inisettingsobject.h> @@ -86,6 +89,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 { @@ -335,13 +339,16 @@ void MultiMC::initLogger() QsLogging::Logger &logger = QsLogging::Logger::instance(); logger.setLoggingLevel(QsLogging::TraceLevel); m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination(logBase.arg(0)); - m_debugDestination = QsLogging::DestinationFactory::MakeQDebugDestination(); + m_debugDestination = QsLogging::DestinationFactory::MakeDebugOutputDestination(); logger.addDestination(m_fileDestination.get()); 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)); @@ -355,31 +362,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)) { @@ -455,6 +488,7 @@ void MultiMC::initGlobalSettings() // Java Settings m_settings->registerSetting("JavaPath", ""); m_settings->registerSetting("LastHostname", ""); + m_settings->registerSetting("JavaDetectionHack", ""); m_settings->registerSetting("JvmArgs", ""); // Custom Commands @@ -475,6 +509,8 @@ void MultiMC::initGlobalSettings() m_settings->registerSetting("ConsoleWindowGeometry", ""); m_settings->registerSetting("SettingsGeometry", ""); + + m_settings->registerSetting("PagedGeometry", ""); } void MultiMC::initHttpMetaCache() @@ -614,6 +650,16 @@ std::shared_ptr<JavaVersionList> MultiMC::javalist() return m_javalist; } +std::shared_ptr<URNResolver> MultiMC::resolver() +{ + if (!m_resolver) + { + m_resolver.reset(new URNResolver()); + } + return m_resolver; +} + + void MultiMC::installUpdates(const QString updateFilesDir, UpdateFlags flags) { // if we are going to update on exit, save the params now @@ -23,21 +23,13 @@ class NewsChecker; class StatusChecker; class BaseProfilerFactory; class BaseDetachedToolFactory; +class URNResolver; #if defined(MMC) #undef MMC #endif #define MMC (static_cast<MultiMC *>(QCoreApplication::instance())) -// FIXME: possibly move elsewhere -enum InstSortMode -{ - // Sort alphabetically by name. - Sort_Name, - // Sort by which instance was launched most recently. - Sort_LastLaunch -}; - enum UpdateFlag { None = 0x0, @@ -48,6 +40,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 @@ -125,6 +120,8 @@ public: std::shared_ptr<JavaVersionList> javalist(); + std::shared_ptr<URNResolver> resolver(); + QMap<QString, std::shared_ptr<BaseProfilerFactory>> profilers() { return m_profilers; @@ -211,8 +208,11 @@ private: std::shared_ptr<LiteLoaderVersionList> m_liteloaderlist; std::shared_ptr<MinecraftVersionList> m_minecraftlist; std::shared_ptr<JavaVersionList> m_javalist; + std::shared_ptr<URNResolver> m_resolver; + QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers; QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> m_tools; + QsLogging::DestinationPtr m_fileDestination; QsLogging::DestinationPtr m_debugDestination; diff --git a/WinBacktrace.cpp b/WinBacktrace.cpp new file mode 100644 index 00000000..1ea079bf --- /dev/null +++ b/WinBacktrace.cpp @@ -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; +} diff --git a/WinBacktrace.h b/WinBacktrace.h new file mode 100644 index 00000000..d1e6301a --- /dev/null +++ b/WinBacktrace.h @@ -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); diff --git a/changelog.yaml b/changelog.yaml index 7fb75534..f0fe01cb 100644 --- a/changelog.yaml +++ b/changelog.yaml @@ -76,3 +76,15 @@ 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. +0.3.7 + - Fixed forge for 1.7.10-pre4 (and any future prereleases) +0.3.8 + - Workaround for performance issues with Intel integrated graphics chips +0.4.0 + - In Development... diff --git a/depends/launcher/CMakeLists.txt b/depends/launcher/CMakeLists.txt index ad06fa7b..42c77a89 100644 --- a/depends/launcher/CMakeLists.txt +++ b/depends/launcher/CMakeLists.txt @@ -18,7 +18,7 @@ set(SRC # 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 + org/multimc/LegacyFrame.java # onesix launcher org/multimc/onesix/OneSixLauncher.java diff --git a/depends/launcher/net/minecraft/Launcher.java b/depends/launcher/net/minecraft/Launcher.java index c9b137e1..a53501ec 100644 --- a/depends/launcher/net/minecraft/Launcher.java +++ b/depends/launcher/net/minecraft/Launcher.java @@ -24,6 +24,7 @@ import java.awt.BorderLayout; import java.awt.Graphics; import java.applet.Applet; import java.applet.AppletStub; +import java.net.MalformedURLException; public class Launcher extends Applet implements AppletStub { @@ -130,13 +131,23 @@ public class Launcher extends Applet implements AppletStub @Override public URL getCodeBase() { - return wrappedApplet.getCodeBase(); + try { + return new URL("http://www.minecraft.net/game/"); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; } @Override public URL getDocumentBase() { - return documentBase; + try { + return new URL("http://www.minecraft.net/game/"); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; } @Override diff --git a/depends/launcher/org/multimc/legacy/LegacyFrame.java b/depends/launcher/org/multimc/LegacyFrame.java index c3c0cafc..a081f3ae 100644 --- a/depends/launcher/org/multimc/legacy/LegacyFrame.java +++ b/depends/launcher/org/multimc/LegacyFrame.java @@ -1,4 +1,4 @@ -package org.multimc.legacy;/* +package org.multimc;/* * Copyright 2012-2014 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/depends/launcher/org/multimc/legacy/LegacyLauncher.java b/depends/launcher/org/multimc/legacy/LegacyLauncher.java index 1ca37c4a..347bb1a2 100644 --- a/depends/launcher/org/multimc/legacy/LegacyLauncher.java +++ b/depends/launcher/org/multimc/legacy/LegacyLauncher.java @@ -14,10 +14,7 @@ package org.multimc.legacy;/* * limitations under the License. */ -import org.multimc.Launcher; -import org.multimc.NotFoundException; -import org.multimc.ParamBucket; -import org.multimc.Utils; +import org.multimc.*; import java.applet.Applet; import java.awt.*; diff --git a/depends/launcher/org/multimc/onesix/OneSixLauncher.java b/depends/launcher/org/multimc/onesix/OneSixLauncher.java index 28f8e6ee..b9eb4d66 100644 --- a/depends/launcher/org/multimc/onesix/OneSixLauncher.java +++ b/depends/launcher/org/multimc/onesix/OneSixLauncher.java @@ -17,128 +17,185 @@ package org.multimc.onesix; import org.multimc.*; +import java.applet.Applet; import java.io.File; +import java.awt.*; import java.io.IOException; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; public class OneSixLauncher implements Launcher { - @Override - public int launch(ParamBucket params) + // parameters, separated from ParamBucket + private List<String> libraries; + private List<String> extlibs; + private List<String> mcparams; + private List<String> mods; + private List<String> traits; + private String appletClass; + private String mainClass; + private String natives; + private String userName, sessionId; + private String windowTitle; + private String windowParams; + + // secondary parameters + private Dimension winSize; + private boolean maximize; + private String cwd; + + // the much abused system classloader, for convenience (for further abuse) + private ClassLoader cl; + + private void processParams(ParamBucket params) throws NotFoundException { - // get and process the launch script params - List<String> libraries; - List<String> extlibs; - List<String> mcparams; - List<String> mods; - String mainClass; - String natives; - final String windowTitle; - String windowParams; - try - { - libraries = params.all("cp"); - extlibs = params.all("ext"); - mcparams = params.all("param"); - mainClass = params.first("mainClass"); - mods = params.allSafe("mods", new ArrayList<String>()); - natives = params.first("natives"); + libraries = params.all("cp"); + extlibs = params.all("ext"); + mcparams = params.allSafe("param", new ArrayList<String>() ); + mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); + appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); + mods = params.allSafe("mods", new ArrayList<String>()); + traits = params.allSafe("traits", new ArrayList<String>()); + natives = params.first("natives"); - windowTitle = params.first("windowTitle"); - // windowParams = params.first("windowParams"); - } catch (NotFoundException e) + userName = params.first("userName"); + sessionId = params.first("sessionId"); + windowTitle = params.firstSafe("windowTitle", "Minecraft"); + windowParams = params.firstSafe("windowParams", "854x480"); + + cwd = System.getProperty("user.dir"); + winSize = new Dimension(854, 480); + maximize = false; + + String[] dimStrings = windowParams.split("x"); + + if (windowParams.equalsIgnoreCase("max")) { - System.err.println("Not enough arguments."); - e.printStackTrace(System.err); - return -1; + maximize = true; } + else if (dimStrings.length == 2) + { + try + { + winSize = new Dimension(Integer.parseInt(dimStrings[0]), Integer.parseInt(dimStrings[1])); + } catch (NumberFormatException ignored) {} + } + } + + private void printStats() + { + Utils.log("Main Class:"); + Utils.log(" " + mainClass); + Utils.log(); - List<String> allJars = new ArrayList<String>(); - allJars.addAll(mods); - allJars.addAll(libraries); + Utils.log("Native path:"); + Utils.log(" " + natives); + Utils.log(); - if(!Utils.addToClassPath(allJars)) + Utils.log("Traits:"); + Utils.log(" " + traits); + Utils.log(); + + Utils.log("Libraries:"); + for (String s : libraries) { - System.err.println("Halting launch due to previous errors."); - return -1; + Utils.log(" " + s); } + Utils.log(); - // print the pretty things + if(mods.size() > 0) { - Utils.log("Main Class:"); - Utils.log(" " + mainClass); - Utils.log(); - - Utils.log("Native path:"); - Utils.log(" " + natives); - Utils.log(); - - Utils.log("Libraries:"); - for (String s : libraries) + Utils.log("Class Path Mods:"); + for (String s : mods) { Utils.log(" " + s); } Utils.log(); + } + + Utils.log("Params:"); + Utils.log(" " + mcparams.toString()); + Utils.log(); + } + + int legacyLaunch() + { + // Get the Minecraft Class and set the base folder + Class<?> mc; + try + { + mc = cl.loadClass(mainClass); - if(mods.size() > 0) + Field f = Utils.getMCPathField(mc); + + if (f == null) { - Utils.log("Class Path Mods:"); - for (String s : mods) - { - Utils.log(" " + s); - } - Utils.log(); + System.err.println("Could not find Minecraft path field."); } - - Utils.log("Params:"); - Utils.log(" " + mcparams.toString()); - Utils.log(); + else + { + f.setAccessible(true); + f.set(null, new File(cwd)); + } + } catch (Exception e) + { + System.err.println("Could not set base folder. Failed to find/access Minecraft main class:"); + e.printStackTrace(System.err); + return -1; } - // set up the natives path(s). - Utils.log("Preparing native libraries..."); - String property = System.getProperty("os.arch"); - boolean is_64 = property.equalsIgnoreCase("x86_64") || property.equalsIgnoreCase("amd64"); - for(String extlib: extlibs) + System.setProperty("minecraft.applet.TargetDirectory", cwd); + + String[] mcArgs = new String[2]; + mcArgs[0] = userName; + mcArgs[1] = sessionId; + + Utils.log("Launching with applet wrapper..."); + try + { + Class<?> MCAppletClass = cl.loadClass(appletClass); + Applet mcappl = (Applet) MCAppletClass.newInstance(); + LegacyFrame mcWindow = new LegacyFrame(windowTitle); + mcWindow.start(mcappl, userName, sessionId, winSize, maximize); + } catch (Exception e) { + Utils.log("Applet wrapper failed:", "Error"); + e.printStackTrace(System.err); + Utils.log(); + Utils.log("Falling back to compatibility mode."); try { - String cleanlib = extlib.replace("${arch}", is_64 ? "64" : "32"); - File cleanlibf = new File(cleanlib); - Utils.log("Extracting " + cleanlibf.getName()); - Utils.unzip(cleanlibf, new File(natives)); - } catch (IOException e) + mc.getMethod("main", String[].class).invoke(null, (Object) mcArgs); + } catch (Exception e1) { - System.err.println("Failed to extract native library:"); - e.printStackTrace(System.err); + Utils.log("Failed to invoke the Minecraft main class:", "Fatal"); + e1.printStackTrace(System.err); return -1; } } - Utils.log(); - - System.setProperty("java.library.path", natives); - Field fieldSysPath; - try + return 0; + } + + int launchWithMainClass() + { + // window size, title and state, onesix + if (maximize) { - fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); - fieldSysPath.setAccessible( true ); - fieldSysPath.set( null, null ); - } catch (Exception e) + // FIXME: there is no good way to maximize the minecraft window in onesix. + // the following often breaks linux screen setups + // mcparams.add("--fullscreen"); + } + else { - System.err.println("Failed to set the native library path:"); - e.printStackTrace(System.err); - return -1; + mcparams.add("--width"); + mcparams.add(Integer.toString(winSize.width)); + mcparams.add("--height"); + mcparams.add(Integer.toString(winSize.height)); } - + // Get the Minecraft Class. - final ClassLoader cl = ClassLoader.getSystemClassLoader(); Class<?> mc; try { @@ -161,9 +218,7 @@ public class OneSixLauncher implements Launcher e.printStackTrace(System.err); return -1; } - - // FIXME: works only on linux, we need a better solution -/* + /* final java.nio.ByteBuffer[] icons = IconLoader.load("icon.png"); new Thread() { public void run() { @@ -174,18 +229,21 @@ public class OneSixLauncher implements Launcher Method isCreated; Method setTitle; Method setIcon; - + Field fieldWindowCreated; + Boolean created = false; Display = cl.loadClass("org.lwjgl.opengl.Display"); - isCreated = Display.getMethod("isCreated"); + fieldWindowCreated = Display.getDeclaredField("window_created"); + fieldWindowCreated.setAccessible( true ); setTitle = Display.getMethod("setTitle", String.class); setIcon = Display.getMethod("setIcon", java.nio.ByteBuffer[].class); - + created = (Boolean) fieldWindowCreated.get( null ); // set the window title? Maybe? - while(!(Boolean) isCreated.invoke(null)) + while(!created) { try { Thread.sleep(150); + created = (Boolean) fieldWindowCreated.get( null ); } catch (InterruptedException ignored) {} } // Give it a bit more time ;) @@ -206,12 +264,13 @@ public class OneSixLauncher implements Launcher } } .start(); -*/ - // start Minecraft - String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); // init params accordingly + */ + // init params for the main method to chomp on. + String[] paramsArray = mcparams.toArray(new String[mcparams.size()]); try { - meth.invoke(null, (Object) paramsArray); // static method doesn't have an instance + // static method doesn't have an instance + meth.invoke(null, (Object) paramsArray); } catch (Exception e) { System.err.println("Failed to start Minecraft:"); @@ -220,4 +279,88 @@ public class OneSixLauncher implements Launcher } return 0; } + + @Override + public int launch(ParamBucket params) + { + // get and process the launch script params + try + { + processParams(params); + } catch (NotFoundException e) + { + System.err.println("Not enough arguments."); + e.printStackTrace(System.err); + return -1; + } + + // do some horrible black magic with the classpath + { + List<String> allJars = new ArrayList<String>(); + allJars.addAll(mods); + allJars.addAll(libraries); + + if(!Utils.addToClassPath(allJars)) + { + System.err.println("Halting launch due to previous errors."); + return -1; + } + } + + // print the pretty things + printStats(); + + // extract native libs (depending on platform here... java!) + Utils.log("Preparing native libraries..."); + String property = System.getProperty("os.arch"); + boolean is_64 = property.equalsIgnoreCase("x86_64") || property.equalsIgnoreCase("amd64"); + for(String extlib: extlibs) + { + try + { + String cleanlib = extlib.replace("${arch}", is_64 ? "64" : "32"); + File cleanlibf = new File(cleanlib); + Utils.log("Extracting " + cleanlibf.getName()); + Utils.unzip(cleanlibf, new File(natives)); + } catch (IOException e) + { + System.err.println("Failed to extract native library:"); + e.printStackTrace(System.err); + return -1; + } + } + Utils.log(); + + // set the native libs path... the brute force way + try + { + System.setProperty("java.library.path", natives); + System.setProperty("org.lwjgl.librarypath", natives); + System.setProperty("net.java.games.input.librarypath", natives); + // by the power of reflection, initialize native libs again. DIRTY! + // this is SO BAD. imagine doing that to ld + Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths"); + fieldSysPath.setAccessible( true ); + fieldSysPath.set( null, null ); + } catch (Exception e) + { + System.err.println("Failed to set the native library path:"); + e.printStackTrace(System.err); + return -1; + } + + // grab the system classloader and ... + cl = ClassLoader.getSystemClassLoader(); + + if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch") ) + { + // legacy launch uses the applet wrapper + return legacyLaunch(); + } + else + { + // normal launch just calls main() + return launchWithMainClass(); + } + } } diff --git a/depends/util/src/pathutils.cpp b/depends/util/src/pathutils.cpp index 20888754..3a964806 100644 --- a/depends/util/src/pathutils.cpp +++ b/depends/util/src/pathutils.cpp @@ -138,10 +138,10 @@ void openDirInDefaultProgram(QString path, bool ensureExists) { parentPath.mkpath(dir.absolutePath()); } - QDesktopServices::openUrl("file:///" + dir.absolutePath()); + QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); } void openFileInDefaultProgram(QString filename) { - QDesktopServices::openUrl("file:///" + QFileInfo(filename).absolutePath()); + QDesktopServices::openUrl(QUrl::fromLocalFile(filename)); } diff --git a/gui/ConsoleWindow.cpp b/gui/ConsoleWindow.cpp index ac3752c5..62908d29 100644 --- a/gui/ConsoleWindow.cpp +++ b/gui/ConsoleWindow.cpp @@ -14,29 +14,117 @@ */ #include "ConsoleWindow.h" -#include "ui_ConsoleWindow.h" #include "MultiMC.h" #include <QScrollBar> #include <QMessageBox> #include <QSystemTrayIcon> +#include <QHBoxLayout> +#include <QPushButton> +#include <qlayoutitem.h> #include <gui/Platform.h> #include <gui/dialogs/CustomMessageBox.h> #include <gui/dialogs/ProgressDialog.h> -#include "dialogs/ScreenshotDialog.h" +#include "widgets/PageContainer.h" +#include "pages/LogPage.h" -#include "logic/net/PasteUpload.h" #include "logic/icons/IconList.h" -#include <logic/screenshots/ScreenshotList.h> + +class LogPageProvider : public BasePageProvider +{ +public: + LogPageProvider(BasePageProviderPtr parent, BasePage * log_page) + { + m_parent = parent; + m_log_page = log_page; + } + virtual QString dialogTitle() {return "Fake";}; + virtual QList<BasePage *> getPages() + { + auto pages = m_parent->getPages(); + pages.prepend(m_log_page); + return pages; + } +private: + BasePageProviderPtr m_parent; + BasePage * m_log_page; +}; ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent) - : QMainWindow(parent), ui(new Ui::ConsoleWindow), proc(mcproc) + : QMainWindow(parent), m_proc(mcproc) { MultiMCPlatform::fixWM_CLASS(this); - ui->setupUi(this); - connect(mcproc, SIGNAL(log(QString, MessageLevel::Enum)), this, - SLOT(write(QString, MessageLevel::Enum))); + + auto instance = m_proc->instance(); + auto icon = MMC->icons()->getIcon(instance->iconKey()); + QString windowTitle = tr("Console window for ") + instance->name(); + + // Set window properties + { + setWindowIcon(icon); + setWindowTitle(windowTitle); + } + + // Add page container + { + auto mainLayout = new QVBoxLayout; + auto provider = std::dynamic_pointer_cast<BasePageProvider>(m_proc->instance()); + auto proxy_provider = std::make_shared<LogPageProvider>(provider, new LogPage(m_proc)); + m_container = new PageContainer(proxy_provider, "console", this); + mainLayout->addWidget(m_container); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0,0,0,0); + setLayout(mainLayout); + setCentralWidget(m_container); + } + + // Add custom buttons to the page container layout. + { + auto horizontalLayout = new QHBoxLayout(); + horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + horizontalLayout->setContentsMargins(6, -1, 6, -1); + + auto btnHelp = new QPushButton(); + btnHelp->setText(tr("Help")); + horizontalLayout->addWidget(btnHelp); + connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help())); + + auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout->addSpacerItem(spacer); + + m_killButton = new QPushButton(); + m_killButton->setText(tr("Kill Minecraft")); + horizontalLayout->addWidget(m_killButton); + connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked())); + + m_closeButton = new QPushButton(); + m_closeButton->setText(tr("Close")); + horizontalLayout->addWidget(m_closeButton); + connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked())); + + m_container->addButtons(horizontalLayout); + } + + // restore window state + { + auto base64State = MMC->settings()->get("ConsoleWindowState").toByteArray(); + restoreState(QByteArray::fromBase64(base64State)); + auto base64Geometry = MMC->settings()->get("ConsoleWindowGeometry").toByteArray(); + restoreGeometry(QByteArray::fromBase64(base64Geometry)); + } + + // Set up tray icon + { + m_trayIcon = new QSystemTrayIcon(icon, this); + m_trayIcon->setToolTip(windowTitle); + + connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); + m_trayIcon->show(); + } + + // Set up signal connections 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, @@ -44,34 +132,12 @@ ConsoleWindow::ConsoleWindow(MinecraftProcess *mcproc, QWidget *parent) connect(mcproc, SIGNAL(launch_failed(InstancePtr)), this, SLOT(onLaunchFailed(InstancePtr))); - restoreState( - QByteArray::fromBase64(MMC->settings()->get("ConsoleWindowState").toByteArray())); - restoreGeometry( - QByteArray::fromBase64(MMC->settings()->get("ConsoleWindowGeometry").toByteArray())); - - QString iconKey = proc->instance()->iconKey(); - QString name = proc->instance()->name(); - auto icon = MMC->icons()->getIcon(iconKey); - setWindowIcon(icon); - m_trayIcon = new QSystemTrayIcon(icon, this); - // TODO add screenshot upload as a menu item in the tray icon - QString consoleTitle = tr("Console window for ") + name; - m_trayIcon->setToolTip(consoleTitle); - setWindowTitle(consoleTitle); + setMayClose(false); - connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), - SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); - m_trayIcon->show(); if (mcproc->instance()->settings().get("ShowConsole").toBool()) { show(); } - setMayClose(false); -} - -ConsoleWindow::~ConsoleWindow() -{ - delete ui; } void ConsoleWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) @@ -87,123 +153,23 @@ void ConsoleWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) } } -void ConsoleWindow::writeColor(QString text, const char *color, const char * background) -{ - // append a paragraph - QString newtext; - newtext += "<span style=\""; - { - if (color) - newtext += QString("color:") + color + ";"; - if (background) - newtext += QString("background-color:") + background + ";"; - newtext += "font-family: monospace;"; - } - newtext += "\">"; - newtext += text.toHtmlEscaped(); - newtext += "</span>"; - ui->text->appendHtml(newtext); -} - -void ConsoleWindow::write(QString data, MessageLevel::Enum mode) -{ - QScrollBar *bar = ui->text->verticalScrollBar(); - int max_bar = bar->maximum(); - int val_bar = bar->value(); - if(isVisible()) - { - if (m_scroll_active) - { - m_scroll_active = (max_bar - val_bar) <= 1; - } - else - { - m_scroll_active = val_bar == max_bar; - } - } - if (data.endsWith('\n')) - data = data.left(data.length() - 1); - QStringList paragraphs = data.split('\n'); - for (QString ¶graph : paragraphs) - { - paragraph = paragraph.trimmed(); - } - - QListIterator<QString> iter(paragraphs); - if (mode == MessageLevel::MultiMC) - while (iter.hasNext()) - writeColor(iter.next(), "blue", 0); - else if (mode == MessageLevel::Error) - while (iter.hasNext()) - writeColor(iter.next(), "red", 0); - else if (mode == MessageLevel::Warning) - while (iter.hasNext()) - writeColor(iter.next(), "orange", 0); - else if (mode == MessageLevel::Fatal) - while (iter.hasNext()) - writeColor(iter.next(), "red", "black"); - else if (mode == MessageLevel::Debug) - while (iter.hasNext()) - writeColor(iter.next(), "green", 0); - else if (mode == MessageLevel::PrePost) - while (iter.hasNext()) - writeColor(iter.next(), "grey", 0); - // TODO: implement other MessageLevels - else - while (iter.hasNext()) - writeColor(iter.next(), 0, 0); - if(isVisible()) - { - if (m_scroll_active) - { - bar->setValue(bar->maximum()); - } - m_last_scroll_value = bar->value(); - } -} - -void ConsoleWindow::clear() -{ - ui->text->clear(); -} - 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) - ui->closeButton->setText(tr("Close")); + m_closeButton->setText(tr("Close")); else - ui->closeButton->setText(tr("Hide")); + m_closeButton->setText(tr("Hide")); m_mayclose = mayclose; } void ConsoleWindow::toggleConsole() { - QScrollBar *bar = ui->text->verticalScrollBar(); + //QScrollBar *bar = ui->text->verticalScrollBar(); if (isVisible()) { if(!isActiveWindow()) @@ -211,15 +177,17 @@ void ConsoleWindow::toggleConsole() activateWindow(); return; } + /* int max_bar = bar->maximum(); int val_bar = m_last_scroll_value = bar->value(); m_scroll_active = (max_bar - val_bar) <= 1; + */ hide(); } else { show(); - isTopLevel(); + /* if (m_scroll_active) { bar->setValue(bar->maximum()); @@ -228,6 +196,7 @@ void ConsoleWindow::toggleConsole() { bar->setValue(m_last_scroll_value); } + */ } } @@ -250,25 +219,23 @@ void ConsoleWindow::closeEvent(QCloseEvent *event) void ConsoleWindow::on_btnKillMinecraft_clicked() { - ui->btnKillMinecraft->setEnabled(false); + m_killButton->setEnabled(false); auto response = CustomMessageBox::selectable( this, tr("Kill Minecraft?"), tr("This can cause the instance to get corrupted and should only be used if Minecraft " "is frozen for some reason"), QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)->exec(); if (response == QMessageBox::Yes) - proc->killMinecraft(); + m_proc->killMinecraft(); else - ui->btnKillMinecraft->setEnabled(true); + m_killButton->setEnabled(true); } void ConsoleWindow::onEnded(InstancePtr instance, int code, QProcess::ExitStatus status) { bool peacefulExit = code == 0 && status != QProcess::CrashExit; - ui->btnKillMinecraft->setEnabled(false); - + m_killButton->setEnabled(false); setMayClose(true); - if (instance->settings().get("AutoCloseConsole").toBool()) { if (peacefulExit) @@ -277,15 +244,8 @@ void ConsoleWindow::onEnded(InstancePtr instance, int code, QProcess::ExitStatus return; } } - /* - if(!peacefulExit) - { - m_trayIcon->showMessage(tr("Oh no!"), tr("Minecraft crashed!"), QSystemTrayIcon::Critical); - } - */ if (!isVisible()) show(); - // Raise Window if (MMC->settings()->get("RaiseConsole").toBool()) { @@ -296,23 +256,10 @@ void ConsoleWindow::onEnded(InstancePtr instance, int code, QProcess::ExitStatus void ConsoleWindow::onLaunchFailed(InstancePtr instance) { - ui->btnKillMinecraft->setEnabled(false); + m_killButton->setEnabled(false); setMayClose(true); if (!isVisible()) show(); } - -void ConsoleWindow::on_btnPaste_clicked() -{ - auto text = ui->text->toPlainText(); - ProgressDialog dialog(this); - PasteUpload *paste = new PasteUpload(this, text); - dialog.exec(paste); - if (!paste->successful()) - { - CustomMessageBox::selectable(this, "Upload failed", paste->failReason(), - QMessageBox::Critical)->exec(); - } -} diff --git a/gui/ConsoleWindow.h b/gui/ConsoleWindow.h index 17c64392..97600baa 100644 --- a/gui/ConsoleWindow.h +++ b/gui/ConsoleWindow.h @@ -19,18 +19,15 @@ #include <QSystemTrayIcon> #include "logic/MinecraftProcess.h" -namespace Ui -{ -class ConsoleWindow; -} - +class QPushButton; +class PageContainer; class ConsoleWindow : public QMainWindow { Q_OBJECT public: explicit ConsoleWindow(MinecraftProcess *proc, QWidget *parent = 0); - ~ConsoleWindow(); + virtual ~ConsoleWindow() {}; /** * @brief specify if the window is allowed to close @@ -39,38 +36,12 @@ public: */ void setMayClose(bool mayclose); -private: - /** - * @brief write a colored paragraph - * @param data the string - * @param color the css color name - * this will only insert a single paragraph. - * \n are ignored. a real \n is always appended. - */ - void writeColor(QString text, const char *color, const char *background); - signals: void isClosing(); -public -slots: - /** - * @brief write a string - * @param data the string - * @param mode the WriteMode - * lines have to be put through this as a whole! - */ - void write(QString data, MessageLevel::Enum level = MessageLevel::MultiMC); - - /** - * @brief clear the text widget - */ - void clear(); - private slots: void on_closeButton_clicked(); - void on_btnScreenshots_clicked(); void on_btnKillMinecraft_clicked(); void onEnded(InstancePtr instance, int code, QProcess::ExitStatus status); @@ -79,18 +50,16 @@ slots: // FIXME: add handlers for the other MinecraftProcess signals (pre/post launch command // failures) - void on_btnPaste_clicked(); void iconActivated(QSystemTrayIcon::ActivationReason); void toggleConsole(); protected: void closeEvent(QCloseEvent *); private: - Ui::ConsoleWindow *ui = nullptr; - MinecraftProcess *proc = nullptr; + MinecraftProcess *m_proc = nullptr; bool m_mayclose = true; - int m_last_scroll_value = 0; - bool m_scroll_active = true; QSystemTrayIcon *m_trayIcon = nullptr; - int m_saved_offset = 0; + PageContainer *m_container = nullptr; + QPushButton *m_closeButton = nullptr; + QPushButton *m_killButton = nullptr; }; diff --git a/gui/ConsoleWindow.ui b/gui/ConsoleWindow.ui deleted file mode 100644 index 344cf74d..00000000 --- a/gui/ConsoleWindow.ui +++ /dev/null @@ -1,93 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ConsoleWindow</class> - <widget class="QMainWindow" name="ConsoleWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>640</width> - <height>440</height> - </rect> - </property> - <property name="windowTitle"> - <string>MultiMC Console</string> - </property> - <widget class="QWidget" name="centralwidget"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QPlainTextEdit" name="text"> - <property name="undoRedoEnabled"> - <bool>false</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - <property name="plainText"> - <string notr="true"/> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - <property name="centerOnScroll"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="leftMargin"> - <number>6</number> - </property> - <property name="rightMargin"> - <number>6</number> - </property> - <item> - <widget class="QPushButton" name="btnPaste"> - <property name="text"> - <string>Upload Log</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="btnScreenshots"> - <property name="text"> - <string>Manage Screenshots</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="btnKillMinecraft"> - <property name="text"> - <string>&Kill Minecraft</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="closeButton"> - <property name="text"> - <string>&Close</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index 9661537a..bee250c4 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -56,9 +56,7 @@ #include "gui/dialogs/VersionSelectDialog.h" #include "gui/dialogs/CustomMessageBox.h" #include "gui/dialogs/LwjglSelectDialog.h" -#include "gui/dialogs/InstanceSettings.h" #include "gui/dialogs/IconPickerDialog.h" -#include "gui/dialogs/EditNotesDialog.h" #include "gui/dialogs/CopyInstanceDialog.h" #include "gui/dialogs/AccountListDialog.h" #include "gui/dialogs/AccountSelectDialog.h" @@ -67,12 +65,13 @@ #include "gui/dialogs/NotificationDialog.h" #include "gui/ConsoleWindow.h" +#include "pagedialog/PageDialog.h" -#include "logic/lists/InstanceList.h" -#include "logic/lists/MinecraftVersionList.h" -#include "logic/lists/LwjglVersionList.h" +#include "logic/InstanceList.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/LwjglVersionList.h" #include "logic/icons/IconList.h" -#include "logic/lists/JavaVersionList.h" +#include "logic/java/JavaVersionList.h" #include "logic/auth/flows/AuthenticateTask.h" #include "logic/auth/flows/RefreshTask.h" @@ -90,7 +89,7 @@ #include "logic/InstanceFactory.h" #include "logic/MinecraftProcess.h" #include "logic/OneSixUpdate.h" -#include "logic/JavaUtils.h" +#include "logic/java/JavaUtils.h" #include "logic/NagUtils.h" #include "logic/SkinUtils.h" @@ -109,7 +108,8 @@ 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(BuildConfig.printableVersionString()); + QString winTitle = + QString("MultiMC 5 - Version %1").arg(BuildConfig.printableVersionString()); if (!BuildConfig.BUILD_PLATFORM.isEmpty()) winTitle += " on " + BuildConfig.BUILD_PLATFORM; setWindowTitle(winTitle); @@ -119,7 +119,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // Global shortcuts { - //FIXME: This is kinda weird. and bad. We need some kind of managed shutdown. + // 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())); } @@ -258,7 +258,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi auto accounts = MMC->accounts(); - QList<CacheDownloadPtr> skin_dls; + QList<CacheDownloadPtr> skin_dls; for (int i = 0; i < accounts->count(); i++) { auto account = accounts->at(i); @@ -269,21 +269,21 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi auto meta = MMC->metacache()->resolveEntry("skins", profile.name + ".png"); auto action = CacheDownload::make( QUrl("http://" + URLConstants::SKINS_BASE + profile.name + ".png"), meta); - skin_dls.append(action); + skin_dls.append(action); meta->stale = true; } } } - if(!skin_dls.isEmpty()) - { - auto job = new NetJob("Startup player skins download"); - connect(job, SIGNAL(succeeded()), SLOT(skinJobFinished())); - connect(job, SIGNAL(failed()), SLOT(skinJobFinished())); - for(auto action: skin_dls) - job->addNetAction(action); - skin_download_job.reset(job); - job->start(); - } + if (!skin_dls.isEmpty()) + { + auto job = new NetJob("Startup player skins download"); + connect(job, SIGNAL(succeeded()), SLOT(skinJobFinished())); + connect(job, SIGNAL(failed()), SLOT(skinJobFinished())); + for (auto action : skin_dls) + job->addNetAction(action); + skin_download_job.reset(job); + job->start(); + } // run the things that load and download other things... FIXME: this is NOT the place // FIXME: invisible actions in the background = NOPE. @@ -334,11 +334,10 @@ MainWindow::~MainWindow() void MainWindow::skinJobFinished() { - activeAccountChanged(); - skin_download_job.reset(); + activeAccountChanged(); + skin_download_job.reset(); } - void MainWindow::showInstanceContextMenu(const QPoint &pos) { QList<QAction *> actions; @@ -360,9 +359,10 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) 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())); + 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); @@ -377,7 +377,8 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) QAction *actionCreateInstance = new QAction(tr("Create instance"), this); actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip()); - connect(actionCreateInstance, SIGNAL(triggered(bool)), SLOT(on_actionAddInstance_triggered())); + connect(actionCreateInstance, SIGNAL(triggered(bool)), + SLOT(on_actionAddInstance_triggered())); actions.prepend(actionSep); actions.prepend(actionVoid); @@ -385,7 +386,7 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) } QMenu myMenu; myMenu.addActions(actions); - if(onInstance) + if (onInstance) myMenu.setEnabled(m_selectedInstance->canLaunch()); myMenu.exec(view->mapToGlobal(pos)); } @@ -398,7 +399,8 @@ void MainWindow::updateToolsMenu() } QMenu *launchMenu = new QMenu(this); QAction *normalLaunch = launchMenu->addAction(tr("Launch")); - connect(normalLaunch, &QAction::triggered, [this](){doLaunch();}); + connect(normalLaunch, &QAction::triggered, [this]() + { doLaunch(); }); launchMenu->addSeparator()->setText(tr("Profilers")); for (auto profiler : MMC->profilers().values()) { @@ -407,11 +409,13 @@ void MainWindow::updateToolsMenu() if (!profiler->check(&error)) { profilerAction->setDisabled(true); - profilerAction->setToolTip(tr("Profiler not setup correctly. Go into settings, \"External Tools\".")); + profilerAction->setToolTip( + tr("Profiler not setup correctly. Go into settings, \"External Tools\".")); } else { - connect(profilerAction, &QAction::triggered, [this, profiler](){doLaunch(true, profiler.get());}); + connect(profilerAction, &QAction::triggered, [this, profiler]() + { doLaunch(true, profiler.get()); }); } } launchMenu->addSeparator()->setText(tr("Tools")); @@ -422,14 +426,13 @@ void MainWindow::updateToolsMenu() if (!tool->check(&error)) { toolAction->setDisabled(true); - toolAction->setToolTip(tr("Tool not setup correctly. Go into settings, \"External Tools\".")); + toolAction->setToolTip( + tr("Tool not setup correctly. Go into settings, \"External Tools\".")); } else { connect(toolAction, &QAction::triggered, [this, tool]() - { - tool->createDetachedTool(m_selectedInstance, this)->run(); - }); + { tool->createDetachedTool(m_selectedInstance, this)->run(); }); } } ui->actionLaunchInstance->setMenu(launchMenu); @@ -671,7 +674,7 @@ void MainWindow::downloadUpdates(QString repo, int versionId, bool installOnExit if (updateDlg.exec(&updateTask)) { UpdateFlags baseFlags = None; - if(BuildConfig.UPDATER_DRY_RUN) + if (BuildConfig.UPDATER_DRY_RUN) baseFlags |= DryRun; if (installOnExit) MMC->installUpdates(updateTask.updateFilesDir(), baseFlags | OnExit); @@ -708,6 +711,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()) { @@ -864,17 +872,14 @@ void MainWindow::updateInstanceToolIcon(QString new_icon) void MainWindow::setSelectedInstanceById(const QString &id) { - QModelIndex selectionIndex = proxymodel->index(0, 0); - if (!id.isNull()) + if (id.isNull()) + return; + const QModelIndex index = MMC->instances()->getInstanceIndexById(id); + if (index.isValid()) { - const QModelIndex index = MMC->instances()->getInstanceIndexById(id); - if (index.isValid()) - { - selectionIndex = proxymodel->mapFromSource(index); - } + QModelIndex selectionIndex = proxymodel->mapFromSource(index); + view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); } - view->selectionModel()->setCurrentIndex(selectionIndex, - QItemSelectionModel::ClearAndSelect); } void MainWindow::on_actionChangeInstGroup_triggered() @@ -938,6 +943,37 @@ void MainWindow::on_actionSettings_triggered() updateToolsMenu(); } +template <typename T> +void ShowPageDialog(T raw_provider, QWidget * parent, QString open_page = QString()) +{ + auto provider = std::dynamic_pointer_cast<BasePageProvider>(raw_provider); + if(!provider) + return; + PageDialog dlg(provider, open_page, parent); + dlg.exec(); +} + +void MainWindow::on_actionInstanceSettings_triggered() +{ + ShowPageDialog(m_selectedInstance, this, "settings"); +} + +void MainWindow::on_actionEditInstNotes_triggered() +{ + ShowPageDialog(m_selectedInstance, this, "notes"); +} + +void MainWindow::on_actionEditInstance_triggered() +{ + ShowPageDialog(m_selectedInstance, this); +} + +void MainWindow::on_actionScreenshots_triggered() +{ + ShowPageDialog(m_selectedInstance, this, "screenshots"); +} + + void MainWindow::on_actionManageAccounts_triggered() { AccountListDialog dialog(this); @@ -1026,17 +1062,6 @@ void MainWindow::on_actionViewSelectedInstFolder_triggered() } } -void MainWindow::on_actionEditInstMods_triggered() -{ - if (m_selectedInstance) - { - auto dialog = m_selectedInstance->createModEditDialog(this); - if (dialog) - dialog->exec(); - dialog->deleteLater(); - } -} - void MainWindow::closeEvent(QCloseEvent *event) { // Save the window state and geometry. @@ -1062,10 +1087,10 @@ void MainWindow::instanceActivated(QModelIndex index) { if (!index.isValid()) return; - QString id = index.data(InstanceList::InstanceIDRole).toString(); + QString id = index.data(InstanceList::InstanceIDRole).toString(); InstancePtr inst = MMC->instances()->getInstanceById(id); - if(!inst) - return; + if (!inst) + return; NagUtils::checkJVMArgs(inst->settings().get("JvmArgs").toString(), this); @@ -1218,7 +1243,8 @@ void MainWindow::doLaunch(bool online, BaseProfilerFactory *profiler) } } -void MainWindow::updateInstance(InstancePtr instance, AuthSessionPtr session, BaseProfilerFactory *profiler) +void MainWindow::updateInstance(InstancePtr instance, AuthSessionPtr session, + BaseProfilerFactory *profiler) { auto updateTask = instance->doUpdate(); if (!updateTask) @@ -1233,14 +1259,15 @@ void MainWindow::updateInstance(InstancePtr instance, AuthSessionPtr session, Ba tDialog.exec(updateTask.get()); } -void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, BaseProfilerFactory *profiler) +void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, + BaseProfilerFactory *profiler) { Q_ASSERT_X(instance != NULL, "launchInstance", "instance is NULL"); Q_ASSERT_X(session.get() != nullptr, "launchInstance", "session is NULL"); QString launchScript; - if(!instance->prepareForLaunch(session, launchScript)) + if (!instance->prepareForLaunch(session, launchScript)) return; MinecraftProcess *proc = new MinecraftProcess(instance); @@ -1260,7 +1287,8 @@ void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, Ba QString error; if (!profiler->check(&error)) { - QMessageBox::critical(this, tr("Error"), tr("Couldn't start profiler: %1").arg(error)); + QMessageBox::critical(this, tr("Error"), + tr("Couldn't start profiler: %1").arg(error)); proc->abort(); return; } @@ -1270,9 +1298,11 @@ void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, Ba dialog.setMaximum(0); dialog.setValue(0); dialog.setLabelText(tr("Waiting for profiler...")); - connect(&dialog, &QProgressDialog::canceled, profilerInstance, &BaseProfiler::abortProfiling); + connect(&dialog, &QProgressDialog::canceled, profilerInstance, + &BaseProfiler::abortProfiling); dialog.show(); - connect(profilerInstance, &BaseProfiler::readyToLaunch, [&dialog, this, proc](const QString &message) + connect(profilerInstance, &BaseProfiler::readyToLaunch, + [&dialog, this, proc](const QString & message) { dialog.accept(); QMessageBox msg; @@ -1285,7 +1315,8 @@ void MainWindow::launchInstance(InstancePtr instance, AuthSessionPtr session, Ba msg.exec(); proc->launch(); }); - connect(profilerInstance, &BaseProfiler::abortLaunch, [&dialog, this, proc](const QString &message) + connect(profilerInstance, &BaseProfiler::abortLaunch, + [&dialog, this, proc](const QString & message) { dialog.accept(); QMessageBox msg; @@ -1333,119 +1364,26 @@ void MainWindow::startTask(Task *task) task->start(); } -// Create A Desktop Shortcut -void MainWindow::on_actionMakeDesktopShortcut_triggered() -{ - QString name("Test"); - name = QInputDialog::getText(this, tr("MultiMC Shortcut"), tr("Enter a Shortcut Name."), - QLineEdit::Normal, name); - - Util::createShortCut(Util::getDesktopDir(), QApplication::instance()->applicationFilePath(), - QStringList() << "-dl" << QDir::currentPath() << "test", name, - "application-x-octet-stream"); - - CustomMessageBox::selectable( - this, tr("Not useful"), - tr("A Dummy Shortcut was created. it will not do anything productive"), - QMessageBox::Warning)->show(); -} - // BrowserDialog void MainWindow::openWebPage(QUrl url) { QDesktopServices::openUrl(url); } -void MainWindow::on_actionChangeInstMCVersion_triggered() +void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &previous) { - if (view->selectionModel()->selectedIndexes().count() < 1) - return; - - VersionSelectDialog vselect(m_selectedInstance->versionList().get(), - tr("Change Minecraft version"), this); - vselect.setFuzzyFilter(1, "*OneSix*"); - if (!vselect.exec() || !vselect.selectedVersion()) - return; - - if (!MMC->accounts()->anyAccountIsValid()) - { - CustomMessageBox::selectable( - this, tr("Error"), - tr("MultiMC cannot download Minecraft or update instances unless you have at least " - "one account added.\nPlease add your Mojang or Minecraft account."), - QMessageBox::Warning)->show(); - return; - } - - if (m_selectedInstance->versionIsCustom()) - { - auto result = CustomMessageBox::selectable( - this, tr("Are you sure?"), - tr("This will remove any library/version customization you did previously. " - "This includes things like Forge install and similar."), - QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Abort, - QMessageBox::Abort)->exec(); - - if (result != QMessageBox::Ok) - return; - } - m_selectedInstance->setIntendedVersionId(vselect.selectedVersion()->descriptor()); - - auto updateTask = m_selectedInstance->doUpdate(); - if (!updateTask) + if(!current.isValid()) { + MMC->settings()->set("SelectedInstance", QString()); + selectionBad(); return; } - ProgressDialog tDialog(this); - connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); - tDialog.exec(updateTask.get()); -} - -void MainWindow::on_actionChangeInstLWJGLVersion_triggered() -{ - if (!m_selectedInstance) - return; - - LWJGLSelectDialog lselect(this); - lselect.exec(); - if (lselect.result() == QDialog::Accepted) - { - auto ptr = std::dynamic_pointer_cast<LegacyInstance>(m_selectedInstance); - if(ptr) - ptr->setLWJGLVersion(lselect.selectedVersion()); - } -} - -void MainWindow::on_actionInstanceSettings_triggered() -{ - if (view->selectionModel()->selectedIndexes().count() < 1) - return; - - InstanceSettings settings(&m_selectedInstance->settings(), this); - settings.setWindowTitle(tr("Instance settings")); - settings.exec(); -} - -void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &previous) -{ - if(!current.isValid()) - { - selectionBad(); - MMC->settings()->set("SelectedInstance", QString()); - return; - } - QString id = current.data(InstanceList::InstanceIDRole).toString(); - m_selectedInstance = MMC->instances()->getInstanceById(id); + QString id = current.data(InstanceList::InstanceIDRole).toString(); + m_selectedInstance = MMC->instances()->getInstanceById(id); if ( m_selectedInstance ) { ui->instanceToolBar->setEnabled(m_selectedInstance->canLaunch()); renameButton->setText(m_selectedInstance->name()); - ui->actionChangeInstLWJGLVersion->setEnabled( - m_selectedInstance->menuActionEnabled("actionChangeInstLWJGLVersion")); - ui->actionEditInstMods->setEnabled( - m_selectedInstance->menuActionEnabled("actionEditInstMods")); - ui->actionChangeInstMCVersion->setEnabled( - m_selectedInstance->menuActionEnabled("actionChangeInstMCVersion")); m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); updateInstanceToolIcon(m_selectedInstance->iconKey()); @@ -1455,9 +1393,9 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & } else { - selectionBad(); - MMC->settings()->set("SelectedInstance", QString()); - return; + MMC->settings()->set("SelectedInstance", QString()); + selectionBad(); + return; } } @@ -1475,20 +1413,6 @@ void MainWindow::selectionBad() setSelectedInstanceById(MMC->settings()->get("SelectedInstance").toString()); } -void MainWindow::on_actionEditInstNotes_triggered() -{ - if (!m_selectedInstance) - return; - - EditNotesDialog noteedit(m_selectedInstance->notes(), m_selectedInstance->name(), this); - noteedit.exec(); - if (noteedit.result() == QDialog::Accepted) - { - - m_selectedInstance->setNotes(noteedit.getText()); - } -} - void MainWindow::instanceEnded() { this->show(); @@ -1522,7 +1446,9 @@ void MainWindow::checkMigrateLegacyAssets() void MainWindow::checkSetDefaultJava() { + const QString javaHack = "IntelHack"; bool askForJava = false; + do { QString currentHostName = QHostInfo::localHostName(); QString oldHostName = MMC->settings()->get("LastHostname").toString(); @@ -1530,16 +1456,30 @@ void MainWindow::checkSetDefaultJava() { MMC->settings()->set("LastHostname", currentHostName); askForJava = true; + break; } - } - - { QString currentJavaPath = MMC->settings()->get("JavaPath").toString(); if (currentJavaPath.isEmpty()) { askForJava = true; + break; } - } + #if defined Q_OS_WIN32 + QString currentHack = MMC->settings()->get("JavaDetectionHack").toString(); + if (currentHack != javaHack) + { + CustomMessageBox::selectable( + this, tr("Java detection forced"), + tr("Because of graphics performance issues caused by Intel drivers on Windows, " + "MultiMC java detection was forced. Please select a Java " + "version.<br/><br/>If you have custom java versions set for your instances, " + "make sure you use the 'javaw.exe' executable."), + QMessageBox::Warning)->exec(); + askForJava = true; + break; + } + #endif + } while (0); if (askForJava) { @@ -1567,7 +1507,10 @@ void MainWindow::checkSetDefaultJava() java = ju.GetDefaultJava(); } if (java) + { MMC->settings()->set("JavaPath", java->path); + MMC->settings()->set("JavaDetectionHack", javaHack); + } else MMC->settings()->set("JavaPath", QString("java")); } diff --git a/gui/MainWindow.h b/gui/MainWindow.h index 45ca520c..182e9c0c 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -19,11 +19,10 @@ #include <QProcess> #include <QTimer> -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" #include "logic/BaseInstance.h" - #include "logic/auth/MojangAccount.h" -#include <logic/net/NetJob.h> +#include "logic/net/NetJob.h" class QToolButton; class LabeledToolButton; @@ -81,6 +80,8 @@ slots: void on_actionSettings_triggered(); + void on_actionInstanceSettings_triggered(); + void on_actionManageAccounts_triggered(); void on_actionReportBug_triggered(); @@ -103,14 +104,12 @@ slots: void on_actionRenameInstance_triggered(); - void on_actionMakeDesktopShortcut_triggered(); - - void on_actionChangeInstMCVersion_triggered(); - - void on_actionEditInstMods_triggered(); + void on_actionEditInstance_triggered(); 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. @@ -133,12 +132,8 @@ slots: void taskStart(); void taskEnd(); - void on_actionChangeInstLWJGLVersion_triggered(); - void instanceEnded(); - void on_actionInstanceSettings_triggered(); - // called when an icon is changed in the icon model. void iconUpdated(QString); diff --git a/gui/MainWindow.ui b/gui/MainWindow.ui index 47789449..7adc5d37 100644 --- a/gui/MainWindow.ui +++ b/gui/MainWindow.ui @@ -115,10 +115,8 @@ <addaction name="separator"/> <addaction name="actionScreenshots"/> <addaction name="separator"/> + <addaction name="actionEditInstance"/> <addaction name="actionInstanceSettings"/> - <addaction name="actionChangeInstMCVersion"/> - <addaction name="actionChangeInstLWJGLVersion"/> - <addaction name="actionEditInstMods"/> <addaction name="actionViewSelectedInstFolder"/> <addaction name="actionConfig_Folder"/> <addaction name="separator"/> @@ -388,82 +386,18 @@ <string>Edit the notes for the selected instance.</string> </property> </action> - <action name="actionInstanceSettings"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="text"> - <string>Settings</string> - </property> - <property name="toolTip"> - <string>Change settings for the selected instance.</string> - </property> - <property name="statusTip"> - <string>Change settings for the selected instance.</string> - </property> - </action> - <action name="actionMakeDesktopShortcut"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Make Shortcut</string> - </property> - <property name="toolTip"> - <string>Make a shortcut on the desktop for the selected instance.</string> - </property> - <property name="statusTip"> - <string>Make a shortcut on the desktop for the selected instance.</string> - </property> - </action> - <action name="actionManageInstSaves"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Manage Saves</string> - </property> - <property name="toolTip"> - <string>Manage saves for the selected instance.</string> - </property> - <property name="statusTip"> - <string>Manage saves for the selected instance.</string> - </property> - </action> - <action name="actionEditInstMods"> + <action name="actionEditInstance"> <property name="text"> <string>Edit Mods</string> </property> - <property name="toolTip"> - <string>Edit the mods for the selected instance.</string> - </property> - <property name="statusTip"> - <string>Edit the mods for the selected instance.</string> - </property> - </action> - <action name="actionChangeInstMCVersion"> - <property name="text"> - <string>Change Version</string> - </property> - <property name="toolTip"> - <string>Change the selected instance's Minecraft version.</string> - </property> - <property name="statusTip"> - <string>Change the selected instance's Minecraft version.</string> - </property> - </action> - <action name="actionChangeInstLWJGLVersion"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Change LWJGL</string> + <property name="iconText"> + <string>Edit Instance</string> </property> <property name="toolTip"> - <string>Change the version of LWJGL for the selected instance to use.</string> + <string>Change the instance settings, mods and versions.</string> </property> <property name="statusTip"> - <string>Change the version of LWJGL for the selected instance to use.</string> + <string>Change the instance settings, mods and versions.</string> </property> </action> <action name="actionViewSelectedInstFolder"> @@ -555,6 +489,14 @@ <string><html><head/><body><p>View and upload screenshots for this instance</p></body></html></string> </property> </action> + <action name="actionInstanceSettings"> + <property name="text"> + <string>Instance Settings</string> + </property> + <property name="toolTip"> + <string>Change the settings specific to the instance</string> + </property> + </action> </widget> <layoutdefault spacing="6" margin="11"/> <resources> diff --git a/gui/dialogs/CopyInstanceDialog.cpp b/gui/dialogs/CopyInstanceDialog.cpp index 71429367..188cf274 100644 --- a/gui/dialogs/CopyInstanceDialog.cpp +++ b/gui/dialogs/CopyInstanceDialog.cpp @@ -28,7 +28,6 @@ #include "logic/InstanceFactory.h" #include "logic/BaseVersion.h" #include "logic/icons/IconList.h" -#include "logic/lists/MinecraftVersionList.h" #include "logic/tasks/Task.h" #include "logic/BaseInstance.h" diff --git a/gui/dialogs/EditNotesDialog.cpp b/gui/dialogs/EditNotesDialog.cpp deleted file mode 100644 index f2aa029f..00000000 --- a/gui/dialogs/EditNotesDialog.cpp +++ /dev/null @@ -1,43 +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. - */ - -#include "EditNotesDialog.h" -#include "ui_EditNotesDialog.h" -#include "gui/Platform.h" - -#include <QIcon> -#include <QApplication> - -EditNotesDialog::EditNotesDialog(QString notes, QString name, QWidget *parent) - : QDialog(parent), ui(new Ui::EditNotesDialog), m_instance_name(name), - m_instance_notes(notes) -{ - MultiMCPlatform::fixWM_CLASS(this); - ui->setupUi(this); - ui->noteEditor->setText(notes); - setWindowTitle(tr("Edit notes of %1").arg(m_instance_name)); - // connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); -} - -EditNotesDialog::~EditNotesDialog() -{ - delete ui; -} - -QString EditNotesDialog::getText() -{ - QString test = ui->noteEditor->toPlainText(); - return test; -} diff --git a/gui/dialogs/EditNotesDialog.ui b/gui/dialogs/EditNotesDialog.ui deleted file mode 100644 index 487dfb84..00000000 --- a/gui/dialogs/EditNotesDialog.ui +++ /dev/null @@ -1,77 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>EditNotesDialog</class> - <widget class="QDialog" name="EditNotesDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>459</width> - <height>399</height> - </rect> - </property> - <property name="windowTitle"> - <string>Edit Notes</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTextEdit" name="noteEditor"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOn</enum> - </property> - <property name="acceptRichText"> - <bool>false</bool> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </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> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>EditNotesDialog</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>248</x> - <y>254</y> - </hint> - <hint type="destinationlabel"> - <x>157</x> - <y>274</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>EditNotesDialog</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>316</x> - <y>260</y> - </hint> - <hint type="destinationlabel"> - <x>286</x> - <y>274</y> - </hint> - </hints> - </connection> - </connections> -</ui> diff --git a/gui/dialogs/InstanceSettings.cpp b/gui/dialogs/InstanceSettings.cpp deleted file mode 100644 index edb4a921..00000000 --- a/gui/dialogs/InstanceSettings.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Authors: Andrew Okin - * Peterix - * Orochimarufan <orochimarufan.x3@gmail.com> - * - * 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 "MultiMC.h" -#include "InstanceSettings.h" -#include "ui_InstanceSettings.h" -#include "gui/Platform.h" -#include "gui/dialogs/VersionSelectDialog.h" - -#include "logic/JavaUtils.h" -#include "logic/NagUtils.h" -#include "logic/lists/JavaVersionList.h" -#include "logic/JavaChecker.h" - -#include <QFileDialog> -#include <QMessageBox> - -InstanceSettings::InstanceSettings(SettingsObject *obj, QWidget *parent) - : QDialog(parent), ui(new Ui::InstanceSettings), m_obj(obj) -{ - MultiMCPlatform::fixWM_CLASS(this); - ui->setupUi(this); - - restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("SettingsGeometry").toByteArray())); - - loadSettings(); -} - -InstanceSettings::~InstanceSettings() -{ - delete ui; -} - -void InstanceSettings::showEvent(QShowEvent *ev) -{ - QDialog::showEvent(ev); -} - -void InstanceSettings::closeEvent(QCloseEvent *ev) -{ - MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64()); - - QDialog::closeEvent(ev); -} - -void InstanceSettings::on_customCommandsGroupBox_toggled(bool state) -{ - ui->labelCustomCmdsDescription->setEnabled(state); -} - -void InstanceSettings::on_buttonBox_accepted() -{ - MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64()); - - applySettings(); - accept(); -} - -void InstanceSettings::on_buttonBox_rejected() -{ - MMC->settings()->set("SettingsGeometry", saveGeometry().toBase64()); - - reject(); -} - -void InstanceSettings::applySettings() -{ - // Console - bool console = ui->consoleSettingsBox->isChecked(); - m_obj->set("OverrideConsole", console); - if (console) - { - m_obj->set("ShowConsole", ui->showConsoleCheck->isChecked()); - m_obj->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); - } - else - { - m_obj->reset("ShowConsole"); - m_obj->reset("AutoCloseConsole"); - } - - // Window Size - bool window = ui->windowSizeGroupBox->isChecked(); - m_obj->set("OverrideWindow", window); - if (window) - { - m_obj->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); - m_obj->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); - m_obj->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); - } - else - { - m_obj->reset("LaunchMaximized"); - m_obj->reset("MinecraftWinWidth"); - m_obj->reset("MinecraftWinHeight"); - } - - // Memory - bool memory = ui->memoryGroupBox->isChecked(); - m_obj->set("OverrideMemory", memory); - if (memory) - { - m_obj->set("MinMemAlloc", ui->minMemSpinBox->value()); - m_obj->set("MaxMemAlloc", ui->maxMemSpinBox->value()); - m_obj->set("PermGen", ui->permGenSpinBox->value()); - } - else - { - m_obj->reset("MinMemAlloc"); - m_obj->reset("MaxMemAlloc"); - m_obj->reset("PermGen"); - } - - // Java Settings - bool java = ui->javaSettingsGroupBox->isChecked(); - m_obj->set("OverrideJava", java); - if (java) - { - m_obj->set("JavaPath", ui->javaPathTextBox->text()); - m_obj->set("JvmArgs", ui->jvmArgsTextBox->text()); - - NagUtils::checkJVMArgs(m_obj->get("JvmArgs").toString(), this->parentWidget()); - } - else - { - m_obj->reset("JavaPath"); - m_obj->reset("JvmArgs"); - } - - // Custom Commands - bool custcmd = ui->customCommandsGroupBox->isChecked(); - m_obj->set("OverrideCommands", custcmd); - if (custcmd) - { - m_obj->set("PreLaunchCommand", ui->preLaunchCmdTextBox->text()); - m_obj->set("PostExitCommand", ui->postExitCmdTextBox->text()); - } - else - { - m_obj->reset("PreLaunchCommand"); - m_obj->reset("PostExitCommand"); - } -} - -void InstanceSettings::loadSettings() -{ - // Console - ui->consoleSettingsBox->setChecked(m_obj->get("OverrideConsole").toBool()); - ui->showConsoleCheck->setChecked(m_obj->get("ShowConsole").toBool()); - ui->autoCloseConsoleCheck->setChecked(m_obj->get("AutoCloseConsole").toBool()); - - // Window Size - ui->windowSizeGroupBox->setChecked(m_obj->get("OverrideWindow").toBool()); - ui->maximizedCheckBox->setChecked(m_obj->get("LaunchMaximized").toBool()); - ui->windowWidthSpinBox->setValue(m_obj->get("MinecraftWinWidth").toInt()); - ui->windowHeightSpinBox->setValue(m_obj->get("MinecraftWinHeight").toInt()); - - // Memory - ui->memoryGroupBox->setChecked(m_obj->get("OverrideMemory").toBool()); - ui->minMemSpinBox->setValue(m_obj->get("MinMemAlloc").toInt()); - ui->maxMemSpinBox->setValue(m_obj->get("MaxMemAlloc").toInt()); - ui->permGenSpinBox->setValue(m_obj->get("PermGen").toInt()); - - // Java Settings - ui->javaSettingsGroupBox->setChecked(m_obj->get("OverrideJava").toBool()); - ui->javaPathTextBox->setText(m_obj->get("JavaPath").toString()); - ui->jvmArgsTextBox->setText(m_obj->get("JvmArgs").toString()); - - // Custom Commands - ui->customCommandsGroupBox->setChecked(m_obj->get("OverrideCommands").toBool()); - ui->preLaunchCmdTextBox->setText(m_obj->get("PreLaunchCommand").toString()); - ui->postExitCmdTextBox->setText(m_obj->get("PostExitCommand").toString()); -} - -void InstanceSettings::on_javaDetectBtn_clicked() -{ - JavaVersionPtr java; - - VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); - vselect.setResizeOn(2); - vselect.exec(); - - if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) - { - java = std::dynamic_pointer_cast<JavaVersion>(vselect.selectedVersion()); - ui->javaPathTextBox->setText(java->path); - } -} - -void InstanceSettings::on_javaBrowseBtn_clicked() -{ - QString dir = QFileDialog::getOpenFileName(this, tr("Find Java executable")); - if (!dir.isNull()) - { - ui->javaPathTextBox->setText(dir); - } -} - -void InstanceSettings::on_javaTestBtn_clicked() -{ - checker.reset(new JavaChecker()); - connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, - SLOT(checkFinished(JavaCheckResult))); - checker->path = ui->javaPathTextBox->text(); - checker->performCheck(); -} - -void InstanceSettings::checkFinished(JavaCheckResult result) -{ - if (result.valid) - { - QString text; - text += "Java test succeeded!\n"; - if (result.is_64bit) - text += "Using 64bit java.\n"; - text += "\n"; - text += "Platform reported: " + result.realPlatform; - QMessageBox::information(this, tr("Java test success"), text); - } - else - { - QMessageBox::warning( - this, tr("Java test failure"), - tr("The specified java binary didn't work. You should use the auto-detect feature, " - "or set the path to the java executable.")); - } -} diff --git a/gui/dialogs/LegacyModEditDialog.cpp b/gui/dialogs/LegacyModEditDialog.cpp deleted file mode 100644 index e5039c02..00000000 --- a/gui/dialogs/LegacyModEditDialog.cpp +++ /dev/null @@ -1,393 +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. - */ - -#include "MultiMC.h" -#include "LegacyModEditDialog.h" -#include "ModEditDialogCommon.h" -#include "VersionSelectDialog.h" -#include "ProgressDialog.h" -#include "ui_LegacyModEditDialog.h" -#include "logic/ModList.h" -#include "logic/lists/ForgeVersionList.h" -#include "gui/Platform.h" - -#include <pathutils.h> -#include <QFileDialog> -//#include <QMessageBox> -#include <QDebug> -#include <QEvent> -#include <QKeyEvent> - -LegacyModEditDialog::LegacyModEditDialog(LegacyInstance *inst, QWidget *parent) - : QDialog(parent), ui(new Ui::LegacyModEditDialog), m_inst(inst) -{ - MultiMCPlatform::fixWM_CLASS(this); - ui->setupUi(this); - - // Jar mods - { - ensureFolderPathExists(m_inst->jarModsDir()); - m_jarmods = m_inst->jarModList(); - ui->jarModsTreeView->setModel(m_jarmods.get()); -#ifndef Q_OS_LINUX - // FIXME: internal DnD causes segfaults later - ui->jarModsTreeView->setDragDropMode(QAbstractItemView::DragDrop); - // FIXME: DnD is glitched with contiguous (we move only first item in selection) - ui->jarModsTreeView->setSelectionMode(QAbstractItemView::SingleSelection); -#endif - ui->jarModsTreeView->installEventFilter(this); - m_jarmods->startWatching(); - auto smodel = ui->jarModsTreeView->selectionModel(); - connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), - SLOT(jarCurrent(QModelIndex, QModelIndex))); - } - // Core mods - { - ensureFolderPathExists(m_inst->coreModsDir()); - m_coremods = m_inst->coreModList(); - ui->coreModsTreeView->setModel(m_coremods.get()); - ui->coreModsTreeView->installEventFilter(this); - m_coremods->startWatching(); - auto smodel = ui->coreModsTreeView->selectionModel(); - connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), - SLOT(coreCurrent(QModelIndex, QModelIndex))); - } - // Loader mods - { - ensureFolderPathExists(m_inst->loaderModsDir()); - m_mods = m_inst->loaderModList(); - ui->loaderModTreeView->setModel(m_mods.get()); - ui->loaderModTreeView->installEventFilter(this); - m_mods->startWatching(); - auto smodel = ui->loaderModTreeView->selectionModel(); - connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), - SLOT(loaderCurrent(QModelIndex, QModelIndex))); - } - // texture packs - { - ensureFolderPathExists(m_inst->texturePacksDir()); - m_texturepacks = m_inst->texturePackList(); - ui->texPackTreeView->setModel(m_texturepacks.get()); - ui->texPackTreeView->installEventFilter(this); - m_texturepacks->startWatching(); - } -} - -LegacyModEditDialog::~LegacyModEditDialog() -{ - m_mods->stopWatching(); - m_coremods->stopWatching(); - m_jarmods->stopWatching(); - m_texturepacks->stopWatching(); - delete ui; -} - -bool LegacyModEditDialog::coreListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmCoreBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addCoreBtn_clicked(); - return true; - default: - break; - } - return QDialog::eventFilter(ui->coreModsTreeView, keyEvent); -} - -bool LegacyModEditDialog::jarListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Up: - { - if (keyEvent->modifiers() & Qt::ControlModifier) - { - on_moveJarUpBtn_clicked(); - return true; - } - break; - } - case Qt::Key_Down: - { - if (keyEvent->modifiers() & Qt::ControlModifier) - { - on_moveJarDownBtn_clicked(); - return true; - } - break; - } - case Qt::Key_Delete: - on_rmJarBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addJarBtn_clicked(); - return true; - default: - break; - } - return QDialog::eventFilter(ui->jarModsTreeView, keyEvent); -} - -bool LegacyModEditDialog::loaderListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmModBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addModBtn_clicked(); - return true; - default: - break; - } - return QDialog::eventFilter(ui->loaderModTreeView, keyEvent); -} - -bool LegacyModEditDialog::texturePackListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmTexPackBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addTexPackBtn_clicked(); - return true; - default: - break; - } - return QDialog::eventFilter(ui->texPackTreeView, keyEvent); -} - -bool LegacyModEditDialog::eventFilter(QObject *obj, QEvent *ev) -{ - if (ev->type() != QEvent::KeyPress) - { - return QDialog::eventFilter(obj, ev); - } - QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); - if (obj == ui->jarModsTreeView) - return jarListFilter(keyEvent); - if (obj == ui->coreModsTreeView) - return coreListFilter(keyEvent); - if (obj == ui->loaderModTreeView) - return loaderListFilter(keyEvent); - if (obj == ui->texPackTreeView) - return texturePackListFilter(keyEvent); - return QDialog::eventFilter(obj, ev); -} - -void LegacyModEditDialog::on_addCoreBtn_clicked() -{ - //: Title of core mod selection dialog - QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Core Mods")); - for (auto filename : fileNames) - { - m_coremods->stopWatching(); - m_coremods->installMod(QFileInfo(filename)); - m_coremods->startWatching(); - } -} -void LegacyModEditDialog::on_addForgeBtn_clicked() -{ - VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); - vselect.setExactFilter(1, m_inst->intendedVersionId()); - if (vselect.exec() && vselect.selectedVersion()) - { - ForgeVersionPtr forge = - std::dynamic_pointer_cast<ForgeVersion>(vselect.selectedVersion()); - if (!forge) - return; - auto entry = MMC->metacache()->resolveEntry("minecraftforge", forge->filename); - if (entry->stale) - { - NetJob *fjob = new NetJob("Forge download"); - fjob->addNetAction(CacheDownload::make(forge->universal_url, entry)); - ProgressDialog dlg(this); - dlg.exec(fjob); - if (dlg.result() == QDialog::Accepted) - { - m_jarmods->stopWatching(); - m_jarmods->installMod(QFileInfo(entry->getFullPath())); - m_jarmods->startWatching(); - } - else - { - // failed to download forge :/ - } - } - else - { - m_jarmods->stopWatching(); - m_jarmods->installMod(QFileInfo(entry->getFullPath())); - m_jarmods->startWatching(); - } - } -} -void LegacyModEditDialog::on_addJarBtn_clicked() -{ - //: Title of jar mod selection dialog - QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Jar Mods")); - for (auto filename : fileNames) - { - m_jarmods->stopWatching(); - m_jarmods->installMod(QFileInfo(filename)); - m_jarmods->startWatching(); - } -} -void LegacyModEditDialog::on_addModBtn_clicked() -{ - //: Title of regular mod selection dialog - QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Loader Mods")); - for (auto filename : fileNames) - { - m_mods->stopWatching(); - m_mods->installMod(QFileInfo(filename)); - m_mods->startWatching(); - } -} -void LegacyModEditDialog::on_addTexPackBtn_clicked() -{ - //: Title of texture pack selection dialog - QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Texture Packs")); - for (auto filename : fileNames) - { - m_texturepacks->stopWatching(); - m_texturepacks->installMod(QFileInfo(filename)); - m_texturepacks->startWatching(); - } -} - -void LegacyModEditDialog::on_moveJarDownBtn_clicked() -{ - int first, last; - auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - - m_jarmods->moveModsDown(first, last); -} -void LegacyModEditDialog::on_moveJarUpBtn_clicked() -{ - int first, last; - auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_jarmods->moveModsUp(first, last); -} -void LegacyModEditDialog::on_rmCoreBtn_clicked() -{ - int first, last; - auto list = ui->coreModsTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_coremods->stopWatching(); - m_coremods->deleteMods(first, last); - m_coremods->startWatching(); -} -void LegacyModEditDialog::on_rmJarBtn_clicked() -{ - int first, last; - auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_jarmods->stopWatching(); - m_jarmods->deleteMods(first, last); - m_jarmods->startWatching(); -} -void LegacyModEditDialog::on_rmModBtn_clicked() -{ - int first, last; - auto list = ui->loaderModTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_mods->stopWatching(); - m_mods->deleteMods(first, last); - m_mods->startWatching(); -} -void LegacyModEditDialog::on_rmTexPackBtn_clicked() -{ - int first, last; - auto list = ui->texPackTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_texturepacks->stopWatching(); - m_texturepacks->deleteMods(first, last); - m_texturepacks->startWatching(); -} -void LegacyModEditDialog::on_viewCoreBtn_clicked() -{ - openDirInDefaultProgram(m_inst->coreModsDir(), true); -} -void LegacyModEditDialog::on_viewModBtn_clicked() -{ - openDirInDefaultProgram(m_inst->loaderModsDir(), true); -} -void LegacyModEditDialog::on_viewTexPackBtn_clicked() -{ - openDirInDefaultProgram(m_inst->texturePacksDir(), true); -} - -void LegacyModEditDialog::on_buttonBox_rejected() -{ - close(); -} - -void LegacyModEditDialog::jarCurrent(QModelIndex current, QModelIndex previous) -{ - if (!current.isValid()) - { - ui->jarMIFrame->clear(); - return; - } - int row = current.row(); - Mod &m = m_jarmods->operator[](row); - ui->jarMIFrame->updateWithMod(m); -} - -void LegacyModEditDialog::coreCurrent(QModelIndex current, QModelIndex previous) -{ - if (!current.isValid()) - { - ui->coreMIFrame->clear(); - return; - } - int row = current.row(); - Mod &m = m_coremods->operator[](row); - ui->coreMIFrame->updateWithMod(m); -} - -void LegacyModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previous) -{ - if (!current.isValid()) - { - ui->loaderMIFrame->clear(); - return; - } - int row = current.row(); - Mod &m = m_mods->operator[](row); - ui->loaderMIFrame->updateWithMod(m); -} diff --git a/gui/dialogs/LegacyModEditDialog.h b/gui/dialogs/LegacyModEditDialog.h deleted file mode 100644 index d5582aef..00000000 --- a/gui/dialogs/LegacyModEditDialog.h +++ /dev/null @@ -1,78 +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 <QDialog> -#include "logic/LegacyInstance.h" -#include <logic/net/NetJob.h> - -namespace Ui -{ -class LegacyModEditDialog; -} - -class LegacyModEditDialog : public QDialog -{ - Q_OBJECT - -public: - explicit LegacyModEditDialog(LegacyInstance *inst, QWidget *parent = 0); - ~LegacyModEditDialog(); - -private -slots: - - void on_addJarBtn_clicked(); - void on_rmJarBtn_clicked(); - void on_addForgeBtn_clicked(); - void on_moveJarUpBtn_clicked(); - void on_moveJarDownBtn_clicked(); - - void on_addCoreBtn_clicked(); - void on_rmCoreBtn_clicked(); - void on_viewCoreBtn_clicked(); - - void on_addModBtn_clicked(); - void on_rmModBtn_clicked(); - void on_viewModBtn_clicked(); - - void on_addTexPackBtn_clicked(); - void on_rmTexPackBtn_clicked(); - void on_viewTexPackBtn_clicked(); - - // Questionable: SettingsDialog doesn't need this for some reason? - void on_buttonBox_rejected(); - - void jarCurrent(QModelIndex current, QModelIndex previous); - void coreCurrent(QModelIndex current, QModelIndex previous); - void loaderCurrent(QModelIndex current, QModelIndex previous); - -protected: - bool eventFilter(QObject *obj, QEvent *ev); - bool jarListFilter(QKeyEvent *ev); - bool coreListFilter(QKeyEvent *ev); - bool loaderListFilter(QKeyEvent *ev); - bool texturePackListFilter(QKeyEvent *ev); - -private: - Ui::LegacyModEditDialog *ui; - std::shared_ptr<ModList> m_mods; - std::shared_ptr<ModList> m_coremods; - std::shared_ptr<ModList> m_jarmods; - std::shared_ptr<ModList> m_texturepacks; - LegacyInstance *m_inst; - NetJobPtr forgeJob; -}; diff --git a/gui/dialogs/LegacyModEditDialog.ui b/gui/dialogs/LegacyModEditDialog.ui deleted file mode 100644 index 0662c712..00000000 --- a/gui/dialogs/LegacyModEditDialog.ui +++ /dev/null @@ -1,321 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>LegacyModEditDialog</class> - <widget class="QDialog" name="LegacyModEditDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>540</width> - <height>420</height> - </rect> - </property> - <property name="windowTitle"> - <string>Edit Mods</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="jarTab"> - <attribute name="title"> - <string>Jar Mods</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="ModListView" name="jarModsTreeView"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOn</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="jarModsButtonBox"> - <item> - <widget class="QPushButton" name="addJarBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmJarBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="addForgeBtn"> - <property name="text"> - <string>MCForge</string> - </property> - </widget> - </item> - <item> - <spacer name="jarModsButtonSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="moveJarUpBtn"> - <property name="text"> - <string>Move &Up</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="moveJarDownBtn"> - <property name="text"> - <string>Move &Down</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </item> - <item> - <widget class="MCModInfoFrame" name="jarMIFrame"> - <property name="frameShadow"> - <enum>QFrame::Plain</enum> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="coreTab"> - <attribute name="title"> - <string>Core Mods</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="ModListView" name="coreModsTreeView"> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="coreModsButtonBox"> - <item> - <widget class="QPushButton" name="addCoreBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmCoreBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <spacer name="coreModsButtonSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="viewCoreBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </item> - <item> - <widget class="MCModInfoFrame" name="coreMIFrame"> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="modTab"> - <attribute name="title"> - <string>Loader Mods</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="ModListView" name="loaderModTreeView"> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="mlModsButtonBox"> - <item> - <widget class="QPushButton" name="addModBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmModBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <spacer name="mlModsButtonSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="viewModBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </item> - <item> - <widget class="MCModInfoFrame" name="loaderMIFrame"> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="texPackTab"> - <property name="acceptDrops"> - <bool>false</bool> - </property> - <attribute name="title"> - <string>Texture Packs</string> - </attribute> - <layout class="QHBoxLayout" name="horizontalLayout_4"> - <item> - <widget class="ModListView" name="texPackTreeView"> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="texturePacksButtonBox"> - <item> - <widget class="QPushButton" name="addTexPackBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmTexPackBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <spacer name="texturePacksButtonSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="viewTexPackBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </widget> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Close</set> - </property> - </widget> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>ModListView</class> - <extends>QTreeView</extends> - <header>gui/widgets/ModListView.h</header> - </customwidget> - <customwidget> - <class>MCModInfoFrame</class> - <extends>QFrame</extends> - <header>gui/widgets/MCModInfoFrame.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/gui/dialogs/LwjglSelectDialog.cpp b/gui/dialogs/LwjglSelectDialog.cpp index 046a4e2e..e64228b2 100644 --- a/gui/dialogs/LwjglSelectDialog.cpp +++ b/gui/dialogs/LwjglSelectDialog.cpp @@ -18,7 +18,7 @@ #include "ui_LwjglSelectDialog.h" #include "gui/Platform.h" -#include "logic/lists/LwjglVersionList.h" +#include "logic/LwjglVersionList.h" LWJGLSelectDialog::LWJGLSelectDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LWJGLSelectDialog) diff --git a/gui/dialogs/ModEditDialogCommon.cpp b/gui/dialogs/ModEditDialogCommon.cpp index eee42e5e..35942374 100644 --- a/gui/dialogs/ModEditDialogCommon.cpp +++ b/gui/dialogs/ModEditDialogCommon.cpp @@ -1,24 +1,7 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - #include "ModEditDialogCommon.h" #include "CustomMessageBox.h" -#include <QDesktopServices> -#include <QMessageBox> -#include <QString> #include <QUrl> + bool lastfirst(QModelIndexList &list, int &first, int &last) { if (!list.size()) @@ -54,4 +37,4 @@ void showWebsiteForMod(QWidget *parentDlg, Mod &m) QObject::tr("The mod author didn't provide a website link for this mod."), QMessageBox::Warning); } -} +}
\ No newline at end of file diff --git a/gui/dialogs/ModEditDialogCommon.h b/gui/dialogs/ModEditDialogCommon.h index a226d5a9..3ccfbf6b 100644 --- a/gui/dialogs/ModEditDialogCommon.h +++ b/gui/dialogs/ModEditDialogCommon.h @@ -1,22 +1,9 @@ -/* 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 <QAbstractItemModel> +#include <QModelIndex> +#include <QDesktopServices> +#include <QWidget> #include <logic/Mod.h> bool lastfirst(QModelIndexList &list, int &first, int &last); -void showWebsiteForMod(QWidget *parentDlg, Mod &m);
\ No newline at end of file +void showWebsiteForMod(QWidget *parentDlg, Mod &m); diff --git a/gui/dialogs/NewInstanceDialog.cpp b/gui/dialogs/NewInstanceDialog.cpp index c7b273af..41ae329c 100644 --- a/gui/dialogs/NewInstanceDialog.cpp +++ b/gui/dialogs/NewInstanceDialog.cpp @@ -20,7 +20,7 @@ #include "logic/InstanceFactory.h" #include "logic/BaseVersion.h" #include "logic/icons/IconList.h" -#include "logic/lists/MinecraftVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" #include "logic/tasks/Task.h" #include "gui/Platform.h" @@ -47,7 +47,7 @@ NewInstanceDialog::NewInstanceDialog(QWidget *parent) taskDlg->exec(loadTask); } */ - setSelectedVersion(MMC->minecraftlist()->getLatestStable()); + setSelectedVersion(MMC->minecraftlist()->getLatestStable(), true); InstIconKey = "infinity"; ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); } @@ -63,13 +63,17 @@ void NewInstanceDialog::updateDialogState() ->setEnabled(!instName().isEmpty() && m_selectedVersion); } -void NewInstanceDialog::setSelectedVersion(BaseVersionPtr version) +void NewInstanceDialog::setSelectedVersion(BaseVersionPtr version, bool initial) { m_selectedVersion = version; if (m_selectedVersion) { ui->versionTextBox->setText(version->name()); + if(ui->instNameTextBox->text().isEmpty() && !initial) + { + ui->instNameTextBox->setText(version->name()); + } } else { diff --git a/gui/dialogs/NewInstanceDialog.h b/gui/dialogs/NewInstanceDialog.h index 4357c28d..17045ec0 100644 --- a/gui/dialogs/NewInstanceDialog.h +++ b/gui/dialogs/NewInstanceDialog.h @@ -33,7 +33,7 @@ public: void updateDialogState(); - void setSelectedVersion(BaseVersionPtr version); + void setSelectedVersion(BaseVersionPtr version, bool initial = false); void loadVersionList(); diff --git a/gui/dialogs/OneSixModEditDialog.cpp b/gui/dialogs/OneSixModEditDialog.cpp deleted file mode 100644 index a3598eb9..00000000 --- a/gui/dialogs/OneSixModEditDialog.cpp +++ /dev/null @@ -1,399 +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. - */ - -#include "MultiMC.h" - -#include <pathutils.h> -#include <QFileDialog> -#include <QMessageBox> -#include <QDebug> -#include <QEvent> -#include <QKeyEvent> -#include <QDesktopServices> - -#include "OneSixModEditDialog.h" -#include "ModEditDialogCommon.h" -#include "ui_OneSixModEditDialog.h" - -#include "gui/Platform.h" -#include "gui/dialogs/CustomMessageBox.h" -#include "gui/dialogs/VersionSelectDialog.h" - -#include "gui/dialogs/ProgressDialog.h" - -#include "logic/ModList.h" -#include "logic/VersionFinal.h" -#include "logic/EnabledItemFilter.h" -#include "logic/lists/ForgeVersionList.h" -#include "logic/lists/LiteLoaderVersionList.h" -#include "logic/ForgeInstaller.h" -#include "logic/LiteLoaderInstaller.h" -#include "logic/OneSixVersionBuilder.h" - -OneSixModEditDialog::OneSixModEditDialog(OneSixInstance *inst, QWidget *parent) - : QDialog(parent), ui(new Ui::OneSixModEditDialog), m_inst(inst) -{ - MultiMCPlatform::fixWM_CLASS(this); - ui->setupUi(this); - // libraries! - - m_version = m_inst->getFullVersion(); - if (m_version) - { - main_model = new EnabledItemFilter(this); - main_model->setActive(true); - main_model->setSourceModel(m_version.get()); - ui->libraryTreeView->setModel(main_model); - ui->libraryTreeView->installEventFilter(this); - connect(ui->libraryTreeView->selectionModel(), &QItemSelectionModel::currentChanged, - this, &OneSixModEditDialog::versionCurrent); - updateVersionControls(); - } - else - { - disableVersionControls(); - } - // Loader mods - { - ensureFolderPathExists(m_inst->loaderModsDir()); - m_mods = m_inst->loaderModList(); - ui->loaderModTreeView->setModel(m_mods.get()); - ui->loaderModTreeView->installEventFilter(this); - m_mods->startWatching(); - auto smodel = ui->loaderModTreeView->selectionModel(); - connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), - SLOT(loaderCurrent(QModelIndex, QModelIndex))); - } - // resource packs - { - ensureFolderPathExists(m_inst->resourcePacksDir()); - m_resourcepacks = m_inst->resourcePackList(); - ui->resPackTreeView->setModel(m_resourcepacks.get()); - ui->resPackTreeView->installEventFilter(this); - m_resourcepacks->startWatching(); - } - - connect(m_inst, &OneSixInstance::versionReloaded, this, - &OneSixModEditDialog::updateVersionControls); -} - -OneSixModEditDialog::~OneSixModEditDialog() -{ - m_mods->stopWatching(); - m_resourcepacks->stopWatching(); - delete ui; -} - -void OneSixModEditDialog::updateVersionControls() -{ - ui->forgeBtn->setEnabled(true); - ui->liteloaderBtn->setEnabled(true); -} - -void OneSixModEditDialog::disableVersionControls() -{ - ui->forgeBtn->setEnabled(false); - ui->liteloaderBtn->setEnabled(false); - ui->reloadLibrariesBtn->setEnabled(false); - ui->removeLibraryBtn->setEnabled(false); -} - -bool OneSixModEditDialog::reloadInstanceVersion() -{ - try - { - m_inst->reloadVersion(); - return true; - } - catch (MMCError &e) - { - QMessageBox::critical(this, tr("Error"), e.cause()); - return false; - } - catch (...) - { - QMessageBox::critical( - this, tr("Error"), - tr("Failed to load the version description file for reasons unknown.")); - return false; - } -} - -void OneSixModEditDialog::on_reloadLibrariesBtn_clicked() -{ - reloadInstanceVersion(); -} - -void OneSixModEditDialog::on_removeLibraryBtn_clicked() -{ - if (ui->libraryTreeView->currentIndex().isValid()) - { - // FIXME: use actual model, not reloading. - if (!m_version->remove(ui->libraryTreeView->currentIndex().row())) - { - QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file")); - } - } -} - -void OneSixModEditDialog::on_resetLibraryOrderBtn_clicked() -{ - try - { - m_version->resetOrder(); - } - catch (MMCError &e) - { - QMessageBox::critical(this, tr("Error"), e.cause()); - } -} - -void OneSixModEditDialog::on_moveLibraryUpBtn_clicked() -{ - if (ui->libraryTreeView->selectionModel()->selectedRows().isEmpty()) - { - return; - } - try - { - const int row = ui->libraryTreeView->selectionModel()->selectedRows().first().row(); - const int newRow = 0;m_version->move(row, VersionFinal::MoveUp); - //ui->libraryTreeView->selectionModel()->setCurrentIndex(m_version->index(newRow), QItemSelectionModel::ClearAndSelect); - } - catch (MMCError &e) - { - QMessageBox::critical(this, tr("Error"), e.cause()); - } -} - -void OneSixModEditDialog::on_moveLibraryDownBtn_clicked() -{ - if (ui->libraryTreeView->selectionModel()->selectedRows().isEmpty()) - { - return; - } - try - { - const int row = ui->libraryTreeView->selectionModel()->selectedRows().first().row(); - const int newRow = 0;m_version->move(row, VersionFinal::MoveDown); - //ui->libraryTreeView->selectionModel()->setCurrentIndex(m_version->index(newRow), QItemSelectionModel::ClearAndSelect); - } - catch (MMCError &e) - { - QMessageBox::critical(this, tr("Error"), e.cause()); - } -} - -void OneSixModEditDialog::on_forgeBtn_clicked() -{ - // FIXME: use actual model, not reloading. Move logic to model. - if (m_version->hasFtbPack()) - { - if (QMessageBox::question(this, tr("Revert?"), - tr("This action will remove the FTB pack version patch. Continue?")) != - QMessageBox::Yes) - { - return; - } - m_version->removeFtbPack(); - reloadInstanceVersion(); - } - if (m_version->isCustom()) - { - if (QMessageBox::question(this, tr("Revert?"), - tr("This action will remove your custom.json. Continue?")) != - QMessageBox::Yes) - { - return; - } - m_version->revertToBase(); - reloadInstanceVersion(); - } - VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); - 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()) - { - ProgressDialog dialog(this); - dialog.exec(ForgeInstaller().createInstallTask(m_inst, vselect.selectedVersion(), this)); - } -} - -void OneSixModEditDialog::on_liteloaderBtn_clicked() -{ - if (m_version->hasFtbPack()) - { - if (QMessageBox::question(this, tr("Revert?"), - tr("This action will remove the FTB pack version patch. Continue?")) != - QMessageBox::Yes) - { - return; - } - m_version->removeFtbPack(); - reloadInstanceVersion(); - } - if (m_version->isCustom()) - { - if (QMessageBox::question(this, tr("Revert?"), - tr("This action will remove your custom.json. Continue?")) != - QMessageBox::Yes) - { - return; - } - m_version->revertToBase(); - reloadInstanceVersion(); - } - VersionSelectDialog vselect(MMC->liteloaderlist().get(), tr("Select LiteLoader version"), - this); - 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()) - { - ProgressDialog dialog(this); - dialog.exec(LiteLoaderInstaller().createInstallTask(m_inst, vselect.selectedVersion(), this)); - } -} - -bool OneSixModEditDialog::loaderListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmModBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addModBtn_clicked(); - return true; - default: - break; - } - return QDialog::eventFilter(ui->loaderModTreeView, keyEvent); -} - -bool OneSixModEditDialog::resourcePackListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmResPackBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addResPackBtn_clicked(); - return true; - default: - break; - } - return QDialog::eventFilter(ui->resPackTreeView, keyEvent); -} - -bool OneSixModEditDialog::eventFilter(QObject *obj, QEvent *ev) -{ - if (ev->type() != QEvent::KeyPress) - { - return QDialog::eventFilter(obj, ev); - } - QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); - if (obj == ui->loaderModTreeView) - return loaderListFilter(keyEvent); - if (obj == ui->resPackTreeView) - return resourcePackListFilter(keyEvent); - return QDialog::eventFilter(obj, ev); -} - -void OneSixModEditDialog::on_buttonBox_rejected() -{ - close(); -} - -void OneSixModEditDialog::on_addModBtn_clicked() -{ - QStringList fileNames = QFileDialog::getOpenFileNames( - this, QApplication::translate("LegacyModEditDialog", "Select Loader Mods")); - for (auto filename : fileNames) - { - m_mods->stopWatching(); - m_mods->installMod(QFileInfo(filename)); - m_mods->startWatching(); - } -} -void OneSixModEditDialog::on_rmModBtn_clicked() -{ - int first, last; - auto list = ui->loaderModTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_mods->stopWatching(); - m_mods->deleteMods(first, last); - m_mods->startWatching(); -} -void OneSixModEditDialog::on_viewModBtn_clicked() -{ - openDirInDefaultProgram(m_inst->loaderModsDir(), true); -} - -void OneSixModEditDialog::on_addResPackBtn_clicked() -{ - QStringList fileNames = QFileDialog::getOpenFileNames( - this, QApplication::translate("LegacyModEditDialog", "Select Resource Packs")); - for (auto filename : fileNames) - { - m_resourcepacks->stopWatching(); - m_resourcepacks->installMod(QFileInfo(filename)); - m_resourcepacks->startWatching(); - } -} -void OneSixModEditDialog::on_rmResPackBtn_clicked() -{ - int first, last; - auto list = ui->resPackTreeView->selectionModel()->selectedRows(); - - if (!lastfirst(list, first, last)) - return; - m_resourcepacks->stopWatching(); - m_resourcepacks->deleteMods(first, last); - m_resourcepacks->startWatching(); -} -void OneSixModEditDialog::on_viewResPackBtn_clicked() -{ - openDirInDefaultProgram(m_inst->resourcePacksDir(), true); -} - -void OneSixModEditDialog::loaderCurrent(QModelIndex current, QModelIndex previous) -{ - if (!current.isValid()) - { - ui->frame->clear(); - return; - } - int row = current.row(); - Mod &m = m_mods->operator[](row); - ui->frame->updateWithMod(m); -} - -void OneSixModEditDialog::versionCurrent(const QModelIndex ¤t, - const QModelIndex &previous) -{ - if (!current.isValid()) - { - ui->removeLibraryBtn->setDisabled(true); - } - else - { - ui->removeLibraryBtn->setEnabled(m_version->canRemove(current.row())); - } -} diff --git a/gui/dialogs/OneSixModEditDialog.ui b/gui/dialogs/OneSixModEditDialog.ui deleted file mode 100644 index 2c9f70bb..00000000 --- a/gui/dialogs/OneSixModEditDialog.ui +++ /dev/null @@ -1,310 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>OneSixModEditDialog</class> - <widget class="QDialog" name="OneSixModEditDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>555</width> - <height>463</height> - </rect> - </property> - <property name="windowTitle"> - <string>Manage Mods</string> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QTabWidget" name="tabWidget"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="minimumSize"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="libTab"> - <attribute name="title"> - <string>Version</string> - </attribute> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_10"> - <item> - <widget class="ModListView" name="libraryTreeView"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOn</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <attribute name="headerVisible"> - <bool>false</bool> - </attribute> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QPushButton" name="forgeBtn"> - <property name="toolTip"> - <string>Replace any current custom version with Minecraft Forge</string> - </property> - <property name="text"> - <string>Install Forge</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="liteloaderBtn"> - <property name="text"> - <string>Install LiteLoader</string> - </property> - </widget> - </item> - <item> - <widget class="Line" name="line"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="reloadLibrariesBtn"> - <property name="text"> - <string>Reload</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="removeLibraryBtn"> - <property name="text"> - <string>Remove</string> - </property> - </widget> - </item> - <item> - <widget class="Line" name="line_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="moveLibraryUpBtn"> - <property name="toolTip"> - <string>This isn't implemented yet.</string> - </property> - <property name="text"> - <string>Move up</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="moveLibraryDownBtn"> - <property name="toolTip"> - <string>This isn't implemented yet.</string> - </property> - <property name="text"> - <string>Move down</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="resetLibraryOrderBtn"> - <property name="toolTip"> - <string>This isn't implemented yet.</string> - </property> - <property name="text"> - <string>Reset order</string> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_7"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - </layout> - </widget> - <widget class="QWidget" name="modTab"> - <attribute name="title"> - <string>Loader Mods</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_6"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="ModListView" name="loaderModTreeView"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QPushButton" name="addModBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmModBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="viewModBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </item> - <item> - <widget class="MCModInfoFrame" name="frame"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="resPackTab"> - <attribute name="title"> - <string>Resource Packs</string> - </attribute> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="ModListView" name="resPackTreeView"> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QPushButton" name="addResPackBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmResPackBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_2"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="viewResPackBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </widget> - </item> - <item row="1" column="0"> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="autoFillBackground"> - <bool>false</bool> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Close</set> - </property> - </widget> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>ModListView</class> - <extends>QTreeView</extends> - <header>gui/widgets/ModListView.h</header> - </customwidget> - <customwidget> - <class>MCModInfoFrame</class> - <extends>QFrame</extends> - <header>gui/widgets/MCModInfoFrame.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/gui/dialogs/ScreenshotDialog.cpp b/gui/dialogs/ScreenshotDialog.cpp deleted file mode 100644 index a88c8dfd..00000000 --- a/gui/dialogs/ScreenshotDialog.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "ScreenshotDialog.h" -#include "ui_ScreenshotDialog.h" - -#include <QModelIndex> -#include <QMutableListIterator> - -#include "ProgressDialog.h" -#include "CustomMessageBox.h" -#include "logic/net/NetJob.h" -#include "logic/screenshots/ImgurUpload.h" -#include "logic/screenshots/ImgurAlbumCreation.h" -#include "logic/tasks/SequentialTask.h" - -ScreenshotDialog::ScreenshotDialog(ScreenshotList *list, QWidget *parent) - : QDialog(parent), ui(new Ui::ScreenshotDialog), m_list(list) -{ - ui->setupUi(this); - ui->listView->setModel(m_list); -} - -ScreenshotDialog::~ScreenshotDialog() -{ - delete ui; -} - -QString ScreenshotDialog::message() const -{ - return tr("<a href=\"https://imgur.com/a/%1\">Visit album</a><br/>Delete hash: %2 (save " - "this if you want to be able to edit/delete the album)") - .arg(m_imgurAlbum->id(), m_imgurAlbum->deleteHash()); -} - -QList<ScreenshotPtr> ScreenshotDialog::selected() const -{ - QList<ScreenshotPtr> list; - QList<ScreenshotPtr> first = m_list->screenshots(); - for (QModelIndex index : ui->listView->selectionModel()->selectedRows()) - { - list.append(first.at(index.row())); - } - return list; -} - -void ScreenshotDialog::on_uploadBtn_clicked() -{ - m_uploaded = selected(); - if (m_uploaded.isEmpty()) - { - done(NothingDone); - return; - } - SequentialTask *task = new SequentialTask(this); - NetJob *job = new NetJob("Screenshot Upload"); - for (auto shot : m_uploaded) - { - job->addNetAction(ImgurUpload::make(shot)); - } - NetJob *albumTask = new NetJob("Imgur Album Creation"); - albumTask->addNetAction(m_imgurAlbum = ImgurAlbumCreation::make(m_uploaded)); - task->addTask(NetJobPtr(job)); - task->addTask(NetJobPtr(albumTask)); - ProgressDialog prog(this); - if (prog.exec(task) == QDialog::Accepted) - { - accept(); - } - else - { - CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), - tr("Unknown error"), QMessageBox::Warning)->exec(); - reject(); - } -} - -void ScreenshotDialog::on_deleteBtn_clicked() -{ - m_list->deleteSelected(this); -} diff --git a/gui/dialogs/ScreenshotDialog.h b/gui/dialogs/ScreenshotDialog.h deleted file mode 100644 index 29dd6765..00000000 --- a/gui/dialogs/ScreenshotDialog.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include <QDialog> -#include "logic/screenshots/ScreenshotList.h" - -class ImgurAlbumCreation; - -namespace Ui -{ -class ScreenshotDialog; -} - -class ScreenshotDialog : public QDialog -{ - Q_OBJECT - -public: - explicit ScreenshotDialog(ScreenshotList *list, QWidget *parent = 0); - ~ScreenshotDialog(); - - enum - { - NothingDone = 0x42 - }; - - QString message() const; - QList<ScreenshotPtr> selected() const; - -private -slots: - void on_uploadBtn_clicked(); - - void on_deleteBtn_clicked(); - -private: - Ui::ScreenshotDialog *ui; - ScreenshotList *m_list; - QList<ScreenshotPtr> m_uploaded; - std::shared_ptr<ImgurAlbumCreation> m_imgurAlbum; -}; diff --git a/gui/dialogs/ScreenshotDialog.ui b/gui/dialogs/ScreenshotDialog.ui deleted file mode 100644 index eb3dafba..00000000 --- a/gui/dialogs/ScreenshotDialog.ui +++ /dev/null @@ -1,110 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ScreenshotDialog</class> - <widget class="QDialog" name="ScreenshotDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>470</width> - <height>300</height> - </rect> - </property> - <property name="windowTitle"> - <string>Screenshot Manager</string> - </property> - <property name="windowIcon"> - <iconset resource="../../resources/multimc/multimc.qrc"> - <normaloff>:/icons/multimc/scalable/apps/multimc.svg</normaloff>:/icons/multimc/scalable/apps/multimc.svg</iconset> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QListView" name="listView"> - <property name="selectionMode"> - <enum>QAbstractItemView::ExtendedSelection</enum> - </property> - <property name="selectionBehavior"> - <enum>QAbstractItemView::SelectItems</enum> - </property> - <property name="iconSize"> - <size> - <width>120</width> - <height>90</height> - </size> - </property> - <property name="flow"> - <enum>QListView::LeftToRight</enum> - </property> - <property name="isWrapping" stdset="0"> - <bool>true</bool> - </property> - <property name="resizeMode"> - <enum>QListView::Adjust</enum> - </property> - <property name="viewMode"> - <enum>QListView::IconMode</enum> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QPushButton" name="uploadBtn"> - <property name="text"> - <string>Upload</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="deleteBtn"> - <property name="text"> - <string>Delete</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="closeBtn"> - <property name="text"> - <string>Close</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - <resources> - <include location="../../resources/multimc/multimc.qrc"/> - </resources> - <connections> - <connection> - <sender>closeBtn</sender> - <signal>clicked()</signal> - <receiver>ScreenshotDialog</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>315</x> - <y>272</y> - </hint> - <hint type="destinationlabel"> - <x>271</x> - <y>258</y> - </hint> - </hints> - </connection> - </connections> -</ui> diff --git a/gui/dialogs/SettingsDialog.cpp b/gui/dialogs/SettingsDialog.cpp index 4229ea91..87da0c68 100644 --- a/gui/dialogs/SettingsDialog.cpp +++ b/gui/dialogs/SettingsDialog.cpp @@ -22,10 +22,11 @@ #include "gui/dialogs/VersionSelectDialog.h" #include "gui/dialogs/CustomMessageBox.h" -#include "logic/JavaUtils.h" #include "logic/NagUtils.h" -#include "logic/lists/JavaVersionList.h" -#include <logic/JavaChecker.h> + +#include "logic/java/JavaUtils.h" +#include "logic/java/JavaVersionList.h" +#include "logic/java/JavaChecker.h" #include "logic/updater/UpdateChecker.h" @@ -37,6 +38,15 @@ #include <QMessageBox> #include <QDir> +// FIXME: possibly move elsewhere +enum InstSortMode +{ + // Sort alphabetically by name. + Sort_Name, + // Sort by which instance was launched most recently. + Sort_LastLaunch +}; + SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent), ui(new Ui::SettingsDialog) { MultiMCPlatform::fixWM_CLASS(this); @@ -423,7 +433,7 @@ void SettingsDialog::loadSettings(SettingsObject *s) foreach(const QString & lang, QDir(MMC->staticData() + "/translations") .entryList(QStringList() << "*.qm", QDir::Files)) { - QLocale locale(lang.section(QRegExp("[_\.]"), 1)); + QLocale locale(lang.section(QRegExp("[_\\.]"), 1)); ui->languageBox->addItem(QLocale::languageToString(locale.language()), locale); } ui->languageBox->setCurrentIndex( diff --git a/gui/dialogs/SettingsDialog.h b/gui/dialogs/SettingsDialog.h index d8495fdd..c65e9fb5 100644 --- a/gui/dialogs/SettingsDialog.h +++ b/gui/dialogs/SettingsDialog.h @@ -18,7 +18,7 @@ #include <memory> #include <QDialog> -#include "logic/JavaChecker.h" +#include "logic/java/JavaChecker.h" class SettingsObject; diff --git a/gui/dialogs/VersionSelectDialog.cpp b/gui/dialogs/VersionSelectDialog.cpp index cae5a732..fd8b569d 100644 --- a/gui/dialogs/VersionSelectDialog.cpp +++ b/gui/dialogs/VersionSelectDialog.cpp @@ -24,7 +24,7 @@ #include "gui/Platform.h" #include <logic/BaseVersion.h> -#include <logic/lists/BaseVersionList.h> +#include <logic/BaseVersionList.h> #include <logic/tasks/Task.h> VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent, diff --git a/gui/groupview/InstanceDelegate.cpp b/gui/groupview/InstanceDelegate.cpp index cd26ddaa..64dc31d2 100644 --- a/gui/groupview/InstanceDelegate.cpp +++ b/gui/groupview/InstanceDelegate.cpp @@ -22,7 +22,7 @@ #include "GroupView.h" #include "logic/BaseInstance.h" -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" QCache<QString, QPixmap> ListViewDelegate::m_pixmapCache; diff --git a/gui/pagedialog/PageDialog.cpp b/gui/pagedialog/PageDialog.cpp new file mode 100644 index 00000000..07027a84 --- /dev/null +++ b/gui/pagedialog/PageDialog.cpp @@ -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. + */ + +#include "PageDialog.h" +#include "gui/Platform.h" +#include "MultiMC.h" +#include <settingsobject.h> + +#include <gui/widgets/IconLabel.h> +#include <gui/widgets/PageContainer.h> +#include <QDialogButtonBox> +#include <QPushButton> +#include <QVBoxLayout> +#include <QtGui/QKeyEvent> + +PageDialog::PageDialog(BasePageProviderPtr pageProvider, QString defaultId, QWidget *parent) : QDialog(parent) +{ + MultiMCPlatform::fixWM_CLASS(this); + setWindowTitle(pageProvider->dialogTitle()); + m_container = new PageContainer(pageProvider, defaultId, this); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(m_container); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0,0,0,0); + setLayout(mainLayout); + + QDialogButtonBox *buttons = + new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close); + buttons->button(QDialogButtonBox::Close)->setDefault(true); + m_container->addButtons(buttons); + + connect(buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(close())); + connect(buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()), m_container, SLOT(help())); + + restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("PagedGeometry").toByteArray())); +} + +void PageDialog::closeEvent(QCloseEvent * event) +{ + if(m_container->requestClose(event)) + { + MMC->settings()->set("PagedGeometry", saveGeometry().toBase64()); + QDialog::closeEvent(event); + } +} diff --git a/gui/dialogs/EditNotesDialog.h b/gui/pagedialog/PageDialog.h index b74558c4..097eac64 100644 --- a/gui/dialogs/EditNotesDialog.h +++ b/gui/pagedialog/PageDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013 MultiMC Contributors +/* 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. @@ -14,25 +14,22 @@ */ #pragma once - #include <QDialog> +#include <gui/pages/BasePageProvider.h> -namespace Ui -{ -class EditNotesDialog; -} - -class EditNotesDialog : public QDialog +class PageContainer; +class PageDialog : public QDialog { Q_OBJECT - public: - explicit EditNotesDialog(QString notes, QString name, QWidget *parent = 0); - ~EditNotesDialog(); - QString getText(); + explicit PageDialog(BasePageProviderPtr pageProvider, QString defaultId = QString(), + QWidget *parent = 0); + virtual ~PageDialog() {}; + +private +slots: + virtual void closeEvent(QCloseEvent *event); private: - Ui::EditNotesDialog *ui; - QString m_instance_name; - QString m_instance_notes; + PageContainer * m_container; }; diff --git a/gui/pages/BasePage.h b/gui/pages/BasePage.h new file mode 100644 index 00000000..09af3a59 --- /dev/null +++ b/gui/pages/BasePage.h @@ -0,0 +1,48 @@ +/* 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 <QString> +#include <QIcon> +#include <memory> + +class BasePage +{ +public: + virtual ~BasePage(){}; + virtual QString id() = 0; + virtual QString displayName() = 0; + virtual QIcon icon() = 0; + virtual bool apply() + { + return true; + } + virtual bool shouldDisplay() + { + return true; + } + virtual QString helpPage() + { + return QString(); + } + virtual void opened() + { + + } + int stackIndex = -1; + int listIndex = -1; +}; + +typedef std::shared_ptr<BasePage> BasePagePtr; diff --git a/logic/NostalgiaInstance.h b/gui/pages/BasePageProvider.h index f95531d2..cff9c8e7 100644 --- a/logic/NostalgiaInstance.h +++ b/gui/pages/BasePageProvider.h @@ -1,4 +1,4 @@ -/* Copyright 2013 MultiMC Contributors +/* 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. @@ -15,15 +15,14 @@ #pragma once -#include "OneSixInstance.h" +#include "BasePage.h" +#include <memory> -class NostalgiaInstance : public OneSixInstance +class BasePageProvider { - Q_OBJECT public: - explicit NostalgiaInstance(const QString &rootDir, SettingsObject *settings, - QObject *parent = 0); - virtual ~NostalgiaInstance() {}; - virtual QString getStatusbarDescription(); - virtual bool menuActionEnabled(QString action_name) const; + virtual QList<BasePage *> getPages() = 0; + virtual QString dialogTitle() = 0; }; + +typedef std::shared_ptr<BasePageProvider> BasePageProviderPtr; diff --git a/gui/pages/InstanceSettingsPage.cpp b/gui/pages/InstanceSettingsPage.cpp new file mode 100644 index 00000000..6e2ce238 --- /dev/null +++ b/gui/pages/InstanceSettingsPage.cpp @@ -0,0 +1,229 @@ +#include "InstanceSettingsPage.h" +#include <gui/dialogs/VersionSelectDialog.h> +#include "logic/NagUtils.h" +#include <logic/java/JavaVersionList.h> +#include "MultiMC.h" +#include <QDialog> +#include <QFileDialog> +#include <QMessageBox> +#include "ui_InstanceSettingsPage.h" + +QString InstanceSettingsPage::displayName() +{ + return tr("Settings"); +} + +QIcon InstanceSettingsPage::icon() +{ + return QIcon::fromTheme("settings"); +} + +QString InstanceSettingsPage::id() +{ + return "settings"; +} + +InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) + : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst) +{ + m_settings = &(inst->settings()); + ui->setupUi(this); + loadSettings(); +} + +bool InstanceSettingsPage::shouldDisplay() +{ + return !m_instance->isRunning(); +} + +InstanceSettingsPage::~InstanceSettingsPage() +{ + delete ui; +} + +bool InstanceSettingsPage::apply() +{ + applySettings(); + return true; +} + +void InstanceSettingsPage::applySettings() +{ + // Console + bool console = ui->consoleSettingsBox->isChecked(); + m_settings->set("OverrideConsole", console); + if (console) + { + m_settings->set("ShowConsole", ui->showConsoleCheck->isChecked()); + m_settings->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); + } + else + { + m_settings->reset("ShowConsole"); + m_settings->reset("AutoCloseConsole"); + } + + // Window Size + bool window = ui->windowSizeGroupBox->isChecked(); + m_settings->set("OverrideWindow", window); + if (window) + { + m_settings->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); + m_settings->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); + m_settings->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); + } + else + { + m_settings->reset("LaunchMaximized"); + m_settings->reset("MinecraftWinWidth"); + m_settings->reset("MinecraftWinHeight"); + } + + // Memory + bool memory = ui->memoryGroupBox->isChecked(); + m_settings->set("OverrideMemory", memory); + if (memory) + { + m_settings->set("MinMemAlloc", ui->minMemSpinBox->value()); + m_settings->set("MaxMemAlloc", ui->maxMemSpinBox->value()); + m_settings->set("PermGen", ui->permGenSpinBox->value()); + } + else + { + m_settings->reset("MinMemAlloc"); + m_settings->reset("MaxMemAlloc"); + m_settings->reset("PermGen"); + } + + // Java Install Settings + bool javaInstall = ui->javaSettingsGroupBox->isChecked(); + m_settings->set("OverrideJavaLocation", javaInstall); + if (javaInstall) + { + m_settings->set("JavaPath", ui->javaPathTextBox->text()); + } + else + { + m_settings->reset("JavaPath"); + } + + // Java arguments + bool javaArgs = ui->javaArgumentsGroupBox->isChecked(); + m_settings->set("OverrideJavaArgs", javaArgs); + if(javaArgs) + { + m_settings->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); + NagUtils::checkJVMArgs(m_settings->get("JvmArgs").toString(), this->parentWidget()); + } + else + { + m_settings->reset("JvmArgs"); + } + + // old generic 'override both' is removed. + m_settings->reset("OverrideJava"); + + // Custom Commands + bool custcmd = ui->customCommandsGroupBox->isChecked(); + m_settings->set("OverrideCommands", custcmd); + if (custcmd) + { + m_settings->set("PreLaunchCommand", ui->preLaunchCmdTextBox->text()); + m_settings->set("PostExitCommand", ui->postExitCmdTextBox->text()); + } + else + { + m_settings->reset("PreLaunchCommand"); + m_settings->reset("PostExitCommand"); + } +} + +void InstanceSettingsPage::loadSettings() +{ + // Console + ui->consoleSettingsBox->setChecked(m_settings->get("OverrideConsole").toBool()); + ui->showConsoleCheck->setChecked(m_settings->get("ShowConsole").toBool()); + ui->autoCloseConsoleCheck->setChecked(m_settings->get("AutoCloseConsole").toBool()); + + // Window Size + ui->windowSizeGroupBox->setChecked(m_settings->get("OverrideWindow").toBool()); + ui->maximizedCheckBox->setChecked(m_settings->get("LaunchMaximized").toBool()); + ui->windowWidthSpinBox->setValue(m_settings->get("MinecraftWinWidth").toInt()); + ui->windowHeightSpinBox->setValue(m_settings->get("MinecraftWinHeight").toInt()); + + // Memory + ui->memoryGroupBox->setChecked(m_settings->get("OverrideMemory").toBool()); + ui->minMemSpinBox->setValue(m_settings->get("MinMemAlloc").toInt()); + ui->maxMemSpinBox->setValue(m_settings->get("MaxMemAlloc").toInt()); + ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt()); + + // Java Settings + bool overrideJava = m_settings->get("OverrideJava").toBool(); + bool overrideLocation = m_settings->get("OverrideJavaLocation").toBool() || overrideJava; + bool overrideArgs = m_settings->get("OverrideJavaArgs").toBool() || overrideJava; + + ui->javaSettingsGroupBox->setChecked(overrideLocation); + ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString()); + + ui->javaArgumentsGroupBox->setChecked(overrideArgs); + ui->jvmArgsTextBox->setPlainText(m_settings->get("JvmArgs").toString()); + + // Custom Commands + ui->customCommandsGroupBox->setChecked(m_settings->get("OverrideCommands").toBool()); + ui->preLaunchCmdTextBox->setText(m_settings->get("PreLaunchCommand").toString()); + ui->postExitCmdTextBox->setText(m_settings->get("PostExitCommand").toString()); +} + +void InstanceSettingsPage::on_javaDetectBtn_clicked() +{ + JavaVersionPtr java; + + VersionSelectDialog vselect(MMC->javalist().get(), tr("Select a Java version"), this, true); + vselect.setResizeOn(2); + vselect.exec(); + + if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) + { + java = std::dynamic_pointer_cast<JavaVersion>(vselect.selectedVersion()); + ui->javaPathTextBox->setText(java->path); + } +} + +void InstanceSettingsPage::on_javaBrowseBtn_clicked() +{ + QString dir = QFileDialog::getOpenFileName(this, tr("Find Java executable")); + if (!dir.isNull()) + { + ui->javaPathTextBox->setText(dir); + } +} + +void InstanceSettingsPage::on_javaTestBtn_clicked() +{ + checker.reset(new JavaChecker()); + connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this, + SLOT(checkFinished(JavaCheckResult))); + checker->path = ui->javaPathTextBox->text(); + checker->performCheck(); +} + +void InstanceSettingsPage::checkFinished(JavaCheckResult result) +{ + if (result.valid) + { + QString text; + text += "Java test succeeded!\n"; + if (result.is_64bit) + text += "Using 64bit java.\n"; + text += "\n"; + text += "Platform reported: " + result.realPlatform; + QMessageBox::information(this, tr("Java test success"), text); + } + else + { + QMessageBox::warning( + this, tr("Java test failure"), + tr("The specified java binary didn't work. You should use the auto-detect feature, " + "or set the path to the java executable.")); + } +} diff --git a/gui/dialogs/InstanceSettings.h b/gui/pages/InstanceSettingsPage.h index e296db4c..2447168f 100644 --- a/gui/dialogs/InstanceSettings.h +++ b/gui/pages/InstanceSettingsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013 MultiMC Contributors +/* 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. @@ -14,38 +14,33 @@ */ #pragma once +#include <QWidget> -#include <QDialog> -#include "settingsobject.h" -#include "logic/JavaChecker.h" +#include <logic/OneSixInstance.h> +#include <logic/net/NetJob.h> +#include <logic/java/JavaChecker.h> +#include "BasePage.h" +class JavaChecker; namespace Ui { -class InstanceSettings; +class InstanceSettingsPage; } -class InstanceSettings : public QDialog +class InstanceSettingsPage : public QWidget, public BasePage { Q_OBJECT public: - explicit InstanceSettings(SettingsObject *s, QWidget *parent = 0); - ~InstanceSettings(); - - void updateCheckboxStuff(); - - void applySettings(); - void loadSettings(); - -protected: - virtual void showEvent(QShowEvent *); - virtual void closeEvent(QCloseEvent *); -private -slots: - void on_customCommandsGroupBox_toggled(bool arg1); - void on_buttonBox_accepted(); - void on_buttonBox_rejected(); - + explicit InstanceSettingsPage(BaseInstance *inst, QWidget *parent = 0); + virtual ~InstanceSettingsPage(); + virtual QString displayName() override; + virtual QIcon icon() override; + virtual QString id() override; + virtual bool apply(); + virtual QString helpPage() override { return "Instance-settings"; } + virtual bool shouldDisplay(); +private slots: void on_javaDetectBtn_clicked(); void on_javaTestBtn_clicked(); @@ -53,8 +48,12 @@ slots: void on_javaBrowseBtn_clicked(); void checkFinished(JavaCheckResult result); + + void applySettings(); + void loadSettings(); private: - Ui::InstanceSettings *ui; - SettingsObject *m_obj; + Ui::InstanceSettingsPage *ui; + BaseInstance *m_instance; + SettingsObject *m_settings; std::shared_ptr<JavaChecker> checker; }; diff --git a/gui/dialogs/InstanceSettings.ui b/gui/pages/InstanceSettingsPage.ui index 9c7e1757..b8af6c60 100644 --- a/gui/dialogs/InstanceSettings.ui +++ b/gui/pages/InstanceSettingsPage.ui @@ -1,19 +1,31 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>InstanceSettings</class> - <widget class="QDialog" name="InstanceSettings"> + <class>InstanceSettingsPage</class> + <widget class="QWidget" name="InstanceSettingsPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>526</width> - <height>637</height> + <width>458</width> + <height>426</height> </rect> </property> <property name="windowTitle"> - <string>Instance Settings</string> + <string>Form</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> <item> <widget class="QTabWidget" name="settingsTabs"> <property name="tabShape"> @@ -24,16 +36,16 @@ </property> <widget class="QWidget" name="minecraftTab"> <attribute name="title"> - <string>Minecraft</string> + <string>Java</string> </attribute> - <layout class="QVBoxLayout" name="verticalLayout_3"> + <layout class="QVBoxLayout" name="verticalLayout_5"> <item> - <widget class="QGroupBox" name="windowSizeGroupBox"> + <widget class="QGroupBox" name="javaSettingsGroupBox"> <property name="enabled"> <bool>true</bool> </property> <property name="title"> - <string>Window Size</string> + <string>Java installation</string> </property> <property name="checkable"> <bool>true</bool> @@ -41,90 +53,28 @@ <property name="checked"> <bool>false</bool> </property> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QCheckBox" name="maximizedCheckBox"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="3"> + <widget class="QLineEdit" name="javaPathTextBox"/> + </item> + <item row="1" column="0"> + <widget class="QPushButton" name="javaDetectBtn"> <property name="text"> - <string>Start Minecraft maximized?</string> + <string>Auto-detect...</string> </property> </widget> </item> - <item> - <layout class="QGridLayout" name="gridLayoutWindowSize"> - <item row="1" column="0"> - <widget class="QLabel" name="labelWindowHeight"> - <property name="text"> - <string>Window height:</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="labelWindowWidth"> - <property name="text"> - <string>Window width:</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QSpinBox" name="windowWidthSpinBox"> - <property name="minimum"> - <number>854</number> - </property> - <property name="maximum"> - <number>65536</number> - </property> - <property name="singleStep"> - <number>1</number> - </property> - <property name="value"> - <number>854</number> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QSpinBox" name="windowHeightSpinBox"> - <property name="minimum"> - <number>480</number> - </property> - <property name="maximum"> - <number>65536</number> - </property> - <property name="value"> - <number>480</number> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="consoleSettingsBox"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="title"> - <string>Console Settings</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="checked"> - <bool>false</bool> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QCheckBox" name="showConsoleCheck"> + <item row="1" column="1"> + <widget class="QPushButton" name="javaBrowseBtn"> <property name="text"> - <string>Show console while the game is running?</string> + <string>Browse...</string> </property> </widget> </item> - <item> - <widget class="QCheckBox" name="autoCloseConsoleCheck"> + <item row="1" column="2"> + <widget class="QPushButton" name="javaTestBtn"> <property name="text"> - <string>Automatically close console when the game quits?</string> + <string>Test</string> </property> </widget> </item> @@ -132,26 +82,6 @@ </widget> </item> <item> - <spacer name="verticalSpacerMinecraft"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <widget class="QWidget" name="javaTab"> - <attribute name="title"> - <string>Java</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> <widget class="QGroupBox" name="memoryGroupBox"> <property name="enabled"> <bool>true</bool> @@ -257,12 +187,12 @@ </widget> </item> <item> - <widget class="QGroupBox" name="javaSettingsGroupBox"> + <widget class="QGroupBox" name="javaArgumentsGroupBox"> <property name="enabled"> <bool>true</bool> </property> <property name="title"> - <string>Java Settings</string> + <string>Java arguments</string> </property> <property name="checkable"> <bool>true</bool> @@ -270,45 +200,131 @@ <property name="checked"> <bool>false</bool> </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="2" column="4"> - <widget class="QPushButton" name="javaTestBtn"> - <property name="text"> - <string>Test</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="labelJavaPath"> - <property name="text"> - <string>Java path:</string> - </property> - </widget> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="1" column="1"> + <widget class="QPlainTextEdit" name="jvmArgsTextBox"/> </item> - <item row="3" column="0"> - <widget class="QLabel" name="labelJVMArgs"> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacerMinecraft"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="javaTab"> + <attribute name="title"> + <string>Game windows</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QGroupBox" name="windowSizeGroupBox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="title"> + <string>Game Window</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QCheckBox" name="maximizedCheckBox"> <property name="text"> - <string>JVM arguments:</string> + <string>Start Minecraft maximized?</string> </property> </widget> </item> - <item row="3" column="2" colspan="3"> - <widget class="QLineEdit" name="jvmArgsTextBox"/> - </item> - <item row="0" column="2" colspan="3"> - <widget class="QLineEdit" name="javaPathTextBox"/> + <item> + <layout class="QGridLayout" name="gridLayoutWindowSize"> + <item row="1" column="0"> + <widget class="QLabel" name="labelWindowHeight"> + <property name="text"> + <string>Window height:</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="labelWindowWidth"> + <property name="text"> + <string>Window width:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="windowWidthSpinBox"> + <property name="minimum"> + <number>854</number> + </property> + <property name="maximum"> + <number>65536</number> + </property> + <property name="singleStep"> + <number>1</number> + </property> + <property name="value"> + <number>854</number> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="windowHeightSpinBox"> + <property name="minimum"> + <number>480</number> + </property> + <property name="maximum"> + <number>65536</number> + </property> + <property name="value"> + <number>480</number> + </property> + </widget> + </item> + </layout> </item> - <item row="2" column="3"> - <widget class="QPushButton" name="javaBrowseBtn"> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="consoleSettingsBox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="title"> + <string>Console Settings</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QCheckBox" name="showConsoleCheck"> <property name="text"> - <string>Browse...</string> + <string>Show console while the game is running?</string> </property> </widget> </item> - <item row="2" column="2"> - <widget class="QPushButton" name="javaDetectBtn"> + <item> + <widget class="QCheckBox" name="autoCloseConsoleCheck"> <property name="text"> - <string>Auto-detect...</string> + <string>Automatically close console when the game quits?</string> </property> </widget> </item> @@ -316,6 +332,26 @@ </widget> </item> <item> + <spacer name="verticalSpacerMinecraft_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>88</width> + <height>125</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>Custom commands</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> <widget class="QGroupBox" name="customCommandsGroupBox"> <property name="enabled"> <bool>true</bool> @@ -358,12 +394,6 @@ <property name="enabled"> <bool>false</bool> </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> <property name="text"> <string>Pre-launch command runs before the instance launches and post-exit command runs after it exits. Both will be run in MultiMC's working directory with INST_ID, INST_DIR, and INST_NAME as environment variables.</string> </property> @@ -378,42 +408,25 @@ </property> </widget> </item> + <item> + <spacer name="verticalSpacerMinecraft_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>88</width> + <height>186</height> + </size> + </property> + </spacer> + </item> </layout> </widget> </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> - <tabstops> - <tabstop>settingsTabs</tabstop> - <tabstop>buttonBox</tabstop> - <tabstop>windowSizeGroupBox</tabstop> - <tabstop>maximizedCheckBox</tabstop> - <tabstop>windowWidthSpinBox</tabstop> - <tabstop>windowHeightSpinBox</tabstop> - <tabstop>consoleSettingsBox</tabstop> - <tabstop>showConsoleCheck</tabstop> - <tabstop>autoCloseConsoleCheck</tabstop> - <tabstop>memoryGroupBox</tabstop> - <tabstop>minMemSpinBox</tabstop> - <tabstop>maxMemSpinBox</tabstop> - <tabstop>permGenSpinBox</tabstop> - <tabstop>javaSettingsGroupBox</tabstop> - <tabstop>jvmArgsTextBox</tabstop> - <tabstop>customCommandsGroupBox</tabstop> - <tabstop>preLaunchCmdTextBox</tabstop> - <tabstop>postExitCmdTextBox</tabstop> - </tabstops> <resources/> <connections/> </ui> diff --git a/gui/pages/LegacyJarModPage.cpp b/gui/pages/LegacyJarModPage.cpp new file mode 100644 index 00000000..b1c0d49a --- /dev/null +++ b/gui/pages/LegacyJarModPage.cpp @@ -0,0 +1,208 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LegacyJarModPage.h" +#include "ui_LegacyJarModPage.h" +#include "gui/dialogs/VersionSelectDialog.h" +#include "gui/dialogs/ProgressDialog.h" +#include "gui/dialogs/ModEditDialogCommon.h" +#include "logic/ModList.h" +#include "logic/LegacyInstance.h" +#include "logic/forge/ForgeVersion.h" +#include "logic/forge/ForgeVersionList.h" +#include "MultiMC.h" +#include <pathutils.h> +#include <QtGui/QKeyEvent> +#include <QFileDialog> +#include <QKeyEvent> + +LegacyJarModPage::LegacyJarModPage(LegacyInstance *inst, QWidget *parent) + : QWidget(parent), ui(new Ui::LegacyJarModPage), m_inst(inst) +{ + ui->setupUi(this); + m_jarmods = m_inst->jarModList(); + ui->jarModsTreeView->setModel(m_jarmods.get()); + ui->jarModsTreeView->setDragDropMode(QAbstractItemView::DragDrop); + ui->jarModsTreeView->setSelectionMode(QAbstractItemView::SingleSelection); + ui->jarModsTreeView->installEventFilter(this); + m_jarmods->startWatching(); + auto smodel = ui->jarModsTreeView->selectionModel(); + connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), + SLOT(jarCurrent(QModelIndex, QModelIndex))); +} + +LegacyJarModPage::~LegacyJarModPage() +{ + m_jarmods->stopWatching(); + delete ui; +} + +QString LegacyJarModPage::displayName() +{ + return tr("Jar Mods"); +} + +bool LegacyJarModPage::shouldDisplay() +{ + return !m_inst->isRunning(); +} + +QIcon LegacyJarModPage::icon() +{ + return QIcon::fromTheme("plugin-red"); +} + +QString LegacyJarModPage::id() +{ + return "jarmods"; +} + +bool LegacyJarModPage::eventFilter(QObject *obj, QEvent *ev) +{ + if (ev->type() != QEvent::KeyPress || obj != ui->jarModsTreeView) + { + return QWidget::eventFilter(obj, ev); + } + + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); + switch (keyEvent->key()) + { + case Qt::Key_Up: + { + if (keyEvent->modifiers() & Qt::ControlModifier) + { + on_moveJarUpBtn_clicked(); + return true; + } + break; + } + case Qt::Key_Down: + { + if (keyEvent->modifiers() & Qt::ControlModifier) + { + on_moveJarDownBtn_clicked(); + return true; + } + break; + } + case Qt::Key_Delete: + on_rmJarBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addJarBtn_clicked(); + return true; + default: + break; + } + return QWidget::eventFilter(obj, ev); +} + +void LegacyJarModPage::on_addForgeBtn_clicked() +{ + VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); + vselect.setExactFilter(1, m_inst->intendedVersionId()); + if (vselect.exec() && vselect.selectedVersion()) + { + ForgeVersionPtr forge = + std::dynamic_pointer_cast<ForgeVersion>(vselect.selectedVersion()); + if (!forge) + return; + auto entry = MMC->metacache()->resolveEntry("minecraftforge", forge->filename()); + if (entry->stale) + { + NetJob *fjob = new NetJob("Forge download"); + fjob->addNetAction(CacheDownload::make(forge->universal_url, entry)); + ProgressDialog dlg(this); + dlg.exec(fjob); + if (dlg.result() == QDialog::Accepted) + { + m_jarmods->stopWatching(); + m_jarmods->installMod(QFileInfo(entry->getFullPath())); + m_jarmods->startWatching(); + } + else + { + // failed to download forge :/ + } + } + else + { + m_jarmods->stopWatching(); + m_jarmods->installMod(QFileInfo(entry->getFullPath())); + m_jarmods->startWatching(); + } + } +} +void LegacyJarModPage::on_addJarBtn_clicked() +{ + //: Title of jar mod selection dialog + QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select Jar Mods")); + for (auto filename : fileNames) + { + m_jarmods->stopWatching(); + m_jarmods->installMod(QFileInfo(filename)); + m_jarmods->startWatching(); + } +} + +void LegacyJarModPage::on_moveJarDownBtn_clicked() +{ + int first, last; + auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); + + if (!lastfirst(list, first, last)) + return; + + m_jarmods->moveModsDown(first, last); +} + +void LegacyJarModPage::on_moveJarUpBtn_clicked() +{ + int first, last; + auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); + + if (!lastfirst(list, first, last)) + return; + m_jarmods->moveModsUp(first, last); +} + +void LegacyJarModPage::on_rmJarBtn_clicked() +{ + int first, last; + auto list = ui->jarModsTreeView->selectionModel()->selectedRows(); + + if (!lastfirst(list, first, last)) + return; + m_jarmods->stopWatching(); + m_jarmods->deleteMods(first, last); + m_jarmods->startWatching(); +} + +void LegacyJarModPage::on_viewJarBtn_clicked() +{ + openDirInDefaultProgram(m_inst->jarModsDir(), true); +} + +void LegacyJarModPage::jarCurrent(QModelIndex current, QModelIndex previous) +{ + if (!current.isValid()) + { + ui->jarMIFrame->clear(); + return; + } + int row = current.row(); + Mod &m = m_jarmods->operator[](row); + ui->jarMIFrame->updateWithMod(m); +} diff --git a/gui/pages/LegacyJarModPage.h b/gui/pages/LegacyJarModPage.h new file mode 100644 index 00000000..016f4a8f --- /dev/null +++ b/gui/pages/LegacyJarModPage.h @@ -0,0 +1,63 @@ +/* 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 <QDialog> +#include <logic/net/NetJob.h> +#include "BasePage.h" + +class ModList; +class LegacyInstance; +namespace Ui +{ +class LegacyJarModPage; +} + +class LegacyJarModPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit LegacyJarModPage(LegacyInstance *inst, QWidget *parent = 0); + virtual ~LegacyJarModPage(); + + virtual QString displayName(); + virtual QIcon icon(); + virtual QString id(); + virtual QString helpPage() override { return "Legacy-jar-mods"; }; + virtual bool shouldDisplay(); + +private +slots: + + void on_addJarBtn_clicked(); + void on_rmJarBtn_clicked(); + void on_addForgeBtn_clicked(); + void on_moveJarUpBtn_clicked(); + void on_moveJarDownBtn_clicked(); + void on_viewJarBtn_clicked(); + + void jarCurrent(QModelIndex current, QModelIndex previous); + +protected: + virtual bool eventFilter(QObject *obj, QEvent *ev) override; + +private: + Ui::LegacyJarModPage *ui; + std::shared_ptr<ModList> m_jarmods; + LegacyInstance *m_inst; + NetJobPtr forgeJob; +}; diff --git a/gui/pages/LegacyJarModPage.ui b/gui/pages/LegacyJarModPage.ui new file mode 100644 index 00000000..a1da2b20 --- /dev/null +++ b/gui/pages/LegacyJarModPage.ui @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>LegacyJarModPage</class> + <widget class="QWidget" name="LegacyJarModPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>378</width> + <height>324</height> + </rect> + </property> + <property name="windowTitle"> + <string>LegacyJarModPage</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="ModListView" name="jarModsTreeView"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="jarModsButtonBox"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Selection</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="rmJarBtn"> + <property name="text"> + <string>&Remove</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveJarUpBtn"> + <property name="text"> + <string>Move &Up</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveJarDownBtn"> + <property name="text"> + <string>Move &Down</string> + </property> + </widget> + </item> + <item> + <widget class="LineSeparator" name="separator" native="true"/> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Install</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="addJarBtn"> + <property name="text"> + <string>&Add jar mod</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="addForgeBtn"> + <property name="text"> + <string>Install Forge</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="viewJarBtn"> + <property name="text"> + <string>&View Folder</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="MCModInfoFrame" name="jarMIFrame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ModListView</class> + <extends>QTreeView</extends> + <header>gui/widgets/ModListView.h</header> + </customwidget> + <customwidget> + <class>MCModInfoFrame</class> + <extends>QFrame</extends> + <header>gui/widgets/MCModInfoFrame.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>LineSeparator</class> + <extends>QWidget</extends> + <header>gui/widgets/LineSeparator.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/gui/pages/LegacyUpgradePage.cpp b/gui/pages/LegacyUpgradePage.cpp new file mode 100644 index 00000000..bb54210c --- /dev/null +++ b/gui/pages/LegacyUpgradePage.cpp @@ -0,0 +1,39 @@ +#include "LegacyUpgradePage.h" +#include <logic/LegacyInstance.h> +#include "ui_LegacyUpgradePage.h" + +QString LegacyUpgradePage::displayName() +{ + return tr("Upgrade"); +} + +QIcon LegacyUpgradePage::icon() +{ + return QIcon::fromTheme("checkupdate"); +} + +QString LegacyUpgradePage::id() +{ + return "upgrade"; +} + +LegacyUpgradePage::LegacyUpgradePage(LegacyInstance *inst, QWidget *parent) + : QWidget(parent), ui(new Ui::LegacyUpgradePage), m_inst(inst) +{ + ui->setupUi(this); +} + +LegacyUpgradePage::~LegacyUpgradePage() +{ + delete ui; +} + +void LegacyUpgradePage::on_upgradeButton_clicked() +{ + // now what? +} + +bool LegacyUpgradePage::shouldDisplay() +{ + return !m_inst->isRunning(); +}
\ No newline at end of file diff --git a/gui/pages/LegacyUpgradePage.h b/gui/pages/LegacyUpgradePage.h new file mode 100644 index 00000000..eb816a7a --- /dev/null +++ b/gui/pages/LegacyUpgradePage.h @@ -0,0 +1,48 @@ +/* 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 <QWidget> + +#include <logic/OneSixInstance.h> +#include <logic/net/NetJob.h> +#include "BasePage.h" + +class EnabledItemFilter; +namespace Ui +{ +class LegacyUpgradePage; +} + +class LegacyUpgradePage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit LegacyUpgradePage(LegacyInstance *inst, QWidget *parent = 0); + virtual ~LegacyUpgradePage(); + virtual QString displayName() override; + virtual QIcon icon() override; + virtual QString id() override; + virtual QString helpPage() override { return "Legacy-upgrade"; }; + virtual bool shouldDisplay(); +private +slots: + void on_upgradeButton_clicked(); + +private: + Ui::LegacyUpgradePage *ui; + LegacyInstance *m_inst; +}; diff --git a/gui/pages/LegacyUpgradePage.ui b/gui/pages/LegacyUpgradePage.ui new file mode 100644 index 00000000..8d676eae --- /dev/null +++ b/gui/pages/LegacyUpgradePage.ui @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>LegacyUpgradePage</class> + <widget class="QWidget" name="LegacyUpgradePage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>546</width> + <height>405</height> + </rect> + </property> + <property name="windowTitle"> + <string>Upgrade</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QTextBrowser" name="textBrowser"> + <property name="html"> + <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;"> </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:18pt; font-weight:600;">New format is available</span> </p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">MultiMC now supports old Minecraft versions in the new (OneSix) instance format. The old format won't be getting any new features and only the most critical bugfixes. As a consequence, you should upgrade this instance. </p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The upgrade will create a new instance with the same contents as the current one, in the new format. The original instance will remain untouched, in case anything goes wrong in the process. </p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Please report any issues on our <a href="https://github.com/MultiMC/MultiMC5/issues"><img src=":/icons/multimc/22x22/bug.png" /></a><a href="https://github.com/MultiMC/MultiMC5/issues"><span style=" text-decoration: underline; color:#68a0df;">github issues page</span></a>.</p></body></html></string> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCommandLinkButton" name="upgradeButton"> + <property name="text"> + <string>Start the upgrade! (Not Yet Implemented, Coming Soonâ„¢)</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/gui/pages/LogPage.cpp b/gui/pages/LogPage.cpp new file mode 100644 index 00000000..dd088862 --- /dev/null +++ b/gui/pages/LogPage.cpp @@ -0,0 +1,137 @@ +#include "LogPage.h" +#include <gui/dialogs/CustomMessageBox.h> +#include <gui/dialogs/ProgressDialog.h> +#include <logic/MinecraftProcess.h> +#include <QtGui/QIcon> +#include "ui_LogPage.h" +#include "logic/net/PasteUpload.h" +#include <QScrollBar> + +QString LogPage::displayName() +{ + return tr("Minecraft Log"); +} + +QIcon LogPage::icon() +{ + return QIcon::fromTheme("refresh"); +} + +QString LogPage::id() +{ + return "console"; +} + +LogPage::LogPage(MinecraftProcess *proc, QWidget *parent) + : QWidget(parent), ui(new Ui::LogPage), m_process(proc) +{ + ui->setupUi(this); + connect(m_process, SIGNAL(log(QString, MessageLevel::Enum)), this, + SLOT(write(QString, MessageLevel::Enum))); +} + +LogPage::~LogPage() +{ + delete ui; +} + +bool LogPage::apply() +{ + return true; +} + +bool LogPage::shouldDisplay() +{ + return m_process->instance()->isRunning(); +} + +void LogPage::on_btnPaste_clicked() +{ + auto text = ui->text->toPlainText(); + ProgressDialog dialog(this); + PasteUpload *paste = new PasteUpload(this, text); + dialog.exec(paste); + if (!paste->successful()) + { + CustomMessageBox::selectable(this, "Upload failed", paste->failReason(), + QMessageBox::Critical)->exec(); + } +} + +void LogPage::writeColor(QString text, const char *color, const char * background) +{ + // append a paragraph + QString newtext; + newtext += "<span style=\""; + { + if (color) + newtext += QString("color:") + color + ";"; + if (background) + newtext += QString("background-color:") + background + ";"; + newtext += "font-family: monospace;"; + } + newtext += "\">"; + newtext += text.toHtmlEscaped(); + newtext += "</span>"; + ui->text->appendHtml(newtext); +} + +void LogPage::write(QString data, MessageLevel::Enum mode) +{ + QScrollBar *bar = ui->text->verticalScrollBar(); + int max_bar = bar->maximum(); + int val_bar = bar->value(); + if(isVisible()) + { + if (m_scroll_active) + { + m_scroll_active = (max_bar - val_bar) <= 1; + } + else + { + m_scroll_active = val_bar == max_bar; + } + } + if (data.endsWith('\n')) + data = data.left(data.length() - 1); + QStringList paragraphs = data.split('\n'); + QStringList filtered; + for (QString ¶graph : paragraphs) + { + // Quick hack for + if(paragraph.contains("Detected an attempt by a mod null to perform game activity during mod construction")) + continue; + filtered.append(paragraph.trimmed()); + } + QListIterator<QString> iter(filtered); + if (mode == MessageLevel::MultiMC) + while (iter.hasNext()) + writeColor(iter.next(), "blue", 0); + else if (mode == MessageLevel::Error) + while (iter.hasNext()) + writeColor(iter.next(), "red", 0); + else if (mode == MessageLevel::Warning) + while (iter.hasNext()) + writeColor(iter.next(), "orange", 0); + else if (mode == MessageLevel::Fatal) + while (iter.hasNext()) + writeColor(iter.next(), "red", "black"); + else if (mode == MessageLevel::Debug) + while (iter.hasNext()) + writeColor(iter.next(), "green", 0); + else if (mode == MessageLevel::PrePost) + while (iter.hasNext()) + writeColor(iter.next(), "grey", 0); + // TODO: implement other MessageLevels + else + while (iter.hasNext()) + writeColor(iter.next(), 0, 0); + if(isVisible()) + { + if (m_scroll_active) + { + bar->setValue(bar->maximum()); + } + m_last_scroll_value = bar->value(); + } +} diff --git a/gui/pages/LogPage.h b/gui/pages/LogPage.h new file mode 100644 index 00000000..7cdea2c1 --- /dev/null +++ b/gui/pages/LogPage.h @@ -0,0 +1,72 @@ +/* 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 <QWidget> + +#include <logic/BaseInstance.h> +#include <logic/net/NetJob.h> +#include <logic/MinecraftProcess.h> +#include "BasePage.h" + +class EnabledItemFilter; +class MinecraftProcess; +namespace Ui +{ +class LogPage; +} + +class LogPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit LogPage(MinecraftProcess *proc, QWidget *parent = 0); + virtual ~LogPage(); + virtual QString displayName() override; + virtual QIcon icon() override; + virtual QString id() override; + virtual bool apply(); + virtual QString helpPage() override { return "Minecraft-Log"; }; + virtual bool shouldDisplay(); + +private: + /** + * @brief write a colored paragraph + * @param data the string + * @param color the css color name + * this will only insert a single paragraph. + * \n are ignored. a real \n is always appended. + */ + void writeColor(QString text, const char *color, const char *background); + +private slots: + /** + * @brief write a string + * @param data the string + * @param mode the WriteMode + * lines have to be put through this as a whole! + */ + void write(QString data, MessageLevel::Enum level = MessageLevel::MultiMC); + void on_btnPaste_clicked(); + +private: + Ui::LogPage *ui; + MinecraftProcess *m_process; + int m_last_scroll_value = 0; + bool m_scroll_active = true; + int m_saved_offset = 0; +}; diff --git a/gui/pages/LogPage.ui b/gui/pages/LogPage.ui new file mode 100644 index 00000000..00b611b5 --- /dev/null +++ b/gui/pages/LogPage.ui @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>LogPage</class> + <widget class="QWidget" name="LogPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>831</width> + <height>596</height> + </rect> + </property> + <property name="windowTitle"> + <string>Log</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QPlainTextEdit" name="text"> + <property name="undoRedoEnabled"> + <bool>false</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="plainText"> + <string notr="true"/> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + <property name="centerOnScroll"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPushButton" name="btnPaste"> + <property name="text"> + <string>Upload Log</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/gui/pages/ModFolderPage.cpp b/gui/pages/ModFolderPage.cpp new file mode 100644 index 00000000..2035e57a --- /dev/null +++ b/gui/pages/ModFolderPage.cpp @@ -0,0 +1,149 @@ +/* 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 "MultiMC.h" + +#include <pathutils.h> +#include <QFileDialog> +#include <QMessageBox> +#include <QDebug> +#include <QEvent> +#include <QKeyEvent> +#include <QDesktopServices> +#include <QAbstractItemModel> + +#include "ModFolderPage.h" +#include "ui_ModFolderPage.h" + +#include "gui/dialogs/CustomMessageBox.h" +#include "gui/dialogs/ModEditDialogCommon.h" + +#include "logic/ModList.h" +#include "logic/Mod.h" + +QString ModFolderPage::displayName() +{ + return m_displayName; +} + +QIcon ModFolderPage::icon() +{ + return QIcon::fromTheme(m_iconName); +} + +QString ModFolderPage::id() +{ + return m_id; +} + +ModFolderPage::ModFolderPage(BaseInstance * inst, std::shared_ptr<ModList> mods, QString id, QString iconName, + QString displayName, QString helpPage, QWidget *parent) + : QWidget(parent), ui(new Ui::ModFolderPage) +{ + ui->setupUi(this); + m_inst = inst; + m_mods = mods; + m_id = id; + m_displayName = displayName; + m_iconName = iconName; + m_helpName = helpPage; + ui->modTreeView->setModel(m_mods.get()); + ui->modTreeView->installEventFilter(this); + m_mods->startWatching(); + auto smodel = ui->modTreeView->selectionModel(); + connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), + SLOT(modCurrent(QModelIndex, QModelIndex))); +} + +ModFolderPage::~ModFolderPage() +{ + m_mods->stopWatching(); + delete ui; +} + +bool ModFolderPage::shouldDisplay() +{ + if(m_inst) + return !m_inst->isRunning(); + return true; +} + +bool ModFolderPage::modListFilter(QKeyEvent *keyEvent) +{ + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_rmModBtn_clicked(); + return true; + case Qt::Key_Plus: + on_addModBtn_clicked(); + return true; + default: + break; + } + return QWidget::eventFilter(ui->modTreeView, keyEvent); +} + +bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev) +{ + if (ev->type() != QEvent::KeyPress) + { + return QWidget::eventFilter(obj, ev); + } + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); + if (obj == ui->modTreeView) + return modListFilter(keyEvent); + return QWidget::eventFilter(obj, ev); +} + +void ModFolderPage::on_addModBtn_clicked() +{ + QStringList fileNames = QFileDialog::getOpenFileNames( + this, QApplication::translate("ModFolderPage", "Select Loader Mods")); + for (auto filename : fileNames) + { + m_mods->stopWatching(); + m_mods->installMod(QFileInfo(filename)); + m_mods->startWatching(); + } +} +void ModFolderPage::on_rmModBtn_clicked() +{ + int first, last; + auto list = ui->modTreeView->selectionModel()->selectedRows(); + + if (!lastfirst(list, first, last)) + return; + m_mods->stopWatching(); + m_mods->deleteMods(first, last); + m_mods->startWatching(); +} + +void ModFolderPage::on_viewModBtn_clicked() +{ + openDirInDefaultProgram(m_mods->dir().absolutePath(), true); +} + +void ModFolderPage::modCurrent(const QModelIndex ¤t, const QModelIndex &previous) +{ + if (!current.isValid()) + { + ui->frame->clear(); + return; + } + int row = current.row(); + Mod &m = m_mods->operator[](row); + ui->frame->updateWithMod(m); +} diff --git a/gui/pages/ModFolderPage.h b/gui/pages/ModFolderPage.h new file mode 100644 index 00000000..c193f4c1 --- /dev/null +++ b/gui/pages/ModFolderPage.h @@ -0,0 +1,63 @@ +/* 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 <QWidget> + +#include <logic/OneSixInstance.h> +#include <logic/net/NetJob.h> +#include "BasePage.h" + +class EnabledItemFilter; +class ModList; +namespace Ui +{ +class ModFolderPage; +} + +class ModFolderPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit ModFolderPage(BaseInstance * inst, std::shared_ptr<ModList> mods, QString id, QString iconName, + QString displayName, QString helpPage = "" , QWidget *parent = 0); + virtual ~ModFolderPage(); + virtual QString displayName() override; + virtual QIcon icon() override; + virtual QString id() override; + virtual QString helpPage() override { return m_helpName; }; + virtual bool shouldDisplay(); +protected: + bool eventFilter(QObject *obj, QEvent *ev); + bool modListFilter(QKeyEvent *ev); +protected: + BaseInstance * m_inst; +private: + Ui::ModFolderPage *ui; + std::shared_ptr<ModList> m_mods; + QString m_iconName; + QString m_id; + QString m_displayName; + QString m_helpName; + +public slots: + void modCurrent(const QModelIndex ¤t, const QModelIndex &previous); + +private slots: + void on_addModBtn_clicked(); + void on_rmModBtn_clicked(); + void on_viewModBtn_clicked(); +}; diff --git a/gui/pages/ModFolderPage.ui b/gui/pages/ModFolderPage.ui new file mode 100644 index 00000000..eb29a2c0 --- /dev/null +++ b/gui/pages/ModFolderPage.ui @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ModFolderPage</class> + <widget class="QWidget" name="ModFolderPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>723</width> + <height>532</height> + </rect> + </property> + <property name="windowTitle"> + <string>Mods</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="ModListView" name="modTreeView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::DropOnly</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPushButton" name="addModBtn"> + <property name="text"> + <string>&Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="rmModBtn"> + <property name="text"> + <string>&Remove</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="viewModBtn"> + <property name="text"> + <string>&View Folder</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="MCModInfoFrame" name="frame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ModListView</class> + <extends>QTreeView</extends> + <header>gui/widgets/ModListView.h</header> + </customwidget> + <customwidget> + <class>MCModInfoFrame</class> + <extends>QFrame</extends> + <header>gui/widgets/MCModInfoFrame.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/gui/pages/NotesPage.cpp b/gui/pages/NotesPage.cpp new file mode 100644 index 00000000..b4746a77 --- /dev/null +++ b/gui/pages/NotesPage.cpp @@ -0,0 +1,35 @@ +#include "NotesPage.h" +#include "ui_NotesPage.h" + +QString NotesPage::displayName() +{ + return tr("Notes"); +} + +QIcon NotesPage::icon() +{ + return QIcon::fromTheme("news"); +} + +QString NotesPage::id() +{ + return "notes"; +} + +NotesPage::NotesPage(BaseInstance *inst, QWidget *parent) + : QWidget(parent), ui(new Ui::NotesPage), m_inst(inst) +{ + ui->setupUi(this); + ui->noteEditor->setText(m_inst->notes()); +} + +NotesPage::~NotesPage() +{ + delete ui; +} + +bool NotesPage::apply() +{ + m_inst->setNotes(ui->noteEditor->toPlainText()); + return true; +} diff --git a/gui/pages/NotesPage.h b/gui/pages/NotesPage.h new file mode 100644 index 00000000..fe916f21 --- /dev/null +++ b/gui/pages/NotesPage.h @@ -0,0 +1,45 @@ +/* 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 <QWidget> + +#include <logic/BaseInstance.h> +#include <logic/net/NetJob.h> +#include "BasePage.h" + +class EnabledItemFilter; +namespace Ui +{ +class NotesPage; +} + +class NotesPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit NotesPage(BaseInstance *inst, QWidget *parent = 0); + virtual ~NotesPage(); + virtual QString displayName() override; + virtual QIcon icon() override; + virtual QString id() override; + virtual bool apply(); + virtual QString helpPage() override { return "Notes"; }; + +private: + Ui::NotesPage *ui; + BaseInstance *m_inst; +}; diff --git a/gui/pages/NotesPage.ui b/gui/pages/NotesPage.ui new file mode 100644 index 00000000..ab33ffd3 --- /dev/null +++ b/gui/pages/NotesPage.ui @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>NotesPage</class> + <widget class="QWidget" name="NotesPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QTextEdit" name="noteEditor"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> + </property> + <property name="acceptRichText"> + <bool>false</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/gui/pages/ResourcePackPage.h b/gui/pages/ResourcePackPage.h new file mode 100644 index 00000000..06367905 --- /dev/null +++ b/gui/pages/ResourcePackPage.h @@ -0,0 +1,19 @@ +#pragma once +#include "ModFolderPage.h" + +class ResourcePackPage : public ModFolderPage +{ +public: + explicit ResourcePackPage(BaseInstance *instance, QWidget *parent = 0) + : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks", "resourcepacks", + tr("Resource packs"), "Resource-packs", parent) + { + } + + virtual ~ResourcePackPage() {}; + virtual bool shouldDisplay() override + { + return !m_inst->traits().contains("no-texturepacks") && + !m_inst->traits().contains("texturepacks"); + } +}; diff --git a/gui/pages/ScreenshotsPage.cpp b/gui/pages/ScreenshotsPage.cpp new file mode 100644 index 00000000..051bc12d --- /dev/null +++ b/gui/pages/ScreenshotsPage.cpp @@ -0,0 +1,270 @@ +#include "ScreenshotsPage.h" +#include "ui_ScreenshotsPage.h" + +#include <QModelIndex> +#include <QMutableListIterator> +#include <QFileIconProvider> +#include <QFileSystemModel> +#include <QStyledItemDelegate> +#include <QLineEdit> +#include <QtGui/qevent.h> + +#include <pathutils.h> + +#include "gui/dialogs/ProgressDialog.h" +#include "gui/dialogs/CustomMessageBox.h" +#include "logic/net/NetJob.h" +#include "logic/screenshots/ImgurUpload.h" +#include "logic/screenshots/ImgurAlbumCreation.h" +#include "logic/tasks/SequentialTask.h" + +class FilterModel : public QIdentityProxyModel +{ +public: + virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const + { + auto model = sourceModel(); + if (!model) + return QVariant(); + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); + return result.toString().remove(QRegExp("\\.png$")); + } + if (role == Qt::DecorationRole) + { + QVariant result = sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); + QString filePath = result.toString(); + if(thumbnailCache.contains(filePath)) + { + return thumbnailCache[filePath]; + } + bool failed = false; + QFileInfo info(filePath); + failed |= info.isDir(); + failed |= (info.suffix().compare("png", Qt::CaseInsensitive) != 0); + // WARNING: really an IF! this is purely for using break instead of goto... + while(!failed) + { + QImage image(info.absoluteFilePath()); + if (image.isNull()) + { + // TODO: schedule a retry. + failed = true; + break; + } + QImage thumbnail = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); + QIcon icon(QPixmap::fromImage(thumbnail)); + // the casts are a hack for the stupid method being const. + ((QMap<QString, QIcon> &)thumbnailCache).insert(filePath, icon); + return icon; + } + // we failed anyway... + return sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FileIconRole); + } + else + { + QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); + return result; + } + } + virtual bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) + { + auto model = sourceModel(); + if (!model) + return false; + if (role != Qt::EditRole) + return false; + // FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't + // sort after renames + { + ((QFileSystemModel *)model)->setNameFilterDisables(true); + ((QFileSystemModel *)model)->setNameFilterDisables(false); + } + return model->setData(mapToSource(index), value.toString() + ".png", role); + } +private: + QMap<QString, QIcon> thumbnailCache; +}; + +class CenteredEditingDelegate : public QStyledItemDelegate +{ +public: + explicit CenteredEditingDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) + { + } + virtual ~CenteredEditingDelegate() + { + } + virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const + { + auto widget = QStyledItemDelegate::createEditor(parent, option, index); + auto foo = dynamic_cast<QLineEdit *>(widget); + if (foo) + { + foo->setAlignment(Qt::AlignHCenter); + foo->setFrame(true); + foo->setMaximumWidth(192); + } + return widget; + } +}; + +QString ScreenshotsPage::displayName() +{ + return tr("Screenshots"); +} + +QIcon ScreenshotsPage::icon() +{ + return QIcon::fromTheme("screenshots"); +} + +QString ScreenshotsPage::id() +{ + return "screenshots"; +} + +ScreenshotsPage::ScreenshotsPage(BaseInstance *instance, QWidget *parent) + : QWidget(parent), ui(new Ui::ScreenshotsPage) +{ + m_model.reset(new QFileSystemModel()); + m_filterModel.reset(new FilterModel()); + m_filterModel->setSourceModel(m_model.get()); + m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable); + m_model->setReadOnly(false); + m_folder = PathCombine(instance->minecraftRoot(), "screenshots"); + m_valid = ensureFolderPathExists(m_folder); + + ui->setupUi(this); + ui->listView->setModel(m_filterModel.get()); + ui->listView->setIconSize(QSize(128, 128)); + ui->listView->setGridSize(QSize(192, 128)); + ui->listView->setSpacing(9); + // ui->listView->setUniformItemSizes(true); + ui->listView->setLayoutMode(QListView::Batched); + ui->listView->setViewMode(QListView::IconMode); + ui->listView->setResizeMode(QListView::Adjust); + ui->listView->installEventFilter(this); + ui->listView->setEditTriggers(0); + ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); + connect(ui->listView, SIGNAL(activated(QModelIndex)), SLOT(onItemActivated(QModelIndex))); +} + +bool ScreenshotsPage::eventFilter(QObject *obj, QEvent *evt) +{ + if (obj != ui->listView) + return QWidget::eventFilter(obj, evt); + if (evt->type() != QEvent::KeyPress) + { + return QWidget::eventFilter(obj, evt); + } + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(evt); + switch (keyEvent->key()) + { + case Qt::Key_Delete: + on_deleteBtn_clicked(); + return true; + case Qt::Key_F2: + on_renameBtn_clicked(); + return true; + default: + break; + } + return QWidget::eventFilter(obj, evt); +} + +ScreenshotsPage::~ScreenshotsPage() +{ + delete ui; +} + +void ScreenshotsPage::onItemActivated(QModelIndex index) +{ + if (!index.isValid()) + return; + auto info = m_model->fileInfo(index); + QString fileName = info.absoluteFilePath(); + openFileInDefaultProgram(info.absoluteFilePath()); +} + +void ScreenshotsPage::on_viewFolderBtn_clicked() +{ + openDirInDefaultProgram(m_folder, true); +} + +void ScreenshotsPage::on_uploadBtn_clicked() +{ + auto selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.isEmpty()) + return; + + QList<ScreenshotPtr> uploaded; + auto job = std::make_shared<NetJob>("Screenshot Upload"); + for (auto item : selection) + { + auto info = m_model->fileInfo(item); + auto screenshot = std::make_shared<ScreenShot>(info); + uploaded.push_back(screenshot); + job->addNetAction(ImgurUpload::make(screenshot)); + } + SequentialTask task; + auto albumTask = std::make_shared<NetJob>("Imgur Album Creation"); + auto imgurAlbum = ImgurAlbumCreation::make(uploaded); + albumTask->addNetAction(imgurAlbum); + task.addTask(job); + task.addTask(albumTask); + ProgressDialog prog(this); + if (prog.exec(&task) != QDialog::Accepted) + { + CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), + tr("Unknown error"), QMessageBox::Warning)->exec(); + } + else + { + CustomMessageBox::selectable( + this, tr("Upload finished"), + tr("<a href=\"https://imgur.com/a/%1\">Visit album</a><br/>Delete hash: %2 (save " + "this if you want to be able to edit/delete the album)") + .arg(imgurAlbum->id(), imgurAlbum->deleteHash()), + QMessageBox::Information)->exec(); + } +} + +void ScreenshotsPage::on_deleteBtn_clicked() +{ + auto mbox = CustomMessageBox::selectable( + this, tr("Are you sure?"), tr("This will delete all selected screenshots."), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No); + std::unique_ptr<QMessageBox> box(mbox); + + if (box->exec() != QMessageBox::Yes) + return; + + auto selected = ui->listView->selectionModel()->selectedIndexes(); + for (auto item : selected) + { + m_model->remove(item); + } +} + +void ScreenshotsPage::on_renameBtn_clicked() +{ + auto selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.isEmpty()) + return; + ui->listView->edit(selection[0]); + // TODO: mass renaming +} + +void ScreenshotsPage::opened() +{ + if (m_valid) + { + QString path = QDir(m_folder).absolutePath(); + m_model->setRootPath(path); + ui->listView->setRootIndex(m_filterModel->mapFromSource(m_model->index(path))); + } +} diff --git a/gui/pages/ScreenshotsPage.h b/gui/pages/ScreenshotsPage.h new file mode 100644 index 00000000..4098e5e4 --- /dev/null +++ b/gui/pages/ScreenshotsPage.h @@ -0,0 +1,68 @@ +/* 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 <QWidget> +#include <logic/OneSixInstance.h> +#include "BasePage.h" +#include <QIcon> +#include <QEvent> + +class QFileSystemModel; +class QIdentityProxyModel; +namespace Ui +{ +class ScreenshotsPage; +} + +class ScreenShot; +class ScreenshotList; +class ImgurAlbumCreation; + +class ScreenshotsPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit ScreenshotsPage(BaseInstance *instance, QWidget *parent = 0); + virtual ~ScreenshotsPage(); + + virtual void opened() override; + + enum + { + NothingDone = 0x42 + }; + + virtual bool eventFilter(QObject *, QEvent *); + virtual QString displayName() override; + virtual QIcon icon() override; + virtual QString id() override; + virtual QString helpPage() override { return "Screenshots-management"; }; +private +slots: + void on_uploadBtn_clicked(); + void on_deleteBtn_clicked(); + void on_renameBtn_clicked(); + void on_viewFolderBtn_clicked(); + void onItemActivated(QModelIndex); + +private: + Ui::ScreenshotsPage *ui; + std::shared_ptr<QFileSystemModel> m_model; + std::shared_ptr<QIdentityProxyModel> m_filterModel; + QString m_folder; + bool m_valid = false; +}; diff --git a/gui/pages/ScreenshotsPage.ui b/gui/pages/ScreenshotsPage.ui new file mode 100644 index 00000000..5951ab02 --- /dev/null +++ b/gui/pages/ScreenshotsPage.ui @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ScreenshotsPage</class> + <widget class="QWidget" name="ScreenshotsPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>723</width> + <height>532</height> + </rect> + </property> + <property name="windowTitle"> + <string>Mods</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QListView" name="listView"> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectItems</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPushButton" name="uploadBtn"> + <property name="text"> + <string>&Upload</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="deleteBtn"> + <property name="text"> + <string>&Delete</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="renameBtn"> + <property name="text"> + <string>&Rename</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="viewFolderBtn"> + <property name="text"> + <string>&View Folder</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + <tabstops> + <tabstop>listView</tabstop> + <tabstop>uploadBtn</tabstop> + <tabstop>deleteBtn</tabstop> + <tabstop>renameBtn</tabstop> + <tabstop>viewFolderBtn</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/gui/pages/TexturePackPage.h b/gui/pages/TexturePackPage.h new file mode 100644 index 00000000..69a47204 --- /dev/null +++ b/gui/pages/TexturePackPage.h @@ -0,0 +1,17 @@ +#pragma once +#include "ModFolderPage.h" + +class TexturePackPage : public ModFolderPage +{ +public: + explicit TexturePackPage(BaseInstance *instance, QWidget *parent = 0) + : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks", + tr("Texture packs"), "Texture-packs", parent) + { + } + virtual ~TexturePackPage() {}; + virtual bool shouldDisplay() override + { + return m_inst->traits().contains("texturepacks"); + } +}; diff --git a/gui/pages/VersionPage.cpp b/gui/pages/VersionPage.cpp new file mode 100644 index 00000000..34599111 --- /dev/null +++ b/gui/pages/VersionPage.cpp @@ -0,0 +1,394 @@ +/* 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 "MultiMC.h" + +#include <pathutils.h> +#include <QFileDialog> +#include <QMessageBox> +#include <QDebug> +#include <QEvent> +#include <QKeyEvent> + +#include "VersionPage.h" +#include "ui_VersionPage.h" + +#include "gui/Platform.h" +#include "gui/dialogs/CustomMessageBox.h" +#include "gui/dialogs/VersionSelectDialog.h" +#include "gui/dialogs/ModEditDialogCommon.h" + +#include "gui/dialogs/ProgressDialog.h" + +#include "logic/ModList.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/EnabledItemFilter.h" +#include "logic/forge/ForgeVersionList.h" +#include "logic/forge/ForgeInstaller.h" +#include "logic/liteloader/LiteLoaderVersionList.h" +#include "logic/liteloader/LiteLoaderInstaller.h" +#include "logic/minecraft/VersionBuilder.h" +#include "logic/auth/MojangAccountList.h" + +#include <QAbstractItemModel> +#include <logic/Mod.h> +#include <logic/icons/IconList.h> + +#include <QMessageBox> +#include <QListView> +#include <QString> +#include <QUrl> + +QString VersionPage::displayName() +{ + return tr("Version"); +} + +QIcon VersionPage::icon() +{ + return MMC->icons()->getIcon(m_inst->iconKey()); +} + +QString VersionPage::id() +{ + return "version"; +} + +bool VersionPage::shouldDisplay() +{ + return !m_inst->isRunning(); +} + +VersionPage::VersionPage(OneSixInstance *inst, QWidget *parent) + : QWidget(parent), ui(new Ui::VersionPage), m_inst(inst) +{ + ui->setupUi(this); + // libraries! + + m_version = m_inst->getFullVersion(); + if (m_version) + { + main_model = new EnabledItemFilter(this); + main_model->setActive(true); + main_model->setSourceModel(m_version.get()); + ui->libraryTreeView->setModel(main_model); + ui->libraryTreeView->installEventFilter(this); + ui->libraryTreeView->setSelectionMode(QAbstractItemView::SingleSelection); + connect(ui->libraryTreeView->selectionModel(), &QItemSelectionModel::currentChanged, + this, &VersionPage::versionCurrent); + updateVersionControls(); + // select first item. + auto index = main_model->index(0,0); + if(index.isValid()) + ui->libraryTreeView->setCurrentIndex(index); + } + else + { + disableVersionControls(); + } + connect(m_inst, &OneSixInstance::versionReloaded, this, + &VersionPage::updateVersionControls); +} + +VersionPage::~VersionPage() +{ + delete ui; +} + +void VersionPage::updateVersionControls() +{ + ui->forgeBtn->setEnabled(true); + ui->liteloaderBtn->setEnabled(true); +} + +void VersionPage::disableVersionControls() +{ + ui->forgeBtn->setEnabled(false); + ui->liteloaderBtn->setEnabled(false); + ui->reloadLibrariesBtn->setEnabled(false); + ui->removeLibraryBtn->setEnabled(false); +} + +bool VersionPage::reloadInstanceVersion() +{ + try + { + m_inst->reloadVersion(); + return true; + } + catch (MMCError &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + return false; + } + catch (...) + { + QMessageBox::critical( + this, tr("Error"), + tr("Failed to load the version description file for reasons unknown.")); + return false; + } +} + +void VersionPage::on_reloadLibrariesBtn_clicked() +{ + reloadInstanceVersion(); +} + +void VersionPage::on_removeLibraryBtn_clicked() +{ + if (ui->libraryTreeView->currentIndex().isValid()) + { + // FIXME: use actual model, not reloading. + if (!m_version->remove(ui->libraryTreeView->currentIndex().row())) + { + QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file")); + } + } +} + +void VersionPage::on_jarmodBtn_clicked() +{ + QFileDialog w; + QSet<QString> locations; + QString modsFolder = MMC->settings()->get("CentralModsDir").toString(); + auto f = [&](QStandardPaths::StandardLocation l) + { + QString location = QStandardPaths::writableLocation(l); + QFileInfo finfo(location); + if (!finfo.exists()) + return; + locations.insert(location); + }; + f(QStandardPaths::DesktopLocation); + f(QStandardPaths::DocumentsLocation); + f(QStandardPaths::DownloadLocation); + f(QStandardPaths::HomeLocation); + QList<QUrl> urls; + for (auto location : locations) + { + urls.append(QUrl::fromLocalFile(location)); + } + urls.append(QUrl::fromLocalFile(modsFolder)); + + w.setFileMode(QFileDialog::ExistingFiles); + w.setAcceptMode(QFileDialog::AcceptOpen); + w.setNameFilter(tr("Minecraft jar mods (*.zip *.jar)")); + w.setDirectory(modsFolder); + w.setSidebarUrls(urls); + + if (w.exec()) + m_version->installJarMods(w.selectedFiles()); +} + +void VersionPage::on_resetLibraryOrderBtn_clicked() +{ + try + { + m_version->resetOrder(); + } + catch (MMCError &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + } +} + +void VersionPage::on_moveLibraryUpBtn_clicked() +{ + if (ui->libraryTreeView->selectionModel()->selectedRows().isEmpty()) + { + return; + } + try + { + const int row = ui->libraryTreeView->selectionModel()->selectedRows().first().row(); + const int newRow = 0; + m_version->move(row, InstanceVersion::MoveUp); + // ui->libraryTreeView->selectionModel()->setCurrentIndex(m_version->index(newRow), + // QItemSelectionModel::ClearAndSelect); + } + catch (MMCError &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + } +} + +void VersionPage::on_moveLibraryDownBtn_clicked() +{ + if (ui->libraryTreeView->selectionModel()->selectedRows().isEmpty()) + { + return; + } + try + { + const int row = ui->libraryTreeView->selectionModel()->selectedRows().first().row(); + const int newRow = 0; + m_version->move(row, InstanceVersion::MoveDown); + // ui->libraryTreeView->selectionModel()->setCurrentIndex(m_version->index(newRow), + // QItemSelectionModel::ClearAndSelect); + } + catch (MMCError &e) + { + QMessageBox::critical(this, tr("Error"), e.cause()); + } +} + +void VersionPage::on_changeMCVersionBtn_clicked() +{ + VersionSelectDialog vselect(m_inst->versionList().get(), tr("Change Minecraft version"), + this); + if (!vselect.exec() || !vselect.selectedVersion()) + return; + + if (!MMC->accounts()->anyAccountIsValid()) + { + CustomMessageBox::selectable( + this, tr("Error"), + tr("MultiMC cannot download Minecraft or update instances unless you have at least " + "one account added.\nPlease add your Mojang or Minecraft account."), + QMessageBox::Warning)->show(); + return; + } + + if (m_inst->versionIsCustom()) + { + auto result = CustomMessageBox::selectable( + this, tr("Are you sure?"), + tr("This will remove any library/version customization you did previously. " + "This includes things like Forge install and similar."), + QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Abort, + QMessageBox::Abort)->exec(); + + if (result != QMessageBox::Ok) + return; + m_version->revertToVanilla(); + reloadInstanceVersion(); + } + m_inst->setIntendedVersionId(vselect.selectedVersion()->descriptor()); + + auto updateTask = m_inst->doUpdate(); + if (!updateTask) + { + return; + } + ProgressDialog tDialog(this); + connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString))); + tDialog.exec(updateTask.get()); +} + +void VersionPage::on_forgeBtn_clicked() +{ + // FIXME: use actual model, not reloading. Move logic to model. + if (m_version->hasFtbPack()) + { + if (QMessageBox::question( + this, tr("Revert?"), + tr("This action will remove the FTB pack version patch. Continue?")) != + QMessageBox::Yes) + { + return; + } + m_version->removeFtbPack(); + reloadInstanceVersion(); + } + if (m_version->hasDeprecatedVersionFiles()) + { + if (QMessageBox::question(this, tr("Revert?"), + tr("This action will remove deprecated version files " + "(custom.json and version.json). Continue?")) != + QMessageBox::Yes) + { + return; + } + m_version->removeDeprecatedVersionFiles(); + reloadInstanceVersion(); + } + VersionSelectDialog vselect(MMC->forgelist().get(), tr("Select Forge version"), this); + 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()) + { + ProgressDialog dialog(this); + dialog.exec( + ForgeInstaller().createInstallTask(m_inst, vselect.selectedVersion(), this)); + } +} + +void VersionPage::on_liteloaderBtn_clicked() +{ + if (m_version->hasFtbPack()) + { + if (QMessageBox::question( + this, tr("Revert?"), + tr("This action will remove the FTB pack version patch. Continue?")) != + QMessageBox::Yes) + { + return; + } + m_version->removeFtbPack(); + reloadInstanceVersion(); + } + if (m_version->hasDeprecatedVersionFiles()) + { + if (QMessageBox::question(this, tr("Revert?"), + tr("This action will remove deprecated version files " + "(custom.json and version.json). Continue?")) != + QMessageBox::Yes) + { + return; + } + m_version->removeDeprecatedVersionFiles(); + reloadInstanceVersion(); + } + VersionSelectDialog vselect(MMC->liteloaderlist().get(), tr("Select LiteLoader version"), + this); + 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()) + { + ProgressDialog dialog(this); + dialog.exec( + LiteLoaderInstaller().createInstallTask(m_inst, vselect.selectedVersion(), this)); + } +} + +void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous) +{ + if (!current.isValid()) + { + ui->removeLibraryBtn->setDisabled(true); + ui->moveLibraryDownBtn->setDisabled(true); + ui->moveLibraryUpBtn->setDisabled(true); + } + else + { + bool enabled = m_version->canRemove(current.row()); + ui->removeLibraryBtn->setEnabled(enabled); + ui->moveLibraryDownBtn->setEnabled(enabled); + ui->moveLibraryUpBtn->setEnabled(enabled); + } + QString selectedId = m_version->versionFileId(current.row()); + if (selectedId == "net.minecraft" || selectedId == "org.multimc.custom.json" || + selectedId == "org.multimc.version.json") + { + ui->changeMCVersionBtn->setEnabled(true); + } + else + { + ui->changeMCVersionBtn->setEnabled(false); + } +} diff --git a/gui/dialogs/OneSixModEditDialog.h b/gui/pages/VersionPage.h index e106c6fe..dfbb6741 100644 --- a/gui/dialogs/OneSixModEditDialog.h +++ b/gui/pages/VersionPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013 MultiMC Contributors +/* 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. @@ -14,35 +14,34 @@ */ #pragma once -#include <QDialog> +#include <QWidget> #include <logic/OneSixInstance.h> +#include <logic/net/NetJob.h> +#include "BasePage.h" class EnabledItemFilter; namespace Ui { -class OneSixModEditDialog; +class VersionPage; } -class OneSixModEditDialog : public QDialog +class VersionPage : public QWidget, public BasePage { Q_OBJECT public: - explicit OneSixModEditDialog(OneSixInstance *inst, QWidget *parent = 0); - virtual ~OneSixModEditDialog(); - + explicit VersionPage(OneSixInstance *inst, QWidget *parent = 0); + virtual ~VersionPage(); + virtual QString displayName() override; + virtual QIcon icon() override; + virtual QString id() override; + virtual QString helpPage() override { return "Instance-version"; }; + virtual bool shouldDisplay(); private slots: - void on_addModBtn_clicked(); - void on_rmModBtn_clicked(); - void on_viewModBtn_clicked(); - void on_addResPackBtn_clicked(); - void on_rmResPackBtn_clicked(); - void on_viewResPackBtn_clicked(); - // Questionable: SettingsDialog doesn't need this for some reason? - void on_buttonBox_rejected(); + // version tab void on_forgeBtn_clicked(); void on_liteloaderBtn_clicked(); void on_reloadLibrariesBtn_clicked(); @@ -50,26 +49,24 @@ slots: void on_resetLibraryOrderBtn_clicked(); void on_moveLibraryUpBtn_clicked(); void on_moveLibraryDownBtn_clicked(); + void on_jarmodBtn_clicked(); + void updateVersionControls(); void disableVersionControls(); + void on_changeMCVersionBtn_clicked(); protected: - bool eventFilter(QObject *obj, QEvent *ev); - bool loaderListFilter(QKeyEvent *ev); - bool resourcePackListFilter(QKeyEvent *ev); /// FIXME: this shouldn't be necessary! bool reloadInstanceVersion(); private: - Ui::OneSixModEditDialog *ui; - std::shared_ptr<VersionFinal> m_version; - std::shared_ptr<ModList> m_mods; - std::shared_ptr<ModList> m_resourcepacks; + Ui::VersionPage *ui; + std::shared_ptr<InstanceVersion> m_version; EnabledItemFilter *main_model; OneSixInstance *m_inst; + NetJobPtr forgeJob; public slots: - void loaderCurrent(QModelIndex current, QModelIndex previous); void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous); }; diff --git a/gui/pages/VersionPage.ui b/gui/pages/VersionPage.ui new file mode 100644 index 00000000..f770df55 --- /dev/null +++ b/gui/pages/VersionPage.ui @@ -0,0 +1,197 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>VersionPage</class> + <widget class="QWidget" name="VersionPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>475</height> + </rect> + </property> + <property name="windowTitle"> + <string>Version</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout" name="verticalLayout_10"> + <item> + <widget class="ModListView" name="libraryTreeView"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="headerHidden"> + <bool>false</bool> + </property> + <attribute name="headerVisible"> + <bool>true</bool> + </attribute> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Selection</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="changeMCVersionBtn"> + <property name="text"> + <string>Change version</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveLibraryUpBtn"> + <property name="toolTip"> + <string>This isn't implemented yet.</string> + </property> + <property name="text"> + <string>Move up</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveLibraryDownBtn"> + <property name="toolTip"> + <string>This isn't implemented yet.</string> + </property> + <property name="text"> + <string>Move down</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeLibraryBtn"> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </item> + <item> + <widget class="LineSeparator" name="separator" native="true"/> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Install</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="forgeBtn"> + <property name="toolTip"> + <string>Replace any current custom version with Minecraft Forge</string> + </property> + <property name="text"> + <string>Install Forge</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="liteloaderBtn"> + <property name="text"> + <string>Install LiteLoader</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="jarmodBtn"> + <property name="text"> + <string>Add jar mod</string> + </property> + </widget> + </item> + <item> + <widget class="LineSeparator" name="widget" native="true"/> + </item> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>List</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="resetLibraryOrderBtn"> + <property name="toolTip"> + <string>This isn't implemented yet.</string> + </property> + <property name="text"> + <string>Reset order</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="reloadLibrariesBtn"> + <property name="text"> + <string>Reload</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_7"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ModListView</class> + <extends>QTreeView</extends> + <header>gui/widgets/ModListView.h</header> + </customwidget> + <customwidget> + <class>LineSeparator</class> + <extends>QWidget</extends> + <header>gui/widgets/LineSeparator.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/gui/widgets/IconLabel.cpp b/gui/widgets/IconLabel.cpp index 1bfe8dc9..773f0b99 100644 --- a/gui/widgets/IconLabel.cpp +++ b/gui/widgets/IconLabel.cpp @@ -7,7 +7,7 @@ #include <QRect> IconLabel::IconLabel(QWidget *parent, QIcon icon, QSize size) - : QWidget(parent), m_icon(icon), m_size(size) + : QWidget(parent), m_size(size), m_icon(icon) { setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); } diff --git a/gui/widgets/LineSeparator.h b/gui/widgets/LineSeparator.h index 376f2056..9546e747 100644 --- a/gui/widgets/LineSeparator.h +++ b/gui/widgets/LineSeparator.h @@ -9,10 +9,10 @@ class LineSeparator : public QWidget public: /// Create a line separator. orientation is the orientation of the line. - explicit LineSeparator(QWidget *parent, Qt::Orientation orientation = Qt::Vertical); + explicit LineSeparator(QWidget *parent, Qt::Orientation orientation = Qt::Horizontal); QSize sizeHint() const; void paintEvent(QPaintEvent *); void initStyleOption(QStyleOption *option) const; private: - Qt::Orientation m_orientation = Qt::Vertical; + Qt::Orientation m_orientation = Qt::Horizontal; }; diff --git a/gui/widgets/PageContainer.cpp b/gui/widgets/PageContainer.cpp new file mode 100644 index 00000000..237e7224 --- /dev/null +++ b/gui/widgets/PageContainer.cpp @@ -0,0 +1,196 @@ +/* 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 "PageContainer.h" +#include "gui/Platform.h" +#include <QStackedLayout> +#include <QPushButton> +#include <QSortFilterProxyModel> +#include <QUrl> +#include "MultiMC.h" +#include <QStyledItemDelegate> +#include <QListView> +#include <QLineEdit> +#include <QLabel> +#include <QDialogButtonBox> +#include <QGridLayout> +#include <QDesktopServices> +#include <settingsobject.h> + +#include "PageContainer_p.h" +#include <gui/widgets/IconLabel.h> + +class PageEntryFilterModel : public QSortFilterProxyModel +{ +public: + explicit PageEntryFilterModel(QObject *parent = 0) : QSortFilterProxyModel(parent) + { + } + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const + { + const QString pattern = filterRegExp().pattern(); + const auto model = static_cast<PageModel *>(sourceModel()); + const auto page = model->pages().at(sourceRow); + if(!page->shouldDisplay()) + return false; + // Regular contents check, then check page-filter. + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); + } +}; + +PageContainer::PageContainer(BasePageProviderPtr pageProvider, QString defaultId, QWidget *parent) : QWidget(parent) +{ + createUI(); + m_model = new PageModel(this); + m_proxyModel = new PageEntryFilterModel(this); + int firstIndex = -1; + int counter = 0; + auto pages = pageProvider->getPages(); + for(auto page: pages) + { + page->stackIndex = m_pageStack->addWidget(dynamic_cast<QWidget *>(page)); + page->listIndex = counter; + counter++; + if(firstIndex == -1) + { + firstIndex = page->stackIndex; + } + } + m_model->setPages(pages); + + m_proxyModel->setSourceModel(m_model); + m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + + m_pageList->setIconSize(QSize(pageIconSize, pageIconSize)); + m_pageList->setSelectionMode(QAbstractItemView::SingleSelection); + m_pageList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_pageList->setModel(m_proxyModel); + connect(m_pageList->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + this, SLOT(currentChanged(QModelIndex))); + m_pageStack->setStackingMode(QStackedLayout::StackOne); + m_pageList->setFocus(); + // now find what we want to have selected... + auto page = m_model->findPageEntryById(defaultId); + QModelIndex index; + if(page) + { + index = m_proxyModel->mapFromSource(m_model->index(page->listIndex)); + } + else + { + index = m_proxyModel->index(0,0); + } + if(index.isValid()) + m_pageList->setCurrentIndex(index); +} + +void PageContainer::createUI() +{ + m_pageStack = new QStackedLayout; + m_filter = new QLineEdit; + m_pageList = new PageView; + m_header = new QLabel(); + m_iconHeader = new IconLabel(this, QIcon(), QSize(24,24)); + + QFont headerLabelFont = m_header->font(); + headerLabelFont.setBold(true); + const int pointSize = headerLabelFont.pointSize(); + if (pointSize > 0) + headerLabelFont.setPointSize(pointSize + 2); + m_header->setFont(headerLabelFont); + + QHBoxLayout *headerHLayout = new QHBoxLayout; + const int leftMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); + headerHLayout->addSpacerItem( + new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + headerHLayout->addWidget(m_header); + headerHLayout->addSpacerItem( + new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + headerHLayout->addWidget(m_iconHeader); + + m_pageStack->setMargin(0); + m_pageStack->addWidget(new QWidget(this)); + + m_layout = new QGridLayout; + m_layout->addLayout(headerHLayout, 0, 1, 1, 1); + m_layout->addWidget(m_pageList, 0, 0, 2, 1); + m_layout->addLayout(m_pageStack, 1, 1, 1, 1); + m_layout->setColumnStretch(1, 4); + setLayout(m_layout); +} + +void PageContainer::addButtons(QWidget *buttons) +{ + m_layout->addWidget(buttons, 2, 0, 1, 2); +} + +void PageContainer::addButtons(QLayout *buttons) +{ + m_layout->addLayout(buttons, 2, 0, 1, 2); +} + + +void PageContainer::showPage(int row) +{ + if(row != -1) + { + m_currentPage = m_model->pages().at(row); + } + else + { + m_currentPage = nullptr; + } + if(m_currentPage) + { + m_pageStack->setCurrentIndex(m_currentPage->stackIndex); + m_header->setText(m_currentPage->displayName()); + m_iconHeader->setIcon(m_currentPage->icon()); + m_currentPage->opened(); + } + else + { + m_pageStack->setCurrentIndex(0); + m_header->setText(QString()); + m_iconHeader->setIcon(QIcon::fromTheme("bug")); + } +} + +void PageContainer::help() +{ + if(m_currentPage) + { + QString pageId = m_currentPage->helpPage(); + if(pageId.isEmpty()) + return; + QDesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/wiki/" + pageId)); + } +} + +void PageContainer::currentChanged(const QModelIndex ¤t) +{ + showPage(current.isValid() ? m_proxyModel->mapToSource(current).row() : -1); +} + +bool PageContainer::requestClose(QCloseEvent * event) +{ + for(auto page: m_model->pages()) + { + if(!page->apply()) + return false; + } + return true; +} diff --git a/gui/widgets/PageContainer.h b/gui/widgets/PageContainer.h new file mode 100644 index 00000000..c0f17e90 --- /dev/null +++ b/gui/widgets/PageContainer.h @@ -0,0 +1,61 @@ +/* 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 <QWidget> +#include <QModelIndex> +#include <gui/pages/BasePageProvider.h> + +class QLayout; +class IconLabel; +class QSortFilterProxyModel; +class PageModel; +class QLabel; +class QListView; +class QLineEdit; +class QStackedLayout; +class QGridLayout; + +class PageContainer : public QWidget +{ + Q_OBJECT +public: + explicit PageContainer(BasePageProviderPtr pageProvider, QString defaultId = QString(), + QWidget *parent = 0); + virtual ~PageContainer() {}; + + void addButtons(QWidget * buttons); + void addButtons(QLayout * buttons); + bool requestClose(QCloseEvent *event); + +private: + void createUI(); +private +slots: + void currentChanged(const QModelIndex ¤t); + void showPage(int row); + void help(); + +private: + BasePage * m_currentPage; + QSortFilterProxyModel *m_proxyModel; + PageModel *m_model; + QStackedLayout *m_pageStack; + QLineEdit *m_filter; + QListView *m_pageList; + QLabel *m_header; + IconLabel *m_iconHeader; + QGridLayout *m_layout; +}; diff --git a/gui/widgets/PageContainer_p.h b/gui/widgets/PageContainer_p.h new file mode 100644 index 00000000..f10e8f2c --- /dev/null +++ b/gui/widgets/PageContainer_p.h @@ -0,0 +1,106 @@ +#pragma once +#include <QListView> +#include <QStyledItemDelegate> +#include <QEvent> +#include <QScrollBar> + +class BasePage; +const int pageIconSize = 24; + +class PageViewDelegate : public QStyledItemDelegate +{ +public: + PageViewDelegate(QObject *parent) : QStyledItemDelegate(parent) + { + } + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const + { + QSize size = QStyledItemDelegate::sizeHint(option, index); + size.setHeight(qMax(size.height(), 32)); + return size; + } +}; + +class PageModel : public QAbstractListModel +{ +public: + PageModel(QObject *parent = 0) : QAbstractListModel(parent) + { + QPixmap empty(pageIconSize, pageIconSize); + empty.fill(Qt::transparent); + m_emptyIcon = QIcon(empty); + } + virtual ~PageModel() {}; + + int rowCount(const QModelIndex &parent = QModelIndex()) const + { + return parent.isValid() ? 0 : m_pages.size(); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const + { + switch (role) + { + case Qt::DisplayRole: + return m_pages.at(index.row())->displayName(); + case Qt::DecorationRole: + { + QIcon icon = m_pages.at(index.row())->icon(); + if (icon.isNull()) + icon = m_emptyIcon; + return icon; + } + } + return QVariant(); + } + + void setPages(const QList<BasePage *> &pages) + { + beginResetModel(); + m_pages = pages; + endResetModel(); + } + const QList<BasePage *> &pages() const + { + return m_pages; + } + + BasePage * findPageEntryById(QString id) + { + for(auto page: m_pages) + { + if (page->id() == id) + return page; + } + return nullptr; + } + + QList<BasePage *> m_pages; + QIcon m_emptyIcon; +}; + +class PageView : public QListView +{ +public: + PageView(QWidget *parent = 0) : QListView(parent) + { + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); + setItemDelegate(new PageViewDelegate(this)); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + + virtual QSize sizeHint() const + { + int width = sizeHintForColumn(0) + frameWidth() * 2 + 5; + if (verticalScrollBar()->isVisible()) + width += verticalScrollBar()->width(); + return QSize(width, 100); + } + + virtual bool eventFilter(QObject *obj, QEvent *event) + { + if (obj == verticalScrollBar() && + (event->type() == QEvent::Show || event->type() == QEvent::Hide)) + updateGeometry(); + return QListView::eventFilter(obj, event); + } +}; diff --git a/gui/widgets/ServerStatus.cpp b/gui/widgets/ServerStatus.cpp index e540a301..10ed79fb 100644 --- a/gui/widgets/ServerStatus.cpp +++ b/gui/widgets/ServerStatus.cpp @@ -59,7 +59,7 @@ void ServerStatus::reloadStatus() void ServerStatus::addLine() { - layout->addWidget(new LineSeparator(this)); + layout->addWidget(new LineSeparator(this, Qt::Vertical)); } void ServerStatus::addStatus(QString key, QString name) diff --git a/logic/BaseInstaller.cpp b/logic/BaseInstaller.cpp index 669fd0ac..5660eb07 100644 --- a/logic/BaseInstaller.cpp +++ b/logic/BaseInstaller.cpp @@ -13,15 +13,10 @@ * limitations under the License. */ -#include "BaseInstaller.h" - #include <QFile> -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "OneSixInstance.h" - -#include "cmdutils.h" +#include "logic/BaseInstaller.h" +#include "logic/OneSixInstance.h" BaseInstaller::BaseInstaller() { diff --git a/logic/BaseInstaller.h b/logic/BaseInstaller.h index d59833cc..9531fbff 100644 --- a/logic/BaseInstaller.h +++ b/logic/BaseInstaller.h @@ -29,7 +29,7 @@ class BaseInstaller { public: BaseInstaller(); - + virtual ~BaseInstaller(){}; bool isApplied(OneSixInstance *on); virtual bool add(OneSixInstance *to); diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index 5fa62593..15bf5ab6 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -27,7 +27,7 @@ #include "pathutils.h" #include <cmdutils.h> -#include "lists/MinecraftVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" #include "logic/icons/IconList.h" BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir, @@ -58,6 +58,8 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir, // Java Settings settings().registerSetting("OverrideJava", false); + settings().registerSetting("OverrideJavaLocation", false); + settings().registerSetting("OverrideJavaArgs", false); settings().registerOverride(globalSettings->getSetting("JavaPath")); settings().registerOverride(globalSettings->getSetting("JvmArgs")); @@ -104,6 +106,18 @@ QString BaseInstance::id() const return QFileInfo(instanceRoot()).fileName(); } +bool BaseInstance::isRunning() const +{ + I_D(BaseInstance); + return d->m_isRunning; +} + +void BaseInstance::setRunning(bool running) const +{ + I_D(BaseInstance); + d->m_isRunning = running; +} + QString BaseInstance::instanceType() const { I_D(BaseInstance); diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 16791592..5f5378e7 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -22,9 +22,10 @@ #include <settingsobject.h> #include "inifile.h" -#include "lists/BaseVersionList.h" +#include "logic/BaseVersionList.h" #include "logic/auth/MojangAccount.h" +class ModList; class QDialog; class QDir; class Task; @@ -64,6 +65,9 @@ public: /// be unique. virtual QString id() const; + virtual void setRunning(bool running) const; + virtual bool isRunning() const; + /// get the type of this instance QString instanceType() const; @@ -89,7 +93,7 @@ public: void setGroupInitial(QString val); void setGroupPost(QString val); - QStringList extraArguments() const; + virtual QStringList extraArguments() const; virtual QString intendedVersionId() const = 0; virtual bool setIntendedVersionId(QString version) = 0; @@ -110,6 +114,19 @@ public: virtual bool shouldUpdate() const = 0; virtual void setShouldUpdate(bool val) = 0; + ////// Mod Lists ////// + virtual std::shared_ptr<ModList> resourcePackList() + { + return nullptr; + } + virtual std::shared_ptr<ModList> texturePackList() + { + return nullptr; + } + + /// Traits. Normally inside the version, depends on instance implementation. + virtual QSet <QString> traits() = 0; + /// Get the curent base jar of this instance. By default, it's the /// versions/$version/$version.jar QString baseJar() const; @@ -169,12 +186,6 @@ public: /// 'prepareForLaunch' virtual void cleanupAfterRun() = 0; - /// create a mod edit dialog for the instance - virtual QDialog *createModEditDialog(QWidget *parent) = 0; - - /// is a particular action enabled with this instance selected? - virtual bool menuActionEnabled(QString action_name) const = 0; - virtual QString getStatusbarDescription() = 0; /// FIXME: this really should be elsewhere... diff --git a/logic/BaseInstance_p.h b/logic/BaseInstance_p.h index 999ff545..6498454f 100644 --- a/logic/BaseInstance_p.h +++ b/logic/BaseInstance_p.h @@ -32,4 +32,5 @@ public: QString m_group; std::shared_ptr<SettingsObject> m_settings; QSet<BaseInstance::InstanceFlag> m_flags; + bool m_isRunning = false; }; diff --git a/logic/BaseVersion.h b/logic/BaseVersion.h index 43f5942a..ed63f551 100644 --- a/logic/BaseVersion.h +++ b/logic/BaseVersion.h @@ -16,6 +16,8 @@ #pragma once #include <memory> +#include <QString> +#include <QMetaType> /*! * An abstract base class for versions. @@ -52,4 +54,4 @@ struct BaseVersion typedef std::shared_ptr<BaseVersion> BaseVersionPtr; -Q_DECLARE_METATYPE(BaseVersionPtr)
\ No newline at end of file +Q_DECLARE_METATYPE(BaseVersionPtr) diff --git a/logic/lists/BaseVersionList.cpp b/logic/BaseVersionList.cpp index 6e2c5282..b34750b5 100644 --- a/logic/lists/BaseVersionList.cpp +++ b/logic/BaseVersionList.cpp @@ -13,7 +13,7 @@ * limitations under the License. */ -#include "logic/lists/BaseVersionList.h" +#include "logic/BaseVersionList.h" #include "logic/BaseVersion.h" BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent) diff --git a/logic/lists/BaseVersionList.h b/logic/BaseVersionList.h index 21b44e8d..21b44e8d 100644 --- a/logic/lists/BaseVersionList.h +++ b/logic/BaseVersionList.h diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index 95fd855b..c0a392e0 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -13,28 +13,26 @@ * limitations under the License. */ -#include "InstanceFactory.h" - #include <QDir> #include <QFileInfo> - -#include "BaseInstance.h" -#include "LegacyInstance.h" -#include "LegacyFTBInstance.h" -#include "OneSixInstance.h" -#include "OneSixFTBInstance.h" -#include "NostalgiaInstance.h" -#include "OneSixInstance.h" -#include "BaseVersion.h" -#include "MinecraftVersion.h" - -#include "inifile.h" +#include <inifile.h> #include <inisettingsobject.h> #include <setting.h> -#include "pathutils.h" +#include <pathutils.h> #include "logger/QsLog.h" +#include "logic/InstanceFactory.h" + +#include "logic/BaseInstance.h" +#include "logic/LegacyInstance.h" +#include "logic/LegacyFTBInstance.h" +#include "logic/OneSixInstance.h" +#include "logic/OneSixFTBInstance.h" +#include "logic/OneSixInstance.h" +#include "logic/BaseVersion.h" +#include "logic/minecraft/MinecraftVersion.h" + InstanceFactory InstanceFactory::loader; InstanceFactory::InstanceFactory() : QObject(NULL) @@ -51,7 +49,7 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(InstancePtr &inst, QString inst_type = m_settings->get("InstanceType").toString(); // FIXME: replace with a map lookup, where instance classes register their types - if (inst_type == "OneSix") + if (inst_type == "OneSix" || inst_type == "Nostalgia") { inst.reset(new OneSixInstance(instDir, m_settings, this)); } @@ -59,10 +57,6 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(InstancePtr &inst, { inst.reset(new LegacyInstance(instDir, m_settings, this)); } - else if (inst_type == "Nostalgia") - { - inst.reset(new NostalgiaInstance(instDir, m_settings, this)); - } else if (inst_type == "LegacyFTB") { inst.reset(new LegacyFTBInstance(instDir, m_settings, this)); @@ -98,55 +92,26 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(InstancePtr &in if (type == NormalInst) { - switch (mcVer->type) - { - case MinecraftVersion::Legacy: - // TODO new instance type - m_settings->set("InstanceType", "Legacy"); - inst.reset(new LegacyInstance(instDir, m_settings, this)); - inst->setIntendedVersionId(version->descriptor()); - inst->setShouldUseCustomBaseJar(false); - break; - case MinecraftVersion::OneSix: - m_settings->set("InstanceType", "OneSix"); - inst.reset(new OneSixInstance(instDir, m_settings, this)); - inst->setIntendedVersionId(version->descriptor()); - inst->setShouldUseCustomBaseJar(false); - break; - case MinecraftVersion::Nostalgia: - m_settings->set("InstanceType", "Nostalgia"); - inst.reset(new NostalgiaInstance(instDir, m_settings, this)); - inst->setIntendedVersionId(version->descriptor()); - inst->setShouldUseCustomBaseJar(false); - break; - default: - { - delete m_settings; - return InstanceFactory::NoSuchVersion; - } - } + m_settings->set("InstanceType", "OneSix"); + inst.reset(new OneSixInstance(instDir, m_settings, this)); + inst->setIntendedVersionId(version->descriptor()); + inst->setShouldUseCustomBaseJar(false); } else if (type == FTBInstance) { - switch (mcVer->type) + if(mcVer->usesLegacyLauncher()) { - case MinecraftVersion::Legacy: m_settings->set("InstanceType", "LegacyFTB"); inst.reset(new LegacyFTBInstance(instDir, m_settings, this)); inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); - break; - case MinecraftVersion::OneSix: + } + else + { m_settings->set("InstanceType", "OneSixFTB"); inst.reset(new OneSixFTBInstance(instDir, m_settings, this)); inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); - break; - default: - { - delete m_settings; - return InstanceFactory::NoSuchVersion; - } } } else diff --git a/logic/InstanceLauncher.cpp b/logic/InstanceLauncher.cpp index c0079d80..9170c87f 100644 --- a/logic/InstanceLauncher.cpp +++ b/logic/InstanceLauncher.cpp @@ -22,7 +22,7 @@ #include "gui/dialogs/ProgressDialog.h" #include "logic/MinecraftProcess.h" -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" InstanceLauncher::InstanceLauncher(QString instId) : QObject(), instId(instId) { diff --git a/logic/lists/InstanceList.cpp b/logic/InstanceList.cpp index 1ff0d2ec..5a988fd3 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/InstanceList.cpp @@ -27,13 +27,13 @@ #include <pathutils.h> #include "MultiMC.h" -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" #include "logic/icons/IconList.h" -#include "logic/lists/MinecraftVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" #include "logger/QsLog.h" -#include <gui/groupview/GroupView.h> +#include "gui/groupview/GroupView.h" const static int GROUP_FILE_FORMAT_VERSION = 1; @@ -46,16 +46,6 @@ InstanceList::InstanceList(const QString &instDir, QObject *parent) { QDir::current().mkpath(m_instDir); } - - /* - * FIXME HACK: instances sometimes need to be created at launch. They need the versions for - * that. - * - * Remove this. it has no business of reloading the whole list. The instances which - * need it should track such events themselves and CHANGE THEIR DATA ONLY! - */ - connect(MMC->minecraftlist().get(), &MinecraftVersionList::modelReset, this, - &InstanceList::loadList); } InstanceList::~InstanceList() @@ -296,17 +286,16 @@ 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()) + if (!dataDir.exists()) { - QLOG_INFO() << "The FTB launcher directory specified does not exist. Please check your " - "settings."; + QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings"; return records; } - else if (!dataDir.exists()) + else if (!dir.exists()) { - QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings"; + QLOG_INFO() << "The FTB launcher data directory specified does not exist. Please check your settings"; return records; } dir.cd("ModPacks"); @@ -337,6 +326,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(); diff --git a/logic/lists/InstanceList.h b/logic/InstanceList.h index f0bbb7ec..f0bbb7ec 100644 --- a/logic/lists/InstanceList.h +++ b/logic/InstanceList.h diff --git a/logic/LegacyFTBInstance.cpp b/logic/LegacyFTBInstance.cpp index 3c3356c9..73a1f73d 100644 --- a/logic/LegacyFTBInstance.cpp +++ b/logic/LegacyFTBInstance.cpp @@ -14,11 +14,6 @@ QString LegacyFTBInstance::getStatusbarDescription() return "Legacy FTB: " + intendedVersionId(); } -bool LegacyFTBInstance::menuActionEnabled(QString action_name) const -{ - return false; -} - QString LegacyFTBInstance::id() const { return "FTB/" + BaseInstance::id(); diff --git a/logic/LegacyFTBInstance.h b/logic/LegacyFTBInstance.h index 70f60535..a2fe1ead 100644 --- a/logic/LegacyFTBInstance.h +++ b/logic/LegacyFTBInstance.h @@ -9,6 +9,5 @@ public: explicit LegacyFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent = 0); virtual QString getStatusbarDescription(); - virtual bool menuActionEnabled(QString action_name) const; virtual QString id() const; }; diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index 6648e059..5c15616a 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -28,8 +28,13 @@ #include "logic/MinecraftProcess.h" #include "logic/LegacyUpdate.h" #include "logic/icons/IconList.h" - -#include "gui/dialogs/LegacyModEditDialog.h" +#include "gui/pages/LegacyUpgradePage.h" +#include "gui/pages/ModFolderPage.h" +#include "gui/pages/LegacyJarModPage.h" +#include <gui/pages/TexturePackPage.h> +#include <gui/pages/InstanceSettingsPage.h> +#include <gui/pages/NotesPage.h> +#include <gui/pages/ScreenshotsPage.h> LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) @@ -42,6 +47,27 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings, settings->registerSetting("IntendedJarVersion", ""); } +QList<BasePage *> LegacyInstance::getPages() +{ + QList<BasePage *> values; + values.append(new LegacyUpgradePage(this)); + values.append(new LegacyJarModPage(this)); + values.append(new ModFolderPage(this, loaderModList(), "mods", "plugin-blue", tr("Loader mods"), + "Loader-mods")); + values.append(new ModFolderPage(this, coreModList(), "coremods", "plugin-green", tr("Core mods"), + "Core-mods")); + values.append(new TexturePackPage(this)); + values.append(new NotesPage(this)); + values.append(new ScreenshotsPage(this)); + values.append(new InstanceSettingsPage(this)); + return values; +} + +QString LegacyInstance::dialogTitle() +{ + return tr("Edit Instance (%1)").arg(name()); +} + std::shared_ptr<Task> LegacyInstance::doUpdate() { // make sure the jar mods list is initialized by asking for it. @@ -50,7 +76,7 @@ std::shared_ptr<Task> LegacyInstance::doUpdate() return std::shared_ptr<Task>(new LegacyUpdate(this, this)); } -bool LegacyInstance::prepareForLaunch(AuthSessionPtr account, QString & launchScript) +bool LegacyInstance::prepareForLaunch(AuthSessionPtr account, QString &launchScript) { QIcon icon = MMC->icons()->getIcon(iconKey()); auto pixmap = icon.pixmap(128, 128); @@ -136,11 +162,6 @@ std::shared_ptr<ModList> LegacyInstance::texturePackList() return d->texture_pack_list; } -QDialog *LegacyInstance::createModEditDialog(QWidget *parent) -{ - return new LegacyModEditDialog(this, parent); -} - QString LegacyInstance::jarModsDir() const { return PathCombine(instanceRoot(), "instMods"); @@ -263,27 +284,11 @@ QString LegacyInstance::defaultCustomBaseJar() const return PathCombine(binDir(), "mcbackup.jar"); } -bool LegacyInstance::menuActionEnabled(QString action_name) const -{ - if (flags().contains(VersionBrokenFlag)) - { - return false; - } - if (action_name == "actionChangeInstMCVersion") - { - return false; - } - return true; -} - QString LegacyInstance::getStatusbarDescription() { if (flags().contains(VersionBrokenFlag)) { - return "Legacy : " + intendedVersionId() + " (broken)"; + return tr("Legacy : %1 (broken)").arg(intendedVersionId()); } - if (shouldUpdate()) - return "Legacy : " + currentVersionId() + " -> " + intendedVersionId(); - else - return "Legacy : " + currentVersionId(); + return tr("Legacy : %1").arg(intendedVersionId()); } diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index aa80968e..76a8c24d 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -16,11 +16,12 @@ #pragma once #include "BaseInstance.h" +#include "gui/pages/BasePageProvider.h" class ModList; class Task; -class LegacyInstance : public BaseInstance +class LegacyInstance : public BaseInstance, public BasePageProvider { Q_OBJECT public: @@ -34,6 +35,10 @@ public: //! Path to the instance's modlist file. QString modListFile() const; + ////// Edit Instance Dialog stuff ////// + virtual QList<BasePage *> getPages(); + virtual QString dialogTitle(); + ////// Mod Lists ////// std::shared_ptr<ModList> jarModList(); std::shared_ptr<ModList> coreModList(); @@ -75,18 +80,21 @@ public: return false; } + virtual QSet<QString> traits() + { + return {"legacy-instance", "texturepacks"}; + }; + virtual bool shouldUpdate() const override; virtual void setShouldUpdate(bool val) override; virtual std::shared_ptr<Task> doUpdate() override; virtual bool prepareForLaunch(AuthSessionPtr account, QString & launchScript) override; virtual void cleanupAfterRun() override; - virtual QDialog *createModEditDialog(QWidget *parent) override; virtual QString defaultBaseJar() const override; virtual QString defaultCustomBaseJar() const override; - bool menuActionEnabled(QString action_name) const; virtual QString getStatusbarDescription() override; protected diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp index 15c99234..00ee795d 100644 --- a/logic/LegacyUpdate.cpp +++ b/logic/LegacyUpdate.cpp @@ -13,69 +13,27 @@ * limitations under the License. */ -#include "LegacyUpdate.h" -#include "lists/LwjglVersionList.h" -#include "lists/MinecraftVersionList.h" -#include "BaseInstance.h" -#include "LegacyInstance.h" -#include "MultiMC.h" -#include "ModList.h" +#include <QStringList> + #include <pathutils.h> #include <quazip.h> #include <quazipfile.h> #include <JlCompress.h> + +#include "logic/LegacyUpdate.h" +#include "logic/LwjglVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/BaseInstance.h" +#include "logic/LegacyInstance.h" +#include "MultiMC.h" +#include "logic/ModList.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() @@ -110,6 +68,7 @@ void LegacyUpdate::fmllibsStart() bool forge_present = false; QString version = inst->intendedVersionId(); + auto & fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; if (!fmlLibsMapping.contains(version)) { lwjglStart(); @@ -152,7 +111,7 @@ void LegacyUpdate::fmllibsStart() // now check the lib folder inside the instance for files. for (auto &lib : libList) { - QFileInfo libInfo(PathCombine(inst->libDir(), lib.name)); + QFileInfo libInfo(PathCombine(inst->libDir(), lib.filename)); if (libInfo.exists()) continue; fmlLibsToProcess.append(lib); @@ -171,9 +130,9 @@ void LegacyUpdate::fmllibsStart() 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; + auto entry = metacache->resolveEntry("fmllibs", lib.filename); + QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename + : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename; dljob->addNetAction(CacheDownload::make(QUrl(urlString), entry)); } @@ -196,16 +155,16 @@ void LegacyUpdate::fmllibsFinished() for (auto &lib : fmlLibsToProcess) { progress(index, fmlLibsToProcess.size()); - auto entry = metacache->resolveEntry("fmllibs", lib.name); - auto path = PathCombine(inst->libDir(), lib.name); + auto entry = metacache->resolveEntry("fmllibs", lib.filename); + auto path = PathCombine(inst->libDir(), lib.filename); if(!ensureFilePathExists(path)) { emitFailed(tr("Failed creating FML library folder inside the instance.")); return; } - if (!QFile::copy(entry->getFullPath(), PathCombine(inst->libDir(), lib.name))) + if (!QFile::copy(entry->getFullPath(), PathCombine(inst->libDir(), lib.filename))) { - emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.name)); + emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); return; } index++; @@ -265,8 +224,6 @@ void LegacyUpdate::lwjglStart() connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); connect(worker.get(), SIGNAL(finished(QNetworkReply *)), SLOT(lwjglFinished(QNetworkReply *))); - // connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), - // SLOT(downloadError(QNetworkReply::NetworkError))); } void LegacyUpdate::lwjglFinished(QNetworkReply *reply) diff --git a/logic/LegacyUpdate.h b/logic/LegacyUpdate.h index 5b073cb7..140ee1e3 100644 --- a/logic/LegacyUpdate.h +++ b/logic/LegacyUpdate.h @@ -21,19 +21,13 @@ #include "logic/net/NetJob.h" #include "logic/tasks/Task.h" +#include "logic/VersionFilterData.h" class MinecraftVersion; class BaseInstance; class QuaZip; class Mod; -struct FMLlib -{ - QString name; - QString checksum; - bool ours; -}; - class LegacyUpdate : public Task { Q_OBJECT @@ -84,5 +78,4 @@ private: NetJobPtr legacyDownloadJob; BaseInstance *m_inst = nullptr; QList<FMLlib> fmlLibsToProcess; - QMap<QString, QList<FMLlib>> fmlLibsMapping; }; diff --git a/logic/lists/LwjglVersionList.cpp b/logic/LwjglVersionList.cpp index df46d7be..df46d7be 100644 --- a/logic/lists/LwjglVersionList.cpp +++ b/logic/LwjglVersionList.cpp diff --git a/logic/lists/LwjglVersionList.h b/logic/LwjglVersionList.h index fa57e8eb..fa57e8eb 100644 --- a/logic/lists/LwjglVersionList.h +++ b/logic/LwjglVersionList.h diff --git a/logic/MMCJson.cpp b/logic/MMCJson.cpp index 65423436..8de88b6b 100644 --- a/logic/MMCJson.cpp +++ b/logic/MMCJson.cpp @@ -1,5 +1,6 @@ #include "MMCJson.h" #include <QString> +#include <QStringList> #include <math.h> bool MMCJson::ensureBoolean(const QJsonValue val, const QString what) @@ -11,7 +12,7 @@ bool MMCJson::ensureBoolean(const QJsonValue val, const QString what) QJsonValue MMCJson::ensureExists(QJsonValue val, const QString what) { - if(val.isNull()) + if(val.isUndefined() || val.isUndefined()) throw JSONValidationError(what + " does not exist"); return val; } @@ -59,3 +60,24 @@ QString MMCJson::ensureString(const QJsonValue val, const QString what) return val.toString(); } +void MMCJson::writeString(QJsonObject &to, QString key, QString value) +{ + if(value.size()) + { + to.insert(key, value); + } +} + +void MMCJson::writeStringList(QJsonObject &to, QString key, QStringList values) +{ + if(values.size()) + { + QJsonArray array; + for(auto value: values) + { + array.append(value); + } + to.insert(key, array); + } +} + diff --git a/logic/MMCJson.h b/logic/MMCJson.h index 71ded435..8408f29b 100644 --- a/logic/MMCJson.h +++ b/logic/MMCJson.h @@ -43,4 +43,23 @@ int ensureInteger(const QJsonValue val, QString what = "value"); /// make sure the value is converted into a double precision floating number. throw otherwise. double ensureDouble(const QJsonValue val, QString what = "value"); + +void writeString(QJsonObject & to, QString key, QString value); + +void writeStringList (QJsonObject & to, QString key, QStringList values); + +template <typename T> +void writeObjectList (QJsonObject & to, QString key, QList<T> values) +{ + if(values.size()) + { + QJsonArray array; + for(auto value: values) + { + array.append(value->toJson()); + } + to.insert(key, array); + } +} } + diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp index b268a4cc..a3ffedba 100644 --- a/logic/MinecraftProcess.cpp +++ b/logic/MinecraftProcess.cpp @@ -71,6 +71,9 @@ MinecraftProcess::MinecraftProcess(InstancePtr inst) : m_instance(inst) connect(&m_prepostlaunchprocess, &QProcess::readyReadStandardOutput, this, &MinecraftProcess::on_prepost_stdOut); } + + // a process has been constructed for the instance. It is running from MultiMC POV + m_instance->setRunning(true); } void MinecraftProcess::setWorkdir(QString path) @@ -254,6 +257,8 @@ void MinecraftProcess::finish(int code, ExitStatus status) // run post-exit postLaunch(); m_instance->cleanupAfterRun(); + // no longer running... + m_instance->setRunning(false); emit ended(m_instance, code, status); } @@ -304,6 +309,8 @@ bool MinecraftProcess::preLaunch() m_instance->cleanupAfterRun(); emit prelaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), m_prepostlaunchprocess.exitStatus()); + // not running, failed + m_instance->setRunning(false); return false; } else @@ -343,6 +350,8 @@ bool MinecraftProcess::postLaunch() MessageLevel::Error); emit postlaunch_failed(m_instance, m_prepostlaunchprocess.exitCode(), m_prepostlaunchprocess.exitStatus()); + // not running, failed + m_instance->setRunning(false); } else emit log(tr("Post-Launch command ran successfully.\n\n")); @@ -460,6 +469,8 @@ void MinecraftProcess::arm() emit log(tr("Could not launch minecraft!"), MessageLevel::Error); m_instance->cleanupAfterRun(); emit launch_failed(m_instance); + // not running, failed + m_instance->setRunning(false); return; } // send the launch script to the launcher part diff --git a/logic/MinecraftVersion.h b/logic/MinecraftVersion.h deleted file mode 100644 index 504381a8..00000000 --- a/logic/MinecraftVersion.h +++ /dev/null @@ -1,89 +0,0 @@ -/* Copyright 2013 Andrew Okin - * - * 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 "BaseVersion.h" -#include <QStringList> - -struct MinecraftVersion : public BaseVersion -{ - /*! - * Gets the version's timestamp. - * This is primarily used for sorting versions in a list. - */ - qint64 timestamp; - - /// The URL that this version will be downloaded from. maybe. - QString download_url; - - /// This version's type. Used internally to identify what kind of version this is. - enum VersionType - { - OneSix, - Legacy, - Nostalgia - } type; - - /// is this the latest version? - bool is_latest = false; - - /// is this a snapshot? - bool is_snapshot = false; - - QString m_name; - - QString m_descriptor; - - virtual QString descriptor() - { - return m_descriptor; - } - - virtual QString name() - { - return m_name; - } - - virtual QString typeString() const - { - QStringList pre_final; - if (is_latest == true) - { - pre_final.append("Latest"); - } - switch (type) - { - case OneSix: - pre_final.append("OneSix"); - break; - case Legacy: - pre_final.append("Legacy"); - break; - case Nostalgia: - pre_final.append("Nostalgia"); - break; - - default: - pre_final.append(QString("Type(%1)").arg(type)); - break; - } - if (is_snapshot == true) - { - pre_final.append("Snapshot"); - } - return pre_final.join(' '); - } -}; diff --git a/logic/ModList.cpp b/logic/ModList.cpp index 79b56986..f7770920 100644 --- a/logic/ModList.cpp +++ b/logic/ModList.cpp @@ -26,6 +26,7 @@ ModList::ModList(const QString &dir, const QString &list_file) : QAbstractListModel(), m_dir(dir), m_list_file(list_file) { + ensureFolderPathExists(m_dir.absolutePath()); m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); diff --git a/logic/NostalgiaInstance.cpp b/logic/NostalgiaInstance.cpp deleted file mode 100644 index 52820725..00000000 --- a/logic/NostalgiaInstance.cpp +++ /dev/null @@ -1,36 +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. - */ - -#include "NostalgiaInstance.h" - -NostalgiaInstance::NostalgiaInstance(const QString &rootDir, SettingsObject *settings, - QObject *parent) - : OneSixInstance(rootDir, settings, parent) -{ -} - -QString NostalgiaInstance::getStatusbarDescription() -{ - if (flags().contains(VersionBrokenFlag)) - { - return "Nostalgia : " + intendedVersionId() + " (broken)"; - } - return "Nostalgia : " + intendedVersionId(); -} - -bool NostalgiaInstance::menuActionEnabled(QString action_name) const -{ - return false; -} diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp index 172830bb..ef951987 100644 --- a/logic/OneSixFTBInstance.cpp +++ b/logic/OneSixFTBInstance.cpp @@ -1,12 +1,12 @@ #include "OneSixFTBInstance.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/minecraft/VersionBuilder.h" #include "tasks/SequentialTask.h" -#include "ForgeInstaller.h" -#include "lists/ForgeVersionList.h" +#include "forge/ForgeInstaller.h" +#include "forge/ForgeVersionList.h" #include "OneSixInstance_p.h" -#include "OneSixVersionBuilder.h" #include "MultiMC.h" #include "pathutils.h" @@ -100,6 +100,7 @@ QDir OneSixFTBInstance::librariesPath() const { return QDir(MMC->settings()->get("FTBRoot").toString() + "/libraries"); } + QDir OneSixFTBInstance::versionsPath() const { return QDir(MMC->settings()->get("FTBRoot").toString() + "/versions"); @@ -107,7 +108,6 @@ QDir OneSixFTBInstance::versionsPath() const QStringList OneSixFTBInstance::externalPatches() const { - I_D(OneSixInstance); return QStringList() << versionsPath().absoluteFilePath(intendedVersionId() + "/" + intendedVersionId() + ".json") << minecraftRoot() + "/pack.json"; } @@ -125,10 +125,6 @@ QString OneSixFTBInstance::getStatusbarDescription() } return "OneSix FTB: " + intendedVersionId(); } -bool OneSixFTBInstance::menuActionEnabled(QString action_name) const -{ - return false; -} std::shared_ptr<Task> OneSixFTBInstance::doUpdate() { diff --git a/logic/OneSixFTBInstance.h b/logic/OneSixFTBInstance.h index 440dc9f1..ecfa2231 100644 --- a/logic/OneSixFTBInstance.h +++ b/logic/OneSixFTBInstance.h @@ -16,7 +16,6 @@ public: void copy(const QDir &newDir) override; virtual QString getStatusbarDescription(); - virtual bool menuActionEnabled(QString action_name) const; virtual std::shared_ptr<Task> doUpdate() override; diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 6f3018cb..7bac3424 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -13,52 +13,84 @@ * limitations under the License. */ -#include "OneSixInstance.h" - #include <QIcon> - -#include "OneSixInstance_p.h" -#include "OneSixUpdate.h" -#include "VersionFinal.h" -#include "pathutils.h" +#include <pathutils.h> #include "logger/QsLog.h" -#include "assets/AssetsUtils.h" #include "MultiMC.h" -#include "icons/IconList.h" -#include "MinecraftProcess.h" -#include "gui/dialogs/OneSixModEditDialog.h" -#include <MMCError.h> +#include "MMCError.h" + +#include "logic/OneSixInstance.h" -OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) +#include "logic/OneSixInstance_p.h" +#include "logic/OneSixUpdate.h" +#include "logic/minecraft/InstanceVersion.h" +#include "minecraft/VersionBuildError.h" + +#include "logic/assets/AssetsUtils.h" +#include "icons/IconList.h" +#include "logic/MinecraftProcess.h" +#include "gui/pagedialog/PageDialog.h" +#include "gui/pages/VersionPage.h" +#include <gui/pages/ModFolderPage.h> +#include <gui/pages/ResourcePackPage.h> +#include <gui/pages/TexturePackPage.h> +#include <gui/pages/InstanceSettingsPage.h> +#include <gui/pages/NotesPage.h> +#include <gui/pages/ScreenshotsPage.h> + +OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent) : BaseInstance(new OneSixInstancePrivate(), rootDir, settings, parent) { I_D(OneSixInstance); d->m_settings->registerSetting("IntendedVersion", ""); - d->m_settings->registerSetting("ShouldUpdate", false); - d->version.reset(new VersionFinal(this, this)); - d->vanillaVersion.reset(new VersionFinal(this, this)); + d->version.reset(new InstanceVersion(this, this)); } void OneSixInstance::init() { - // FIXME: why is this decided here? what does this even mean? - if (QDir(instanceRoot()).exists("version.json")) + try { - try - { - reloadVersion(); - } - catch(MMCError & e) - { - // QLOG_ERROR() << "Caught exception on instance init: " << e.cause(); - } + reloadVersion(); } - else + catch (MMCError &e) { - clearVersion(); + QLOG_ERROR() << "Caught exception on instance init: " << e.cause(); } } +QList<BasePage *> OneSixInstance::getPages() +{ + QList<BasePage *> values; + values.append(new VersionPage(this)); + values.append(new ModFolderPage(this, loaderModList(), "mods", "plugin-blue", tr("Loader mods"), + "Loader-mods")); + values.append(new ModFolderPage(this, coreModList(), "coremods", "plugin-green", tr("Core mods"), + "Core-mods")); + values.append(new ResourcePackPage(this)); + values.append(new TexturePackPage(this)); + values.append(new NotesPage(this)); + values.append(new ScreenshotsPage(this)); + values.append(new InstanceSettingsPage(this)); + return values; +} + +QString OneSixInstance::dialogTitle() +{ + return tr("Edit Instance (%1)").arg(name()); +} + +QSet<QString> OneSixInstance::traits() +{ + auto version = getFullVersion(); + if (!version) + { + return {"version-incomplete"}; + } + else + return version->traits; +} + std::shared_ptr<Task> OneSixInstance::doUpdate() { return std::shared_ptr<Task>(new OneSixUpdate(this)); @@ -88,7 +120,7 @@ QString replaceTokensIn(QString text, QMap<QString, QString> with) return result; } -QDir OneSixInstance::reconstructAssets(std::shared_ptr<VersionFinal> version) +QDir OneSixInstance::reconstructAssets(std::shared_ptr<InstanceVersion> version) { QDir assetsDir = QDir("assets/"); QDir indexDir = QDir(PathCombine(assetsDir.path(), "indexes")); @@ -126,7 +158,7 @@ QDir OneSixInstance::reconstructAssets(std::shared_ptr<VersionFinal> version) QString original_path = PathCombine(PathCombine(objectDir.path(), tlk), asset_object.hash); QFile original(original_path); - if(!original.exists()) + if (!original.exists()) continue; if (!target.exists()) { @@ -138,7 +170,7 @@ QDir OneSixInstance::reconstructAssets(std::shared_ptr<VersionFinal> version) bool couldCopy = original.copy(target_path); QLOG_DEBUG() << " Copying" << original_path << "to" << target_path - << QString::number(couldCopy); // << original.errorString(); + << QString::number(couldCopy); // << original.errorString(); } } @@ -189,7 +221,7 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) return parts; } -bool OneSixInstance::prepareForLaunch(AuthSessionPtr account, QString &launchScript) +bool OneSixInstance::prepareForLaunch(AuthSessionPtr session, QString &launchScript) { I_D(OneSixInstance); @@ -200,44 +232,79 @@ bool OneSixInstance::prepareForLaunch(AuthSessionPtr account, QString &launchScr auto version = d->version; if (!version) return nullptr; + + // libraries and class path. { auto libs = version->getActiveNormalLibs(); for (auto lib : libs) { launchScript += "cp " + librariesPath().absoluteFilePath(lib->storagePath()) + "\n"; } - QString targetstr = version->id + "/" + version->id + ".jar"; - launchScript += "cp " + versionsPath().absoluteFilePath(targetstr) + "\n"; + QString minecraftjarpath; + if (version->hasJarMods()) + { + for (auto jarmod : version->jarMods) + { + launchScript += "cp " + jarmodsPath().absoluteFilePath(jarmod->name) + "\n"; + } + minecraftjarpath = version->id + "/" + version->id + "-stripped.jar"; + } + else + { + minecraftjarpath = version->id + "/" + version->id + ".jar"; + } + launchScript += "cp " + versionsPath().absoluteFilePath(minecraftjarpath) + "\n"; + } + if (!version->mainClass.isEmpty()) + { + launchScript += "mainClass " + version->mainClass + "\n"; + } + if (!version->appletClass.isEmpty()) + { + launchScript += "appletClass " + version->appletClass + "\n"; } - launchScript += "mainClass " + version->mainClass + "\n"; - for (auto param : processMinecraftArgs(account)) + // generic minecraft params + for (auto param : processMinecraftArgs(session)) { launchScript += "param " + param + "\n"; } - // Set the width and height for 1.6 instances - bool maximize = settings().get("LaunchMaximized").toBool(); - if (maximize) + // window size, title and state, legacy { - // this is probably a BAD idea - // launchScript += "param --fullscreen\n"; + QString windowParams; + if (settings().get("LaunchMaximized").toBool()) + windowParams = "max"; + else + windowParams = QString("%1x%2") + .arg(settings().get("MinecraftWinWidth").toInt()) + .arg(settings().get("MinecraftWinHeight").toInt()); + launchScript += "windowTitle " + windowTitle() + "\n"; + launchScript += "windowParams " + windowParams + "\n"; } - else + + // legacy auth + { + launchScript += "userName " + session->player_name + "\n"; + launchScript += "sessionId " + session->session + "\n"; + } + + // native libraries (mostly LWJGL) { - launchScript += - "param --width\nparam " + settings().get("MinecraftWinWidth").toString() + "\n"; - launchScript += - "param --height\nparam " + settings().get("MinecraftWinHeight").toString() + "\n"; + QDir natives_dir(PathCombine(instanceRoot(), "natives/")); + for (auto native : version->getActiveNativeLibs()) + { + QFileInfo finfo(PathCombine("libraries", native->storagePath())); + launchScript += "ext " + finfo.absoluteFilePath() + "\n"; + } + launchScript += "natives " + natives_dir.absolutePath() + "\n"; } - QDir natives_dir(PathCombine(instanceRoot(), "natives/")); - launchScript += "windowTitle " + windowTitle() + "\n"; - for(auto native: version->getActiveNativeLibs()) + + // traits. including legacyLaunch and others ;) + for (auto trait : version->traits) { - QFileInfo finfo(PathCombine("libraries", native->storagePath())); - launchScript += "ext " + finfo.absoluteFilePath() + "\n"; + launchScript += "traits " + trait + "\n"; } - launchScript += "natives " + natives_dir.absolutePath() + "\n"; launchScript += "launcher onesix\n"; return true; } @@ -260,6 +327,17 @@ std::shared_ptr<ModList> OneSixInstance::loaderModList() return d->loader_mod_list; } +std::shared_ptr<ModList> OneSixInstance::coreModList() +{ + I_D(OneSixInstance); + if (!d->core_mod_list) + { + d->core_mod_list.reset(new ModList(coreModsDir())); + } + d->core_mod_list->update(); + return d->core_mod_list; +} + std::shared_ptr<ModList> OneSixInstance::resourcePackList() { I_D(OneSixInstance); @@ -271,15 +349,20 @@ std::shared_ptr<ModList> OneSixInstance::resourcePackList() return d->resource_pack_list; } -QDialog *OneSixInstance::createModEditDialog(QWidget *parent) +std::shared_ptr<ModList> OneSixInstance::texturePackList() { - return new OneSixModEditDialog(this, parent); + I_D(OneSixInstance); + if (!d->texture_pack_list) + { + d->texture_pack_list.reset(new ModList(texturePacksDir())); + } + d->texture_pack_list->update(); + return d->texture_pack_list; } bool OneSixInstance::setIntendedVersionId(QString version) { settings().set("IntendedVersion", version); - setShouldUpdate(true); QFile::remove(PathCombine(instanceRoot(), "version.json")); clearVersion(); return true; @@ -290,34 +373,31 @@ QString OneSixInstance::intendedVersionId() const return settings().get("IntendedVersion").toString(); } -void OneSixInstance::setShouldUpdate(bool val) +void OneSixInstance::setShouldUpdate(bool) { - settings().set("ShouldUpdate", val); } bool OneSixInstance::shouldUpdate() const { - QVariant var = settings().get("ShouldUpdate"); - if (!var.isValid() || var.toBool() == false) - { - return intendedVersionId() != currentVersionId(); - } return true; } bool OneSixInstance::versionIsCustom() { - QDir patches(PathCombine(instanceRoot(), "patches/")); - return (patches.exists() && patches.count() >= 0) - || QFile::exists(PathCombine(instanceRoot(), "custom.json")) - || QFile::exists(PathCombine(instanceRoot(), "user.json")); + I_D(const OneSixInstance); + auto ver = d->version; + if (ver) + { + return !ver->isVanilla(); + } + return false; } bool OneSixInstance::versionIsFTBPack() { I_D(const OneSixInstance); auto ver = d->version; - if(ver) + if (ver) { return ver->hasFtbPack(); } @@ -335,17 +415,18 @@ void OneSixInstance::reloadVersion() try { - d->version->reload(false, externalPatches()); - d->vanillaVersion->reload(true, externalPatches()); + d->version->reload(externalPatches()); d->m_flags.remove(VersionBrokenFlag); emit versionReloaded(); } - catch(MMCError & error) + catch (VersionIncomplete &error) + { + } + catch (MMCError &error) { d->version->clear(); - d->vanillaVersion->clear(); d->m_flags.insert(VersionBrokenFlag); - //TODO: rethrow to show some error message(s)? + // TODO: rethrow to show some error message(s)? emit versionReloaded(); throw; } @@ -355,22 +436,15 @@ void OneSixInstance::clearVersion() { I_D(OneSixInstance); d->version->clear(); - d->vanillaVersion->clear(); emit versionReloaded(); } -std::shared_ptr<VersionFinal> OneSixInstance::getFullVersion() const +std::shared_ptr<InstanceVersion> OneSixInstance::getFullVersion() const { I_D(const OneSixInstance); return d->version; } -std::shared_ptr<VersionFinal> OneSixInstance::getVanillaVersion() const -{ - I_D(const OneSixInstance); - return d->vanillaVersion; -} - QString OneSixInstance::defaultBaseJar() const { return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; @@ -381,37 +455,38 @@ QString OneSixInstance::defaultCustomBaseJar() const return PathCombine(instanceRoot(), "custom.jar"); } -bool OneSixInstance::menuActionEnabled(QString action_name) const +QString OneSixInstance::getStatusbarDescription() { - if (flags().contains(VersionBrokenFlag)) + QStringList traits; + if (versionIsCustom()) { - return false; + traits.append(tr("custom")); } - if (action_name == "actionChangeInstLWJGLVersion") + if (flags().contains(VersionBrokenFlag)) { - return false; + traits.append(tr("broken")); } - return true; -} -QString OneSixInstance::getStatusbarDescription() -{ - QString descr = "OneSix : " + intendedVersionId(); - if (versionIsCustom()) + if (traits.size()) { - descr += " (custom)"; + return tr("Minecraft %1 (%2)").arg(intendedVersionId()).arg(traits.join(", ")); } - if (flags().contains(VersionBrokenFlag)) + else { - descr += " (broken)"; + return tr("Minecraft %1").arg(intendedVersionId()); } - return descr; } QDir OneSixInstance::librariesPath() const { return QDir::current().absoluteFilePath("libraries"); } + +QDir OneSixInstance::jarmodsPath() const +{ + return QDir(jarModsDir()); +} + QDir OneSixInstance::versionsPath() const { return QDir::current().absoluteFilePath("versions"); @@ -429,7 +504,7 @@ bool OneSixInstance::providesVersionFile() const bool OneSixInstance::reload() { - if(BaseInstance::reload()) + if (BaseInstance::reload()) { try { @@ -449,12 +524,46 @@ QString OneSixInstance::loaderModsDir() const return PathCombine(minecraftRoot(), "mods"); } +QString OneSixInstance::coreModsDir() const +{ + return PathCombine(minecraftRoot(), "coremods"); +} + QString OneSixInstance::resourcePacksDir() const { return PathCombine(minecraftRoot(), "resourcepacks"); } +QString OneSixInstance::texturePacksDir() const +{ + return PathCombine(minecraftRoot(), "texturepacks"); +} + QString OneSixInstance::instanceConfigFolder() const { return PathCombine(minecraftRoot(), "config"); } + +QString OneSixInstance::jarModsDir() const +{ + return PathCombine(instanceRoot(), "jarmods"); +} + +QString OneSixInstance::libDir() const +{ + return PathCombine(minecraftRoot(), "lib"); +} + +QStringList OneSixInstance::extraArguments() const +{ + auto list = BaseInstance::extraArguments(); + auto version = getFullVersion(); + if (!version) + return list; + if (version->hasJarMods()) + { + list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", + "-Dfml.ignorePatchDiscrepancies=true"}); + } + return list; +} diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index 13a392c2..75caef1f 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -17,10 +17,11 @@ #include "BaseInstance.h" -#include "VersionFinal.h" -#include "ModList.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/ModList.h" +#include "gui/pages/BasePageProvider.h" -class OneSixInstance : public BaseInstance +class OneSixInstance : public BaseInstance, public BasePageProvider { Q_OBJECT public: @@ -30,13 +31,25 @@ public: virtual void init() override; + ////// Edit Instance Dialog stuff ////// + virtual QList<BasePage *> getPages(); + virtual QString dialogTitle(); + ////// Mod Lists ////// std::shared_ptr<ModList> loaderModList(); - std::shared_ptr<ModList> resourcePackList(); - - ////// Directories ////// + std::shared_ptr<ModList> coreModList(); + std::shared_ptr<ModList> resourcePackList() override; + std::shared_ptr<ModList> texturePackList() override; + + virtual QSet<QString> traits(); + + ////// Directories and files ////// + QString jarModsDir() const; QString resourcePacksDir() const; + QString texturePacksDir() const; QString loaderModsDir() const; + QString coreModsDir() const; + QString libDir() const; virtual QString instanceConfigFolder() const override; virtual std::shared_ptr<Task> doUpdate() override; @@ -52,42 +65,43 @@ public: virtual bool shouldUpdate() const override; virtual void setShouldUpdate(bool val) override; - virtual QDialog *createModEditDialog(QWidget *parent) override; - /** - * reload the full version json files. return true on success! + * reload the full version json files. * * throws various exceptions :3 */ void reloadVersion(); + /// clears all version information in preparation for an update void clearVersion(); + /// get the current full version info - std::shared_ptr<VersionFinal> getFullVersion() const; - /// gets the current version info, but only for version.json - std::shared_ptr<VersionFinal> getVanillaVersion() const; + std::shared_ptr<InstanceVersion> getFullVersion() const; + /// is the current version original, or custom? virtual bool versionIsCustom() override; + /// does this instance have an FTB pack patch inside? bool versionIsFTBPack(); virtual QString defaultBaseJar() const override; virtual QString defaultCustomBaseJar() const override; - virtual bool menuActionEnabled(QString action_name) const override; virtual QString getStatusbarDescription() override; + virtual QDir jarmodsPath() const; virtual QDir librariesPath() const; virtual QDir versionsPath() const; virtual QStringList externalPatches() const; virtual bool providesVersionFile() const; bool reload() override; - + virtual QStringList extraArguments() const override; + signals: void versionReloaded(); private: QStringList processMinecraftArgs(AuthSessionPtr account); - QDir reconstructAssets(std::shared_ptr<VersionFinal> version); + QDir reconstructAssets(std::shared_ptr<InstanceVersion> version); }; diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h index c70de07c..3c4ef324 100644 --- a/logic/OneSixInstance_p.h +++ b/logic/OneSixInstance_p.h @@ -15,16 +15,19 @@ #pragma once -#include "BaseInstance_p.h" -#include "VersionFinal.h" -#include "ModList.h" +#include "logic/BaseInstance_p.h" + +class ModList; +class InstanceVersion; class OneSixInstancePrivate : public BaseInstancePrivate { public: virtual ~OneSixInstancePrivate() {}; - std::shared_ptr<VersionFinal> version; - std::shared_ptr<VersionFinal> vanillaVersion; + std::shared_ptr<InstanceVersion> version; + std::shared_ptr<ModList> jar_mod_list; std::shared_ptr<ModList> loader_mod_list; + std::shared_ptr<ModList> core_mod_list; std::shared_ptr<ModList> resource_pack_list; + std::shared_ptr<ModList> texture_pack_list; }; diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index d083c2ba..abc5f9c1 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -22,28 +22,24 @@ #include <QFileInfo> #include <QTextStream> #include <QDataStream> - -#include "BaseInstance.h" -#include "lists/MinecraftVersionList.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "OneSixInstance.h" -#include "net/ForgeMirrors.h" -#include "net/URLConstants.h" -#include "assets/AssetsUtils.h" - -#include "pathutils.h" +#include <pathutils.h> #include <JlCompress.h> -OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) - : Task(parent), m_inst(inst) +#include "logic/BaseInstance.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/OneSixInstance.h" +#include "logic/forge/ForgeMirrors.h" +#include "logic/net/URLConstants.h" +#include "logic/assets/AssetsUtils.h" + +OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { } void OneSixUpdate::executeTask() { - QString intendedVersion = m_inst->intendedVersionId(); - // Make directories QDir mcDir(m_inst->minecraftRoot()); if (!mcDir.exists() && !mcDir.mkpath(".")) @@ -52,102 +48,44 @@ void OneSixUpdate::executeTask() return; } - if (m_inst->shouldUpdate()) + // Get a pointer to the version object that corresponds to the instance's version. + targetVersion = std::dynamic_pointer_cast<MinecraftVersion>( + MMC->minecraftlist()->findVersion(m_inst->intendedVersionId())); + if (targetVersion == nullptr) { - // Get a pointer to the version object that corresponds to the instance's version. - targetVersion = std::dynamic_pointer_cast<MinecraftVersion>( - MMC->minecraftlist()->findVersion(intendedVersion)); - if (targetVersion == nullptr) - { - // don't do anything if it was invalid - emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); - return; - } - versionFileStart(); + // don't do anything if it was invalid + emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); + return; } - else + if (m_inst->providesVersionFile() || !targetVersion->needsUpdate()) { jarlibStart(); + return; } -} - -void OneSixUpdate::versionFileStart() -{ - if (m_inst->providesVersionFile()) + versionUpdateTask = MMC->minecraftlist()->createUpdateTask(m_inst->intendedVersionId()); + if (!versionUpdateTask) { jarlibStart(); return; } - QLOG_INFO() << m_inst->name() << ": getting version file."; - setStatus(tr("Getting the version files from Mojang...")); - - QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + - targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; - auto job = new NetJob("Version index"); - job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); - specificVersionDownloadJob.reset(job); - connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(versionFileFinished())); - connect(specificVersionDownloadJob.get(), SIGNAL(failed()), SLOT(versionFileFailed())); - connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + connect(versionUpdateTask.get(), SIGNAL(succeeded()), SLOT(jarlibStart())); + connect(versionUpdateTask.get(), SIGNAL(failed(QString)), SLOT(versionUpdateFailed(QString))); + connect(versionUpdateTask.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); - specificVersionDownloadJob->start(); -} - -void OneSixUpdate::versionFileFinished() -{ - NetActionPtr DlJob = specificVersionDownloadJob->first(); - - QString version_id = targetVersion->descriptor(); - QString inst_dir = m_inst->instanceRoot(); - // save the version file in $instanceId/version.json - { - QString version1 = PathCombine(inst_dir, "/version.json"); - ensureFilePathExists(version1); - // FIXME: detect errors here, download to a temp file, swap - QSaveFile vfile1(version1); - if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) - { - emitFailed(tr("Can't open %1 for writing.").arg(version1)); - return; - } - auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data; - qint64 actual = 0; - if ((actual = vfile1.write(data)) != data.size()) - { - emitFailed(tr("Failed to write into %1. Written %2 out of %3.").arg(version1).arg(actual).arg(data.size())); - return; - } - if (!vfile1.commit()) - { - emitFailed(tr("Can't commit changes to %1").arg(version1)); - return; - } - } - - // the version is downloaded safely. update is 'done' at this point - m_inst->setShouldUpdate(false); - - // delete any custom version inside the instance (it's no longer relevant, we did an update) - QString custom = PathCombine(inst_dir, "/custom.json"); - QFile finfo(custom); - if (finfo.exists()) - { - finfo.remove(); - } - // NOTE: Version is reloaded in jarlibStart - jarlibStart(); + setStatus(tr("Getting the version files from Mojang...")); + versionUpdateTask->start(); } -void OneSixUpdate::versionFileFailed() +void OneSixUpdate::versionUpdateFailed(QString reason) { - emitFailed(tr("Failed to download the version description. Try again.")); + emitFailed(reason); } void OneSixUpdate::assetIndexStart() { setStatus(tr("Updating assets index...")); OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr<VersionFinal> version = inst->getFullVersion(); + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); QString assetName = version->assets; QUrl indexUrl = "http://" + URLConstants::AWS_DOWNLOAD_INDEXES + assetName + ".json"; QString localPath = assetName + ".json"; @@ -171,7 +109,7 @@ void OneSixUpdate::assetIndexFinished() AssetsIndex index; OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr<VersionFinal> version = inst->getFullVersion(); + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); QString assetName = version->assets; QString asset_fname = "assets/indexes/" + assetName + ".json"; @@ -235,19 +173,19 @@ void OneSixUpdate::jarlibStart() { inst->reloadVersion(); } - catch(MMCError & e) + catch (MMCError &e) { emitFailed(e.cause()); return; } - catch(...) + catch (...) { emitFailed(tr("Failed to load the version description file for reasons unknown.")); return; } // Build a list of URLs that will need to be downloaded. - std::shared_ptr<VersionFinal> version = inst->getFullVersion(); + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); // minecraft.jar for this version { QString version_id = version->id; @@ -259,7 +197,7 @@ void OneSixUpdate::jarlibStart() auto metacache = MMC->metacache(); auto entry = metacache->resolveEntry("versions", localPath); job->addNetAction(CacheDownload::make(QUrl(urlstr), entry)); - + jarHashOnEntry = entry->md5sum; jarlibDownloadJob.reset(job); } @@ -274,7 +212,7 @@ void OneSixUpdate::jarlibStart() { if (lib->hint() == "local") { - if(!lib->filesExist(m_inst->librariesPath())) + if (!lib->filesExist(m_inst->librariesPath())) brokenLocalLibs.append(lib); continue; } @@ -311,16 +249,19 @@ void OneSixUpdate::jarlibStart() f(raw_storage, raw_dl); } } - if(!brokenLocalLibs.empty()) + if (!brokenLocalLibs.empty()) { jarlibDownloadJob.reset(); QStringList failed; - for(auto brokenLib : brokenLocalLibs) + 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)); + 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 @@ -341,12 +282,222 @@ void OneSixUpdate::jarlibStart() void OneSixUpdate::jarlibFinished() { - assetIndexStart(); + OneSixInstance *inst = (OneSixInstance *)m_inst; + std::shared_ptr<InstanceVersion> version = inst->getFullVersion(); + + // create stripped jar, if needed + if (version->hasJarMods()) + { + // FIXME: good candidate for moving elsewhere (jar location resolving/version caching). + QString version_id = version->id; + QString localPath = version_id + "/" + version_id + ".jar"; + QString strippedPath = version_id + "/" + version_id + "-stripped.jar"; + auto metacache = MMC->metacache(); + auto entry = metacache->resolveEntry("versions", localPath); + auto entryStripped = metacache->resolveEntry("versions", strippedPath); + + QString fullJarPath = entry->getFullPath(); + QString fullStrippedJarPath = entryStripped->getFullPath(); + QFileInfo finfo(fullStrippedJarPath); + if (entry->md5sum != jarHashOnEntry || !finfo.exists()) + { + stripJar(fullJarPath, fullStrippedJarPath); + } + } + if (version->traits.contains("legacyFML")) + { + fmllibsStart(); + } + else + { + assetIndexStart(); + } } void OneSixUpdate::jarlibFailed() { QStringList failed = jarlibDownloadJob->getFailedFiles(); QString failed_all = failed.join("\n"); - emitFailed(tr("Failed to download the following files:\n%1\n\nPlease try again.").arg(failed_all)); + emitFailed( + tr("Failed to download the following files:\n%1\n\nPlease try again.").arg(failed_all)); +} + +void OneSixUpdate::stripJar(QString origPath, QString newPath) +{ + QFileInfo runnableJar(newPath); + if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) + { + emitFailed("Failed to delete old minecraft.jar"); + return; + } + + // TaskStep(); // STEP 1 + setStatus(tr("Creating stripped jar: Opening minecraft.jar ...")); + + QuaZip zipOut(runnableJar.filePath()); + if (!zipOut.open(QuaZip::mdCreate)) + { + QFile::remove(runnableJar.filePath()); + emitFailed("Failed to open the minecraft.jar for stripping"); + return; + } + // Modify the jar + setStatus(tr("Creating stripped jar: Adding files...")); + if (!MergeZipFiles(&zipOut, origPath)) + { + zipOut.close(); + QFile::remove(runnableJar.filePath()); + emitFailed("Failed to add " + origPath + " to the jar."); + return; + } +} + +bool OneSixUpdate::MergeZipFiles(QuaZip *into, QString from) +{ + setStatus(tr("Installing mods: Adding ") + from + " ..."); + + QuaZip modZip(from); + modZip.open(QuaZip::mdUnzip); + + QuaZipFile fileInsideMod(&modZip); + QuaZipFile zipOutFile(into); + for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) + { + QString filename = modZip.getCurrentFileName(); + if (filename.contains("META-INF")) + { + QLOG_INFO() << "Skipping META-INF " << filename << " from " << from; + continue; + } + QLOG_INFO() << "Adding file " << filename << " from " << from; + + if (!fileInsideMod.open(QIODevice::ReadOnly)) + { + QLOG_ERROR() << "Failed to open " << filename << " from " << from; + return false; + } + /* + QuaZipFileInfo old_info; + fileInsideMod.getFileInfo(&old_info); + */ + QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); + /* + info_out.externalAttr = old_info.externalAttr; + */ + if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) + { + QLOG_ERROR() << "Failed to open " << filename << " in the jar"; + fileInsideMod.close(); + return false; + } + if (!JlCompress::copyData(fileInsideMod, zipOutFile)) + { + zipOutFile.close(); + fileInsideMod.close(); + QLOG_ERROR() << "Failed to copy data of " << filename << " into the jar"; + return false; + } + zipOutFile.close(); + fileInsideMod.close(); + } + return true; +} + +void OneSixUpdate::fmllibsStart() +{ + // Get the mod list + OneSixInstance *inst = (OneSixInstance *)m_inst; + std::shared_ptr<InstanceVersion> fullversion = inst->getFullVersion(); + bool forge_present = false; + + QString version = inst->intendedVersionId(); + auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; + if (!fmlLibsMapping.contains(version)) + { + assetIndexStart(); + return; + } + + auto &libList = fmlLibsMapping[version]; + + // determine if we need some libs for FML or forge + setStatus(tr("Checking for FML libraries...")); + forge_present = (fullversion->versionPatch("net.minecraftforge") != nullptr); + // we don't... + if (!forge_present) + { + assetIndexStart(); + return; + } + + // now check the lib folder inside the instance for files. + for (auto &lib : libList) + { + QFileInfo libInfo(PathCombine(inst->libDir(), lib.filename)); + if (libInfo.exists()) + continue; + fmlLibsToProcess.append(lib); + } + + // if everything is in place, there's nothing to do here... + if (fmlLibsToProcess.isEmpty()) + { + assetIndexStart(); + 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.filename); + QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename + : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename; + 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 OneSixUpdate::fmllibsFinished() +{ + legacyDownloadJob.reset(); + if (!fmlLibsToProcess.isEmpty()) + { + setStatus(tr("Copying FML libraries into the instance...")); + OneSixInstance *inst = (OneSixInstance *)m_inst; + auto metacache = MMC->metacache(); + int index = 0; + for (auto &lib : fmlLibsToProcess) + { + progress(index, fmlLibsToProcess.size()); + auto entry = metacache->resolveEntry("fmllibs", lib.filename); + auto path = PathCombine(inst->libDir(), lib.filename); + if (!ensureFilePathExists(path)) + { + emitFailed(tr("Failed creating FML library folder inside the instance.")); + return; + } + if (!QFile::copy(entry->getFullPath(), PathCombine(inst->libDir(), lib.filename))) + { + emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); + return; + } + index++; + } + progress(index, fmlLibsToProcess.size()); + } + assetIndexStart(); +} + +void OneSixUpdate::fmllibsFailed() +{ + emitFailed("Game update failed: it was impossible to fetch the required FML libraries."); + return; } diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h index eac882b5..139143db 100644 --- a/logic/OneSixUpdate.h +++ b/logic/OneSixUpdate.h @@ -21,6 +21,8 @@ #include "logic/net/NetJob.h" #include "logic/tasks/Task.h" +#include "logic/VersionFilterData.h" +#include <quazip.h> class MinecraftVersion; class OneSixInstance; @@ -34,14 +36,16 @@ public: private slots: - void versionFileStart(); - void versionFileFinished(); - void versionFileFailed(); + void versionUpdateFailed(QString reason); void jarlibStart(); void jarlibFinished(); void jarlibFailed(); + void fmllibsStart(); + void fmllibsFinished(); + void fmllibsFailed(); + void assetIndexStart(); void assetIndexFinished(); void assetIndexFailed(); @@ -49,11 +53,18 @@ slots: void assetsFinished(); void assetsFailed(); + void stripJar(QString origPath, QString newPath); + bool MergeZipFiles(QuaZip *into, QString from); private: - NetJobPtr specificVersionDownloadJob; NetJobPtr jarlibDownloadJob; + NetJobPtr legacyDownloadJob; - // target version, determined during this task + /// target version, determined during this task std::shared_ptr<MinecraftVersion> targetVersion; + /// the task that is spawned for version updates + std::shared_ptr<Task> versionUpdateTask; + OneSixInstance *m_inst = nullptr; + QString jarHashOnEntry; + QList<FMLlib> fmlLibsToProcess; }; diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp deleted file mode 100644 index be3a7da4..00000000 --- a/logic/OneSixVersionBuilder.cpp +++ /dev/null @@ -1,252 +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. - */ - -#include "OneSixVersionBuilder.h" - -#include <QList> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonDocument> -#include <QFile> -#include <QFileInfo> -#include <QMessageBox> -#include <QObject> -#include <QDir> -#include <QDebug> - -#include "VersionFinal.h" -#include "OneSixInstance.h" -#include "OneSixRule.h" -#include "VersionFile.h" -#include "MMCJson.h" -#include "modutils.h" -#include "logger/QsLog.h" - -OneSixVersionBuilder::OneSixVersionBuilder() -{ -} - -void OneSixVersionBuilder::build(VersionFinal *version, OneSixInstance *instance, - const bool onlyVanilla, const QStringList &external) -{ - OneSixVersionBuilder builder; - builder.m_version = version; - builder.m_instance = instance; - builder.buildInternal(onlyVanilla, external); -} - -void OneSixVersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, - const QJsonObject &obj) -{ - OneSixVersionBuilder builder; - builder.m_version = version; - builder.m_instance = 0; - builder.readJsonAndApply(obj); -} - -void OneSixVersionBuilder::buildInternal(const bool onlyVanilla, const QStringList &external) -{ - m_version->versionFiles.clear(); - - QDir root(m_instance->instanceRoot()); - QDir patches(root.absoluteFilePath("patches/")); - - // if we do external files, do just those. - if (!external.isEmpty()) - { - int externalOrder = -1; - for (auto fileName : external) - { - QLOG_INFO() << "Reading" << fileName; - auto file = - parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json")); - file->name = QFileInfo(fileName).fileName(); - file->fileId = "org.multimc.external." + file->name; - file->order = (externalOrder += 1); - file->version = QString(); - file->mcVersion = QString(); - m_version->versionFiles.append(file); - } - } - // else, if there's custom json, we just do that. - else if (QFile::exists(root.absoluteFilePath("custom.json"))) - { - QLOG_INFO() << "Reading custom.json"; - auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("custom.json")), false); - file->name = "custom.json"; - file->filename = "custom.json"; - file->fileId = "org.multimc.custom.json"; - file->order = -1; - file->version = QString(); - m_version->versionFiles.append(file); - // QObject::tr("The version descriptors of this instance are not compatible with the - // current version of MultiMC")); - // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more info.") - } - // version.json -> patches/*.json -> user.json - else - do - { - // version.json - QLOG_INFO() << "Reading version.json"; - auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("version.json")), false); - file->name = "Minecraft"; - file->fileId = "org.multimc.version.json"; - file->order = -1; - file->version = m_instance->intendedVersionId(); - file->mcVersion = m_instance->intendedVersionId(); - m_version->versionFiles.append(file); - // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more - // info.").arg(root.absoluteFilePath("version.json"))); - - if (onlyVanilla) - break; - - // patches/ - // load all, put into map for ordering, apply in the right order - - QMap<int, QPair<QString, VersionFilePtr>> files; - for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) - { - QLOG_INFO() << "Reading" << info.fileName(); - auto file = parseJsonFile(info, true); - if (files.contains(file->order)) - { - throw VersionBuildError(QObject::tr("%1 has the same order as %2").arg( - file->fileId, files[file->order].second->fileId)); - } - files.insert(file->order, qMakePair(info.fileName(), file)); - } - for (auto order : files.keys()) - { - auto &filePair = files[order]; - m_version->versionFiles.append(filePair.second); - } - } while (0); - - // some final touches - m_version->finalize(); -} - - - -void OneSixVersionBuilder::readJsonAndApply(const QJsonObject &obj) -{ - m_version->clear(); - - auto file = VersionFile::fromJson(QJsonDocument(obj), QString(), false); - // QObject::tr("Error while reading. Please check MultiMC-0.log for more info.")); - - file->applyTo(m_version); - m_version->versionFiles.append(file); - // QObject::tr("Error while applying. Please check MultiMC-0.log for more info.")); - // QObject::tr("The version descriptors of this instance are not compatible with the current - // version of MultiMC")); -} - -VersionFilePtr OneSixVersionBuilder::parseJsonFile(const QFileInfo &fileInfo, - const bool requireOrder, bool isFTB) -{ - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") - .arg(fileInfo.fileName(), file.errorString())); - } - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - throw JSONValidationError(QObject::tr("Unable to process the version file %1: %2 at %3.") - .arg(fileInfo.fileName(), error.errorString()) - .arg(error.offset)); - } - return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); - // QObject::tr("Error while reading %1. Please check MultiMC-0.log for more - // info.").arg(file.fileName()); -} - -QMap<QString, int> OneSixVersionBuilder::readOverrideOrders(OneSixInstance *instance) -{ - QMap<QString, int> out; - - // make sure the order file exists - if (!QDir(instance->instanceRoot()).exists("order.json")) - return out; - - // and it can be opened - QFile orderFile(instance->instanceRoot() + "/order.json"); - if (!orderFile.open(QFile::ReadOnly)) - { - QLOG_ERROR() << "Couldn't open" << orderFile.fileName() - << " for reading:" << orderFile.errorString(); - QLOG_WARN() << "Ignoring overriden order"; - return out; - } - - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); - QLOG_WARN() << "Ignoring overriden order"; - return out; - } - - // and then read it and process it if all above is true. - try - { - auto obj = MMCJson::ensureObject(doc); - for (auto it = obj.begin(); it != obj.end(); ++it) - { - if (it.key().startsWith("org.multimc.")) - { - continue; - } - out.insert(it.key(), MMCJson::ensureInteger(it.value())); - } - } - catch (JSONValidationError &err) - { - QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; - QLOG_WARN() << "Ignoring overriden order"; - return out; - } - return out; -} - -bool OneSixVersionBuilder::writeOverrideOrders(const QMap<QString, int> &order, - OneSixInstance *instance) -{ - QJsonObject obj; - for (auto it = order.cbegin(); it != order.cend(); ++it) - { - if (it.key().startsWith("org.multimc.")) - { - continue; - } - obj.insert(it.key(), it.value()); - } - QFile orderFile(instance->instanceRoot() + "/order.json"); - if (!orderFile.open(QFile::WriteOnly)) - { - QLOG_ERROR() << "Couldn't open" << orderFile.fileName() - << "for writing:" << orderFile.errorString(); - return false; - } - orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); - return true; -} diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h deleted file mode 100644 index 7a799e5b..00000000 --- a/logic/OneSixVersionBuilder.h +++ /dev/null @@ -1,47 +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> -#include <QMap> -#include "VersionFile.h" - -class VersionFinal; -class OneSixInstance; -class QJsonObject; -class QFileInfo; - -class OneSixVersionBuilder -{ - OneSixVersionBuilder(); -public: - static void build(VersionFinal *version, OneSixInstance *instance, const bool onlyVanilla, - const QStringList &external); - static void readJsonAndApplyToVersion(VersionFinal *version, const QJsonObject &obj); - - static QMap<QString, int> readOverrideOrders(OneSixInstance *instance); - static bool writeOverrideOrders(const QMap<QString, int> &order, OneSixInstance *instance); - -private: - VersionFinal *m_version; - OneSixInstance *m_instance; - - void buildInternal(const bool onlyVanilla, const QStringList &external); - void readJsonAndApply(const QJsonObject &obj); - - VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, - bool isFTB = false); -}; diff --git a/logic/URNResolver.cpp b/logic/URNResolver.cpp new file mode 100644 index 00000000..b6bdcf41 --- /dev/null +++ b/logic/URNResolver.cpp @@ -0,0 +1,98 @@ +#include "URNResolver.h" +#include <logger/QsLog.h> +#include "MultiMC.h" +#include "logic/forge/ForgeVersionList.h" +#include "logic/forge/ForgeVersion.h" + +QString unescapeNSS(QString RawNSS) +{ + QString NSS; + NSS.reserve(RawNSS.size()); + enum + { + Normal, + FirstHex, + SecondHex + } ParseState = Normal; + + QByteArray translator(" "); + + for (auto ch : RawNSS) + { + if(ParseState == Normal) + { + if(ch == '%') + { + ParseState = FirstHex; + continue; + } + else + { + NSS.append(ch); + } + } + if(ParseState == FirstHex) + { + translator[0] = ch.toLower().unicode(); + ParseState = SecondHex; + } + else if(ParseState == SecondHex) + { + translator[1] = ch.toLower().unicode(); + auto result = QByteArray::fromHex(translator); + if (result[0] == '\0') + return NSS; + NSS.append(result); + ParseState = Normal; + } + } + return NSS; +} + +bool URNResolver::parse(const QString &URN, QString &NID, QString &NSS) +{ + QRegExp URNPattern( + "^urn:([a-z0-9][a-z0-9-]{0,31}):(([a-z0-9()+,\\-.:=@;$_!*']|%[0-9a-f]{2})+).*", + Qt::CaseInsensitive); + if (URNPattern.indexIn(URN) == -1) + return false; + auto captures = URNPattern.capturedTexts(); + QString RawNID = captures[1]; + QString RawNSS = captures[2]; + + NID = RawNID.toLower(); + NSS = unescapeNSS(RawNSS); + return true; +} + +URNResolver::URNResolver() +{ +} + +QVariant URNResolver::resolve(QString URN) +{ + QString NID, NSS; + parse(URN, NID, NSS); + + if(NID != "x-mmc") + return QVariant(); + auto parts = NSS.split(":"); + if(parts.size() < 1) + return QVariant(); + unsigned int version = parts[0].toUInt(); + switch(version) + { + case 1: + return resolveV1(parts.mid(1)); + default: + return QVariant(); + } +} + +/** + * TODO: implement. + */ +QVariant URNResolver::resolveV1(QStringList parts) +{ + return QVariant(); +} diff --git a/logic/URNResolver.h b/logic/URNResolver.h new file mode 100644 index 00000000..dfc7f5eb --- /dev/null +++ b/logic/URNResolver.h @@ -0,0 +1,18 @@ +#pragma once +#include <QString> +#include <QMap> +#include <memory> +#include <QVariant> + +class URNResolver; +typedef std::shared_ptr<URNResolver> URNResolverPtr; + +class URNResolver +{ +public: + URNResolver(); + QVariant resolve (QString URN); + static bool parse (const QString &URN, QString &NID, QString &NSS); +private: + QVariant resolveV1 (QStringList parts); +}; diff --git a/logic/VersionFile.h b/logic/VersionFile.h deleted file mode 100644 index 169a2066..00000000 --- a/logic/VersionFile.h +++ /dev/null @@ -1,127 +0,0 @@ -#pragma once - -#include <QString> -#include <QStringList> -#include <memory> -#include "logic/OpSys.h" -#include "logic/OneSixRule.h" -#include "MMCError.h" - -class VersionFinal; - -class VersionBuildError : public MMCError -{ -public: - VersionBuildError(QString cause) : MMCError(cause) {}; - virtual ~VersionBuildError() noexcept {} -}; - -/** - * the base version file was meant for a newer version of the vanilla launcher than we support - */ -class LauncherVersionError : public VersionBuildError -{ -public: - LauncherVersionError(int actual, int supported) - : VersionBuildError(QObject::tr( - "The base version file of this instance was meant for a newer (%1) " - "version of the vanilla launcher than this version of MultiMC supports (%2).") - .arg(actual) - .arg(supported)) {}; - virtual ~LauncherVersionError() noexcept {} -}; - -/** - * some patch was intended for a different version of minecraft - */ -class MinecraftVersionMismatch : public VersionBuildError -{ -public: - MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion) - : VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft " - "(%2) than that of the instance (%3).") - .arg(fileId) - .arg(mcVersion) - .arg(parentMcVersion)) {}; - virtual ~MinecraftVersionMismatch() noexcept {} -}; - -struct RawLibrary; -typedef std::shared_ptr<RawLibrary> RawLibraryPtr; -struct RawLibrary -{ - QString name; - QString url; - QString hint; - QString absoluteUrl; - bool applyExcludes = false; - QStringList excludes; - bool applyNatives = false; - QList<QPair<OpSys, QString>> natives; - bool applyRules = false; - QList<std::shared_ptr<Rule>> rules; - - // user for '+' libraries - enum InsertType - { - Apply, - Append, - Prepend, - Replace - }; - InsertType insertType = Append; - QString insertData; - enum DependType - { - Soft, - Hard - }; - DependType dependType = Soft; - - static RawLibraryPtr fromJson(const QJsonObject &libObj, const QString &filename); -}; - -struct VersionFile; -typedef std::shared_ptr<VersionFile> VersionFilePtr; -struct VersionFile -{ -public: /* methods */ - static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename, - const bool requireOrder, const bool isFTB = false); - - static OneSixLibraryPtr createLibrary(RawLibraryPtr lib); - int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle); - void applyTo(VersionFinal *version); - -public: /* data */ - int order = 0; - QString name; - QString fileId; - QString version; - // TODO use the mcVersion to determine if a version file should be removed on update - QString mcVersion; - QString filename; - // TODO requirements - // QMap<QString, QString> requirements; - QString id; - QString mainClass; - QString overwriteMinecraftArguments; - QString addMinecraftArguments; - QString removeMinecraftArguments; - QString processArguments; - QString type; - QString releaseTime; - QString time; - QString assets; - int minimumLauncherVersion = -1; - - bool shouldOverwriteTweakers = false; - QStringList overwriteTweakers; - QStringList addTweakers; - QStringList removeTweakers; - - bool shouldOverwriteLibs = false; - QList<RawLibraryPtr> overwriteLibs; - QList<RawLibraryPtr> addLibs; - QList<QString> removeLibs; -}; diff --git a/logic/VersionFilterData.cpp b/logic/VersionFilterData.cpp new file mode 100644 index 00000000..8b521266 --- /dev/null +++ b/logic/VersionFilterData.cpp @@ -0,0 +1,68 @@ +#include "VersionFilterData.h" +#include "minecraft/ParseUtils.h" + +extern VersionFilterData g_VersionFilterData = VersionFilterData(); + +VersionFilterData::VersionFilterData() +{ + // 1.3.* + 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; + + // 1.4.* + 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; + + // 1.5 + 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}}; + + // 1.5.1 + 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}}; + + // 1.5.2 + 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}}; + + // don't use installers for those. + forgeInstallerBlacklist = QSet<QString>({"1.5.2"}); + // these won't show up in version lists because they are extremely bad and dangerous + legacyBlacklist = QSet<QString>({"rd-160052"}); + /* + * nothing older than this will be accepted from Mojang servers + * (these versions need to be tested by us first) + */ + legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00"); +} diff --git a/logic/VersionFilterData.h b/logic/VersionFilterData.h new file mode 100644 index 00000000..e010adc7 --- /dev/null +++ b/logic/VersionFilterData.h @@ -0,0 +1,26 @@ +#pragma once +#include <QMap> +#include <QString> +#include <QSet> +#include <QDateTime> + +struct FMLlib +{ + QString filename; + QString checksum; + bool ours; +}; + +struct VersionFilterData +{ + VersionFilterData(); + // mapping between minecraft versions and FML libraries required + QMap<QString, QList<FMLlib>> fmlLibsMapping; + // set of minecraft versions for which using forge installers is blacklisted + QSet<QString> forgeInstallerBlacklist; + // set of 'legacy' versions that will not show up in the version lists. + QSet<QString> legacyBlacklist; + // no new versions below this date will be accepted from Mojang servers + QDateTime legacyCutoffDate; +}; +extern VersionFilterData g_VersionFilterData; diff --git a/logic/VersionFinal.cpp b/logic/VersionFinal.cpp deleted file mode 100644 index dedf2ce5..00000000 --- a/logic/VersionFinal.cpp +++ /dev/null @@ -1,377 +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. - */ - -#include "VersionFinal.h" - -#include <QDebug> -#include <QFile> -#include <QDir> - -#include "OneSixVersionBuilder.h" -#include "OneSixInstance.h" - -template <typename A, typename B> QMap<A, B> invert(const QMap<B, A> &in) -{ - QMap<A, B> out; - for (auto it = in.begin(); it != in.end(); ++it) - { - out.insert(it.value(), it.key()); - } - return out; -} - -VersionFinal::VersionFinal(OneSixInstance *instance, QObject *parent) - : QAbstractListModel(parent), m_instance(instance) -{ - clear(); -} - -void VersionFinal::reload(const bool onlyVanilla, const QStringList &external) -{ - //FIXME: source of epic failure. - beginResetModel(); - OneSixVersionBuilder::build(this, m_instance, onlyVanilla, external); - reapply(true); - endResetModel(); -} - -void VersionFinal::clear() -{ - id.clear(); - time.clear(); - releaseTime.clear(); - type.clear(); - assets.clear(); - processArguments.clear(); - minecraftArguments.clear(); - minimumLauncherVersion = 0xDEADBEAF; - mainClass.clear(); - libraries.clear(); - tweakers.clear(); -} - -bool VersionFinal::canRemove(const int index) const -{ - if (index < versionFiles.size()) - { - return versionFiles.at(index)->fileId != "org.multimc.version.json"; - } - return false; -} - -bool VersionFinal::remove(const int index) -{ - if (canRemove(index) && QFile::remove(versionFiles.at(index)->filename)) - { - beginResetModel(); - versionFiles.removeAt(index); - reapply(true); - endResetModel(); - return true; - } - return false; -} - -bool VersionFinal::remove(const QString id) -{ - int i = 0; - for (auto file : versionFiles) - { - if (file->fileId == id) - { - return remove(i); - } - i++; - } - return false; -} - -QString VersionFinal::versionFileId(const int index) const -{ - if (index < 0 || index >= versionFiles.size()) - { - return QString(); - } - return versionFiles.at(index)->fileId; -} - -VersionFilePtr VersionFinal::versionFile(const QString &id) -{ - for (auto file : versionFiles) - { - if (file->fileId == id) - { - return file; - } - } - return 0; -} - -bool VersionFinal::hasFtbPack() -{ - return versionFile("org.multimc.ftb.pack.json") != nullptr; -} - -bool VersionFinal::removeFtbPack() -{ - return remove("org.multimc.ftb.pack.json"); -} - -QList<std::shared_ptr<OneSixLibrary> > VersionFinal::getActiveNormalLibs() -{ - QList<std::shared_ptr<OneSixLibrary> > output; - for (auto lib : libraries) - { - if (lib->isActive() && !lib->isNative()) - { - output.append(lib); - } - } - return output; -} -QList<std::shared_ptr<OneSixLibrary> > VersionFinal::getActiveNativeLibs() -{ - QList<std::shared_ptr<OneSixLibrary> > output; - for (auto lib : libraries) - { - if (lib->isActive() && lib->isNative()) - { - output.append(lib); - } - } - return output; -} - -std::shared_ptr<VersionFinal> VersionFinal::fromJson(const QJsonObject &obj) -{ - std::shared_ptr<VersionFinal> version(new VersionFinal(0)); - try - { - OneSixVersionBuilder::readJsonAndApplyToVersion(version.get(), obj); - } - catch(MMCError & err) - { - return 0; - } - return version; -} - -QVariant VersionFinal::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= versionFiles.size()) - return QVariant(); - - if (role == Qt::DisplayRole) - { - switch (column) - { - case 0: - return versionFiles.at(row)->name; - case 1: - return versionFiles.at(row)->version; - default: - return QVariant(); - } - } - return QVariant(); -} -QVariant VersionFinal::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal) - { - if (role == Qt::DisplayRole) - { - switch (section) - { - case 0: - return tr("Name"); - case 1: - return tr("Version"); - default: - return QVariant(); - } - } - } - return QVariant(); -} -Qt::ItemFlags VersionFinal::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; -} - -int VersionFinal::rowCount(const QModelIndex &parent) const -{ - return versionFiles.size(); -} - -int VersionFinal::columnCount(const QModelIndex &parent) const -{ - return 2; -} - -bool VersionFinal::isCustom() -{ - return QDir(m_instance->instanceRoot()).exists("custom.json"); -} -bool VersionFinal::revertToBase() -{ - return QDir(m_instance->instanceRoot()).remove("custom.json"); -} - -QMap<QString, int> VersionFinal::getExistingOrder() const -{ - - QMap<QString, int> order; - // default - { - for (auto file : versionFiles) - { - order.insert(file->fileId, file->order); - } - } - // overriden - { - QMap<QString, int> overridenOrder = OneSixVersionBuilder::readOverrideOrders(m_instance); - for (auto id : order.keys()) - { - if (overridenOrder.contains(id)) - { - order[id] = overridenOrder[id]; - } - } - } - return order; -} - -void VersionFinal::move(const int index, const MoveDirection direction) -{ - int theirIndex; - if (direction == MoveUp) - { - theirIndex = index - 1; - } - else - { - theirIndex = index + 1; - } - if (theirIndex < 0 || theirIndex >= versionFiles.size()) - { - return; - } - const QString ourId = versionFileId(index); - const QString theirId = versionFileId(theirIndex); - if (ourId.isNull() || ourId.startsWith("org.multimc.") || - theirId.isNull() || theirId.startsWith("org.multimc.")) - { - return; - } - - VersionFilePtr we = versionFiles[index]; - VersionFilePtr them = versionFiles[theirIndex]; - if (!we || !them) - { - return; - } - beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex); - versionFiles.replace(theirIndex, we); - versionFiles.replace(index, them); - endMoveRows(); - - auto order = getExistingOrder(); - order[ourId] = theirIndex; - order[theirId] = index; - - if (!OneSixVersionBuilder::writeOverrideOrders(order, m_instance)) - { - throw MMCError(tr("Couldn't save the new order")); - } - else - { - reapply(); - } -} -void VersionFinal::resetOrder() -{ - QDir(m_instance->instanceRoot()).remove("order.json"); - reapply(); -} - -void VersionFinal::reapply(const bool alreadyReseting) -{ - if (!alreadyReseting) - { - beginResetModel(); - } - - clear(); - - auto existingOrders = getExistingOrder(); - QList<int> orders = existingOrders.values(); - std::sort(orders.begin(), orders.end()); - QList<VersionFilePtr> newVersionFiles; - for (auto order : orders) - { - auto file = versionFile(existingOrders.key(order)); - newVersionFiles.append(file); - file->applyTo(this); - } - versionFiles.swap(newVersionFiles); - finalize(); - if (!alreadyReseting) - { - endResetModel(); - } -} - -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"; - } - if (minecraftArguments.isEmpty()) - { - QString toCompare = processArguments.toLower(); - if (toCompare == "legacy") - { - minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - minecraftArguments = "--username ${auth_player_name} " - "--session ${auth_session} " - "--version ${profile_name}"; - } - } -} diff --git a/logic/ForgeInstaller.cpp b/logic/forge/ForgeInstaller.cpp index 94b3f319..7f952408 100644 --- a/logic/ForgeInstaller.cpp +++ b/logic/forge/ForgeInstaller.cpp @@ -14,9 +14,15 @@ */ #include "ForgeInstaller.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "net/HttpMetaCache.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/net/HttpMetaCache.h" +#include "logic/tasks/Task.h" +#include "logic/OneSixInstance.h" +#include "logic/forge/ForgeVersionList.h" +#include "logic/VersionFilterData.h" +#include "gui/dialogs/ProgressDialog.h" + #include <quazip.h> #include <quazipfile.h> #include <pathutils.h> @@ -24,23 +30,17 @@ #include <QRegularExpression> #include <QRegularExpressionMatch> #include "MultiMC.h" -#include "tasks/Task.h" -#include "OneSixInstance.h" -#include "lists/ForgeVersionList.h" -#include "gui/dialogs/ProgressDialog.h" - #include <QJsonDocument> #include <QJsonArray> #include <QSaveFile> #include <QCryptographicHash> -ForgeInstaller::ForgeInstaller() - : BaseInstaller() +ForgeInstaller::ForgeInstaller() : BaseInstaller() { } void ForgeInstaller::prepare(const QString &filename, const QString &universalUrl) { - std::shared_ptr<VersionFinal> newVersion; + std::shared_ptr<InstanceVersion> newVersion; m_universal_url = universalUrl; QuaZip zip(filename); @@ -73,7 +73,7 @@ void ForgeInstaller::prepare(const QString &filename, const QString &universalUr // read the forge version info { - newVersion = VersionFinal::fromJson(versionInfoVal.toObject()); + newVersion = InstanceVersion::fromJson(versionInfoVal.toObject()); if (!newVersion) return; } @@ -115,8 +115,8 @@ void ForgeInstaller::prepare(const QString &filename, const QString &universalUr } file.close(); - m_forge_version = newVersion; - realVersionId = m_forge_version->id = installObj.value("minecraft").toString(); + m_forge_json = newVersion; + realVersionId = m_forge_json->id = installObj.value("minecraft").toString(); } bool ForgeInstaller::add(OneSixInstance *to) { @@ -128,7 +128,7 @@ bool ForgeInstaller::add(OneSixInstance *to) QJsonObject obj; obj.insert("order", 5); - if (!m_forge_version) + if (!m_forge_json) return false; int sliding_insert_window = 0; { @@ -136,7 +136,7 @@ bool ForgeInstaller::add(OneSixInstance *to) // for each library in the version we are adding (except for the blacklisted) QSet<QString> blacklist{"lwjgl", "lwjgl_util", "lwjgl-platform"}; - for (auto lib : m_forge_version->libraries) + for (auto lib : m_forge_json->libraries) { QString libName = lib->name(); // WARNING: This could actually break. @@ -157,7 +157,7 @@ bool ForgeInstaller::add(OneSixInstance *to) bool found = false; bool equals = false; // find an entry that matches this one - for (auto tolib : to->getVanillaVersion()->libraries) + for (auto tolib : to->getFullVersion()->vanillaLibraries) { if (tolib->name() != libName) continue; @@ -187,8 +187,8 @@ bool ForgeInstaller::add(OneSixInstance *to) librariesPlus.prepend(libObj); } obj.insert("+libraries", librariesPlus); - obj.insert("mainClass", m_forge_version->mainClass); - QString args = m_forge_version->minecraftArguments; + obj.insert("mainClass", m_forge_json->mainClass); + QString args = m_forge_json->minecraftArguments; QStringList tweakers; { QRegularExpression expression("--tweakClass ([a-zA-Z0-9\\.]*)"); @@ -200,7 +200,7 @@ bool ForgeInstaller::add(OneSixInstance *to) match = expression.match(args); } } - if (!args.isEmpty() && args != to->getVanillaVersion()->minecraftArguments) + if (!args.isEmpty() && args != to->getFullVersion()->vanillaMinecraftArguments) { obj.insert("minecraftArguments", args); } @@ -208,10 +208,10 @@ bool ForgeInstaller::add(OneSixInstance *to) { obj.insert("+tweakers", QJsonArray::fromStringList(tweakers)); } - if (!m_forge_version->processArguments.isEmpty() && - m_forge_version->processArguments != to->getVanillaVersion()->processArguments) + if (!m_forge_json->processArguments.isEmpty() && + m_forge_json->processArguments != to->getFullVersion()->vanillaProcessArguments) { - obj.insert("processArguments", m_forge_version->processArguments); + obj.insert("processArguments", m_forge_json->processArguments); } } @@ -233,11 +233,65 @@ bool ForgeInstaller::add(OneSixInstance *to) return true; } +bool ForgeInstaller::addLegacy(OneSixInstance *to) +{ + if (!BaseInstaller::add(to)) + { + return false; + } + auto entry = MMC->metacache()->resolveEntry("minecraftforge", m_forge_version->filename()); + finalPath = PathCombine(to->jarModsDir(), m_forge_version->filename()); + if (!ensureFilePathExists(finalPath)) + { + return false; + } + if (!QFile::copy(entry->getFullPath(),finalPath)) + { + return false; + } + QJsonObject obj; + obj.insert("order", 5); + { + QJsonArray jarmodsPlus; + { + QJsonObject libObj; + libObj.insert("name", m_forge_version->universal_filename); + jarmodsPlus.append(libObj); + } + obj.insert("+jarMods", jarmodsPlus); + } + + obj.insert("name", QString("Forge")); + obj.insert("fileId", id()); + obj.insert("version", m_forge_version->jobbuildver); + obj.insert("mcVersion", to->intendedVersionId()); + if (g_VersionFilterData.fmlLibsMapping.contains(m_forge_version->mcver)) + { + QJsonArray traitsPlus; + traitsPlus.append(QString("legacyFML")); + obj.insert("+traits", traitsPlus); + } + auto fullversion = to->getFullVersion(); + fullversion->remove("net.minecraftforge"); + + QFile file(filename(to->instanceRoot())); + if (!file.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(QJsonDocument(obj).toJson()); + file.close(); + return true; +} + class ForgeInstallTask : public Task { Q_OBJECT public: - ForgeInstallTask(ForgeInstaller *installer, OneSixInstance *instance, BaseVersionPtr version, QObject *parent = 0) + ForgeInstallTask(ForgeInstaller *installer, OneSixInstance *instance, + BaseVersionPtr version, QObject *parent = 0) : Task(parent), m_installer(installer), m_instance(instance), m_version(version) { } @@ -245,57 +299,59 @@ public: protected: void executeTask() override { + setStatus(tr("Installing forge...")); + ForgeVersionPtr forgeVersion = std::dynamic_pointer_cast<ForgeVersion>(m_version); + if (!forgeVersion) { - setStatus(tr("Installing forge...")); - ForgeVersionPtr forgeVersion = - std::dynamic_pointer_cast<ForgeVersion>(m_version); - if (!forgeVersion) - { - emitFailed(tr("Unknown error occured")); - return; - } - auto entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename); - if (entry->stale) + emitFailed(tr("Unknown error occured")); + return; + } + prepare(forgeVersion); + } + void prepare(ForgeVersionPtr forgeVersion) + { + auto entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename()); + auto installFunction = [this, entry, forgeVersion]() + { + if (!install(entry, forgeVersion)) { - NetJob *fjob = new NetJob("Forge download"); - fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry)); - connect(fjob, &NetJob::progress, [this](qint64 current, qint64 total){setProgress(100 * current / qMax((qint64)1, total));}); - connect(fjob, &NetJob::status, [this](const QString &msg){setStatus(msg);}); - connect(fjob, &NetJob::failed, [this](){emitFailed(tr("Failure to download forge"));}); - connect(fjob, &NetJob::succeeded, [this, entry, forgeVersion]() - { - if (!install(entry, forgeVersion)) - { - QLOG_ERROR() << "Failure installing forge"; - emitFailed(tr("Failure to install forge")); - } - else - { - reload(); - } - }); - fjob->start(); + QLOG_ERROR() << "Failure installing forge"; + emitFailed(tr("Failure to install forge")); } else { - if (!install(entry, forgeVersion)) - { - QLOG_ERROR() << "Failure installing forge"; - emitFailed(tr("Failure to install forge")); - } - else - { - reload(); - } + reload(); } + }; + + if (entry->stale) + { + NetJob *fjob = new NetJob("Forge download"); + fjob->addNetAction(CacheDownload::make(forgeVersion->url(), entry)); + connect(fjob, &NetJob::progress, [this](qint64 current, qint64 total) + { setProgress(100 * current / qMax((qint64)1, total)); }); + connect(fjob, &NetJob::status, [this](const QString & msg) + { setStatus(msg); }); + connect(fjob, &NetJob::failed, [this]() + { emitFailed(tr("Failure to download forge")); }); + connect(fjob, &NetJob::succeeded, installFunction); + fjob->start(); + } + else + { + installFunction(); } } - bool install(const std::shared_ptr<MetaEntry> &entry, const ForgeVersionPtr &forgeVersion) { - QString forgePath = entry->getFullPath(); - m_installer->prepare(forgePath, forgeVersion->universal_url); - return m_installer->add(m_instance); + if (forgeVersion->usesInstaller()) + { + QString forgePath = entry->getFullPath(); + m_installer->prepare(forgePath, forgeVersion->universal_url); + return m_installer->add(m_instance); + } + else + return m_installer->addLegacy(m_instance); } void reload() { @@ -320,8 +376,14 @@ private: BaseVersionPtr m_version; }; -ProgressProvider *ForgeInstaller::createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) +ProgressProvider *ForgeInstaller::createInstallTask(OneSixInstance *instance, + BaseVersionPtr version, QObject *parent) { + if (!version) + { + return nullptr; + } + m_forge_version = std::dynamic_pointer_cast<ForgeVersion>(version); return new ForgeInstallTask(this, instance, version, parent); } diff --git a/logic/ForgeInstaller.h b/logic/forge/ForgeInstaller.h index 05cc994b..14aeeb51 100644 --- a/logic/ForgeInstaller.h +++ b/logic/forge/ForgeInstaller.h @@ -15,28 +15,34 @@ #pragma once -#include "BaseInstaller.h" +#include "logic/BaseInstaller.h" #include <QString> #include <memory> -class VersionFinal; +class InstanceVersion; +class ForgeInstallTask; +class ForgeVersion; class ForgeInstaller : public BaseInstaller { + friend class ForgeInstallTask; public: ForgeInstaller(); + virtual ~ForgeInstaller(){}; + virtual ProgressProvider *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) override; +protected: + virtual QString id() const override { return "net.minecraftforge"; } void prepare(const QString &filename, const QString &universalUrl); bool add(OneSixInstance *to) override; - - QString id() const override { return "net.minecraftforge"; } - - ProgressProvider *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) override; + bool addLegacy(OneSixInstance *to); private: - // the version, read from the installer - std::shared_ptr<VersionFinal> m_forge_version; + // the parsed version json, read from the installer + std::shared_ptr<InstanceVersion> m_forge_json; + // the actual forge version + std::shared_ptr<ForgeVersion> m_forge_version; QString internalPath; QString finalPath; QString realVersionId; diff --git a/logic/net/ForgeMirror.h b/logic/forge/ForgeMirror.h index 2518dffe..2518dffe 100644 --- a/logic/net/ForgeMirror.h +++ b/logic/forge/ForgeMirror.h diff --git a/logic/net/ForgeMirrors.cpp b/logic/forge/ForgeMirrors.cpp index b224306f..b224306f 100644 --- a/logic/net/ForgeMirrors.cpp +++ b/logic/forge/ForgeMirrors.cpp diff --git a/logic/net/ForgeMirrors.h b/logic/forge/ForgeMirrors.h index 6784fba1..d25762db 100644 --- a/logic/net/ForgeMirrors.h +++ b/logic/forge/ForgeMirrors.h @@ -15,10 +15,10 @@ #pragma once -#include "NetAction.h" -#include "HttpMetaCache.h" -#include "ForgeXzDownload.h" -#include "NetJob.h" +#include "logic/net/NetAction.h" +#include "logic/net/HttpMetaCache.h" +#include "logic/net/NetJob.h" +#include "logic/forge/ForgeXzDownload.h" #include <QFile> #include <QTemporaryFile> typedef std::shared_ptr<class ForgeMirrors> ForgeMirrorsPtr; diff --git a/logic/forge/ForgeVersion.cpp b/logic/forge/ForgeVersion.cpp new file mode 100644 index 00000000..3131ec39 --- /dev/null +++ b/logic/forge/ForgeVersion.cpp @@ -0,0 +1,55 @@ +#include "ForgeVersion.h" +#include "logic/VersionFilterData.h" +#include <QObject> + +QString ForgeVersion::name() +{ + return "Forge " + jobbuildver; +} + +QString ForgeVersion::descriptor() +{ + return universal_filename; +} + +QString ForgeVersion::typeString() const +{ + if (is_recommended) + return QObject::tr("Recommended"); + return QString(); +} + +bool ForgeVersion::operator<(BaseVersion &a) +{ + ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a); + if (!pa) + return true; + return m_buildnr < pa->m_buildnr; +} + +bool ForgeVersion::operator>(BaseVersion &a) +{ + ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a); + if (!pa) + return false; + return m_buildnr > pa->m_buildnr; +} + +bool ForgeVersion::usesInstaller() +{ + if(installer_url.isEmpty()) + return false; + if(g_VersionFilterData.forgeInstallerBlacklist.contains(mcver)) + return false; + return true; +} + +QString ForgeVersion::filename() +{ + return usesInstaller() ? installer_filename : universal_filename; +} + +QString ForgeVersion::url() +{ + return usesInstaller() ? installer_url : universal_url; +} diff --git a/logic/forge/ForgeVersion.h b/logic/forge/ForgeVersion.h new file mode 100644 index 00000000..466f46bd --- /dev/null +++ b/logic/forge/ForgeVersion.h @@ -0,0 +1,33 @@ +#pragma once +#include <QString> +#include <memory> +#include "logic/BaseVersion.h" + +struct ForgeVersion; +typedef std::shared_ptr<ForgeVersion> ForgeVersionPtr; + +struct ForgeVersion : public BaseVersion +{ + virtual QString descriptor() override; + virtual QString name() override; + virtual QString typeString() const override; + virtual bool operator<(BaseVersion &a) override; + virtual bool operator>(BaseVersion &a) override; + + QString filename(); + QString url(); + + bool usesInstaller(); + + int m_buildnr = 0; + QString branch; + QString universal_url; + QString changelog_url; + QString installer_url; + QString jobbuildver; + QString mcver; + QString mcver_sane; + QString universal_filename; + QString installer_filename; + bool is_recommended = false; +}; diff --git a/logic/lists/ForgeVersionList.cpp b/logic/forge/ForgeVersionList.cpp index 4902dc64..efb30ba6 100644 --- a/logic/lists/ForgeVersionList.cpp +++ b/logic/forge/ForgeVersionList.cpp @@ -13,9 +13,10 @@ * limitations under the License. */ -#include "ForgeVersionList.h" -#include <logic/net/NetJob.h> -#include <logic/net/URLConstants.h> +#include "logic/forge/ForgeVersionList.h" +#include "logic/forge/ForgeVersion.h" +#include "logic/net/NetJob.h" +#include "logic/net/URLConstants.h" #include "MultiMC.h" #include <QtNetwork> @@ -71,7 +72,7 @@ QVariant ForgeVersionList::data(const QModelIndex &index, int role) const return version->name(); case 1: - return version->mcver; + return version->mcver_sane; case 2: return version->typeString(); @@ -236,7 +237,7 @@ bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out) if (!build_nr) continue; QJsonArray files = obj.value("files").toArray(); - QString url, jobbuildver, mcver, buildtype, filename; + QString url, jobbuildver, mcver, buildtype, universal_filename; QString changelog_url, installer_url; QString installer_filename; bool valid = false; @@ -254,7 +255,7 @@ bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out) url = file.value("url").toString(); jobbuildver = file.value("jobbuildver").toString(); int lastSlash = url.lastIndexOf('/'); - filename = url.mid(lastSlash + 1); + universal_filename = url.mid(lastSlash + 1); valid = true; } else if (buildtype == "changelog") @@ -281,15 +282,9 @@ bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out) fVersion->changelog_url = changelog_url; fVersion->installer_url = installer_url; fVersion->jobbuildver = jobbuildver; - fVersion->mcver = mcver; - if (installer_filename.isEmpty()) - { - fVersion->filename = filename; - } - else - { - fVersion->filename = installer_filename; - } + fVersion->mcver = fVersion->mcver_sane = mcver; + fVersion->installer_filename = installer_filename; + fVersion->universal_filename = universal_filename; fVersion->m_buildnr = build_nr; out.append(fVersion); } @@ -341,9 +336,17 @@ bool ForgeListLoadTask::parseForgeGradleList(QList<BaseVersionPtr> &out) std::shared_ptr<ForgeVersion> fVersion(new ForgeVersion()); fVersion->m_buildnr = number.value("build").toDouble(); fVersion->jobbuildver = number.value("version").toString(); + fVersion->branch = number.value("branch").toString(""); fVersion->mcver = number.value("mcversion").toString(); - fVersion->filename = ""; - QString filename, installer_filename; + fVersion->universal_filename = ""; + fVersion->installer_filename = ""; + // HACK: here, we fix the minecraft version used by forge. + // HACK: this will inevitably break (later) + // FIXME: replace with a dictionary + fVersion->mcver_sane = fVersion->mcver; + fVersion->mcver_sane.replace("_pre", "-pre"); + + QString universal_filename, installer_filename; QJsonArray files = number.value("files").toArray(); for (auto fIt = files.begin(); fIt != files.end(); ++fIt) { @@ -353,37 +356,47 @@ bool ForgeListLoadTask::parseForgeGradleList(QList<BaseVersionPtr> &out) { continue; } - if (file.at(1).toString() == "installer") + + QString extension = file.at(0).toString(); + QString part = file.at(1).toString(); + QString checksum = file.at(2).toString(); + + // insane form of mcver is used here + QString longVersion = fVersion->mcver + "-" + fVersion->jobbuildver; + if (!fVersion->branch.isEmpty()) + { + longVersion = longVersion + "-" + fVersion->branch; + } + QString filename = artifact + "-" + longVersion + "-" + part + "." + extension; + + QString url = QString("%1/%2/%3") + .arg(webpath) + .arg(longVersion) + .arg(filename); + + if (part == "installer") { - fVersion->installer_url = QString("%1/%2-%3/%4-%2-%3-installer.%5").arg( - webpath, fVersion->mcver, fVersion->jobbuildver, artifact, - file.at(0).toString()); - installer_filename = QString("%1-%2-%3-installer.%4").arg( - artifact, fVersion->mcver, fVersion->jobbuildver, file.at(0).toString()); + fVersion->installer_url = url; + installer_filename = filename; } - else if (file.at(1).toString() == "universal") + else if (part == "universal") { - fVersion->universal_url = QString("%1/%2-%3/%4-%2-%3-universal.%5").arg( - webpath, fVersion->mcver, fVersion->jobbuildver, artifact, - file.at(0).toString()); - filename = QString("%1-%2-%3-universal.%4").arg( - artifact, fVersion->mcver, fVersion->jobbuildver, file.at(0).toString()); + fVersion->universal_url = url; + universal_filename = filename; } - else if (file.at(1).toString() == "changelog") + else if (part == "changelog") { - fVersion->changelog_url = QString("%1/%2-%3/%4-%2-%3-changelog.%5").arg( - webpath, fVersion->mcver, fVersion->jobbuildver, artifact, - file.at(0).toString()); + fVersion->changelog_url = url; } } if (fVersion->installer_url.isEmpty() && fVersion->universal_url.isEmpty()) { continue; } - fVersion->filename = fVersion->installer_url.isEmpty() ? filename : installer_filename; + fVersion->universal_filename = universal_filename; + fVersion->installer_filename = installer_filename; out.append(fVersion); } - return true; } @@ -425,6 +438,7 @@ void ForgeListLoadTask::listFailed() QLOG_ERROR() << "Getting forge version list failed for reasons unknown."; } } + void ForgeListLoadTask::gradleListFailed() { auto reply = gradleListDownload->m_reply; diff --git a/logic/lists/ForgeVersionList.h b/logic/forge/ForgeVersionList.h index b19d3f56..477edb3d 100644 --- a/logic/lists/ForgeVersionList.h +++ b/logic/forge/ForgeVersionList.h @@ -18,57 +18,12 @@ #include <QObject> #include <QAbstractListModel> #include <QUrl> - #include <QNetworkReply> -#include "BaseVersionList.h" + +#include "logic/BaseVersionList.h" #include "logic/tasks/Task.h" #include "logic/net/NetJob.h" - -class ForgeVersion; -typedef std::shared_ptr<ForgeVersion> ForgeVersionPtr; - -struct ForgeVersion : public BaseVersion -{ - virtual QString descriptor() override - { - return filename; - } - ; - virtual QString name() override - { - return "Forge " + jobbuildver; - } - ; - virtual QString typeString() const override - { - if (installer_url.isEmpty()) - return "Universal"; - else - return "Installer"; - } - - virtual bool operator<(BaseVersion &a) override - { - ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a); - if(!pa) - return true; - return m_buildnr < pa->m_buildnr; - } - virtual bool operator>(BaseVersion &a) override - { - ForgeVersion *pa = dynamic_cast<ForgeVersion *>(&a); - if(!pa) - return false; - return m_buildnr > pa->m_buildnr; - } - int m_buildnr = 0; - QString universal_url; - QString changelog_url; - QString installer_url; - QString jobbuildver; - QString mcver; - QString filename; -}; +#include "logic/forge/ForgeVersion.h" class ForgeVersionList : public BaseVersionList { @@ -86,6 +41,8 @@ public: virtual BaseVersionPtr getLatestStable() const; + ForgeVersionPtr findVersionByVersionNr(QString version); + virtual QVariant data(const QModelIndex &index, int role) const; virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; virtual int columnCount(const QModelIndex &parent) const; diff --git a/logic/net/ForgeXzDownload.cpp b/logic/forge/ForgeXzDownload.cpp index 359ad858..359ad858 100644 --- a/logic/net/ForgeXzDownload.cpp +++ b/logic/forge/ForgeXzDownload.cpp diff --git a/logic/net/ForgeXzDownload.h b/logic/forge/ForgeXzDownload.h index 7bdfb6d9..f2564380 100644 --- a/logic/net/ForgeXzDownload.h +++ b/logic/forge/ForgeXzDownload.h @@ -15,8 +15,8 @@ #pragma once -#include "NetAction.h" -#include "HttpMetaCache.h" +#include "logic/net/NetAction.h" +#include "logic/net/HttpMetaCache.h" #include <QFile> #include <QTemporaryFile> #include "ForgeMirror.h" diff --git a/logic/LegacyForge.cpp b/logic/forge/LegacyForge.cpp index 94212ae4..94212ae4 100644 --- a/logic/LegacyForge.cpp +++ b/logic/forge/LegacyForge.cpp diff --git a/logic/LegacyForge.h b/logic/forge/LegacyForge.h index f4165ffa..ec49f63c 100644 --- a/logic/LegacyForge.h +++ b/logic/forge/LegacyForge.h @@ -15,7 +15,7 @@ #pragma once -#include "Mod.h" +#include "logic/Mod.h" class MinecraftForge : public Mod { diff --git a/logic/JavaChecker.cpp b/logic/java/JavaChecker.cpp index b87ee3d5..b87ee3d5 100644 --- a/logic/JavaChecker.cpp +++ b/logic/java/JavaChecker.cpp diff --git a/logic/JavaChecker.h b/logic/java/JavaChecker.h index e19895f7..e19895f7 100644 --- a/logic/JavaChecker.h +++ b/logic/java/JavaChecker.h diff --git a/logic/JavaCheckerJob.cpp b/logic/java/JavaCheckerJob.cpp index b0aea758..b0aea758 100644 --- a/logic/JavaCheckerJob.cpp +++ b/logic/java/JavaCheckerJob.cpp diff --git a/logic/JavaCheckerJob.h b/logic/java/JavaCheckerJob.h index 132a92d4..132a92d4 100644 --- a/logic/JavaCheckerJob.h +++ b/logic/java/JavaCheckerJob.h diff --git a/logic/JavaUtils.cpp b/logic/java/JavaUtils.cpp index 3a3046bd..09719c73 100644 --- a/logic/JavaUtils.cpp +++ b/logic/java/JavaUtils.cpp @@ -16,7 +16,6 @@ #include <QStringList> #include <QString> #include <QDir> -#include <QMessageBox> #include <QStringList> #include <setting.h> @@ -24,11 +23,10 @@ #include "MultiMC.h" -#include "JavaUtils.h" #include "logger/QsLog.h" -#include "gui/dialogs/VersionSelectDialog.h" -#include "JavaCheckerJob.h" -#include "lists/JavaVersionList.h" +#include "logic/java/JavaUtils.h" +#include "logic/java/JavaCheckerJob.h" +#include "logic/java/JavaVersionList.h" JavaUtils::JavaUtils() { @@ -124,7 +122,7 @@ QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString javaVersion->id = subKeyName; javaVersion->arch = archType; javaVersion->path = - QDir(PathCombine(value, "bin")).absoluteFilePath("java.exe"); + QDir(PathCombine(value, "bin")).absoluteFilePath("javaw.exe"); javas.append(javaVersion); } @@ -154,12 +152,12 @@ QList<QString> JavaUtils::FindJavaPaths() KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); java_candidates.append(JRE64s); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/java.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/java.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe")); java_candidates.append(JDK64s); java_candidates.append(JRE32s); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/java.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/java.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe")); java_candidates.append(JDK32s); java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path)); diff --git a/logic/JavaUtils.h b/logic/java/JavaUtils.h index 22a68ef3..af92100f 100644 --- a/logic/JavaUtils.h +++ b/logic/java/JavaUtils.h @@ -21,7 +21,7 @@ #include <osutils.h> #include "JavaCheckerJob.h" #include "JavaChecker.h" -#include "lists/JavaVersionList.h" +#include "JavaVersionList.h" #if WINDOWS #include <windows.h> diff --git a/logic/lists/JavaVersionList.cpp b/logic/java/JavaVersionList.cpp index 4fd0bc19..dcb6ced6 100644 --- a/logic/lists/JavaVersionList.cpp +++ b/logic/java/JavaVersionList.cpp @@ -13,16 +13,16 @@ * limitations under the License. */ -#include "JavaVersionList.h" -#include "MultiMC.h" - #include <QtNetwork> #include <QtXml> #include <QRegExp> +#include "MultiMC.h" #include "logger/QsLog.h" -#include "logic/JavaCheckerJob.h" -#include "logic/JavaUtils.h" + +#include "logic/java/JavaVersionList.h" +#include "logic/java/JavaCheckerJob.h" +#include "logic/java/JavaUtils.h" JavaVersionList::JavaVersionList(QObject *parent) : BaseVersionList(parent) { diff --git a/logic/lists/JavaVersionList.h b/logic/java/JavaVersionList.h index e6cc8e5f..a46f33a2 100644 --- a/logic/lists/JavaVersionList.h +++ b/logic/java/JavaVersionList.h @@ -18,9 +18,9 @@ #include <QObject> #include <QAbstractListModel> -#include "BaseVersionList.h" +#include "logic/BaseVersionList.h" #include "logic/tasks/Task.h" -#include "logic/JavaCheckerJob.h" +#include "logic/java/JavaCheckerJob.h" class JavaListLoadTask; diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp deleted file mode 100644 index b9d60c61..00000000 --- a/logic/lists/MinecraftVersionList.cpp +++ /dev/null @@ -1,290 +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. - */ - -#include "MinecraftVersionList.h" -#include "MultiMC.h" -#include "logic/net/URLConstants.h" - -#include <QtXml> - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonValue> -#include <QJsonParseError> - -#include <QtAlgorithms> - -#include <QtNetwork> - -MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) -{ -} - -Task *MinecraftVersionList::getLoadTask() -{ - return new MCVListLoadTask(this); -} - -bool MinecraftVersionList::isLoaded() -{ - return m_loaded; -} - -const BaseVersionPtr MinecraftVersionList::at(int i) const -{ - return m_vlist.at(i); -} - -int MinecraftVersionList::count() const -{ - return m_vlist.count(); -} - -static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) -{ - auto left = std::dynamic_pointer_cast<MinecraftVersion>(first); - auto right = std::dynamic_pointer_cast<MinecraftVersion>(second); - return left->timestamp > right->timestamp; -} - -void MinecraftVersionList::sortInternal() -{ - qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); -} - -void MinecraftVersionList::sort() -{ - beginResetModel(); - sortInternal(); - endResetModel(); -} - -BaseVersionPtr MinecraftVersionList::getLatestStable() const -{ - for (int i = 0; i < m_vlist.length(); i++) - { - auto ver = std::dynamic_pointer_cast<MinecraftVersion>(m_vlist.at(i)); - if (ver->is_latest && !ver->is_snapshot) - { - return m_vlist.at(i); - } - } - return BaseVersionPtr(); -} - -void MinecraftVersionList::updateListData(QList<BaseVersionPtr> versions) -{ - beginResetModel(); - m_vlist = versions; - m_loaded = true; - sortInternal(); - endResetModel(); -} - -inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) -{ - QDomNodeList elementList = parent.elementsByTagName(tagname); - if (elementList.count()) - return elementList.at(0).toElement(); - else - return QDomElement(); -} - -inline QDateTime timeFromS3Time(QString str) -{ - return QDateTime::fromString(str, Qt::ISODate); -} - -MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) -{ - m_list = vlist; - m_currentStable = NULL; - vlistReply = nullptr; - legacyWhitelist.insert("1.5.2"); - legacyWhitelist.insert("1.5.1"); - legacyWhitelist.insert("1.5"); - legacyWhitelist.insert("1.4.7"); - legacyWhitelist.insert("1.4.6"); - legacyWhitelist.insert("1.4.5"); - legacyWhitelist.insert("1.4.4"); - legacyWhitelist.insert("1.4.3"); - legacyWhitelist.insert("1.4.2"); - legacyWhitelist.insert("1.4.1"); - legacyWhitelist.insert("1.4"); - legacyWhitelist.insert("1.3.2"); - legacyWhitelist.insert("1.3.1"); - legacyWhitelist.insert("1.3"); - legacyWhitelist.insert("1.2.5"); - legacyWhitelist.insert("1.2.4"); - legacyWhitelist.insert("1.2.3"); - legacyWhitelist.insert("1.2.2"); - legacyWhitelist.insert("1.2.1"); - legacyWhitelist.insert("1.1"); - legacyWhitelist.insert("1.0.1"); - legacyWhitelist.insert("1.0"); -} - -MCVListLoadTask::~MCVListLoadTask() -{ -} - -void MCVListLoadTask::executeTask() -{ - setStatus(tr("Loading instance version list...")); - auto worker = MMC->qnam(); - vlistReply = worker->get(QNetworkRequest(QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json"))); - connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); -} - -void MCVListLoadTask::list_downloaded() -{ - if (vlistReply->error() != QNetworkReply::NoError) - { - vlistReply->deleteLater(); - emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString()); - return; - } - - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(vlistReply->readAll(), &jsonError); - vlistReply->deleteLater(); - - if (jsonError.error != QJsonParseError::NoError) - { - emitFailed("Error parsing version list JSON:" + jsonError.errorString()); - return; - } - - if (!jsonDoc.isObject()) - { - emitFailed("Error parsing version list JSON: jsonDoc is not an object"); - return; - } - - QJsonObject root = jsonDoc.object(); - - // Get the ID of the latest release and the latest snapshot. - if (!root.value("latest").isObject()) - { - emitFailed("Error parsing version list JSON: version list is missing 'latest' object"); - return; - } - - QJsonObject latest = root.value("latest").toObject(); - - QString latestReleaseID = latest.value("release").toString(""); - QString latestSnapshotID = latest.value("snapshot").toString(""); - if (latestReleaseID.isEmpty()) - { - emitFailed("Error parsing version list JSON: latest release field is missing"); - return; - } - if (latestSnapshotID.isEmpty()) - { - emitFailed("Error parsing version list JSON: latest snapshot field is missing"); - return; - } - - // Now, get the array of versions. - if (!root.value("versions").isArray()) - { - emitFailed( - "Error parsing version list JSON: version list object is missing 'versions' array"); - return; - } - QJsonArray versions = root.value("versions").toArray(); - - QList<BaseVersionPtr> tempList; - for (int i = 0; i < versions.count(); i++) - { - bool is_snapshot = false; - bool is_latest = false; - - // Load the version info. - if (!versions[i].isObject()) - { - // FIXME: log this somewhere - continue; - } - QJsonObject version = versions[i].toObject(); - QString versionID = version.value("id").toString(""); - QString versionTimeStr = version.value("releaseTime").toString(""); - QString versionTypeStr = version.value("type").toString(""); - if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) - { - // FIXME: log this somewhere - continue; - } - - // Parse the timestamp. - QDateTime versionTime = timeFromS3Time(versionTimeStr); - if (!versionTime.isValid()) - { - // FIXME: log this somewhere - continue; - } - // Parse the type. - MinecraftVersion::VersionType versionType; - // OneSix or Legacy. use filter to determine type - if (versionTypeStr == "release") - { - versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy - : MinecraftVersion::OneSix; - is_latest = (versionID == latestReleaseID); - is_snapshot = false; - } - else if (versionTypeStr == "snapshot") // It's a snapshot... yay - { - versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy - : MinecraftVersion::OneSix; - is_latest = (versionID == latestSnapshotID); - is_snapshot = true; - } - else if (versionTypeStr == "old_alpha") - { - versionType = MinecraftVersion::Nostalgia; - is_latest = false; - is_snapshot = false; - } - else if (versionTypeStr == "old_beta") - { - versionType = MinecraftVersion::Legacy; - is_latest = false; - is_snapshot = false; - } - else - { - // FIXME: log this somewhere - continue; - } - // Get the download URL. - QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; - - // Now, we construct the version object and add it to the list. - std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); - mcVersion->m_name = mcVersion->m_descriptor = versionID; - mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); - mcVersion->download_url = dlUrl; - mcVersion->is_latest = is_latest; - mcVersion->is_snapshot = is_snapshot; - mcVersion->type = versionType; - tempList.append(mcVersion); - } - m_list->updateListData(tempList); - - emitSucceeded(); - return; -} diff --git a/logic/LiteLoaderInstaller.cpp b/logic/liteloader/LiteLoaderInstaller.cpp index 99cc5643..ea1a4396 100644 --- a/logic/LiteLoaderInstaller.cpp +++ b/logic/liteloader/LiteLoaderInstaller.cpp @@ -20,11 +20,11 @@ #include "logger/QsLog.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "OneSixInstance.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/OneSixInstance.h" #include "MultiMC.h" -#include "lists/LiteLoaderVersionList.h" +#include "logic/liteloader/LiteLoaderVersionList.h" LiteLoaderInstaller::LiteLoaderInstaller() : BaseInstaller() { diff --git a/logic/LiteLoaderInstaller.h b/logic/liteloader/LiteLoaderInstaller.h index 3ab5acb2..43ad6b83 100644 --- a/logic/LiteLoaderInstaller.h +++ b/logic/liteloader/LiteLoaderInstaller.h @@ -15,12 +15,11 @@ #pragma once -#include "BaseInstaller.h" - #include <QString> #include <QMap> -#include "logic/lists/LiteLoaderVersionList.h" +#include "logic/BaseInstaller.h" +#include "logic/liteloader/LiteLoaderVersionList.h" class LiteLoaderInstaller : public BaseInstaller { diff --git a/logic/lists/LiteLoaderVersionList.cpp b/logic/liteloader/LiteLoaderVersionList.cpp index ef95eefd..ef95eefd 100644 --- a/logic/lists/LiteLoaderVersionList.cpp +++ b/logic/liteloader/LiteLoaderVersionList.cpp diff --git a/logic/lists/LiteLoaderVersionList.h b/logic/liteloader/LiteLoaderVersionList.h index bfc913e5..0aecc3e1 100644 --- a/logic/lists/LiteLoaderVersionList.h +++ b/logic/liteloader/LiteLoaderVersionList.h @@ -19,10 +19,10 @@ #include <QString> #include <QStringList> -#include "BaseVersionList.h" -#include "logic/tasks/Task.h" #include "logic/BaseVersion.h" -#include <logic/net/NetJob.h> +#include "logic/BaseVersionList.h" +#include "logic/tasks/Task.h" +#include "logic/net/NetJob.h" class LLListLoadTask; class QNetworkReply; diff --git a/logic/minecraft/InstanceVersion.cpp b/logic/minecraft/InstanceVersion.cpp new file mode 100644 index 00000000..ca0e3796 --- /dev/null +++ b/logic/minecraft/InstanceVersion.cpp @@ -0,0 +1,537 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <QDebug> +#include <QFile> +#include <QDir> +#include <QUuid> +#include <QJsonDocument> +#include <QJsonArray> +#include <pathutils.h> + +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/VersionBuilder.h" +#include "logic/OneSixInstance.h" + +InstanceVersion::InstanceVersion(OneSixInstance *instance, QObject *parent) + : QAbstractListModel(parent), m_instance(instance) +{ + clear(); +} + +void InstanceVersion::reload(const QStringList &external) +{ + m_externalPatches = external; + beginResetModel(); + VersionBuilder::build(this, m_instance, m_externalPatches); + reapply(true); + endResetModel(); +} + +void InstanceVersion::clear() +{ + id.clear(); + m_updateTimeString.clear(); + m_updateTime = QDateTime(); + m_releaseTimeString.clear(); + m_releaseTime = QDateTime(); + type.clear(); + assets.clear(); + processArguments.clear(); + minecraftArguments.clear(); + minimumLauncherVersion = 0xDEADBEAF; + mainClass.clear(); + appletClass.clear(); + libraries.clear(); + tweakers.clear(); + jarMods.clear(); + traits.clear(); +} + +bool InstanceVersion::canRemove(const int index) const +{ + return VersionPatches.at(index)->isMoveable(); +} + +bool InstanceVersion::preremove(VersionPatchPtr patch) +{ + bool ok = true; + for(auto & jarmod: patch->getJarMods()) + { + QString fullpath =PathCombine(m_instance->jarModsDir(), jarmod->name); + QFileInfo finfo (fullpath); + if(finfo.exists()) + ok &= QFile::remove(fullpath); + } + return ok; +} + +bool InstanceVersion::remove(const int index) +{ + if (!canRemove(index)) + return false; + if(!preremove(VersionPatches[index])) + { + return false; + } + if(!QFile::remove(VersionPatches.at(index)->getPatchFilename())) + return false; + beginRemoveRows(QModelIndex(), index, index); + VersionPatches.removeAt(index); + endRemoveRows(); + reapply(true); + saveCurrentOrder(); + return true; +} + +bool InstanceVersion::remove(const QString id) +{ + int i = 0; + for (auto patch : VersionPatches) + { + if (patch->getPatchID() == id) + { + return remove(i); + } + i++; + } + return false; +} + +QString InstanceVersion::versionFileId(const int index) const +{ + if (index < 0 || index >= VersionPatches.size()) + { + return QString(); + } + return VersionPatches.at(index)->getPatchID(); +} + +VersionPatchPtr InstanceVersion::versionPatch(const QString &id) +{ + for (auto file : VersionPatches) + { + if (file->getPatchID() == id) + { + return file; + } + } + return 0; +} + +VersionPatchPtr InstanceVersion::versionPatch(int index) +{ + if(index < 0 || index >= VersionPatches.size()) + return 0; + return VersionPatches[index]; +} + + +bool InstanceVersion::hasJarMods() +{ + return !jarMods.isEmpty(); +} + +bool InstanceVersion::hasFtbPack() +{ + return versionPatch("org.multimc.ftb.pack.json") != nullptr; +} + +bool InstanceVersion::removeFtbPack() +{ + return remove("org.multimc.ftb.pack.json"); +} + +bool InstanceVersion::isVanilla() +{ + QDir patches(PathCombine(m_instance->instanceRoot(), "patches/")); + if(VersionPatches.size() > 1) + return false; + if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) + return false; + if(QFile::exists(PathCombine(m_instance->instanceRoot(), "version.json"))) + return false; + return true; +} + +bool InstanceVersion::revertToVanilla() +{ + beginResetModel(); + // remove custom.json, if present + QString customPath = PathCombine(m_instance->instanceRoot(), "custom.json"); + if(QFile::exists(customPath)) + { + if(!QFile::remove(customPath)) + { + endResetModel(); + return false; + } + } + // remove version.json, if present + QString versionPath = PathCombine(m_instance->instanceRoot(), "version.json"); + if(QFile::exists(versionPath)) + { + if(!QFile::remove(versionPath)) + { + endResetModel(); + return false; + } + } + // remove patches, if present + auto it = VersionPatches.begin(); + while (it != VersionPatches.end()) + { + if ((*it)->isMoveable()) + { + if(!preremove(*it)) + { + endResetModel(); + saveCurrentOrder(); + return false; + } + if(!QFile::remove((*it)->getPatchFilename())) + { + endResetModel(); + saveCurrentOrder(); + return false; + } + it = VersionPatches.erase(it); + } + else + it++; + } + reapply(true); + endResetModel(); + saveCurrentOrder(); + return true; +} + +bool InstanceVersion::hasDeprecatedVersionFiles() +{ + if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) + return true; + if(QFile::exists(PathCombine(m_instance->instanceRoot(), "version.json"))) + return true; + return false; +} + +bool InstanceVersion::removeDeprecatedVersionFiles() +{ + beginResetModel(); + // remove custom.json, if present + QString customPath = PathCombine(m_instance->instanceRoot(), "custom.json"); + if(QFile::exists(customPath)) + { + if(!QFile::remove(customPath)) + { + endResetModel(); + return false; + } + } + // remove version.json, if present + QString versionPath = PathCombine(m_instance->instanceRoot(), "version.json"); + if(QFile::exists(versionPath)) + { + if(!QFile::remove(versionPath)) + { + endResetModel(); + return false; + } + } + endResetModel(); + return true; +} + +QList<std::shared_ptr<OneSixLibrary> > InstanceVersion::getActiveNormalLibs() +{ + QList<std::shared_ptr<OneSixLibrary> > output; + for (auto lib : libraries) + { + if (lib->isActive() && !lib->isNative()) + { + output.append(lib); + } + } + return output; +} +QList<std::shared_ptr<OneSixLibrary> > InstanceVersion::getActiveNativeLibs() +{ + QList<std::shared_ptr<OneSixLibrary> > output; + for (auto lib : libraries) + { + if (lib->isActive() && lib->isNative()) + { + output.append(lib); + } + } + return output; +} + +std::shared_ptr<InstanceVersion> InstanceVersion::fromJson(const QJsonObject &obj) +{ + std::shared_ptr<InstanceVersion> version(new InstanceVersion(0)); + try + { + VersionBuilder::readJsonAndApplyToVersion(version.get(), obj); + } + catch(MMCError & err) + { + return 0; + } + return version; +} + +QVariant InstanceVersion::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= VersionPatches.size()) + return QVariant(); + + if (role == Qt::DisplayRole) + { + switch (column) + { + case 0: + return VersionPatches.at(row)->getPatchName(); + case 1: + return VersionPatches.at(row)->getPatchVersion(); + default: + return QVariant(); + } + } + return QVariant(); +} +QVariant InstanceVersion::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) + { + if (role == Qt::DisplayRole) + { + switch (section) + { + case 0: + return tr("Name"); + case 1: + return tr("Version"); + default: + return QVariant(); + } + } + } + return QVariant(); +} +Qt::ItemFlags InstanceVersion::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +int InstanceVersion::rowCount(const QModelIndex &parent) const +{ + return VersionPatches.size(); +} + +int InstanceVersion::columnCount(const QModelIndex &parent) const +{ + return 2; +} + +void InstanceVersion::saveCurrentOrder() const +{ + PatchOrder order; + for(auto item: VersionPatches) + { + if(!item->isMoveable()) + continue; + order.append(item->getPatchID()); + } + VersionBuilder::writeOverrideOrders(m_instance, order); +} + +void InstanceVersion::move(const int index, const MoveDirection direction) +{ + int theirIndex; + if (direction == MoveUp) + { + theirIndex = index - 1; + } + else + { + theirIndex = index + 1; + } + + if (index < 0 || index >= VersionPatches.size()) + return; + if (theirIndex >= rowCount()) + theirIndex = rowCount() - 1; + if (theirIndex == -1) + theirIndex = rowCount() - 1; + if (index == theirIndex) + return; + int togap = theirIndex > index ? theirIndex + 1 : theirIndex; + + auto from = versionPatch(index); + auto to = versionPatch(theirIndex); + + if (!from || !to || !to->isMoveable() || !from->isMoveable()) + { + return; + } + beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); + VersionPatches.swap(index, theirIndex); + endMoveRows(); + saveCurrentOrder(); + reapply(); +} +void InstanceVersion::resetOrder() +{ + QDir(m_instance->instanceRoot()).remove("order.json"); + reload(m_externalPatches); +} + +void InstanceVersion::reapply(const bool alreadyReseting) +{ + clear(); + for(auto file: VersionPatches) + { + file->applyTo(this); + } + finalize(); +} + +void InstanceVersion::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"; + } + auto finalizeArguments = [&]( QString & minecraftArguments, const QString & processArguments ) -> void + { + if (!minecraftArguments.isEmpty()) + return; + QString toCompare = processArguments.toLower(); + if (toCompare == "legacy") + { + minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + minecraftArguments = "--username ${auth_player_name} " + "--session ${auth_session} " + "--version ${profile_name}"; + } + }; + finalizeArguments(vanillaMinecraftArguments, vanillaProcessArguments); + finalizeArguments(minecraftArguments, processArguments); +} + +void InstanceVersion::installJarMods(QStringList selectedFiles) +{ + for(auto filename: selectedFiles) + { + installJarModByFilename(filename); + } +} + +void InstanceVersion::installJarModByFilename(QString filepath) +{ + QString patchDir = PathCombine(m_instance->instanceRoot(), "patches"); + if(!ensureFolderPathExists(patchDir)) + { + // THROW... + return; + } + + if (!ensureFolderPathExists(m_instance->jarModsDir())) + { + // THROW... + return; + } + + QFileInfo sourceInfo(filepath); + auto uuid = QUuid::createUuid(); + QString id = uuid.toString().remove('{').remove('}'); + QString target_filename = id + ".jar"; + QString target_id = "org.multimc.jarmod." + id; + QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; + QString finalPath = PathCombine(m_instance->jarModsDir(), target_filename); + + QFileInfo targetInfo(finalPath); + if(targetInfo.exists()) + { + // THROW + return; + } + + if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) + { + // THROW + return; + } + + auto f = std::make_shared<VersionFile>(); + auto jarMod = std::make_shared<Jarmod>(); + jarMod->name = target_filename; + f->jarMods.append(jarMod); + f->name = target_name; + f->fileId = target_id; + f->order = getFreeOrderNumber(); + + QFile file(PathCombine(patchDir, target_id + ".json")); + if (!file.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return; + // THROW + } + file.write(f->toJson(true).toJson()); + file.close(); + int index = VersionPatches.size(); + beginInsertRows(QModelIndex(), index, index); + VersionPatches.append(f); + endInsertRows(); + saveCurrentOrder(); +} + +int InstanceVersion::getFreeOrderNumber() +{ + int largest = 100; + // yes, I do realize this is dumb. The order thing itself is dumb. and to be removed next. + for(auto thing: VersionPatches) + { + int order = thing->getOrder(); + if(order > largest) + largest = order; + } + return largest + 1; +} diff --git a/logic/VersionFinal.h b/logic/minecraft/InstanceVersion.h index 41fd23bd..6b69ab47 100644 --- a/logic/VersionFinal.h +++ b/logic/minecraft/InstanceVersion.h @@ -23,14 +23,15 @@ #include "OneSixLibrary.h" #include "VersionFile.h" +#include "JarMod.h" class OneSixInstance; -class VersionFinal : public QAbstractListModel +class InstanceVersion : public QAbstractListModel { Q_OBJECT public: - explicit VersionFinal(OneSixInstance *instance, QObject *parent = 0); + explicit InstanceVersion(OneSixInstance *instance, QObject *parent = 0); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; @@ -38,22 +39,32 @@ public: virtual int columnCount(const QModelIndex &parent) const; virtual Qt::ItemFlags flags(const QModelIndex &index) const; - void reload(const bool onlyVanilla = false, const QStringList &external = QStringList()); + void reload(const QStringList &external = QStringList()); void clear(); bool canRemove(const int index) const; QString versionFileId(const int index) const; - // does this instance have an all overriding custom.json - bool isCustom(); - // remove custom.json - bool revertToBase(); - // does this instance have an FTB pack patch file? + // is this version unmodded vanilla minecraft? + bool isVanilla(); + // remove any customizations on top of vanilla + bool revertToVanilla(); + + // does this version consist of obsolete files? + bool hasDeprecatedVersionFiles(); + // remove obsolete files + bool removeDeprecatedVersionFiles(); + + // does this version have an FTB pack patch file? bool hasFtbPack(); // remove FTB pack bool removeFtbPack(); + // does this version have any jar mods? + bool hasJarMods(); + void installJarMods(QStringList selectedFiles); + void installJarModByFilename(QString filepath); enum MoveDirection { MoveUp, MoveDown }; void move(const int index, const MoveDirection direction); @@ -72,16 +83,24 @@ public: QList<std::shared_ptr<OneSixLibrary>> getActiveNormalLibs(); QList<std::shared_ptr<OneSixLibrary>> getActiveNativeLibs(); - static std::shared_ptr<VersionFinal> fromJson(const QJsonObject &obj); + static std::shared_ptr<InstanceVersion> fromJson(const QJsonObject &obj); +private: + bool preremove(VersionPatchPtr patch); + // data members public: /// the ID - determines which jar to use! ACTUALLY IMPORTANT! QString id; - /// Last updated time - as a string - QString time; - /// Release time - as a string - QString releaseTime; + + /// the time this version was actually released by Mojang, as string and as QDateTime + QString m_releaseTimeString; + QDateTime m_releaseTime; + + /// the time this version was last updated by Mojang, as string and as QDateTime + QString m_updateTimeString; + QDateTime m_updateTime; + /// Release type - "release" or "snapshot" QString type; /// Assets type - "legacy" or a version ID @@ -91,6 +110,8 @@ public: * ex: "username_session_version" */ QString processArguments; + /// Same as above, but only for vanilla + QString vanillaProcessArguments; /** * arguments that should be used for launching minecraft * @@ -98,6 +119,8 @@ public: * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" */ QString minecraftArguments; + /// Same as above, but only for vanilla + QString vanillaMinecraftArguments; /** * the minimum launcher version required by this version ... current is 4 (at point of * writing) @@ -111,9 +134,22 @@ public: * The main class to load first */ QString mainClass; - + /** + * The applet class, for some very old minecraft releases + */ + QString appletClass; + /// the list of libs - both active and inactive, native and java QList<std::shared_ptr<OneSixLibrary>> libraries; + + /// same, but only vanilla. + QList<std::shared_ptr<OneSixLibrary>> vanillaLibraries; + + /// traits, collected from all the version files (version files can only add) + QSet<QString> traits; + + /// A list of jar mods. version files can add those. + QList<JarmodPtr> jarMods; /* FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. @@ -136,10 +172,13 @@ public: */ // QList<Rule> rules; - QList<VersionFilePtr> versionFiles; - VersionFilePtr versionFile(const QString &id); + QList<VersionPatchPtr> VersionPatches; + VersionPatchPtr versionPatch(const QString &id); + VersionPatchPtr versionPatch(int index); private: + QStringList m_externalPatches; OneSixInstance *m_instance; - QMap<QString, int> getExistingOrder() const; + void saveCurrentOrder() const; + int getFreeOrderNumber(); }; diff --git a/logic/minecraft/JarMod.cpp b/logic/minecraft/JarMod.cpp new file mode 100644 index 00000000..18a9411c --- /dev/null +++ b/logic/minecraft/JarMod.cpp @@ -0,0 +1,56 @@ +#include "JarMod.h" +#include "logic/MMCJson.h" +using namespace MMCJson; + +JarmodPtr Jarmod::fromJson(const QJsonObject &libObj, const QString &filename) +{ + JarmodPtr out(new Jarmod()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + + "contains a jarmod that doesn't have a 'name' field"); + } + out->name = libObj.value("name").toString(); + + auto readString = [libObj, filename](const QString & key, QString & variable) + { + if (libObj.contains(key)) + { + QJsonValue val = libObj.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("url", out->baseurl); + readString("MMC-hint", out->hint); + readString("MMC-absoluteUrl", out->absoluteUrl); + if(!out->baseurl.isEmpty() && out->absoluteUrl.isEmpty()) + { + out->absoluteUrl = out->baseurl + out->name; + } + return out; +} + +QJsonObject Jarmod::toJson() +{ + QJsonObject out; + writeString(out, "name", name); + writeString(out, "url", baseurl); + writeString(out, "MMC-absoluteUrl", absoluteUrl); + writeString(out, "MMC-hint", hint); + return out; +} + +QString Jarmod::url() +{ + if(!absoluteUrl.isEmpty()) + return absoluteUrl; + else return baseurl + name; +} diff --git a/logic/minecraft/JarMod.h b/logic/minecraft/JarMod.h new file mode 100644 index 00000000..c438dbcd --- /dev/null +++ b/logic/minecraft/JarMod.h @@ -0,0 +1,18 @@ +#pragma once +#include <QString> +#include <QJsonObject> +#include <memory> +class Jarmod; +typedef std::shared_ptr<Jarmod> JarmodPtr; +class Jarmod +{ +public: /* methods */ + static JarmodPtr fromJson(const QJsonObject &libObj, const QString &filename); + QJsonObject toJson(); + QString url(); +public: /* data */ + QString name; + QString baseurl; + QString hint; + QString absoluteUrl; +}; diff --git a/logic/minecraft/MinecraftVersion.cpp b/logic/minecraft/MinecraftVersion.cpp new file mode 100644 index 00000000..488a180a --- /dev/null +++ b/logic/minecraft/MinecraftVersion.cpp @@ -0,0 +1,143 @@ +#include "MinecraftVersion.h" +#include "InstanceVersion.h" +#include "VersionBuildError.h" +#include "VersionBuilder.h" + +bool MinecraftVersion::usesLegacyLauncher() +{ + return m_traits.contains("legacyLaunch") || m_traits.contains("aplhaLaunch"); +} + +QString MinecraftVersion::descriptor() +{ + return m_descriptor; +} + +QString MinecraftVersion::name() +{ + return m_name; +} + +QString MinecraftVersion::typeString() const +{ + if(m_type == "snapshot") + { + return QObject::tr("Snapshot"); + } + else if (m_type == "release") + { + return QObject::tr("Regular release"); + } + else if (m_type == "old_alpha") + { + return QObject::tr("Alpha"); + } + else if (m_type == "old_beta") + { + return QObject::tr("Beta"); + } + else + { + return QString(); + } +} + +bool MinecraftVersion::hasJarMods() +{ + return false; +} + +bool MinecraftVersion::isMinecraftVersion() +{ + return true; +} + +// 1. assume the local file is good. load, check. If it's good, apply. +// 2. if discrepancies are found, fall out and fail (impossible to apply incomplete version). +void MinecraftVersion::applyFileTo(InstanceVersion *version) +{ + QFileInfo versionFile(QString("versions/%1/%1.dat").arg(m_descriptor)); + + auto versionObj = VersionBuilder::parseBinaryJsonFile(versionFile); + versionObj->applyTo(version); +} + +void MinecraftVersion::applyTo(InstanceVersion *version) +{ + // do we have this one cached? + if (m_versionSource == Local) + { + applyFileTo(version); + return; + } + // if not builtin, do not proceed any further. + if (m_versionSource != Builtin) + { + throw VersionIncomplete(QObject::tr( + "Minecraft version %1 could not be applied: version files are missing.").arg(m_descriptor)); + } + if (!m_descriptor.isNull()) + { + version->id = m_descriptor; + } + if (!m_mainClass.isNull()) + { + version->mainClass = m_mainClass; + } + if (!m_appletClass.isNull()) + { + version->appletClass = m_appletClass; + } + if (!m_processArguments.isNull()) + { + version->vanillaProcessArguments = m_processArguments; + version->processArguments = m_processArguments; + } + if (!m_type.isNull()) + { + version->type = m_type; + } + if (!m_releaseTimeString.isNull()) + { + version->m_releaseTimeString = m_releaseTimeString; + version->m_releaseTime = m_releaseTime; + } + if (!m_updateTimeString.isNull()) + { + version->m_updateTimeString = m_updateTimeString; + version->m_updateTime = m_updateTime; + } + version->traits.unite(m_traits); +} + +int MinecraftVersion::getOrder() +{ + return order; +} + +void MinecraftVersion::setOrder(int order) +{ + this->order = order; +} + +QList<JarmodPtr> MinecraftVersion::getJarMods() +{ + return QList<JarmodPtr>(); +} + +QString MinecraftVersion::getPatchName() +{ + return "Minecraft"; +} +QString MinecraftVersion::getPatchVersion() +{ + return m_descriptor; +} +QString MinecraftVersion::getPatchID() +{ + return "net.minecraft"; +} +QString MinecraftVersion::getPatchFilename() +{ + return QString(); +} diff --git a/logic/minecraft/MinecraftVersion.h b/logic/minecraft/MinecraftVersion.h new file mode 100644 index 00000000..82073a97 --- /dev/null +++ b/logic/minecraft/MinecraftVersion.h @@ -0,0 +1,103 @@ +/* Copyright 2013 Andrew Okin + * + * 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 <QStringList> +#include <QSet> +#include <QDateTime> + +#include "logic/BaseVersion.h" +#include "VersionPatch.h" +#include "VersionFile.h" +#include "VersionSource.h" + +class InstanceVersion; +class MinecraftVersion; +typedef std::shared_ptr<MinecraftVersion> MinecraftVersionPtr; + +class MinecraftVersion : public BaseVersion, public VersionPatch +{ +public: /* methods */ + bool usesLegacyLauncher(); + virtual QString descriptor() override; + virtual QString name() override; + virtual QString typeString() const override; + virtual bool hasJarMods() override; + virtual bool isMinecraftVersion() override; + virtual void applyTo(InstanceVersion *version) override; + virtual int getOrder(); + virtual void setOrder(int order); + virtual QList<JarmodPtr> getJarMods() override; + virtual QString getPatchID() override; + virtual QString getPatchVersion() override; + virtual QString getPatchName() override; + virtual QString getPatchFilename() override; + bool needsUpdate() + { + return m_versionSource == Remote; + } + bool hasUpdate() + { + return m_versionSource == Remote || (m_versionSource == Local && upstreamUpdate); + } + +private: /* methods */ + void applyFileTo(InstanceVersion *version); + +public: /* data */ + /// The URL that this version will be downloaded from. maybe. + QString download_url; + + VersionSource m_versionSource = Builtin; + + /// the human readable version name + QString m_name; + + /// the version ID. + QString m_descriptor; + + /// version traits. added by MultiMC + QSet<QString> m_traits; + + /// The main class this version uses (if any, can be empty). + QString m_mainClass; + + /// The applet class this version uses (if any, can be empty). + QString m_appletClass; + + /// The process arguments used by this version + QString m_processArguments; + + /// The type of this release + QString m_type; + + /// the time this version was actually released by Mojang, as string and as QDateTime + QString m_releaseTimeString; + QDateTime m_releaseTime; + + /// the time this version was last updated by Mojang, as string and as QDateTime + QString m_updateTimeString; + QDateTime m_updateTime; + + /// MD5 hash of the minecraft jar + QString m_jarChecksum; + + /// order of this file... default = -2 + int order = -2; + + /// an update available from Mojang + MinecraftVersionPtr upstreamUpdate; +}; diff --git a/logic/minecraft/MinecraftVersionList.cpp b/logic/minecraft/MinecraftVersionList.cpp new file mode 100644 index 00000000..3aa1ac01 --- /dev/null +++ b/logic/minecraft/MinecraftVersionList.cpp @@ -0,0 +1,602 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <QtXml> +#include "logic/MMCJson.h" +#include <QtAlgorithms> +#include <QtNetwork> + +#include "MultiMC.h" +#include "MMCError.h" + +#include "MinecraftVersionList.h" +#include "logic/net/URLConstants.h" + +#include "ParseUtils.h" +#include "VersionBuilder.h" +#include <logic/VersionFilterData.h> +#include <pathutils.h> + +static const char * localVersionCache = "versions/versions.dat"; + +class ListLoadError : public MMCError +{ +public: + ListLoadError(QString cause) : MMCError(cause) {}; + virtual ~ListLoadError() noexcept + { + } +}; + +MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) +{ + loadBuiltinList(); + loadCachedList(); +} + +Task *MinecraftVersionList::getLoadTask() +{ + return new MCVListLoadTask(this); +} + +bool MinecraftVersionList::isLoaded() +{ + return m_loaded; +} + +const BaseVersionPtr MinecraftVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +int MinecraftVersionList::count() const +{ + return m_vlist.count(); +} + +static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) +{ + auto left = std::dynamic_pointer_cast<MinecraftVersion>(first); + auto right = std::dynamic_pointer_cast<MinecraftVersion>(second); + return left->m_releaseTime > right->m_releaseTime; +} + +void MinecraftVersionList::sortInternal() +{ + qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); +} + +void MinecraftVersionList::loadCachedList() +{ + QFile localIndex(localVersionCache); + if (!localIndex.exists()) + { + return; + } + if (!localIndex.open(QIODevice::ReadOnly)) + { + // FIXME: this is actually a very bad thing! How do we deal with this? + QLOG_ERROR() << "The minecraft version cache can't be read."; + return; + } + auto data = localIndex.readAll(); + try + { + localIndex.close(); + QJsonDocument jsonDoc = QJsonDocument::fromBinaryData(data); + if (jsonDoc.isNull()) + { + throw ListLoadError(tr("Error reading the version list.")); + } + loadMojangList(jsonDoc, Local); + } + catch (MMCError &e) + { + // the cache has gone bad for some reason... flush it. + QLOG_ERROR() << "The minecraft version cache is corrupted. Flushing cache."; + localIndex.remove(); + return; + } + m_hasLocalIndex = true; +} + +void MinecraftVersionList::loadBuiltinList() +{ + QLOG_INFO() << "Loading builtin version list."; + // grab the version list data from internal resources. + QResource versionList(":/versions/minecraft.json"); + QFile filez(versionList.absoluteFilePath()); + filez.open(QIODevice::ReadOnly); + auto data = filez.readAll(); + + // parse the data as json + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + QJsonObject root = jsonDoc.object(); + + // parse all the versions + for (const auto version : MMCJson::ensureArray(root.value("versions"))) + { + QJsonObject versionObj = version.toObject(); + QString versionID = versionObj.value("id").toString(""); + QString versionTypeStr = versionObj.value("type").toString(""); + if (versionID.isEmpty() || versionTypeStr.isEmpty()) + { + QLOG_ERROR() << "Parsed version is missing ID or type"; + continue; + } + + if (g_VersionFilterData.legacyBlacklist.contains(versionID)) + { + QLOG_WARN() << "Blacklisted legacy version ignored: " << versionID; + continue; + } + + // Now, we construct the version object and add it to the list. + std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); + mcVersion->m_name = mcVersion->m_descriptor = versionID; + + // Parse the timestamp. + if (!parse_timestamp(versionObj.value("releaseTime").toString(""), + mcVersion->m_releaseTimeString, mcVersion->m_releaseTime)) + { + QLOG_ERROR() << "Error while parsing version" << versionID + << ": invalid version timestamp"; + continue; + } + + // Get the download URL. + mcVersion->download_url = + "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; + + mcVersion->m_versionSource = Builtin; + mcVersion->m_type = versionTypeStr; + mcVersion->m_appletClass = versionObj.value("appletClass").toString(""); + mcVersion->m_mainClass = versionObj.value("mainClass").toString(""); + mcVersion->m_jarChecksum = versionObj.value("checksum").toString(""); + mcVersion->m_processArguments = versionObj.value("processArguments").toString("legacy"); + if (versionObj.contains("+traits")) + { + for (auto traitVal : MMCJson::ensureArray(versionObj.value("+traits"))) + { + mcVersion->m_traits.insert(MMCJson::ensureString(traitVal)); + } + } + m_lookup[versionID] = mcVersion; + m_vlist.append(mcVersion); + } +} + +void MinecraftVersionList::loadMojangList(QJsonDocument jsonDoc, VersionSource source) +{ + QLOG_INFO() << "Loading" << ((source == Remote) ? "remote" : "local") << "version list."; + + if (!jsonDoc.isObject()) + { + throw ListLoadError(tr("Error parsing version list JSON: jsonDoc is not an object")); + } + + QJsonObject root = jsonDoc.object(); + + try + { + QJsonObject latest = MMCJson::ensureObject(root.value("latest")); + m_latestReleaseID = MMCJson::ensureString(latest.value("release")); + m_latestSnapshotID = MMCJson::ensureString(latest.value("snapshot")); + } + catch (MMCError &err) + { + QLOG_ERROR() + << tr("Error parsing version list JSON: couldn't determine latest versions"); + } + + // Now, get the array of versions. + if (!root.value("versions").isArray()) + { + throw ListLoadError(tr("Error parsing version list JSON: version list object is " + "missing 'versions' array")); + } + QJsonArray versions = root.value("versions").toArray(); + + QList<BaseVersionPtr> tempList; + for (auto version : versions) + { + // Load the version info. + if (!version.isObject()) + { + QLOG_ERROR() << "Error while parsing version list : invalid JSON structure"; + continue; + } + + QJsonObject versionObj = version.toObject(); + QString versionID = versionObj.value("id").toString(""); + if (versionID.isEmpty()) + { + QLOG_ERROR() << "Error while parsing version : version ID is missing"; + continue; + } + + if (g_VersionFilterData.legacyBlacklist.contains(versionID)) + { + QLOG_WARN() << "Blacklisted legacy version ignored: " << versionID; + continue; + } + + // Now, we construct the version object and add it to the list. + std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); + mcVersion->m_name = mcVersion->m_descriptor = versionID; + + if (!parse_timestamp(versionObj.value("releaseTime").toString(""), + mcVersion->m_releaseTimeString, mcVersion->m_releaseTime)) + { + QLOG_ERROR() << "Error while parsing version" << versionID + << ": invalid release timestamp"; + continue; + } + if (!parse_timestamp(versionObj.value("time").toString(""), + mcVersion->m_updateTimeString, mcVersion->m_updateTime)) + { + QLOG_ERROR() << "Error while parsing version" << versionID + << ": invalid update timestamp"; + continue; + } + + if (mcVersion->m_releaseTime < g_VersionFilterData.legacyCutoffDate) + { + continue; + } + + // depends on where we load the version from -- network request or local file? + mcVersion->m_versionSource = source; + + QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; + mcVersion->download_url = dlUrl; + QString versionTypeStr = versionObj.value("type").toString(""); + if (versionTypeStr.isEmpty()) + { + // FIXME: log this somewhere + continue; + } + // OneSix or Legacy. use filter to determine type + if (versionTypeStr == "release") + { + } + else if (versionTypeStr == "snapshot") // It's a snapshot... yay + { + } + else if (versionTypeStr == "old_alpha") + { + } + else if (versionTypeStr == "old_beta") + { + } + else + { + // FIXME: log this somewhere + continue; + } + mcVersion->m_type = versionTypeStr; + tempList.append(mcVersion); + } + updateListData(tempList); + if(source == Remote) + { + m_loaded = true; + } +} + +void MinecraftVersionList::sort() +{ + beginResetModel(); + sortInternal(); + endResetModel(); +} + +BaseVersionPtr MinecraftVersionList::getLatestStable() const +{ + if(m_lookup.contains(m_latestReleaseID)) + return m_lookup[m_latestReleaseID]; + return BaseVersionPtr(); +} + +void MinecraftVersionList::updateListData(QList<BaseVersionPtr> versions) +{ + beginResetModel(); + for (auto version : versions) + { + auto descr = version->descriptor(); + + if (!m_lookup.contains(descr)) + { + m_lookup[version->descriptor()] = version; + m_vlist.append(version); + continue; + } + auto orig = std::dynamic_pointer_cast<MinecraftVersion>(m_lookup[descr]); + auto added = std::dynamic_pointer_cast<MinecraftVersion>(version); + // updateListData is called after Mojang list loads. those can be local or remote + // remote comes always after local + // any other options are ignored + if (orig->m_versionSource != Local || added->m_versionSource != Remote) + { + continue; + } + // is it actually an update? + if (orig->m_updateTime >= added->m_updateTime) + { + // nope. + continue; + } + // alright, it's an update. put it inside the original, for further processing. + orig->upstreamUpdate = added; + } + sortInternal(); + endResetModel(); +} + +inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) +{ + QDomNodeList elementList = parent.elementsByTagName(tagname); + if (elementList.count()) + return elementList.at(0).toElement(); + else + return QDomElement(); +} + +MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) +{ + m_list = vlist; + m_currentStable = NULL; + vlistReply = nullptr; +} + +void MCVListLoadTask::executeTask() +{ + setStatus(tr("Loading instance version list...")); + auto worker = MMC->qnam(); + vlistReply = worker->get(QNetworkRequest( + QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json"))); + connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); +} + +void MCVListLoadTask::list_downloaded() +{ + if (vlistReply->error() != QNetworkReply::NoError) + { + vlistReply->deleteLater(); + emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString()); + return; + } + + auto data = vlistReply->readAll(); + vlistReply->deleteLater(); + try + { + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + throw ListLoadError( + tr("Error parsing version list JSON: %1").arg(jsonError.errorString())); + } + m_list->loadMojangList(jsonDoc, Remote); + } + catch (MMCError &e) + { + emitFailed(e.cause()); + return; + } + + emitSucceeded(); + return; +} + +MCVListVersionUpdateTask::MCVListVersionUpdateTask(MinecraftVersionList *vlist, + QString updatedVersion) + : Task() +{ + m_list = vlist; + versionToUpdate = updatedVersion; +} + +void MCVListVersionUpdateTask::executeTask() +{ + QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionToUpdate + "/" + + versionToUpdate + ".json"; + auto job = new NetJob("Version index"); + job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); + specificVersionDownloadJob.reset(job); + connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(json_downloaded())); + connect(specificVersionDownloadJob.get(), SIGNAL(failed(QString)), SIGNAL(failed(QString))); + connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + specificVersionDownloadJob->start(); +} + +void MCVListVersionUpdateTask::json_downloaded() +{ + NetActionPtr DlJob = specificVersionDownloadJob->first(); + auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data; + specificVersionDownloadJob.reset(); + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed(tr("The download version file is not valid.")); + return; + } + VersionFilePtr file; + try + { + file = VersionFile::fromJson(jsonDoc, "net.minecraft.json", false); + } + catch (MMCError &e) + { + emitFailed(tr("Couldn't process version file: %1").arg(e.cause())); + return; + } + QList<RawLibraryPtr> filteredLibs; + QList<RawLibraryPtr> lwjglLibs; + QSet<QString> lwjglFilter = { + "net.java.jinput:jinput", "net.java.jinput:jinput-platform", + "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", + "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; + for (auto lib : file->overwriteLibs) + { + if (lwjglFilter.contains(lib->fullname())) + { + lwjglLibs.append(lib); + } + else + { + filteredLibs.append(lib); + } + } + file->overwriteLibs = filteredLibs; + + // TODO: recognize and add LWJGL versions here. + + file->fileId = "net.minecraft"; + + // now dump the file to disk + auto doc = file->toJson(false); + auto newdata = doc.toBinaryData(); + QLOG_INFO() << newdata; + QString targetPath = "versions/" + versionToUpdate + "/" + versionToUpdate + ".dat"; + ensureFilePathExists(targetPath); + QSaveFile vfile1(targetPath); + if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) + { + emitFailed(tr("Can't open %1 for writing.").arg(targetPath)); + return; + } + qint64 actual = 0; + if ((actual = vfile1.write(newdata)) != newdata.size()) + { + emitFailed(tr("Failed to write into %1. Written %2 out of %3.") + .arg(targetPath) + .arg(actual) + .arg(newdata.size())); + return; + } + if (!vfile1.commit()) + { + emitFailed(tr("Can't commit changes to %1").arg(targetPath)); + return; + } + + m_list->finalizeUpdate(versionToUpdate); + emitSucceeded(); +} + +std::shared_ptr<Task> MinecraftVersionList::createUpdateTask(QString version) +{ + return std::shared_ptr<Task>(new MCVListVersionUpdateTask(this, version)); +} + +void MinecraftVersionList::saveCachedList() +{ + // FIXME: throw. + if (!ensureFilePathExists(localVersionCache)) + return; + QSaveFile tfile(localVersionCache); + if (!tfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) + return; + QJsonObject toplevel; + QJsonArray entriesArr; + for (auto version : m_vlist) + { + auto mcversion = std::dynamic_pointer_cast<MinecraftVersion>(version); + // do not save the remote versions. + if (mcversion->m_versionSource != Local) + continue; + QJsonObject entryObj; + + entryObj.insert("id", mcversion->descriptor()); + entryObj.insert("time", mcversion->m_updateTimeString); + entryObj.insert("releaseTime", mcversion->m_releaseTimeString); + entryObj.insert("type", mcversion->m_type); + entriesArr.append(entryObj); + } + toplevel.insert("versions", entriesArr); + + { + bool someLatest = false; + QJsonObject latestObj; + if(!m_latestReleaseID.isNull()) + { + latestObj.insert("release", m_latestReleaseID); + someLatest = true; + } + if(!m_latestSnapshotID.isNull()) + { + latestObj.insert("snapshot", m_latestSnapshotID); + someLatest = true; + } + if(someLatest) + { + toplevel.insert("latest", latestObj); + } + } + + QJsonDocument doc(toplevel); + QByteArray jsonData = doc.toBinaryData(); + qint64 result = tfile.write(jsonData); + if (result == -1) + return; + if (result != jsonData.size()) + return; + tfile.commit(); +} + +void MinecraftVersionList::finalizeUpdate(QString version) +{ + int idx = -1; + for (int i = 0; i < m_vlist.size(); i++) + { + if (version == m_vlist[i]->descriptor()) + { + idx = i; + break; + } + } + if (idx == -1) + { + return; + } + + auto updatedVersion = std::dynamic_pointer_cast<MinecraftVersion>(m_vlist[idx]); + + if (updatedVersion->m_versionSource == Builtin) + return; + + if (updatedVersion->upstreamUpdate) + { + auto updatedWith = updatedVersion->upstreamUpdate; + updatedWith->m_versionSource = Local; + m_vlist[idx] = updatedWith; + m_lookup[version] = updatedWith; + } + else + { + updatedVersion->m_versionSource = Local; + } + + dataChanged(index(idx), index(idx)); + + saveCachedList(); +} diff --git a/logic/lists/MinecraftVersionList.h b/logic/minecraft/MinecraftVersionList.h index 167f4d11..4753ce05 100644 --- a/logic/lists/MinecraftVersionList.h +++ b/logic/minecraft/MinecraftVersionList.h @@ -19,11 +19,13 @@ #include <QList> #include <QSet> -#include "BaseVersionList.h" +#include "logic/BaseVersionList.h" #include "logic/tasks/Task.h" -#include "logic/MinecraftVersion.h" +#include "logic/minecraft/MinecraftVersion.h" +#include <logic/net/NetJob.h> class MCVListLoadTask; +class MCVListVersionUpdateTask; class QNetworkReply; class MinecraftVersionList : public BaseVersionList @@ -31,11 +33,19 @@ class MinecraftVersionList : public BaseVersionList Q_OBJECT private: void sortInternal(); + void loadBuiltinList(); + void loadMojangList(QJsonDocument jsonDoc, VersionSource source); + void loadCachedList(); + void saveCachedList(); + void finalizeUpdate(QString version); public: friend class MCVListLoadTask; + friend class MCVListVersionUpdateTask; explicit MinecraftVersionList(QObject *parent = 0); + std::shared_ptr<Task> createUpdateTask(QString version); + virtual Task *getLoadTask(); virtual bool isLoaded(); virtual const BaseVersionPtr at(int i) const; @@ -46,8 +56,12 @@ public: protected: QList<BaseVersionPtr> m_vlist; + QMap<QString, BaseVersionPtr> m_lookup; bool m_loaded = false; + bool m_hasLocalIndex = false; + QString m_latestReleaseID = "INVALID"; + QString m_latestSnapshotID = "INVALID"; protected slots: @@ -60,9 +74,9 @@ class MCVListLoadTask : public Task public: explicit MCVListLoadTask(MinecraftVersionList *vlist); - ~MCVListLoadTask(); + virtual ~MCVListLoadTask() override{}; - virtual void executeTask(); + virtual void executeTask() override; protected slots: @@ -72,5 +86,23 @@ protected: QNetworkReply *vlistReply; MinecraftVersionList *m_list; MinecraftVersion *m_currentStable; - QSet<QString> legacyWhitelist; +}; + +class MCVListVersionUpdateTask : public Task +{ + Q_OBJECT + +public: + explicit MCVListVersionUpdateTask(MinecraftVersionList *vlist, QString updatedVersion); + virtual ~MCVListVersionUpdateTask() override{}; + virtual void executeTask() override; + +protected +slots: + void json_downloaded(); + +protected: + NetJobPtr specificVersionDownloadJob; + QString versionToUpdate; + MinecraftVersionList *m_list; }; diff --git a/logic/OneSixLibrary.cpp b/logic/minecraft/OneSixLibrary.cpp index 45fa169e..7f69d9f8 100644 --- a/logic/OneSixLibrary.cpp +++ b/logic/minecraft/OneSixLibrary.cpp @@ -23,6 +23,23 @@ #include <JlCompress.h> #include "logger/QsLog.h" +OneSixLibrary::OneSixLibrary(RawLibraryPtr base) +{ + m_name = base->m_name; + m_base_url = base->m_base_url; + m_hint = base->m_hint; + m_absolute_url = base->m_absolute_url; + extract_excludes = base->extract_excludes; + m_native_suffixes = base->m_native_suffixes; + m_rules = base->m_rules; + finalize(); +} + +OneSixLibraryPtr OneSixLibrary::fromRawLibrary(RawLibraryPtr lib) +{ + return OneSixLibraryPtr(new OneSixLibrary(lib)); +} + void OneSixLibrary::finalize() { QStringList parts = m_name.split(':'); @@ -30,7 +47,7 @@ void OneSixLibrary::finalize() relative.replace('.', '/'); relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; - if (!m_is_native) + if (!isNative()) relative += ".jar"; else { @@ -65,7 +82,7 @@ void OneSixLibrary::finalize() } m_is_active = (result == Allow); } - if (m_is_native) + if (isNative()) { m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); m_decenttype = "Native"; @@ -84,13 +101,8 @@ void OneSixLibrary::setBaseUrl(const QString &base_url) { m_base_url = base_url; } -void OneSixLibrary::setIsNative() -{ - m_is_native = true; -} void OneSixLibrary::addNative(OpSys os, const QString &suffix) { - m_is_native = true; m_native_suffixes[os] = suffix; } void OneSixLibrary::clearSuffixes() @@ -105,10 +117,6 @@ bool OneSixLibrary::isActive() const { return m_is_active; } -bool OneSixLibrary::isNative() const -{ - return m_is_native; -} QString OneSixLibrary::downloadUrl() const { if (m_absolute_url.size()) @@ -223,52 +231,3 @@ bool OneSixLibrary::extractTo(QString target_dir) } return true; } - -QJsonObject OneSixLibrary::toJson() -{ - QJsonObject libRoot; - libRoot.insert("name", m_name); - if (m_absolute_url.size()) - libRoot.insert("MMC-absoluteUrl", m_absolute_url); - if (m_hint.size()) - libRoot.insert("MMC-hint", m_hint); - if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::LIBRARY_BASE && !m_base_url.isEmpty()) - { - libRoot.insert("url", m_base_url); - } - if (isNative() && m_native_suffixes.size()) - { - QJsonObject nativeList; - auto iter = m_native_suffixes.begin(); - while (iter != m_native_suffixes.end()) - { - nativeList.insert(OpSys_toString(iter.key()), iter.value()); - iter++; - } - libRoot.insert("natives", nativeList); - } - if (isNative() && extract_excludes.size()) - { - QJsonArray excludes; - QJsonObject extract; - for (auto exclude : extract_excludes) - { - excludes.append(exclude); - } - extract.insert("exclude", excludes); - libRoot.insert("extract", extract); - } - if (m_rules.size()) - { - QJsonArray allRules; - for (auto &rule : m_rules) - { - QJsonObject ruleObj = rule->toJson(); - allRules.append(ruleObj); - } - libRoot.insert("rules", allRules); - } - return libRoot; -} diff --git a/logic/OneSixLibrary.h b/logic/minecraft/OneSixLibrary.h index 61d4c8e2..3d38985b 100644 --- a/logic/OneSixLibrary.h +++ b/logic/minecraft/OneSixLibrary.h @@ -23,28 +23,17 @@ #include <memory> #include "logic/net/URLConstants.h" -#include "OpSys.h" +#include "logic/minecraft/OpSys.h" +#include "logic/minecraft/RawLibrary.h" class Rule; class OneSixLibrary; typedef std::shared_ptr<OneSixLibrary> OneSixLibraryPtr; -class OneSixLibrary +class OneSixLibrary : public RawLibrary { private: - // basic values used internally (so far) - QString m_name; - QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; - QList<std::shared_ptr<Rule>> m_rules; - - // custom values - /// absolute URL. takes precedence over m_download_path, if defined - QString m_absolute_url; - /// download hint - how to actually get the library - QString m_hint; - - // derived values used for real things /// a decent name fit for display QString m_decentname; /// a decent version fit for display @@ -57,13 +46,9 @@ private: QString m_download_url; /// is this lib actually active on the current OS? bool m_is_active = false; - /// is the library a native? - bool m_is_native = false; - /// native suffixes per OS - QMap<OpSys, QString> m_native_suffixes; public: - QStringList extract_excludes; + QString minVersion; enum DependType @@ -80,15 +65,16 @@ public: m_name = name; dependType = type; } - + /// Constructor + OneSixLibrary(RawLibraryPtr base); + static OneSixLibraryPtr fromRawLibrary(RawLibraryPtr lib); + /// Returns the raw name field QString rawName() const { return m_name; } - QJsonObject toJson(); - /** * finalize the library, processing the input values into derived values and state * @@ -116,8 +102,6 @@ public: /// Set the url base for downloads void setBaseUrl(const QString &base_url); - /// Call this to mark the library as 'native' (it's a zip archive with DLLs) - void setIsNative(); /// Attach a name suffix to the specified OS native void addNative(OpSys os, const QString &suffix); /// Clears all suffixes @@ -127,8 +111,6 @@ public: /// Returns true if the library should be loaded (or extracted, in case of natives) bool isActive() const; - /// Returns true if the library is native - bool isNative() const; /// Get the URL to download the library from QString downloadUrl() const; /// Get the relative path where the library should be saved diff --git a/logic/OneSixRule.cpp b/logic/minecraft/OneSixRule.cpp index d8d13b50..93c49e5e 100644 --- a/logic/OneSixRule.cpp +++ b/logic/minecraft/OneSixRule.cpp @@ -18,6 +18,15 @@ #include "OneSixRule.h" +RuleAction RuleAction_fromString(QString name) +{ + if (name == "allow") + return Allow; + if (name == "disallow") + return Disallow; + return Defer; +} + QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules) { QList<std::shared_ptr<Rule>> rules; @@ -79,11 +88,3 @@ QJsonObject OsRule::toJson() return ruleObj; } -RuleAction RuleAction_fromString(QString name) -{ - if (name == "allow") - return Allow; - if (name == "disallow") - return Disallow; - return Defer; -} diff --git a/logic/OneSixRule.h b/logic/minecraft/OneSixRule.h index 426e2886..a18093b0 100644 --- a/logic/OneSixRule.h +++ b/logic/minecraft/OneSixRule.h @@ -16,8 +16,13 @@ #pragma once #include <QString> +#include <QList> +#include <QJsonObject> +#include <memory> +#include "OpSys.h" -#include "logic/OneSixLibrary.h" +class OneSixLibrary; +class Rule; enum RuleAction { @@ -26,7 +31,6 @@ enum RuleAction Defer }; -RuleAction RuleAction_fromString(QString); QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules); class Rule diff --git a/logic/OpSys.cpp b/logic/minecraft/OpSys.cpp index e001b7f3..e001b7f3 100644 --- a/logic/OpSys.cpp +++ b/logic/minecraft/OpSys.cpp diff --git a/logic/OpSys.h b/logic/minecraft/OpSys.h index 363c87d7..363c87d7 100644 --- a/logic/OpSys.h +++ b/logic/minecraft/OpSys.h diff --git a/logic/minecraft/ParseUtils.cpp b/logic/minecraft/ParseUtils.cpp new file mode 100644 index 00000000..f94de6ff --- /dev/null +++ b/logic/minecraft/ParseUtils.cpp @@ -0,0 +1,24 @@ +#include <QDateTime> +#include <QString> +#include "ParseUtils.h" +#include <logic/MMCJson.h> + +QDateTime timeFromS3Time(QString str) +{ + return QDateTime::fromString(str, Qt::ISODate); +} + +bool parse_timestamp (const QString & raw, QString & save_here, QDateTime & parse_here) +{ + save_here = raw; + if (save_here.isEmpty()) + { + return false; + } + parse_here = timeFromS3Time(save_here); + if (!parse_here.isValid()) + { + return false; + } + return true; +} diff --git a/logic/minecraft/ParseUtils.h b/logic/minecraft/ParseUtils.h new file mode 100644 index 00000000..bd2f6ffb --- /dev/null +++ b/logic/minecraft/ParseUtils.h @@ -0,0 +1,14 @@ +#pragma once +#include <QString> +#include <QDateTime> + +/** + * parse the S3 timestamp in 'raw' and fill the forwarded variables. + * return true/false for success/failure + */ +bool parse_timestamp (const QString &raw, QString &save_here, QDateTime &parse_here); + +/** + * take the timestamp used by S3 and turn it into QDateTime + */ +QDateTime timeFromS3Time(QString str); diff --git a/logic/minecraft/RawLibrary.cpp b/logic/minecraft/RawLibrary.cpp new file mode 100644 index 00000000..7e0ebff0 --- /dev/null +++ b/logic/minecraft/RawLibrary.cpp @@ -0,0 +1,205 @@ +#include "logic/MMCJson.h" +using namespace MMCJson; + +#include "RawLibrary.h" + +RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &filename) +{ + RawLibraryPtr out(new RawLibrary()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + + "contains a library that doesn't have a 'name' field"); + } + out->m_name = libObj.value("name").toString(); + + auto readString = [libObj, filename](const QString & key, QString & variable) + { + if (libObj.contains(key)) + { + QJsonValue val = libObj.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("url", out->m_base_url); + readString("MMC-hint", out->m_hint); + readString("MMC-absulute_url", out->m_absolute_url); + readString("MMC-absoluteUrl", out->m_absolute_url); + if (libObj.contains("extract")) + { + out->applyExcludes = true; + auto extractObj = ensureObject(libObj.value("extract")); + for (auto excludeVal : ensureArray(extractObj.value("exclude"))) + { + out->extract_excludes.append(ensureString(excludeVal)); + } + } + if (libObj.contains("natives")) + { + QJsonObject nativesObj = ensureObject(libObj.value("natives")); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + { + if (!it.value().isString()) + { + QLOG_WARN() << filename << "contains an invalid native (skipping)"; + } + OpSys opSys = OpSys_fromString(it.key()); + if (opSys != Os_Other) + { + out->m_native_suffixes[opSys] = it.value().toString(); + } + } + } + if (libObj.contains("rules")) + { + out->applyRules = true; + out->m_rules = rulesFromJsonV4(libObj); + } + return out; +} + +RawLibraryPtr RawLibrary::fromJsonPlus(const QJsonObject &libObj, const QString &filename) +{ + auto lib = RawLibrary::fromJson(libObj, filename); + if (libObj.contains("insert")) + { + QJsonValue insertVal = ensureExists(libObj.value("insert"), "library insert rule"); + QString insertString; + { + if (insertVal.isString()) + { + insertString = insertVal.toString(); + } + else if (insertVal.isObject()) + { + QJsonObject insertObj = insertVal.toObject(); + if (insertObj.isEmpty()) + { + throw JSONValidationError("One library has an empty insert object in " + + filename); + } + insertString = insertObj.keys().first(); + lib->insertData = insertObj.value(insertString).toString(); + } + } + if (insertString == "apply") + { + lib->insertType = RawLibrary::Apply; + } + else if (insertString == "prepend") + { + lib->insertType = RawLibrary::Prepend; + } + else if (insertString == "append") + { + lib->insertType = RawLibrary::Append; + } + else if (insertString == "replace") + { + lib->insertType = RawLibrary::Replace; + } + else + { + throw JSONValidationError("A '+' library in " + filename + + " contains an invalid insert type"); + } + } + if (libObj.contains("MMC-depend")) + { + const QString dependString = ensureString(libObj.value("MMC-depend")); + if (dependString == "hard") + { + lib->dependType = RawLibrary::Hard; + } + else if (dependString == "soft") + { + lib->dependType = RawLibrary::Soft; + } + else + { + throw JSONValidationError("A '+' library in " + filename + + " contains an invalid depend type"); + } + } + return lib; +} + +bool RawLibrary::isNative() const +{ + return m_native_suffixes.size() != 0; +} + +QJsonObject RawLibrary::toJson() +{ + QJsonObject libRoot; + libRoot.insert("name", m_name); + if (m_absolute_url.size()) + libRoot.insert("MMC-absoluteUrl", m_absolute_url); + if (m_hint.size()) + libRoot.insert("MMC-hint", m_hint); + if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::LIBRARY_BASE && !m_base_url.isEmpty()) + { + libRoot.insert("url", m_base_url); + } + if (isNative()) + { + QJsonObject nativeList; + auto iter = m_native_suffixes.begin(); + while (iter != m_native_suffixes.end()) + { + nativeList.insert(OpSys_toString(iter.key()), iter.value()); + iter++; + } + libRoot.insert("natives", nativeList); + if (extract_excludes.size()) + { + QJsonArray excludes; + QJsonObject extract; + for (auto exclude : extract_excludes) + { + excludes.append(exclude); + } + extract.insert("exclude", excludes); + libRoot.insert("extract", extract); + } + } + if (m_rules.size()) + { + QJsonArray allRules; + for (auto &rule : m_rules) + { + QJsonObject ruleObj = rule->toJson(); + allRules.append(ruleObj); + } + libRoot.insert("rules", allRules); + } + return libRoot; +} + +QString RawLibrary::fullname() +{ + QStringList parts = m_name.split(':'); + return parts[0] + ":" + parts[1]; +} + +QString RawLibrary::group() +{ + QStringList parts = m_name.split(':'); + return parts[0]; +} + +QString RawLibrary::version() +{ + QStringList parts = m_name.split(':'); + return parts[2]; +} diff --git a/logic/minecraft/RawLibrary.h b/logic/minecraft/RawLibrary.h new file mode 100644 index 00000000..f5a28c61 --- /dev/null +++ b/logic/minecraft/RawLibrary.h @@ -0,0 +1,64 @@ +#pragma once +#include <QString> +#include <QPair> +#include <QList> +#include <QStringList> +#include <QMap> +#include <memory> + +#include "logic/minecraft/OneSixRule.h" +#include "logic/minecraft/OpSys.h" +#include "logic/net/URLConstants.h" + +class RawLibrary; +typedef std::shared_ptr<RawLibrary> RawLibraryPtr; + +class RawLibrary +{ +public: /* methods */ + /// read and create a basic library + static RawLibraryPtr fromJson(const QJsonObject &libObj, const QString &filename); + /// read and create a MultiMC '+' library. Those have some extra fields. + static RawLibraryPtr fromJsonPlus(const QJsonObject &libObj, const QString &filename); + QJsonObject toJson(); + + QString fullname(); + QString version(); + QString group(); + +public: /* data */ + QString m_name; + QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; + /// type hint - modifies how the library is treated + QString m_hint; + /// absolute URL. takes precedence over m_download_path, if defined + QString m_absolute_url; + + bool applyExcludes = false; + QStringList extract_excludes; + + /// Returns true if the library is native + bool isNative() const; + /// native suffixes per OS + QMap<OpSys, QString> m_native_suffixes; + + bool applyRules = false; + QList<std::shared_ptr<Rule>> m_rules; + + // used for '+' libraries + enum InsertType + { + Apply, + Append, + Prepend, + Replace + } insertType = Append; + QString insertData; + + // soft or hard dependency? hard means 'needs equal', soft means 'needs equal or newer' + enum DependType + { + Soft, + Hard + } dependType = Soft; +}; diff --git a/logic/minecraft/VersionBuildError.h b/logic/minecraft/VersionBuildError.h new file mode 100644 index 00000000..ae479851 --- /dev/null +++ b/logic/minecraft/VersionBuildError.h @@ -0,0 +1,58 @@ +#include "MMCError.h" + +class VersionBuildError : public MMCError +{ +public: + VersionBuildError(QString cause) : MMCError(cause) {}; + virtual ~VersionBuildError() noexcept + { + } +}; + +/** + * the base version file was meant for a newer version of the vanilla launcher than we support + */ +class LauncherVersionError : public VersionBuildError +{ +public: + LauncherVersionError(int actual, int supported) + : VersionBuildError(QObject::tr( + "The base version file of this instance was meant for a newer (%1) " + "version of the vanilla launcher than this version of MultiMC supports (%2).") + .arg(actual) + .arg(supported)) {}; + virtual ~LauncherVersionError() noexcept + { + } +}; + +/** + * some patch was intended for a different version of minecraft + */ +class MinecraftVersionMismatch : public VersionBuildError +{ +public: + MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion) + : VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft " + "(%2) than that of the instance (%3).") + .arg(fileId) + .arg(mcVersion) + .arg(parentMcVersion)) {}; + virtual ~MinecraftVersionMismatch() noexcept + { + } +}; + +/** + * files required for the version are not (yet?) present + */ +class VersionIncomplete : public VersionBuildError +{ +public: + VersionIncomplete(QString missingPatch) + : VersionBuildError(QObject::tr("Version is incomplete: missing %1.") + .arg(missingPatch)) {}; + virtual ~VersionIncomplete() noexcept + { + } +};
\ No newline at end of file diff --git a/logic/minecraft/VersionBuilder.cpp b/logic/minecraft/VersionBuilder.cpp new file mode 100644 index 00000000..ddcf6333 --- /dev/null +++ b/logic/minecraft/VersionBuilder.cpp @@ -0,0 +1,349 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <QList> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonDocument> +#include <QFile> +#include <QFileInfo> +#include <QMessageBox> +#include <QObject> +#include <QDir> +#include <QDebug> +#include <qresource.h> +#include <modutils.h> + +#include "MultiMC.h" +#include "logic/minecraft/VersionBuilder.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/OneSixRule.h" +#include "logic/minecraft/VersionPatch.h" +#include "logic/minecraft/VersionFile.h" +#include "VersionBuildError.h" +#include "MinecraftVersionList.h" + +#include "logic/OneSixInstance.h" +#include "logic/MMCJson.h" + +#include "logger/QsLog.h" + +VersionBuilder::VersionBuilder() +{ +} + +void VersionBuilder::build(InstanceVersion *version, OneSixInstance *instance, + const QStringList &external) +{ + VersionBuilder builder; + builder.m_version = version; + builder.m_instance = instance; + builder.external_patches = external; + builder.buildInternal(); +} + +void VersionBuilder::readJsonAndApplyToVersion(InstanceVersion *version, const QJsonObject &obj) +{ + VersionBuilder builder; + builder.m_version = version; + builder.m_instance = 0; + builder.readJsonAndApply(obj); +} + +void VersionBuilder::buildFromCustomJson() +{ + QLOG_INFO() << "Building version from custom.json within the instance."; + QLOG_INFO() << "Reading custom.json"; + auto file = parseJsonFile(QFileInfo(instance_root.absoluteFilePath("custom.json")), false); + file->name = "custom.json"; + file->filename = "custom.json"; + file->fileId = "org.multimc.custom.json"; + file->order = -1; + file->version = QString(); + m_version->VersionPatches.append(file); + m_version->finalize(); + return; +} + +void VersionBuilder::buildFromVersionJson() +{ + QLOG_INFO() << "Building version from version.json and patches within the instance."; + QLOG_INFO() << "Reading version.json"; + auto file = parseJsonFile(QFileInfo(instance_root.absoluteFilePath("version.json")), false); + file->name = "Minecraft"; + file->fileId = "org.multimc.version.json"; + file->order = -1; + file->version = m_instance->intendedVersionId(); + file->mcVersion = m_instance->intendedVersionId(); + m_version->VersionPatches.append(file); + + // load all patches, put into map for ordering, apply in the right order + readInstancePatches(); + + // some final touches + m_version->finalize(); +} + +void VersionBuilder::readInstancePatches() +{ + PatchOrder userOrder; + readOverrideOrders(m_instance, userOrder); + QDir patches(instance_root.absoluteFilePath("patches/")); + + // first, load things by sort order. + for (auto id : userOrder) + { + // ignore builtins + if (id == "net.minecraft") + continue; + if (id == "org.lwjgl") + continue; + // parse the file + QString filename = patches.absoluteFilePath(id + ".json"); + QLOG_INFO() << "Reading" << filename << "by user order"; + auto file = parseJsonFile(QFileInfo(filename), false); + // sanity check. prevent tampering with files. + if (file->fileId != id) + { + throw VersionBuildError( + QObject::tr("load id %1 does not match internal id %2").arg(id, file->fileId)); + } + m_version->VersionPatches.append(file); + } + // now load the rest by internal preference. + QMap<int, QPair<QString, VersionFilePtr>> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + // parse the file + QLOG_INFO() << "Reading" << info.fileName(); + auto file = parseJsonFile(info, true); + // ignore builtins + if (file->fileId == "net.minecraft") + continue; + if (file->fileId == "org.lwjgl") + continue; + // do not load what we already loaded in the first pass + if (userOrder.contains(file->fileId)) + continue; + if (files.contains(file->order)) + { + // FIXME: do not throw? + throw VersionBuildError(QObject::tr("%1 has the same order as %2") + .arg(file->fileId, files[file->order].second->fileId)); + } + files.insert(file->order, qMakePair(info.fileName(), file)); + } + for (auto order : files.keys()) + { + auto &filePair = files[order]; + m_version->VersionPatches.append(filePair.second); + } +} + +void VersionBuilder::buildFromExternalPatches() +{ + QLOG_INFO() << "Building version from external files."; + int externalOrder = -1; + for (auto fileName : external_patches) + { + QLOG_INFO() << "Reading" << fileName; + auto file = parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json")); + file->name = QFileInfo(fileName).fileName(); + file->fileId = "org.multimc.external." + file->name; + file->order = (externalOrder += 1); + file->version = QString(); + file->mcVersion = QString(); + m_version->VersionPatches.append(file); + } + // some final touches + m_version->finalize(); +} + +void VersionBuilder::buildFromMultilayer() +{ + QLOG_INFO() << "Building version from multilayered sources."; + // just the builtin stuff for now + auto minecraftList = MMC->minecraftlist(); + auto mcversion = minecraftList->findVersion(m_instance->intendedVersionId()); + auto minecraftPatch = std::dynamic_pointer_cast<VersionPatch>(mcversion); + if (!minecraftPatch) + { + throw VersionIncomplete("net.minecraft"); + } + minecraftPatch->setOrder(-2); + m_version->VersionPatches.append(minecraftPatch); + + // TODO: this is obviously fake. + QResource LWJGL(":/versions/LWJGL/2.9.1.json"); + auto lwjgl = parseJsonFile(LWJGL.absoluteFilePath(), false, false); + auto lwjglPatch = std::dynamic_pointer_cast<VersionPatch>(lwjgl); + if (!lwjglPatch) + { + throw VersionIncomplete("org.lwjgl"); + } + lwjglPatch->setOrder(-1); + m_version->VersionPatches.append(lwjglPatch); + + // load all patches, put into map for ordering, apply in the right order + readInstancePatches(); + + m_version->finalize(); +} + +void VersionBuilder::buildInternal() +{ + m_version->VersionPatches.clear(); + instance_root = QDir(m_instance->instanceRoot()); + // if we do external files, do just those. + if (!external_patches.isEmpty()) + { + buildFromExternalPatches(); + } + // else, if there's custom json, we just do that. + else if (QFile::exists(instance_root.absoluteFilePath("custom.json"))) + { + buildFromCustomJson(); + } + // version.json -> patches/*.json + else if (QFile::exists(instance_root.absoluteFilePath("version.json"))) + { + buildFromVersionJson(); + } + else + { + buildFromMultilayer(); + } +} + +void VersionBuilder::readJsonAndApply(const QJsonObject &obj) +{ + m_version->clear(); + + auto file = VersionFile::fromJson(QJsonDocument(obj), QString(), false); + + file->applyTo(m_version); + m_version->VersionPatches.append(file); +} + +VersionFilePtr VersionBuilder::parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, + bool isFTB) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") + .arg(fileInfo.fileName(), file.errorString())); + } + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + throw JSONValidationError( + QObject::tr("Unable to process the version file %1: %2 at %3.") + .arg(fileInfo.fileName(), error.errorString()) + .arg(error.offset)); + } + return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); +} + +VersionFilePtr VersionBuilder::parseBinaryJsonFile(const QFileInfo &fileInfo) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") + .arg(fileInfo.fileName(), file.errorString())); + } + QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); + file.close(); + if (doc.isNull()) + { + file.remove(); + throw JSONValidationError( + QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); + } + return VersionFile::fromJson(doc, file.fileName(), false, false); +} + +static const int currentOrderFileVersion = 1; + +bool VersionBuilder::readOverrideOrders(OneSixInstance *instance, PatchOrder &order) +{ + QFile orderFile(instance->instanceRoot() + "/order.json"); + if (!orderFile.open(QFile::ReadOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() + << " for reading:" << orderFile.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + return false; + } + + // and it's valid JSON + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + return false; + } + + // and then read it and process it if all above is true. + try + { + auto obj = MMCJson::ensureObject(doc); + // check order file version. + auto version = MMCJson::ensureInteger(obj.value("version"), "version"); + if (version != currentOrderFileVersion) + { + throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") + .arg(currentOrderFileVersion)); + } + auto orderArray = MMCJson::ensureArray(obj.value("order")); + for(auto item: orderArray) + { + order.append(MMCJson::ensureString(item)); + } + } + catch (JSONValidationError &err) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; + QLOG_WARN() << "Ignoring overriden order"; + order.clear(); + return false; + } + return true; +} + +bool VersionBuilder::writeOverrideOrders(OneSixInstance *instance, const PatchOrder &order) +{ + QJsonObject obj; + obj.insert("version", currentOrderFileVersion); + QJsonArray orderArray; + for(auto str: order) + { + orderArray.append(str); + } + obj.insert("order", orderArray); + QFile orderFile(instance->instanceRoot() + "/order.json"); + if (!orderFile.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() + << "for writing:" << orderFile.errorString(); + return false; + } + orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); + return true; +} diff --git a/logic/minecraft/VersionBuilder.h b/logic/minecraft/VersionBuilder.h new file mode 100644 index 00000000..350179b9 --- /dev/null +++ b/logic/minecraft/VersionBuilder.h @@ -0,0 +1,56 @@ +/* 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> +#include <QMap> +#include "VersionFile.h" + +class InstanceVersion; +class OneSixInstance; +class QJsonObject; +class QFileInfo; + +typedef QStringList PatchOrder; + +class VersionBuilder +{ + VersionBuilder(); +public: + static void build(InstanceVersion *version, OneSixInstance *instance, const QStringList &external); + static void readJsonAndApplyToVersion(InstanceVersion *version, const QJsonObject &obj); + static VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, bool isFTB = false); + static VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo); + + bool readOverrideOrders(OneSixInstance *instance, PatchOrder &order); + static bool writeOverrideOrders(OneSixInstance *instance, const PatchOrder &order); + +private: + InstanceVersion *m_version; + OneSixInstance *m_instance; + QStringList external_patches; + QDir instance_root; + + void buildInternal(); + void buildFromExternalPatches(); + void buildFromCustomJson(); + void buildFromVersionJson(); + void buildFromMultilayer(); + + void readInstancePatches(); + + void readJsonAndApply(const QJsonObject &obj); +}; diff --git a/logic/VersionFile.cpp b/logic/minecraft/VersionFile.cpp index cd2a4f9c..93f57116 100644 --- a/logic/VersionFile.cpp +++ b/logic/minecraft/VersionFile.cpp @@ -1,84 +1,41 @@ #include <QJsonArray> #include <QJsonDocument> - #include <modutils.h> #include "logger/QsLog.h" -#include "logic/VersionFile.h" -#include "logic/OneSixLibrary.h" -#include "logic/VersionFinal.h" -#include "MMCJson.h" +#include "logic/minecraft/VersionFile.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/minecraft/InstanceVersion.h" +#include "logic/minecraft/JarMod.h" +#include "ParseUtils.h" + +#include "logic/MMCJson.h" using namespace MMCJson; +#include "VersionBuildError.h" + #define CURRENT_MINIMUM_LAUNCHER_VERSION 14 -RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &filename) +int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) { - RawLibraryPtr out(new RawLibrary()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + - "contains a library that doesn't have a 'name' field"); - } - out->name = libObj.value("name").toString(); - - auto readString = [libObj, filename](const QString & key, QString & variable) - { - if (libObj.contains(key)) - { - QJsonValue val = libObj.value(key); - if (!val.isString()) - { - QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; - } - else - { - variable = val.toString(); - } - } - }; - - readString("url", out->url); - readString("MMC-hint", out->hint); - readString("MMC-absulute_url", out->absoluteUrl); - readString("MMC-absoluteUrl", out->absoluteUrl); - if (libObj.contains("extract")) - { - out->applyExcludes = true; - auto extractObj = ensureObject(libObj.value("extract")); - for (auto excludeVal : ensureArray(extractObj.value("exclude"))) - { - out->excludes.append(ensureString(excludeVal)); - } - } - if (libObj.contains("natives")) + int retval = -1; + for (int i = 0; i < haystack.size(); ++i) { - out->applyNatives = true; - QJsonObject nativesObj = ensureObject(libObj.value("natives")); - for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + QString chunk = haystack.at(i)->rawName(); + if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1) { - if (!it.value().isString()) - { - QLOG_WARN() << filename << "contains an invalid native (skipping)"; - } - OpSys opSys = OpSys_fromString(it.key()); - if (opSys != Os_Other) - { - out->natives.append(qMakePair(opSys, it.value().toString())); - } + // only one is allowed. + if (retval != -1) + return -1; + retval = i; } } - if (libObj.contains("rules")) - { - out->applyRules = true; - out->rules = rulesFromJsonV4(libObj); - } - return out; + return retval; } VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &filename, - const bool requireOrder, const bool isFTB) + const bool requireOrder, const bool isFTB) { VersionFilePtr out(new VersionFile()); if (doc.isEmpty() || doc.isNull()) @@ -87,7 +44,6 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } if (!doc.isObject()) { - throw JSONValidationError("The root of " + filename + " is not an object"); } QJsonObject root = doc.object(); @@ -111,7 +67,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi out->mcVersion = root.value("mcVersion").toString(); out->filename = filename; - auto readString = [root, filename](const QString & key, QString & variable) + auto readString = [root](const QString & key, QString & variable) { if (root.contains(key)) { @@ -119,6 +75,16 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } }; + auto readStringRet = [root](const QString & key)->QString + { + if (root.contains(key)) + { + return ensureString(root.value(key)); + } + return QString(); + } + ; + // FIXME: This should be ignored when applying. if (!isFTB) { @@ -126,13 +92,16 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } readString("mainClass", out->mainClass); + readString("appletClass", out->appletClass); readString("processArguments", out->processArguments); readString("minecraftArguments", out->overwriteMinecraftArguments); readString("+minecraftArguments", out->addMinecraftArguments); readString("-minecraftArguments", out->removeMinecraftArguments); readString("type", out->type); - readString("releaseTime", out->releaseTime); - readString("time", out->time); + + parse_timestamp(readStringRet("releaseTime"), out->m_releaseTimeString, out->m_releaseTime); + parse_timestamp(readStringRet("time"), out->m_updateTimeString, out->m_updateTime); + readString("assets", out->assets); if (root.contains("minimumLauncherVersion")) @@ -165,6 +134,14 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } } + if (root.contains("+traits")) + { + for (auto tweakerVal : ensureArray(root.value("+traits"))) + { + out->traits.insert(ensureString(tweakerVal)); + } + } + if (root.contains("libraries")) { // FIXME: This should be done when applying. @@ -177,7 +154,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi // FIXME: This should be done when applying. if (isFTB) { - lib->hint = "local"; + lib->m_hint = "local"; lib->insertType = RawLibrary::Prepend; out->addLibs.prepend(lib); } @@ -188,76 +165,29 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } } + if (root.contains("+jarMods")) + { + for (auto libVal : ensureArray(root.value("+jarMods"))) + { + QJsonObject libObj = ensureObject(libVal); + // parse the jarmod + auto lib = Jarmod::fromJson(libObj, filename); + // and add to jar mods + out->jarMods.append(lib); + } + } + if (root.contains("+libraries")) { for (auto libVal : ensureArray(root.value("+libraries"))) { QJsonObject libObj = ensureObject(libVal); - QJsonValue insertVal = ensureExists(libObj.value("insert")); - // parse the library - auto lib = RawLibrary::fromJson(libObj, filename); - - // TODO: utility functions for handling this case. templates? - QString insertString; - { - if (insertVal.isString()) - { - insertString = insertVal.toString(); - } - else if (insertVal.isObject()) - { - QJsonObject insertObj = insertVal.toObject(); - if (insertObj.isEmpty()) - { - throw JSONValidationError("One library has an empty insert object in " + - filename); - } - insertString = insertObj.keys().first(); - lib->insertData = insertObj.value(insertString).toString(); - } - } - if (insertString == "apply") - { - lib->insertType = RawLibrary::Apply; - } - else if (insertString == "prepend") - { - lib->insertType = RawLibrary::Prepend; - } - else if (insertString == "append") - { - lib->insertType = RawLibrary::Prepend; - } - else if (insertString == "replace") - { - lib->insertType = RawLibrary::Replace; - } - else - { - throw JSONValidationError("A '+' library in " + filename + - " contains an invalid insert type"); - } - if (libObj.contains("MMC-depend")) - { - const QString dependString = ensureString(libObj.value("MMC-depend")); - if (dependString == "hard") - { - lib->dependType = RawLibrary::Hard; - } - else if (dependString == "soft") - { - lib->dependType = RawLibrary::Soft; - } - else - { - throw JSONValidationError("A '+' library in " + filename + - " contains an invalid depend type"); - } - } + auto lib = RawLibrary::fromJsonPlus(libObj, filename); out->addLibs.append(lib); } } + if (root.contains("-libraries")) { for (auto libVal : ensureArray(root.value("-libraries"))) @@ -269,53 +199,81 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi return out; } -OneSixLibraryPtr VersionFile::createLibrary(RawLibraryPtr lib) +QJsonDocument VersionFile::toJson(bool saveOrder) { - std::shared_ptr<OneSixLibrary> out(new OneSixLibrary(lib->name)); - if (!lib->url.isEmpty()) + QJsonObject root; + if (saveOrder) + { + root.insert("order", order); + } + writeString(root, "name", name); + writeString(root, "fileId", fileId); + writeString(root, "version", version); + writeString(root, "mcVersion", mcVersion); + writeString(root, "id", id); + writeString(root, "mainClass", mainClass); + writeString(root, "appletClass", appletClass); + writeString(root, "processArguments", processArguments); + writeString(root, "minecraftArguments", overwriteMinecraftArguments); + writeString(root, "+minecraftArguments", addMinecraftArguments); + writeString(root, "-minecraftArguments", removeMinecraftArguments); + writeString(root, "type", type); + writeString(root, "assets", assets); + if (isMinecraftVersion()) + { + writeString(root, "releaseTime", m_releaseTimeString); + writeString(root, "time", m_updateTimeString); + } + if (minimumLauncherVersion != -1) { - out->setBaseUrl(lib->url); + root.insert("minimumLauncherVersion", minimumLauncherVersion); } - out->setHint(lib->hint); - if (!lib->absoluteUrl.isEmpty()) + writeStringList(root, "tweakers", overwriteTweakers); + writeStringList(root, "+tweakers", addTweakers); + writeStringList(root, "-tweakers", removeTweakers); + writeStringList(root, "+traits", traits.toList()); + writeObjectList(root, "libraries", overwriteLibs); + writeObjectList(root, "+libraries", addLibs); + writeObjectList(root, "+jarMods", jarMods); + // FIXME: removed libs are special snowflakes. + if (removeLibs.size()) { - out->setAbsoluteUrl(lib->absoluteUrl); + QJsonArray array; + for (auto lib : removeLibs) + { + QJsonObject rmlibobj; + rmlibobj.insert("name", lib); + array.append(rmlibobj); + } + root.insert("-libraries", array); } - out->setAbsoluteUrl(lib->absoluteUrl); - out->extract_excludes = lib->excludes; - for (auto native : lib->natives) + // write the contents to a json document. { - out->addNative(native.first, native.second); + QJsonDocument out; + out.setObject(root); + return out; } - out->setRules(lib->rules); - out->finalize(); - return out; } -int VersionFile::findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) +bool VersionFile::isMinecraftVersion() { - int retval = -1; - for (int i = 0; i < haystack.size(); ++i) - { - QString chunk = haystack.at(i)->rawName(); - if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1) - { - // only one is allowed. - if(retval != -1) - return -1; - retval = i; - } - } - return retval; + return (fileId == "org.multimc.version.json") || (fileId == "net.minecraft") || + (fileId == "org.multimc.custom.json"); +} + +bool VersionFile::hasJarMods() +{ + return !jarMods.isEmpty(); } -void VersionFile::applyTo(VersionFinal *version) +void VersionFile::applyTo(InstanceVersion *version) { if (minimumLauncherVersion != -1) { if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) { - throw LauncherVersionError(minimumLauncherVersion, CURRENT_MINIMUM_LAUNCHER_VERSION); + throw LauncherVersionError(minimumLauncherVersion, + CURRENT_MINIMUM_LAUNCHER_VERSION); } } @@ -336,21 +294,34 @@ void VersionFile::applyTo(VersionFinal *version) { version->mainClass = mainClass; } - if (!processArguments.isNull()) + if (!appletClass.isNull()) { - version->processArguments = processArguments; + version->appletClass = appletClass; } - if (!type.isNull()) - { - version->type = type; - } - if (!releaseTime.isNull()) + if (!processArguments.isNull()) { - version->releaseTime = releaseTime; + if (isMinecraftVersion()) + { + version->vanillaProcessArguments = processArguments; + } + version->processArguments = processArguments; } - if (!time.isNull()) + if (isMinecraftVersion()) { - version->time = time; + if (!type.isNull()) + { + version->type = type; + } + if (!m_releaseTimeString.isNull()) + { + version->m_releaseTimeString = m_releaseTimeString; + version->m_releaseTime = m_releaseTime; + } + if (!m_updateTimeString.isNull()) + { + version->m_updateTimeString = m_updateTimeString; + version->m_updateTime = m_updateTime; + } } if (!assets.isNull()) { @@ -358,10 +329,15 @@ void VersionFile::applyTo(VersionFinal *version) } if (minimumLauncherVersion >= 0) { - version->minimumLauncherVersion = minimumLauncherVersion; + if (version->minimumLauncherVersion < minimumLauncherVersion) + version->minimumLauncherVersion = minimumLauncherVersion; } if (!overwriteMinecraftArguments.isNull()) { + if (isMinecraftVersion()) + { + version->vanillaMinecraftArguments = overwriteMinecraftArguments; + } version->minecraftArguments = overwriteMinecraftArguments; } if (!addMinecraftArguments.isNull()) @@ -384,13 +360,20 @@ void VersionFile::applyTo(VersionFinal *version) { version->tweakers.removeAll(tweaker); } + version->jarMods.append(jarMods); + version->traits.unite(traits); if (shouldOverwriteLibs) { - version->libraries.clear(); + QList<OneSixLibraryPtr> libs; for (auto lib : overwriteLibs) { - version->libraries.append(createLibrary(lib)); + libs.append(OneSixLibrary::fromRawLibrary(lib)); + } + if (isMinecraftVersion()) + { + version->vanillaLibraries = libs; } + version->libraries = libs; } for (auto lib : addLibs) { @@ -399,43 +382,46 @@ void VersionFile::applyTo(VersionFinal *version) case RawLibrary::Apply: { // QLOG_INFO() << "Applying lib " << lib->name; - int index = findLibrary(version->libraries, lib->name); + int index = findLibrary(version->libraries, lib->m_name); if (index >= 0) { auto library = version->libraries[index]; - if (!lib->url.isNull()) + if (!lib->m_base_url.isNull()) { - library->setBaseUrl(lib->url); + library->setBaseUrl(lib->m_base_url); } - if (!lib->hint.isNull()) + if (!lib->m_hint.isNull()) { - library->setHint(lib->hint); + library->setHint(lib->m_hint); } - if (!lib->absoluteUrl.isNull()) + if (!lib->m_absolute_url.isNull()) { - library->setAbsoluteUrl(lib->absoluteUrl); + library->setAbsoluteUrl(lib->m_absolute_url); } if (lib->applyExcludes) { - library->extract_excludes = lib->excludes; + library->extract_excludes = lib->extract_excludes; } - if (lib->applyNatives) + if (lib->isNative()) { - library->clearSuffixes(); + // library->clearSuffixes(); + library->m_native_suffixes = lib->m_native_suffixes; + /* for (auto native : lib->natives) { library->addNative(native.first, native.second); } + */ } if (lib->applyRules) { - library->setRules(lib->rules); + library->setRules(lib->m_rules); } library->finalize(); } else { - QLOG_WARN() << "Couldn't find" << lib->name << "(skipping)"; + QLOG_WARN() << "Couldn't find" << lib->m_name << "(skipping)"; } break; } @@ -443,24 +429,24 @@ void VersionFile::applyTo(VersionFinal *version) case RawLibrary::Prepend: { // QLOG_INFO() << "Adding lib " << lib->name; - const int startOfVersion = lib->name.lastIndexOf(':') + 1; + const int startOfVersion = lib->m_name.lastIndexOf(':') + 1; const int index = findLibrary( - version->libraries, QString(lib->name).replace(startOfVersion, INT_MAX, '*')); + version->libraries, QString(lib->m_name).replace(startOfVersion, INT_MAX, '*')); if (index < 0) { if (lib->insertType == RawLibrary::Append) { - version->libraries.append(createLibrary(lib)); + version->libraries.append(OneSixLibrary::fromRawLibrary(lib)); } else { - version->libraries.prepend(createLibrary(lib)); + version->libraries.prepend(OneSixLibrary::fromRawLibrary(lib)); } } else { auto otherLib = version->libraries.at(index); - const Util::Version ourVersion = lib->name.mid(startOfVersion, INT_MAX); + const Util::Version ourVersion = lib->m_name.mid(startOfVersion, INT_MAX); const Util::Version otherVersion = otherLib->version(); // if the existing version is a hard dependency we can either use it or // fail, but we can't change it @@ -474,7 +460,7 @@ void VersionFile::applyTo(VersionFinal *version) throw VersionBuildError( QObject::tr( "Error resolving library dependencies between %1 and %2 in %3.") - .arg(otherLib->rawName(), lib->name, filename)); + .arg(otherLib->rawName(), lib->m_name, filename)); } else { @@ -486,7 +472,7 @@ void VersionFile::applyTo(VersionFinal *version) // if we are higher it means we should update if (ourVersion > otherVersion) { - auto library = createLibrary(lib); + auto library = OneSixLibrary::fromRawLibrary(lib); if (Util::Version(otherLib->minVersion) < ourVersion) { library->minVersion = ourVersion.toString(); @@ -501,7 +487,7 @@ void VersionFile::applyTo(VersionFinal *version) { throw VersionBuildError(QObject::tr( "Error resolving library dependencies between %1 and %2 in %3.") - .arg(otherLib->rawName(), lib->name, + .arg(otherLib->rawName(), lib->m_name, filename)); } } @@ -512,10 +498,10 @@ void VersionFile::applyTo(VersionFinal *version) case RawLibrary::Replace: { QString toReplace; - if(lib->insertData.isEmpty()) + if (lib->insertData.isEmpty()) { - const int startOfVersion = lib->name.lastIndexOf(':') + 1; - toReplace = QString(lib->name).replace(startOfVersion, INT_MAX, '*'); + const int startOfVersion = lib->m_name.lastIndexOf(':') + 1; + toReplace = QString(lib->m_name).replace(startOfVersion, INT_MAX, '*'); } else toReplace = lib->insertData; @@ -523,7 +509,7 @@ void VersionFile::applyTo(VersionFinal *version) int index = findLibrary(version->libraries, toReplace); if (index >= 0) { - version->libraries.replace(index, createLibrary(lib)); + version->libraries.replace(index, OneSixLibrary::fromRawLibrary(lib)); } else { diff --git a/logic/minecraft/VersionFile.h b/logic/minecraft/VersionFile.h new file mode 100644 index 00000000..186f4335 --- /dev/null +++ b/logic/minecraft/VersionFile.h @@ -0,0 +1,103 @@ +#pragma once + +#include <QString> +#include <QStringList> +#include <QDateTime> +#include <memory> +#include "logic/minecraft/OpSys.h" +#include "logic/minecraft/OneSixRule.h" +#include "VersionPatch.h" +#include "MMCError.h" +#include "OneSixLibrary.h" +#include "JarMod.h" + +class InstanceVersion; +struct VersionFile; + +typedef std::shared_ptr<VersionFile> VersionFilePtr; +class VersionFile : public VersionPatch +{ +public: /* methods */ + static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename, + const bool requireOrder, const bool isFTB = false); + QJsonDocument toJson(bool saveOrder); + + virtual void applyTo(InstanceVersion *version) override; + virtual bool isMinecraftVersion() override; + virtual bool hasJarMods() override; + virtual int getOrder() override + { + return order; + } + virtual void setOrder(int order) override + { + this->order = order; + } + virtual QList<JarmodPtr> getJarMods() override + { + return jarMods; + } + virtual QString getPatchID() override + { + return fileId; + } + virtual QString getPatchName() override + { + return name; + } + virtual QString getPatchVersion() override + { + return version; + } + virtual QString getPatchFilename() override + { + return filename; + } + +public: /* data */ + int order = 0; + QString name; + QString fileId; + QString version; + // TODO use the mcVersion to determine if a version file should be removed on update + QString mcVersion; + QString filename; + // TODO requirements + // QMap<QString, QString> requirements; + QString id; + QString mainClass; + QString appletClass; + QString overwriteMinecraftArguments; + QString addMinecraftArguments; + QString removeMinecraftArguments; + QString processArguments; + QString type; + + /// the time this version was actually released by Mojang, as string and as QDateTime + QString m_releaseTimeString; + QDateTime m_releaseTime; + + /// the time this version was last updated by Mojang, as string and as QDateTime + QString m_updateTimeString; + QDateTime m_updateTime; + + /// asset group used by this ... thing. + QString assets; + int minimumLauncherVersion = -1; + + bool shouldOverwriteTweakers = false; + QStringList overwriteTweakers; + QStringList addTweakers; + QStringList removeTweakers; + + bool shouldOverwriteLibs = false; + QList<RawLibraryPtr> overwriteLibs; + QList<RawLibraryPtr> addLibs; + QList<QString> removeLibs; + + QSet<QString> traits; + + QList<JarmodPtr> jarMods; +}; + + diff --git a/logic/minecraft/VersionPatch.h b/logic/minecraft/VersionPatch.h new file mode 100644 index 00000000..1dd30e79 --- /dev/null +++ b/logic/minecraft/VersionPatch.h @@ -0,0 +1,31 @@ +#pragma once + +#include <memory> +#include <QList> +#include "JarMod.h" + +class InstanceVersion; +class VersionPatch +{ +public: + virtual ~VersionPatch(){}; + virtual void applyTo(InstanceVersion *version) = 0; + + virtual bool isMinecraftVersion() = 0; + virtual bool hasJarMods() = 0; + virtual QList<JarmodPtr> getJarMods() = 0; + + virtual bool isMoveable() + { + return getOrder() >= 0; + } + virtual void setOrder(int order) = 0; + virtual int getOrder() = 0; + + virtual QString getPatchID() = 0; + virtual QString getPatchName() = 0; + virtual QString getPatchVersion() = 0; + virtual QString getPatchFilename() = 0; +}; + +typedef std::shared_ptr<VersionPatch> VersionPatchPtr; diff --git a/logic/minecraft/VersionSource.h b/logic/minecraft/VersionSource.h new file mode 100644 index 00000000..75b2c24b --- /dev/null +++ b/logic/minecraft/VersionSource.h @@ -0,0 +1,9 @@ +#pragma once + +/// where is a version from? +enum VersionSource +{ + Builtin, //!< version loaded from the internal resources. + Local, //!< version loaded from a file in the cache. + Remote, //!< incomplete version on a remote server. +};
\ No newline at end of file diff --git a/logic/net/NetJob.h b/logic/net/NetJob.h index 2df8428b..d05e7b6f 100644 --- a/logic/net/NetJob.h +++ b/logic/net/NetJob.h @@ -21,7 +21,6 @@ #include "MD5EtagDownload.h" #include "CacheDownload.h" #include "HttpMetaCache.h" -#include "ForgeXzDownload.h" #include "logic/tasks/ProgressProvider.h" class NetJob; diff --git a/logic/screenshots/ImgurAlbumCreation.cpp b/logic/screenshots/ImgurAlbumCreation.cpp index e473952e..91cf4bb1 100644 --- a/logic/screenshots/ImgurAlbumCreation.cpp +++ b/logic/screenshots/ImgurAlbumCreation.cpp @@ -4,8 +4,8 @@ #include <QJsonDocument> #include <QJsonObject> #include <QUrl> +#include <QStringList> -#include "logic/screenshots//ScreenshotList.h" #include "logic/net/URLConstants.h" #include "MultiMC.h" #include "logger/QsLog.h" @@ -28,7 +28,7 @@ void ImgurAlbumCreation::start() QStringList ids; for (auto shot : m_screenshots) { - ids.append(shot->imgurId); + ids.append(shot->m_imgurId); } const QByteArray data = "ids=" + ids.join(',').toUtf8() + "&title=Minecraft%20Screenshots&privacy=hidden"; diff --git a/logic/screenshots/ImgurUpload.cpp b/logic/screenshots/ImgurUpload.cpp index 62033ef5..f305aec0 100644 --- a/logic/screenshots/ImgurUpload.cpp +++ b/logic/screenshots/ImgurUpload.cpp @@ -8,7 +8,6 @@ #include <QFile> #include <QUrl> -#include "logic/screenshots/ScreenshotList.h" #include "logic/net/URLConstants.h" #include "MultiMC.h" #include "logger/QsLog.h" @@ -27,7 +26,7 @@ void ImgurUpload::start() request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3"); request.setRawHeader("Accept", "application/json"); - QFile f(m_shot->file); + QFile f(m_shot->m_file.absoluteFilePath()); if (!f.open(QFile::ReadOnly)) { emit failed(m_index_within_job); @@ -46,7 +45,7 @@ void ImgurUpload::start() multipart->append(typePart); QHttpPart namePart; namePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"name\""); - namePart.setBody(m_shot->timestamp.toString(Qt::ISODate).toUtf8()); + namePart.setBody(m_shot->m_file.baseName().toUtf8()); multipart->append(namePart); auto worker = MMC->qnam(); @@ -84,8 +83,8 @@ void ImgurUpload::downloadFinished() emit failed(m_index_within_job); return; } - m_shot->imgurId = object.value("data").toObject().value("id").toString(); - m_shot->url = object.value("data").toObject().value("link").toString(); + m_shot->m_imgurId = object.value("data").toObject().value("id").toString(); + m_shot->m_url = object.value("data").toObject().value("link").toString(); m_status = Job_Finished; emit succeeded(m_index_within_job); return; diff --git a/logic/screenshots/Screenshot.cpp b/logic/screenshots/Screenshot.cpp deleted file mode 100644 index 882e491f..00000000 --- a/logic/screenshots/Screenshot.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "Screenshot.h" -#include <QImage> -#include <QIcon> -QIcon ScreenShot::getImage() -{ - if(!imageloaded) - { - QImage image(file); - QImage thumbnail = image.scaledToWidth(256, Qt::SmoothTransformation); - m_image = QIcon(QPixmap::fromImage(thumbnail)); - imageloaded = true; - } - return m_image; -} diff --git a/logic/screenshots/Screenshot.h b/logic/screenshots/Screenshot.h index 815c0d47..b48cbe99 100644 --- a/logic/screenshots/Screenshot.h +++ b/logic/screenshots/Screenshot.h @@ -2,18 +2,18 @@ #include <QDateTime> #include <QString> +#include <QFileInfo> #include <memory> -#include <QIcon> struct ScreenShot { - QIcon getImage(); - QIcon m_image; - bool imageloaded = false; - QDateTime timestamp; - QString file; - QString url; - QString imgurId; + ScreenShot(QFileInfo file) + { + m_file = file; + } + QFileInfo m_file; + QString m_url; + QString m_imgurId; }; typedef std::shared_ptr<ScreenShot> ScreenshotPtr; diff --git a/logic/screenshots/ScreenshotList.cpp b/logic/screenshots/ScreenshotList.cpp deleted file mode 100644 index a34f4d46..00000000 --- a/logic/screenshots/ScreenshotList.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "ScreenshotList.h" -#include "gui/dialogs/ScreenshotDialog.h" - -#include <QDir> -#include <QIcon> -#include <QList> -#include "gui/dialogs/ProgressDialog.h" -#include "gui/dialogs/CustomMessageBox.h" - -ScreenshotList::ScreenshotList(InstancePtr instance, QObject *parent) - : QAbstractListModel(parent), m_instance(instance) -{ -} - -int ScreenshotList::rowCount(const QModelIndex &) const -{ - return m_screenshots.size(); -} - -QVariant ScreenshotList::data(const QModelIndex &index, int role) const -{ - if (index.row() >= m_screenshots.size() || index.row() < 0) - return QVariant(); - - switch (role) - { - case Qt::DecorationRole: - return m_screenshots.at(index.row())->getImage(); - case Qt::DisplayRole: - return m_screenshots.at(index.row())->timestamp.toString("yyyy-MM-dd HH:mm:ss"); - case Qt::ToolTipRole: - return m_screenshots.at(index.row())->timestamp.toString("yyyy-MM-dd HH:mm:ss"); - case Qt::TextAlignmentRole: - return (int)(Qt::AlignHCenter | Qt::AlignVCenter); - default: - return QVariant(); - } -} - -QVariant ScreenshotList::headerData(int section, Qt::Orientation orientation, int role) const -{ - return QVariant(); -} - -Qt::ItemFlags ScreenshotList::flags(const QModelIndex &index) const -{ - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; -} - -Task *ScreenshotList::load() -{ - return new ScreenshotLoadTask(this); -} - -ScreenshotLoadTask::ScreenshotLoadTask(ScreenshotList *list) : m_list(list) -{ -} - -ScreenshotLoadTask::~ScreenshotLoadTask() -{ -} - -void ScreenshotLoadTask::executeTask() -{ - auto dir = QDir(m_list->instance()->minecraftRoot()); - if (!dir.cd("screenshots")) - { - emitFailed("Selected instance does not have any screenshots!"); - return; - } - dir.setNameFilters(QStringList() << "*.png"); - this->m_results.clear(); - for (auto file : dir.entryList()) - { - ScreenShot *shot = new ScreenShot(); - shot->timestamp = QDateTime::fromString(file, "yyyy-MM-dd_HH.mm.ss.png"); - shot->file = dir.absoluteFilePath(file); - m_results.append(ScreenshotPtr(shot)); - } - m_list->loadShots(m_results); - emitSucceeded(); -} - -void ScreenshotList::deleteSelected(ScreenshotDialog *dialog) -{ - auto screens = dialog->selected(); - if (screens.isEmpty()) - { - return; - } - beginResetModel(); - QList<std::shared_ptr<ScreenShot>>::const_iterator it; - for (it = screens.cbegin(); it != screens.cend(); it++) - { - auto shot = *it; - if (!QFile(shot->file).remove()) - { - CustomMessageBox::selectable(dialog, tr("Error!"), - tr("Failed to delete screenshots!"), - QMessageBox::Warning)->exec(); - break; - } - } - ProgressDialog refresh(dialog); - Task *t = load(); - if (refresh.exec(t) != QDialog::Accepted) - { - CustomMessageBox::selectable(dialog, tr("Error!"), - tr("Unable to refresh list: %1").arg(t->failReason()), - QMessageBox::Warning)->exec(); - } - endResetModel(); -} diff --git a/logic/screenshots/ScreenshotList.h b/logic/screenshots/ScreenshotList.h deleted file mode 100644 index dc26a698..00000000 --- a/logic/screenshots/ScreenshotList.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include <QAbstractListModel> -#include "logic/BaseInstance.h" -#include "logic/tasks/Task.h" - -#include "Screenshot.h" - -class ScreenshotList : public QAbstractListModel -{ - Q_OBJECT -public: - ScreenshotList(InstancePtr instance, QObject *parent = 0); - - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role) const; - - int rowCount(const QModelIndex &parent) const; - - Qt::ItemFlags flags(const QModelIndex &index) const; - - Task *load(); - - void loadShots(QList<ScreenshotPtr> shots) - { - m_screenshots = shots; - } - - QList<ScreenshotPtr> screenshots() const - { - return m_screenshots; - } - - InstancePtr instance() const - { - return m_instance; - } - - void deleteSelected(class ScreenshotDialog *dialog); - -signals: - -public -slots: - -private: - QList<ScreenshotPtr> m_screenshots; - InstancePtr m_instance; -}; - -class ScreenshotLoadTask : public Task -{ - Q_OBJECT - -public: - explicit ScreenshotLoadTask(ScreenshotList *list); - ~ScreenshotLoadTask(); - - QList<ScreenshotPtr> screenShots() const - { - return m_results; - } - -protected: - virtual void executeTask(); - -private: - ScreenshotList *m_list; - QList<ScreenshotPtr> m_results; -}; diff --git a/logic/tasks/ProgressProvider.h b/logic/tasks/ProgressProvider.h index 15e453a3..dcb71139 100644 --- a/logic/tasks/ProgressProvider.h +++ b/logic/tasks/ProgressProvider.h @@ -32,6 +32,7 @@ signals: void status(QString status); public: + virtual ~ProgressProvider() {}; virtual QString getStatus() const = 0; virtual void getProgress(qint64 ¤t, qint64 &total) = 0; virtual bool isRunning() const = 0; @@ -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 @@ -22,6 +28,12 @@ int main(int argc, char *argv[]) Q_INIT_RESOURCE(instances); Q_INIT_RESOURCE(multimc); Q_INIT_RESOURCE(backgrounds); + Q_INIT_RESOURCE(versions); + +#ifdef HANDLE_SEGV + // Register signal handler for generating crash reports. + initBlackMagic(); +#endif Q_INIT_RESOURCE(pe_dark); Q_INIT_RESOURCE(pe_light); diff --git a/resources/multimc/16x16/plugin-blue.png b/resources/multimc/16x16/plugin-blue.png Binary files differnew file mode 100644 index 00000000..b5ab3fce --- /dev/null +++ b/resources/multimc/16x16/plugin-blue.png diff --git a/resources/multimc/16x16/plugin-green.png b/resources/multimc/16x16/plugin-green.png Binary files differnew file mode 100644 index 00000000..af0f1166 --- /dev/null +++ b/resources/multimc/16x16/plugin-green.png diff --git a/resources/multimc/16x16/plugin-red.png b/resources/multimc/16x16/plugin-red.png Binary files differnew file mode 100644 index 00000000..1a97c9c0 --- /dev/null +++ b/resources/multimc/16x16/plugin-red.png diff --git a/resources/multimc/16x16/resourcepacks.png b/resources/multimc/16x16/resourcepacks.png Binary files differnew file mode 100644 index 00000000..d862f5ca --- /dev/null +++ b/resources/multimc/16x16/resourcepacks.png diff --git a/resources/multimc/16x16/screenshots.png b/resources/multimc/16x16/screenshots.png Binary files differnew file mode 100644 index 00000000..460000d4 --- /dev/null +++ b/resources/multimc/16x16/screenshots.png diff --git a/resources/multimc/22x22/screenshots.png b/resources/multimc/22x22/screenshots.png Binary files differnew file mode 100644 index 00000000..6fb42bbd --- /dev/null +++ b/resources/multimc/22x22/screenshots.png diff --git a/resources/multimc/24x24/plugin-blue.png b/resources/multimc/24x24/plugin-blue.png Binary files differnew file mode 100644 index 00000000..250a6260 --- /dev/null +++ b/resources/multimc/24x24/plugin-blue.png diff --git a/resources/multimc/24x24/plugin-green.png b/resources/multimc/24x24/plugin-green.png Binary files differnew file mode 100644 index 00000000..90603d24 --- /dev/null +++ b/resources/multimc/24x24/plugin-green.png diff --git a/resources/multimc/24x24/plugin-red.png b/resources/multimc/24x24/plugin-red.png Binary files differnew file mode 100644 index 00000000..68cb8e9d --- /dev/null +++ b/resources/multimc/24x24/plugin-red.png diff --git a/resources/multimc/24x24/resourcepacks.png b/resources/multimc/24x24/resourcepacks.png Binary files differnew file mode 100644 index 00000000..68359d39 --- /dev/null +++ b/resources/multimc/24x24/resourcepacks.png diff --git a/resources/multimc/32x32/plugin-blue.png b/resources/multimc/32x32/plugin-blue.png Binary files differnew file mode 100644 index 00000000..c4ca12e2 --- /dev/null +++ b/resources/multimc/32x32/plugin-blue.png diff --git a/resources/multimc/32x32/plugin-green.png b/resources/multimc/32x32/plugin-green.png Binary files differnew file mode 100644 index 00000000..770d695e --- /dev/null +++ b/resources/multimc/32x32/plugin-green.png diff --git a/resources/multimc/32x32/plugin-red.png b/resources/multimc/32x32/plugin-red.png Binary files differnew file mode 100644 index 00000000..5cda173a --- /dev/null +++ b/resources/multimc/32x32/plugin-red.png diff --git a/resources/multimc/32x32/resourcepacks.png b/resources/multimc/32x32/resourcepacks.png Binary files differnew file mode 100644 index 00000000..c14759ef --- /dev/null +++ b/resources/multimc/32x32/resourcepacks.png diff --git a/resources/multimc/32x32/screenshots.png b/resources/multimc/32x32/screenshots.png Binary files differnew file mode 100644 index 00000000..4fcd6224 --- /dev/null +++ b/resources/multimc/32x32/screenshots.png diff --git a/resources/multimc/48x48/screenshots.png b/resources/multimc/48x48/screenshots.png Binary files differnew file mode 100644 index 00000000..03c0059f --- /dev/null +++ b/resources/multimc/48x48/screenshots.png diff --git a/resources/multimc/64x64/plugin-blue.png b/resources/multimc/64x64/plugin-blue.png Binary files differnew file mode 100644 index 00000000..24618fd0 --- /dev/null +++ b/resources/multimc/64x64/plugin-blue.png diff --git a/resources/multimc/64x64/plugin-green.png b/resources/multimc/64x64/plugin-green.png Binary files differnew file mode 100644 index 00000000..668be334 --- /dev/null +++ b/resources/multimc/64x64/plugin-green.png diff --git a/resources/multimc/64x64/plugin-red.png b/resources/multimc/64x64/plugin-red.png Binary files differnew file mode 100644 index 00000000..55d1a42a --- /dev/null +++ b/resources/multimc/64x64/plugin-red.png diff --git a/resources/multimc/64x64/resourcepacks.png b/resources/multimc/64x64/resourcepacks.png Binary files differnew file mode 100644 index 00000000..fb874e7d --- /dev/null +++ b/resources/multimc/64x64/resourcepacks.png diff --git a/resources/multimc/64x64/screenshots.png b/resources/multimc/64x64/screenshots.png Binary files differnew file mode 100644 index 00000000..af18e39c --- /dev/null +++ b/resources/multimc/64x64/screenshots.png diff --git a/resources/multimc/multimc.qrc b/resources/multimc/multimc.qrc index 1df22c29..ad5ae5a4 100644 --- a/resources/multimc/multimc.qrc +++ b/resources/multimc/multimc.qrc @@ -25,6 +25,15 @@ <file>32x32/bug.png</file> <file>48x48/bug.png</file> <file>64x64/bug.png</file> + + <!-- Screenshots. Our own. --> + <!-- frame is adapted and simplified from http://www.wpclipart.com/page_frames/picture_frames/golden_picture_frame.png.html --> + <file>16x16/screenshots.png</file> + <file>22x22/screenshots.png</file> + <file>32x32/screenshots.png</file> + <file>48x48/screenshots.png</file> + <file>64x64/screenshots.png</file> + <file>scalable/screenshots.svg</file> <!-- Patron logo. (C) 2014 Patreon, Inc., http://www.patreon.com/toolbox?ftyp=media --> <file>16x16/patreon.png</file> @@ -102,6 +111,30 @@ <file>32x32/status-good.png</file> <file>48x48/status-good.png</file> <file>64x64/status-good.png</file> + + <!-- Plugin (blue recolor), CC-BY-SA 3.0, Oxygen icons. --> + <file>16x16/plugin-blue.png</file> + <file>24x24/plugin-blue.png</file> + <file>32x32/plugin-blue.png</file> + <file>64x64/plugin-blue.png</file> + + <!-- Plugin (red recolor), CC-BY-SA 3.0, Oxygen icons. --> + <file>16x16/plugin-red.png</file> + <file>24x24/plugin-red.png</file> + <file>32x32/plugin-red.png</file> + <file>64x64/plugin-red.png</file> + + <!-- Plugin (green original), CC-BY-SA 3.0, Oxygen icons. --> + <file>16x16/plugin-green.png</file> + <file>24x24/plugin-green.png</file> + <file>32x32/plugin-green.png</file> + <file>64x64/plugin-green.png</file> + + <!-- Resource packs, CC-BY-SA 3.0, Oxygen icons. --> + <file>16x16/resourcepacks.png</file> + <file>24x24/resourcepacks.png</file> + <file>32x32/resourcepacks.png</file> + <file>64x64/resourcepacks.png</file> <!-- Refresh, CC-BY-SA 3.0, Oxygen icons. --> <file>16x16/refresh.png</file> diff --git a/resources/multimc/scalable/screenshots.svg b/resources/multimc/scalable/screenshots.svg new file mode 100644 index 00000000..a3d4d8e2 --- /dev/null +++ b/resources/multimc/scalable/screenshots.svg @@ -0,0 +1,1231 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1100" + height="1100" + id="svg2" + version="1.1" + inkscape:version="0.48.5 r10040" + sodipodi:docname="screenshots.svg" + inkscape:export-filename="/home/peterix/minecraft/src/MultiMC5/resources/multimc/16x16/screenshots.png" + inkscape:export-xdpi="1.3099999" + inkscape:export-ydpi="1.3099999"> + <title + id="title3887">Golden Picture Frame</title> + <defs + id="defs4"> + <linearGradient + id="linearGradient3871"> + <stop + id="stop3873" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /> + <stop + style="stop-color:#604d00;stop-opacity:1;" + offset="0.24157524" + id="stop3875" /> + <stop + id="stop3877" + offset="1" + style="stop-color:#745d00;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient3831"> + <stop + style="stop-color:#fff2be;stop-opacity:1;" + offset="0" + id="stop3835" /> + <stop + id="stop3839" + offset="0.5" + style="stop-color:#6a5500;stop-opacity:1;" /> + <stop + id="stop3837" + offset="1" + style="stop-color:#745d00;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient3817"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop3819" /> + <stop + id="stop3825" + offset="0.39502749" + style="stop-color:#604d00;stop-opacity:1;" /> + <stop + style="stop-color:#745d00;stop-opacity:1;" + offset="1" + id="stop3821" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3831" + id="linearGradient3821" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.3556705,0,0,2.3556705,-1198.3938,-507.60896)" + x1="568.4173" + y1="501.39673" + x2="588.28278" + y2="502.30829" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3817" + id="linearGradient3823" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.3556705,0,0,2.3556705,-1198.3938,-507.60896)" + x1="731.26221" + y1="376.98492" + x2="731.15552" + y2="364.66559" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3871" + id="linearGradient3825" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.3556705,0,0,2.3556705,-1198.3938,-507.60896)" + x1="883.03296" + y1="502.69846" + x2="897.77411" + y2="502.96545" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3831" + id="linearGradient3827" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.3556705,0,0,2.3556705,-1198.3938,-507.60896)" + x1="723.38055" + y1="621.29663" + x2="721.71783" + y2="661.40479" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.16698148" + inkscape:cx="-510.5746" + inkscape:cy="-1131.4434" + inkscape:document-units="px" + inkscape:current-layer="g3807" + showgrid="false" + inkscape:window-width="1612" + inkscape:window-height="1026" + inkscape:window-x="1677" + inkscape:window-y="-4" + inkscape:window-maximized="1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:snap-bbox="true" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-midpoints="true" + inkscape:snap-bbox-edge-midpoints="true" /> + <g + inkscape:label="Capa 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(31.270747,-9.54776)"> + <g + id="g3017" + transform="matrix(-1.3082428,0,0,1.1906703,1197.3531,-130.52295)"> + <g + id="g3807" + transform="translate(0,-102.53568)"> + <rect + y="365.48618" + x="555.12488" + height="287.45703" + width="344.34955" + id="rect3017" + style="fill:#000000;fill-opacity:0.78921569" + transform="matrix(2.3556705,0,0,2.3556705,-1198.3938,-507.60896)" /> + <rect + y="352.5882" + x="141.80692" + height="641.88568" + width="768.85199" + id="rect3019" + style="fill:#000000;fill-opacity:1" /> + <rect + style="fill:#fff6d5;fill-opacity:1;stroke:#502d16;stroke-width:2.35567045" + id="rect3013" + width="723.00299" + height="599.56348" + x="164.73138" + y="374.21686" /> + <rect + style="fill:none;stroke:#808000;stroke-width:2.35567045" + id="rect3009" + width="603.09033" + height="469.07022" + x="224.68771" + y="438.99588" /> + <rect + style="fill:#999999;stroke:none" + id="rect3015" + width="553.71448" + height="419.69443" + x="249.37567" + y="463.68384" /> + <path + id="path3802" + d="m 120.66044,334.92569 0,677.18161 811.16043,0 0,-677.18161 -811.16043,0 z m 21.12742,17.66753 768.90559,0 0,641.8466 -768.90559,0 0,-641.8466 z" + style="fill:#ffffff;fill-opacity:1;stroke:#502d16;stroke-width:2.35567045" + inkscape:connector-curvature="0" /> + <path + style="fill:#554400;fill-opacity:1" + d="m 559.94852,357.66235 0,287.46875 344.34375,0 -0.43711,-287.04864 z m 8.96875,7.5 326.40625,0 0,272.46875 -326.40625,0 z" + id="rect3797" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccc" + transform="matrix(2.3556705,0,0,2.3556705,-1198.3938,-507.60896)" /> + <path + style="fill:url(#linearGradient3821);fill-opacity:1;stroke:none" + d="m 164.75566,973.3124 0,-599.59176 -22.9678,-21.12742 0,641.8466 z" + id="path3815" + inkscape:connector-curvature="0" /> + <path + style="fill:url(#linearGradient3823);fill-opacity:1;stroke:none" + d="m 164.75566,373.72064 722.96999,0 22.9678,-21.12742 -768.90559,0 z" + id="path3813" + inkscape:connector-curvature="0" /> + <path + style="fill:url(#linearGradient3825);fill-opacity:1;stroke:none" + d="m 887.72565,373.72064 0,599.59176 22.9678,21.12742 0,-641.8466 z" + id="path3811" + inkscape:connector-curvature="0" /> + <path + style="fill:url(#linearGradient3827);fill-opacity:1;stroke:none" + d="m 887.72565,973.3124 -722.96999,0 -22.9678,21.12742 768.90559,0 z" + id="rect3804" + inkscape:connector-curvature="0" /> + <image + y="463.68384" + x="-803.09015" + id="image3837" + xlink:href="data:image/png;base64,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 ++1s8lHyeawAAAABJRU5ErkJggg== +" + height="419.69443" + width="553.71448" + transform="scale(-1,1)" /> + </g> + </g> + </g> + <metadata + id="metadata40"> + <rdf:RDF> + <cc:Work> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <cc:license + rdf:resource="http://creativecommons.org/licenses/publicdomain/" /> + <dc:publisher> + <cc:Agent + rdf:about="http://openclipart.org/"> + <dc:title>Open Clip Art Library</dc:title> + </cc:Agent> + </dc:publisher> + <dc:title>Golden Picture Frame</dc:title> + <dc:date>2012-05-24T10:08:07</dc:date> + <dc:description>Golden picture frame, Landscape</dc:description> + <dc:source>http://openclipart.org/detail/170182/golden-picture-frame-by-tasper</dc:source> + <dc:creator> + <cc:Agent> + <dc:title>tasper</dc:title> + </cc:Agent> + </dc:creator> + <dc:subject> + <rdf:Bag> + <rdf:li>clip art</rdf:li> + <rdf:li>clipart</rdf:li> + <rdf:li>frame</rdf:li> + <rdf:li>golden</rdf:li> + <rdf:li>landscape</rdf:li> + <rdf:li>photo</rdf:li> + <rdf:li>picture</rdf:li> + </rdf:Bag> + </dc:subject> + <dc:contributor> + <cc:Agent> + <dc:title>edited by Paul Sherman</dc:title> + </cc:Agent> + </dc:contributor> + </cc:Work> + <cc:License + rdf:about="http://creativecommons.org/licenses/publicdomain/"> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Reproduction" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Distribution" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> + </cc:License> + </rdf:RDF> + </metadata> +</svg> diff --git a/resources/versions/LWJGL/2.9.0.json b/resources/versions/LWJGL/2.9.0.json new file mode 100644 index 00000000..5dbd624e --- /dev/null +++ b/resources/versions/LWJGL/2.9.0.json @@ -0,0 +1,45 @@ + { + "fileId": "org.lwjgl", + "name": "LWJGL", + "version": "2.9.0", + "+libraries": [ + { + "name": "net.java.jinput:jinput:2.0.5" + }, + { + "name": "net.java.jinput:jinput-platform:2.0.5", + "natives": { + "linux": "natives-linux", + "windows": "natives-windows", + "osx": "natives-osx" + }, + "extract": { + "exclude": [ + "META-INF/" + ] + } + }, + { + "name": "net.java.jutils:jutils:1.0.0" + }, + { + "name": "org.lwjgl.lwjgl:lwjgl:2.9.0" + }, + { + "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.0" + }, + { + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.0", + "natives": { + "linux": "natives-linux", + "windows": "natives-windows", + "osx": "natives-osx" + }, + "extract": { + "exclude": [ + "META-INF/" + ] + } + } + ] +} diff --git a/resources/versions/LWJGL/2.9.1-nightly-20130708-debug3.json b/resources/versions/LWJGL/2.9.1-nightly-20130708-debug3.json new file mode 100644 index 00000000..7265b3b0 --- /dev/null +++ b/resources/versions/LWJGL/2.9.1-nightly-20130708-debug3.json @@ -0,0 +1,45 @@ +{ + "fileId": "org.lwjgl", + "name": "LWJGL", + "version": "2.9.1-nightly-20130708-debug3", + "+libraries": [ + { + "name": "net.java.jinput:jinput:2.0.5" + }, + { + "name": "net.java.jinput:jinput-platform:2.0.5", + "natives": { + "linux": "natives-linux", + "windows": "natives-windows", + "osx": "natives-osx" + }, + "extract": { + "exclude": [ + "META-INF/" + ] + } + }, + { + "name": "net.java.jutils:jutils:1.0.0" + }, + { + "name": "org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20130708-debug3" + }, + { + "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.1-nightly-20130708-debug3" + }, + { + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.1-nightly-20130708-debug3", + "natives": { + "linux": "natives-linux", + "windows": "natives-windows", + "osx": "natives-osx" + }, + "extract": { + "exclude": [ + "META-INF/" + ] + } + } + ] +} diff --git a/resources/versions/LWJGL/2.9.1.json b/resources/versions/LWJGL/2.9.1.json new file mode 100644 index 00000000..e7f5e947 --- /dev/null +++ b/resources/versions/LWJGL/2.9.1.json @@ -0,0 +1,45 @@ + { + "fileId": "org.lwjgl", + "name": "LWJGL", + "version": "2.9.1", + "+libraries": [ + { + "name": "net.java.jinput:jinput:2.0.5" + }, + { + "name": "net.java.jinput:jinput-platform:2.0.5", + "natives": { + "linux": "natives-linux", + "windows": "natives-windows", + "osx": "natives-osx" + }, + "extract": { + "exclude": [ + "META-INF/" + ] + } + }, + { + "name": "net.java.jutils:jutils:1.0.0" + }, + { + "name": "org.lwjgl.lwjgl:lwjgl:2.9.1" + }, + { + "name": "org.lwjgl.lwjgl:lwjgl_util:2.9.1" + }, + { + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.1", + "natives": { + "linux": "natives-linux", + "windows": "natives-windows", + "osx": "natives-osx" + }, + "extract": { + "exclude": [ + "META-INF/" + ] + } + } + ] +} diff --git a/resources/versions/minecraft.json b/resources/versions/minecraft.json new file mode 100644 index 00000000..58b153c2 --- /dev/null +++ b/resources/versions/minecraft.json @@ -0,0 +1,668 @@ +{ +"versions": [ + { + "id": "1.5.2", + "checksum": "6897c3287fb971c9f362eb3ab20f5ddd", + "releaseTime": "2013-04-25T17:45:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.5.1", + "checksum": "5c1219d869b87d233de3033688ec7567", + "releaseTime": "2013-03-20T12:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.5", + "checksum": "fd11cbc5b01aae1d62cff0145171f3d9", + "releaseTime": "2013-03-07T00:00:00+02:00", + "type": "snapshot", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.4.7", + "checksum": "8e80fb01b321c6b3c7efca397a3eea35", + "releaseTime": "2012-12-28T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.4.6", + "checksum": "48677dc4c2b98c29918722b5ab27b4fd", + "releaseTime": "2012-12-20T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.4.5", + "checksum": "b15e2b2b6b4629f0d99a95b6b44412a0", + "releaseTime": "2012-11-20T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.4.4", + "checksum": "7aa46c8058cba2f38e9d2ddddcc77c72", + "releaseTime": "2012-11-14T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.4.3", + "checksum": "9cc3295931edb6339f22989fe1b612a6", + "releaseTime": "2012-11-01T00:00:00+02:00", + "type": "snapshot", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.4.2", + "checksum": "771175c01778ea67395bc6919a5a9dc5", + "releaseTime": "2012-10-25T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.4.1", + "checksum": "542621a5298659dc65f383f35170fc4c", + "releaseTime": "2012-10-23T00:00:00+02:00", + "type": "snapshot", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.4", + "checksum": "32a654388b54d3e4bb29c1a46e7d6a12", + "releaseTime": "2012-10-19T00:00:00+02:00", + "type": "snapshot", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.3.2", + "checksum": "969699f13e5bbe7f12e40ac4f32b7d9a", + "releaseTime": "2012-08-16T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.3.1", + "checksum": "266ccbc9798afd2eadf3d6c01b4c562a", + "releaseTime": "2012-08-01T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.3", + "checksum": "a6effac1eaccf5d429aae340cf95ed5d", + "releaseTime": "2012-07-26T00:00:00+02:00", + "type": "snapshot", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.2.5", + "checksum": "8e8778078a175a33603a585257f28563", + "releaseTime": "2012-03-30T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.2.4", + "checksum": "25423eab6d8707f96cc6ad8a21a7250a", + "releaseTime": "2012-03-22T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.2.3", + "checksum": "12f6c4b1bdcc63f029e3c088a364b8e4", + "releaseTime": "2012-03-02T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.2.2", + "checksum": "6189e96efaea11e5164b4a4755574324", + "releaseTime": "2012-03-01T00:00:01+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.2.1", + "checksum": "97067a603eba2b6eb75d3194f81f6bcd", + "releaseTime": "2012-03-01T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.1", + "checksum": "e92302d2acdba7c97e0d8df1e10d2006", + "releaseTime": "2012-01-12T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "1.0", + "checksum": "3820d222b95d0b8c520d9596a756a6e6", + "releaseTime": "2011-11-18T00:00:00+02:00", + "type": "release", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.8.1", + "checksum": "f8c5a2ccd3bc996792bbe436d8cc08bc", + "releaseTime": "2011-09-19T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.8", + "checksum": "a59a9fd4c726a573b0a2bdd10d857f59", + "releaseTime": "2011-09-15T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.7.3", + "checksum": "eae3353fdaa7e10a59b4cb5b45bfa10d", + "releaseTime": "2011-07-08T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.7.2", + "checksum": "dd9215ab1141170d4871f42bff4ab302", + "releaseTime": "2011-07-01T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.7", + "checksum": "682419e9ed1a236c3067822d53cda1e4", + "releaseTime": "2011-06-30T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.6.6", + "checksum": "ce80072464433cd5b05d505aa8ff29d1", + "releaseTime": "2011-05-31T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.6.5", + "checksum": "2aba888864b32038c8d22ee5df71b7c8", + "releaseTime": "2011-05-28T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.6.4", + "checksum": "5c4df6f120336f113180698613853dba", + "releaseTime": "2011-05-26T00:00:04+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.6.3", + "checksum": "efc2becca965e4f8feb5b4210c6a4fd1", + "releaseTime": "2011-05-26T00:00:03+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.6.2", + "checksum": "01330b1c930102a683a4dd8d792e632e", + "releaseTime": "2011-05-26T00:00:02+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.6.1", + "checksum": "a7e82c441a57ef4068c533f4d777336a", + "releaseTime": "2011-05-26T00:00:01+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.6", + "checksum": "d531e221227a65392259d3141893280d", + "releaseTime": "2011-05-26T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.5_01", + "checksum": "d02fa9998e30693d8d989d5f88cf0040", + "releaseTime": "2011-04-20T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.5", + "checksum": "24289130902822d73f8722b52bc07cdb", + "releaseTime": "2011-04-19T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.4_01", + "checksum": "9379e54b581ba4ef3acc3e326e87db91", + "releaseTime": "2011-04-05T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.4", + "checksum": "71e64b61175b371ed148b385f2d14ebf", + "releaseTime": "2011-03-31T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.3_01", + "checksum": "4203826f35e1036f089919032c3d19d1", + "releaseTime": "2011-02-23T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.3b", + "checksum": "de2164df461d028229ed2e101181bbd4", + "releaseTime": "2011-02-22T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.2_02", + "checksum": "1736c5ba4f63a981220c2a18a4120180", + "releaseTime": "2011-01-21T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.2_01", + "checksum": "486d83ec00554b45ffa21af2faa0116a", + "releaseTime": "2011-01-14T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.2", + "checksum": "6426223efe23c3931a4ef89685be3349", + "releaseTime": "2011-01-13T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.1_02", + "checksum": "7d547e495a770c62054ef136add43034", + "releaseTime": "2010-12-22T00:00:01+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.1_01", + "checksum": "1f9331f2bfca81b6ce2acdfc1f105837", + "releaseTime": "2010-12-22T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.0.2", + "checksum": "d200c465b8c167cc8df6537531fc9a48", + "releaseTime": "2010-12-21T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.0_01", + "checksum": "03bd20b870dbbd121de5dca98af4e1ce", + "releaseTime": "2010-12-20T00:00:01+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "b1.0", + "checksum": "5f8733dbbf09b4e7c874661a3c29c239", + "releaseTime": "2010-12-20T00:00:00+02:00", + "type": "old_beta", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "a1.2.6", + "checksum": "ddd5e39467f28d1ea1a03b4d9e790867", + "releaseTime": "2010-12-03T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "a1.2.5", + "checksum": "7d3a43037190970ff2e11153b5718b74", + "releaseTime": "2010-12-01T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "a1.2.4_01", + "checksum": "0a1cc8c668faa6dc93fc418e8b4b097a", + "releaseTime": "2010-11-30T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "a1.2.3_04", + "checksum": "b2c25a753c82a1cd228ce71469829dc1", + "releaseTime": "2010-11-26T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "a1.2.3_02", + "checksum": "3ad4808ef2ac3b65d10305315260da03", + "releaseTime": "2010-11-25T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "a1.2.3_01", + "checksum": "70cbab762b17c5b11fefea9b12564119", + "releaseTime": "2010-11-24T00:00:01+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "a1.2.3", + "checksum": "25f053114e34b915e675f82d58f08711", + "releaseTime": "2010-11-24T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "a1.2.2b", + "checksum": "6250fb17f8898c4d970d6bd03c229177", + "releaseTime": "2010-11-10T00:00:01+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "a1.2.2a", + "checksum": "0f9fe018b344fd9dd849005f9bdca803", + "releaseTime": "2010-11-10T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "texturepacks"] + }, + { + "id": "a1.2.1_01", + "checksum": "0a496e44a7b4e2f493b5893d8e5845bd", + "releaseTime": "2010-11-05T00:00:01+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.2.1", + "checksum": "0a496e44a7b4e2f493b5893d8e5845bd", + "releaseTime": "2010-11-05T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.2.0_02", + "checksum": "f5bcb4d0c0e78bc220f164b89ae9bd60", + "releaseTime": "2010-11-04T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.2.0_01", + "checksum": "b2e9333e967cb89488884c2e5c715d4f", + "releaseTime": "2010-10-31T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.2.0", + "checksum": "44c384dae02390f700458b95d82c3e2a", + "releaseTime": "2010-10-30T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.1.2_01", + "checksum": "94346e1b8f6ad0e4a284314f0e29207b", + "releaseTime": "2010-09-23T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.1.2", + "checksum": "72ba1f834327805cb44164a42b331522", + "releaseTime": "2010-09-20T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.1.0", + "checksum": "891fd93e04f5daaf35d73c58e45c01b1", + "releaseTime": "2010-09-13T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.0.17_04", + "checksum": "16ed7dc58244772847991e504afcf02f", + "releaseTime": "2010-08-23T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.0.17_02", + "checksum": "d89760b0871ef61a55c9f336c0439d58", + "releaseTime": "2010-08-20T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.0.16", + "checksum": "6bbde02c13aed5766275f4398ede6aae", + "releaseTime": "2010-08-12T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.0.15", + "checksum": "ade257d2080d56fa983763f9c701fa14", + "releaseTime": "2010-08-04T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.0.14", + "checksum": "227d0c6fa896a231de6269a074c9a458", + "releaseTime": "2010-07-30T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.0.11", + "checksum": "6f1b1dd157fa0df39760f5be3eab01b0", + "releaseTime": "2010-07-23T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.0.5_01", + "checksum": "ae5f606caa18222e7568819c910ee423", + "releaseTime": "2010-07-13T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "y", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "a1.0.4", + "checksum": "13ce7935c3670e7494e26b2704bfa3e9", + "releaseTime": "2010-07-09T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "ax", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "inf-20100618", + "checksum": "f5f5aa34760facc10486e906a7c60196", + "releaseTime": "2010-06-16T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "net.minecraft.client.d", + "appletClass": "net.minecraft.client.MinecraftApplet", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "c0.30_01c", + "checksum": "fcfd7f83a6b27503cf48202381a5adf2", + "releaseTime": "2009-12-22T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "com.mojang.minecraft.l", + "appletClass": "com.mojang.minecraft.MinecraftApplet", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "c0.0.13a_03", + "checksum": "a9527cb5aef198e0f53e235ebe13dc75", + "releaseTime": "2009-05-22T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "com.mojang.minecraft.c", + "appletClass": "com.mojang.minecraft.MinecraftApplet", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "c0.0.13a", + "checksum": "3617fbf5fbfd2b837ebf5ceb63584908", + "releaseTime": "2009-05-31T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "com.mojang.minecraft.Minecraft", + "appletClass": "com.mojang.minecraft.MinecraftApplet", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "c0.0.11a", + "checksum": "a6e03c2eaf74709facc0d2477648e999", + "releaseTime": "2009-05-17T00:00:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "com.mojang.minecraft.Minecraft", + "appletClass": "com.mojang.minecraft.MinecraftApplet", + "+traits": ["legacyLaunch", "no-texturepacks"] + }, + { + "id": "rd-161348", + "checksum": "80882b8936a5c8d91500838a6660b504", + "releaseTime": "2009-05-16T13:48:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "com.mojang.minecraft.RubyDung", + "+traits": ["no-texturepacks"] + }, + { + "id": "rd-160052", + "checksum": "24c5cc99a2a612697ed2f7d5d04242fe", + "releaseTime": "2009-05-16T00:52:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "com.mojang.rubydung.RubyDung", + "+traits": ["no-texturepacks"] + }, + { + "id": "rd-132328", + "checksum": "70e33a81c541b13a477e68c1207124eb", + "releaseTime": "2009-05-13T23:28:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "com.mojang.rubydung.RubyDung", + "+traits": ["no-texturepacks"] + }, + { + "id": "rd-132211", + "checksum": "99fdaea10c494b9c3c3254636b98b799", + "releaseTime": "2009-05-13T22:11:00+02:00", + "type": "old_alpha", + "processArguments": "legacy", + "mainClass": "com.mojang.rubydung.RubyDung", + "+traits": ["no-texturepacks"] + } +] +} diff --git a/resources/versions/versions.qrc b/resources/versions/versions.qrc new file mode 100644 index 00000000..6ba05744 --- /dev/null +++ b/resources/versions/versions.qrc @@ -0,0 +1,11 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> + <qresource prefix="/versions"> + <!-- builtin legacy versions --> + <file>minecraft.json</file> + <!-- LWJGL --> + <file>LWJGL/2.9.0.json</file> + <file>LWJGL/2.9.1-nightly-20130708-debug3.json</file> + <file>LWJGL/2.9.1.json</file> + </qresource> +</RCC> |