summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt296
-rw-r--r--HandleCrash.cpp1
-rw-r--r--MultiMC.cpp38
-rw-r--r--MultiMC.h15
-rw-r--r--changelog.yaml2
-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/pack200/src/coding.cpp3
-rw-r--r--depends/quazip/unzip.c2
-rw-r--r--depends/quazip/zip.c4
-rw-r--r--depends/settings/CMakeLists.txt41
-rw-r--r--depends/util/src/pathutils.cpp5
-rw-r--r--depends/util/src/userutils.cpp1
-rw-r--r--gui/ConsoleWindow.cpp271
-rw-r--r--gui/ConsoleWindow.h45
-rw-r--r--gui/ConsoleWindow.ui93
-rw-r--r--gui/MainWindow.cpp210
-rw-r--r--gui/MainWindow.h17
-rw-r--r--gui/MainWindow.ui97
-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/IconPickerDialog.h3
-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/ProgressDialog.h4
-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.cpp50
-rw-r--r--gui/dialogs/SettingsDialog.h2
-rw-r--r--gui/dialogs/SettingsDialog.ui39
-rw-r--r--gui/dialogs/VersionSelectDialog.cpp4
-rw-r--r--gui/groupview/GroupView.cpp13
-rw-r--r--gui/groupview/InstanceDelegate.cpp4
-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.h (renamed from logic/NostalgiaInstance.h)39
-rw-r--r--gui/pages/BasePageProvider.h (renamed from depends/settings/libsettings_config.h)21
-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.cpp152
-rw-r--r--gui/pages/LogPage.h72
-rw-r--r--gui/pages/LogPage.ui76
-rw-r--r--gui/pages/ModFolderPage.cpp148
-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.cpp387
-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.cpp387
-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--gui/widgets/VersionListView.cpp5
-rw-r--r--logic/BaseInstaller.cpp9
-rw-r--r--logic/BaseInstaller.h4
-rw-r--r--logic/BaseInstance.cpp22
-rw-r--r--logic/BaseInstance.h31
-rw-r--r--logic/BaseInstance_p.h3
-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)3
-rw-r--r--logic/InstanceFactory.cpp84
-rw-r--r--logic/InstanceFactory.h2
-rw-r--r--logic/InstanceLauncher.cpp2
-rw-r--r--logic/InstanceList.cpp (renamed from logic/lists/InstanceList.cpp)16
-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.cpp60
-rw-r--r--logic/LegacyInstance.h14
-rw-r--r--logic/LegacyInstance_p.h2
-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.cpp12
-rw-r--r--logic/MinecraftVersion.h89
-rw-r--r--logic/Mod.cpp2
-rw-r--r--logic/ModList.cpp5
-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/RWStorage.h60
-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/assets/AssetsUtils.h2
-rw-r--r--logic/auth/flows/AuthenticateTask.cpp1
-rw-r--r--logic/auth/flows/RefreshTask.cpp1
-rw-r--r--logic/auth/flows/ValidateTask.cpp1
-rw-r--r--logic/forge/ForgeInstaller.cpp (renamed from logic/ForgeInstaller.cpp)195
-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)28
-rw-r--r--logic/forge/ForgeVersionList.h (renamed from logic/lists/ForgeVersionList.h)55
-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/icons/IconList.cpp10
-rw-r--r--logic/icons/IconList.h4
-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)10
-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)17
-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)18
-rw-r--r--logic/liteloader/LiteLoaderVersionList.h (renamed from logic/lists/LiteLoaderVersionList.h)9
-rw-r--r--logic/minecraft/InstanceVersion.cpp536
-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.cpp348
-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/net/PasteUpload.cpp7
-rw-r--r--logic/net/PasteUpload.h11
-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/settings/INIFile.cpp (renamed from depends/settings/inifile.cpp)2
-rw-r--r--logic/settings/INIFile.h (renamed from depends/settings/inifile.h)4
-rw-r--r--logic/settings/INISettingsObject.cpp (renamed from depends/settings/inisettingsobject.cpp)4
-rw-r--r--logic/settings/INISettingsObject.h (renamed from depends/settings/inisettingsobject.h)8
-rw-r--r--logic/settings/OverrideSetting.cpp (renamed from depends/settings/overridesetting.cpp)2
-rw-r--r--logic/settings/OverrideSetting.h (renamed from depends/settings/overridesetting.h)6
-rw-r--r--logic/settings/Setting.cpp (renamed from depends/settings/setting.cpp)6
-rw-r--r--logic/settings/Setting.h (renamed from depends/settings/setting.h)8
-rw-r--r--logic/settings/SettingsObject.cpp (renamed from depends/settings/settingsobject.cpp)47
-rw-r--r--logic/settings/SettingsObject.h (renamed from depends/settings/settingsobject.h)12
-rw-r--r--logic/tasks/ProgressProvider.h1
-rw-r--r--logic/tools/JProfiler.cpp2
-rw-r--r--logic/tools/JVisualVM.cpp2
-rw-r--r--logic/tools/MCEditTool.cpp2
-rw-r--r--logic/updater/UpdateChecker.cpp2
-rw-r--r--main.cpp5
-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/index.theme10
-rw-r--r--resources/multimc/multimc.qrc36
-rw-r--r--resources/multimc/scalable/screenshot-placeholder.svg86
-rw-r--r--resources/multimc/scalable/screenshots.svg1231
-rw-r--r--resources/pe_dark/16x16/status-bad.pngbin0 -> 468 bytes
-rw-r--r--resources/pe_dark/16x16/status-good.pngbin0 -> 527 bytes
-rw-r--r--resources/pe_dark/22x22/status-bad.pngbin0 -> 592 bytes
-rw-r--r--resources/pe_dark/22x22/status-good.pngbin0 -> 665 bytes
-rw-r--r--resources/pe_dark/24x24/status-bad.pngbin0 -> 605 bytes
-rw-r--r--resources/pe_dark/24x24/status-good.pngbin0 -> 676 bytes
-rw-r--r--resources/pe_dark/32x32/status-bad.pngbin0 -> 839 bytes
-rw-r--r--resources/pe_dark/32x32/status-good.pngbin0 -> 802 bytes
-rw-r--r--resources/pe_dark/48x48/status-bad.pngbin0 -> 1162 bytes
-rw-r--r--resources/pe_dark/48x48/status-good.pngbin0 -> 1159 bytes
-rw-r--r--resources/pe_dark/64x64/status-bad.pngbin0 -> 1413 bytes
-rw-r--r--resources/pe_dark/64x64/status-good.pngbin0 -> 1478 bytes
-rw-r--r--resources/pe_dark/index.theme39
-rw-r--r--resources/pe_dark/pe_dark.qrc57
-rw-r--r--resources/pe_dark/scalable/about.svg22
-rw-r--r--resources/pe_dark/scalable/bug.svg29
-rw-r--r--resources/pe_dark/scalable/centralmods.svg22
-rw-r--r--resources/pe_dark/scalable/checkupdate.svg25
-rw-r--r--resources/pe_dark/scalable/console.svg228
-rw-r--r--resources/pe_dark/scalable/console_error.svg247
-rw-r--r--resources/pe_dark/scalable/copy.svg21
-rw-r--r--resources/pe_dark/scalable/new.svg22
-rw-r--r--resources/pe_dark/scalable/patreon.svg22
-rw-r--r--resources/pe_dark/scalable/refresh.svg21
-rw-r--r--resources/pe_dark/scalable/settings.svg36
-rw-r--r--resources/pe_dark/scalable/viewfolder.svg20
-rw-r--r--resources/pe_light/16x16/status-bad.pngbin0 -> 468 bytes
-rw-r--r--resources/pe_light/16x16/status-good.pngbin0 -> 527 bytes
-rw-r--r--resources/pe_light/22x22/status-bad.pngbin0 -> 592 bytes
-rw-r--r--resources/pe_light/22x22/status-good.pngbin0 -> 665 bytes
-rw-r--r--resources/pe_light/24x24/status-bad.pngbin0 -> 605 bytes
-rw-r--r--resources/pe_light/24x24/status-good.pngbin0 -> 676 bytes
-rw-r--r--resources/pe_light/32x32/status-bad.pngbin0 -> 839 bytes
-rw-r--r--resources/pe_light/32x32/status-good.pngbin0 -> 802 bytes
-rw-r--r--resources/pe_light/48x48/status-bad.pngbin0 -> 1162 bytes
-rw-r--r--resources/pe_light/48x48/status-good.pngbin0 -> 1159 bytes
-rw-r--r--resources/pe_light/64x64/status-bad.pngbin0 -> 1413 bytes
-rw-r--r--resources/pe_light/64x64/status-good.pngbin0 -> 1478 bytes
-rw-r--r--resources/pe_light/index.theme39
-rw-r--r--resources/pe_light/pe_light.qrc61
-rw-r--r--resources/pe_light/scalable/about.svg21
-rw-r--r--resources/pe_light/scalable/bug.svg50
-rw-r--r--resources/pe_light/scalable/centralmods.svg20
-rw-r--r--resources/pe_light/scalable/checkupdate.svg24
-rw-r--r--resources/pe_light/scalable/console.svg228
-rw-r--r--resources/pe_light/scalable/console_error.svg247
-rw-r--r--resources/pe_light/scalable/copy.svg20
-rw-r--r--resources/pe_light/scalable/new.svg21
-rw-r--r--resources/pe_light/scalable/news.svg296
-rw-r--r--resources/pe_light/scalable/patreon.svg83
-rw-r--r--resources/pe_light/scalable/refresh.svg20
-rw-r--r--resources/pe_light/scalable/settings.svg64
-rw-r--r--resources/pe_light/scalable/viewfolder.svg19
-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
-rw-r--r--tests/TestUtil.h4
-rw-r--r--tests/tst_UpdateChecker.cpp4
-rw-r--r--tests/tst_inifile.cpp2
297 files changed, 12472 insertions, 5721 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index da86405f..83057b3c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -103,8 +103,8 @@ set(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch
######## Set version numbers ########
set(MultiMC_VERSION_MAJOR 0)
-set(MultiMC_VERSION_MINOR 3)
-set(MultiMC_VERSION_HOTFIX 9)
+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.")
@@ -252,11 +252,6 @@ add_definitions(-DLIBUTIL_STATIC)
add_subdirectory(depends/util)
include_directories(${LIBUTIL_INCLUDE_DIR})
-# Add the settings library.
-add_definitions(-DLIBSETTINGS_STATIC)
-add_subdirectory(depends/settings)
-include_directories(${LIBSETTINGS_INCLUDE_DIR})
-
# Add the updater
add_subdirectory(mmc_updater)
@@ -291,48 +286,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
@@ -347,6 +356,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
@@ -363,30 +375,51 @@ 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
logic/MMCJson.cpp
+ # RW lock protected map
+ logic/RWStorage.h
+
# network stuffs
logic/net/NetAction.h
logic/net/MD5EtagDownload.h
@@ -395,10 +428,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
@@ -449,9 +478,6 @@ SET(MULTIMC_SOURCES
logic/LegacyUpdate.h
logic/LegacyUpdate.cpp
- logic/LegacyForge.h
- logic/LegacyForge.cpp
-
# OneSix instances
logic/OneSixUpdate.h
logic/OneSixUpdate.cpp
@@ -460,30 +486,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
@@ -491,27 +529,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
@@ -523,11 +542,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
@@ -537,17 +551,27 @@ 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
+ # Settings
+ logic/settings/INIFile.cpp
+ logic/settings/INIFile.h
+ logic/settings/INISettingsObject.cpp
+ logic/settings/INISettingsObject.h
+ logic/settings/OverrideSetting.cpp
+ logic/settings/OverrideSetting.h
+ logic/settings/Setting.cpp
+ logic/settings/Setting.h
+ logic/settings/SettingsObject.cpp
+ logic/settings/SettingsObject.h
+
+ # 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
@@ -566,6 +590,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
)
@@ -573,7 +618,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
@@ -582,18 +636,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
@@ -614,7 +663,10 @@ endforeach()
set(MULTIMC_QRCS
resources/backgrounds/backgrounds.qrc
resources/multimc/multimc.qrc
+ resources/pe_dark/pe_dark.qrc
+ resources/pe_light/pe_light.qrc
resources/instances/instances.qrc
+ resources/versions/versions.qrc
)
@@ -656,7 +708,7 @@ add_executable(MultiMC MACOSX_BUNDLE WIN32 main.cpp ${MULTIMC_RCS})
# Link
target_link_libraries(MultiMC MultiMC_common)
-target_link_libraries(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings ${MultiMC_LINK_ADDITIONAL_LIBS})
+target_link_libraries(MultiMC_common xz-embedded unpack200 quazip libUtil ${MultiMC_LINK_ADDITIONAL_LIBS})
qt5_use_modules(MultiMC Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
qt5_use_modules(MultiMC_common Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES})
@@ -694,7 +746,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInf
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
- REGEX "tga|svg|tiff|mng" EXCLUDE
+ REGEX "tga|tiff|mng" EXCLUDE
)
# Platform plugins
@@ -710,7 +762,7 @@ else()
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
- REGEX "tga|svg|tiff|mng" EXCLUDE
+ REGEX "tga|tiff|mng" EXCLUDE
REGEX "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
)
@@ -795,8 +847,6 @@ include(CPack)
include(Coverity)
-include_directories(${PROJECT_BINARY_DIR}/include)
-
# Translations
add_subdirectory(translations)
diff --git a/HandleCrash.cpp b/HandleCrash.cpp
index ed319cf1..cc2a5109 100644
--- a/HandleCrash.cpp
+++ b/HandleCrash.cpp
@@ -208,7 +208,6 @@ void dumpBacktrace(int dumpFile, CrashData crash)
void dumpSysInfo(int dumpFile)
{
#ifdef Q_OS_UNIX
- bool gotSysInfo = false; // True if system info check succeeded
utsname sysinfo; // System information
// Dump system info
diff --git a/MultiMC.cpp b/MultiMC.cpp
index bc2ba3fb..7e7c6c88 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,12 +38,14 @@
#include "logic/tools/JVisualVM.h"
#include "logic/tools/MCEditTool.h"
+#include "logic/URNResolver.h"
+
#include "pathutils.h"
#include "cmdutils.h"
-#include <inisettingsobject.h>
-#include <setting.h>
+#include "logic/settings/INISettingsObject.h"
+#include "logic/settings/Setting.h"
#include "logger/QsLog.h"
-#include <logger/QsLogDest.h>
+#include "logger/QsLogDest.h"
#ifdef Q_OS_WIN32
#include <windows.h>
@@ -216,7 +219,7 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override)
m_instances.reset(new InstanceList(InstDirSetting->get().toString(), this));
QLOG_INFO() << "Loading Instances...";
m_instances->loadList();
- connect(InstDirSetting.get(), SIGNAL(settingChanged(const Setting &, QVariant)),
+ connect(InstDirSetting.get(), SIGNAL(SettingChanged(const Setting &, QVariant)),
m_instances.get(), SLOT(on_InstFolderChanged(const Setting &, QVariant)));
// and accounts
@@ -336,7 +339,7 @@ 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
@@ -352,6 +355,7 @@ void MultiMC::initGlobalSettings()
// Updates
m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
m_settings->registerSetting("AutoUpdate", true);
+ m_settings->registerSetting("IconTheme", QString("multimc"));
// Notifications
m_settings->registerSetting("ShownNotifications", QString());
@@ -505,6 +509,8 @@ void MultiMC::initGlobalSettings()
m_settings->registerSetting("ConsoleWindowGeometry", "");
m_settings->registerSetting("SettingsGeometry", "");
+
+ m_settings->registerSetting("PagedGeometry", "");
}
void MultiMC::initHttpMetaCache()
@@ -644,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 0fd60b7d..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,
@@ -128,6 +120,8 @@ public:
std::shared_ptr<JavaVersionList> javalist();
+ std::shared_ptr<URNResolver> resolver();
+
QMap<QString, std::shared_ptr<BaseProfilerFactory>> profilers()
{
return m_profilers;
@@ -214,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/changelog.yaml b/changelog.yaml
index 75ede196..e3d6a782 100644
--- a/changelog.yaml
+++ b/changelog.yaml
@@ -88,3 +88,5 @@
- Workaround for performance issues with Intel integrated graphics chips
0.3.9
- Workaround for 1.7.10 Forge
+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/pack200/src/coding.cpp b/depends/pack200/src/coding.cpp
index 3e311131..6bd17a3c 100644
--- a/depends/pack200/src/coding.cpp
+++ b/depends/pack200/src/coding.cpp
@@ -44,6 +44,9 @@
extern coding basic_codings[];
+// CODING_PRIVATE causes a lot of them
+#pragma GCC diagnostic ignored "-Wunused-variable"
+
#define CODING_PRIVATE(spec) \
int spec_ = spec; \
int B = CODING_B(spec_); \
diff --git a/depends/quazip/unzip.c b/depends/quazip/unzip.c
index 52bc081f..823e8504 100644
--- a/depends/quazip/unzip.c
+++ b/depends/quazip/unzip.c
@@ -1245,7 +1245,7 @@ extern int ZEXPORT unzReadCurrentFile (file, buf, len)
return UNZ_PARAMERROR;
- if ((pfile_in_zip_read_info->read_buffer == NULL))
+ if (pfile_in_zip_read_info->read_buffer == NULL)
return UNZ_END_OF_LIST_OF_FILE;
if (len==0)
return 0;
diff --git a/depends/quazip/zip.c b/depends/quazip/zip.c
index a36a20a1..9cb8dab7 100644
--- a/depends/quazip/zip.c
+++ b/depends/quazip/zip.c
@@ -777,9 +777,9 @@ extern int ZEXPORT zipOpenNewFileInZip3 (file, filename, zipfi,
zi->ci.flag = 0;
if ((level==8) || (level==9))
zi->ci.flag |= 2;
- if ((level==2))
+ if (level==2)
zi->ci.flag |= 4;
- if ((level==1))
+ if (level==1)
zi->ci.flag |= 6;
if (password != NULL)
{
diff --git a/depends/settings/CMakeLists.txt b/depends/settings/CMakeLists.txt
deleted file mode 100644
index 346e15b5..00000000
--- a/depends/settings/CMakeLists.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-project(libSettings)
-
-# Find Qt
-find_package(Qt5Core REQUIRED)
-
-# Include Qt headers.
-include_directories(${Qt5Base_INCLUDE_DIRS})
-
-include(UseCXX11)
-include(Coverage)
-
-set(LIBSETTINGS_SOURCES
- libsettings_config.h
-
- inifile.h
- inifile.cpp
-
- settingsobject.h
- settingsobject.cpp
- inisettingsobject.h
- inisettingsobject.cpp
-
- setting.h
- setting.cpp
- overridesetting.h
- overridesetting.cpp
-)
-
-# Set the include dir path.
-set(LIBSETTINGS_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" PARENT_SCOPE)
-
-# Static link!
-add_definitions(-DLIBSETTINGS_STATIC)
-
-add_definitions(-DLIBSETTINGS_LIBRARY)
-
-set(CMAKE_POSITION_INDEPENDENT_CODE ON)
-
-add_library(libSettings STATIC ${LIBSETTINGS_SOURCES})
-qt5_use_modules(libSettings Core)
-target_link_libraries(libSettings)
diff --git a/depends/util/src/pathutils.cpp b/depends/util/src/pathutils.cpp
index 20888754..1d09fe45 100644
--- a/depends/util/src/pathutils.cpp
+++ b/depends/util/src/pathutils.cpp
@@ -19,7 +19,6 @@
#include <QDir>
#include <QDesktopServices>
#include <QUrl>
-#include <QDebug>
QString PathCombine(QString path1, QString path2)
{
@@ -138,10 +137,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/depends/util/src/userutils.cpp b/depends/util/src/userutils.cpp
index 060a58e9..a26af5af 100644
--- a/depends/util/src/userutils.cpp
+++ b/depends/util/src/userutils.cpp
@@ -75,7 +75,6 @@ bool Util::createShortCut(QString location, QString dest, QStringList args, QStr
{
#if LINUX
location = PathCombine(location, name + ".desktop");
- qDebug("location: %s", qPrintable(location));
QFile f(location);
f.open(QIODevice::WriteOnly | QIODevice::Text);
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 3aa3fdbf..bee250c4 100644
--- a/gui/MainWindow.cpp
+++ b/gui/MainWindow.cpp
@@ -56,24 +56,22 @@
#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"
#include "gui/dialogs/UpdateDialog.h"
#include "gui/dialogs/EditAccountDialog.h"
#include "gui/dialogs/NotificationDialog.h"
-#include "dialogs/ScreenshotDialog.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"
@@ -91,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"
@@ -874,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()
@@ -948,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);
@@ -1036,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.
@@ -1349,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()
-{
- 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)
- {
- 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())
+ if(!current.isValid())
{
- selectionBad();
MMC->settings()->set("SelectedInstance", QString());
+ selectionBad();
return;
}
QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = MMC->instances()->getInstanceById(id);
- if (m_selectedInstance)
+ 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());
@@ -1471,8 +1393,8 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
}
else
{
- selectionBad();
MMC->settings()->set("SelectedInstance", QString());
+ selectionBad();
return;
}
}
@@ -1491,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();
@@ -1607,27 +1515,3 @@ void MainWindow::checkSetDefaultJava()
MMC->settings()->set("JavaPath", QString("java"));
}
}
-
-void MainWindow::on_actionScreenshots_triggered()
-{
- if (!m_selectedInstance)
- return;
- ScreenshotList *list = new ScreenshotList(m_selectedInstance);
- 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();
- }
-}
-
-
diff --git a/gui/MainWindow.h b/gui/MainWindow.h
index 69cf11b0..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,11 +104,7 @@ 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();
@@ -135,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 1d7fbec9..03a07ce5 100644
--- a/gui/MainWindow.ui
+++ b/gui/MainWindow.ui
@@ -74,7 +74,7 @@
<addaction name="actionReportBug"/>
<addaction name="actionAbout"/>
<addaction name="separator"/>
- <addaction name="actionPatreon"/>
+ <addaction name="actionPatreon"/>
<addaction name="actionCAT"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
@@ -109,16 +109,13 @@
<addaction name="actionChangeInstIcon"/>
<addaction name="actionLaunchInstance"/>
<addaction name="actionLaunchInstanceOffline"/>
- <addaction name="separator"/>
- <addaction name="actionEditInstNotes"/>
<addaction name="actionChangeInstGroup"/>
<addaction name="separator"/>
+ <addaction name="actionEditInstance"/>
+ <addaction name="actionInstanceSettings"/>
+ <addaction name="actionEditInstNotes"/>
<addaction name="actionScreenshots"/>
<addaction name="separator"/>
- <addaction name="actionInstanceSettings"/>
- <addaction name="actionChangeInstMCVersion"/>
- <addaction name="actionChangeInstLWJGLVersion"/>
- <addaction name="actionEditInstMods"/>
<addaction name="actionViewSelectedInstFolder"/>
<addaction name="actionConfig_Folder"/>
<addaction name="separator"/>
@@ -284,7 +281,7 @@
<property name="statusTip">
<string>Open the MultiMC Patreon page.</string>
</property>
- </action>
+ </action>
<action name="actionMoreNews">
<property name="icon">
<iconset theme="news">
@@ -388,82 +385,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,9 +488,19 @@
<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>
+ <include location="../resources/pe_dark/pe_dark.qrc"/>
+ <include location="../resources/pe_light/pe_light.qrc"/>
<include location="../resources/multimc/multimc.qrc"/>
<include location="../resources/instances/instances.qrc"/>
</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/IconPickerDialog.h b/gui/dialogs/IconPickerDialog.h
index f00c2388..70951da6 100644
--- a/gui/dialogs/IconPickerDialog.h
+++ b/gui/dialogs/IconPickerDialog.h
@@ -29,7 +29,10 @@ class IconPickerDialog : public QDialog
public:
explicit IconPickerDialog(QWidget *parent = 0);
~IconPickerDialog();
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Woverloaded-virtual"
int exec(QString selection);
+ #pragma clang diagnostic pop
QString selectedIconKey;
protected:
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/ProgressDialog.h b/gui/dialogs/ProgressDialog.h
index fe63a826..45a6238a 100644
--- a/gui/dialogs/ProgressDialog.h
+++ b/gui/dialogs/ProgressDialog.h
@@ -34,7 +34,11 @@ public:
void updateSize();
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Woverloaded-virtual"
int exec(ProgressProvider *task);
+ #pragma clang diagnostic pop
+
void setSkipButton(bool present, QString label = QString());
ProgressProvider *getTask();
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 2dd19077..5108daa2 100644
--- a/gui/dialogs/SettingsDialog.cpp
+++ b/gui/dialogs/SettingsDialog.cpp
@@ -22,21 +22,31 @@
#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"
#include "logic/tools/BaseProfiler.h"
-#include <settingsobject.h>
+#include "logic/settings/SettingsObject.h"
#include <pathutils.h>
#include <QFileDialog>
#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);
@@ -316,7 +326,20 @@ void SettingsDialog::applySettings(SettingsObject *s)
// Updates
s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
s->set("UpdateChannel", m_currentUpdateChannel);
-
+ //FIXME: make generic
+ switch (ui->themeComboBox->currentIndex())
+ {
+ case 1:
+ s->set("IconTheme", "pe_dark");
+ break;
+ case 2:
+ s->set("IconTheme", "pe_light");
+ break;
+ case 0:
+ default:
+ s->set("IconTheme", "multimc");
+ break;
+ }
// FTB
s->set("TrackFTBInstances", ui->trackFtbBox->isChecked());
s->set("FTBLauncherRoot", ui->ftbLauncherBox->text());
@@ -410,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(
@@ -419,7 +442,20 @@ void SettingsDialog::loadSettings(SettingsObject *s)
// Updates
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
m_currentUpdateChannel = s->get("UpdateChannel").toString();
-
+ //FIXME: make generic
+ auto theme = s->get("IconTheme").toString();
+ if (theme == "pe_dark")
+ {
+ ui->themeComboBox->setCurrentIndex(1);
+ }
+ else if (theme == "pe_light")
+ {
+ ui->themeComboBox->setCurrentIndex(2);
+ }
+ else
+ {
+ ui->themeComboBox->setCurrentIndex(0);
+ }
// FTB
ui->trackFtbBox->setChecked(s->get("TrackFTBInstances").toBool());
ui->ftbLauncherBox->setText(s->get("FTBLauncherRoot").toString());
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/SettingsDialog.ui b/gui/dialogs/SettingsDialog.ui
index 74ed68d2..d4e90302 100644
--- a/gui/dialogs/SettingsDialog.ui
+++ b/gui/dialogs/SettingsDialog.ui
@@ -20,7 +20,7 @@
<string>Settings</string>
</property>
<property name="windowIcon">
- <iconset resource="../../graphics.qrc">
+ <iconset>
<normaloff>:/icons/toolbar/settings</normaloff>:/icons/toolbar/settings</iconset>
</property>
<property name="modal">
@@ -327,6 +327,43 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="themeBox">
+ <property name="title">
+ <string>Icon Theme</string>
+ </property>
+ <layout class="QHBoxLayout" name="themeBoxLayout">
+ <item>
+ <widget class="QComboBox" name="themeComboBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string>Default</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Simple</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Simple (Light Icons)</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<widget class="QGroupBox" name="editorsBox">
<property name="title">
<string>External Editors (leave empty for system default)</string>
diff --git a/gui/dialogs/VersionSelectDialog.cpp b/gui/dialogs/VersionSelectDialog.cpp
index cae5a732..2745eccc 100644
--- a/gui/dialogs/VersionSelectDialog.cpp
+++ b/gui/dialogs/VersionSelectDialog.cpp
@@ -18,13 +18,11 @@
#include <QHeaderView>
-#include <QDebug>
-
#include <gui/dialogs/ProgressDialog.h>
#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/GroupView.cpp b/gui/groupview/GroupView.cpp
index b650efee..69a06fd3 100644
--- a/gui/groupview/GroupView.cpp
+++ b/gui/groupview/GroupView.cpp
@@ -3,7 +3,6 @@
#include <QPainter>
#include <QApplication>
#include <QtMath>
-#include <QDebug>
#include <QMouseEvent>
#include <QListView>
#include <QPersistentModelIndex>
@@ -12,6 +11,7 @@
#include <QScrollBar>
#include "Group.h"
+#include "logger/QsLog.h"
template <typename T> bool listsIntersect(const QList<T> &l1, const QList<T> t2)
{
@@ -283,7 +283,6 @@ void GroupView::mousePressEvent(QMouseEvent *event)
m_pressedIndex = index;
m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex);
- QItemSelectionModel::SelectionFlags selection_flags = selectionCommand(index, event);
m_pressedPosition = geometryPos;
m_pressedCategory = categoryAt(geometryPos);
@@ -918,10 +917,10 @@ QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction,
auto current = currentIndex();
if(!current.isValid())
{
- qDebug() << "model row: invalid";
+ QLOG_DEBUG() << "model row: invalid";
return current;
}
- qDebug() << "model row: " << current.row();
+ QLOG_DEBUG() << "model row: " << current.row();
auto cat = category(current);
int i = m_groups.indexOf(cat);
if(i >= 0)
@@ -935,11 +934,11 @@ QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction,
break;
beginning_row += group->numRows();
}
- qDebug() << "category: " << real_group->text;
+ QLOG_DEBUG() << "category: " << real_group->text;
QPair<int, int> pos = categoryInternalPosition(current);
int row = beginning_row + pos.second;
- qDebug() << "row: " << row;
- qDebug() << "column: " << pos.first;
+ QLOG_DEBUG() << "row: " << row;
+ QLOG_DEBUG() << "column: " << pos.first;
}
return current;
}
diff --git a/gui/groupview/InstanceDelegate.cpp b/gui/groupview/InstanceDelegate.cpp
index cd26ddaa..3bd77747 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;
@@ -120,6 +120,8 @@ void drawBadges(QPainter *painter, const QStyleOptionViewItemV4 &option, BaseIns
case BaseInstance::VersionBrokenFlag:
pixmaps.append("broken");
break;
+ default:
+ break;
}
}
diff --git a/gui/pagedialog/PageDialog.cpp b/gui/pagedialog/PageDialog.cpp
new file mode 100644
index 00000000..4dd4a811
--- /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 "logic/settings/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/logic/NostalgiaInstance.h b/gui/pages/BasePage.h
index f95531d2..09af3a59 100644
--- a/logic/NostalgiaInstance.h
+++ b/gui/pages/BasePage.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,16 +14,35 @@
*/
#pragma once
+#include <QString>
+#include <QIcon>
+#include <memory>
-#include "OneSixInstance.h"
-
-class NostalgiaInstance : public OneSixInstance
+class BasePage
{
- 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 ~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/depends/settings/libsettings_config.h b/gui/pages/BasePageProvider.h
index e5beed28..cff9c8e7 100644
--- a/depends/settings/libsettings_config.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 <QtCore/QtGlobal>
+#include "BasePage.h"
+#include <memory>
-#ifdef LIBSETTINGS_STATIC
-#define LIBSETTINGS_EXPORT
-#else
-#ifdef LIBSETTINGS_LIBRARY
-#define LIBSETTINGS_EXPORT Q_DECL_EXPORT
-#else
-#define LIBSETTINGS_EXPORT Q_DECL_IMPORT
-#endif
-#endif
+class BasePageProvider
+{
+public:
+ 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..a9e32cc2
--- /dev/null
+++ b/gui/pages/LogPage.cpp
@@ -0,0 +1,152 @@
+#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>
+#include <QtGui/QClipboard>
+#include <QtGui/QDesktopServices>
+
+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();
+ }
+ else
+ {
+ QString link = paste->pasteLink();
+ QClipboard *clipboard = QApplication::clipboard();
+ clipboard->setText(link);
+ QDesktopServices::openUrl(link);
+ CustomMessageBox::selectable(
+ this, tr("Upload finished"),
+ tr("The <a href=\"%1\">link to the uploaded log</a> has been opened in the default browser and placed in your clipboard.")
+ .arg(link),
+ QMessageBox::Information)->exec();
+ }
+ delete paste;
+}
+
+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..7e0eea52
--- /dev/null
+++ b/gui/pages/ModFolderPage.cpp
@@ -0,0 +1,148 @@
+/* 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 <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..ff4f0099
--- /dev/null
+++ b/gui/pages/ScreenshotsPage.cpp
@@ -0,0 +1,387 @@
+#include "ScreenshotsPage.h"
+#include "ui_ScreenshotsPage.h"
+
+#include <QModelIndex>
+#include <QMutableListIterator>
+#include <QMap>
+#include <QSet>
+#include <QFileIconProvider>
+#include <QFileSystemModel>
+#include <QStyledItemDelegate>
+#include <QLineEdit>
+#include <QtGui/qevent.h>
+#include <QtGui/QPainter>
+#include <QtGui/QClipboard>
+#include <QtGui/QDesktopServices>
+
+#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"
+
+#include "logic/RWStorage.h"
+typedef RWStorage<QString, QIcon> SharedIconCache;
+typedef std::shared_ptr<SharedIconCache> SharedIconCachePtr;
+
+class ThumbnailingResult : public QObject
+{
+ Q_OBJECT
+public Q_SLOTS:
+ inline void emitResultsReady(const QString &path)
+ {
+ emit resultsReady(path);
+ }
+ inline void emitResultsFailed(const QString &path)
+ {
+ emit resultsFailed(path);
+ }
+Q_SIGNALS:
+ void resultsReady(const QString &path);
+ void resultsFailed(const QString &path);
+};
+
+class ThumbnailRunnable: public QRunnable
+{
+public:
+ ThumbnailRunnable (QString path, SharedIconCachePtr cache)
+ {
+ m_path = path;
+ m_cache = cache;
+ }
+ void run()
+ {
+ QFileInfo info(m_path);
+ if(info.isDir())
+ return;
+ if((info.suffix().compare("png", Qt::CaseInsensitive) != 0))
+ return;
+ int tries = 5;
+ while(tries)
+ {
+ if(!m_cache->stale(m_path))
+ return;
+ QImage image(m_path);
+ if (image.isNull())
+ {
+ QThread::msleep(500);
+ tries--;
+ continue;
+ }
+ QImage small;
+ if(image.width() > image.height())
+ small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
+ else
+ small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
+ auto smallSize = small.size();
+ QPoint offset((256-small.width())/2, (256-small.height())/2);
+ QImage square(QSize(256,256), QImage::Format_ARGB32);
+ square.fill(Qt::transparent);
+
+ QPainter painter(&square);
+ painter.drawImage(offset, small);
+ painter.end();
+
+ QIcon icon(QPixmap::fromImage(square));
+ m_cache->add(m_path, icon);
+ m_resultEmitter.emitResultsReady(m_path);
+ return;
+ }
+ m_resultEmitter.emitResultsFailed(m_path);
+ }
+ QString m_path;
+ SharedIconCachePtr m_cache;
+ ThumbnailingResult m_resultEmitter;
+};
+
+// this is about as elegant and well written as a bag of bricks with scribbles done by insane asylum patients.
+class FilterModel : public QIdentityProxyModel
+{
+ Q_OBJECT
+public:
+ explicit FilterModel(QObject *parent = 0):QIdentityProxyModel(parent)
+ {
+ m_thumbnailingPool.setMaxThreadCount(4);
+ m_thumbnailCache = std::make_shared<SharedIconCache>();
+ m_thumbnailCache->add("placeholder", QIcon::fromTheme("screenshot-placeholder"));
+ connect(&watcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
+ // FIXME: the watched file set is not updated when files are removed
+ }
+ virtual ~FilterModel()
+ {
+ m_thumbnailingPool.waitForDone(500);
+ }
+ 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();
+ QIcon temp;
+ if(!watched.contains(filePath))
+ {
+ ((QFileSystemWatcher &)watcher).addPath(filePath);
+ ((QSet<QString> &)watched).insert(filePath);
+ }
+ if(m_thumbnailCache->get(filePath, temp))
+ {
+ return temp;
+ }
+ if(!m_failed.contains(filePath))
+ {
+ ((FilterModel *)this)->thumbnailImage(filePath);
+ }
+ return(m_thumbnailCache->get("placeholder"));
+ }
+ return sourceModel()->data(mapToSource(proxyIndex), role);
+ }
+ 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:
+ void thumbnailImage(QString path)
+ {
+ auto runnable = new ThumbnailRunnable(path, m_thumbnailCache);
+ connect(&(runnable->m_resultEmitter),SIGNAL(resultsReady(QString)), SLOT(thumbnailReady(QString)));
+ connect(&(runnable->m_resultEmitter),SIGNAL(resultsFailed(QString)), SLOT(thumbnailFailed(QString)));
+ ((QThreadPool &)m_thumbnailingPool).start(runnable);
+ }
+private
+slots:
+ void thumbnailReady(QString path)
+ {
+ emit layoutChanged();
+ }
+ void thumbnailFailed(QString path)
+ {
+ m_failed.insert(path);
+ }
+ void fileChanged(QString filepath)
+ {
+ m_thumbnailCache->setStale(filepath);
+ thumbnailImage(filepath);
+ // reinsert the path...
+ watcher.removePath(filepath);
+ watcher.addPath(filepath);
+ }
+
+private:
+ SharedIconCachePtr m_thumbnailCache;
+ QThreadPool m_thumbnailingPool;
+ QSet<QString> m_failed;
+ QSet<QString> watched;
+ QFileSystemWatcher watcher;
+};
+
+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_model->setNameFilters({"*.png"});
+ m_model->setNameFilterDisables(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, 160));
+ 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
+ {
+ auto link = QString("https://imgur.com/a/%1").arg(imgurAlbum->id());
+ QClipboard *clipboard = QApplication::clipboard();
+ clipboard->setText(link);
+ QDesktopServices::openUrl(link);
+ CustomMessageBox::selectable(
+ this, tr("Upload finished"),
+ tr("The <a href=\"%1\">link to the uploaded album</a> has been opened in the default browser and placed in your clipboard.<br/>Delete hash: %2 (save "
+ "this if you want to be able to edit/delete the album)")
+ .arg(link, 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)));
+ }
+}
+
+#include "ScreenshotsPage.moc"
diff --git a/gui/pages/ScreenshotsPage.h b/gui/pages/ScreenshotsPage.h
new file mode 100644
index 00000000..78307f6a
--- /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;
+}
+
+struct 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..ec83ef87
--- /dev/null
+++ b/gui/pages/VersionPage.cpp
@@ -0,0 +1,387 @@
+/* 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 <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();
+ m_version->move(row, InstanceVersion::MoveUp);
+ }
+ 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();
+ m_version->move(row, InstanceVersion::MoveDown);
+ }
+ 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..928e15dd
--- /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 "logic/settings/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/gui/widgets/VersionListView.cpp b/gui/widgets/VersionListView.cpp
index b7f45f27..e552d738 100644
--- a/gui/widgets/VersionListView.cpp
+++ b/gui/widgets/VersionListView.cpp
@@ -85,12 +85,9 @@ void VersionListView::paintEvent(QPaintEvent *event)
void VersionListView::paintInfoLabel(QPaintEvent *event)
{
- int scrollInterval = 500;
-
//calculate the rect for the overlay
QPainter painter(viewport());
painter.setRenderHint(QPainter::Antialiasing, true);
- const QChar letter = 'Q';
QFont font("sans", 20);
font.setBold(true);
@@ -147,4 +144,4 @@ void ModListView::setModel ( QAbstractItemModel* model )
head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
}
}
-*/ \ No newline at end of file
+*/
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..d89ab7c2 100644
--- a/logic/BaseInstaller.h
+++ b/logic/BaseInstaller.h
@@ -22,14 +22,14 @@ class QDir;
class QString;
class QObject;
class ProgressProvider;
-class BaseVersion;
+struct BaseVersion;
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
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..9a811577 100644
--- a/logic/BaseInstance.cpp
+++ b/logic/BaseInstance.cpp
@@ -21,13 +21,13 @@
#include <QDir>
#include "MultiMC.h"
-#include "inisettingsobject.h"
-#include "setting.h"
-#include "overridesetting.h"
+#include "logic/settings/INISettingsObject.h"
+#include "logic/settings/Setting.h"
+#include "logic/settings/OverrideSetting.h"
#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..0cd17de9 100644
--- a/logic/BaseInstance.h
+++ b/logic/BaseInstance.h
@@ -19,12 +19,13 @@
#include <QDateTime>
#include <QSet>
-#include <settingsobject.h>
+#include "logic/settings/SettingsObject.h"
-#include "inifile.h"
-#include "lists/BaseVersionList.h"
+#include "logic/settings/INIFile.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..77486abc 100644
--- a/logic/BaseInstance_p.h
+++ b/logic/BaseInstance_p.h
@@ -18,7 +18,7 @@
#include <QString>
#include <QSet>
-#include <settingsobject.h>
+#include "logic/settings/SettingsObject.h"
#include "BaseInstance.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..f903b52c 100644
--- a/logic/lists/BaseVersionList.h
+++ b/logic/BaseVersionList.h
@@ -100,7 +100,10 @@ public:
/*!
* Sorts the version list.
*/
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Woverloaded-virtual"
virtual void sort() = 0;
+ #pragma clang diagnostic pop
protected
slots:
diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp
index 95fd855b..f0f7ffb3 100644
--- a/logic/InstanceFactory.cpp
+++ b/logic/InstanceFactory.cpp
@@ -13,28 +13,27 @@
* 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 <inisettingsobject.h>
-#include <setting.h>
-
-#include "pathutils.h"
+#include "logic/settings/INIFile.h"
+#include "logic/settings/INISettingsObject.h"
+#include "logic/settings/Setting.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 +50,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 +58,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 +93,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/InstanceFactory.h b/logic/InstanceFactory.h
index 96e2375e..32a31080 100644
--- a/logic/InstanceFactory.h
+++ b/logic/InstanceFactory.h
@@ -22,7 +22,7 @@
#include "BaseVersion.h"
#include "BaseInstance.h"
-class BaseVersion;
+struct BaseVersion;
class BaseInstance;
/*!
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 8808d6b5..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()
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..0239a325 100644
--- a/logic/LegacyInstance.cpp
+++ b/logic/LegacyInstance.cpp
@@ -16,7 +16,7 @@
#include <QFileInfo>
#include <QDir>
#include <QImage>
-#include <setting.h>
+#include <logic/settings/Setting.h>
#include <pathutils.h>
#include <cmdutils.h>
@@ -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,28 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings,
settings->registerSetting("IntendedJarVersion", "");
}
+QList<BasePage *> LegacyInstance::getPages()
+{
+ QList<BasePage *> values;
+ // FIXME: actually implement the legacy instance upgrade, then enable this.
+ //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 +77,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 +163,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 +285,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/LegacyInstance_p.h b/logic/LegacyInstance_p.h
index 061847de..50862027 100644
--- a/logic/LegacyInstance_p.h
+++ b/logic/LegacyInstance_p.h
@@ -15,7 +15,7 @@
#pragma once
#include <QString>
-#include <settingsobject.h>
+#include "logic/settings/SettingsObject.h"
#include <memory>
#include "BaseInstance_p.h"
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..dfa1dee7 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"));
@@ -443,7 +452,6 @@ void MinecraftProcess::arm()
}
m_instance->setLastLaunch();
- auto &settings = m_instance->settings();
QStringList args = javaArguments();
@@ -460,6 +468,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/Mod.cpp b/logic/Mod.cpp
index c6872d6e..534fa796 100644
--- a/logic/Mod.cpp
+++ b/logic/Mod.cpp
@@ -24,7 +24,7 @@
#include "Mod.h"
#include <pathutils.h>
-#include <inifile.h>
+#include "logic/settings/INIFile.h"
#include "logger/QsLog.h"
Mod::Mod(const QFileInfo &file)
diff --git a/logic/ModList.cpp b/logic/ModList.cpp
index 79b56986..3382a6ef 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);
@@ -410,7 +411,7 @@ QVariant ModList::data(const QModelIndex &index, int role) const
switch (role)
{
case Qt::DisplayRole:
- switch (index.column())
+ switch (column)
{
case NameColumn:
return mods[row].name();
@@ -425,7 +426,7 @@ QVariant ModList::data(const QModelIndex &index, int role) const
return mods[row].mmc_id();
case Qt::CheckStateRole:
- switch (index.column())
+ switch (column)
{
case ActiveColumn:
return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
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/RWStorage.h b/logic/RWStorage.h
new file mode 100644
index 00000000..b1598ca4
--- /dev/null
+++ b/logic/RWStorage.h
@@ -0,0 +1,60 @@
+#pragma once
+template <typename K, typename V>
+class RWStorage
+{
+public:
+ void add(K key, V value)
+ {
+ QWriteLocker l(&lock);
+ cache[key] = value;
+ stale_entries.remove(key);
+ }
+ V get(K key)
+ {
+ QReadLocker l(&lock);
+ if(cache.contains(key))
+ {
+ return cache[key];
+ }
+ else return V();
+ }
+ bool get(K key, V& value)
+ {
+ QReadLocker l(&lock);
+ if(cache.contains(key))
+ {
+ value = cache[key];
+ return true;
+ }
+ else return false;
+ }
+ bool has(K key)
+ {
+ QReadLocker l(&lock);
+ return cache.contains(key);
+ }
+ bool stale(K key)
+ {
+ QReadLocker l(&lock);
+ if(!cache.contains(key))
+ return true;
+ return stale_entries.contains(key);
+ }
+ void setStale(K key)
+ {
+ QReadLocker l(&lock);
+ if(cache.contains(key))
+ {
+ stale_entries.insert(key);
+ }
+ }
+ void clear()
+ {
+ QWriteLocker l(&lock);
+ cache.clear();
+ }
+private:
+ QReadWriteLock lock;
+ QMap<K, V> cache;
+ QSet<K> stale_entries;
+}; \ No newline at end of file
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..e8523018
--- /dev/null
+++ b/logic/VersionFilterData.cpp
@@ -0,0 +1,68 @@
+#include "VersionFilterData.h"
+#include "minecraft/ParseUtils.h"
+
+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/assets/AssetsUtils.h b/logic/assets/AssetsUtils.h
index aaacc2db..5735afd0 100644
--- a/logic/assets/AssetsUtils.h
+++ b/logic/assets/AssetsUtils.h
@@ -18,8 +18,6 @@
#include <QString>
#include <QMap>
-class AssetObject;
-
struct AssetObject
{
QString hash;
diff --git a/logic/auth/flows/AuthenticateTask.cpp b/logic/auth/flows/AuthenticateTask.cpp
index 340235e3..33634fd4 100644
--- a/logic/auth/flows/AuthenticateTask.cpp
+++ b/logic/auth/flows/AuthenticateTask.cpp
@@ -22,7 +22,6 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QVariant>
-#include <QDebug>
#include "logger/QsLog.h"
diff --git a/logic/auth/flows/RefreshTask.cpp b/logic/auth/flows/RefreshTask.cpp
index 7e926c2b..61974476 100644
--- a/logic/auth/flows/RefreshTask.cpp
+++ b/logic/auth/flows/RefreshTask.cpp
@@ -21,7 +21,6 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QVariant>
-#include <QDebug>
#include "logger/QsLog.h"
diff --git a/logic/auth/flows/ValidateTask.cpp b/logic/auth/flows/ValidateTask.cpp
index f3fc1e71..dd73218f 100644
--- a/logic/auth/flows/ValidateTask.cpp
+++ b/logic/auth/flows/ValidateTask.cpp
@@ -22,7 +22,6 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QVariant>
-#include <QDebug>
#include "logger/QsLog.h"
diff --git a/logic/ForgeInstaller.cpp b/logic/forge/ForgeInstaller.cpp
index 5b3e8029..7ab0a09b 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,16 +128,17 @@ 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;
{
QJsonArray librariesPlus;
// A blacklist - we ignore these entirely
QSet<QString> blacklist{"lwjgl", "lwjgl_util", "lwjgl-platform"};
+ //
QList<QString> xzlist{"org.scala-lang", "com.typesafe"};
// for each library in the version we are adding (except for the blacklisted)
- for (auto lib : m_forge_version->libraries)
+ for (auto lib : m_forge_json->libraries)
{
QString libName = lib->name();
QString rawName = lib->rawName();
@@ -170,7 +171,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;
@@ -200,8 +201,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\\.]*)");
@@ -213,7 +214,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);
}
@@ -221,10 +222,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);
}
}
@@ -246,11 +247,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)
{
}
@@ -258,57 +313,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()
{
@@ -333,8 +390,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..1c7452d7 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;
+struct 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 50899d24..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>
@@ -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")
@@ -282,14 +283,8 @@ bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out)
fVersion->installer_url = installer_url;
fVersion->jobbuildver = jobbuildver;
fVersion->mcver = fVersion->mcver_sane = mcver;
- if (installer_filename.isEmpty())
- {
- fVersion->filename = filename;
- }
- else
- {
- fVersion->filename = installer_filename;
- }
+ fVersion->installer_filename = installer_filename;
+ fVersion->universal_filename = universal_filename;
fVersion->m_buildnr = build_nr;
out.append(fVersion);
}
@@ -343,12 +338,13 @@ bool ForgeListLoadTask::parseForgeGradleList(QList<BaseVersionPtr> &out)
fVersion->jobbuildver = number.value("version").toString();
fVersion->branch = number.value("branch").toString("");
fVersion->mcver = number.value("mcversion").toString();
+ 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");
- fVersion->filename = "";
QString universal_filename, installer_filename;
QJsonArray files = number.value("files").toArray();
@@ -397,7 +393,8 @@ bool ForgeListLoadTask::parseForgeGradleList(QList<BaseVersionPtr> &out)
{
continue;
}
- fVersion->filename = fVersion->installer_url.isEmpty() ? universal_filename : installer_filename;
+ fVersion->universal_filename = universal_filename;
+ fVersion->installer_filename = installer_filename;
out.append(fVersion);
}
return true;
@@ -441,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 091072e4..477edb3d 100644
--- a/logic/lists/ForgeVersionList.h
+++ b/logic/forge/ForgeVersionList.h
@@ -18,59 +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;
- QString branch;
- QString mcver_sane;
-};
+#include "logic/forge/ForgeVersion.h"
class ForgeVersionList : public BaseVersionList
{
@@ -88,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/icons/IconList.cpp b/logic/icons/IconList.cpp
index 829fc5fc..872d5a8a 100644
--- a/logic/icons/IconList.cpp
+++ b/logic/icons/IconList.cpp
@@ -15,14 +15,14 @@
#include "IconList.h"
#include <pathutils.h>
-#include <settingsobject.h>
+#include "logic/settings/SettingsObject.h"
#include <QMap>
#include <QEventLoop>
#include <QMimeData>
#include <QUrl>
#include <QFileSystemWatcher>
#include <MultiMC.h>
-#include <setting.h>
+#include <logic/settings/Setting.h>
#define MAX_SIZE 1024
@@ -45,8 +45,8 @@ IconList::IconList(QObject *parent) : QAbstractListModel(parent)
auto setting = MMC->settings()->getSetting("IconsDir");
QString path = setting->get().toString();
- connect(setting.get(), SIGNAL(settingChanged(const Setting &, QVariant)),
- SLOT(settingChanged(const Setting &, QVariant)));
+ connect(setting.get(), SIGNAL(SettingChanged(const Setting &, QVariant)),
+ SLOT(SettingChanged(const Setting &, QVariant)));
directoryChanged(path);
}
@@ -143,7 +143,7 @@ void IconList::fileChanged(const QString &path)
emit iconUpdated(key);
}
-void IconList::settingChanged(const Setting &setting, QVariant value)
+void IconList::SettingChanged(const Setting &setting, QVariant value)
{
if(setting.id() != "IconsDir")
return;
diff --git a/logic/icons/IconList.h b/logic/icons/IconList.h
index 4ee3f782..e3ff9fab 100644
--- a/logic/icons/IconList.h
+++ b/logic/icons/IconList.h
@@ -22,7 +22,7 @@
#include <QtGui/QIcon>
#include <memory>
#include "MMCIcon.h"
-#include "setting.h"
+#include "logic/settings/Setting.h"
class QFileSystemWatcher;
@@ -68,7 +68,7 @@ protected
slots:
void directoryChanged(const QString &path);
void fileChanged(const QString &path);
- void settingChanged(const Setting & setting, QVariant value);
+ void SettingChanged(const Setting & setting, QVariant value);
private:
std::shared_ptr<QFileSystemWatcher> m_watcher;
bool is_watching;
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 707a8bc5..2b11ddb9 100644
--- a/logic/JavaUtils.cpp
+++ b/logic/java/JavaUtils.cpp
@@ -16,19 +16,17 @@
#include <QStringList>
#include <QString>
#include <QDir>
-#include <QMessageBox>
#include <QStringList>
-#include <setting.h>
+#include <logic/settings/Setting.h>
#include <pathutils.h>
#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()
{
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..863c7fcb 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()
{
@@ -49,13 +49,12 @@ bool LiteLoaderInstaller::add(OneSixInstance *to)
QJsonArray libraries;
- for (auto libStr : m_version->libraries)
+ for (auto rawLibrary : m_version->libraries)
{
- OneSixLibrary lib(libStr);
+ rawLibrary->insertType = RawLibrary::Prepend;
+ OneSixLibrary lib(rawLibrary);
lib.finalize();
- QJsonObject libObj = lib.toJson();
- libObj.insert("insert", QString("prepend"));
- libraries.append(libObj);
+ libraries.append(lib.toJson());
}
// liteloader
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..83c34efe 100644
--- a/logic/lists/LiteLoaderVersionList.cpp
+++ b/logic/liteloader/LiteLoaderVersionList.cpp
@@ -16,6 +16,7 @@
#include "LiteLoaderVersionList.h"
#include "MultiMC.h"
#include "logic/net/URLConstants.h"
+#include <MMCError.h>
#include <QtXml>
@@ -206,7 +207,22 @@ void LLListLoadTask::listDownloaded()
const QJsonArray libs = artefact.value("libraries").toArray();
for (auto lIt = libs.begin(); lIt != libs.end(); ++lIt)
{
- version->libraries.append((*lIt).toObject().value("name").toString());
+ auto libobject = (*lIt).toObject();
+ try
+ {
+ auto lib = RawLibrary::fromJson(libobject, "versions.json");
+ // hack to make liteloader 1.7.10_00 work
+ if(lib->m_name == "org.ow2.asm:asm-all:5.0.3")
+ {
+ lib->m_base_url = "http://repo.maven.apache.org/maven2/";
+ }
+ version->libraries.append(lib);
+ }
+ catch (MMCError &e)
+ {
+ QLOG_ERROR() << "Couldn't read JSON object:";
+ continue;
+ }
}
perMcVersionList.append(version);
}
diff --git a/logic/lists/LiteLoaderVersionList.h b/logic/liteloader/LiteLoaderVersionList.h
index bfc913e5..91ed077c 100644
--- a/logic/lists/LiteLoaderVersionList.h
+++ b/logic/liteloader/LiteLoaderVersionList.h
@@ -19,10 +19,11 @@
#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"
+#include <logic/minecraft/RawLibrary.h>
class LLListLoadTask;
class QNetworkReply;
@@ -55,7 +56,7 @@ public:
int timestamp;
bool isLatest;
QString tweakClass;
- QStringList libraries;
+ QList<RawLibraryPtr> libraries;
// meta
QString defaultUrl;
diff --git a/logic/minecraft/InstanceVersion.cpp b/logic/minecraft/InstanceVersion.cpp
new file mode 100644
index 00000000..e71609e6
--- /dev/null
+++ b/logic/minecraft/InstanceVersion.cpp
@@ -0,0 +1,536 @@
+/* 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 <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..66e7d327
--- /dev/null
+++ b/logic/minecraft/VersionBuilder.cpp
@@ -0,0 +1,348 @@
+/* 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 <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..9a6c5d3c
--- /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;
+class 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/net/PasteUpload.cpp b/logic/net/PasteUpload.cpp
index 402eb400..ccd7dc48 100644
--- a/logic/net/PasteUpload.cpp
+++ b/logic/net/PasteUpload.cpp
@@ -15,7 +15,7 @@ void PasteUpload::executeTask()
QNetworkRequest request(QUrl("http://paste.ee/api"));
request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)");
QByteArray content(
- "key=public&description=MultiMC5+Log+File&language=plain&format=json&paste=" +
+ "key=public&description=MultiMC5+Log+File&language=plain&format=json&expire=2592000&paste=" +
m_text.toUtf8());
request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
request.setRawHeader("Content-Length", QByteArray::number(content.size()));
@@ -77,9 +77,8 @@ bool PasteUpload::parseResult(QJsonDocument doc)
QLOG_ERROR() << "paste.ee reported error:" << QString(object.value("error").toString());
return false;
}
- // FIXME: not the place for GUI things.
- QString pasteUrl = object.value("paste").toObject().value("link").toString();
- QDesktopServices::openUrl(pasteUrl);
+ m_pasteLink = object.value("paste").toObject().value("link").toString();
+ m_pasteID = object.value("paste").toObject().value("id").toString();
return true;
}
diff --git a/logic/net/PasteUpload.h b/logic/net/PasteUpload.h
index 55cfabf4..0ddc8cef 100644
--- a/logic/net/PasteUpload.h
+++ b/logic/net/PasteUpload.h
@@ -10,7 +10,14 @@ class PasteUpload : public Task
public:
PasteUpload(QWidget *window, QString text);
virtual ~PasteUpload(){};
-
+ QString pasteLink()
+ {
+ return m_pasteLink;
+ }
+ QString pasteID()
+ {
+ return m_pasteID;
+ }
protected:
virtual void executeTask();
@@ -19,6 +26,8 @@ private:
QString m_text;
QString m_error;
QWidget *m_window;
+ QString m_pasteID;
+ QString m_pasteLink;
std::shared_ptr<QNetworkReply> m_reply;
public
slots:
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/depends/settings/inifile.cpp b/logic/settings/INIFile.cpp
index b2098913..9cf76c0a 100644
--- a/depends/settings/inifile.cpp
+++ b/logic/settings/INIFile.cpp
@@ -13,7 +13,7 @@
* limitations under the License.
*/
-#include "inifile.h"
+#include "logic/settings/INIFile.h"
#include <QFile>
#include <QTextStream>
diff --git a/depends/settings/inifile.h b/logic/settings/INIFile.h
index 4fb0054d..991198e6 100644
--- a/depends/settings/inifile.h
+++ b/logic/settings/INIFile.h
@@ -19,10 +19,8 @@
#include <QVariant>
#include <QIODevice>
-#include "libsettings_config.h"
-
// Sectionless INI parser (for instance config files)
-class LIBSETTINGS_EXPORT INIFile : public QMap<QString, QVariant>
+class INIFile : public QMap<QString, QVariant>
{
public:
explicit INIFile();
diff --git a/depends/settings/inisettingsobject.cpp b/logic/settings/INISettingsObject.cpp
index 2cee8e3c..eb5e6dd4 100644
--- a/depends/settings/inisettingsobject.cpp
+++ b/logic/settings/INISettingsObject.cpp
@@ -13,8 +13,8 @@
* limitations under the License.
*/
-#include "inisettingsobject.h"
-#include "setting.h"
+#include "INISettingsObject.h"
+#include "Setting.h"
INISettingsObject::INISettingsObject(const QString &path, QObject *parent)
: SettingsObject(parent)
diff --git a/depends/settings/inisettingsobject.h b/logic/settings/INISettingsObject.h
index 12370896..86b4b098 100644
--- a/depends/settings/inisettingsobject.h
+++ b/logic/settings/INISettingsObject.h
@@ -17,16 +17,14 @@
#include <QObject>
-#include "inifile.h"
+#include "logic/settings/INIFile.h"
-#include "settingsobject.h"
-
-#include "libsettings_config.h"
+#include "logic/settings/SettingsObject.h"
/*!
* \brief A settings object that stores its settings in an INIFile.
*/
-class LIBSETTINGS_EXPORT INISettingsObject : public SettingsObject
+class INISettingsObject : public SettingsObject
{
Q_OBJECT
public:
diff --git a/depends/settings/overridesetting.cpp b/logic/settings/OverrideSetting.cpp
index 7b5f5418..69d976bd 100644
--- a/depends/settings/overridesetting.cpp
+++ b/logic/settings/OverrideSetting.cpp
@@ -13,7 +13,7 @@
* limitations under the License.
*/
-#include "overridesetting.h"
+#include "OverrideSetting.h"
OverrideSetting::OverrideSetting(std::shared_ptr<Setting> other)
: Setting(other->configKeys(), QVariant())
diff --git a/depends/settings/overridesetting.h b/logic/settings/OverrideSetting.h
index 5ef901d0..1d1582d2 100644
--- a/depends/settings/overridesetting.h
+++ b/logic/settings/OverrideSetting.h
@@ -18,9 +18,7 @@
#include <QObject>
#include <memory>
-#include "setting.h"
-
-#include "libsettings_config.h"
+#include "Setting.h"
/*!
* \brief A setting that 'overrides another.'
@@ -28,7 +26,7 @@
* The other setting can be (and usually is) a part of a different SettingsObject
* than this one.
*/
-class LIBSETTINGS_EXPORT OverrideSetting : public Setting
+class OverrideSetting : public Setting
{
Q_OBJECT
public:
diff --git a/depends/settings/setting.cpp b/logic/settings/Setting.cpp
index 0d685771..5bdfe0fc 100644
--- a/depends/settings/setting.cpp
+++ b/logic/settings/Setting.cpp
@@ -13,8 +13,8 @@
* limitations under the License.
*/
-#include "setting.h"
-#include "settingsobject.h"
+#include "Setting.h"
+#include "logic/settings/SettingsObject.h"
Setting::Setting(QStringList synonyms, QVariant defVal)
: QObject(), m_synonyms(synonyms), m_defVal(defVal)
@@ -44,7 +44,7 @@ QVariant Setting::defValue() const
void Setting::set(QVariant value)
{
- emit settingChanged(*this, value);
+ emit SettingChanged(*this, value);
}
void Setting::reset()
diff --git a/depends/settings/setting.h b/logic/settings/Setting.h
index a73474d2..406bdada 100644
--- a/depends/settings/setting.h
+++ b/logic/settings/Setting.h
@@ -20,14 +20,12 @@
#include <QStringList>
#include <memory>
-#include "libsettings_config.h"
-
class SettingsObject;
/*!
*
*/
-class LIBSETTINGS_EXPORT Setting : public QObject
+class Setting : public QObject
{
Q_OBJECT
public:
@@ -86,7 +84,7 @@ signals:
* \param setting A reference to the Setting that changed.
* \param value This Setting object's new value.
*/
- void settingChanged(const Setting &setting, QVariant value);
+ void SettingChanged(const Setting &setting, QVariant value);
/*!
* \brief Signal emitted when this Setting object's value resets to default.
@@ -98,7 +96,7 @@ public
slots:
/*!
* \brief Changes the setting's value.
- * This is done by emitting the settingChanged() signal which will then be
+ * This is done by emitting the SettingChanged() signal which will then be
* handled by the SettingsObject object and cause the setting to change.
* \param value The new value.
*/
diff --git a/depends/settings/settingsobject.cpp b/logic/settings/SettingsObject.cpp
index 0e3030df..37f68251 100644
--- a/depends/settings/settingsobject.cpp
+++ b/logic/settings/SettingsObject.cpp
@@ -13,9 +13,10 @@
* limitations under the License.
*/
-#include "settingsobject.h"
-#include "setting.h"
-#include "overridesetting.h"
+#include "logic/settings/SettingsObject.h"
+#include "logic/settings/Setting.h"
+#include "logic/settings/OverrideSetting.h"
+#include "logger/QsLog.h"
#include <QVariant>
@@ -32,9 +33,8 @@ std::shared_ptr<Setting> SettingsObject::registerOverride(std::shared_ptr<Settin
{
if (contains(original->id()))
{
- qDebug(QString("Failed to register setting %1. ID already exists.")
- .arg(original->id())
- .toUtf8());
+ QLOG_ERROR() << QString("Failed to register setting %1. ID already exists.")
+ .arg(original->id());
return nullptr; // Fail
}
auto override = std::make_shared<OverrideSetting>(original);
@@ -50,9 +50,8 @@ std::shared_ptr<Setting> SettingsObject::registerSetting(QStringList synonyms, Q
return nullptr;
if (contains(synonyms.first()))
{
- qDebug(QString("Failed to register setting %1. ID already exists.")
- .arg(synonyms.first())
- .toUtf8());
+ QLOG_ERROR() << QString("Failed to register setting %1. ID already exists.")
+ .arg(synonyms.first());
return nullptr; // Fail
}
auto setting = std::make_shared<Setting>(synonyms, defVal);
@@ -62,28 +61,6 @@ std::shared_ptr<Setting> SettingsObject::registerSetting(QStringList synonyms, Q
return setting;
}
-/*
-
-bool SettingsObject::registerSetting(Setting *setting)
-{
- if (contains(setting->id()))
- {
- qDebug(QString("Failed to register setting %1. ID already exists.")
- .arg(setting->id())
- .toUtf8());
- return false; // Fail
- }
-
- m_settings.insert(setting->id(), setting);
- setting->setParent(this); // Take ownership.
-
- // Connect signals.
- connectSignals(*setting);
-
- // qDebug(QString("Registered setting %1.").arg(setting->id()).toUtf8());
- return true;
-}
-*/
std::shared_ptr<Setting> SettingsObject::getSetting(const QString &id) const
{
// Make sure there is a setting with the given ID.
@@ -104,7 +81,7 @@ bool SettingsObject::set(const QString &id, QVariant value)
auto setting = getSetting(id);
if (!setting)
{
- qDebug(QString("Error changing setting %1. Setting doesn't exist.").arg(id).toUtf8());
+ QLOG_ERROR() << QString("Error changing setting %1. Setting doesn't exist.").arg(id);
return false;
}
else
@@ -137,10 +114,10 @@ bool SettingsObject::reload()
void SettingsObject::connectSignals(const Setting &setting)
{
- connect(&setting, SIGNAL(settingChanged(const Setting &, QVariant)),
+ connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)),
SLOT(changeSetting(const Setting &, QVariant)));
- connect(&setting, SIGNAL(settingChanged(const Setting &, QVariant)),
- SIGNAL(settingChanged(const Setting &, QVariant)));
+ connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)),
+ SIGNAL(SettingChanged(const Setting &, QVariant)));
connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &)));
connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &)));
diff --git a/depends/settings/settingsobject.h b/logic/settings/SettingsObject.h
index b1b26b09..4f11310d 100644
--- a/depends/settings/settingsobject.h
+++ b/logic/settings/SettingsObject.h
@@ -21,8 +21,6 @@
#include <QVariant>
#include <memory>
-#include "libsettings_config.h"
-
class Setting;
/*!
@@ -37,7 +35,7 @@ class Setting;
*
* \sa Setting
*/
-class LIBSETTINGS_EXPORT SettingsObject : public QObject
+class SettingsObject : public QObject
{
Q_OBJECT
public:
@@ -93,7 +91,7 @@ public:
/*!
* \brief Sets the value of the setting with the given ID.
- * If no setting with the given ID exists, returns false and logs to qDebug
+ * If no setting with the given ID exists, returns false
* \param id The ID of the setting to change.
* \param value The new value of the setting.
* \return True if successful, false if it failed.
@@ -123,11 +121,11 @@ signals:
/*!
* \brief Signal emitted when one of this SettingsObject object's settings changes.
* This is usually just connected directly to each Setting object's
- * settingChanged() signals.
+ * SettingChanged() signals.
* \param setting A reference to the Setting object that changed.
* \param value The Setting object's new value.
*/
- void settingChanged(const Setting &setting, QVariant value);
+ void SettingChanged(const Setting &setting, QVariant value);
/*!
* \brief Signal emitted when one of this SettingsObject object's settings resets.
@@ -142,7 +140,7 @@ slots:
/*!
* \brief Changes a setting.
* This slot is usually connected to each Setting object's
- * settingChanged() signal. The signal is emitted, causing this slot
+ * SettingChanged() signal. The signal is emitted, causing this slot
* to update the setting's value in the config file.
* \param setting A reference to the Setting object that changed.
* \param value The setting's new value.
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/logic/tools/JProfiler.cpp b/logic/tools/JProfiler.cpp
index 35d5d304..988a875a 100644
--- a/logic/tools/JProfiler.cpp
+++ b/logic/tools/JProfiler.cpp
@@ -3,7 +3,7 @@
#include <QDir>
#include <QMessageBox>
-#include "settingsobject.h"
+#include "logic/settings/SettingsObject.h"
#include "logic/MinecraftProcess.h"
#include "logic/BaseInstance.h"
#include "MultiMC.h"
diff --git a/logic/tools/JVisualVM.cpp b/logic/tools/JVisualVM.cpp
index 02e7d8b5..09ab1762 100644
--- a/logic/tools/JVisualVM.cpp
+++ b/logic/tools/JVisualVM.cpp
@@ -3,7 +3,7 @@
#include <QDir>
#include <QStandardPaths>
-#include "settingsobject.h"
+#include "logic/settings/SettingsObject.h"
#include "logic/MinecraftProcess.h"
#include "logic/BaseInstance.h"
#include "MultiMC.h"
diff --git a/logic/tools/MCEditTool.cpp b/logic/tools/MCEditTool.cpp
index 36b8d5bd..91643585 100644
--- a/logic/tools/MCEditTool.cpp
+++ b/logic/tools/MCEditTool.cpp
@@ -5,7 +5,7 @@
#include <QDesktopServices>
#include <QUrl>
-#include "settingsobject.h"
+#include "logic/settings/SettingsObject.h"
#include "logic/BaseInstance.h"
#include "MultiMC.h"
diff --git a/logic/updater/UpdateChecker.cpp b/logic/updater/UpdateChecker.cpp
index e47e1c4c..c2895430 100644
--- a/logic/updater/UpdateChecker.cpp
+++ b/logic/updater/UpdateChecker.cpp
@@ -24,7 +24,7 @@
#include <QJsonArray>
#include <QJsonValue>
-#include <settingsobject.h>
+#include "logic/settings/SettingsObject.h"
#define API_VERSION 0
#define CHANLIST_FORMAT 0
diff --git a/main.cpp b/main.cpp
index a79cc014..618e6745 100644
--- a/main.cpp
+++ b/main.cpp
@@ -10,7 +10,7 @@
int main_gui(MultiMC &app)
{
// show main window
- QIcon::setThemeName("multimc");
+ QIcon::setThemeName(MMC->settings()->get("IconTheme").toString());
MainWindow mainWin;
mainWin.restoreState(QByteArray::fromBase64(MMC->settings()->get("MainWindowState").toByteArray()));
mainWin.restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("MainWindowGeometry").toByteArray()));
@@ -28,11 +28,14 @@ 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);
switch (app.status())
{
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/index.theme b/resources/multimc/index.theme
index 776792b7..8e1241e9 100644
--- a/resources/multimc/index.theme
+++ b/resources/multimc/index.theme
@@ -2,7 +2,7 @@
Name=multimc
Comment=MultiMC Default Icons
Inherits=default
-Directories=scalable/apps,8x8,16x16,22x22,24x24,32x32,48x48
+Directories=scalable/apps,8x8,16x16,22x22,24x24,32x32,48x48,scalable
[scalable/apps]
Size=48
@@ -30,4 +30,10 @@ Size=32
Size=48
[64x64]
-Size=64 \ No newline at end of file
+Size=64
+
+[scalable]
+Size=48
+Type=Scalable
+MinSize=16
+MaxSize=256
diff --git a/resources/multimc/multimc.qrc b/resources/multimc/multimc.qrc
index 1df22c29..5c49017b 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>
@@ -131,5 +164,8 @@
<file>24x24/noaccount.png</file>
<file>32x32/noaccount.png</file>
<file>48x48/noaccount.png</file>
+
+ <!-- placeholder when loading screenshot images -->
+ <file>scalable/screenshot-placeholder.svg</file>
</qresource>
</RCC>
diff --git a/resources/multimc/scalable/screenshot-placeholder.svg b/resources/multimc/scalable/screenshot-placeholder.svg
new file mode 100644
index 00000000..a7a2a3d6
--- /dev/null
+++ b/resources/multimc/scalable/screenshot-placeholder.svg
@@ -0,0 +1,86 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="256"
+ height="256"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.5 r10040"
+ sodipodi:docname="screenshot-placeholder.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.49497475"
+ inkscape:cx="-33.672765"
+ inkscape:cy="136.19802"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:snap-nodes="false"
+ inkscape:window-width="1612"
+ inkscape:window-height="1026"
+ inkscape:window-x="1677"
+ inkscape:window-y="-4"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2985"
+ empspacing="5"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true"
+ spacingx="8px"
+ spacingy="8px" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-796.36218)">
+ <g
+ id="g3009"
+ style="fill:#000000">
+ <path
+ id="rect2987"
+ transform="translate(0,796.36218)"
+ d="M 24 8 C 15.136 8 8 15.136 8 24 L 8 232 C 8 240.864 15.136 248 24 248 L 232 248 C 240.864 248 248 240.864 248 232 L 248 24 C 248 15.136 240.864 8 232 8 L 24 8 z M 24 16 L 232 16 C 236.432 16 240 19.568 240 24 L 240 232 C 240 236.432 236.432 240 232 240 L 24 240 C 19.568 240 16 236.432 16 232 L 16 24 C 16 19.568 19.568 16 24 16 z "
+ style="opacity:0.75000000000000000;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.75000000000000000;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ style="fill:#000000;opacity:0.75000000000000000;stroke:none"
+ inkscape:connector-curvature="0"
+ d="m 85.749999,937.9006 c 0,24.30067 18.915811,43.99997 42.249991,43.99997 23.33419,0 42.25001,-19.6993 42.25001,-43.99997 0,-24.30069 -18.91582,-43.99999 -42.25001,-43.99999 -23.33418,0 -42.249991,19.6993 -42.249991,43.99999 z M 219,863.43909 h -45.5 c -3.25,-13.53846 -6.50001,-27.07691 -19.5,-27.07691 h -52 c -13.000001,0 -16.250001,13.53845 -19.500001,27.07691 H 37 c -7.15,0 -13,6.09231 -13,13.53846 v 121.84603 c 0,7.44632 5.85,13.53862 13,13.53862 h 182 c 7.15,0 13,-6.0923 13,-13.53862 V 876.97755 c 0,-7.44615 -5.85,-13.53846 -13,-13.53846 z m -91.00001,134.53849 c -31.860147,0 -57.687491,-26.89678 -57.687491,-60.07698 0,-33.1798 25.827344,-60.0769 57.687491,-60.0769 31.86057,0 57.68751,26.8971 57.68751,60.0769 0,33.1802 -25.82653,60.07698 -57.68751,60.07698 z M 219,904.05445 h -26 v -13.53846 h 26 v 13.53846 z"
+ id="path2996" />
+ </g>
+ </g>
+</svg>
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/pe_dark/16x16/status-bad.png b/resources/pe_dark/16x16/status-bad.png
new file mode 100644
index 00000000..0187b990
--- /dev/null
+++ b/resources/pe_dark/16x16/status-bad.png
Binary files differ
diff --git a/resources/pe_dark/16x16/status-good.png b/resources/pe_dark/16x16/status-good.png
new file mode 100644
index 00000000..661405db
--- /dev/null
+++ b/resources/pe_dark/16x16/status-good.png
Binary files differ
diff --git a/resources/pe_dark/22x22/status-bad.png b/resources/pe_dark/22x22/status-bad.png
new file mode 100644
index 00000000..7b6e9bcb
--- /dev/null
+++ b/resources/pe_dark/22x22/status-bad.png
Binary files differ
diff --git a/resources/pe_dark/22x22/status-good.png b/resources/pe_dark/22x22/status-good.png
new file mode 100644
index 00000000..3da59106
--- /dev/null
+++ b/resources/pe_dark/22x22/status-good.png
Binary files differ
diff --git a/resources/pe_dark/24x24/status-bad.png b/resources/pe_dark/24x24/status-bad.png
new file mode 100644
index 00000000..bef1e5a3
--- /dev/null
+++ b/resources/pe_dark/24x24/status-bad.png
Binary files differ
diff --git a/resources/pe_dark/24x24/status-good.png b/resources/pe_dark/24x24/status-good.png
new file mode 100644
index 00000000..2b55aeae
--- /dev/null
+++ b/resources/pe_dark/24x24/status-good.png
Binary files differ
diff --git a/resources/pe_dark/32x32/status-bad.png b/resources/pe_dark/32x32/status-bad.png
new file mode 100644
index 00000000..fc700e19
--- /dev/null
+++ b/resources/pe_dark/32x32/status-bad.png
Binary files differ
diff --git a/resources/pe_dark/32x32/status-good.png b/resources/pe_dark/32x32/status-good.png
new file mode 100644
index 00000000..eff50142
--- /dev/null
+++ b/resources/pe_dark/32x32/status-good.png
Binary files differ
diff --git a/resources/pe_dark/48x48/status-bad.png b/resources/pe_dark/48x48/status-bad.png
new file mode 100644
index 00000000..846ac51d
--- /dev/null
+++ b/resources/pe_dark/48x48/status-bad.png
Binary files differ
diff --git a/resources/pe_dark/48x48/status-good.png b/resources/pe_dark/48x48/status-good.png
new file mode 100644
index 00000000..56574e38
--- /dev/null
+++ b/resources/pe_dark/48x48/status-good.png
Binary files differ
diff --git a/resources/pe_dark/64x64/status-bad.png b/resources/pe_dark/64x64/status-bad.png
new file mode 100644
index 00000000..b87772cc
--- /dev/null
+++ b/resources/pe_dark/64x64/status-bad.png
Binary files differ
diff --git a/resources/pe_dark/64x64/status-good.png b/resources/pe_dark/64x64/status-good.png
new file mode 100644
index 00000000..afedf8cb
--- /dev/null
+++ b/resources/pe_dark/64x64/status-good.png
Binary files differ
diff --git a/resources/pe_dark/index.theme b/resources/pe_dark/index.theme
new file mode 100644
index 00000000..2768cb50
--- /dev/null
+++ b/resources/pe_dark/index.theme
@@ -0,0 +1,39 @@
+[Icon Theme]
+Name=pe_dark
+Comment=Icons by pexner (dark)
+Inherits=multimc
+Directories=scalable/apps,8x8,16x16,22x22,24x24,32x32,48x48,scalable
+
+[scalable/apps]
+Size=48
+Type=scalable
+MinSize=1
+MaxSize=512
+Context=Applications
+
+[8x8]
+Size=8
+
+[16x16]
+Size=16
+
+[22x22]
+Size=22
+
+[24x24]
+Size=24
+
+[32x32]
+Size=32
+
+[48x48]
+Size=48
+
+[64x64]
+Size=64
+
+[scalable]
+Size=48
+Type=Scalable
+MinSize=16
+MaxSize=256
diff --git a/resources/pe_dark/pe_dark.qrc b/resources/pe_dark/pe_dark.qrc
new file mode 100644
index 00000000..98823a27
--- /dev/null
+++ b/resources/pe_dark/pe_dark.qrc
@@ -0,0 +1,57 @@
+<!DOCTYPE RCC>
+<RCC version="1.0">
+ <qresource prefix="/icons/pe_dark">
+ <file>index.theme</file>
+ <!-- OK console icon. Our own -->
+ <file>scalable/console.svg</file>
+
+ <!-- ERROR console icon. Our own -->
+ <file>scalable/console_error.svg</file>
+
+ <!-- About dialog. Our own. -->
+ <file>scalable/about.svg</file>
+
+ <!-- Report bug. Our own. -->
+ <file>scalable/bug.svg</file>
+
+ <!-- Patron logo, black and white. (C) 2014 Patreon, Inc., http://www.patreon.com/toolbox?ftyp=media -->
+ <file>scalable/patreon.svg</file>
+
+ <!-- Show mods folder. Our own. -->
+ <file>scalable/centralmods.svg</file>
+
+ <!-- Update. Our own. -->
+ <file>scalable/checkupdate.svg</file>
+
+ <!-- copy instance. Our own. -->
+ <file>scalable/copy.svg</file>
+
+ <!-- New instance. Our own. -->
+ <file>scalable/new.svg</file>
+
+ <!-- Bad status. Our own. -->
+ <file>16x16/status-bad.png</file>
+ <file>22x22/status-bad.png</file>
+ <file>24x24/status-bad.png</file>
+ <file>32x32/status-bad.png</file>
+ <file>48x48/status-bad.png</file>
+ <file>64x64/status-bad.png</file>
+
+ <!-- Good status. Our own. -->
+ <file>16x16/status-good.png</file>
+ <file>22x22/status-good.png</file>
+ <file>24x24/status-good.png</file>
+ <file>32x32/status-good.png</file>
+ <file>48x48/status-good.png</file>
+ <file>64x64/status-good.png</file>
+
+ <!-- Refresh, Our own. -->
+ <file>scalable/refresh.svg</file>
+
+ <!-- Settings, Our own. -->
+ <file>scalable/settings.svg</file>
+
+ <!-- View folder. Our own. -->
+ <file>scalable/viewfolder.svg</file>
+ </qresource>
+</RCC>
diff --git a/resources/pe_dark/scalable/about.svg b/resources/pe_dark/scalable/about.svg
new file mode 100644
index 00000000..35eb8e87
--- /dev/null
+++ b/resources/pe_dark/scalable/about.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="297.5" y1="-370.9326" x2="297.5" y2="-404.8098" gradientTransform="matrix(1 0 0 -1 -281.5 -372)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="0.4868" style="stop-color:#030303"/>
+ <stop offset="0.6368" style="stop-color:#0D0D0D"/>
+ <stop offset="0.774" style="stop-color:#1E1E1E"/>
+ <stop offset="0.9029" style="stop-color:#363636"/>
+ <stop offset="1" style="stop-color:#4D4D4D"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M24.753,14.774C24.629,15.172,24.417,15.585,24,16c-2,2-2,4-2,4v2c0,1.105-0.896,2-2,2h-8
+ c-1.104,0-2-0.895-2-2v-2c0-1.453-1.049-2.895-1.627-3.581c-0.017-0.021-0.032-0.042-0.05-0.062C8.131,16.132,8,16,8,16v-0.071
+ C6.756,14.272,6,12.233,6,10C6,4.478,10.478,0,16,0c5.521,0,10,4.478,10,10C26,11.739,25.54,13.357,24.753,14.774z M16,4
+ c-3.313,0-6,2.687-6,6c0,2.609,1.675,4.806,4,5.633V18c0,1.105,0.896,2,2,2c1.104,0,2-0.895,2-2v-2.367c2.325-0.827,4-3.024,4-5.633
+ C22,6.687,19.312,4,16,4z M13,26h6c0.552,0,1,0.448,1,1s-0.448,1-1,1h-6c-0.553,0-1-0.448-1-1S12.447,26,13,26z M15,30h2
+ c0.552,0,1,0.449,1,1c0,0.552-0.448,1-1,1h-2c-0.553,0-1-0.448-1-1C14,30.449,14.447,30,15,30z"/>
+</svg>
diff --git a/resources/pe_dark/scalable/bug.svg b/resources/pe_dark/scalable/bug.svg
new file mode 100644
index 00000000..6f78087f
--- /dev/null
+++ b/resources/pe_dark/scalable/bug.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<g>
+ <g>
+
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="297.5" y1="-372" x2="297.5" y2="-403.7599" gradientTransform="matrix(1 0 0 -1 -281.5 -372)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="0.4868" style="stop-color:#030303"/>
+ <stop offset="0.6368" style="stop-color:#0D0D0D"/>
+ <stop offset="0.774" style="stop-color:#1E1E1E"/>
+ <stop offset="0.9029" style="stop-color:#363636"/>
+ <stop offset="1" style="stop-color:#4D4D4D"/>
+ </linearGradient>
+ <path fill="url(#SVGID_1_)" d="M25.375,10.375h-1.702c-0.106,0.589-0.173,1.214-0.173,1.875s0.066,1.286,0.173,1.875h1.702
+ c1.036,0,1.875-0.839,1.875-1.875S26.411,10.375,25.375,10.375z M25.375,1C20.043,6.449,12.25,6.625,12.25,6.625H6.625
+ C3.519,6.625,1,9.144,1,12.25s2.519,5.625,5.625,5.625c0,0.26,0.052,0.506,0.147,0.73c0.177,0.418,0.512,0.746,0.919,0.942
+ C7.72,19.562,7.74,19.59,7.77,19.604c0.224,0.094,0.471,0.146,0.729,0.146l-1.875,9.375C6.625,30.161,7.464,31,8.5,31h3.75
+ c1.036,0,1.875-0.839,1.875-1.875s-0.839-1.875-1.875-1.875l1.5-7.5h0.375c0.847,0,1.534-0.57,1.767-1.34
+ c2.73,0.637,6.493,2.033,9.483,5.09C28.48,23.5,31,18.463,31,12.25S28.48,1,25.375,1z M6.625,14.125H4.75v-3.75h1.875V14.125z
+ M25.375,19.75c-2.07,0-3.75-3.357-3.75-7.5c0-4.141,1.68-7.5,3.75-7.5s3.75,3.359,3.75,7.5
+ C29.125,16.393,27.445,19.75,25.375,19.75z"/>
+ </g>
+</g>
+</svg>
diff --git a/resources/pe_dark/scalable/centralmods.svg b/resources/pe_dark/scalable/centralmods.svg
new file mode 100644
index 00000000..7c06f505
--- /dev/null
+++ b/resources/pe_dark/scalable/centralmods.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="579" y1="809" x2="579" y2="840.7599" gradientTransform="matrix(1 0 0 1 -563 -809)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="0.4868" style="stop-color:#030303"/>
+ <stop offset="0.6368" style="stop-color:#0D0D0D"/>
+ <stop offset="0.774" style="stop-color:#1E1E1E"/>
+ <stop offset="0.9029" style="stop-color:#363636"/>
+ <stop offset="1" style="stop-color:#4D4D4D"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M25.375,31H6.625C3.519,31,1,28.48,1,25.375V6.625C1,3.519,3.519,1,6.625,1h7.5
+ c2.445,0,4.506,1.57,5.281,3.75h5.969C28.48,4.75,31,7.269,31,10.375v15C31,28.48,28.48,31,25.375,31z M27.25,10.375
+ c0-1.036-0.839-1.875-1.875-1.875H16V6.625c0-1.036-0.839-1.875-1.875-1.875h-7.5c-1.036,0-1.875,0.839-1.875,1.875v18.75
+ c0,1.036,0.839,1.875,1.875,1.875h18.75c1.036,0,1.875-0.839,1.875-1.875V10.375z"/>
+<path d="M16,10.604l2.078,4.212l4.646,0.675l-3.363,3.278l0.795,4.627L16,21.211l-4.156,2.186l0.794-4.627L9.275,15.49l4.646-0.675
+ L16,10.604z"/>
+</svg>
diff --git a/resources/pe_dark/scalable/checkupdate.svg b/resources/pe_dark/scalable/checkupdate.svg
new file mode 100644
index 00000000..59a5ad75
--- /dev/null
+++ b/resources/pe_dark/scalable/checkupdate.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="297.5" y1="-372" x2="297.5" y2="-403.7599" gradientTransform="matrix(1 0 0 -1 -281.5 -372)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="0.4868" style="stop-color:#030303"/>
+ <stop offset="0.6368" style="stop-color:#0D0D0D"/>
+ <stop offset="0.774" style="stop-color:#1E1E1E"/>
+ <stop offset="0.9029" style="stop-color:#363636"/>
+ <stop offset="1" style="stop-color:#4D4D4D"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M25.375,23.5h-0.337c-0.033-0.092-0.056-0.188-0.093-0.275c-0.621-1.508-1.445-2.634-3.32-3.154
+ v-2.195c0-3.102-2.523-5.625-5.625-5.625c-3.101,0-5.625,2.523-5.625,5.625v2.191c-1.875,0.521-2.698,1.646-3.321,3.147
+ c-0.018,0.043-0.028,0.093-0.043,0.136C3.582,22.659,1,19.631,1,16v-1.875c0-4.142,3.358-7.5,7.5-7.5
+ c0.714,0,1.388,0.131,2.042,0.317C11.26,3.548,14.268,1,17.875,1c4.143,0,7.5,3.358,7.5,7.5c0,0.662-0.113,1.292-0.271,1.902
+ c0.092-0.005,0.181-0.027,0.271-0.027C28.484,10.375,31,12.895,31,16v1.875C31,20.982,28.484,23.5,25.375,23.5z M12.25,23.5h1.88
+ v-5.625c0-1.037,0.837-1.875,1.875-1.875c1.039,0,1.877,0.838,1.877,1.875V23.5h1.871c0.756,0,1.442,0.458,1.731,1.157
+ c0.072,0.176,0.115,0.354,0.136,0.536c0.05,0.55-0.14,1.104-0.542,1.508l-3.753,3.75C16.961,30.814,16.479,31,16,31
+ s-0.958-0.186-1.324-0.549l-3.75-3.75c-0.399-0.403-0.591-0.958-0.539-1.508c0.016-0.184,0.057-0.36,0.132-0.536
+ C10.81,23.958,11.493,23.5,12.25,23.5z"/>
+<rect fill="none" width="32" height="32"/>
+</svg>
diff --git a/resources/pe_dark/scalable/console.svg b/resources/pe_dark/scalable/console.svg
new file mode 100644
index 00000000..ec14ab68
--- /dev/null
+++ b/resources/pe_dark/scalable/console.svg
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ 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="32px"
+ height="32px"
+ id="svg2985"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="console.svg"
+ inkscape:export-filename="/home/peterix/projects/MultiMC4/src/resources/console.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs2987">
+ <linearGradient
+ id="linearGradient6244">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop6246" />
+ <stop
+ id="stop6254"
+ offset="0.4642857"
+ style="stop-color:#000000;stop-opacity:1" />
+ <stop
+ id="stop6252"
+ offset="0.53571427"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="1"
+ id="stop6248" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6212">
+ <stop
+ style="stop-color:#959595;stop-opacity:1"
+ offset="0"
+ id="stop6214" />
+ <stop
+ id="stop6224"
+ offset="0.14849657"
+ style="stop-color:#b0b0b0;stop-opacity:1;" />
+ <stop
+ id="stop6220"
+ offset="0.41380492"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#cacaca;stop-opacity:1;"
+ offset="0.65110856"
+ id="stop6222" />
+ <stop
+ id="stop6228"
+ offset="0.87847149"
+ style="stop-color:#b0b0b0;stop-opacity:1;" />
+ <stop
+ style="stop-color:#969696;stop-opacity:1;"
+ offset="1"
+ id="stop6216" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6194"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#8e8e8e;stop-opacity:1;"
+ offset="0"
+ id="stop6196" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3050">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3052" />
+ <stop
+ id="stop3840"
+ offset="0.64285713"
+ style="stop-color:#164315;stop-opacity:1" />
+ <stop
+ id="stop3838"
+ offset="0.85714287"
+ style="stop-color:#24a91f;stop-opacity:1" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1"
+ offset="1"
+ id="stop3054" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3050"
+ id="linearGradient3056"
+ x1="15"
+ y1="16"
+ x2="15"
+ y2="2"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6212"
+ id="linearGradient6218"
+ x1="19.373737"
+ y1="18.689655"
+ x2="30.317204"
+ y2="31.204504"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="repeat" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6244"
+ id="linearGradient6250"
+ x1="2"
+ y1="2"
+ x2="30"
+ y2="30"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6244"
+ id="linearGradient6258"
+ gradientUnits="userSpaceOnUse"
+ x1="2"
+ y1="2"
+ x2="30"
+ y2="30"
+ gradientTransform="matrix(1.0666667,0,0,1.0666667,-33.066667,-33.066667)" />
+ <filter
+ inkscape:collect="always"
+ id="filter6272">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.32596875"
+ id="feGaussianBlur6274" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.5"
+ inkscape:cx="-151.66767"
+ inkscape:cy="-123.35228"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1607"
+ inkscape:window-height="1030"
+ inkscape:window-x="1676"
+ inkscape:window-y="-3"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2995"
+ empspacing="2"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata2990">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ id="rect2993"
+ width="32"
+ height="32"
+ x="-1.9984014e-15"
+ y="4.4408921e-16"
+ ry="2.6666667" />
+ <rect
+ style="fill:url(#linearGradient3056);fill-opacity:1;stroke:none"
+ id="rect2993-1"
+ width="28"
+ height="28"
+ x="2"
+ y="2"
+ ry="2.3333333" />
+ <g
+ style="font-size:85.93203735px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;font-family:Sans"
+ id="text3001">
+ <path
+ style="font-size:45.2372551px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#008000;fill-opacity:1;stroke:none;filter:url(#filter6272);font-family:Sans"
+ d="m 7.4824597,10.427541 2.2812496,5.78125 0.2812497,-0.71875 -1.9999993,-5.0625 -0.5625,0 z m 8.4687493,0 0,11.34375 -1.875,0 0,0.65625 2.6875,0 0,-12 -0.8125,0 z m 11.625,0.3125 0,2.21875 c -0.5312,-0.3906 -1.046212,-0.693063 -1.5625,-0.875 -0.516305,-0.181918 -1.054102,-0.24999 -1.625,-0.25 -1.022671,10e-6 -1.820457,0.324952 -2.40625,1.03125 -0.585803,0.706316 -0.875004,1.696118 -0.875,2.9375 -4e-6,1.236043 0.289197,2.199946 0.875,2.90625 0.23333,0.281334 0.509858,0.486976 0.8125,0.65625 -0.585803,-0.706304 -0.875004,-1.670207 -0.875,-2.90625 -4e-6,-1.241382 0.289197,-2.231184 0.875,-2.9375 0.585793,-0.706298 1.383579,-1.06249 2.40625,-1.0625 0.570898,10e-6 1.139945,0.09933 1.65625,0.28125 0.516288,0.181937 1.0313,0.45315 1.5625,0.84375 l 0,-2.46875 c -0.271888,-0.152046 -0.55994,-0.261514 -0.84375,-0.375 z m -21.2812493,2.28125 0,8.75 -1.875,0 0,0.65625 2.71875,0 0,-7.28125 -0.84375,-2.125 z m 6.9374993,0 -2.5625,6.46875 -1.2187493,0 0.2499996,0.625 1.8124997,0 1.71875,-4.34375 0,-2.75 z m 15.1875,6.25 c -0.287318,0.211276 -0.560795,0.412277 -0.84375,0.5625 l 0,1.28125 c -0.526236,0.294295 -1.054104,0.537677 -1.625,0.6875 -0.570913,0.149823 -1.191958,0.21875 -1.8125,0.21875 -1.332472,0 -2.440972,-0.298702 -3.375,-0.875 1.063521,0.998245 2.465058,1.5 4.21875,1.5 0.620542,0 1.210337,-0.06893 1.78125,-0.21875 0.570896,-0.149823 1.130014,-0.361955 1.65625,-0.65625 l 0,-2.5 z"
+ id="text3022"
+ inkscape:connector-curvature="0" />
+ <text
+ xml:space="preserve"
+ style="font-size:45.2372551px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;font-family:Sans"
+ x="2.7196128"
+ y="21.15748"
+ id="text3014"
+ sodipodi:linespacing="125%"
+ transform="scale(0.9632149,1.0381899)"><tspan
+ sodipodi:role="line"
+ id="tspan3016"
+ x="2.7196128"
+ y="21.15748"
+ style="font-size:15.83304024000000076px;font-weight:bold;-inkscape-font-specification:Bitstream Vera Sans Bold;font-family:Bitstream Vera Sans;font-style:normal;font-stretch:normal;font-variant:normal">MC</tspan></text>
+ </g>
+ </g>
+</svg>
diff --git a/resources/pe_dark/scalable/console_error.svg b/resources/pe_dark/scalable/console_error.svg
new file mode 100644
index 00000000..a71c6b35
--- /dev/null
+++ b/resources/pe_dark/scalable/console_error.svg
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ 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="32px"
+ height="32px"
+ id="svg2985"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="console_error.svg"
+ inkscape:export-filename="/home/peterix/projects/MultiMC4/src/resources/console_error.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs2987">
+ <linearGradient
+ id="linearGradient6244">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop6246" />
+ <stop
+ id="stop6254"
+ offset="0.4642857"
+ style="stop-color:#000000;stop-opacity:1" />
+ <stop
+ id="stop6252"
+ offset="0.53571427"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="1"
+ id="stop6248" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6212">
+ <stop
+ style="stop-color:#959595;stop-opacity:1"
+ offset="0"
+ id="stop6214" />
+ <stop
+ id="stop6224"
+ offset="0.14849657"
+ style="stop-color:#b0b0b0;stop-opacity:1;" />
+ <stop
+ id="stop6220"
+ offset="0.41380492"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#cacaca;stop-opacity:1;"
+ offset="0.65110856"
+ id="stop6222" />
+ <stop
+ id="stop6228"
+ offset="0.87847149"
+ style="stop-color:#b0b0b0;stop-opacity:1;" />
+ <stop
+ style="stop-color:#969696;stop-opacity:1;"
+ offset="1"
+ id="stop6216" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6194"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#8e8e8e;stop-opacity:1;"
+ offset="0"
+ id="stop6196" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3050">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3052" />
+ <stop
+ id="stop3840"
+ offset="0.64285713"
+ style="stop-color:#431515;stop-opacity:1;" />
+ <stop
+ id="stop3838"
+ offset="0.85714287"
+ style="stop-color:#a91f1f;stop-opacity:1;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1"
+ offset="1"
+ id="stop3054" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3050"
+ id="linearGradient3056"
+ x1="15"
+ y1="16"
+ x2="15"
+ y2="2"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6244"
+ id="linearGradient6250"
+ x1="2"
+ y1="2"
+ x2="30"
+ y2="30"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6244"
+ id="linearGradient6258"
+ gradientUnits="userSpaceOnUse"
+ x1="2"
+ y1="2"
+ x2="30"
+ y2="30"
+ gradientTransform="matrix(1.0666667,0,0,1.0666667,-33.066667,-33.066667)" />
+ <filter
+ inkscape:collect="always"
+ id="filter6373">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.64"
+ id="feGaussianBlur6375" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.4142136"
+ inkscape:cx="149.24645"
+ inkscape:cy="89.508966"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1607"
+ inkscape:window-height="1030"
+ inkscape:window-x="1676"
+ inkscape:window-y="-3"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2995"
+ empspacing="2"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata2990">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ id="rect2993"
+ width="32"
+ height="32"
+ x="-1.9984014e-15"
+ y="4.4408921e-16"
+ ry="2.6666667" />
+ <rect
+ style="fill:url(#linearGradient3056);fill-opacity:1;stroke:none"
+ id="rect2993-1"
+ width="28"
+ height="28"
+ x="2"
+ y="2"
+ ry="2.3333333" />
+ <g
+ style="font-size:85.93203735px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;font-family:Sans"
+ id="text3001" />
+ <g
+ id="g6331"
+ transform="translate(6,-7)">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter6373)"
+ id="rect6363"
+ width="16"
+ height="16"
+ x="8"
+ y="8"
+ transform="translate(-6,7)" />
+ <path
+ id="path6377"
+ d="m 4,17 0,4 4,0 0,-4 z"
+ style="fill:#800000;fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ style="fill:#800000;fill-opacity:1;stroke:none"
+ d="m 8,21 0,2 -2,0 0,6 2,0 0,-2 4,0 0,2 2,0 0,-6 -2,0 0,-2 z"
+ id="path6379"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccc" />
+ <path
+ sodipodi:nodetypes="ccccc"
+ inkscape:connector-curvature="0"
+ style="fill:#800000;fill-opacity:1;stroke:none"
+ d="m 12,17 0,4 4,0 0,-4 z"
+ id="path6381" />
+ <path
+ sodipodi:nodetypes="ccccc"
+ inkscape:connector-curvature="0"
+ style="fill:#ff0000;fill-opacity:1;stroke:none"
+ d="m 6,19 0,2 2,0 0,-2 z"
+ id="path6383" />
+ <path
+ id="path6385"
+ d="m 12,19 0,2 2,0 0,-2 z"
+ style="fill:#ff0000;fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ sodipodi:nodetypes="ccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path6387"
+ d="m 8,23 0,2 -2,0 0,4 2,0 0,-2 4,0 0,2 2,0 0,-4 -2,0 0,-2 z"
+ style="fill:#ff0000;fill-opacity:1;stroke:none" />
+ </g>
+ </g>
+</svg>
diff --git a/resources/pe_dark/scalable/copy.svg b/resources/pe_dark/scalable/copy.svg
new file mode 100644
index 00000000..dc89de43
--- /dev/null
+++ b/resources/pe_dark/scalable/copy.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="15.9995" y1="0" x2="15.9995" y2="31.7589">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="0.4868" style="stop-color:#030303"/>
+ <stop offset="0.6368" style="stop-color:#0D0D0D"/>
+ <stop offset="0.774" style="stop-color:#1E1E1E"/>
+ <stop offset="0.9029" style="stop-color:#363636"/>
+ <stop offset="1" style="stop-color:#4D4D4D"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M27.25,25.031V10.375c0-3.106-2.519-5.625-5.625-5.625H6.969C7.745,2.57,9.804,1,12.25,1h13.125
+ C28.481,1,31,3.519,31,6.625V19.75C31,22.196,29.431,24.256,27.25,25.031z M25.375,12.25v13.125c0,3.107-2.519,5.625-5.625,5.625
+ H6.625C3.519,31,1,28.482,1,25.375V12.25c0-3.106,2.519-5.625,5.625-5.625H19.75C22.856,6.625,25.375,9.144,25.375,12.25z
+ M4.75,25.375c0,1.036,0.839,1.875,1.875,1.875H19.75c1.036,0,1.875-0.839,1.875-1.875v-11.25H4.75V25.375z M6.625,8.5
+ c-1.036,0-1.875,0.839-1.875,1.875s0.839,1.875,1.875,1.875S8.5,11.411,8.5,10.375S7.661,8.5,6.625,8.5z"/>
+<rect fill="none" width="32" height="32"/>
+</svg>
diff --git a/resources/pe_dark/scalable/new.svg b/resources/pe_dark/scalable/new.svg
new file mode 100644
index 00000000..88b9bb34
--- /dev/null
+++ b/resources/pe_dark/scalable/new.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="297.5" y1="-372" x2="297.5" y2="-403.7599" gradientTransform="matrix(1 0 0 -1 -281.5 -372)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="0.4868" style="stop-color:#030303"/>
+ <stop offset="0.6368" style="stop-color:#0D0D0D"/>
+ <stop offset="0.774" style="stop-color:#1E1E1E"/>
+ <stop offset="0.9029" style="stop-color:#363636"/>
+ <stop offset="1" style="stop-color:#4D4D4D"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M25.375,31H6.625C3.519,31,1,28.482,1,25.375V6.625C1,3.519,3.519,1,6.625,1h18.75
+ C28.48,1,31,3.519,31,6.625v18.75C31,28.482,28.48,31,25.375,31z M6.625,2.875c-1.036,0-1.875,0.839-1.875,1.875
+ s0.839,1.875,1.875,1.875S8.5,5.786,8.5,4.75S7.661,2.875,6.625,2.875z M27.25,8.5H4.75v16.875c0,1.036,0.839,1.875,1.875,1.875
+ h18.75c1.036,0,1.875-0.839,1.875-1.875V8.5z M19.75,19.75h-1.875v1.875c0,1.036-0.839,1.875-1.875,1.875s-1.875-0.839-1.875-1.875
+ V19.75H12.25c-1.036,0-1.875-0.839-1.875-1.875S11.214,16,12.25,16h1.875v-1.875c0-1.036,0.839-1.875,1.875-1.875
+ s1.875,0.839,1.875,1.875V16h1.875c1.036,0,1.875,0.839,1.875,1.875S20.786,19.75,19.75,19.75z"/>
+</svg>
diff --git a/resources/pe_dark/scalable/patreon.svg b/resources/pe_dark/scalable/patreon.svg
new file mode 100644
index 00000000..071101eb
--- /dev/null
+++ b/resources/pe_dark/scalable/patreon.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="15.9995" y1="1" x2="15.9995" y2="32.7589">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="0.4868" style="stop-color:#030303"/>
+ <stop offset="0.6368" style="stop-color:#0D0D0D"/>
+ <stop offset="0.774" style="stop-color:#1E1E1E"/>
+ <stop offset="0.9029" style="stop-color:#363636"/>
+ <stop offset="1" style="stop-color:#4D4D4D"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M16,2C7.716,2,1,8.716,1,17v14.986h14.371
+ C15.58,31.994,15.789,32,16,32c8.284,0,15-6.716,15-15S24.284,2,16,2L16,2z"/>
+<path fill="#FFFFFF" d="M16,5.482C9.639,5.482,4.482,10.639,4.482,17v6.865v4.641v3.48h4.609V17.012
+ c0-3.803,3.082-6.887,6.886-6.887s6.886,3.084,6.886,6.887c0,3.804-3.082,6.887-6.886,6.887c-1.391,0-2.685-0.414-3.768-1.122v4.95
+ c0.725,0.446,2.381,0.762,4.323,0.778C22.646,28.226,27.519,23.182,27.519,17C27.519,10.639,22.361,5.482,16,5.482L16,5.482z"/>
+<path fill="#FFFFFF" d="M15.518,28.506c0.159,0.007,0.32,0.013,0.482,0.013c0.17,0,0.339-0.006,0.508-0.013H15.518z"/>
+</svg>
diff --git a/resources/pe_dark/scalable/refresh.svg b/resources/pe_dark/scalable/refresh.svg
new file mode 100644
index 00000000..1bfcc007
--- /dev/null
+++ b/resources/pe_dark/scalable/refresh.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="297.5" y1="-373.373" x2="297.5" y2="-402.4094" gradientTransform="matrix(1 0 0 -1 -281.5 -372)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="0.4868" style="stop-color:#030303"/>
+ <stop offset="0.6368" style="stop-color:#0D0D0D"/>
+ <stop offset="0.774" style="stop-color:#1E1E1E"/>
+ <stop offset="0.9029" style="stop-color:#363636"/>
+ <stop offset="1" style="stop-color:#4D4D4D"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M31.825,17.41C31.473,16.559,30.639,16,29.714,16h-2.286c0-7.573-6.141-13.713-13.714-13.713
+ C6.141,2.287,0,8.427,0,16c0,7.574,6.141,13.714,13.713,13.714c2.482,0,4.803-0.669,6.809-1.821l-3.447-3.406
+ c-1.042,0.412-2.17,0.656-3.361,0.656c-5.049,0-9.143-4.094-9.143-9.143s4.094-9.143,9.143-9.143c5.051,0,9.145,4.094,9.145,9.143
+ H20.57c-0.924,0-1.758,0.559-2.111,1.41c-0.352,0.855-0.158,1.838,0.496,2.492l4.57,4.57c0.447,0.446,1.031,0.67,1.617,0.67
+ c0.584,0,1.17-0.224,1.614-0.67l4.572-4.57C31.984,19.248,32.18,18.266,31.825,17.41z"/>
+</svg>
diff --git a/resources/pe_dark/scalable/settings.svg b/resources/pe_dark/scalable/settings.svg
new file mode 100644
index 00000000..1d571214
--- /dev/null
+++ b/resources/pe_dark/scalable/settings.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<g>
+ <g>
+
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="297.5" y1="-372" x2="297.5" y2="-403.7599" gradientTransform="matrix(1 0 0 -1 -281.5 -372)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="0.4868" style="stop-color:#030303"/>
+ <stop offset="0.6368" style="stop-color:#0D0D0D"/>
+ <stop offset="0.774" style="stop-color:#1E1E1E"/>
+ <stop offset="0.9029" style="stop-color:#363636"/>
+ <stop offset="1" style="stop-color:#4D4D4D"/>
+ </linearGradient>
+ <path fill="url(#SVGID_1_)" d="M29.125,14.125H27.25c-0.066,0-0.121,0.031-0.186,0.038c-0.285-1.73-0.953-3.323-1.935-4.691
+ c0.048-0.04,0.106-0.056,0.149-0.101l1.326-1.325c0.731-0.732,0.731-1.92,0-2.652s-1.92-0.732-2.651,0L22.63,6.719
+ c-0.044,0.044-0.062,0.103-0.101,0.15c-1.369-0.981-2.961-1.649-4.692-1.935c0.007-0.062,0.038-0.118,0.038-0.184V2.875
+ C17.875,1.839,17.036,1,16,1s-1.875,0.839-1.875,1.875V4.75c0,0.066,0.031,0.122,0.038,0.185
+ c-1.732,0.286-3.324,0.953-4.692,1.935C9.431,6.822,9.414,6.764,9.37,6.72L8.045,5.394c-0.731-0.732-1.919-0.732-2.651,0
+ s-0.732,1.92,0,2.652L6.72,9.371c0.044,0.045,0.103,0.061,0.149,0.101c-0.98,1.368-1.648,2.961-1.934,4.691
+ c-0.063-0.007-0.119-0.038-0.185-0.038H2.875C1.839,14.125,1,14.964,1,16s0.839,1.875,1.875,1.875H4.75
+ c0.066,0,0.122-0.029,0.185-0.038c0.286,1.731,0.953,3.324,1.934,4.692c-0.047,0.039-0.105,0.057-0.149,0.101l-1.326,1.325
+ c-0.732,0.732-0.732,1.92,0,2.65c0.732,0.732,1.92,0.732,2.651,0l1.326-1.324c0.044-0.045,0.061-0.104,0.101-0.15
+ c1.368,0.98,2.96,1.648,4.691,1.934c-0.006,0.064-0.038,0.119-0.038,0.187v1.875C14.125,30.161,14.964,31,16,31
+ s1.875-0.839,1.875-1.875V27.25c0-0.066-0.031-0.121-0.038-0.186c1.731-0.285,3.323-0.953,4.692-1.935
+ c0.039,0.048,0.057,0.106,0.101,0.149l1.325,1.326c0.73,0.731,1.92,0.731,2.65,0c0.732-0.731,0.732-1.92,0-2.651l-1.324-1.325
+ c-0.045-0.044-0.104-0.062-0.15-0.1c0.98-1.369,1.648-2.961,1.934-4.693c0.064,0.008,0.119,0.038,0.187,0.038h1.875
+ C30.161,17.875,31,17.036,31,16S30.161,14.125,29.125,14.125z M16,21.625c-3.106,0-5.625-2.518-5.625-5.625
+ c0-3.106,2.519-5.625,5.625-5.625c3.105,0,5.625,2.519,5.625,5.625C21.625,19.107,19.105,21.625,16,21.625z"/>
+ </g>
+</g>
+</svg>
diff --git a/resources/pe_dark/scalable/viewfolder.svg b/resources/pe_dark/scalable/viewfolder.svg
new file mode 100644
index 00000000..f520574a
--- /dev/null
+++ b/resources/pe_dark/scalable/viewfolder.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="297.5" y1="-372" x2="297.5" y2="-403.7599" gradientTransform="matrix(1 0 0 -1 -281.5 -372)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#000000"/>
+ <stop offset="0.4868" style="stop-color:#030303"/>
+ <stop offset="0.6368" style="stop-color:#0D0D0D"/>
+ <stop offset="0.774" style="stop-color:#1E1E1E"/>
+ <stop offset="0.9029" style="stop-color:#363636"/>
+ <stop offset="1" style="stop-color:#4D4D4D"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M25.375,31H6.625C3.519,31,1,28.48,1,25.375V6.625C1,3.519,3.519,1,6.625,1h7.5
+ c2.445,0,4.506,1.57,5.281,3.75h5.969C28.48,4.75,31,7.269,31,10.375v15C31,28.48,28.48,31,25.375,31z M27.25,10.375
+ c0-1.036-0.839-1.875-1.875-1.875H16V6.625c0-1.036-0.839-1.875-1.875-1.875h-7.5c-1.036,0-1.875,0.839-1.875,1.875v18.75
+ c0,1.036,0.839,1.875,1.875,1.875h18.75c1.036,0,1.875-0.839,1.875-1.875V10.375z"/>
+</svg>
diff --git a/resources/pe_light/16x16/status-bad.png b/resources/pe_light/16x16/status-bad.png
new file mode 100644
index 00000000..0187b990
--- /dev/null
+++ b/resources/pe_light/16x16/status-bad.png
Binary files differ
diff --git a/resources/pe_light/16x16/status-good.png b/resources/pe_light/16x16/status-good.png
new file mode 100644
index 00000000..661405db
--- /dev/null
+++ b/resources/pe_light/16x16/status-good.png
Binary files differ
diff --git a/resources/pe_light/22x22/status-bad.png b/resources/pe_light/22x22/status-bad.png
new file mode 100644
index 00000000..7b6e9bcb
--- /dev/null
+++ b/resources/pe_light/22x22/status-bad.png
Binary files differ
diff --git a/resources/pe_light/22x22/status-good.png b/resources/pe_light/22x22/status-good.png
new file mode 100644
index 00000000..3da59106
--- /dev/null
+++ b/resources/pe_light/22x22/status-good.png
Binary files differ
diff --git a/resources/pe_light/24x24/status-bad.png b/resources/pe_light/24x24/status-bad.png
new file mode 100644
index 00000000..bef1e5a3
--- /dev/null
+++ b/resources/pe_light/24x24/status-bad.png
Binary files differ
diff --git a/resources/pe_light/24x24/status-good.png b/resources/pe_light/24x24/status-good.png
new file mode 100644
index 00000000..2b55aeae
--- /dev/null
+++ b/resources/pe_light/24x24/status-good.png
Binary files differ
diff --git a/resources/pe_light/32x32/status-bad.png b/resources/pe_light/32x32/status-bad.png
new file mode 100644
index 00000000..fc700e19
--- /dev/null
+++ b/resources/pe_light/32x32/status-bad.png
Binary files differ
diff --git a/resources/pe_light/32x32/status-good.png b/resources/pe_light/32x32/status-good.png
new file mode 100644
index 00000000..eff50142
--- /dev/null
+++ b/resources/pe_light/32x32/status-good.png
Binary files differ
diff --git a/resources/pe_light/48x48/status-bad.png b/resources/pe_light/48x48/status-bad.png
new file mode 100644
index 00000000..846ac51d
--- /dev/null
+++ b/resources/pe_light/48x48/status-bad.png
Binary files differ
diff --git a/resources/pe_light/48x48/status-good.png b/resources/pe_light/48x48/status-good.png
new file mode 100644
index 00000000..56574e38
--- /dev/null
+++ b/resources/pe_light/48x48/status-good.png
Binary files differ
diff --git a/resources/pe_light/64x64/status-bad.png b/resources/pe_light/64x64/status-bad.png
new file mode 100644
index 00000000..b87772cc
--- /dev/null
+++ b/resources/pe_light/64x64/status-bad.png
Binary files differ
diff --git a/resources/pe_light/64x64/status-good.png b/resources/pe_light/64x64/status-good.png
new file mode 100644
index 00000000..afedf8cb
--- /dev/null
+++ b/resources/pe_light/64x64/status-good.png
Binary files differ
diff --git a/resources/pe_light/index.theme b/resources/pe_light/index.theme
new file mode 100644
index 00000000..a782c2ec
--- /dev/null
+++ b/resources/pe_light/index.theme
@@ -0,0 +1,39 @@
+[Icon Theme]
+Name=pe_light
+Comment=Icons by pexner (light)
+Inherits=multimc
+Directories=scalable/apps,8x8,16x16,22x22,24x24,32x32,48x48,scalable
+
+[scalable/apps]
+Size=48
+Type=scalable
+MinSize=1
+MaxSize=512
+Context=Applications
+
+[8x8]
+Size=8
+
+[16x16]
+Size=16
+
+[22x22]
+Size=22
+
+[24x24]
+Size=24
+
+[32x32]
+Size=32
+
+[48x48]
+Size=48
+
+[64x64]
+Size=64
+
+[scalable]
+Size=48
+Type=Scalable
+MinSize=16
+MaxSize=256
diff --git a/resources/pe_light/pe_light.qrc b/resources/pe_light/pe_light.qrc
new file mode 100644
index 00000000..f1dce837
--- /dev/null
+++ b/resources/pe_light/pe_light.qrc
@@ -0,0 +1,61 @@
+<!DOCTYPE RCC>
+<RCC version="1.0">
+ <qresource prefix="/icons/pe_light">
+ <file>index.theme</file>
+
+ <!-- OK console icon. Our own -->
+ <file>scalable/console.svg</file>
+
+ <!-- ERROR console icon. Our own -->
+ <file>scalable/console_error.svg</file>
+
+ <!-- About dialog. Our own. -->
+ <file>scalable/about.svg</file>
+
+ <!-- Report bug. Our own. -->
+ <file>scalable/bug.svg</file>
+
+ <!-- Patron logo, black and white. (C) 2014 Patreon, Inc., http://www.patreon.com/toolbox?ftyp=media -->
+ <file>scalable/patreon.svg</file>
+
+ <!-- Show mods folder. Our own. -->
+ <file>scalable/centralmods.svg</file>
+
+ <!-- Update. Our own. -->
+ <file>scalable/checkupdate.svg</file>
+
+ <!-- copy instance. Our own. -->
+ <file>scalable/copy.svg</file>
+
+ <!-- New instance. Our own. -->
+ <file>scalable/new.svg</file>
+
+ <!-- Open news. Our own. -->
+ <file>scalable/news.svg</file>
+
+ <!-- Bad status. Our own. -->
+ <file>16x16/status-bad.png</file>
+ <file>22x22/status-bad.png</file>
+ <file>24x24/status-bad.png</file>
+ <file>32x32/status-bad.png</file>
+ <file>48x48/status-bad.png</file>
+ <file>64x64/status-bad.png</file>
+
+ <!-- Good status. Our own. -->
+ <file>16x16/status-good.png</file>
+ <file>22x22/status-good.png</file>
+ <file>24x24/status-good.png</file>
+ <file>32x32/status-good.png</file>
+ <file>48x48/status-good.png</file>
+ <file>64x64/status-good.png</file>
+
+ <!-- Refresh, Our own. -->
+ <file>scalable/refresh.svg</file>
+
+ <!-- Settings, Our own. -->
+ <file>scalable/settings.svg</file>
+
+ <!-- View folder. Our own. -->
+ <file>scalable/viewfolder.svg</file>
+ </qresource>
+</RCC>
diff --git a/resources/pe_light/scalable/about.svg b/resources/pe_light/scalable/about.svg
new file mode 100644
index 00000000..9ee74686
--- /dev/null
+++ b/resources/pe_light/scalable/about.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="579" y1="807.9336" x2="579" y2="841.8091" gradientTransform="matrix(1 0 0 1 -563 -809)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M24.753,14.774C24.629,15.172,24.417,15.585,24,16c-2,2-2,4-2,4v2c0,1.105-0.896,2-2,2h-8
+ c-1.104,0-2-0.895-2-2v-2c0-1.453-1.049-2.895-1.627-3.581c-0.017-0.021-0.032-0.042-0.05-0.062C8.131,16.132,8,16,8,16v-0.071
+ C6.756,14.272,6,12.233,6,10C6,4.478,10.478,0,16,0c5.521,0,10,4.478,10,10C26,11.739,25.54,13.357,24.753,14.774z M16,4
+ c-3.313,0-6,2.687-6,6c0,2.609,1.675,4.806,4,5.633V18c0,1.105,0.896,2,2,2c1.104,0,2-0.895,2-2v-2.367c2.325-0.827,4-3.024,4-5.633
+ C22,6.687,19.312,4,16,4z M13,26h6c0.552,0,1,0.448,1,1s-0.448,1-1,1h-6c-0.553,0-1-0.448-1-1S12.447,26,13,26z M15,30h2
+ c0.552,0,1,0.449,1,1c0,0.552-0.448,1-1,1h-2c-0.553,0-1-0.448-1-1C14,30.449,14.447,30,15,30z"/>
+</svg>
diff --git a/resources/pe_light/scalable/bug.svg b/resources/pe_light/scalable/bug.svg
new file mode 100644
index 00000000..44e6d72a
--- /dev/null
+++ b/resources/pe_light/scalable/bug.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<g>
+ <g>
+ <g>
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="15.9995" y1="0" x2="15.9995" y2="31.7588">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+ </linearGradient>
+ <path fill="url(#SVGID_1_)" d="M25.375,10.375h-1.702c-0.106,0.589-0.173,1.214-0.173,1.875s0.066,1.286,0.173,1.875h1.702
+ c1.036,0,1.875-0.839,1.875-1.875S26.411,10.375,25.375,10.375z M25.375,1C20.043,6.449,12.25,6.625,12.25,6.625H6.625
+ C3.519,6.625,1,9.144,1,12.25s2.519,5.625,5.625,5.625c0,0.26,0.052,0.506,0.147,0.73c0.177,0.418,0.512,0.746,0.919,0.941
+ C7.72,19.562,7.74,19.59,7.77,19.604c0.224,0.095,0.471,0.146,0.729,0.146l-1.875,9.375C6.625,30.161,7.464,31,8.5,31h3.75
+ c1.036,0,1.875-0.839,1.875-1.875s-0.839-1.875-1.875-1.875l1.5-7.5h0.375c0.847,0,1.534-0.57,1.767-1.34
+ c2.73,0.637,6.493,2.033,9.483,5.09C28.48,23.5,31,18.463,31,12.25S28.48,1,25.375,1z M6.625,14.125H4.75v-3.75h1.875V14.125z
+ M25.375,19.75c-2.07,0-3.75-3.357-3.75-7.5c0-4.141,1.68-7.5,3.75-7.5s3.75,3.359,3.75,7.5
+ C29.125,16.393,27.445,19.75,25.375,19.75z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+
+ <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="579" y1="809" x2="579" y2="840.7583" gradientTransform="matrix(1 0 0 1 -563 -809)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+ </linearGradient>
+ <path fill="url(#SVGID_2_)" d="M25.375,10.375h-1.702c-0.106,0.589-0.173,1.214-0.173,1.875s0.066,1.286,0.173,1.875h1.702
+ c1.036,0,1.875-0.839,1.875-1.875S26.411,10.375,25.375,10.375z M25.375,1C20.043,6.449,12.25,6.625,12.25,6.625H6.625
+ C3.519,6.625,1,9.144,1,12.25s2.519,5.625,5.625,5.625c0,0.26,0.052,0.506,0.147,0.73c0.177,0.418,0.512,0.746,0.919,0.941
+ C7.72,19.562,7.74,19.59,7.77,19.604c0.224,0.095,0.471,0.146,0.729,0.146l-1.875,9.375C6.625,30.161,7.464,31,8.5,31h3.75
+ c1.036,0,1.875-0.839,1.875-1.875s-0.839-1.875-1.875-1.875l1.5-7.5h0.375c0.847,0,1.534-0.57,1.767-1.34
+ c2.73,0.637,6.493,2.033,9.483,5.09C28.48,23.5,31,18.463,31,12.25S28.48,1,25.375,1z M6.625,14.125H4.75v-3.75h1.875V14.125z
+ M25.375,19.75c-2.07,0-3.75-3.357-3.75-7.5c0-4.141,1.68-7.5,3.75-7.5s3.75,3.359,3.75,7.5
+ C29.125,16.393,27.445,19.75,25.375,19.75z"/>
+ </g>
+ </g>
+</g>
+</svg>
diff --git a/resources/pe_light/scalable/centralmods.svg b/resources/pe_light/scalable/centralmods.svg
new file mode 100644
index 00000000..fea7660a
--- /dev/null
+++ b/resources/pe_light/scalable/centralmods.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="15.9995" y1="0" x2="15.9995" y2="31.7588">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M25.375,31H6.625C3.519,31,1,28.48,1,25.375V6.625C1,3.519,3.519,1,6.625,1h7.5
+ c2.445,0,4.506,1.57,5.281,3.75h5.969C28.48,4.75,31,7.269,31,10.375v15C31,28.48,28.48,31,25.375,31z M27.25,10.375
+ c0-1.036-0.839-1.875-1.875-1.875H16V6.625c0-1.036-0.839-1.875-1.875-1.875h-7.5c-1.036,0-1.875,0.839-1.875,1.875v18.75
+ c0,1.036,0.839,1.875,1.875,1.875h18.75c1.036,0,1.875-0.839,1.875-1.875V10.375z M16,10.604l2.078,4.212l4.646,0.675l-3.363,3.278
+ l0.795,4.627L16,21.211l-4.156,2.186l0.794-4.627L9.275,15.49l4.646-0.675L16,10.604z"/>
+</svg>
diff --git a/resources/pe_light/scalable/checkupdate.svg b/resources/pe_light/scalable/checkupdate.svg
new file mode 100644
index 00000000..a9199659
--- /dev/null
+++ b/resources/pe_light/scalable/checkupdate.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="579" y1="809" x2="579" y2="840.7583" gradientTransform="matrix(1 0 0 1 -563 -809)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M25.375,23.5h-0.337c-0.033-0.092-0.056-0.188-0.093-0.275c-0.621-1.508-1.445-2.634-3.32-3.154
+ v-2.194c0-3.102-2.523-5.625-5.625-5.625c-3.101,0-5.625,2.523-5.625,5.625v2.19c-1.875,0.521-2.698,1.646-3.321,3.147
+ c-0.018,0.043-0.028,0.093-0.043,0.136C3.582,22.659,1,19.631,1,16v-1.875c0-4.142,3.358-7.5,7.5-7.5
+ c0.714,0,1.388,0.131,2.042,0.317C11.26,3.548,14.268,1,17.875,1c4.143,0,7.5,3.358,7.5,7.5c0,0.662-0.113,1.292-0.271,1.902
+ c0.092-0.005,0.182-0.027,0.271-0.027C28.484,10.375,31,12.895,31,16v1.875C31,20.982,28.484,23.5,25.375,23.5z M12.25,23.5h1.88
+ v-5.625c0-1.037,0.837-1.875,1.875-1.875c1.039,0,1.877,0.838,1.877,1.875V23.5h1.871c0.756,0,1.442,0.458,1.731,1.157
+ c0.072,0.176,0.115,0.354,0.136,0.536c0.05,0.55-0.14,1.104-0.542,1.508l-3.753,3.75C16.961,30.814,16.479,31,16,31
+ s-0.958-0.186-1.324-0.549l-3.75-3.75c-0.399-0.403-0.591-0.958-0.539-1.508c0.016-0.185,0.057-0.36,0.132-0.536
+ C10.81,23.958,11.493,23.5,12.25,23.5z"/>
+<rect fill="none" width="32" height="32"/>
+</svg>
diff --git a/resources/pe_light/scalable/console.svg b/resources/pe_light/scalable/console.svg
new file mode 100644
index 00000000..ec14ab68
--- /dev/null
+++ b/resources/pe_light/scalable/console.svg
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ 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="32px"
+ height="32px"
+ id="svg2985"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="console.svg"
+ inkscape:export-filename="/home/peterix/projects/MultiMC4/src/resources/console.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs2987">
+ <linearGradient
+ id="linearGradient6244">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop6246" />
+ <stop
+ id="stop6254"
+ offset="0.4642857"
+ style="stop-color:#000000;stop-opacity:1" />
+ <stop
+ id="stop6252"
+ offset="0.53571427"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="1"
+ id="stop6248" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6212">
+ <stop
+ style="stop-color:#959595;stop-opacity:1"
+ offset="0"
+ id="stop6214" />
+ <stop
+ id="stop6224"
+ offset="0.14849657"
+ style="stop-color:#b0b0b0;stop-opacity:1;" />
+ <stop
+ id="stop6220"
+ offset="0.41380492"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#cacaca;stop-opacity:1;"
+ offset="0.65110856"
+ id="stop6222" />
+ <stop
+ id="stop6228"
+ offset="0.87847149"
+ style="stop-color:#b0b0b0;stop-opacity:1;" />
+ <stop
+ style="stop-color:#969696;stop-opacity:1;"
+ offset="1"
+ id="stop6216" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6194"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#8e8e8e;stop-opacity:1;"
+ offset="0"
+ id="stop6196" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3050">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3052" />
+ <stop
+ id="stop3840"
+ offset="0.64285713"
+ style="stop-color:#164315;stop-opacity:1" />
+ <stop
+ id="stop3838"
+ offset="0.85714287"
+ style="stop-color:#24a91f;stop-opacity:1" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1"
+ offset="1"
+ id="stop3054" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3050"
+ id="linearGradient3056"
+ x1="15"
+ y1="16"
+ x2="15"
+ y2="2"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6212"
+ id="linearGradient6218"
+ x1="19.373737"
+ y1="18.689655"
+ x2="30.317204"
+ y2="31.204504"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="repeat" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6244"
+ id="linearGradient6250"
+ x1="2"
+ y1="2"
+ x2="30"
+ y2="30"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6244"
+ id="linearGradient6258"
+ gradientUnits="userSpaceOnUse"
+ x1="2"
+ y1="2"
+ x2="30"
+ y2="30"
+ gradientTransform="matrix(1.0666667,0,0,1.0666667,-33.066667,-33.066667)" />
+ <filter
+ inkscape:collect="always"
+ id="filter6272">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.32596875"
+ id="feGaussianBlur6274" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.5"
+ inkscape:cx="-151.66767"
+ inkscape:cy="-123.35228"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1607"
+ inkscape:window-height="1030"
+ inkscape:window-x="1676"
+ inkscape:window-y="-3"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2995"
+ empspacing="2"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata2990">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ id="rect2993"
+ width="32"
+ height="32"
+ x="-1.9984014e-15"
+ y="4.4408921e-16"
+ ry="2.6666667" />
+ <rect
+ style="fill:url(#linearGradient3056);fill-opacity:1;stroke:none"
+ id="rect2993-1"
+ width="28"
+ height="28"
+ x="2"
+ y="2"
+ ry="2.3333333" />
+ <g
+ style="font-size:85.93203735px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;font-family:Sans"
+ id="text3001">
+ <path
+ style="font-size:45.2372551px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#008000;fill-opacity:1;stroke:none;filter:url(#filter6272);font-family:Sans"
+ d="m 7.4824597,10.427541 2.2812496,5.78125 0.2812497,-0.71875 -1.9999993,-5.0625 -0.5625,0 z m 8.4687493,0 0,11.34375 -1.875,0 0,0.65625 2.6875,0 0,-12 -0.8125,0 z m 11.625,0.3125 0,2.21875 c -0.5312,-0.3906 -1.046212,-0.693063 -1.5625,-0.875 -0.516305,-0.181918 -1.054102,-0.24999 -1.625,-0.25 -1.022671,10e-6 -1.820457,0.324952 -2.40625,1.03125 -0.585803,0.706316 -0.875004,1.696118 -0.875,2.9375 -4e-6,1.236043 0.289197,2.199946 0.875,2.90625 0.23333,0.281334 0.509858,0.486976 0.8125,0.65625 -0.585803,-0.706304 -0.875004,-1.670207 -0.875,-2.90625 -4e-6,-1.241382 0.289197,-2.231184 0.875,-2.9375 0.585793,-0.706298 1.383579,-1.06249 2.40625,-1.0625 0.570898,10e-6 1.139945,0.09933 1.65625,0.28125 0.516288,0.181937 1.0313,0.45315 1.5625,0.84375 l 0,-2.46875 c -0.271888,-0.152046 -0.55994,-0.261514 -0.84375,-0.375 z m -21.2812493,2.28125 0,8.75 -1.875,0 0,0.65625 2.71875,0 0,-7.28125 -0.84375,-2.125 z m 6.9374993,0 -2.5625,6.46875 -1.2187493,0 0.2499996,0.625 1.8124997,0 1.71875,-4.34375 0,-2.75 z m 15.1875,6.25 c -0.287318,0.211276 -0.560795,0.412277 -0.84375,0.5625 l 0,1.28125 c -0.526236,0.294295 -1.054104,0.537677 -1.625,0.6875 -0.570913,0.149823 -1.191958,0.21875 -1.8125,0.21875 -1.332472,0 -2.440972,-0.298702 -3.375,-0.875 1.063521,0.998245 2.465058,1.5 4.21875,1.5 0.620542,0 1.210337,-0.06893 1.78125,-0.21875 0.570896,-0.149823 1.130014,-0.361955 1.65625,-0.65625 l 0,-2.5 z"
+ id="text3022"
+ inkscape:connector-curvature="0" />
+ <text
+ xml:space="preserve"
+ style="font-size:45.2372551px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;font-family:Sans"
+ x="2.7196128"
+ y="21.15748"
+ id="text3014"
+ sodipodi:linespacing="125%"
+ transform="scale(0.9632149,1.0381899)"><tspan
+ sodipodi:role="line"
+ id="tspan3016"
+ x="2.7196128"
+ y="21.15748"
+ style="font-size:15.83304024000000076px;font-weight:bold;-inkscape-font-specification:Bitstream Vera Sans Bold;font-family:Bitstream Vera Sans;font-style:normal;font-stretch:normal;font-variant:normal">MC</tspan></text>
+ </g>
+ </g>
+</svg>
diff --git a/resources/pe_light/scalable/console_error.svg b/resources/pe_light/scalable/console_error.svg
new file mode 100644
index 00000000..a71c6b35
--- /dev/null
+++ b/resources/pe_light/scalable/console_error.svg
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ 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="32px"
+ height="32px"
+ id="svg2985"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="console_error.svg"
+ inkscape:export-filename="/home/peterix/projects/MultiMC4/src/resources/console_error.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs2987">
+ <linearGradient
+ id="linearGradient6244">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop6246" />
+ <stop
+ id="stop6254"
+ offset="0.4642857"
+ style="stop-color:#000000;stop-opacity:1" />
+ <stop
+ id="stop6252"
+ offset="0.53571427"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="1"
+ id="stop6248" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6212">
+ <stop
+ style="stop-color:#959595;stop-opacity:1"
+ offset="0"
+ id="stop6214" />
+ <stop
+ id="stop6224"
+ offset="0.14849657"
+ style="stop-color:#b0b0b0;stop-opacity:1;" />
+ <stop
+ id="stop6220"
+ offset="0.41380492"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ style="stop-color:#cacaca;stop-opacity:1;"
+ offset="0.65110856"
+ id="stop6222" />
+ <stop
+ id="stop6228"
+ offset="0.87847149"
+ style="stop-color:#b0b0b0;stop-opacity:1;" />
+ <stop
+ style="stop-color:#969696;stop-opacity:1;"
+ offset="1"
+ id="stop6216" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6194"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#8e8e8e;stop-opacity:1;"
+ offset="0"
+ id="stop6196" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3050">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3052" />
+ <stop
+ id="stop3840"
+ offset="0.64285713"
+ style="stop-color:#431515;stop-opacity:1;" />
+ <stop
+ id="stop3838"
+ offset="0.85714287"
+ style="stop-color:#a91f1f;stop-opacity:1;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1"
+ offset="1"
+ id="stop3054" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3050"
+ id="linearGradient3056"
+ x1="15"
+ y1="16"
+ x2="15"
+ y2="2"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="reflect" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6244"
+ id="linearGradient6250"
+ x1="2"
+ y1="2"
+ x2="30"
+ y2="30"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6244"
+ id="linearGradient6258"
+ gradientUnits="userSpaceOnUse"
+ x1="2"
+ y1="2"
+ x2="30"
+ y2="30"
+ gradientTransform="matrix(1.0666667,0,0,1.0666667,-33.066667,-33.066667)" />
+ <filter
+ inkscape:collect="always"
+ id="filter6373">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.64"
+ id="feGaussianBlur6375" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.4142136"
+ inkscape:cx="149.24645"
+ inkscape:cy="89.508966"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1607"
+ inkscape:window-height="1030"
+ inkscape:window-x="1676"
+ inkscape:window-y="-3"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2995"
+ empspacing="2"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata2990">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ id="rect2993"
+ width="32"
+ height="32"
+ x="-1.9984014e-15"
+ y="4.4408921e-16"
+ ry="2.6666667" />
+ <rect
+ style="fill:url(#linearGradient3056);fill-opacity:1;stroke:none"
+ id="rect2993-1"
+ width="28"
+ height="28"
+ x="2"
+ y="2"
+ ry="2.3333333" />
+ <g
+ style="font-size:85.93203735px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;font-family:Sans"
+ id="text3001" />
+ <g
+ id="g6331"
+ transform="translate(6,-7)">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter6373)"
+ id="rect6363"
+ width="16"
+ height="16"
+ x="8"
+ y="8"
+ transform="translate(-6,7)" />
+ <path
+ id="path6377"
+ d="m 4,17 0,4 4,0 0,-4 z"
+ style="fill:#800000;fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ style="fill:#800000;fill-opacity:1;stroke:none"
+ d="m 8,21 0,2 -2,0 0,6 2,0 0,-2 4,0 0,2 2,0 0,-6 -2,0 0,-2 z"
+ id="path6379"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccc" />
+ <path
+ sodipodi:nodetypes="ccccc"
+ inkscape:connector-curvature="0"
+ style="fill:#800000;fill-opacity:1;stroke:none"
+ d="m 12,17 0,4 4,0 0,-4 z"
+ id="path6381" />
+ <path
+ sodipodi:nodetypes="ccccc"
+ inkscape:connector-curvature="0"
+ style="fill:#ff0000;fill-opacity:1;stroke:none"
+ d="m 6,19 0,2 2,0 0,-2 z"
+ id="path6383" />
+ <path
+ id="path6385"
+ d="m 12,19 0,2 2,0 0,-2 z"
+ style="fill:#ff0000;fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ sodipodi:nodetypes="ccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path6387"
+ d="m 8,23 0,2 -2,0 0,4 2,0 0,-2 4,0 0,2 2,0 0,-4 -2,0 0,-2 z"
+ style="fill:#ff0000;fill-opacity:1;stroke:none" />
+ </g>
+ </g>
+</svg>
diff --git a/resources/pe_light/scalable/copy.svg b/resources/pe_light/scalable/copy.svg
new file mode 100644
index 00000000..d8c0fbdc
--- /dev/null
+++ b/resources/pe_light/scalable/copy.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="579" y1="809" x2="579" y2="840.7578" gradientTransform="matrix(1 0 0 1 -563 -809)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M27.25,25.031V10.375c0-3.106-2.52-5.625-5.625-5.625H6.969C7.745,2.57,9.804,1,12.25,1h13.125
+ C28.48,1,31,3.519,31,6.625V19.75C31,22.195,29.432,24.256,27.25,25.031z M25.375,12.25v13.125c0,3.107-2.52,5.625-5.625,5.625
+ H6.625C3.519,31,1,28.482,1,25.375V12.25c0-3.106,2.519-5.625,5.625-5.625H19.75C22.855,6.625,25.375,9.144,25.375,12.25z
+ M4.75,25.375c0,1.036,0.839,1.875,1.875,1.875H19.75c1.036,0,1.875-0.839,1.875-1.875v-11.25H4.75V25.375z M6.625,8.5
+ c-1.036,0-1.875,0.839-1.875,1.875s0.839,1.875,1.875,1.875S8.5,11.411,8.5,10.375S7.661,8.5,6.625,8.5z"/>
+<rect fill="none" width="32" height="32"/>
+</svg>
diff --git a/resources/pe_light/scalable/new.svg b/resources/pe_light/scalable/new.svg
new file mode 100644
index 00000000..2cbd8b92
--- /dev/null
+++ b/resources/pe_light/scalable/new.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="579" y1="809" x2="579" y2="840.7583" gradientTransform="matrix(1 0 0 1 -563 -809)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M25.375,31H6.625C3.519,31,1,28.482,1,25.375V6.625C1,3.519,3.519,1,6.625,1h18.75
+ C28.48,1,31,3.519,31,6.625v18.75C31,28.482,28.48,31,25.375,31z M6.625,2.875c-1.036,0-1.875,0.839-1.875,1.875
+ s0.839,1.875,1.875,1.875S8.5,5.786,8.5,4.75S7.661,2.875,6.625,2.875z M27.25,8.5H4.75v16.875c0,1.036,0.839,1.875,1.875,1.875
+ h18.75c1.036,0,1.875-0.839,1.875-1.875V8.5z M19.75,19.75h-1.875v1.875c0,1.036-0.839,1.875-1.875,1.875s-1.875-0.839-1.875-1.875
+ V19.75H12.25c-1.036,0-1.875-0.839-1.875-1.875S11.214,16,12.25,16h1.875v-1.875c0-1.036,0.839-1.875,1.875-1.875
+ s1.875,0.839,1.875,1.875V16h1.875c1.036,0,1.875,0.839,1.875,1.875S20.786,19.75,19.75,19.75z"/>
+</svg>
diff --git a/resources/pe_light/scalable/news.svg b/resources/pe_light/scalable/news.svg
new file mode 100644
index 00000000..67a370df
--- /dev/null
+++ b/resources/pe_light/scalable/news.svg
@@ -0,0 +1,296 @@
+<?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="128"
+ height="128"
+ id="svg2985"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="news.svg">
+ <defs
+ id="defs2987">
+ <linearGradient
+ id="linearGradient4095">
+ <stop
+ style="stop-color:#fff7d8;stop-opacity:1;"
+ offset="0"
+ id="stop4097" />
+ <stop
+ style="stop-color:#ffeca0;stop-opacity:1;"
+ offset="1"
+ id="stop4099" />
+ </linearGradient>
+ <filter
+ inkscape:collect="always"
+ id="filter3898"
+ x="-0.11333333"
+ width="1.2266667"
+ y="-0.10074074"
+ height="1.2014815">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="1.5111111"
+ id="feGaussianBlur3900" />
+ </filter>
+ <filter
+ inkscape:collect="always"
+ id="filter4091">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="3.6378601"
+ id="feGaussianBlur4093" />
+ </filter>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4095"
+ id="linearGradient4101"
+ x1="88.388351"
+ y1="94.942757"
+ x2="99.525276"
+ y2="103.95837"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.25"
+ inkscape:cx="-700.46253"
+ inkscape:cy="-25.005652"
+ inkscape:current-layer="text3832"
+ showgrid="false"
+ inkscape:document-units="px"
+ inkscape:grid-bbox="true"
+ inkscape:window-width="1607"
+ inkscape:window-height="1030"
+ inkscape:window-x="1676"
+ inkscape:window-y="-3"
+ inkscape:window-maximized="1"
+ showguides="false"
+ inkscape:guide-bbox="true"
+ inkscape:snap-global="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2993"
+ empspacing="8"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ <inkscape:grid
+ type="xygrid"
+ id="grid2995"
+ empspacing="4"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true"
+ color="#00ff0b"
+ opacity="0.08235294"
+ empcolor="#00ff22"
+ empopacity="0.23137255" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="41,100"
+ id="guide4165" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="68,106"
+ id="guide4167" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="73,109"
+ id="guide4169" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="80,106"
+ id="guide4171" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="83,102"
+ id="guide4173" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="104,104"
+ id="guide4175" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata2990">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ transform="translate(0,64)">
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="path3778"
+ d="m 16,8 96,0 0,76 c -9.14072,13.804136 -19.955033,25.58254 -32,36 l -64,0 z"
+ style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;filter:url(#filter4091)"
+ transform="translate(0,-64)" />
+ <path
+ transform="translate(0,-64)"
+ style="color:#000000;fill:#fff6d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 16,8 96,0 0,76 c -9.14072,13.804136 -19.955033,25.58254 -32,36 l -64,0 z"
+ id="rect2997"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccc" />
+ <path
+ style="color:#000000;fill:none;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 68,-28 36,0 0,56 -16,20 -20,0 z"
+ id="rect4044"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccc" />
+ <g
+ id="g4129">
+ <path
+ id="rect4103"
+ d="m 24,36 36,0 0,36 -36,0 z"
+ style="color:#000000;fill:#fff6d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ transform="translate(0,-64)" />
+ <path
+ sodipodi:nodetypes="cccccc"
+ inkscape:connector-curvature="0"
+ id="rect4107"
+ d="m 68,36 36,0 0,57 -16,19 -20,0 z"
+ style="color:#000000;fill:#fff6d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ transform="translate(0,-64)" />
+ <flowRoot
+ id="flowRoot4115"
+ style="font-size:8px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ xml:space="preserve"><flowRegion
+ id="flowRegion4117"><use
+ height="128"
+ width="128"
+ id="use4119"
+ xlink:href="#rect4103"
+ y="0"
+ x="0" /><use
+ height="128"
+ width="128"
+ id="use4121"
+ xlink:href="#rect4107"
+ y="0"
+ x="0" /></flowRegion><flowPara
+ style="font-size:2px;font-weight:bold;text-align:justify;text-anchor:start;-inkscape-font-specification:Sans Bold"
+ id="flowPara4123">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce convallis mauris ullamcorper mauris viverra molestie. Donec ultricies faucibus laoreet. Donec convallis congue neque consequat vehicula. Morbi condimentum tempor nulla et rhoncus. Etiam auctor, augue eu pharetra congue, elit justo lacinia risus, non lacinia est justo sed erat. Ut risus urna, viverra id interdum in, molestie non sem. Morbi leo orci, gravida auctor tempor vel, varius et enim. Nulla sem enim, ultricies vel laoreet ac, semper vel mauris. Ut adipiscing sapien sed leo pretium id vulputate erat gravida. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras tempor leo sit amet velit molestie commodo eget tincidunt leo. Cras dictum metus non ante pulvinar pellentesque. Morbi id elit ullamcorper mi vulputate lobortis. Cras ac vehicula felis. Phasellus dictum, tellus at molestie pellentesque, purus purus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce convallis mauris ullamcorper mauris viverra molestie. Donec ultricies faucibus laoreet. Donec convallis congue neque consequat vehicula. Morbi condimentum tempor nulla et rhoncus. Etiam auctor, augue eu pharetra congue, elit justo lacinia risus, non lacinia est justo sed erat. Ut risus urna, </flowPara></flowRoot> </g>
+ <path
+ style="opacity:0.41176471;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter3898);enable-background:accumulate"
+ d="M 85.522922,28.087287 C 96.299051,25.849792 101.98214,24.118305 110.44998,20.924851 101.30926,34.728987 91.732381,44.562847 79.687414,54.980307 83.49938,42.627824 86.087749,33.764885 85.522922,28.087287 z"
+ id="path3848"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ <g
+ transform="scale(1.3146517,0.76065775)"
+ style="font-size:26.48733711px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#008000;fill-opacity:1;stroke:none;font-family:Sans"
+ id="text3832">
+ <path
+ d="m 30.25786,-47.327461 -2.845319,0 -6.874782,-17.090472 0,17.090472 -2.281973,0 0,-21.034427 3.042631,0 6.84592,17.090472 0,-17.090472 2.281973,0 0,21.034427"
+ style="font-variant:normal;font-stretch:normal;fill:#008000;font-family:Oxygen Mono;-inkscape-font-specification:Oxygen Mono"
+ id="path4157"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccc" />
+ <path
+ d="m 37.27223,-56.530023 0,6.573259 7.606577,0 0.818953,2.629303 -10.707504,0 0,-21.034427 10.649209,0 -0.760658,2.629303 -7.606577,0 0.0038,6.512419 7.602816,0 -3.9e-5,2.690143"
+ style="font-variant:normal;font-stretch:normal;fill:#008000;font-family:Oxygen Mono;-inkscape-font-specification:Oxygen Mono"
+ id="path4159"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccc" />
+ <path
+ d="m 60.091962,-50.290057 0.760658,-18.071831 2.281973,0 -1.521316,21.034427 -3.04263,0 -2.281974,-7.88791 -2.277041,7.88791 -3.047563,0 -1.61189,-21.034427 2.368129,0 0.765077,18.405124 3.042631,-9.202562 1.521315,0 3.042631,8.869269"
+ style="font-variant:normal;font-stretch:normal;fill:#008000;font-family:Oxygen Mono;-inkscape-font-specification:Oxygen Mono"
+ id="path4161"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccc" />
+ <path
+ d="m 67.201805,-51.228789 c 1.498704,1.191317 3.283103,1.786974 5.3532,1.786972 1.124022,2e-6 1.990463,-0.331929 2.599324,-0.995793 0.608839,-0.672953 0.913263,-1.586899 0.913275,-2.741842 -1.2e-5,-0.336472 -0.04684,-0.650214 -0.140504,-0.941229 -0.08431,-0.291 -0.192033,-0.545633 -0.323158,-0.763896 -0.13115,-0.218249 -0.318487,-0.431957 -0.562017,-0.641127 -0.234183,-0.209154 -0.449623,-0.377393 -0.646317,-0.504718 -0.196716,-0.1364 -0.45899,-0.281905 -0.786822,-0.436511 -0.318486,-0.15459 -0.585443,-0.272812 -0.800874,-0.354666 -0.206079,-0.08183 -0.487089,-0.190965 -0.843023,-0.327385 -0.346583,-0.1364 -0.604173,-0.240981 -0.772772,-0.313742 -0.580755,-0.236435 -1.067835,-0.445597 -1.461242,-0.627488 -0.384048,-0.190962 -0.810244,-0.450141 -1.278584,-0.777536 -0.468351,-0.327373 -0.843028,-0.668397 -1.124032,-1.023075 -0.28101,-0.354653 -0.519866,-0.795713 -0.716571,-1.323177 -0.187339,-0.536533 -0.281009,-1.127643 -0.281007,-1.773331 -2e-6,-1.700561 0.594798,-3.019191 1.784399,-3.955891 1.189595,-0.945755 2.721087,-1.418643 4.594479,-1.418664 2.360456,2.1e-5 4.201056,0.504737 5.521806,1.514151 l -0.983527,1.964305 c -1.217713,-0.918476 -2.683637,-1.377724 -4.397774,-1.377741 -1.105305,1.7e-5 -1.985796,0.295573 -2.641474,0.886664 -0.646325,0.582034 -0.969483,1.386852 -0.969477,2.414458 -6e-6,0.400151 0.08429,0.773005 0.252905,1.118562 0.168601,0.336492 0.369989,0.618405 0.604168,0.845743 0.234168,0.218269 0.543277,0.436525 0.927327,0.654768 0.393404,0.218268 0.735296,0.386507 1.025679,0.504717 0.290365,0.109139 0.66036,0.250096 1.10998,0.422871 0.449605,0.163703 0.777446,0.291019 0.983528,0.381948 0.562005,0.245549 1.044402,0.472898 1.44719,0.682049 0.402767,0.20008 0.833645,0.472899 1.292638,0.81846 0.468332,0.336489 0.847692,0.691155 1.13808,1.064 0.290362,0.372862 0.529218,0.832109 0.716571,1.377741 0.196691,0.536553 0.295042,1.123115 0.295058,1.75969 -1.6e-5,1.955213 -0.585447,3.446628 -1.756299,4.474248 -1.161511,1.018529 -2.753887,1.527792 -4.777134,1.527793 -2.519709,-10e-7 -4.6179,-0.541094 -6.294577,-1.623281 l 0.997578,-2.278047"
+ style="font-variant:normal;font-stretch:normal;fill:#008000;font-family:Oxygen Mono;-inkscape-font-specification:Oxygen Mono"
+ id="path4163"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ sodipodi:nodetypes="cccc"
+ inkscape:connector-curvature="0"
+ id="path3838"
+ d="M 88.181818,93.090909 C 100.37216,91.737298 104.94638,87.547007 112,84 c -9.14072,13.804136 -19.955033,25.58254 -32,36 4.69585,-10.93827 8.746645,-21.231493 8.181818,-26.909091 z"
+ style="color:#000000;fill:url(#linearGradient4101);fill-opacity:1.0;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;opacity:1"
+ transform="translate(0,-64)" />
+ <rect
+ style="color:#000000;fill:#accc74;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect4138"
+ width="36"
+ height="36"
+ x="24"
+ y="76"
+ transform="translate(0,-64)" />
+ <g
+ id="g4000"
+ transform="matrix(0.60097005,0,0,0.60097012,3.5088114,25.221343)"
+ style="fill:#333333">
+ <g
+ id="g3937"
+ style="fill:#333333">
+ <rect
+ style="color:#000000;fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect3906"
+ width="16"
+ height="16"
+ x="40"
+ y="-16" />
+ <rect
+ style="color:#000000;fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect3908"
+ width="16"
+ height="16"
+ x="72"
+ y="-16" />
+ <path
+ style="color:#000000;fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 56,64 0,8 -8,0 0,24 8,0 0,-8 16,0 0,8 8,0 0,-24 -8,0 0,-8 -16,0 z"
+ transform="translate(0,-64)"
+ id="rect3910"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 24,32 80,0"
+ id="path3998"
+ inkscape:connector-curvature="0"
+ transform="translate(0,-64)" />
+ <rect
+ style="color:#000000;fill:none;stroke:#666666;stroke-width:0.9014551;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect4007"
+ width="36.058205"
+ height="36.058208"
+ x="23.941793"
+ y="12" />
+ <rect
+ style="color:#000000;fill:none;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ id="rect4034"
+ width="36"
+ height="32"
+ x="24"
+ y="-28" />
+ </g>
+</svg>
diff --git a/resources/pe_light/scalable/patreon.svg b/resources/pe_light/scalable/patreon.svg
new file mode 100644
index 00000000..d48289c8
--- /dev/null
+++ b/resources/pe_light/scalable/patreon.svg
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="Calque_1"
+ x="0px"
+ y="0px"
+ width="32px"
+ height="32px"
+ viewBox="0 0 32 32"
+ enable-background="new 0 0 32 32"
+ xml:space="preserve"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="patreon.svg"><metadata
+ id="metadata28"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs26" /><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1680"
+ inkscape:window-height="1026"
+ id="namedview24"
+ showgrid="false"
+ inkscape:zoom="14.75"
+ inkscape:cx="26.831184"
+ inkscape:cy="18.081755"
+ inkscape:window-x="-3"
+ inkscape:window-y="-4"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="Calque_1" /><rect
+ fill="none"
+ width="32"
+ height="32"
+ id="rect3" /><linearGradient
+ id="SVGID_1_"
+ gradientUnits="userSpaceOnUse"
+ x1="579"
+ y1="810"
+ x2="579"
+ y2="841.7578"
+ gradientTransform="matrix(1 0 0 1 -563 -809)"><stop
+ offset="0"
+ style="stop-color:#999999"
+ id="stop6" /><stop
+ offset="0.3"
+ style="stop-color:#FFFFFF"
+ id="stop8" /><stop
+ offset="0.5216"
+ style="stop-color:#FCFCFC"
+ id="stop10" /><stop
+ offset="0.6995"
+ style="stop-color:#F2F2F2"
+ id="stop12" /><stop
+ offset="0.8618"
+ style="stop-color:#E1E1E1"
+ id="stop14" /><stop
+ offset="1"
+ style="stop-color:#CCCCCC"
+ id="stop16" /></linearGradient><path
+ fill="url(#SVGID_1_)"
+ d="M16,2C7.716,2,1,8.716,1,17v14.986h14.371C15.58,31.994,15.789,32,16,32c8.284,0,15-6.716,15-15 S24.284,2,16,2L16,2z"
+ id="path18" /><path
+ fill="#000000"
+ d="M16,5.482C9.639,5.482,4.482,10.639,4.482,17v6.865v4.641v3.48h4.609V17.012 c0-3.803,3.082-6.887,6.886-6.887s6.886,3.084,6.886,6.887c0,3.805-3.082,6.887-6.886,6.887c-1.391,0-2.685-0.414-3.768-1.121v4.949 c0.725,0.446,2.381,0.762,4.323,0.778C22.646,28.227,27.52,23.182,27.52,17C27.52,10.639,22.361,5.482,16,5.482L16,5.482z"
+ id="path20" /><path
+ fill="#FFFFFF"
+ d="M15.518,28.506c0.159,0.008,0.32,0.014,0.482,0.014c0.17,0,0.339-0.006,0.508-0.014H15.518z"
+ id="path22" /></svg> \ No newline at end of file
diff --git a/resources/pe_light/scalable/refresh.svg b/resources/pe_light/scalable/refresh.svg
new file mode 100644
index 00000000..8d25ea90
--- /dev/null
+++ b/resources/pe_light/scalable/refresh.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="579" y1="810.373" x2="579" y2="839.4073" gradientTransform="matrix(1 0 0 1 -563 -809)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M31.825,17.41C31.473,16.559,30.639,16,29.714,16h-2.286c0-7.573-6.141-13.713-13.714-13.713
+ C6.141,2.287,0,8.427,0,16c0,7.574,6.141,13.714,13.713,13.714c2.482,0,4.803-0.669,6.809-1.821l-3.446-3.406
+ c-1.042,0.412-2.17,0.656-3.361,0.656c-5.049,0-9.143-4.094-9.143-9.143s4.094-9.143,9.143-9.143c5.052,0,9.146,4.094,9.146,9.143
+ H20.57c-0.924,0-1.758,0.559-2.111,1.41c-0.352,0.855-0.158,1.838,0.496,2.492l4.57,4.57c0.447,0.445,1.031,0.67,1.617,0.67
+ c0.584,0,1.17-0.225,1.613-0.67l4.572-4.57C31.984,19.248,32.18,18.266,31.825,17.41z"/>
+</svg>
diff --git a/resources/pe_light/scalable/settings.svg b/resources/pe_light/scalable/settings.svg
new file mode 100644
index 00000000..caa4e75f
--- /dev/null
+++ b/resources/pe_light/scalable/settings.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<g>
+ <g>
+ <g>
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="15.9995" y1="0" x2="15.9995" y2="31.7588">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+ </linearGradient>
+ <path fill="url(#SVGID_1_)" d="M29.125,14.125H27.25c-0.066,0-0.121,0.031-0.186,0.038c-0.285-1.73-0.953-3.323-1.936-4.691
+ c0.048-0.04,0.105-0.056,0.148-0.101l1.326-1.325c0.731-0.732,0.731-1.92,0-2.652c-0.73-0.732-1.92-0.732-2.65,0L22.63,6.719
+ c-0.044,0.044-0.062,0.103-0.101,0.15c-1.369-0.981-2.961-1.649-4.692-1.935c0.007-0.062,0.038-0.118,0.038-0.184V2.875
+ C17.875,1.839,17.036,1,16,1s-1.875,0.839-1.875,1.875V4.75c0,0.066,0.031,0.122,0.038,0.185
+ c-1.732,0.286-3.324,0.953-4.692,1.935C9.431,6.822,9.414,6.764,9.37,6.72L8.045,5.394c-0.731-0.732-1.919-0.732-2.651,0
+ s-0.732,1.92,0,2.652L6.72,9.371c0.044,0.045,0.103,0.061,0.149,0.101c-0.98,1.368-1.648,2.961-1.934,4.691
+ c-0.063-0.007-0.119-0.038-0.185-0.038H2.875C1.839,14.125,1,14.964,1,16s0.839,1.875,1.875,1.875H4.75
+ c0.066,0,0.122-0.029,0.185-0.038c0.286,1.731,0.953,3.324,1.934,4.692c-0.047,0.039-0.105,0.057-0.149,0.101l-1.326,1.325
+ c-0.732,0.732-0.732,1.92,0,2.65c0.732,0.731,1.92,0.731,2.651,0l1.326-1.324c0.044-0.045,0.061-0.104,0.101-0.15
+ c1.368,0.98,2.96,1.648,4.691,1.934c-0.006,0.064-0.038,0.119-0.038,0.188v1.875C14.125,30.161,14.964,31,16,31
+ s1.875-0.839,1.875-1.875V27.25c0-0.066-0.031-0.121-0.038-0.186c1.731-0.285,3.323-0.953,4.692-1.936
+ c0.039,0.048,0.057,0.105,0.101,0.148l1.325,1.326c0.73,0.731,1.92,0.731,2.65,0c0.731-0.73,0.731-1.92,0-2.65l-1.324-1.325
+ c-0.045-0.044-0.104-0.062-0.15-0.101c0.98-1.368,1.648-2.961,1.934-4.692c0.064,0.008,0.119,0.038,0.188,0.038h1.875
+ C30.161,17.875,31,17.036,31,16S30.161,14.125,29.125,14.125z M16,21.625c-3.106,0-5.625-2.518-5.625-5.625
+ c0-3.106,2.519-5.625,5.625-5.625c3.105,0,5.625,2.519,5.625,5.625C21.625,19.107,19.105,21.625,16,21.625z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+
+ <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="579" y1="809" x2="579" y2="840.7583" gradientTransform="matrix(1 0 0 1 -563 -809)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+ </linearGradient>
+ <path fill="url(#SVGID_2_)" d="M29.125,14.125H27.25c-0.066,0-0.121,0.031-0.186,0.038c-0.285-1.73-0.953-3.323-1.936-4.691
+ c0.048-0.04,0.105-0.056,0.148-0.101l1.326-1.325c0.731-0.732,0.731-1.92,0-2.652c-0.73-0.732-1.92-0.732-2.65,0L22.63,6.719
+ c-0.044,0.044-0.062,0.103-0.101,0.15c-1.369-0.981-2.961-1.649-4.692-1.935c0.007-0.062,0.038-0.118,0.038-0.184V2.875
+ C17.875,1.839,17.036,1,16,1s-1.875,0.839-1.875,1.875V4.75c0,0.066,0.031,0.122,0.038,0.185
+ c-1.732,0.286-3.324,0.953-4.692,1.935C9.431,6.822,9.414,6.764,9.37,6.72L8.045,5.394c-0.731-0.732-1.919-0.732-2.651,0
+ s-0.732,1.92,0,2.652L6.72,9.371c0.044,0.045,0.103,0.061,0.149,0.101c-0.98,1.368-1.648,2.961-1.934,4.691
+ c-0.063-0.007-0.119-0.038-0.185-0.038H2.875C1.839,14.125,1,14.964,1,16s0.839,1.875,1.875,1.875H4.75
+ c0.066,0,0.122-0.029,0.185-0.038c0.286,1.731,0.953,3.324,1.934,4.692c-0.047,0.039-0.105,0.057-0.149,0.101l-1.326,1.325
+ c-0.732,0.732-0.732,1.92,0,2.65c0.732,0.731,1.92,0.731,2.651,0l1.326-1.324c0.044-0.045,0.061-0.104,0.101-0.15
+ c1.368,0.98,2.96,1.648,4.691,1.934c-0.006,0.064-0.038,0.119-0.038,0.188v1.875C14.125,30.161,14.964,31,16,31
+ s1.875-0.839,1.875-1.875V27.25c0-0.066-0.031-0.121-0.038-0.186c1.731-0.285,3.323-0.953,4.692-1.936
+ c0.039,0.048,0.057,0.105,0.101,0.148l1.325,1.326c0.73,0.731,1.92,0.731,2.65,0c0.731-0.73,0.731-1.92,0-2.65l-1.324-1.325
+ c-0.045-0.044-0.104-0.062-0.15-0.101c0.98-1.368,1.648-2.961,1.934-4.692c0.064,0.008,0.119,0.038,0.188,0.038h1.875
+ C30.161,17.875,31,17.036,31,16S30.161,14.125,29.125,14.125z M16,21.625c-3.106,0-5.625-2.518-5.625-5.625
+ c0-3.106,2.519-5.625,5.625-5.625c3.105,0,5.625,2.519,5.625,5.625C21.625,19.107,19.105,21.625,16,21.625z"/>
+ </g>
+ </g>
+</g>
+</svg>
diff --git a/resources/pe_light/scalable/viewfolder.svg b/resources/pe_light/scalable/viewfolder.svg
new file mode 100644
index 00000000..3161f61f
--- /dev/null
+++ b/resources/pe_light/scalable/viewfolder.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
+<rect fill="none" width="32" height="32"/>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="579" y1="809" x2="579" y2="840.7583" gradientTransform="matrix(1 0 0 1 -563 -809)">
+ <stop offset="0" style="stop-color:#999999"/>
+ <stop offset="0.3" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5216" style="stop-color:#FCFCFC"/>
+ <stop offset="0.6995" style="stop-color:#F2F2F2"/>
+ <stop offset="0.8618" style="stop-color:#E1E1E1"/>
+ <stop offset="1" style="stop-color:#CCCCCC"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M25.375,31H6.625C3.519,31,1,28.48,1,25.375V6.625C1,3.519,3.519,1,6.625,1h7.5
+ c2.445,0,4.506,1.57,5.281,3.75h5.969C28.48,4.75,31,7.269,31,10.375v15C31,28.48,28.48,31,25.375,31z M27.25,10.375
+ c0-1.036-0.839-1.875-1.875-1.875H16V6.625c0-1.036-0.839-1.875-1.875-1.875h-7.5c-1.036,0-1.875,0.839-1.875,1.875v18.75
+ c0,1.036,0.839,1.875,1.875,1.875h18.75c1.036,0,1.875-0.839,1.875-1.875V10.375z"/>
+</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>
diff --git a/tests/TestUtil.h b/tests/TestUtil.h
index e9099b15..e7017743 100644
--- a/tests/TestUtil.h
+++ b/tests/TestUtil.h
@@ -39,9 +39,9 @@ struct TestsInternal
#define QTEST_GUILESS_MAIN_MULTIMC(TestObject) \
int main(int argc, char *argv[]) \
{ \
- char *argv_[] = { argv[0] _MMC_EXTRA_ARGV }; \
+ const char *argv_[] = { argv[0] _MMC_EXTRA_ARGV }; \
int argc_ = 1 + _MMC_EXTRA_ARGC; \
- MultiMC app(argc_, argv_, true); \
+ MultiMC app(argc_, const_cast<char**>(argv_), true); \
app.setAttribute(Qt::AA_Use96Dpi, true); \
TestObject tc; \
return QTest::qExec(&tc, argc, argv); \
diff --git a/tests/tst_UpdateChecker.cpp b/tests/tst_UpdateChecker.cpp
index ae6fc193..8351ad75 100644
--- a/tests/tst_UpdateChecker.cpp
+++ b/tests/tst_UpdateChecker.cpp
@@ -1,8 +1,8 @@
#include <QTest>
#include <QSignalSpy>
-#include "depends/settings/settingsobject.h"
-#include "depends/settings/setting.h"
+#include "logic/settings/SettingsObject.h"
+#include "logic/settings/Setting.h"
#include "BuildConfig.h"
#include "TestUtil.h"
diff --git a/tests/tst_inifile.cpp b/tests/tst_inifile.cpp
index c0f57c12..93930ae9 100644
--- a/tests/tst_inifile.cpp
+++ b/tests/tst_inifile.cpp
@@ -1,7 +1,7 @@
#include <QTest>
#include "TestUtil.h"
-#include "depends/settings/inifile.h"
+#include "logic/settings/INIFile.h"
class IniFileTest : public QObject
{