summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--BuildConfig.cpp.in2
-rw-r--r--BuildConfig.h4
-rw-r--r--CMakeLists.txt297
-rw-r--r--HandleCrash.cpp376
-rw-r--r--HandleCrash.h18
-rw-r--r--MultiMC.cpp74
-rw-r--r--MultiMC.h18
-rw-r--r--WinBacktrace.cpp77
-rw-r--r--WinBacktrace.h44
-rw-r--r--changelog.yaml12
-rw-r--r--depends/launcher/CMakeLists.txt2
-rw-r--r--depends/launcher/net/minecraft/Launcher.java15
-rw-r--r--depends/launcher/org/multimc/LegacyFrame.java (renamed from depends/launcher/org/multimc/legacy/LegacyFrame.java)2
-rw-r--r--depends/launcher/org/multimc/legacy/LegacyLauncher.java5
-rw-r--r--depends/launcher/org/multimc/onesix/OneSixLauncher.java331
-rw-r--r--depends/util/src/pathutils.cpp4
-rw-r--r--gui/ConsoleWindow.cpp271
-rw-r--r--gui/ConsoleWindow.h45
-rw-r--r--gui/ConsoleWindow.ui93
-rw-r--r--gui/MainWindow.cpp317
-rw-r--r--gui/MainWindow.h19
-rw-r--r--gui/MainWindow.ui86
-rw-r--r--gui/dialogs/CopyInstanceDialog.cpp1
-rw-r--r--gui/dialogs/EditNotesDialog.cpp43
-rw-r--r--gui/dialogs/EditNotesDialog.ui77
-rw-r--r--gui/dialogs/InstanceSettings.cpp243
-rw-r--r--gui/dialogs/LegacyModEditDialog.cpp393
-rw-r--r--gui/dialogs/LegacyModEditDialog.h78
-rw-r--r--gui/dialogs/LegacyModEditDialog.ui321
-rw-r--r--gui/dialogs/LwjglSelectDialog.cpp2
-rw-r--r--gui/dialogs/ModEditDialogCommon.cpp21
-rw-r--r--gui/dialogs/ModEditDialogCommon.h21
-rw-r--r--gui/dialogs/NewInstanceDialog.cpp10
-rw-r--r--gui/dialogs/NewInstanceDialog.h2
-rw-r--r--gui/dialogs/OneSixModEditDialog.cpp399
-rw-r--r--gui/dialogs/OneSixModEditDialog.ui310
-rw-r--r--gui/dialogs/ScreenshotDialog.cpp78
-rw-r--r--gui/dialogs/ScreenshotDialog.h40
-rw-r--r--gui/dialogs/ScreenshotDialog.ui110
-rw-r--r--gui/dialogs/SettingsDialog.cpp18
-rw-r--r--gui/dialogs/SettingsDialog.h2
-rw-r--r--gui/dialogs/VersionSelectDialog.cpp2
-rw-r--r--gui/groupview/InstanceDelegate.cpp2
-rw-r--r--gui/pagedialog/PageDialog.cpp58
-rw-r--r--gui/pagedialog/PageDialog.h (renamed from gui/dialogs/EditNotesDialog.h)27
-rw-r--r--gui/pages/BasePage.h48
-rw-r--r--gui/pages/BasePageProvider.h (renamed from logic/NostalgiaInstance.h)17
-rw-r--r--gui/pages/InstanceSettingsPage.cpp229
-rw-r--r--gui/pages/InstanceSettingsPage.h (renamed from gui/dialogs/InstanceSettings.h)49
-rw-r--r--gui/pages/InstanceSettingsPage.ui (renamed from gui/dialogs/InstanceSettings.ui)353
-rw-r--r--gui/pages/LegacyJarModPage.cpp208
-rw-r--r--gui/pages/LegacyJarModPage.h63
-rw-r--r--gui/pages/LegacyJarModPage.ui158
-rw-r--r--gui/pages/LegacyUpgradePage.cpp39
-rw-r--r--gui/pages/LegacyUpgradePage.h48
-rw-r--r--gui/pages/LegacyUpgradePage.ui58
-rw-r--r--gui/pages/LogPage.cpp137
-rw-r--r--gui/pages/LogPage.h72
-rw-r--r--gui/pages/LogPage.ui76
-rw-r--r--gui/pages/ModFolderPage.cpp149
-rw-r--r--gui/pages/ModFolderPage.h63
-rw-r--r--gui/pages/ModFolderPage.ui118
-rw-r--r--gui/pages/NotesPage.cpp35
-rw-r--r--gui/pages/NotesPage.h45
-rw-r--r--gui/pages/NotesPage.ui46
-rw-r--r--gui/pages/ResourcePackPage.h19
-rw-r--r--gui/pages/ScreenshotsPage.cpp270
-rw-r--r--gui/pages/ScreenshotsPage.h68
-rw-r--r--gui/pages/ScreenshotsPage.ui103
-rw-r--r--gui/pages/TexturePackPage.h17
-rw-r--r--gui/pages/VersionPage.cpp394
-rw-r--r--gui/pages/VersionPage.h (renamed from gui/dialogs/OneSixModEditDialog.h)43
-rw-r--r--gui/pages/VersionPage.ui197
-rw-r--r--gui/widgets/IconLabel.cpp2
-rw-r--r--gui/widgets/LineSeparator.h4
-rw-r--r--gui/widgets/PageContainer.cpp196
-rw-r--r--gui/widgets/PageContainer.h61
-rw-r--r--gui/widgets/PageContainer_p.h106
-rw-r--r--gui/widgets/ServerStatus.cpp2
-rw-r--r--logic/BaseInstaller.cpp9
-rw-r--r--logic/BaseInstaller.h2
-rw-r--r--logic/BaseInstance.cpp16
-rw-r--r--logic/BaseInstance.h27
-rw-r--r--logic/BaseInstance_p.h1
-rw-r--r--logic/BaseVersion.h4
-rw-r--r--logic/BaseVersionList.cpp (renamed from logic/lists/BaseVersionList.cpp)2
-rw-r--r--logic/BaseVersionList.h (renamed from logic/lists/BaseVersionList.h)0
-rw-r--r--logic/InstanceFactory.cpp79
-rw-r--r--logic/InstanceLauncher.cpp2
-rw-r--r--logic/InstanceList.cpp (renamed from logic/lists/InstanceList.cpp)28
-rw-r--r--logic/InstanceList.h (renamed from logic/lists/InstanceList.h)0
-rw-r--r--logic/LegacyFTBInstance.cpp5
-rw-r--r--logic/LegacyFTBInstance.h1
-rw-r--r--logic/LegacyInstance.cpp57
-rw-r--r--logic/LegacyInstance.h14
-rw-r--r--logic/LegacyUpdate.cpp85
-rw-r--r--logic/LegacyUpdate.h9
-rw-r--r--logic/LwjglVersionList.cpp (renamed from logic/lists/LwjglVersionList.cpp)0
-rw-r--r--logic/LwjglVersionList.h (renamed from logic/lists/LwjglVersionList.h)0
-rw-r--r--logic/MMCJson.cpp24
-rw-r--r--logic/MMCJson.h19
-rw-r--r--logic/MinecraftProcess.cpp11
-rw-r--r--logic/MinecraftVersion.h89
-rw-r--r--logic/ModList.cpp1
-rw-r--r--logic/NostalgiaInstance.cpp36
-rw-r--r--logic/OneSixFTBInstance.cpp16
-rw-r--r--logic/OneSixFTBInstance.h1
-rw-r--r--logic/OneSixInstance.cpp301
-rw-r--r--logic/OneSixInstance.h44
-rw-r--r--logic/OneSixInstance_p.h13
-rw-r--r--logic/OneSixUpdate.cpp357
-rw-r--r--logic/OneSixUpdate.h21
-rw-r--r--logic/OneSixVersionBuilder.cpp252
-rw-r--r--logic/OneSixVersionBuilder.h47
-rw-r--r--logic/URNResolver.cpp98
-rw-r--r--logic/URNResolver.h18
-rw-r--r--logic/VersionFile.h127
-rw-r--r--logic/VersionFilterData.cpp68
-rw-r--r--logic/VersionFilterData.h26
-rw-r--r--logic/VersionFinal.cpp377
-rw-r--r--logic/forge/ForgeInstaller.cpp (renamed from logic/ForgeInstaller.cpp)194
-rw-r--r--logic/forge/ForgeInstaller.h (renamed from logic/ForgeInstaller.h)22
-rw-r--r--logic/forge/ForgeMirror.h (renamed from logic/net/ForgeMirror.h)0
-rw-r--r--logic/forge/ForgeMirrors.cpp (renamed from logic/net/ForgeMirrors.cpp)0
-rw-r--r--logic/forge/ForgeMirrors.h (renamed from logic/net/ForgeMirrors.h)8
-rw-r--r--logic/forge/ForgeVersion.cpp55
-rw-r--r--logic/forge/ForgeVersion.h33
-rw-r--r--logic/forge/ForgeVersionList.cpp (renamed from logic/lists/ForgeVersionList.cpp)84
-rw-r--r--logic/forge/ForgeVersionList.h (renamed from logic/lists/ForgeVersionList.h)53
-rw-r--r--logic/forge/ForgeXzDownload.cpp (renamed from logic/net/ForgeXzDownload.cpp)0
-rw-r--r--logic/forge/ForgeXzDownload.h (renamed from logic/net/ForgeXzDownload.h)4
-rw-r--r--logic/forge/LegacyForge.cpp (renamed from logic/LegacyForge.cpp)0
-rw-r--r--logic/forge/LegacyForge.h (renamed from logic/LegacyForge.h)2
-rw-r--r--logic/java/JavaChecker.cpp (renamed from logic/JavaChecker.cpp)0
-rw-r--r--logic/java/JavaChecker.h (renamed from logic/JavaChecker.h)0
-rw-r--r--logic/java/JavaCheckerJob.cpp (renamed from logic/JavaCheckerJob.cpp)0
-rw-r--r--logic/java/JavaCheckerJob.h (renamed from logic/JavaCheckerJob.h)0
-rw-r--r--logic/java/JavaUtils.cpp (renamed from logic/JavaUtils.cpp)18
-rw-r--r--logic/java/JavaUtils.h (renamed from logic/JavaUtils.h)2
-rw-r--r--logic/java/JavaVersionList.cpp (renamed from logic/lists/JavaVersionList.cpp)10
-rw-r--r--logic/java/JavaVersionList.h (renamed from logic/lists/JavaVersionList.h)4
-rw-r--r--logic/lists/MinecraftVersionList.cpp290
-rw-r--r--logic/liteloader/LiteLoaderInstaller.cpp (renamed from logic/LiteLoaderInstaller.cpp)8
-rw-r--r--logic/liteloader/LiteLoaderInstaller.h (renamed from logic/LiteLoaderInstaller.h)5
-rw-r--r--logic/liteloader/LiteLoaderVersionList.cpp (renamed from logic/lists/LiteLoaderVersionList.cpp)0
-rw-r--r--logic/liteloader/LiteLoaderVersionList.h (renamed from logic/lists/LiteLoaderVersionList.h)6
-rw-r--r--logic/minecraft/InstanceVersion.cpp537
-rw-r--r--logic/minecraft/InstanceVersion.h (renamed from logic/VersionFinal.h)73
-rw-r--r--logic/minecraft/JarMod.cpp56
-rw-r--r--logic/minecraft/JarMod.h18
-rw-r--r--logic/minecraft/MinecraftVersion.cpp143
-rw-r--r--logic/minecraft/MinecraftVersion.h103
-rw-r--r--logic/minecraft/MinecraftVersionList.cpp602
-rw-r--r--logic/minecraft/MinecraftVersionList.h (renamed from logic/lists/MinecraftVersionList.h)42
-rw-r--r--logic/minecraft/OneSixLibrary.cpp (renamed from logic/OneSixLibrary.cpp)79
-rw-r--r--logic/minecraft/OneSixLibrary.h (renamed from logic/OneSixLibrary.h)34
-rw-r--r--logic/minecraft/OneSixRule.cpp (renamed from logic/OneSixRule.cpp)17
-rw-r--r--logic/minecraft/OneSixRule.h (renamed from logic/OneSixRule.h)8
-rw-r--r--logic/minecraft/OpSys.cpp (renamed from logic/OpSys.cpp)0
-rw-r--r--logic/minecraft/OpSys.h (renamed from logic/OpSys.h)0
-rw-r--r--logic/minecraft/ParseUtils.cpp24
-rw-r--r--logic/minecraft/ParseUtils.h14
-rw-r--r--logic/minecraft/RawLibrary.cpp205
-rw-r--r--logic/minecraft/RawLibrary.h64
-rw-r--r--logic/minecraft/VersionBuildError.h58
-rw-r--r--logic/minecraft/VersionBuilder.cpp349
-rw-r--r--logic/minecraft/VersionBuilder.h56
-rw-r--r--logic/minecraft/VersionFile.cpp (renamed from logic/VersionFile.cpp)378
-rw-r--r--logic/minecraft/VersionFile.h103
-rw-r--r--logic/minecraft/VersionPatch.h31
-rw-r--r--logic/minecraft/VersionSource.h9
-rw-r--r--logic/net/NetJob.h1
-rw-r--r--logic/screenshots/ImgurAlbumCreation.cpp4
-rw-r--r--logic/screenshots/ImgurUpload.cpp9
-rw-r--r--logic/screenshots/Screenshot.cpp14
-rw-r--r--logic/screenshots/Screenshot.h16
-rw-r--r--logic/screenshots/ScreenshotList.cpp113
-rw-r--r--logic/screenshots/ScreenshotList.h70
-rw-r--r--logic/tasks/ProgressProvider.h1
-rw-r--r--main.cpp12
-rw-r--r--resources/multimc/16x16/plugin-blue.pngbin0 -> 731 bytes
-rw-r--r--resources/multimc/16x16/plugin-green.pngbin0 -> 702 bytes
-rw-r--r--resources/multimc/16x16/plugin-red.pngbin0 -> 693 bytes
-rw-r--r--resources/multimc/16x16/resourcepacks.pngbin0 -> 1207 bytes
-rw-r--r--resources/multimc/16x16/screenshots.pngbin0 -> 976 bytes
-rw-r--r--resources/multimc/22x22/screenshots.pngbin0 -> 1320 bytes
-rw-r--r--resources/multimc/24x24/plugin-blue.pngbin0 -> 1240 bytes
-rw-r--r--resources/multimc/24x24/plugin-green.pngbin0 -> 1281 bytes
-rw-r--r--resources/multimc/24x24/plugin-red.pngbin0 -> 1170 bytes
-rw-r--r--resources/multimc/24x24/resourcepacks.pngbin0 -> 2000 bytes
-rw-r--r--resources/multimc/32x32/plugin-blue.pngbin0 -> 1708 bytes
-rw-r--r--resources/multimc/32x32/plugin-green.pngbin0 -> 1758 bytes
-rw-r--r--resources/multimc/32x32/plugin-red.pngbin0 -> 1566 bytes
-rw-r--r--resources/multimc/32x32/resourcepacks.pngbin0 -> 2818 bytes
-rw-r--r--resources/multimc/32x32/screenshots.pngbin0 -> 1892 bytes
-rw-r--r--resources/multimc/48x48/screenshots.pngbin0 -> 3010 bytes
-rw-r--r--resources/multimc/64x64/plugin-blue.pngbin0 -> 4406 bytes
-rw-r--r--resources/multimc/64x64/plugin-green.pngbin0 -> 5036 bytes
-rw-r--r--resources/multimc/64x64/plugin-red.pngbin0 -> 4003 bytes
-rw-r--r--resources/multimc/64x64/resourcepacks.pngbin0 -> 6805 bytes
-rw-r--r--resources/multimc/64x64/screenshots.pngbin0 -> 4518 bytes
-rw-r--r--resources/multimc/multimc.qrc33
-rw-r--r--resources/multimc/scalable/screenshots.svg1231
-rw-r--r--resources/versions/LWJGL/2.9.0.json45
-rw-r--r--resources/versions/LWJGL/2.9.1-nightly-20130708-debug3.json45
-rw-r--r--resources/versions/LWJGL/2.9.1.json45
-rw-r--r--resources/versions/minecraft.json668
-rw-r--r--resources/versions/versions.qrc11
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
diff --git a/MultiMC.h b/MultiMC.h
index 00eb97f8..ce3f9238 100644
--- a/MultiMC.h
+++ b/MultiMC.h
@@ -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 &paragraph : 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>&amp;Kill Minecraft</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="closeButton">
- <property name="text">
- <string>&amp;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 &current, 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 &current, 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 &current, 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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;View and upload screenshots for this instance&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&amp;Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="rmJarBtn">
- <property name="text">
- <string>&amp;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 &amp;Up</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="moveJarDownBtn">
- <property name="text">
- <string>Move &amp;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>&amp;Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="rmCoreBtn">
- <property name="text">
- <string>&amp;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>&amp;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>&amp;Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="rmModBtn">
- <property name="text">
- <string>&amp;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>&amp;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>&amp;Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="rmTexPackBtn">
- <property name="text">
- <string>&amp;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>&amp;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 &current,
- 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>&amp;Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="rmModBtn">
- <property name="text">
- <string>&amp;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>&amp;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>&amp;Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="rmResPackBtn">
- <property name="text">
- <string>&amp;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>&amp;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>&amp;Remove</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="moveJarUpBtn">
+ <property name="text">
+ <string>Move &amp;Up</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="moveJarDownBtn">
+ <property name="text">
+ <string>Move &amp;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>&amp;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>&amp;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>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; &lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:18pt; font-weight:600;&quot;&gt;New format is available&lt;/span&gt; &lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;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. &lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;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. &lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Please report any issues on our &lt;a href=&quot;https://github.com/MultiMC/MultiMC5/issues&quot;&gt;&lt;img src=&quot;:/icons/multimc/22x22/bug.png&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/MultiMC/MultiMC5/issues&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#68a0df;&quot;&gt;github issues page&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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 &paragraph : 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 &current, 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 &current, 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>&amp;Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="rmModBtn">
+ <property name="text">
+ <string>&amp;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>&amp;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>&amp;Upload</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="deleteBtn">
+ <property name="text">
+ <string>&amp;Delete</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="renameBtn">
+ <property name="text">
+ <string>&amp;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>&amp;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 &current, 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 &current, 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 &current)
+{
+ 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 &current);
+ 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 &current, qint64 &total) = 0;
virtual bool isRunning() const = 0;
diff --git a/main.cpp b/main.cpp
index 25cfb153..618e6745 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,6 +1,12 @@
#include "MultiMC.h"
#include "gui/MainWindow.h"
+// Crash handling
+#ifdef HANDLE_SEGV
+#include <HandleCrash.h>
+#endif
+
+
int main_gui(MultiMC &app)
{
// show main window
@@ -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
new file mode 100644
index 00000000..b5ab3fce
--- /dev/null
+++ b/resources/multimc/16x16/plugin-blue.png
Binary files differ
diff --git a/resources/multimc/16x16/plugin-green.png b/resources/multimc/16x16/plugin-green.png
new file mode 100644
index 00000000..af0f1166
--- /dev/null
+++ b/resources/multimc/16x16/plugin-green.png
Binary files differ
diff --git a/resources/multimc/16x16/plugin-red.png b/resources/multimc/16x16/plugin-red.png
new file mode 100644
index 00000000..1a97c9c0
--- /dev/null
+++ b/resources/multimc/16x16/plugin-red.png
Binary files differ
diff --git a/resources/multimc/16x16/resourcepacks.png b/resources/multimc/16x16/resourcepacks.png
new file mode 100644
index 00000000..d862f5ca
--- /dev/null
+++ b/resources/multimc/16x16/resourcepacks.png
Binary files differ
diff --git a/resources/multimc/16x16/screenshots.png b/resources/multimc/16x16/screenshots.png
new file mode 100644
index 00000000..460000d4
--- /dev/null
+++ b/resources/multimc/16x16/screenshots.png
Binary files differ
diff --git a/resources/multimc/22x22/screenshots.png b/resources/multimc/22x22/screenshots.png
new file mode 100644
index 00000000..6fb42bbd
--- /dev/null
+++ b/resources/multimc/22x22/screenshots.png
Binary files differ
diff --git a/resources/multimc/24x24/plugin-blue.png b/resources/multimc/24x24/plugin-blue.png
new file mode 100644
index 00000000..250a6260
--- /dev/null
+++ b/resources/multimc/24x24/plugin-blue.png
Binary files differ
diff --git a/resources/multimc/24x24/plugin-green.png b/resources/multimc/24x24/plugin-green.png
new file mode 100644
index 00000000..90603d24
--- /dev/null
+++ b/resources/multimc/24x24/plugin-green.png
Binary files differ
diff --git a/resources/multimc/24x24/plugin-red.png b/resources/multimc/24x24/plugin-red.png
new file mode 100644
index 00000000..68cb8e9d
--- /dev/null
+++ b/resources/multimc/24x24/plugin-red.png
Binary files differ
diff --git a/resources/multimc/24x24/resourcepacks.png b/resources/multimc/24x24/resourcepacks.png
new file mode 100644
index 00000000..68359d39
--- /dev/null
+++ b/resources/multimc/24x24/resourcepacks.png
Binary files differ
diff --git a/resources/multimc/32x32/plugin-blue.png b/resources/multimc/32x32/plugin-blue.png
new file mode 100644
index 00000000..c4ca12e2
--- /dev/null
+++ b/resources/multimc/32x32/plugin-blue.png
Binary files differ
diff --git a/resources/multimc/32x32/plugin-green.png b/resources/multimc/32x32/plugin-green.png
new file mode 100644
index 00000000..770d695e
--- /dev/null
+++ b/resources/multimc/32x32/plugin-green.png
Binary files differ
diff --git a/resources/multimc/32x32/plugin-red.png b/resources/multimc/32x32/plugin-red.png
new file mode 100644
index 00000000..5cda173a
--- /dev/null
+++ b/resources/multimc/32x32/plugin-red.png
Binary files differ
diff --git a/resources/multimc/32x32/resourcepacks.png b/resources/multimc/32x32/resourcepacks.png
new file mode 100644
index 00000000..c14759ef
--- /dev/null
+++ b/resources/multimc/32x32/resourcepacks.png
Binary files differ
diff --git a/resources/multimc/32x32/screenshots.png b/resources/multimc/32x32/screenshots.png
new file mode 100644
index 00000000..4fcd6224
--- /dev/null
+++ b/resources/multimc/32x32/screenshots.png
Binary files differ
diff --git a/resources/multimc/48x48/screenshots.png b/resources/multimc/48x48/screenshots.png
new file mode 100644
index 00000000..03c0059f
--- /dev/null
+++ b/resources/multimc/48x48/screenshots.png
Binary files differ
diff --git a/resources/multimc/64x64/plugin-blue.png b/resources/multimc/64x64/plugin-blue.png
new file mode 100644
index 00000000..24618fd0
--- /dev/null
+++ b/resources/multimc/64x64/plugin-blue.png
Binary files differ
diff --git a/resources/multimc/64x64/plugin-green.png b/resources/multimc/64x64/plugin-green.png
new file mode 100644
index 00000000..668be334
--- /dev/null
+++ b/resources/multimc/64x64/plugin-green.png
Binary files differ
diff --git a/resources/multimc/64x64/plugin-red.png b/resources/multimc/64x64/plugin-red.png
new file mode 100644
index 00000000..55d1a42a
--- /dev/null
+++ b/resources/multimc/64x64/plugin-red.png
Binary files differ
diff --git a/resources/multimc/64x64/resourcepacks.png b/resources/multimc/64x64/resourcepacks.png
new file mode 100644
index 00000000..fb874e7d
--- /dev/null
+++ b/resources/multimc/64x64/resourcepacks.png
Binary files differ
diff --git a/resources/multimc/64x64/screenshots.png b/resources/multimc/64x64/screenshots.png
new file mode 100644
index 00000000..af18e39c
--- /dev/null
+++ b/resources/multimc/64x64/screenshots.png
Binary files differ
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>